1<?php 2/** 3 * EGroupware Addressbook - admin, preferences and sidebox-menus and other hooks 4 * 5 * @link http://www.egroupware.org 6 * @package addressbook 7 * @author Ralf Becker <RalfBecker@outdoor-training.de> 8 * @copyright (c) 2006-16 by Ralf Becker <RalfBecker@outdoor-training.de> 9 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License 10 * @version $Id$ 11 */ 12 13use EGroupware\Api; 14use EGroupware\Api\Link; 15use EGroupware\Api\Framework; 16use EGroupware\Api\Egw; 17use EGroupware\Api\Acl; 18 19/** 20 * Class containing admin, preferences and sidebox-menus and other hooks 21 */ 22class addressbook_hooks 23{ 24 /** 25 * hooks to build projectmanager's sidebox-menu plus the admin and preferences sections 26 * 27 * @param string/array $args hook args 28 */ 29 static function all_hooks($args) 30 { 31 $appname = 'addressbook'; 32 $location = is_array($args) ? $args['location'] : $args; 33 //echo "<p>contacts_admin_prefs::all_hooks(".print_r($args,True).") appname='$appname', location='$location'</p>\n"; 34 35 if ($location == 'sidebox_menu') 36 { 37 if ($_GET['menuaction'] == 'addressbook.addressbook_ui.view') 38 { 39 display_sidebox($appname, lang('Contact data'), array( 40 array( 41 'text' => '<div id="'.self::getViewDOMID($_GET['contact_id'], $_GET['crm_list']).'" class="addressbook_view_sidebox"/>', 42 'no_lang' => true, 43 'link' => false, 44 'icon' => false, 45 ), 46 'menuOpened' => true, // display it open by default 47 )); 48 } 49 // Magic etemplate2 favorites menu (from nextmatch widget) 50 display_sidebox($appname, lang('Favorites'), Framework\Favorites::list_favorites('addressbook')); 51 52 $file = array( 53 'Addressbook list' => Egw::link('/index.php',array( 54 'menuaction' => 'addressbook.addressbook_ui.index', 55 'ajax' => 'true')), 56 array( 57 'text' => lang('Add %1',lang(Link::get_registry($appname, 'entry'))), 58 'no_lang' => true, 59 'link' => "javascript:egw.open('','$appname','add')" 60 ), 61 'Advanced search' => "javascript:egw_openWindowCentered2('". 62 Egw::link('/index.php',array('menuaction' => 'addressbook.addressbook_ui.search'),false). 63 "','_blank',870,610,'yes')", 64 'Placeholders' => Egw::link('/index.php','menuaction=api.EGroupware\\Api\\Contacts\\Merge.show_replacements') 65 ); 66 display_sidebox($appname,lang('Addressbook menu'),$file); 67 } 68 69 if ($GLOBALS['egw_info']['user']['apps']['admin'] && $location != 'preferences') 70 { 71 $file = Array( 72 'Site configuration' => Egw::link('/index.php',array( 73 'menuaction' => 'admin.uiconfig.index', 74 'appname' => $appname, 75 'ajax' => 'true', 76 )), 77 'Global Categories' => Egw::link('/index.php',array( 78 'menuaction' => 'admin.admin_categories.index', 79 'appname' => $appname, 80 'global_cats'=> True, 81 'ajax' => 'true', 82 )), 83 ); 84 // custom fields are not availible in LDAP 85 if ($GLOBALS['egw_info']['server']['contact_repository'] != 'ldap') 86 { 87 $file['Custom fields'] = Egw::link('/index.php',array( 88 'menuaction' => 'admin.admin_customfields.index', 89 'appname' => $appname, 90 'use_private'=> 1, 91 'ajax' => 'true' 92 )); 93 } 94 if ($location == 'admin') 95 { 96 display_section($appname,$file); 97 } 98 else 99 { 100 display_sidebox($appname,lang('Admin'),$file); 101 } 102 } 103 } 104 105 /** 106 * Generate unique Id for addressbook view sidebox 107 * @param $contact_id 108 * @param $view 109 * @return string 110 */ 111 static function getViewDOMID($contact_id, $view) 112 { 113 return 'addressbook_'.$contact_id.'_'.$view.'_view_sidebox'; 114 } 115 116 /** 117 * populates $settings for the Api\Preferences 118 * 119 * @param array|string $hook_data 120 * @return array 121 */ 122 static function settings($hook_data) 123 { 124 $settings = array( 125 array( 126 'type' => 'section', 127 'title' => lang('General settings'), 128 'no_lang'=> true, 129 'xmlrpc' => False, 130 'admin' => False 131 ), 132 ); 133 $settings['add_default'] = array( 134 'type' => 'select', 135 'label' => 'Default addressbook for adding contacts', 136 'name' => 'add_default', 137 'help' => 'Which addressbook should be selected when adding a contact AND you have no add rights to the current addressbook.', 138 'values' => !$hook_data['setup'] ? ExecMethod('addressbook.addressbook_ui.get_addressbooks',Acl::ADD) : array(), 139 'xmlrpc' => True, 140 'admin' => False, 141 ); 142 if ($GLOBALS['egw_info']['server']['contact_repository'] != 'ldap') 143 { 144 $settings['private_addressbook'] = array( 145 'type' => 'check', 146 'label' => 'Enable an extra private addressbook', 147 'name' => 'private_addressbook', 148 'help' => 'Do you want a private addressbook, which can not be viewed by users, you grant access to your personal addressbook?', 149 'xmlrpc' => True, 150 'admin' => False, 151 'forced' => false, 152 ); 153 } 154 $settings['hide_accounts'] = array( 155 'type' => 'select', 156 'values' => array('1' => lang('Hide all accounts'), '0' => lang('Show active accounts'), 'none' => lang('Show all accounts')), 157 'label' => 'Hide accounts from addressbook', 158 'name' => 'hide_accounts', 159 'help' => 'Hides accounts completly from the adressbook.', 160 'xmlrpc' => True, 161 'admin' => false, 162 'default'=> '0' 163 ); 164 $settings['hide_groups_as_lists'] = array( 165 'type' => 'check', 166 'label' => 'Hide user groups as distribution lists', 167 'name' => 'hide_groups_as_lists', 168 'help' => 'User groups are automatically shown as distribution lists.', 169 'xmlrpc' => False, 170 'admin' => false, 171 'default'=> '0' 172 ); 173 $contacts = new Api\Contacts(); 174 $fileas_options = $contacts->fileas_options(); 175 foreach(Api\Contacts\Storage::$duplicate_fields as $key => $label) 176 { 177 $duplicate_options[$key] = lang($label); 178 } 179 180 $settings['link_title'] = array( 181 'type' => 'select', 182 'label' => 'Link title for contacts show', 183 'name' => 'link_title', 184 'values' => array( 185 'n_fileas' => lang('own sorting').' ('.lang('default').': '.lang('Company').': '.lang('lastname').', '.lang('firstname').')', 186 )+$fileas_options, // plus all fileas types 187 'help' => 'What should links to the addressbook display in other applications. Empty values will be left out. You need to log in anew, if you change this setting!', 188 'xmlrpc' => True, 189 'admin' => false, 190 'default'=> 'org_name: n_family, n_given', 191 ); 192 if (($cf_opts = Api\Contacts::cf_options())) 193 { 194 $settings['link_title_cf'] = array( 195 'type' => 'multiselect', 196 'label' => 'Add a customfield to link title', 197 'name' => 'link_title_cf', 198 'values' => $cf_opts, 199 'help' => 'Add customfield to links of addressbook, which displays in other applications. The default value is none customfield.', 200 'xmlrpc' => True, 201 'admin' => false, 202 ); 203 } 204 $settings['addr_format'] = array( 205 'type' => 'select', 206 'label' => 'Default address format', 207 'name' => 'addr_format', 208 'values' => array( 209 'postcode_city' => lang('zip code').' '.lang('City'), 210 'city_state_postcode' => lang('City').' '.lang('State').' '.lang('zip code'), 211 ), 212 'help' => 'Which address format should the addressbook use for countries it does not know the address format. If the address format of a country is known, it uses it independent of this setting.', 213 'xmlrpc' => True, 214 'admin' => false, 215 'default'=> 'postcode_city', 216 ); 217 $settings['fileas_default'] = array( 218 'type' => 'select', 219 'label' => 'Default file as format', 220 'name' => 'fileas_default', 221 'values' => $fileas_options, 222 'help' => 'Default format for fileas, eg. for new entries.', 223 'xmlrpc' => True, 224 'admin' => false, 225 'default'=> 'org_name: n_family, n_given', 226 ); 227 $settings['duplicate_fields'] = array( 228 'type' => 'multiselect', 229 'label' => 'Fields to check for duplicates', 230 'name' => 'duplicate_fields', 231 'values' => $duplicate_options, 232 'help' => 'Fields to consider when looking for duplicate contacts.', 233 'admin' => false, 234 'default' => 'n_family,org_name,contact_email' 235 ); 236 $settings['duplicate_threshold'] = array( 237 'type' => 'input', 238 'size' => 5, 239 'label' => 'Duplicate threshold', 240 'name' => 'duplicate_threshold', 241 'help' => 'How many fields must match for the record to be considered a duplicate.', 242 'xmlrpc' => True, 243 'default'=> 3, 244 'admin' => False 245 ); 246 247 $crm_list_options = array( 248 '~edit~' => lang('Edit contact'), 249 'infolog' => lang('Open %1 CRM view', lang('infolog')), 250 'infolog-organisation' => lang('infolog-organisation'), 251 ); 252 if($GLOBALS['egw_info']['user']['apps']['tracker']) 253 { 254 $crm_list_options['tracker'] = lang('Open %1 CRM view', lang('tracker')); 255 } 256 $settings['crm_list'] = array( 257 'type' => 'select', 258 'label' => 'Default action on double-click', 259 'name' => 'crm_list', 260 'values' => $crm_list_options, 261 'help' => 'When viewing a contact, show linked entries from the selected application', 262 'xmlrpc' => True, 263 'admin' => false, 264 'default'=> 'infolog', 265 ); 266 267 $settings['geolocation_src'] = array( 268 'type' => 'select', 269 'label' => 'Default GeoLocation source address', 270 'name' => 'geolocation_src', 271 'values' => array( 272 'browser' => lang('Browser location'), 273 'one' => lang('Business address'), 274 'two' => lang('Private address') 275 ), 276 'help' => 'Select a source address to be used in GeoLocation routing system', 277 'xmlrpc' => True, 278 'admin' => false, 279 'default'=> 'browser', 280 ); 281 282 $settings[] = array( 283 'type' => 'section', 284 'title' => lang('Data exchange settings'), 285 'no_lang'=> true, 286 'xmlrpc' => False, 287 'admin' => False 288 ); 289 // CSV Export 290 291 if ($GLOBALS['egw_info']['user']['apps']['filemanager']) 292 { 293 $settings['default_document'] = array( 294 'type' => 'vfs_file', 295 'size' => 60, 296 'label' => 'Default document to insert contacts', 297 'name' => 'default_document', 298 'help' => lang('If you specify a document (full vfs path) here, %1 displays an extra document icon for each entry. That icon allows to download the specified document with the data inserted.', lang('addressbook')).' '. 299 lang('The document can contain placeholder like {{%1}}, to be replaced with the data.','n_fn').' '. 300 lang('The following document-types are supported:'). implode(',',Api\Storage\Merge::get_file_extensions()), 301 'run_lang' => false, 302 'xmlrpc' => True, 303 'admin' => False, 304 ); 305 $settings['document_dir'] = array( 306 'type' => 'vfs_dirs', 307 'size' => 60, 308 'label' => 'Directory with documents to insert contacts', 309 'name' => 'document_dir', 310 'help' => lang('If you specify a directory (full vfs path) here, %1 displays an action for each document. That action allows to download the specified document with the data inserted.',lang('addressbook')).' '. 311 lang('The document can contain placeholder like {{%1}}, to be replaced with the data.','n_fn').' '. 312 lang('The following document-types are supported:'). implode(',',Api\Storage\Merge::get_file_extensions()), 313 'run_lang' => false, 314 'xmlrpc' => True, 315 'admin' => False, 316 'default' => '/templates/addressbook', 317 ); 318 } 319 320 if ($GLOBALS['egw_info']['user']['apps']['felamimail'] || $GLOBALS['egw_info']['user']['apps']['mail']) 321 { 322 $settings['force_mailto'] = array( 323 'type' => 'check', 324 'label' => 'Open EMail addresses in external mail program', 325 'name' => 'force_mailto', 326 'help' => 'Default is to open EMail addresses in EGroupware EMail application, if user has access to it.', 327 'xmlrpc' => True, 328 'admin' => False, 329 'default'=> false, 330 ); 331 } 332 333 // Import / Export for nextmatch 334 if ($GLOBALS['egw_info']['user']['apps']['importexport']) 335 { 336 $settings['vcard_charset'] = array( 337 'type' => 'select', 338 'label' => 'Charset for the vCard import and export', 339 'name' => 'vcard_charset', 340 'values' => Api\Translation::get_installed_charsets(), 341 'help' => 'Which charset should be used for the vCard import and export.', 342 'xmlrpc' => True, 343 'admin' => false, 344 'default'=> 'utf-8', 345 ); 346 } 347 return $settings; 348 } 349 350 /** 351 * Hook called by link-class to include calendar in the appregistry of the linkage 352 * 353 * @param array/string $location location and other parameters (not used) 354 * @return array with method-names 355 */ 356 static function search_link($location) 357 { 358 unset($location); // not used, but required by function signature 359 360 $links = array( 361 'query' => 'api.EGroupware\\Api\\Contacts.link_query', 362 'title' => 'api.EGroupware\\Api\\Contacts.link_title', 363 'titles' => 'api.EGroupware\\Api\\Contacts.link_titles', 364 'view' => array( 365 'menuaction' => 'addressbook.addressbook_ui.view', 366 'ajax' => 'true', 367 'target' => 'tab', 368 'crm_list' => 'infolog' 369 ), 370 'view_id' => 'contact_id', 371 'list' => array( 372 'menuaction' => 'addressbook.addressbook_ui.index', 373 'ajax' => 'true' 374 ), 375 'edit' => array( 376 'menuaction' => 'addressbook.addressbook_ui.edit' 377 ), 378 'edit_id' => 'contact_id', 379 'edit_popup' => '870x610', 380 'add' => array( 381 'menuaction' => 'addressbook.addressbook_ui.edit' 382 ), 383 'add_app' => 'link_app', 384 'add_id' => 'link_id', 385 'add_popup' => '870x610', 386 'file_access_user' => true, // file_access supports 4th parameter $user 387 'file_access'=> 'api.EGroupware\\Api\\Contacts.file_access', 388 'default_types' => array('n' => array('name' => 'contact', 'options' => array('icon' => 'navbar.png','template' => 'addressbook.edit'))), 389 // registers an addtional type 'addressbook-email', returning only contacts with email, title has email appended 390 'additional' => array( 391 'addressbook-email' => array( 392 'query' => 'api.EGroupware\\Api\\Contacts.link_query_email', 393 'view' => array( 394 'menuaction' => 'addressbook.addressbook_ui.view', 395 'ajax' => 'true' 396 ), 397 'view_id' => 'contact_id', 398 ), 399 ), 400 'merge' => true, 401 'entry' => 'Contact', 402 'entries' => 'Contacts', 403 'modification_time' => array( 404 'key' => 'contact_id', 405 'column' => 'egw_addressbook.contact_modified', 406 'type' => 'int' 407 ), 408 'owner' => array( 409 'key' => 'egw_addressbook.contact_id', 410 'column' => 'egw_addressbook.contact_owner' 411 ) 412 ); 413 return $links; 414 } 415 416 /** 417 * Hook called to retrieve a app specific exportLimit 418 * 419 * @param array/string $location location and other parameters (not used) 420 * @return the export_limit to be applied for the app, may be empty, int or string 421 */ 422 static function getAppExportLimit($location) 423 { 424 unset($location); // not used, but required by function signature 425 426 return $GLOBALS['egw_info']['server']['contact_export_limit']; 427 } 428 429 /** 430 * Register contacts as calendar resources (items which can be sheduled by the calendar) 431 * 432 * @param array $args hook-params (not used) 433 * @return array 434 */ 435 static function calendar_resources($args) 436 { 437 unset($args); // not used, but required by function signature 438 439 return array( 440 'type' => 'c',// one char type-identifiy for this resources 441 'info' => 'api.EGroupware\\Api\\Contacts.calendar_info',// info method, returns array with id, type & name for a given id 442 ); 443 } 444 445 /** 446 * Register addressbook for group-acl 447 * 448 * @param array $args hook-params (not used) 449 * @return boolean|string true=standard group acl link, of string with link 450 */ 451 static function group_acl($args) 452 { 453 unset($args); // not used, but required by function signature 454 455 // addressbook uses group-acl, only if contacts-backend is NOT LDAP, as the ACL can not be modified there 456 return $GLOBALS['egw_info']['server']['contact_repository'] != 'ldap'; 457 } 458 459 /** 460 * For which groups should no group acl be used: addressbook always 461 * 462 * @param string|array $data 463 * @return boolean|array true, false or array with group-account_id's 464 */ 465 static function not_enum_group_acls($data) 466 { 467 unset($data); // not used, but required by function signature 468 469 return true; 470 } 471 472 /** 473 * ACL rights and labels used 474 * 475 * @param string|array string with location or array with parameters incl. "location", specially "owner" for selected Acl owner 476 * @return array Acl::(READ|ADD|EDIT|DELETE|PRIVAT|CUSTOM(1|2|3)) => $label pairs 477 */ 478 public static function acl_rights($params) 479 { 480 unset($params); // not used, but required by function signature 481 482 return array( 483 Acl::READ => 'read', 484 Acl::EDIT => 'edit', 485 Acl::ADD => 'add', 486 Acl::DELETE => 'delete', 487 Acl::CUSTOM1 => 'shared with', // allows to share into given AB 488 ); 489 } 490 491 /** 492 * Hook to tell framework we use standard categories method 493 * 494 * @param string|array $data hook-data or location 495 * @return boolean 496 */ 497 public static function categories($data) 498 { 499 unset($data); // not used, but required by function signature 500 501 return true; 502 } 503 504 /** 505 * Called before displaying site configuration 506 * 507 * @param array $config 508 * @return array with additional config to merge and "sel_options" values 509 */ 510 public static function config(array $config) 511 { 512 $bocontacts = new Api\Contacts(); 513 514 // get the list of account fields 515 $own_account_acl = array(); 516 foreach($bocontacts->contact_fields as $field => $label) 517 { 518 // some fields the user should never be allowed to edit or are covert by an other attribute (n_fn for all n_*) 519 if (!in_array($field,array('id','tid','owner','created','creator','modified','modifier','private','n_prefix','n_given','n_middle','n_family','n_suffix'))) 520 { 521 $own_account_acl[$field] = $label; 522 } 523 } 524 $own_account_acl['link_to'] = 'Links'; 525 if ($config['account_repository'] != 'ldap') // no custom-fields in ldap 526 { 527 foreach(Api\Storage\Customfields::get('addressbook') as $name => $data) 528 { 529 $own_account_acl['#'.$name] = $data['label']; 530 } 531 } 532 533 $org_fields = $own_account_acl; 534 unset($org_fields['n_fn'], $org_fields['account_id']); 535 // Remove country codes as an option, it will be added by BO constructor 536 unset($org_fields['adr_one_countrycode'], $org_fields['adr_two_countrycode']); 537 538 $supported_fields = $bocontacts->get_fields('supported',null,0); // fields supported by the backend (ldap schemas!) 539 // get the list of account fields 540 $copy_fields = array(); 541 foreach($bocontacts->contact_fields as $field => $label) 542 { 543 // some fields the user should never be allowed to copy or are coverted by an other attribute (n_fn for all n_*) 544 if (!in_array($field,array('id','tid','created','creator','modified','modifier','account_id','uid','etag','n_fn'))) 545 { 546 $copy_fields[$field] = $label; 547 } 548 } 549 if ($config['contact_repository'] != 'ldap') // no custom-fields in ldap 550 { 551 foreach(Api\Storage\Customfields::get('addressbook') as $name => $data) 552 { 553 $copy_fields['#'.$name] = $data['label']; 554 } 555 } 556 // Remove country codes as an option, it will be added by UI constructor 557 if(in_array('adr_one_countrycode', $supported_fields)) 558 { 559 unset($copy_fields['adr_one_countrycode'], $copy_fields['adr_two_countrycode']); 560 } 561 562 $repositories = array('sql' => 'SQL'); 563 // check account-repository, contact-repository LDAP is only availible for account-repository == ldap 564 if ($config['account_repository'] == 'ldap' || !$config['account_repository'] && $config['auth_type'] == 'ldap') 565 { 566 $repositories['ldap'] = 'LDAP'; 567 $repositories['sql-ldap'] = 'SQL --> LDAP ('.lang('read only').')'; 568 } 569 // geolocation pre-defined maps 570 $geoLocation = array( 571 array('value' => 'https://maps.here.com/directions/drive{{%rs=/%rs}}%r0,%t0,%z0,%c0{{%d=/%d}}%r1,%t1,%z1+%c1', 'label' => 'Here Maps'), 572 array('value' => 'http://maps.google.com/{{%rs=?saddr=%rs}}%r0+%t0+%z0+%c0{{%d=&daddr=%d}}%r1+%t1+%z1+%c1', 'label' => 'Google Maps'), 573 array('value' => 'https://www.bing.com/maps/{{%rs=?rtp=adr.%rs}}%r0+%t0+%z0+%c0{{%d=~adr.%d}}%r1+%t1+%z1+%c1', 'label' => 'Bing Maps') 574 ); 575 $ret = array( 576 'sel_options' => array( 577 'own_account_acl' => $own_account_acl, 578 'org_fileds_to_update' => $org_fields, // typo has to stay, as it was there allways and we would loose existing config :( 579 'copy_fields' => $copy_fields, 580 'fileas' => $bocontacts->fileas_options(), 581 'contact_repository' => $repositories, 582 'geolocation_url' => $geoLocation, 583 ) 584 ); 585 foreach(Api\Storage\Customfields::get('addressbook') as $tid => $data) 586 { 587 $ret['sel_options']['index_load_cfs'][$tid] = $data['name']; 588 } 589 590 if (empty($config['geolocation_url'])) $ret ['geolocation_url'] = $geoLocation[0]['value']; 591 return $ret; 592 } 593 594 595 /** 596 * get actions 597 * 598 * @return array return an array of actions 599 */ 600 public static function status_get_actions() 601 { 602 $config = Api\Config::read('stylite'); 603 604 return [ 605 'addressbook_phonecall' => [ 606 'caption' => 'Phone Call', 607 'icon' => 'call', 608 'group' => 2, 609 'enabled' => !empty($config['pbx_type']) && !empty($config['pbx_api_key']), 610 'disableIfNoEPL' => !$GLOBALS['egw_info']['apps']['stylite'], 611 'children' => [ 612 'addressbook_tel_work' => [ 613 'caption' => lang('Business phone'), 614 'icon' => 'phone', 615 'onExecute' => 'javaScript:app.status.phoneCall', 616 'enabled' => 'javaScript:app.status.phoneIsAvailable' 617 ], 618 'addressbook_tel_cell' => [ 619 'caption' => lang('Mobile phone'), 620 'icon' => 'personal', 621 'onExecute' => 'javaScript:app.status.phoneCall', 622 'enabled' => 'javaScript:app.status.phoneIsAvailable' 623 ], 624 'addressbook_tel_home' => [ 625 'caption' => lang('Home phone'), 626 'icon' => 'home', 627 'onExecute' => 'javaScript:app.status.phoneCall', 628 'enabled' => 'javaScript:app.status.phoneIsAvailable' 629 ], 630 'addressbook_tel_prefer' => [ 631 'caption' => lang('Favorite phone'), 632 'icon' => 'fav_filter', 633 'onExecute' => 'javaScript:app.status.phoneCall', 634 'enabled' => 'javaScript:app.status.phoneIsAvailable' 635 ] 636 ] 637 ] 638 ]; 639 } 640} 641