| +-----------------------------------------------------------------------+ */ class rcmail_action_contacts_index extends rcmail_action { public static $aliases = [ 'add' => 'edit', ]; protected static $SEARCH_MODS_DEFAULT = [ 'name' => 1, 'firstname' => 1, 'surname' => 1, 'email' => 1, '*' => 1, ]; /** * General definition of contact coltypes */ public static $CONTACT_COLTYPES = [ 'name' => [ 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => 'name', 'category' => 'main' ], 'firstname' => [ 'size' => 19, 'maxlength' => 50, 'limit' => 1, 'label' => 'firstname', 'category' => 'main' ], 'surname' => [ 'size' => 19, 'maxlength' => 50, 'limit' => 1, 'label' => 'surname', 'category' => 'main' ], 'email' => [ 'size' => 40, 'maxlength' => 254, 'label' => 'email', 'subtypes' => ['home', 'work', 'other'], 'category' => 'main' ], 'middlename' => [ 'size' => 19, 'maxlength' => 50, 'limit' => 1, 'label' => 'middlename', 'category' => 'main' ], 'prefix' => [ 'size' => 8, 'maxlength' => 20, 'limit' => 1, 'label' => 'nameprefix', 'category' => 'main' ], 'suffix' => [ 'size' => 8, 'maxlength' => 20, 'limit' => 1, 'label' => 'namesuffix', 'category' => 'main' ], 'nickname' => [ 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => 'nickname', 'category' => 'main' ], 'jobtitle' => [ 'size' => 40, 'maxlength' => 128, 'limit' => 1, 'label' => 'jobtitle', 'category' => 'main' ], 'organization' => [ 'size' => 40, 'maxlength' => 128, 'limit' => 1, 'label' => 'organization', 'category' => 'main' ], 'department' => [ 'size' => 40, 'maxlength' => 128, 'limit' => 1, 'label' => 'department', 'category' => 'main' ], 'gender' => [ 'type' => 'select', 'limit' => 1, 'label' => 'gender', 'category' => 'personal', 'options' => [ 'male' => 'male', 'female' => 'female' ], ], 'maidenname' => [ 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => 'maidenname', 'category' => 'personal' ], 'phone' => [ 'size' => 40, 'maxlength' => 20, 'label' => 'phone', 'category' => 'main', 'subtypes' => ['home', 'home2', 'work', 'work2', 'mobile', 'main', 'homefax', 'workfax', 'car', 'pager', 'video', 'assistant', 'other'], ], 'address' => [ 'type' => 'composite', 'label' => 'address', 'subtypes' => ['home', 'work', 'other'], 'category' => 'main', 'childs' => [ 'street' => [ 'label' => 'street', 'size' => 40, 'maxlength' => 50, ], 'locality' => [ 'label' => 'locality', 'size' => 28, 'maxlength' => 50, ], 'zipcode' => [ 'label' => 'zipcode', 'size' => 8, 'maxlength' => 15, ], 'region' => [ 'label' => 'region', 'size' => 12, 'maxlength' => 50, ], 'country' => [ 'label' => 'country', 'size' => 40, 'maxlength' => 50, ], ], ], 'birthday' => [ 'type' => 'date', 'size' => 12, 'maxlength' => 16, 'label' => 'birthday', 'limit' => 1, 'render_func' => 'rcmail_action_contacts_index::format_date_col', 'category' => 'personal' ], 'anniversary' => [ 'type' => 'date', 'size' => 12, 'maxlength' => 16, 'label' => 'anniversary', 'limit' => 1, 'render_func' => 'rcmail_action_contacts_index::format_date_col', 'category' => 'personal' ], 'website' => [ 'size' => 40, 'maxlength' => 128, 'label' => 'website', 'subtypes' => ['homepage', 'work', 'blog', 'profile', 'other'], 'category' => 'main' ], 'im' => [ 'size' => 40, 'maxlength' => 128, 'label' => 'instantmessenger', 'subtypes' => ['aim', 'icq', 'msn', 'yahoo', 'jabber', 'skype', 'other'], 'category' => 'main' ], 'notes' => [ 'type' => 'textarea', 'size' => 40, 'rows' => 15, 'maxlength' => 500, 'label' => 'notes', 'limit' => 1 ], 'photo' => [ 'type' => 'image', 'limit' => 1, 'category' => 'main' ], 'assistant' => [ 'size' => 40, 'maxlength' => 128, 'limit' => 1, 'label' => 'assistant', 'category' => 'personal' ], 'manager' => [ 'size' => 40, 'maxlength' => 128, 'limit' => 1, 'label' => 'manager', 'category' => 'personal' ], 'spouse' => [ 'size' => 40, 'maxlength' => 128, 'limit' => 1, 'label' => 'spouse', 'category' => 'personal' ], ]; protected static $CONTACTS; protected static $SOURCE_ID; protected static $contact; /** * Request handler. * * @param array $args Arguments from the previous step(s) */ public function run($args = []) { $rcmail = rcmail::get_instance(); // Prepare coltypes foreach (self::$CONTACT_COLTYPES as $idx => $val) { if (!empty($val['label'])) { self::$CONTACT_COLTYPES[$idx]['label'] = $rcmail->gettext($val['label']); } if (!empty($val['options'])) { foreach ($val['options'] as $i => $v) { self::$CONTACT_COLTYPES[$idx]['options'][$i] = $rcmail->gettext($v); } } if (!empty($val['childs'])) { foreach ($val['childs'] as $i => $v) { self::$CONTACT_COLTYPES[$idx]['childs'][$i]['label'] = $rcmail->gettext($v['label']); if (empty($v['type'])) { self::$CONTACT_COLTYPES[$idx]['childs'][$i]['type'] = 'text'; } } } if (empty($val['type'])) { self::$CONTACT_COLTYPES[$idx]['type'] = 'text'; } } // Addressbook UI if (!$rcmail->action && !$rcmail->output->ajax_call) { // add list of address sources to client env $js_list = $rcmail->get_address_sources(); // count all/writeable sources $writeable = 0; $count = 0; foreach ($js_list as $sid => $s) { $count++; if (!$s['readonly']) { $writeable++; } // unset hidden sources if (!empty($s['hidden'])) { unset($js_list[$sid]); } } $rcmail->output->set_env('display_next', (bool) $rcmail->config->get('display_next')); $rcmail->output->set_env('search_mods', $rcmail->config->get('addressbook_search_mods', self::$SEARCH_MODS_DEFAULT)); $rcmail->output->set_env('address_sources', $js_list); $rcmail->output->set_env('writable_source', $writeable); $rcmail->output->set_env('contact_move_enabled', $writeable > 1); $rcmail->output->set_env('contact_copy_enabled', $writeable > 1 || ($writeable == 1 && count($js_list) > 1)); $rcmail->output->set_pagetitle($rcmail->gettext('contacts')); $_SESSION['addressbooks_count'] = $count; $_SESSION['addressbooks_count_writeable'] = $writeable; // select address book $source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC); // use first directory by default if (!strlen($source) || !isset($js_list[$source])) { $source = $rcmail->config->get('default_addressbook'); if (!is_string($source) || !strlen($source) || !isset($js_list[$source])) { $source = strval(key($js_list)); } } self::$CONTACTS = self::contact_source($source, true); } // remove undo information... if (!empty($_SESSION['contact_undo'])) { // ...after timeout $undo = $_SESSION['contact_undo']; $undo_time = $rcmail->config->get('undo_timeout', 0); if ($undo['ts'] < time() - $undo_time) { $rcmail->session->remove('contact_undo'); } } // register UI objects $rcmail->output->add_handlers([ 'directorylist' => [$this, 'directory_list'], 'savedsearchlist' => [$this, 'savedsearch_list'], 'addresslist' => [$this, 'contacts_list'], 'addresslisttitle' => [$this, 'contacts_list_title'], 'recordscountdisplay' => [$this, 'rowcount_display'], 'searchform' => [$rcmail->output, 'search_form'] ]); // Disable qr-code if imagick, iconv or BaconQrCode is not installed if (!$rcmail->output->ajax_call && rcmail_action_contacts_qrcode::check_support()) { $rcmail->output->set_env('qrcode', true); $rcmail->output->add_label('qrcode'); } } // instantiate a contacts object according to the given source public static function contact_source($source = null, $init_env = false, $writable = false) { if ($source === null || !strlen((string) $source)) { $source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC); } $rcmail = rcmail::get_instance(); $page_size = $rcmail->config->get('addressbook_pagesize', $rcmail->config->get('pagesize', 50)); // Get object $contacts = $rcmail->get_address_book($source, $writable); if (!$contacts) { return null; } $contacts->set_pagesize($page_size); // set list properties and session vars if (!empty($_GET['_page'])) { $contacts->set_page(($_SESSION['page'] = intval($_GET['_page']))); } else { $contacts->set_page($_SESSION['page'] ?? 1); } if ($group = rcube_utils::get_input_string('_gid', rcube_utils::INPUT_GP)) { $contacts->set_group($group); } if (!$init_env) { return $contacts; } $rcmail->output->set_env('readonly', $contacts->readonly); $rcmail->output->set_env('source', (string) $source); $rcmail->output->set_env('group', $group); // reduce/extend $CONTACT_COLTYPES with specification from the current $CONTACT object if (is_array($contacts->coltypes)) { // remove cols not listed by the backend class $contact_cols = isset($contacts->coltypes[0]) ? array_flip($contacts->coltypes) : $contacts->coltypes; self::$CONTACT_COLTYPES = array_intersect_key(self::$CONTACT_COLTYPES, $contact_cols); // add associative coltypes definition if (empty($contacts->coltypes[0])) { foreach ($contacts->coltypes as $col => $colprop) { if (!empty($colprop['childs'])) { foreach ($colprop['childs'] as $childcol => $childprop) { $colprop['childs'][$childcol] = array_merge((array) self::$CONTACT_COLTYPES[$col]['childs'][$childcol], $childprop); } } if (isset(self::$CONTACT_COLTYPES[$col])) { self::$CONTACT_COLTYPES[$col] = array_merge(self::$CONTACT_COLTYPES[$col], $colprop); } else { self::$CONTACT_COLTYPES[$col] = $colprop; } } } } $rcmail->output->set_env('photocol', !empty(self::$CONTACT_COLTYPES['photo'])); return $contacts; } public static function set_sourcename($abook) { $rcmail = rcmail::get_instance(); // get address book name (for display) if ($abook && !empty($_SESSION['addressbooks_count']) && $_SESSION['addressbooks_count'] > 1) { $name = $abook->get_name(); if (!$name) { $name = $rcmail->gettext('personaladrbook'); } $rcmail->output->set_env('sourcename', html_entity_decode($name, ENT_COMPAT, 'UTF-8')); } } public static function directory_list($attrib) { if (empty($attrib['id'])) { $attrib['id'] = 'rcmdirectorylist'; } $rcmail = rcmail::get_instance(); $out = ''; $jsdata = []; $line_templ = html::tag('li', ['id' => 'rcmli%s', 'class' => '%s', 'noclose' => true], html::a( [ 'href' => '%s', 'rel' => '%s', 'onclick' => "return ".rcmail_output::JS_OBJECT_NAME.".command('list','%s',this)" ], '%s' ) ); $sources = (array) $rcmail->output->get_env('address_sources'); reset($sources); // currently selected source $current = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC); foreach ($sources as $j => $source) { $id = strval(strlen($source['id']) ? $source['id'] : $j); $js_id = rcube::JQ($id); // set class name(s) $class_name = 'addressbook'; if ($current === $id) { $class_name .= ' selected'; } if (!empty($source['readonly'])) { $class_name .= ' readonly'; } if (!empty($source['class_name'])) { $class_name .= ' ' . $source['class_name']; } $name = $source['name'] ?: $id; $out .= sprintf($line_templ, rcube_utils::html_identifier($id, true), $class_name, rcube::Q($rcmail->url(['_source' => $id])), $source['id'], $js_id, $name ); $groupdata = ['out' => $out, 'jsdata' => $jsdata, 'source' => $id]; if (!empty($source['groups'])) { $groupdata = self::contact_groups($groupdata); } $jsdata = $groupdata['jsdata']; $out = $groupdata['out']; $out .= ''; } $rcmail->output->set_env('contactgroups', $jsdata); $rcmail->output->set_env('collapsed_abooks', (string) $rcmail->config->get('collapsed_abooks','')); $rcmail->output->add_gui_object('folderlist', $attrib['id']); $rcmail->output->include_script('treelist.js'); // add some labels to client $rcmail->output->add_label('deletegroupconfirm', 'groupdeleting', 'addingmember', 'removingmember', 'newgroup', 'grouprename', 'searchsave', 'namex', 'save', 'import', 'importcontacts', 'advsearch', 'search' ); return html::tag('ul', $attrib, $out, html::$common_attrib); } public static function savedsearch_list($attrib) { if (empty($attrib['id'])) { $attrib['id'] = 'rcmsavedsearchlist'; } $rcmail = rcmail::get_instance(); $out = ''; $line_templ = html::tag('li', ['id' => 'rcmli%s', 'class' => '%s'], html::a([ 'href' => '#', 'rel' => 'S%s', 'onclick' => "return ".rcmail_output::JS_OBJECT_NAME.".command('listsearch', '%s', this)" ], '%s' ) ); // Saved searches $sources = $rcmail->user->list_searches(rcube_user::SEARCH_ADDRESSBOOK); foreach ($sources as $source) { $id = $source['id']; $js_id = rcube::JQ($id); // set class name(s) $classes = ['contactsearch']; if (!empty($source['class_name'])) { $classes[] = $source['class_name']; } $out .= sprintf($line_templ, rcube_utils::html_identifier('S' . $id, true), join(' ', $classes), $id, $js_id, rcube::Q($source['name'] ?: $id) ); } $rcmail->output->add_gui_object('savedsearchlist', $attrib['id']); return html::tag('ul', $attrib, $out, html::$common_attrib); } public static function contact_groups($args) { $rcmail = rcmail::get_instance(); $groups = $rcmail->get_address_book($args['source'])->list_groups(); $groups_html = ''; if (!empty($groups)) { $line_templ = html::tag('li', ['id' => 'rcmli%s', 'class' => 'contactgroup'], html::a([ 'href' => '#', 'rel' => '%s:%s', 'onclick' => "return ".rcmail_output::JS_OBJECT_NAME.".command('listgroup',{'source':'%s','id':'%s'},this)" ], '%s' ) ); // append collapse/expand toggle and open a new