* @author Aleksander Machniak * * Copyright (C) 2011, Kolab Systems AG * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ class kolab_addressbook extends rcube_plugin { public $task = 'mail|settings|addressbook|calendar'; private $folders; private $sources; private $rc; private $ui; const GLOBAL_FIRST = 0; const PERSONAL_FIRST = 1; const GLOBAL_ONLY = 2; const PERSONAL_ONLY = 3; /** * Startup method of a Roundcube plugin */ public function init() { require_once(dirname(__FILE__) . '/lib/rcube_kolab_contacts.php'); $this->rc = rcube::get_instance(); // load required plugin $this->require_plugin('libkolab'); // register hooks $this->add_hook('addressbooks_list', array($this, 'address_sources')); $this->add_hook('addressbook_get', array($this, 'get_address_book')); $this->add_hook('config_get', array($this, 'config_get')); if ($this->rc->task == 'addressbook') { $this->add_texts('localization'); $this->add_hook('contact_form', array($this, 'contact_form')); // Plugin actions $this->register_action('plugin.book', array($this, 'book_actions')); $this->register_action('plugin.book-save', array($this, 'book_save')); // Load UI elements if ($this->api->output->type == 'html') { require_once($this->home . '/lib/kolab_addressbook_ui.php'); $this->ui = new kolab_addressbook_ui($this); } } else if ($this->rc->task == 'settings') { $this->add_texts('localization'); $this->add_hook('preferences_list', array($this, 'prefs_list')); $this->add_hook('preferences_save', array($this, 'prefs_save')); } } /** * Handler for the addressbooks_list hook. * * This will add all instances of available Kolab-based address books * to the list of address sources of Roundcube. * This will also hide some addressbooks according to kolab_addressbook_prio setting. * * @param array $p Hash array with hook parameters * * @return array Hash array with modified hook parameters */ public function address_sources($p) { // Load configuration $this->load_config(); $abook_prio = (int) $this->rc->config->get('kolab_addressbook_prio'); $undelete = $this->rc->config->get('undo_timeout'); // Disable all global address books // Assumes that all non-kolab_addressbook sources are global if ($abook_prio == self::PERSONAL_ONLY) { $p['sources'] = array(); } $sources = array(); $names = array(); foreach ($this->_list_sources() as $abook_id => $abook) { $name = kolab_storage::folder_displayname($abook->get_name(), $names); // register this address source $sources[$abook_id] = array( 'id' => $abook_id, 'name' => $name, 'readonly' => $abook->readonly, 'editable' => $abook->editable, 'groups' => $abook->groups, 'undelete' => $abook->undelete && $undelete, 'realname' => rcube_charset::convert($abook->get_realname(), 'UTF7-IMAP'), // IMAP folder name 'class_name' => $abook->get_namespace(), 'kolab' => true, ); } // Add personal address sources to the list if ($abook_prio == self::PERSONAL_FIRST) { // $p['sources'] = array_merge($sources, $p['sources']); // Don't use array_merge(), because if you have folders name // that resolve to numeric identifier it will break output array keys foreach ($p['sources'] as $idx => $value) $sources[$idx] = $value; $p['sources'] = $sources; } else { // $p['sources'] = array_merge($p['sources'], $sources); foreach ($sources as $idx => $value) $p['sources'][$idx] = $value; } return $p; } /** * Sets autocomplete_addressbooks option according to * kolab_addressbook_prio setting extending list of address sources * to be used for autocompletion. */ public function config_get($args) { if ($args['name'] != 'autocomplete_addressbooks') { return $args; } // Load configuration $this->load_config(); $abook_prio = (int) $this->rc->config->get('kolab_addressbook_prio'); // here we cannot use rc->config->get() $sources = $GLOBALS['CONFIG']['autocomplete_addressbooks']; // Disable all global address books // Assumes that all non-kolab_addressbook sources are global if ($abook_prio == self::PERSONAL_ONLY) { $sources = array(); } if (!is_array($sources)) { $sources = array(); } $kolab_sources = array(); foreach ($this->_list_sources() as $abook_id => $abook) { if (!in_array($abook_id, $sources)) $kolab_sources[] = $abook_id; } // Add personal address sources to the list if (!empty($kolab_sources)) { if ($abook_prio == self::PERSONAL_FIRST) { $sources = array_merge($kolab_sources, $sources); } else { $sources = array_merge($sources, $kolab_sources); } } $args['result'] = $sources; return $args; } /** * Getter for the rcube_addressbook instance * * @param array $p Hash array with hook parameters * * @return array Hash array with modified hook parameters */ public function get_address_book($p) { if ($p['id']) { $this->_list_sources(); if ($this->sources[$p['id']]) { $p['instance'] = $this->sources[$p['id']]; } } return $p; } private function _list_sources() { // already read sources if (isset($this->sources)) return $this->sources; $this->sources = array(); // Load configuration $this->load_config(); $abook_prio = (int) $this->rc->config->get('kolab_addressbook_prio'); // Personal address source(s) disabled? if ($abook_prio == self::GLOBAL_ONLY) { return $this->sources; } // get all folders that have "contact" type $this->folders = kolab_storage::get_folders('contact'); if (PEAR::isError($this->folders)) { rcube::raise_error(array( 'code' => 600, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Failed to list contact folders from Kolab server:" . $this->folders->getMessage()), true, false); } else { // convert to UTF8 and sort $names = array(); foreach ($this->folders as $c_folder) $names[$c_folder->name] = rcube_charset::convert($c_folder->name, 'UTF7-IMAP'); asort($names, SORT_LOCALE_STRING); foreach ($names as $utf7name => $name) { // create instance of rcube_contacts $abook_id = kolab_storage::folder_id($utf7name); $abook = new rcube_kolab_contacts($utf7name); $this->sources[$abook_id] = $abook; } } return $this->sources; } /** * Plugin hook called before rendering the contact form or detail view * * @param array $p Hash array with hook parameters * * @return array Hash array with modified hook parameters */ public function contact_form($p) { // none of our business if (!is_object($GLOBALS['CONTACTS']) || !is_a($GLOBALS['CONTACTS'], 'rcube_kolab_contacts')) return $p; // extend the list of contact fields to be displayed in the 'personal' section if (is_array($p['form']['personal'])) { $p['form']['personal']['content']['profession'] = array('size' => 40); $p['form']['personal']['content']['children'] = array('size' => 40); $p['form']['personal']['content']['freebusyurl'] = array('size' => 40); $p['form']['personal']['content']['pgppublickey'] = array('size' => 70); $p['form']['personal']['content']['pkcs7publickey'] = array('size' => 70); // re-order fields according to the coltypes list $p['form']['contact']['content'] = $this->_sort_form_fields($p['form']['contact']['content']); $p['form']['personal']['content'] = $this->_sort_form_fields($p['form']['personal']['content']); /* define a separate section 'settings' $p['form']['settings'] = array( 'name' => $this->gettext('settings'), 'content' => array( 'freebusyurl' => array('size' => 40, 'visible' => true), 'pgppublickey' => array('size' => 70, 'visible' => true), 'pkcs7publickey' => array('size' => 70, 'visible' => false), ) ); */ } return $p; } private function _sort_form_fields($contents) { $block = array(); $contacts = reset($this->sources); foreach ($contacts->coltypes as $col => $prop) { if (isset($contents[$col])) $block[$col] = $contents[$col]; } return $block; } /** * Handler for user preferences form (preferences_list hook) * * @param array $args Hash array with hook parameters * * @return array Hash array with modified hook parameters */ public function prefs_list($args) { if ($args['section'] != 'addressbook') { return $args; } // Load configuration $this->load_config(); // Load localization $this->add_texts('localization'); // Check that configuration is not disabled $dont_override = (array) $this->rc->config->get('dont_override', array()); if (!in_array('kolab_addressbook_prio', $dont_override)) { $field_id = '_kolab_addressbook_prio'; $select = new html_select(array('name' => $field_id, 'id' => $field_id)); $select->add($this->gettext('globalfirst'), self::GLOBAL_FIRST); $select->add($this->gettext('personalfirst'), self::PERSONAL_FIRST); $select->add($this->gettext('globalonly'), self::GLOBAL_ONLY); $select->add($this->gettext('personalonly'), self::PERSONAL_ONLY); $args['blocks']['main']['options']['kolab_addressbook_prio'] = array( 'title' => html::label($field_id, Q($this->gettext('addressbookprio'))), 'content' => $select->show((int)$this->rc->config->get('kolab_addressbook_prio')), ); } return $args; } /** * Handler for user preferences save (preferences_save hook) * * @param array $args Hash array with hook parameters * * @return array Hash array with modified hook parameters */ public function prefs_save($args) { if ($args['section'] != 'addressbook') { return $args; } // Load configuration $this->load_config(); // Check that configuration is not disabled $dont_override = (array) $this->rc->config->get('dont_override', array()); if (!in_array('kolab_addressbook_prio', $dont_override)) { $key = 'kolab_addressbook_prio'; $args['prefs'][$key] = (int) get_input_value('_'.$key, RCUBE_INPUT_POST); } return $args; } /** * Handler for plugin actions */ public function book_actions() { $action = trim(get_input_value('_act', RCUBE_INPUT_GPC)); if ($action == 'create') { $this->ui->book_edit(); } else if ($action == 'edit') { $this->ui->book_edit(); } else if ($action == 'delete') { $this->book_delete(); } } /** * Handler for address book create/edit form submit */ public function book_save() { $prop = array( 'name' => trim(get_input_value('_name', RCUBE_INPUT_POST)), 'oldname' => trim(get_input_value('_oldname', RCUBE_INPUT_POST, true)), // UTF7-IMAP 'parent' => trim(get_input_value('_parent', RCUBE_INPUT_POST, true)), // UTF7-IMAP 'type' => 'contact', ); $result = $error = false; $type = strlen($prop['oldname']) ? 'update' : 'create'; $prop = $this->rc->plugins->exec_hook('addressbook_'.$type, $prop); if (!$prop['abort']) { if ($newfolder = kolab_storage::folder_update($prop)) { $folder = $newfolder; $result = true; } else { $error = kolab_storage::$last_error; } } else { $result = $prop['result']; $folder = $prop['name']; } if ($result) { $storage = $this->rc->get_storage(); $delimiter = $storage->get_hierarchy_delimiter(); $kolab_folder = new rcube_kolab_contacts($folder); // create display name for the folder (see self::address_sources()) if (strpos($folder, $delimiter)) { $names = array(); foreach ($this->_list_sources() as $abook_id => $abook) { $realname = $abook->get_realname(); // The list can be not updated yet, handle old folder name if ($type == 'update' && $realname == $oldfolder) { $abook = $kolab_folder; $realname = $folder; } $name = kolab_storage::folder_displayname($abook->get_name(), $names); if ($realname == $folder) { break; } } } else { $name = $kolab_folder->get_name(); } $this->rc->output->show_message('kolab_addressbook.book'.$type.'d', 'confirmation'); $this->rc->output->command('set_env', 'delimiter', $delimiter); $this->rc->output->command('book_update', array( 'id' => kolab_storage::folder_id($folder), 'name' => $name, 'readonly' => false, 'editable' => true, 'groups' => true, 'realname' => rcube_charset::convert($folder, 'UTF7-IMAP'), // IMAP folder name 'class_name' => $kolab_folder->get_namespace(), 'kolab' => true, ), kolab_storage::folder_id($prop['oldname'])); $this->rc->output->send('iframe'); } if (!$error) $error = $plugin['message'] ? $plugin['message'] : 'kolab_addressbook.book'.$type.'error'; $this->rc->output->show_message($error, 'error'); // display the form again $this->ui->book_edit(); } /** * Handler for address book delete action (AJAX) */ private function book_delete() { $folder = trim(get_input_value('_source', RCUBE_INPUT_GPC, true, 'UTF7-IMAP')); if (kolab_storage::folder_delete($folder)) { $this->rc->output->show_message('kolab_addressbook.bookdeleted', 'confirmation'); $this->rc->output->set_env('pagecount', 0); $this->rc->output->command('set_rowcount', rcmail_get_rowcount_text(new rcube_result_set())); $this->rc->output->command('list_contacts_clear'); $this->rc->output->command('book_delete_done', kolab_storage::folder_id($folder)); } else { $this->rc->output->show_message('kolab_addressbook.bookdeleteerror', 'error'); } $this->rc->output->send(); } }