diff --git a/plugins/libkolab/README b/plugins/libkolab/README index 0a3c0ce3..7e27c79f 100644 --- a/plugins/libkolab/README +++ b/plugins/libkolab/README @@ -1,43 +1,38 @@ -libkolab plugin to access to Kolab groupware data -================================================= +libkolab plugin to access to Kolab groupware 2.x data +===================================================== The contained library classes establish a connection to the Kolab server and manage the access to the Kolab groupware objects stored in various -IMAP folders. For reading and writing these objects, the PHP bindings of -the libkolabxml library are used. +IMAP folders. For reading and writing these objects, the Horde Kolab_Format +packages are used. REQUIREMENTS ------------ -* libkolabxml PHP bindings - - kolabformat.so loaded into PHP - - kolabformat.php placed somewhere in the include_path * PEAR: HTTP/Request2 * PEAR: Net/URL2 - -* Optional for old format support: - Horde Kolab_Format package and all of its dependencies +* Horde Kolab_Format package and all of its dependencies which are at least Horde_(Browser,DOM,NLS,String,Utils) INSTALLATION ------------ To use local cache you need to create a dedicated table in Roundcube's database. To do so, execute the SQL commands in SQL/.sql CONFIGURATION ------------- The following options can be configured in Roundcube's main config file or a local config file (config.inc.php) located in the plugin folder. // Enable caching of Kolab objects in local database $rcmail_config['kolab_cache'] = true; // Optional override of the URL to read and trigger Free/Busy information of Kolab users // Defaults to https:///freebusy $rcmail_config['kolab_freebusy_server'] = 'https:///'; // Set this option to disable SSL certificate checks when triggering Free/Busy (enabled by default) $rcmail_config['kolab_ssl_verify_peer'] = false; diff --git a/plugins/libkolab/config.inc.php.dist b/plugins/libkolab/config.inc.php.dist index cb446523..496a3f9b 100644 --- a/plugins/libkolab/config.inc.php.dist +++ b/plugins/libkolab/config.inc.php.dist @@ -1,10 +1,10 @@ diff --git a/plugins/libkolab/lib/kolab_format.php b/plugins/libkolab/lib/kolab_format.php index a4147812..622b411a 100644 --- a/plugins/libkolab/lib/kolab_format.php +++ b/plugins/libkolab/lib/kolab_format.php @@ -1,419 +1,380 @@ * * Copyright (C) 2012, 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 . */ abstract class kolab_format { public static $timezone; public /*abstract*/ $CTYPE; - public /*abstract*/ $CTYPEv2; - protected /*abstract*/ $objclass; - protected /*abstract*/ $read_func; - protected /*abstract*/ $write_func; + protected /*abstract*/ $xmltype; + protected /*abstract*/ $subtype; - protected $obj; + protected $handler; protected $data; protected $xmldata; - protected $xmlobject; protected $loaded = false; - protected $version = 3.0; + protected $version = 2.0; const KTYPE_PREFIX = 'application/x-vnd.kolab.'; - const PRODUCT_ID = 'Roundcube-libkolab-0.9'; + const PRODUCT_ID = 'Roundcube-libkolab-horde-0.9'; /** * Factory method to instantiate a kolab_format object of the given type and version * * @param string Object type to instantiate * @param float Format version * @param string Cached xml data to initialize with * @return object kolab_format */ - public static function factory($type, $version = 3.0, $xmldata = null) + public static function factory($type, $version = 2.0, $xmldata = null) { if (!isset(self::$timezone)) self::$timezone = new DateTimeZone('UTC'); if (!self::supports($version)) return PEAR::raiseError("No support for Kolab format version " . $version); + list($xmltype, $subtype) = explode('.', $type); + $type = preg_replace('/configuration\.[a-z.]+$/', 'configuration', $type); - $suffix = preg_replace('/[^a-z]+/', '', $type); + $suffix = preg_replace('/[^a-z]+/', '', $xmltype); $classname = 'kolab_format_' . $suffix; if (class_exists($classname)) - return new $classname($xmldata, $version); + return new $classname($xmldata, $subtype); return PEAR::raiseError("Failed to load Kolab Format wrapper for type " . $type); } /** * Determine support for the given format version * * @param float Format version to check * @return boolean True if supported, False otherwise */ public static function supports($version) { if ($version == 2.0) - return class_exists('kolabobject'); - // default is version 3 - return class_exists('kolabformat'); + return class_exists('Horde_Kolab_Format'); + + return false; } /** * Convert the given date/time value into a cDateTime object * * @param mixed Date/Time value either as unix timestamp, date string or PHP DateTime object * @param DateTimeZone The timezone the date/time is in. Use global default if Null, local time if False * @param boolean True of the given date has no time component * @return object The libkolabxml date/time object */ public static function get_datetime($datetime, $tz = null, $dateonly = false) { // use timezone information from datetime of global setting if (!$tz && $tz !== false) { if ($datetime instanceof DateTime) $tz = $datetime->getTimezone(); if (!$tz) $tz = self::$timezone; } $result = new cDateTime(); // got a unix timestamp (in UTC) if (is_numeric($datetime)) { $datetime = new DateTime('@'.$datetime, new DateTimeZone('UTC')); if ($tz) $datetime->setTimezone($tz); } else if (is_string($datetime) && strlen($datetime)) $datetime = new DateTime($datetime, $tz ?: null); if ($datetime instanceof DateTime) { $result->setDate($datetime->format('Y'), $datetime->format('n'), $datetime->format('j')); if (!$dateonly) $result->setTime($datetime->format('G'), $datetime->format('i'), $datetime->format('s')); if ($tz && $tz->getName() == 'UTC') $result->setUTC(true); else if ($tz !== false) $result->setTimezone($tz->getName()); } return $result; } /** * Convert the given cDateTime into a PHP DateTime object * * @param object cDateTime The libkolabxml datetime object * @return object DateTime PHP datetime instance */ public static function php_datetime($cdt) { if (!is_object($cdt) || !$cdt->isValid()) return null; $d = new DateTime; $d->setTimezone(self::$timezone); try { if ($tzs = $cdt->timezone()) { $tz = new DateTimeZone($tzs); $d->setTimezone($tz); } else if ($cdt->isUTC()) { $d->setTimezone(new DateTimeZone('UTC')); } } catch (Exception $e) { } $d->setDate($cdt->year(), $cdt->month(), $cdt->day()); if ($cdt->isDateOnly()) { $d->_dateonly = true; $d->setTime(12, 0, 0); // set time to noon to avoid timezone troubles } else { $d->setTime($cdt->hour(), $cdt->minute(), $cdt->second()); } return $d; } /** * Convert a libkolabxml vector to a PHP array * * @param object vector Object * @return array Indexed array contaning vector elements */ public static function vector2array($vec, $max = PHP_INT_MAX) { $arr = array(); for ($i=0; $i < $vec->size() && $i < $max; $i++) $arr[] = $vec->get($i); return $arr; } /** * Build a libkolabxml vector (string) from a PHP array * * @param array Array with vector elements * @return object vectors */ public static function array2vector($arr) { $vec = new vectors; foreach ((array)$arr as $val) { if (strlen($val)) $vec->push($val); } return $vec; } /** * Parse the X-Kolab-Type header from MIME messages and return the object type in short form * * @param string X-Kolab-Type header value * @return string Kolab object type (contact,event,task,note,etc.) */ public static function mime2object_type($x_kolab_type) { return preg_replace('/dictionary.[a-z.]+$/', 'dictionary', substr($x_kolab_type, strlen(self::KTYPE_PREFIX))); } /** * Default constructor of all kolab_format_* objects */ - public function __construct($xmldata = null, $version = null) + public function __construct($xmldata = null, $subtype = null) { - $this->obj = new $this->objclass; - $this->xmldata = $xmldata; + $this->subtype = $subtype; - if ($version) - $this->version = $version; + $handler = Horde_Kolab_Format::factory('XML', $this->xmltype, array('subtype' => $this->subtype)); + if (!is_object($handler) || is_a($handler, 'PEAR_Error')) { + return false; + } - // use libkolab module if available - if (class_exists('kolabobject')) - $this->xmlobject = new XMLObject(); + $this->handler = $handler; + $this->xmldata = $xmldata; } /** * Check for format errors after calling kolabformat::write*() * * @return boolean True if there were errors, False if OK */ protected function format_errors() { $ret = $log = false; switch (kolabformat::error()) { case kolabformat::NoError: $ret = false; break; case kolabformat::Warning: $ret = false; $log = "Warning"; break; default: $ret = true; $log = "Error"; } if ($log) { rcube::raise_error(array( 'code' => 660, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "kolabformat $log: " . kolabformat::errorMessage(), ), true); } return $ret; } /** * Save the last generated UID to the object properties. * Should be called after kolabformat::writeXXXX(); */ protected function update_uid() { // get generated UID if (!$this->data['uid']) { - $this->data['uid'] = $this->xmlobject ? $this->xmlobject->getSerializedUID() : kolabformat::getSerializedUID(); - $this->obj->setUid($this->data['uid']); + $this->data['uid'] = 'TODO'; } } /** * Initialize libkolabxml object with cached xml data */ protected function init() { if (!$this->loaded) { if ($this->xmldata) { $this->load($this->xmldata); $this->xmldata = null; } $this->loaded = true; } } - /** - * Get constant value for libkolab's version parameter - * - * @param float Version value to convert - * @return int Constant value of either kolabobject::KolabV2 or kolabobject::KolabV3 or false if kolabobject module isn't available - */ - protected function libversion($v = null) - { - if (class_exists('kolabobject')) { - $version = $v ?: $this->version; - if ($version <= 2.0) - return kolabobject::KolabV2; - else - return kolabobject::KolabV3; - } - - return false; - } - - /** - * Determine the correct libkolab(xml) wrapper function for the given call - * depending on the available PHP modules - */ - protected function libfunc($func) - { - if (is_array($func) || strpos($func, '::')) - return $func; - else if (class_exists('kolabobject')) - return array($this->xmlobject, $func); - else - return 'kolabformat::' . $func; - } - /** * Direct getter for object properties */ public function __get($var) { return $this->data[$var]; } /** * Load Kolab object data from the given XML block * * @param string XML data * @return boolean True on success, False on failure */ public function load($xml) { - $read_func = $this->libfunc($this->read_func); - - if (is_array($read_func)) - $r = call_user_func($read_func, $xml, $this->libversion()); - else - $r = call_user_func($read_func, $xml, false); - - if (is_resource($r)) - $this->obj = new $this->objclass($r); - else if (is_a($r, $this->objclass)) - $this->obj = $r; + // XML-to-array + $object = $this->handler->load($xml); + $this->fromkolab2($object); $this->loaded = !$this->format_errors(); } /** * Write object data to XML format * * @param float Format version to write * @return string XML data */ public function write($version = null) { $this->init(); - $write_func = $this->libfunc($this->write_func); - if (is_array($write_func)) - $this->xmldata = call_user_func($write_func, $this->obj, $this->libversion($version), self::PRODUCT_ID); - else - $this->xmldata = call_user_func($write_func, $this->obj, self::PRODUCT_ID); + + // TODO: implement his if (!$this->format_errors()) $this->update_uid(); else $this->xmldata = null; return $this->xmldata; } /** * Set properties to the kolabformat object * * @param array Object data as hash array */ abstract public function set(&$object); /** * */ abstract public function is_valid(); /** - * Convert the Kolab object into a hash array data structure + * Getter for the parsed object data * * @return array Kolab object data as hash array */ - abstract public function to_array(); + public function to_array() + { + // load from XML if not done yet + if (!empty($this->data)) + $this->init(); + + return $this->data; + } /** * Load object data from Kolab2 format * * @param array Hash array with object properties (produced by Horde Kolab_Format classes) */ abstract public function fromkolab2($object); /** * Callback for kolab_storage_cache to get object specific tags to cache * * @return array List of tags to save in cache */ public function get_tags() { return array(); } /** * Callback for kolab_storage_cache to get words to index for fulltext search * * @return array List of words to save in cache */ public function get_words() { return array(); } } diff --git a/plugins/libkolab/lib/kolab_format_configuration.php b/plugins/libkolab/lib/kolab_format_configuration.php index 918928b2..2970b103 100644 --- a/plugins/libkolab/lib/kolab_format_configuration.php +++ b/plugins/libkolab/lib/kolab_format_configuration.php @@ -1,159 +1,126 @@ * * Copyright (C) 2012, 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_format_configuration extends kolab_format { public $CTYPE = 'application/x-vnd.kolab.configuration'; - public $CTYPEv2 = 'application/x-vnd.kolab.configuration'; - protected $objclass = 'Configuration'; - protected $read_func = 'readConfiguration'; - protected $write_func = 'writeConfiguration'; + protected $xmltype = 'configuration'; private $type_map = array( 'dictionary' => Configuration::TypeDictionary, 'category' => Configuration::TypeCategoryColor, ); /** * Set properties to the kolabformat object * * @param array Object data as hash array */ public function set(&$object) { $this->init(); + if ($object['type']) + $this->subtype = $object['type']; + // read type-specific properties - switch ($object['type']) { + switch ($this->subtype) { case 'dictionary': - $dict = new Dictionary($object['language']); - $dict->setEntries(self::array2vector($object['e'])); - $this->obj = new Configuration($dict); + // TODO: implement this break; case 'category': // TODO: implement this - $categories = new vectorcategorycolor; - $this->obj = new Configuration($categories); break; default: return false; } - // set some automatic values if missing - if (!empty($object['uid'])) - $this->obj->setUid($object['uid']); - if (!empty($object['created'])) - $this->obj->setCreated(self::get_datetime($object['created'])); - // adjust content-type string - $this->CTYPE = $this->CTYPEv2 = 'application/x-vnd.kolab.configuration.' . $object['type']; + $this->CTYPE = 'application/x-vnd.kolab.configuration.' . $this->subtype; // cache this data $this->data = $object; unset($this->data['_formatobj']); } /** * */ public function is_valid() { return $this->data || (is_object($this->obj) && $this->obj->isValid()); } /** * Convert the Configuration object into a hash array data structure * * @return array Config object data as hash array */ public function to_array() { - // return cached result + // load from XML if not done yet if (!empty($this->data)) - return $this->data; - - $this->init(); - $type_map = array_flip($this->type_map); - - // read object properties - $object = array( - 'uid' => $this->obj->uid(), - 'created' => self::php_datetime($this->obj->created()), - 'changed' => self::php_datetime($this->obj->lastModified()), - 'type' => $type_map[$this->obj->type()], - ); - - // read type-specific properties - switch ($object['type']) { - case 'dictionary': - $dict = $this->obj->dictionary(); - $object['language'] = $dict->language(); - $object['e'] = self::vector2array($dict->entries()); - break; - - case 'category': - // TODO: implement this - break; - } + $this->init(); // adjust content-type string - if ($object['type']) - $this->CTYPE = $this->CTYPEv2 = 'application/x-vnd.kolab.configuration.' . $object['type']; + if ($this->data['type']) { + $this->subtype = $this->data; + $this->CTYPE = 'application/x-vnd.kolab.configuration.' . $this->subtype; + } - $this->data = $object; return $this->data; } /** * Load data from old Kolab2 format */ public function fromkolab2($record) { $object = array( 'uid' => $record['uid'], 'changed' => $record['last-modification-date'], ); $this->data = $object + $record; } /** * Callback for kolab_storage_cache to get object specific tags to cache * * @return array List of tags to save in cache */ public function get_tags() { $tags = array(); if ($this->data['type'] == 'dictionary') $tags = array($this->data['language']); return $tags; } } diff --git a/plugins/libkolab/lib/kolab_format_contact.php b/plugins/libkolab/lib/kolab_format_contact.php index b147c384..fb033e3c 100644 --- a/plugins/libkolab/lib/kolab_format_contact.php +++ b/plugins/libkolab/lib/kolab_format_contact.php @@ -1,512 +1,211 @@ * * Copyright (C) 2012, 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_format_contact extends kolab_format { - public $CTYPE = 'application/vcard+xml'; - public $CTYPEv2 = 'application/x-vnd.kolab.contact'; + public $CTYPE = 'application/x-vnd.kolab.contact'; - protected $objclass = 'Contact'; - protected $read_func = 'readContact'; - protected $write_func = 'writeContact'; + protected $xmltype = 'contact'; public static $fulltext_cols = array('name', 'firstname', 'surname', 'middlename', 'email'); public $phonetypes = array( - 'home' => Telephone::Home, - 'work' => Telephone::Work, - 'text' => Telephone::Text, - 'main' => Telephone::Voice, - 'homefax' => Telephone::Fax, - 'workfax' => Telephone::Fax, - 'mobile' => Telephone::Cell, - 'video' => Telephone::Video, - 'pager' => Telephone::Pager, - 'car' => Telephone::Car, - 'other' => Telephone::Textphone, + 'home' => 'home1', + 'work' => 'business1', + 'text' => 'text', + 'main' => 'primary', + 'homefax' => 'homefax', + 'workfax' => 'businessfax', + 'mobile' => 'mobile', + 'isdn' => 'isdn', + 'pager' => 'pager', + 'car' => 'car', + 'company' => 'company', + 'radio' => 'radio', + 'telex' => 'telex', + 'ttytdd' => 'ttytdd', + 'other' => 'other', + 'assistant' => 'assistant', + 'callback' => 'callback', ); public $addresstypes = array( - 'home' => Address::Home, - 'work' => Address::Work, + 'home' => 'home', + 'work' => 'business', + 'other' => 'other', 'office' => 0, ); - private $gendermap = array( - 'female' => Contact::Female, - 'male' => Contact::Male, - ); - - private $relatedmap = array( - 'manager' => Related::Manager, - 'assistant' => Related::Assistant, - 'spouse' => Related::Spouse, - 'children' => Related::Child, - ); - // old Kolab 2 format field map private $kolab2_fieldmap = array( // kolab => roundcube 'full-name' => 'name', 'given-name' => 'firstname', 'middle-names' => 'middlename', 'last-name' => 'surname', 'prefix' => 'prefix', 'suffix' => 'suffix', 'nick-name' => 'nickname', 'organization' => 'organization', 'department' => 'department', 'job-title' => 'jobtitle', 'birthday' => 'birthday', 'anniversary' => 'anniversary', 'phone' => 'phone', 'im-address' => 'im', 'web-page' => 'website', 'profession' => 'profession', 'manager-name' => 'manager', 'assistant' => 'assistant', 'spouse-name' => 'spouse', 'children' => 'children', 'body' => 'notes', 'pgp-publickey' => 'pgppublickey', 'free-busy-url' => 'freebusyurl', 'picture' => 'photo', ); private $kolab2_phonetypes = array( 'home1' => 'home', 'business1' => 'work', 'business2' => 'work', 'businessfax' => 'workfax', ); private $kolab2_addresstypes = array( 'business' => 'work' ); private $kolab2_gender = array(0 => 'male', 1 => 'female'); /** * Default constructor */ - function __construct($xmldata = null, $version = 3.0) + function __construct($xmldata = null, $subtype = null) { - parent::__construct($xmldata, $version); - - // complete phone types - $this->phonetypes['homefax'] |= Telephone::Home; - $this->phonetypes['workfax'] |= Telephone::Work; + parent::__construct($xmldata, $subtype); } /** * Set contact properties to the kolabformat object * * @param array Contact data as hash array */ public function set(&$object) { $this->init(); - // set some automatic values if missing - if (false && !$this->obj->created()) { - if (!empty($object['created'])) - $object['created'] = new DateTime('now', self::$timezone); - $this->obj->setCreated(self::get_datetime($object['created'])); - } - - if (!empty($object['uid'])) - $this->obj->setUid($object['uid']); - - $object['changed'] = new DateTime('now', self::$timezone); - $this->obj->setLastModified(self::get_datetime($object['changed'], new DateTimeZone('UTC'))); - - // do the hard work of setting object values - $nc = new NameComponents; - $nc->setSurnames(self::array2vector($object['surname'])); - $nc->setGiven(self::array2vector($object['firstname'])); - $nc->setAdditional(self::array2vector($object['middlename'])); - $nc->setPrefixes(self::array2vector($object['prefix'])); - $nc->setSuffixes(self::array2vector($object['suffix'])); - $this->obj->setNameComponents($nc); - $this->obj->setName($object['name']); - - if (isset($object['nickname'])) - $this->obj->setNickNames(self::array2vector($object['nickname'])); - if (isset($object['profession'])) - $this->obj->setTitles(self::array2vector($object['profession'])); - - // organisation related properties (affiliation) - $org = new Affiliation; - $offices = new vectoraddress; - if ($object['organization']) - $org->setOrganisation($object['organization']); - if ($object['department']) - $org->setOrganisationalUnits(self::array2vector($object['department'])); - if ($object['jobtitle']) - $org->setRoles(self::array2vector($object['jobtitle'])); - - $rels = new vectorrelated; - if ($object['manager']) { - foreach ((array)$object['manager'] as $manager) - $rels->push(new Related(Related::Text, $manager, Related::Manager)); - } - if ($object['assistant']) { - foreach ((array)$object['assistant'] as $assistant) - $rels->push(new Related(Related::Text, $assistant, Related::Assistant)); - } - $org->setRelateds($rels); - - // email, im, url - $this->obj->setEmailAddresses(self::array2vector($object['email'])); - $this->obj->setIMaddresses(self::array2vector($object['im'])); - - $vurls = new vectorurl; - foreach ((array)$object['website'] as $url) { - $type = $url['type'] == 'blog' ? Url::Blog : Url::NoType; - $vurls->push(new Url($url['url'], $type)); - } - $this->obj->setUrls($vurls); - - // addresses - $adrs = new vectoraddress; - foreach ((array)$object['address'] as $address) { - $adr = new Address; - $type = $this->addresstypes[$address['type']]; - if (isset($type)) - $adr->setTypes($type); - else if ($address['type']) - $adr->setLabel($address['type']); - if ($address['street']) - $adr->setStreet($address['street']); - if ($address['locality']) - $adr->setLocality($address['locality']); - if ($address['code']) - $adr->setCode($address['code']); - if ($address['region']) - $adr->setRegion($address['region']); - if ($address['country']) - $adr->setCountry($address['country']); - - if ($address['type'] == 'office') - $offices->push($adr); - else - $adrs->push($adr); - } - $this->obj->setAddresses($adrs); - $org->setAddresses($offices); - - // add org affiliation after addresses are set - $orgs = new vectoraffiliation; - $orgs->push($org); - $this->obj->setAffiliations($orgs); - - // telephones - $tels = new vectortelephone; - foreach ((array)$object['phone'] as $phone) { - $tel = new Telephone; - if (isset($this->phonetypes[$phone['type']])) - $tel->setTypes($this->phonetypes[$phone['type']]); - $tel->setNumber($phone['number']); - $tels->push($tel); - } - $this->obj->setTelephones($tels); - - if (isset($object['gender'])) - $this->obj->setGender($this->gendermap[$object['gender']] ? $this->gendermap[$object['gender']] : Contact::NotSet); - if (isset($object['notes'])) - $this->obj->setNote($object['notes']); - if (isset($object['freebusyurl'])) - $this->obj->setFreeBusyUrl($object['freebusyurl']); - if (isset($object['birthday'])) - $this->obj->setBDay(self::get_datetime($object['birthday'], false, true)); - if (isset($object['anniversary'])) - $this->obj->setAnniversary(self::get_datetime($object['anniversary'], false, true)); - - if (!empty($object['photo'])) { - if ($type = rcube_mime::image_content_type($object['photo'])) - $this->obj->setPhoto($object['photo'], $type); - } - else if (isset($object['photo'])) - $this->obj->setPhoto('',''); - else if ($this->obj->photoMimetype()) // load saved photo for caching - $object['photo'] = $this->obj->photo(); - - // spouse and children are relateds - $rels = new vectorrelated; - if ($object['spouse']) { - $rels->push(new Related(Related::Text, $object['spouse'], Related::Spouse)); - } - if ($object['children']) { - foreach ((array)$object['children'] as $child) - $rels->push(new Related(Related::Text, $child, Related::Child)); - } - $this->obj->setRelateds($rels); - - // insert/replace crypto keys - $pgp_index = $pkcs7_index = -1; - $keys = $this->obj->keys(); - for ($i=0; $i < $keys->size(); $i++) { - $key = $keys->get($i); - if ($pgp_index < 0 && $key->type() == Key::PGP) - $pgp_index = $i; - else if ($pkcs7_index < 0 && $key->type() == Key::PKCS7_MIME) - $pkcs7_index = $i; - } - - $pgpkey = $object['pgppublickey'] ? new Key($object['pgppublickey'], Key::PGP) : new Key(); - $pkcs7key = $object['pkcs7publickey'] ? new Key($object['pkcs7publickey'], Key::PKCS7_MIME) : new Key(); - - if ($pgp_index >= 0) - $keys->set($pgp_index, $pgpkey); - else if (!empty($object['pgppublickey'])) - $keys->push($pgpkey); - if ($pkcs7_index >= 0) - $keys->set($pkcs7_index, $pkcs7key); - else if (!empty($object['pkcs7publickey'])) - $keys->push($pkcs7key); - - $this->obj->setKeys($keys); - - // TODO: handle language, gpslocation, etc. - + // TODO: implement this // cache this data $this->data = $object; unset($this->data['_formatobj']); } /** * */ public function is_valid() { - return $this->data || (is_object($this->obj) && $this->obj->uid() /*$this->obj->isValid()*/); - } - - /** - * Convert the Contact object into a hash array data structure - * - * @return array Contact data as hash array - */ - public function to_array() - { - // return cached result - if (!empty($this->data)) - return $this->data; - - $this->init(); - - // read object properties into local data object - $object = array( - 'uid' => $this->obj->uid(), - 'name' => $this->obj->name(), - 'changed' => self::php_datetime($this->obj->lastModified()), - ); - - $nc = $this->obj->nameComponents(); - $object['surname'] = join(' ', self::vector2array($nc->surnames())); - $object['firstname'] = join(' ', self::vector2array($nc->given())); - $object['middlename'] = join(' ', self::vector2array($nc->additional())); - $object['prefix'] = join(' ', self::vector2array($nc->prefixes())); - $object['suffix'] = join(' ', self::vector2array($nc->suffixes())); - $object['nickname'] = join(' ', self::vector2array($this->obj->nickNames())); - $object['profession'] = join(' ', self::vector2array($this->obj->titles())); - - // organisation related properties (affiliation) - $orgs = $this->obj->affiliations(); - if ($orgs->size()) { - $org = $orgs->get(0); - $object['organization'] = $org->organisation(); - $object['jobtitle'] = join(' ', self::vector2array($org->roles())); - $object['department'] = join(' ', self::vector2array($org->organisationalUnits())); - $this->read_relateds($org->relateds(), $object); - } - - $object['email'] = self::vector2array($this->obj->emailAddresses()); - $object['im'] = self::vector2array($this->obj->imAddresses()); - - $urls = $this->obj->urls(); - for ($i=0; $i < $urls->size(); $i++) { - $url = $urls->get($i); - $subtype = $url->type() == Url::Blog ? 'blog' : 'homepage'; - $object['website'][] = array('url' => $url->url(), 'type' => $subtype); - } - - // addresses - $this->read_addresses($this->obj->addresses(), $object); - if ($org && ($offices = $org->addresses())) - $this->read_addresses($offices, $object, 'office'); - - // telehones - $tels = $this->obj->telephones(); - $teltypes = array_flip($this->phonetypes); - for ($i=0; $i < $tels->size(); $i++) { - $tel = $tels->get($i); - $object['phone'][] = array('number' => $tel->number(), 'type' => $teltypes[$tel->types()]); - } - - $object['notes'] = $this->obj->note(); - $object['freebusyurl'] = $this->obj->freeBusyUrl(); - - if ($bday = self::php_datetime($this->obj->bDay())) - $object['birthday'] = $bday->format('c'); - - if ($anniversary = self::php_datetime($this->obj->anniversary())) - $object['anniversary'] = $anniversary->format('c'); - - $gendermap = array_flip($this->gendermap); - if (($g = $this->obj->gender()) && $gendermap[$g]) - $object['gender'] = $gendermap[$g]; - - if ($this->obj->photoMimetype()) - $object['photo'] = $this->obj->photo(); - else if ($this->xmlobject && ($photo_name = $this->xmlobject->pictureAttachmentName())) - $object['photo'] = $photo_name; - - // relateds -> spouse, children - $this->read_relateds($this->obj->relateds(), $object); - - // crypto settings: currently only key values are supported - $keys = $this->obj->keys(); - for ($i=0; is_object($keys) && $i < $keys->size(); $i++) { - $key = $keys->get($i); - if ($key->type() == Key::PGP) - $object['pgppublickey'] = $key->key(); - else if ($key->type() == Key::PKCS7_MIME) - $object['pkcs7publickey'] = $key->key(); - } - - $this->data = $object; return $this->data; } /** * Callback for kolab_storage_cache to get words to index for fulltext search * * @return array List of words to save in cache */ public function get_words() { $data = ''; foreach (self::$fulltext_cols as $col) { $val = is_array($this->data[$col]) ? join(' ', $this->data[$col]) : $this->data[$col]; if (strlen($val)) $data .= $val . ' '; } return array_unique(rcube_utils::normalize_string($data, true)); } /** * Load data from old Kolab2 format * * @param array Hash array with object properties */ public function fromkolab2($record) { $object = array( 'uid' => $record['uid'], 'email' => array(), 'phone' => array(), ); foreach ($this->kolab2_fieldmap as $kolab => $rcube) { if (is_array($record[$kolab]) || strlen($record[$kolab])) $object[$rcube] = $record[$kolab]; } if (isset($record['gender'])) $object['gender'] = $this->kolab2_gender[$record['gender']]; foreach ((array)$record['email'] as $i => $email) $object['email'][] = $email['smtp-address']; if (!$record['email'] && $record['emails']) $object['email'] = preg_split('/,\s*/', $record['emails']); if (is_array($record['address'])) { + $kolab2_addresstypes = array_flip($this->addresstypes); foreach ($record['address'] as $i => $adr) { $object['address'][] = array( - 'type' => $this->kolab2_addresstypes[$adr['type']] ? $this->kolab2_addresstypes[$adr['type']] : $adr['type'], + 'type' => $kolab2_addresstypes[$adr['type']] ? $kolab2_addresstypes[$adr['type']] : $adr['type'], 'street' => $adr['street'], 'locality' => $adr['locality'], 'code' => $adr['postal-code'], 'region' => $adr['region'], 'country' => $adr['country'], ); } } + // map Kolab format phone types to Roundcube types + if (!empty($object['phone'])) { + $kolab2_phonetypes = array_merge(array_flip($this->phonetypes), $this->kolab2_phonetypes); + foreach ($object['phone'] as $i => $phone) { + if ($type = $kolab2_phonetypes[$phone['type']]) + $object['phone'][$i]['type'] = $type; + } + } + // office location goes into an address block if ($record['office-location']) $object['address'][] = array('type' => 'office', 'locality' => $record['office-location']); // merge initials into nickname if ($record['initials']) $object['nickname'] = trim($object['nickname'] . ', ' . $record['initials'], ', '); // remove empty fields $this->data = array_filter($object); } - /** - * Helper method to copy contents of an Address vector to the contact data object - */ - private function read_addresses($addresses, &$object, $type = null) - { - $adrtypes = array_flip($this->addresstypes); - - for ($i=0; $i < $addresses->size(); $i++) { - $adr = $addresses->get($i); - $object['address'][] = array( - 'type' => $type ? $type : ($adrtypes[$adr->types()] ? $adrtypes[$adr->types()] : ''), /*$adr->label()),*/ - 'street' => $adr->street(), - 'code' => $adr->code(), - 'locality' => $adr->locality(), - 'region' => $adr->region(), - 'country' => $adr->country() - ); - } - } - - /** - * Helper method to map contents of a Related vector to the contact data object - */ - private function read_relateds($rels, &$object) - { - $typemap = array_flip($this->relatedmap); - - for ($i=0; $i < $rels->size(); $i++) { - $rel = $rels->get($i); - if ($rel->type() != Related::Text) // we can't handle UID relations yet - continue; - - $types = $rel->relationTypes(); - foreach ($typemap as $t => $field) { - if ($types & $t) { - $object[$field][] = $rel->text(); - break; - } - } - } - } } diff --git a/plugins/libkolab/lib/kolab_format_distributionlist.php b/plugins/libkolab/lib/kolab_format_distributionlist.php index ba54742b..ce44f38f 100644 --- a/plugins/libkolab/lib/kolab_format_distributionlist.php +++ b/plugins/libkolab/lib/kolab_format_distributionlist.php @@ -1,143 +1,79 @@ * * Copyright (C) 2012, 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_format_distributionlist extends kolab_format { - public $CTYPE = 'application/vcard+xml'; - public $CTYPEv2 = 'application/x-vnd.kolab.distribution-list'; + public $CTYPE = 'application/x-vnd.kolab.distribution-list'; - protected $objclass = 'DistList'; - protected $read_func = 'readDistlist'; - protected $write_func = 'writeDistlist'; + protected $xmltype = 'distributionlist'; /** * Set properties to the kolabformat object * * @param array Object data as hash array */ public function set(&$object) { $this->init(); - // set some automatic values if missing - if (!empty($object['uid'])) - $this->obj->setUid($object['uid']); - - $object['changed'] = new DateTime('now', self::$timezone); - $this->obj->setLastModified(self::get_datetime($object['changed'], new DateTimeZone('UTC'))); - - $this->obj->setName($object['name']); - - $seen = array(); - $members = new vectorcontactref; - foreach ((array)$object['member'] as $member) { - if ($member['uid']) - $m = new ContactReference(ContactReference::UidReference, $member['uid']); - else if ($member['email']) - $m = new ContactReference(ContactReference::EmailReference, $member['email']); - else - continue; - - $m->setName($member['name']); - $members->push($m); - $seen[$member['email']]++; - } - - $this->obj->setMembers($members); + // TODO: implement this // set type property for proper caching $object['_type'] = 'distribution-list'; // cache this data $this->data = $object; unset($this->data['_formatobj']); } public function is_valid() { - return $this->data || (is_object($this->obj) && $this->obj->isValid()); + return $this->data; } /** * Load data from old Kolab2 format */ public function fromkolab2($record) { $object = array( 'uid' => $record['uid'], 'changed' => $record['last-modification-date'], 'name' => $record['last-name'], 'member' => array(), ); foreach ((array)$record['member'] as $member) { $object['member'][] = array( 'email' => $member['smtp-address'], 'name' => $member['display-name'], 'uid' => $member['uid'], ); } $this->data = $object; } - /** - * Convert the Distlist object into a hash array data structure - * - * @return array Distribution list data as hash array - */ - public function to_array() - { - // return cached result - if (!empty($this->data)) - return $this->data; - - $this->init(); - - // read object properties - $object = array( - 'uid' => $this->obj->uid(), - 'changed' => self::php_datetime($this->obj->lastModified()), - 'name' => $this->obj->name(), - 'member' => array(), - '_type' => 'distribution-list', - ); - - $members = $this->obj->members(); - for ($i=0; $i < $members->size(); $i++) { - $member = $members->get($i); -# if ($member->type() == ContactReference::UidReference && ($uid = $member->uid())) - $object['member'][] = array( - 'uid' => $member->uid(), - 'email' => $member->email(), - 'name' => $member->name(), - ); - } - - $this->data = $object; - return $this->data; - } - } diff --git a/plugins/libkolab/lib/kolab_format_event.php b/plugins/libkolab/lib/kolab_format_event.php index d1e6b2e3..0a960b1a 100644 --- a/plugins/libkolab/lib/kolab_format_event.php +++ b/plugins/libkolab/lib/kolab_format_event.php @@ -1,307 +1,223 @@ * * Copyright (C) 2012, 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_format_event extends kolab_format_xcal +class kolab_format_event extends kolab_format { - public $CTYPEv2 = 'application/x-vnd.kolab.event'; + public $CTYPE = 'application/x-vnd.kolab.event'; - protected $objclass = 'Event'; - protected $read_func = 'readEvent'; - protected $write_func = 'writeEvent'; + protected $xmltype = 'event'; + + public static $fulltext_cols = array('title', 'description', 'location', 'attendees:name', 'attendees:email', 'categories'); private $kolab2_rolemap = array( 'required' => 'REQ-PARTICIPANT', 'optional' => 'OPT-PARTICIPANT', 'resource' => 'CHAIR', ); private $kolab2_statusmap = array( 'none' => 'NEEDS-ACTION', 'tentative' => 'TENTATIVE', 'accepted' => 'CONFIRMED', 'accepted' => 'ACCEPTED', 'declined' => 'DECLINED', ); private $kolab2_monthmap = array('', 'january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december'); /** * Clones into an instance of libcalendaring's extended EventCal class * * @return mixed EventCal object or false on failure */ public function to_libcal() { - return class_exists('kolabcalendaring') ? new EventCal($this->obj) : false; + return false; } /** * Set event properties to the kolabformat object * * @param array Event data as hash array */ public function set(&$object) { $this->init(); - // set common xcal properties - parent::set($object); - - // do the hard work of setting object values - $this->obj->setStart(self::get_datetime($object['start'], null, $object['allday'])); - $this->obj->setEnd(self::get_datetime($object['end'], null, $object['allday'])); - $this->obj->setTransparency($object['free_busy'] == 'free'); - - $status = kolabformat::StatusUndefined; - if ($object['free_busy'] == 'tentative') - $status = kolabformat::StatusTentative; - if ($object['cancelled']) - $status = kolabformat::StatusCancelled; - $this->obj->setStatus($status); - - // save attachments - $vattach = new vectorattachment; - foreach ((array)$object['_attachments'] as $cid => $attr) { - if (empty($attr)) - continue; - $attach = new Attachment; - $attach->setLabel((string)$attr['name']); - $attach->setUri('cid:' . $cid, $attr['mimetype']); - $vattach->push($attach); - } - $this->obj->setAttachments($vattach); + // TODO: implement this // cache this data $this->data = $object; unset($this->data['_formatobj']); } /** * */ public function is_valid() { - return $this->data || (is_object($this->obj) && $this->obj->isValid() && $this->obj->uid()); - } - - /** - * Convert the Event object into a hash array data structure - * - * @return array Event data as hash array - */ - public function to_array() - { - // return cached result - if (!empty($this->data)) - return $this->data; - - $this->init(); - - // read common xcal props - $object = parent::to_array(); - - // read object properties - $object += array( - 'end' => self::php_datetime($this->obj->end()), - 'allday' => $this->obj->start()->isDateOnly(), - 'free_busy' => $this->obj->transparency() ? 'free' : 'busy', // TODO: transparency is only boolean - 'attendees' => array(), - ); - - // organizer is part of the attendees list in Roundcube - if ($object['organizer']) { - $object['organizer']['role'] = 'ORGANIZER'; - array_unshift($object['attendees'], $object['organizer']); - } - - // status defines different event properties... - $status = $this->obj->status(); - if ($status == kolabformat::StatusTentative) - $object['free_busy'] = 'tentative'; - else if ($status == kolabformat::StatusCancelled) - $objec['cancelled'] = true; - - // handle attachments - $vattach = $this->obj->attachments(); - for ($i=0; $i < $vattach->size(); $i++) { - $attach = $vattach->get($i); - - // skip cid: attachments which are mime message parts handled by kolab_storage_folder - if (substr($attach->uri(), 0, 4) != 'cid:' && $attach->label()) { - $name = $attach->label(); - $data = $attach->data(); - $object['_attachments'][$name] = array( - 'name' => $name, - 'mimetype' => $attach->mimetype(), - 'size' => strlen($data), - 'content' => $data, - ); - } - } - - $this->data = $object; return $this->data; } /** * Callback for kolab_storage_cache to get object specific tags to cache * * @return array List of tags to save in cache */ public function get_tags() { $tags = array(); foreach ((array)$this->data['categories'] as $cat) { $tags[] = rcube_utils::normalize_string($cat); } if (!empty($this->data['alarms'])) { $tags[] = 'x-has-alarms'; } return $tags; } /** * Load data from old Kolab2 format */ public function fromkolab2($rec) { if (PEAR::isError($rec)) return; $start_time = date('H:i:s', $rec['start-date']); $allday = $rec['_is_all_day'] || ($start_time == '00:00:00' && $start_time == date('H:i:s', $rec['end-date'])); // in Roundcube all-day events go from 12:00 to 13:00 if ($allday) { $now = new DateTime('now', self::$timezone); $gmt_offset = $now->getOffset(); $rec['start-date'] += 12 * 3600; $rec['end-date'] -= 11 * 3600; $rec['end-date'] -= $gmt_offset - date('Z', $rec['end-date']); // shift times from server's timezone to user's timezone $rec['start-date'] -= $gmt_offset - date('Z', $rec['start-date']); // because generated with mktime() in Horde_Kolab_Format_Date::decodeDate() // sanity check if ($rec['end-date'] <= $rec['start-date']) $rec['end-date'] += 86400; } // convert alarm time into internal format if ($rec['alarm']) { $alarm_value = $rec['alarm']; $alarm_unit = 'M'; if ($rec['alarm'] % 1440 == 0) { $alarm_value /= 1440; $alarm_unit = 'D'; } else if ($rec['alarm'] % 60 == 0) { $alarm_value /= 60; $alarm_unit = 'H'; } $alarm_value *= -1; } // convert recurrence rules into internal pseudo-vcalendar format if ($recurrence = $rec['recurrence']) { $rrule = array( 'FREQ' => strtoupper($recurrence['cycle']), 'INTERVAL' => intval($recurrence['interval']), ); if ($recurrence['range-type'] == 'number') $rrule['COUNT'] = intval($recurrence['range']); else if ($recurrence['range-type'] == 'date') $rrule['UNTIL'] = date_create('@'.$recurrence['range']); if ($recurrence['day']) { $byday = array(); $prefix = ($rrule['FREQ'] == 'MONTHLY' || $rrule['FREQ'] == 'YEARLY') ? intval($recurrence['daynumber'] ? $recurrence['daynumber'] : 1) : ''; foreach ($recurrence['day'] as $day) $byday[] = $prefix . substr(strtoupper($day), 0, 2); $rrule['BYDAY'] = join(',', $byday); } if ($recurrence['daynumber']) { if ($recurrence['type'] == 'monthday' || $recurrence['type'] == 'daynumber') $rrule['BYMONTHDAY'] = $recurrence['daynumber']; else if ($recurrence['type'] == 'yearday') $rrule['BYYEARDAY'] = $recurrence['daynumber']; } if ($recurrence['month']) { $monthmap = array_flip($this->kolab2_monthmap); $rrule['BYMONTH'] = strtolower($monthmap[$recurrence['month']]); } if ($recurrence['exclusion']) { foreach ((array)$recurrence['exclusion'] as $excl) $rrule['EXDATE'][] = date_create($excl . date(' H:i:s', $rec['start-date'])); // use time of event start } } $attendees = array(); if ($rec['organizer']) { $attendees[] = array( 'role' => 'ORGANIZER', 'name' => $rec['organizer']['display-name'], 'email' => $rec['organizer']['smtp-address'], 'status' => 'ACCEPTED', ); $_attendees .= $rec['organizer']['display-name'] . ' ' . $rec['organizer']['smtp-address'] . ' '; } foreach ((array)$rec['attendee'] as $attendee) { $attendees[] = array( 'role' => $this->kolab2_rolemap[$attendee['role']], 'name' => $attendee['display-name'], 'email' => $attendee['smtp-address'], 'status' => $this->kolab2_statusmap[$attendee['status']], 'rsvp' => $attendee['request-response'], ); $_attendees .= $rec['organizer']['display-name'] . ' ' . $rec['organizer']['smtp-address'] . ' '; } $this->data = array( 'uid' => $rec['uid'], 'title' => $rec['summary'], 'location' => $rec['location'], 'description' => $rec['body'], 'start' => new DateTime('@'.$rec['start-date']), 'end' => new DateTime('@'.$rec['end-date']), 'allday' => $allday, 'recurrence' => $rrule, 'alarms' => $alarm_value . $alarm_unit, 'categories' => explode(',', $rec['categories']), 'attachments' => $attachments, 'attendees' => $attendees, 'free_busy' => $rec['show-time-as'], 'priority' => $rec['priority'], 'sensitivity' => $rec['sensitivity'], 'changed' => $rec['last-modification-date'], ); // assign current timezone to event start/end $this->data['start']->setTimezone(self::$timezone); $this->data['end']->setTimezone(self::$timezone); } } diff --git a/plugins/libkolab/lib/kolab_format_journal.php b/plugins/libkolab/lib/kolab_format_journal.php index 9144ea2d..3e1d8ede 100644 --- a/plugins/libkolab/lib/kolab_format_journal.php +++ b/plugins/libkolab/lib/kolab_format_journal.php @@ -1,108 +1,71 @@ * * Copyright (C) 2012, 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_format_journal extends kolab_format { - public $CTYPE = 'application/calendar+xml'; - public $CTYPEv2 = 'application/x-vnd.kolab.journal'; + public $CTYPE = 'application/x-vnd.kolab.journal'; - protected $objclass = 'Journal'; - protected $read_func = 'readJournal'; - protected $write_func = 'writeJournal'; + protected $xmltype = 'journal'; /** * Set properties to the kolabformat object * * @param array Object data as hash array */ public function set(&$object) { $this->init(); - // set some automatic values if missing - if (!empty($object['uid'])) - $this->obj->setUid($object['uid']); - - $object['changed'] = new DateTime('now', self::$timezone); - $this->obj->setLastModified(self::get_datetime($object['changed'], new DateTimeZone('UTC'))); - // TODO: set object propeties // cache this data $this->data = $object; unset($this->data['_formatobj']); } /** * */ public function is_valid() { - return $this->data || (is_object($this->obj) && $this->obj->isValid()); + return $this->data; } /** * Load data from old Kolab2 format */ public function fromkolab2($record) { $object = array( 'uid' => $record['uid'], 'changed' => $record['last-modification-date'], ); // TODO: implement this $this->data = $object; } - /** - * Convert the Configuration object into a hash array data structure - * - * @return array Config object data as hash array - */ - public function to_array() - { - // return cached result - if (!empty($this->data)) - return $this->data; - - $this->init(); - - // read object properties - $object = array( - 'uid' => $this->obj->uid(), - 'created' => self::php_datetime($this->obj->created()), - 'changed' => self::php_datetime($this->obj->lastModified()), - ); - - - // TODO: read object properties - - $this->data = $object; - return $this->data; - } - } diff --git a/plugins/libkolab/lib/kolab_format_note.php b/plugins/libkolab/lib/kolab_format_note.php index 48e963e1..da17f723 100644 --- a/plugins/libkolab/lib/kolab_format_note.php +++ b/plugins/libkolab/lib/kolab_format_note.php @@ -1,107 +1,71 @@ * * Copyright (C) 2012, 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_format_note extends kolab_format { public $CTYPE = 'application/x-vnd.kolab.note'; - public $CTYPEv2 = 'application/x-vnd.kolab.note'; - protected $objclass = 'Note'; - protected $read_func = 'readNote'; - protected $write_func = 'writeNote'; + protected $xmltype = 'note'; /** * Set properties to the kolabformat object * * @param array Object data as hash array */ public function set(&$object) { $this->init(); - // set some automatic values if missing - if (!empty($object['uid'])) - $this->obj->setUid($object['uid']); - - $object['changed'] = new DateTime('now', self::$timezone); - $this->obj->setLastModified(self::get_datetime($object['changed'], new DateTimeZone('UTC'))); - // TODO: set object propeties // cache this data $this->data = $object; unset($this->data['_formatobj']); } /** * */ public function is_valid() { - return $this->data || (is_object($this->obj) && $this->obj->isValid()); + return $this->data; } /** * Load data from old Kolab2 format */ public function fromkolab2($record) { $object = array( 'uid' => $record['uid'], 'changed' => $record['last-modification-date'], ); + // TODO: implement this $this->data = $object; } - /** - * Convert the Configuration object into a hash array data structure - * - * @return array Config object data as hash array - */ - public function to_array() - { - // return cached result - if (!empty($this->data)) - return $this->data; - - $this->init(); - - // read object properties - $object = array( - 'uid' => $this->obj->uid(), - 'created' => self::php_datetime($this->obj->created()), - 'changed' => self::php_datetime($this->obj->lastModified()), - ); - - - // TODO: read object properties - - $this->data = $object; - return $this->data; - } - } diff --git a/plugins/libkolab/lib/kolab_format_task.php b/plugins/libkolab/lib/kolab_format_task.php index 0bfac3dd..d1d29f4a 100644 --- a/plugins/libkolab/lib/kolab_format_task.php +++ b/plugins/libkolab/lib/kolab_format_task.php @@ -1,142 +1,96 @@ * * Copyright (C) 2012, 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_format_task extends kolab_format_xcal +class kolab_format_task extends kolab_format { - public $CTYPEv2 = 'application/x-vnd.kolab.task'; + public $CTYPE = 'application/x-vnd.kolab.task'; - protected $objclass = 'Todo'; - protected $read_func = 'readTodo'; - protected $write_func = 'writeTodo'; + protected $xmltype = 'task'; + + public static $fulltext_cols = array('title', 'description', 'location', 'attendees:name', 'attendees:email', 'categories'); /** * Set properties to the kolabformat object * * @param array Object data as hash array */ public function set(&$object) { $this->init(); - // set common xcal properties - parent::set($object); - - $this->obj->setPercentComplete(intval($object['complete'])); - - if (isset($object['start'])) - $this->obj->setStart(self::get_datetime($object['start'], null, $object['start']->_dateonly)); - - $this->obj->setDue(self::get_datetime($object['due'], null, $object['due']->_dateonly)); - - $related = new vectors; - if (!empty($object['parent_id'])) - $related->push($object['parent_id']); - $this->obj->setRelatedTo($related); + // TODO: implement this // cache this data $this->data = $object; unset($this->data['_formatobj']); } /** * */ public function is_valid() { - return $this->data || (is_object($this->obj) && $this->obj->isValid()); - } - - /** - * Convert the Configuration object into a hash array data structure - * - * @return array Config object data as hash array - */ - public function to_array() - { - // return cached result - if (!empty($this->data)) - return $this->data; - - $this->init(); - - // read common xcal props - $object = parent::to_array(); - - $object['complete'] = intval($this->obj->percentComplete()); - - // if due date is set - if ($due = $this->obj->due()) - $object['due'] = self::php_datetime($due); - - // related-to points to parent task; we only support one relation - $related = self::vector2array($this->obj->relatedTo()); - if (count($related)) - $object['parent_id'] = $related[0]; - - // TODO: map more properties - - $this->data = $object; return $this->data; } /** * Load data from old Kolab2 format */ public function fromkolab2($record) { $object = array( 'uid' => $record['uid'], 'changed' => $record['last-modification-date'], ); // TODO: implement this $this->data = $object; } /** * Callback for kolab_storage_cache to get object specific tags to cache * * @return array List of tags to save in cache */ public function get_tags() { $tags = array(); if ($this->data['status'] == 'COMPLETED' || $this->data['complete'] == 100) $tags[] = 'x-complete'; if ($this->data['priority'] == 1) $tags[] = 'x-flagged'; if (!empty($this->data['alarms'])) $tags[] = 'x-has-alarms'; if ($this->data['parent_id']) $tags[] = 'x-parent:' . $this->data['parent_id']; return $tags; } } diff --git a/plugins/libkolab/lib/kolab_format_xcal.php b/plugins/libkolab/lib/kolab_format_xcal.php deleted file mode 100644 index 3e1a7217..00000000 --- a/plugins/libkolab/lib/kolab_format_xcal.php +++ /dev/null @@ -1,400 +0,0 @@ - - * - * Copyright (C) 2012, 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 . - */ - -abstract class kolab_format_xcal extends kolab_format -{ - public $CTYPE = 'application/calendar+xml'; - - public static $fulltext_cols = array('title', 'description', 'location', 'attendees:name', 'attendees:email', 'categories'); - - protected $sensitivity_map = array( - 'public' => kolabformat::ClassPublic, - 'private' => kolabformat::ClassPrivate, - 'confidential' => kolabformat::ClassConfidential, - ); - - protected $role_map = array( - 'REQ-PARTICIPANT' => kolabformat::Required, - 'OPT-PARTICIPANT' => kolabformat::Optional, - 'NON-PARTICIPANT' => kolabformat::NonParticipant, - 'CHAIR' => kolabformat::Chair, - ); - - protected $rrule_type_map = array( - 'MINUTELY' => RecurrenceRule::Minutely, - 'HOURLY' => RecurrenceRule::Hourly, - 'DAILY' => RecurrenceRule::Daily, - 'WEEKLY' => RecurrenceRule::Weekly, - 'MONTHLY' => RecurrenceRule::Monthly, - 'YEARLY' => RecurrenceRule::Yearly, - ); - - protected $weekday_map = array( - 'MO' => kolabformat::Monday, - 'TU' => kolabformat::Tuesday, - 'WE' => kolabformat::Wednesday, - 'TH' => kolabformat::Thursday, - 'FR' => kolabformat::Friday, - 'SA' => kolabformat::Saturday, - 'SU' => kolabformat::Sunday, - ); - - protected $alarm_type_map = array( - 'DISPLAY' => Alarm::DisplayAlarm, - 'EMAIL' => Alarm::EMailAlarm, - 'AUDIO' => Alarm::AudioAlarm, - ); - - private $status_map = array( - 'NEEDS-ACTION' => kolabformat::StatusNeedsAction, - 'IN-PROCESS' => kolabformat::StatusInProcess, - 'COMPLETED' => kolabformat::StatusCompleted, - 'CANCELLED' => kolabformat::StatusCancelled, - ); - - protected $part_status_map = array( - 'UNKNOWN' => kolabformat::PartNeedsAction, - 'NEEDS-ACTION' => kolabformat::PartNeedsAction, - 'TENTATIVE' => kolabformat::PartTentative, - 'ACCEPTED' => kolabformat::PartAccepted, - 'DECLINED' => kolabformat::PartDeclined, - 'DELEGATED' => kolabformat::PartDelegated, - ); - - - /** - * Convert common xcard properties into a hash array data structure - * - * @return array Object data as hash array - */ - public function to_array() - { - $status_map = array_flip($this->status_map); - $sensitivity_map = array_flip($this->sensitivity_map); - - $object = array( - 'uid' => $this->obj->uid(), - 'created' => self::php_datetime($this->obj->created()), - 'changed' => self::php_datetime($this->obj->lastModified()), - 'sequence' => intval($this->obj->sequence()), - 'title' => $this->obj->summary(), - 'location' => $this->obj->location(), - 'description' => $this->obj->description(), - 'status' => $this->status_map[$this->obj->status()], - 'sensitivity' => $sensitivity_map[$this->obj->classification()], - 'priority' => $this->obj->priority(), - 'categories' => self::vector2array($this->obj->categories()), - 'start' => self::php_datetime($this->obj->start()), - ); - - // read organizer and attendees - if ($organizer = $this->obj->organizer()) { - $object['organizer'] = array( - 'email' => $organizer->email(), - 'name' => $organizer->name(), - ); - } - - $role_map = array_flip($this->role_map); - $part_status_map = array_flip($this->part_status_map); - $attvec = $this->obj->attendees(); - for ($i=0; $i < $attvec->size(); $i++) { - $attendee = $attvec->get($i); - $cr = $attendee->contact(); - $object['attendees'][] = array( - 'role' => $role_map[$attendee->role()], - 'status' => $part_status_map[$attendee->partStat()], - 'rsvp' => $attendee->rsvp(), - 'email' => $cr->email(), - 'name' => $cr->name(), - ); - } - - // read recurrence rule - if (($rr = $this->obj->recurrenceRule()) && $rr->isValid()) { - $rrule_type_map = array_flip($this->rrule_type_map); - $object['recurrence'] = array('FREQ' => $rrule_type_map[$rr->frequency()]); - - if ($intvl = $rr->interval()) - $object['recurrence']['INTERVAL'] = $intvl; - - if (($count = $rr->count()) && $count > 0) { - $object['recurrence']['COUNT'] = $count; - } - else if ($until = self::php_datetime($rr->end())) { - $until->setTime($object['start']->format('G'), $object['start']->format('i'), 0); - $object['recurrence']['UNTIL'] = $until; - } - - if (($byday = $rr->byday()) && $byday->size()) { - $weekday_map = array_flip($this->weekday_map); - $weekdays = array(); - for ($i=0; $i < $byday->size(); $i++) { - $daypos = $byday->get($i); - $prefix = $daypos->occurence(); - $weekdays[] = ($prefix ? $prefix : '') . $weekday_map[$daypos->weekday()]; - } - $object['recurrence']['BYDAY'] = join(',', $weekdays); - } - - if (($bymday = $rr->bymonthday()) && $bymday->size()) { - $object['recurrence']['BYMONTHDAY'] = join(',', self::vector2array($bymday)); - } - - if (($bymonth = $rr->bymonth()) && $bymonth->size()) { - $object['recurrence']['BYMONTH'] = join(',', self::vector2array($bymonth)); - } - - if ($exceptions = $this->obj->exceptionDates()) { - for ($i=0; $i < $exceptions->size(); $i++) { - if ($exdate = self::php_datetime($exceptions->get($i))) - $object['recurrence']['EXDATE'][] = $exdate; - } - } - } - - // read alarm - $valarms = $this->obj->alarms(); - $alarm_types = array_flip($this->alarm_type_map); - for ($i=0; $i < $valarms->size(); $i++) { - $alarm = $valarms->get($i); - $type = $alarm_types[$alarm->type()]; - - if ($type == 'DISPLAY' || $type == 'EMAIL') { // only DISPLAY and EMAIL alarms are supported - if ($start = self::php_datetime($alarm->start())) { - $object['alarms'] = '@' . $start->format('U'); - } - else if ($offset = $alarm->relativeStart()) { - $value = $alarm->relativeTo() == kolabformat::End ? '+' : '-'; - if ($w = $offset->weeks()) $value .= $w . 'W'; - else if ($d = $offset->days()) $value .= $d . 'D'; - else if ($h = $offset->hours()) $value .= $h . 'H'; - else if ($m = $offset->minutes()) $value .= $m . 'M'; - else if ($s = $offset->seconds()) $value .= $s . 'S'; - else continue; - - $object['alarms'] = $value; - } - $object['alarms'] .= ':' . $type; - break; - } - } - - return $object; - } - - - /** - * Set common xcal properties to the kolabformat object - * - * @param array Event data as hash array - */ - public function set(&$object) - { - $is_new = !$this->obj->uid(); - - // set some automatic values if missing - if (!$this->obj->created()) { - if (!empty($object['created'])) - $object['created'] = new DateTime('now', self::$timezone); - $this->obj->setCreated(self::get_datetime($object['created'])); - } - - if (!empty($object['uid'])) - $this->obj->setUid($object['uid']); - - $object['changed'] = new DateTime('now', self::$timezone); - $this->obj->setLastModified(self::get_datetime($object['changed'], new DateTimeZone('UTC'))); - - // increment sequence on updates - $object['sequence'] = !$is_new ? $this->obj->sequence()+1 : 0; - $this->obj->setSequence($object['sequence']); - - $this->obj->setSummary($object['title']); - $this->obj->setLocation($object['location']); - $this->obj->setDescription($object['description']); - $this->obj->setPriority($object['priority']); - $this->obj->setClassification($this->sensitivity_map[$object['sensitivity']]); - $this->obj->setCategories(self::array2vector($object['categories'])); - - // process event attendees - $attendees = new vectorattendee; - foreach ((array)$object['attendees'] as $attendee) { - if ($attendee['role'] == 'ORGANIZER') { - $object['organizer'] = $attendee; - } - else { - $cr = new ContactReference(ContactReference::EmailReference, $attendee['email']); - $cr->setName($attendee['name']); - - $att = new Attendee; - $att->setContact($cr); - $att->setPartStat($this->status_map[$attendee['status']]); - $att->setRole($this->role_map[$attendee['role']] ? $this->role_map[$attendee['role']] : kolabformat::Required); - $att->setRSVP((bool)$attendee['rsvp']); - - if ($att->isValid()) { - $attendees->push($att); - } - else { - rcube::raise_error(array( - 'code' => 600, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Invalid event attendee: " . json_encode($attendee), - ), true); - } - } - } - $this->obj->setAttendees($attendees); - - if ($object['organizer']) { - $organizer = new ContactReference(ContactReference::EmailReference, $object['organizer']['email']); - $organizer->setName($object['organizer']['name']); - $this->obj->setOrganizer($organizer); - } - - // save recurrence rule - if ($object['recurrence']) { - $rr = new RecurrenceRule; - $rr->setFrequency($this->rrule_type_map[$object['recurrence']['FREQ']]); - - if ($object['recurrence']['INTERVAL']) - $rr->setInterval(intval($object['recurrence']['INTERVAL'])); - - if ($object['recurrence']['BYDAY']) { - $byday = new vectordaypos; - foreach (explode(',', $object['recurrence']['BYDAY']) as $day) { - $occurrence = 0; - if (preg_match('/^([\d-]+)([A-Z]+)$/', $day, $m)) { - $occurrence = intval($m[1]); - $day = $m[2]; - } - if (isset($this->weekday_map[$day])) - $byday->push(new DayPos($occurrence, $this->weekday_map[$day])); - } - $rr->setByday($byday); - } - - if ($object['recurrence']['BYMONTHDAY']) { - $bymday = new vectori; - foreach (explode(',', $object['recurrence']['BYMONTHDAY']) as $day) - $bymday->push(intval($day)); - $rr->setBymonthday($bymday); - } - - if ($object['recurrence']['BYMONTH']) { - $bymonth = new vectori; - foreach (explode(',', $object['recurrence']['BYMONTH']) as $month) - $bymonth->push(intval($month)); - $rr->setBymonth($bymonth); - } - - if ($object['recurrence']['COUNT']) - $rr->setCount(intval($object['recurrence']['COUNT'])); - else if ($object['recurrence']['UNTIL']) - $rr->setEnd(self::get_datetime($object['recurrence']['UNTIL'], null, true)); - - if ($rr->isValid()) { - $this->obj->setRecurrenceRule($rr); - - // add exception dates (only if recurrence rule is valid) - $exdates = new vectordatetime; - foreach ((array)$object['recurrence']['EXDATE'] as $exdate) - $exdates->push(self::get_datetime($exdate, null, true)); - $this->obj->setExceptionDates($exdates); - } - else { - rcube::raise_error(array( - 'code' => 600, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Invalid event recurrence rule: " . json_encode($object['recurrence']), - ), true); - } - } - - // save alarm - $valarms = new vectoralarm; - if ($object['alarms']) { - list($offset, $type) = explode(":", $object['alarms']); - - if ($type == 'EMAIL') { // email alarms implicitly go to event owner - $recipients = new vectorcontactref; - $recipients->push(new ContactReference(ContactReference::EmailReference, $object['_owner'])); - $alarm = new Alarm($object['title'], strval($object['description']), $recipients); - } - else { // default: display alarm - $alarm = new Alarm($object['title']); - } - - if (preg_match('/^@(\d+)/', $offset, $d)) { - $alarm->setStart(self::get_datetime($d[1], new DateTimeZone('UTC'))); - } - else if (preg_match('/^([-+]?)(\d+)([SMHDW])/', $offset, $d)) { - $days = $hours = $minutes = $seconds = 0; - switch ($d[3]) { - case 'W': $days = 7*intval($d[2]); break; - case 'D': $days = intval($d[2]); break; - case 'H': $hours = intval($d[2]); break; - case 'M': $minutes = intval($d[2]); break; - case 'S': $seconds = intval($d[2]); break; - } - $alarm->setRelativeStart(new Duration($days, $hours, $minutes, $seconds, $d[1] == '-'), $d[1] == '-' ? kolabformat::Start : kolabformat::End); - } - - $valarms->push($alarm); - } - $this->obj->setAlarms($valarms); - } - - /** - * Callback for kolab_storage_cache to get words to index for fulltext search - * - * @return array List of words to save in cache - */ - public function get_words() - { - $data = ''; - foreach (self::$fulltext_cols as $colname) { - list($col, $field) = explode(':', $colname); - - if ($field) { - $a = array(); - foreach ((array)$this->data[$col] as $attr) - $a[] = $attr[$field]; - $val = join(' ', $a); - } - else { - $val = is_array($this->data[$col]) ? join(' ', $this->data[$col]) : $this->data[$col]; - } - - if (strlen($val)) - $data .= $val . ' '; - } - - return array_unique(rcube_utils::normalize_string($data, true)); - } - -} \ No newline at end of file diff --git a/plugins/libkolab/lib/kolab_storage.php b/plugins/libkolab/lib/kolab_storage.php index ac7de34b..79f949a1 100644 --- a/plugins/libkolab/lib/kolab_storage.php +++ b/plugins/libkolab/lib/kolab_storage.php @@ -1,656 +1,656 @@ * * Copyright (C) 2012, 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_storage { const CTYPE_KEY = '/shared/vendor/kolab/folder-type'; const CTYPE_KEY_PRIVATE = '/private/vendor/kolab/folder-type'; const COLOR_KEY_SHARED = '/shared/vendor/kolab/color'; const COLOR_KEY_PRIVATE = '/private/vendor/kolab/color'; const SERVERSIDE_SUBSCRIPTION = 0; const CLIENTSIDE_SUBSCRIPTION = 1; public static $version = 3.0; public static $last_error; private static $ready = false; private static $config; private static $cache; private static $imap; /** * Setup the environment needed by the libs */ public static function setup() { if (self::$ready) return true; $rcmail = rcube::get_instance(); self::$config = $rcmail->config; self::$version = $rcmail->config->get('kolab_format_version', self::$version); self::$imap = $rcmail->get_storage(); - self::$ready = class_exists('kolabformat') && + self::$ready = class_exists('Horde_Kolab_Format') && (self::$imap->get_capability('METADATA') || self::$imap->get_capability('ANNOTATEMORE') || self::$imap->get_capability('ANNOTATEMORE2')); if (self::$ready) { // set imap options self::$imap->set_options(array( 'skip_deleted' => true, 'threading' => false, )); self::$imap->set_pagesize(9999); } return self::$ready; } /** * Get a list of storage folders for the given data type * * @param string Data type to list folders for (contact,distribution-list,event,task,note) * * @return array List of Kolab_Folder objects (folder names in UTF7-IMAP) */ public static function get_folders($type) { $folders = $folderdata = array(); if (self::setup()) { foreach ((array)self::list_folders('', '*', $type, false, $folderdata) as $foldername) { $folders[$foldername] = new kolab_storage_folder($foldername, $folderdata[$foldername]); } } return $folders; } /** * Getter for a specific storage folder * * @param string IMAP folder to access (UTF7-IMAP) * @return object kolab_storage_folder The folder object */ public static function get_folder($folder) { return self::setup() ? new kolab_storage_folder($folder) : null; } /** * Getter for a single Kolab object, identified by its UID. * This will search all folders storing objects of the given type. * * @param string Object UID * @param string Object type (contact,distribution-list,event,task,note) * @return array The Kolab object represented as hash array or false if not found */ public static function get_object($uid, $type) { self::setup(); $folder = null; foreach ((array)self::list_folders('', '*', $type) as $foldername) { if (!$folder) $folder = new kolab_storage_folder($foldername); else $folder->set_folder($foldername); if ($object = $folder->get_object($uid)) return $object; } return false; } /** * */ public static function get_freebusy_server() { return unslashify(self::$config->get('kolab_freebusy_server', 'https://' . $_SESSION['imap_host'] . '/freebusy')); } /** * Compose an URL to query the free/busy status for the given user */ public static function get_freebusy_url($email) { return self::get_freebusy_server() . '/' . $email . '.ifb'; } /** * Creates folder ID from folder name * * @param string $folder Folder name (UTF7-IMAP) * * @return string Folder ID string */ public static function folder_id($folder) { return asciiwords(strtr($folder, '/.-', '___')); } /** * Deletes IMAP folder * * @param string $name Folder name (UTF7-IMAP) * * @return bool True on success, false on failure */ public static function folder_delete($name) { // clear cached entries first if ($folder = self::get_folder($name)) $folder->cache->purge(); $success = self::$imap->delete_folder($name); self::$last_error = self::$imap->get_error_str(); return $success; } /** * Creates IMAP folder * * @param string $name Folder name (UTF7-IMAP) * @param string $type Folder type * @param bool $subscribed Sets folder subscription * * @return bool True on success, false on failure */ public static function folder_create($name, $type = null, $subscribed = false) { self::setup(); if ($saved = self::$imap->create_folder($name, $subscribed)) { // set metadata for folder type if ($type) { $saved = self::set_folder_type($name, $type); // revert if metadata could not be set if (!$saved) { self::$imap->delete_folder($name); } } } if ($saved) { return true; } self::$last_error = self::$imap->get_error_str(); return false; } /** * Renames IMAP folder * * @param string $oldname Old folder name (UTF7-IMAP) * @param string $newname New folder name (UTF7-IMAP) * * @return bool True on success, false on failure */ public static function folder_rename($oldname, $newname) { self::setup(); $success = self::$imap->rename_folder($oldname, $newname); self::$last_error = self::$imap->get_error_str(); return $success; } /** * Rename or Create a new IMAP folder. * * Does additional checks for permissions and folder name restrictions * * @param array Hash array with folder properties and metadata * - name: Folder name * - oldname: Old folder name when changed * - parent: Parent folder to create the new one in * - type: Folder type to create * @return mixed New folder name or False on failure */ public static function folder_update(&$prop) { self::setup(); $folder = rcube_charset::convert($prop['name'], RCMAIL_CHARSET, 'UTF7-IMAP'); $oldfolder = $prop['oldname']; // UTF7 $parent = $prop['parent']; // UTF7 $delimiter = self::$imap->get_hierarchy_delimiter(); if (strlen($oldfolder)) { $options = self::$imap->folder_info($oldfolder); } if (!empty($options) && ($options['norename'] || $options['protected'])) { } // sanity checks (from steps/settings/save_folder.inc) else if (!strlen($folder)) { self::$last_error = 'cannotbeempty'; return false; } else if (strlen($folder) > 128) { self::$last_error = 'nametoolong'; return false; } else { // these characters are problematic e.g. when used in LIST/LSUB foreach (array($delimiter, '%', '*') as $char) { if (strpos($folder, $delimiter) !== false) { self::$last_error = 'forbiddencharacter'; return false; } } } if (!empty($options) && ($options['protected'] || $options['norename'])) { $folder = $oldfolder; } else if (strlen($parent)) { $folder = $parent . $delimiter . $folder; } else { // add namespace prefix (when needed) $folder = self::$imap->mod_folder($folder, 'in'); } // Check access rights to the parent folder if (strlen($parent) && (!strlen($oldfolder) || $oldfolder != $folder)) { $parent_opts = self::$imap->folder_info($parent); if ($parent_opts['namespace'] != 'personal' && (empty($parent_opts['rights']) || !preg_match('/[ck]/', implode($parent_opts['rights']))) ) { self::$last_error = 'No permission to create folder'; return false; } } // update the folder name if (strlen($oldfolder)) { if ($oldfolder != $folder) { $result = self::folder_rename($oldfolder, $folder); } else $result = true; } // create new folder else { $result = self::folder_create($folder, $prop['type'], $prop['subscribed'] === self::SERVERSIDE_SUBSCRIPTION); } // save color in METADATA // TODO: also save 'showalarams' and other properties here if ($result && $prop['color']) { $meta_saved = false; $ns = self::$imap->folder_namespace($folder); if ($ns == 'personal') // save in shared namespace for personal folders $meta_saved = self::$imap->set_metadata($folder, array(self::COLOR_KEY_SHARED => $prop['color'])); if (!$meta_saved) // try in private namespace $meta_saved = self::$imap->set_metadata($folder, array(self::COLOR_KEY_PRIVATE => $prop['color'])); if ($meta_saved) unset($prop['color']); // unsetting will prevent fallback to local user prefs } return $result ? $folder : false; } /** * Getter for human-readable name of Kolab object (folder) * See http://wiki.kolab.org/UI-Concepts/Folder-Listing for reference * * @param string $folder IMAP folder name (UTF7-IMAP) * @param string $folder_ns Will be set to namespace name of the folder * * @return string Name of the folder-object */ public static function object_name($folder, &$folder_ns=null) { self::setup(); $found = false; $namespace = self::$imap->get_namespace(); if (!empty($namespace['shared'])) { foreach ($namespace['shared'] as $ns) { if (strlen($ns[0]) && strpos($folder, $ns[0]) === 0) { $prefix = ''; $folder = substr($folder, strlen($ns[0])); $delim = $ns[1]; $found = true; $folder_ns = 'shared'; break; } } } if (!$found && !empty($namespace['other'])) { foreach ($namespace['other'] as $ns) { if (strlen($ns[0]) && strpos($folder, $ns[0]) === 0) { // remove namespace prefix $folder = substr($folder, strlen($ns[0])); $delim = $ns[1]; // get username $pos = strpos($folder, $delim); if ($pos) { $prefix = '('.substr($folder, 0, $pos).') '; $folder = substr($folder, $pos+1); } else { $prefix = '('.$folder.')'; $folder = ''; } $found = true; $folder_ns = 'other'; break; } } } if (!$found && !empty($namespace['personal'])) { foreach ($namespace['personal'] as $ns) { if (strlen($ns[0]) && strpos($folder, $ns[0]) === 0) { // remove namespace prefix $folder = substr($folder, strlen($ns[0])); $prefix = ''; $delim = $ns[1]; $found = true; break; } } } if (empty($delim)) $delim = self::$imap->get_hierarchy_delimiter(); $folder = rcube_charset::convert($folder, 'UTF7-IMAP'); $folder = html::quote($folder); $folder = str_replace(html::quote($delim), ' » ', $folder); if ($prefix) $folder = html::quote($prefix) . ' ' . $folder; if (!$folder_ns) $folder_ns = 'personal'; return $folder; } /** * Helper method to generate a truncated folder name to display */ public static function folder_displayname($origname, &$names) { $name = $origname; // find folder prefix to truncate for ($i = count($names)-1; $i >= 0; $i--) { if (strpos($name, $names[$i] . ' » ') === 0) { $length = strlen($names[$i] . ' » '); $prefix = substr($name, 0, $length); $count = count(explode(' » ', $prefix)); $name = str_repeat('  ', $count-1) . '» ' . substr($name, $length); break; } } $names[] = $origname; return $name; } /** * Creates a SELECT field with folders list * * @param string $type Folder type * @param array $attrs SELECT field attributes (e.g. name) * @param string $current The name of current folder (to skip it) * * @return html_select SELECT object */ public static function folder_selector($type, $attrs, $current = '') { // get all folders of specified type $folders = self::get_folders($type); $delim = self::$imap->get_hierarchy_delimiter(); $names = array(); $len = strlen($current); if ($len && ($rpos = strrpos($current, $delim))) { $parent = substr($current, 0, $rpos); $p_len = strlen($parent); } // Filter folders list foreach ($folders as $c_folder) { $name = $c_folder->name; // skip current folder and it's subfolders if ($len && ($name == $current || strpos($name, $current.$delim) === 0)) { continue; } // always show the parent of current folder if ($p_len && $name == $parent) { } // skip folders where user have no rights to create subfolders else if ($c_folder->get_owner() != $_SESSION['username']) { $rights = $c_folder->get_myrights(); if (!preg_match('/[ck]/', $rights)) { continue; } } $names[$name] = rcube_charset::convert($name, 'UTF7-IMAP'); } // Make sure parent folder is listed (might be skipped e.g. if it's namespace root) if ($p_len && !isset($names[$parent])) { $names[$parent] = rcube_charset::convert($parent, 'UTF7-IMAP'); } // Sort folders list asort($names, SORT_LOCALE_STRING); $folders = array_keys($names); $names = array(); // Build SELECT field of parent folder $attrs['is_escaped'] = true; $select = new html_select($attrs); $select->add('---', ''); foreach ($folders as $name) { $imap_name = $name; $name = $origname = self::object_name($name); // find folder prefix to truncate for ($i = count($names)-1; $i >= 0; $i--) { if (strpos($name, $names[$i].' » ') === 0) { $length = strlen($names[$i].' » '); $prefix = substr($name, 0, $length); $count = count(explode(' » ', $prefix)); $name = str_repeat('  ', $count-1) . '» ' . substr($name, $length); break; } } $names[] = $origname; $select->add($name, $imap_name); } return $select; } /** * Returns a list of folder names * * @param string Optional root folder * @param string Optional name pattern * @param string Data type to list folders for (contact,distribution-list,event,task,note,mail) * @param string Enable to return subscribed folders only * @param array Will be filled with folder-types data * * @return array List of folders */ public static function list_folders($root = '', $mbox = '*', $filter = null, $subscribed = false, &$folderdata = array()) { if (!self::setup()) { return null; } if (!$filter) { // Get ALL folders list, standard way if ($subscribed) { return self::$imap->list_folders_subscribed($root, $mbox); } else { return self::$imap->list_folders($root, $mbox); } } $prefix = $root . $mbox; // get folders types $folderdata = self::$imap->get_metadata($prefix, array(self::CTYPE_KEY, self::CTYPE_KEY_PRIVATE)); if (!is_array($folderdata)) { return array(); } $folderdata = array_map(array('kolab_storage', 'folder_select_metadata'), $folderdata); $regexp = '/^' . preg_quote($filter, '/') . '(\..+)?$/'; // In some conditions we can skip LIST command (?) if ($subscribed == false && $filter != 'mail' && $prefix == '*') { foreach ($folderdata as $folder => $type) { if (!preg_match($regexp, $type)) { unset($folderdata[$folder]); } } return array_keys($folderdata); } // Get folders list if ($subscribed) { $folders = self::$imap->list_folders_subscribed($root, $mbox); } else { $folders = self::$imap->list_folders($root, $mbox); } // In case of an error, return empty list (?) if (!is_array($folders)) { return array(); } // Filter folders list foreach ($folders as $idx => $folder) { $type = $folderdata[$folder]; if ($filter == 'mail' && empty($type)) { continue; } if (empty($type) || !preg_match($regexp, $type)) { unset($folders[$idx]); } } return $folders; } /** * Callback for array_map to select the correct annotation value */ static function folder_select_metadata($types) { if (!empty($types[self::CTYPE_KEY_PRIVATE])) { return $types[self::CTYPE_KEY_PRIVATE]; } else if (!empty($types[self::CTYPE_KEY])) { list($ctype, $suffix) = explode('.', $types[self::CTYPE_KEY]); return $ctype; } return null; } /** * Returns type of IMAP folder * * @param string $folder Folder name (UTF7-IMAP) * * @return string Folder type */ static function folder_type($folder) { self::setup(); $metadata = self::$imap->get_metadata($folder, array(self::CTYPE_KEY, self::CTYPE_KEY_PRIVATE)); if (!is_array($metadata)) { return null; } if (!empty($metadata[$folder])) { return self::folder_select_metadata($metadata[$folder]); } return 'mail'; } /** * Sets folder content-type. * * @param string $folder Folder name * @param string $type Content type * * @return boolean True on success */ static function set_folder_type($folder, $type='mail') { self::setup(); list($ctype, $subtype) = explode('.', $type); $success = self::$imap->set_metadata($folder, array(self::CTYPE_KEY => $ctype, self::CTYPE_KEY_PRIVATE => $subtype ? $type : null)); if (!$success) // fallback: only set private annotation $success |= self::$imap->set_metadata($folder, array(self::CTYPE_KEY_PRIVATE => $type)); return $success; } } diff --git a/plugins/libkolab/lib/kolab_storage_cache.php b/plugins/libkolab/lib/kolab_storage_cache.php index 9eac164e..f5dbe017 100644 --- a/plugins/libkolab/lib/kolab_storage_cache.php +++ b/plugins/libkolab/lib/kolab_storage_cache.php @@ -1,729 +1,729 @@ * * Copyright (C) 2012, 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_storage_cache { private $db; private $imap; private $folder; private $uid2msg; private $objects; private $index = array(); private $resource_uri; private $enabled = true; private $synched = false; private $synclock = false; private $ready = false; private $max_sql_packet = 1046576; // 1 MB - 2000 bytes private $binary_cols = array('photo','pgppublickey','pkcs7publickey'); /** * Default constructor */ public function __construct(kolab_storage_folder $storage_folder = null) { $rcmail = rcube::get_instance(); $this->db = $rcmail->get_dbh(); $this->imap = $rcmail->get_storage(); $this->enabled = $rcmail->config->get('kolab_cache', false); if ($this->enabled) { // remove sync-lock on script termination $rcmail->add_shutdown_function(array($this, '_sync_unlock')); // read max_allowed_packet from mysql config $this->max_sql_packet = min($this->db->get_variable('max_allowed_packet', 1048500), 4*1024*1024) - 2000; // mysql limit or max 4 MB } if ($storage_folder) $this->set_folder($storage_folder); } /** * Connect cache with a storage folder * * @param kolab_storage_folder The storage folder instance to connect with */ public function set_folder(kolab_storage_folder $storage_folder) { $this->folder = $storage_folder; if (empty($this->folder->name)) { $this->ready = false; return; } // compose fully qualified ressource uri for this instance $this->resource_uri = $this->folder->get_resource_uri(); $this->ready = $this->enabled; } /** * Synchronize local cache data with remote */ public function synchronize() { // only sync once per request cycle if ($this->synched) return; // increase time limit @set_time_limit(500); // lock synchronization for this folder or wait if locked $this->_sync_lock(); // synchronize IMAP mailbox cache $this->imap->folder_sync($this->folder->name); // compare IMAP index with object cache index $imap_index = $this->imap->index($this->folder->name); $this->index = $imap_index->get(); // determine objects to fetch or to invalidate if ($this->ready) { // read cache index $sql_result = $this->db->query( "SELECT msguid, uid FROM kolab_cache WHERE resource=? AND type<>?", $this->resource_uri, 'lock' ); $old_index = array(); while ($sql_arr = $this->db->fetch_assoc($sql_result)) { $old_index[] = $sql_arr['msguid']; $this->uid2msg[$sql_arr['uid']] = $sql_arr['msguid']; } // fetch new objects from imap foreach (array_diff($this->index, $old_index) as $msguid) { if ($object = $this->folder->read_object($msguid, '*')) { $this->_extended_insert($msguid, $object); } } $this->_extended_insert(0, null); // delete invalid entries from local DB $del_index = array_diff($old_index, $this->index); if (!empty($del_index)) { $quoted_ids = join(',', array_map(array($this->db, 'quote'), $del_index)); $this->db->query( "DELETE FROM kolab_cache WHERE resource=? AND msguid IN ($quoted_ids)", $this->resource_uri ); } } // remove lock $this->_sync_unlock(); $this->synched = time(); } /** * Read a single entry from cache or from IMAP directly * * @param string Related IMAP message UID * @param string Object type to read * @param string IMAP folder name the entry relates to * @param array Hash array with object properties or null if not found */ public function get($msguid, $type = null, $foldername = null) { // delegate to another cache instance if ($foldername && $foldername != $this->folder->name) { return kolab_storage::get_folder($foldername)->cache->get($msguid, $object); } // load object if not in memory if (!isset($this->objects[$msguid])) { if ($this->ready) { $sql_result = $this->db->query( "SELECT * FROM kolab_cache ". "WHERE resource=? AND type=? AND msguid=?", $this->resource_uri, $type ?: $this->folder->type, $msguid ); if ($sql_arr = $this->db->fetch_assoc($sql_result)) { $this->objects[$msguid] = $this->_unserialize($sql_arr); } } // fetch from IMAP if not present in cache if (empty($this->objects[$msguid])) { $result = $this->_fetch(array($msguid), $type, $foldername); $this->objects[$msguid] = $result[0]; } } return $this->objects[$msguid]; } /** * Insert/Update a cache entry * * @param string Related IMAP message UID * @param mixed Hash array with object properties to save or false to delete the cache entry * @param string IMAP folder name the entry relates to */ public function set($msguid, $object, $foldername = null) { if (!$msguid) { return; } // delegate to another cache instance if ($foldername && $foldername != $this->folder->name) { kolab_storage::get_folder($foldername)->cache->set($msguid, $object); return; } // remove old entry if ($this->ready) { $this->db->query("DELETE FROM kolab_cache WHERE resource=? AND msguid=? AND type<>?", $this->resource_uri, $msguid, 'lock'); } if ($object) { // insert new object data... $this->insert($msguid, $object); } else { // ...or set in-memory cache to false $this->objects[$msguid] = $object; } } /** * Insert a cache entry * * @param string Related IMAP message UID * @param mixed Hash array with object properties to save or false to delete the cache entry */ public function insert($msguid, $object) { // write to cache if ($this->ready) { $sql_data = $this->_serialize($object); $objtype = $object['_type'] ? $object['_type'] : $this->folder->type; $result = $this->db->query( "INSERT INTO kolab_cache ". " (resource, type, msguid, uid, created, changed, data, xml, dtstart, dtend, tags, words)". " VALUES (?, ?, ?, ?, " . $this->db->now() . ", ?, ?, ?, ?, ?, ?, ?)", $this->resource_uri, $objtype, $msguid, $object['uid'], $sql_data['changed'], $sql_data['data'], $sql_data['xml'], $sql_data['dtstart'], $sql_data['dtend'], $sql_data['tags'], $sql_data['words'] ); if (!$this->db->affected_rows($result)) { rcube::raise_error(array( 'code' => 900, 'type' => 'php', 'message' => "Failed to write to kolab cache" ), true); } } // keep a copy in memory for fast access $this->objects[$msguid] = $object; $this->uid2msg[$object['uid']] = $msguid; } /** * Move an existing cache entry to a new resource * * @param string Entry's IMAP message UID * @param string Entry's Object UID * @param string Target IMAP folder to move it to */ public function move($msguid, $objuid, $target_folder) { $target = kolab_storage::get_folder($target_folder); // resolve new message UID in target folder if ($new_msguid = $target->cache->uid2msguid($objuid)) { $this->db->query( "UPDATE kolab_cache SET resource=?, msguid=? ". "WHERE resource=? AND msguid=? AND type<>?", $target->get_resource_uri(), $new_msguid, $this->resource_uri, $msguid, 'lock' ); } else { // just clear cache entry $this->set($msguid, false); } unset($this->uid2msg[$uid]); } /** * Remove all objects from local cache */ public function purge($type = null) { $result = $this->db->query( "DELETE FROM kolab_cache WHERE resource=?". ($type ? ' AND type=?' : ''), $this->resource_uri, $type ); return $this->db->affected_rows($result); } /** * Select Kolab objects filtered by the given query * * @param array Pseudo-SQL query as list of filter parameter triplets * triplet: array('', '', '') * @param boolean Set true to only return UIDs instead of complete objects * @return array List of Kolab data objects (each represented as hash array) or UIDs */ public function select($query = array(), $uids = false) { $result = array(); // read from local cache DB (assume it to be synchronized) if ($this->ready) { $sql_result = $this->db->query( "SELECT " . ($uids ? 'msguid, uid' : '*') . " FROM kolab_cache ". "WHERE resource=? " . $this->_sql_where($query), $this->resource_uri ); while ($sql_arr = $this->db->fetch_assoc($sql_result)) { if ($uids) { $this->uid2msg[$sql_arr['uid']] = $sql_arr['msguid']; $result[] = $sql_arr['uid']; } else if ($object = $this->_unserialize($sql_arr)) { $result[] = $object; } } } else { // extract object type from query parameter $filter = $this->_query2assoc($query); // use 'list' for folder's default objects if ($filter['type'] == $this->type) { $index = $this->index; } else { // search by object type $search = 'UNDELETED HEADER X-Kolab-Type ' . kolab_format::KTYPE_PREFIX . $filter['type']; $index = $this->imap->search_once($this->folder->name, $search)->get(); } // fetch all messages in $index from IMAP $result = $uids ? $this->_fetch_uids($index, $filter['type']) : $this->_fetch($index, $filter['type']); // TODO: post-filter result according to query } return $result; } /** * Get number of objects mathing the given query * * @param array $query Pseudo-SQL query as list of filter parameter triplets * @return integer The number of objects of the given type */ public function count($query = array()) { $count = 0; // cache is in sync, we can count records in local DB if ($this->synched) { $sql_result = $this->db->query( "SELECT COUNT(*) AS numrows FROM kolab_cache ". "WHERE resource=? " . $this->_sql_where($query), $this->resource_uri ); $sql_arr = $this->db->fetch_assoc($sql_result); $count = intval($sql_arr['numrows']); } else { // search IMAP by object type $filter = $this->_query2assoc($query); $ctype = kolab_format::KTYPE_PREFIX . $filter['type']; $index = $this->imap->search_once($this->folder->name, 'UNDELETED HEADER X-Kolab-Type ' . $ctype); $count = $index->count(); } return $count; } /** * Helper method to compose a valid SQL query from pseudo filter triplets */ private function _sql_where($query) { $sql_where = ''; foreach ($query as $param) { if ($param[1] == '=' && is_array($param[2])) { $qvalue = '(' . join(',', array_map(array($this->db, 'quote'), $param[2])) . ')'; $param[1] = 'IN'; } else if ($param[1] == '~' || $param[1] == 'LIKE' || $param[1] == '!~' || $param[1] == '!LIKE') { $not = ($param[1] == '!~' || $param[1] == '!LIKE') ? 'NOT ' : ''; $param[1] = $not . 'LIKE'; $qvalue = $this->db->quote('%'.preg_replace('/(^\^|\$$)/', ' ', $param[2]).'%'); } else if ($param[0] == 'tags') { $param[1] = 'LIKE'; $qvalue = $this->db->quote('% '.$param[2].' %'); } else { $qvalue = $this->db->quote($param[2]); } $sql_where .= sprintf(' AND %s %s %s', $this->db->quote_identifier($param[0]), $param[1], $qvalue ); } return $sql_where; } /** * Helper method to convert the given pseudo-query triplets into * an associative filter array with 'equals' values only */ private function _query2assoc($query) { // extract object type from query parameter $filter = array(); foreach ($query as $param) { if ($param[1] == '=') $filter[$param[0]] = $param[2]; } return $filter; } /** * Fetch messages from IMAP * * @param array List of message UIDs to fetch * @param string Requested object type or * for all * @param string IMAP folder to read from * @return array List of parsed Kolab objects */ private function _fetch($index, $type = null, $folder = null) { $results = array(); foreach ((array)$index as $msguid) { if ($object = $this->folder->read_object($msguid, $type, $folder)) { $results[] = $object; $this->set($msguid, $object); } } return $results; } /** * Fetch object UIDs (aka message subjects) from IMAP * * @param array List of message UIDs to fetch * @param string Requested object type or * for all * @param string IMAP folder to read from * @return array List of parsed Kolab objects */ private function _fetch_uids($index, $type = null) { if (!$type) $type = $this->folder->type; $results = array(); foreach ((array)$this->imap->fetch_headers($this->folder->name, $index, false) as $msguid => $headers) { $object_type = kolab_format::mime2object_type($headers->others['x-kolab-type']); // check object type header and abort on mismatch if ($type != '*' && $object_type != $type) return false; $uid = $headers->subject; $this->uid2msg[$uid] = $msguid; $results[] = $uid; } return $results; } /** * Helper method to convert the given Kolab object into a dataset to be written to cache */ private function _serialize($object) { $bincols = array_flip($this->binary_cols); $sql_data = array('changed' => null, 'dtstart' => null, 'dtend' => null, 'xml' => '', 'tags' => '', 'words' => ''); $objtype = $object['_type'] ? $object['_type'] : $this->folder->type; // set type specific values if ($objtype == 'event') { // database runs in server's timezone so using date() is what we want $sql_data['dtstart'] = date('Y-m-d H:i:s', is_object($object['start']) ? $object['start']->format('U') : $object['start']); $sql_data['dtend'] = date('Y-m-d H:i:s', is_object($object['end']) ? $object['end']->format('U') : $object['end']); // extend date range for recurring events if ($object['recurrence'] && $object['_formatobj']) { $recurrence = new kolab_date_recurrence($object['_formatobj']); $sql_data['dtend'] = date('Y-m-d 23:59:59', $recurrence->end() ?: strtotime('now +1 year')); } } else if ($objtype == 'task') { if ($object['start']) $sql_data['dtstart'] = date('Y-m-d H:i:s', is_object($object['start']) ? $object['start']->format('U') : $object['start']); if ($object['due']) $sql_data['dtend'] = date('Y-m-d H:i:s', is_object($object['due']) ? $object['due']->format('U') : $object['due']); } if ($object['changed']) { $sql_data['changed'] = date('Y-m-d H:i:s', is_object($object['changed']) ? $object['changed']->format('U') : $object['changed']); } if ($object['_formatobj']) { - $sql_data['xml'] = preg_replace('!()[\n\r\t\s]+!ms', '$1', (string)$object['_formatobj']->write(3.0)); + $sql_data['xml'] = preg_replace('!()[\n\r\t\s]+!ms', '$1', (string)$object['_formatobj']->write()); $sql_data['tags'] = ' ' . join(' ', $object['_formatobj']->get_tags()) . ' '; // pad with spaces for strict/prefix search $sql_data['words'] = ' ' . join(' ', $object['_formatobj']->get_words()) . ' '; } // extract object data $data = array(); foreach ($object as $key => $val) { if ($val === "" || $val === null) { // skip empty properties continue; } if (isset($bincols[$key])) { $data[$key] = base64_encode($val); } else if ($key[0] != '_') { $data[$key] = $val; } else if ($key == '_attachments') { foreach ($val as $k => $att) { unset($att['content'], $att['path']); if ($att['id']) $data[$key][$k] = $att; } } } $sql_data['data'] = serialize($data); return $sql_data; } /** * Helper method to turn stored cache data into a valid storage object */ private function _unserialize($sql_arr) { $object = unserialize($sql_arr['data']); // decode binary properties foreach ($this->binary_cols as $key) { if (!empty($object[$key])) $object[$key] = base64_decode($object[$key]); } // add meta data $object['_type'] = $sql_arr['type']; $object['_msguid'] = $sql_arr['msguid']; $object['_mailbox'] = $this->folder->name; - $object['_formatobj'] = kolab_format::factory($sql_arr['type'], 3.0, $sql_arr['xml']); + $object['_formatobj'] = kolab_format::factory($sql_arr['type'], 2.0, $sql_arr['xml']); return $object; } /** * Write records into cache using extended inserts to reduce the number of queries to be executed * * @param int Message UID. Set 0 to commit buffered inserts * @param array Kolab object to cache */ private function _extended_insert($msguid, $object) { static $buffer = ''; $line = ''; if ($object) { $sql_data = $this->_serialize($object); $objtype = $object['_type'] ? $object['_type'] : $this->folder->type; $values = array( $this->db->quote($this->resource_uri), $this->db->quote($objtype), $this->db->quote($msguid), $this->db->quote($object['uid']), $this->db->now(), $this->db->quote($sql_data['changed']), $this->db->quote($sql_data['data']), $this->db->quote($sql_data['xml']), $this->db->quote($sql_data['dtstart']), $this->db->quote($sql_data['dtend']), $this->db->quote($sql_data['tags']), $this->db->quote($sql_data['words']), ); $line = '(' . join(',', $values) . ')'; } if ($buffer && (!$msguid || (strlen($buffer) + strlen($line) > $this->max_sql_packet))) { $result = $this->db->query( "INSERT INTO kolab_cache ". " (resource, type, msguid, uid, created, changed, data, xml, dtstart, dtend, tags, words)". " VALUES $buffer" ); if (!$this->db->affected_rows($result)) { rcube::raise_error(array( 'code' => 900, 'type' => 'php', 'message' => "Failed to write to kolab cache" ), true); } $buffer = ''; } $buffer .= ($buffer ? ',' : '') . $line; } /** * Check lock record for this folder and wait if locked or set lock */ private function _sync_lock() { if (!$this->ready) return; $sql_arr = $this->db->fetch_assoc($this->db->query( "SELECT msguid AS locked, ".$this->db->unixtimestamp('created')." AS created FROM kolab_cache ". "WHERE resource=? AND type=?", $this->resource_uri, 'lock' )); // abort if database is not set-up if ($this->db->is_error()) { $this->ready = false; return; } $this->synclock = true; // create lock record if not exists if (!$sql_arr) { $this->db->query( "INSERT INTO kolab_cache (resource, type, msguid, created, uid, data, xml)". " VALUES (?, ?, 1, ?, '', '', '')", $this->resource_uri, 'lock', date('Y-m-d H:i:s') ); } // wait if locked (expire locks after 10 minutes) else if (intval($sql_arr['locked']) > 0 && (time() - $sql_arr['created']) < 600) { usleep(500000); return $this->_sync_lock(); } // set lock else { $this->db->query( "UPDATE kolab_cache SET msguid=1, created=? ". "WHERE resource=? AND type=?", date('Y-m-d H:i:s'), $this->resource_uri, 'lock' ); } } /** * Remove lock for this folder */ public function _sync_unlock() { if (!$this->ready || !$this->synclock) return; $this->db->query( "UPDATE kolab_cache SET msguid=0 ". "WHERE resource=? AND type=?", $this->resource_uri, 'lock' ); $this->synclock = false; } /** * Resolve an object UID into an IMAP message UID * * @param string Kolab object UID * @param boolean Include deleted objects * @return int The resolved IMAP message UID */ public function uid2msguid($uid, $deleted = false) { if (!isset($this->uid2msg[$uid])) { // use IMAP SEARCH to get the right message $index = $this->imap->search_once($this->folder->name, ($deleted ? '' : 'UNDELETED ') . 'HEADER SUBJECT ' . rcube_imap_generic::escape($uid)); $results = $index->get(); $this->uid2msg[$uid] = $results[0]; } return $this->uid2msg[$uid]; } } diff --git a/plugins/libkolab/lib/kolab_storage_folder.php b/plugins/libkolab/lib/kolab_storage_folder.php index 302efd63..5ba5fa4f 100644 --- a/plugins/libkolab/lib/kolab_storage_folder.php +++ b/plugins/libkolab/lib/kolab_storage_folder.php @@ -1,860 +1,852 @@ * * Copyright (C) 2012, 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_storage_folder { /** * The folder name. * @var string */ public $name; /** * The type of this folder. * @var string */ public $type; /** * Is this folder set to be the default for its type * @var boolean */ public $default = false; /** * Is this folder set to be default * @var boolean */ public $cache; private $type_annotation; private $imap; private $info; private $owner; private $resource_uri; private $uid2msg = array(); /** * Default constructor */ function __construct($name, $type = null) { $this->imap = rcube::get_instance()->get_storage(); $this->imap->set_options(array('skip_deleted' => true)); $this->cache = new kolab_storage_cache($this); $this->set_folder($name, $type); } /** * Set the IMAP folder this instance connects to * * @param string The folder name/path * @param string Optional folder type if known */ public function set_folder($name, $ftype = null) { $this->type_annotation = $ftype ? $ftype : kolab_storage::folder_type($name); list($this->type, $suffix) = explode('.', $this->type_annotation); $this->default = $suffix == 'default'; $this->name = $name; $this->resource_uri = null; $this->imap->set_folder($this->name); $this->cache->set_folder($this); } /** * */ private function get_folder_info() { if (!isset($this->info)) $this->info = $this->imap->folder_info($this->name); return $this->info; } /** * Returns IMAP metadata/annotations (GETMETADATA/GETANNOTATION) * * @param array List of metadata keys to read * @return array Metadata entry-value hash array on success, NULL on error */ public function get_metadata($keys) { $metadata = $this->imap->get_metadata($this->name, (array)$keys); return $metadata[$this->name]; } /** * Sets IMAP metadata/annotations (SETMETADATA/SETANNOTATION) * * @param array $entries Entry-value array (use NULL value as NIL) * @return boolean True on success, False on failure */ public function set_metadata($entries) { return $this->imap->set_metadata($this->name, $entries); } /** * Returns the owner of the folder. * * @return string The owner of this folder. */ public function get_owner() { // return cached value if (isset($this->owner)) return $this->owner; $info = $this->get_folder_info(); $rcmail = rcube::get_instance(); switch ($info['namespace']) { case 'personal': $this->owner = $rcmail->get_user_name(); break; case 'shared': $this->owner = 'anonymous'; break; default: $owner = ''; list($prefix, $user) = explode($this->imap->get_hierarchy_delimiter(), $info['name']); if (strpos($user, '@') === false) { $domain = strstr($rcmail->get_user_name(), '@'); if (!empty($domain)) $user .= $domain; } $this->owner = $user; break; } return $this->owner; } /** * Getter for the name of the namespace to which the IMAP folder belongs * * @return string Name of the namespace (personal, other, shared) */ public function get_namespace() { return $this->imap->folder_namespace($this->name); } /** * Get IMAP ACL information for this folder * * @return string Permissions as string */ public function get_myrights() { $rights = $this->info['rights']; if (!is_array($rights)) $rights = $this->imap->my_rights($this->name); return join('', (array)$rights); } /** * Compose a unique resource URI for this IMAP folder */ public function get_resource_uri() { if (!empty($this->resource_uri)) return $this->resource_uri; // strip namespace prefix from folder name $ns = $this->get_namespace(); $nsdata = $this->imap->get_namespace($ns); if (is_array($nsdata[0]) && strlen($nsdata[0][0]) && strpos($this->name, $nsdata[0][0]) === 0) { $subpath = substr($this->name, strlen($nsdata[0][0])); if ($ns == 'other') { list($user, $suffix) = explode($nsdata[0][1], $subpath); $subpath = $suffix; } } else { $subpath = $this->name; } // compose fully qualified ressource uri for this instance $this->resource_uri = 'imap://' . urlencode($this->get_owner()) . '@' . $this->imap->options['host'] . '/' . $subpath; return $this->resource_uri; } /** * Check subscription status of this folder * * @param string Subscription type (kolab_storage::SERVERSIDE_SUBSCRIPTION or kolab_storage::CLIENTSIDE_SUBSCRIPTION) * @return boolean True if subscribed, false if not */ public function is_subscribed($type = 0) { static $subscribed; // local cache if ($type == kolab_storage::SERVERSIDE_SUBSCRIPTION) { if (!$subscribed) $subscribed = $this->imap->list_folders_subscribed(); return in_array($this->name, $subscribed); } else if (kolab_storage::CLIENTSIDE_SUBSCRIPTION) { // TODO: implement this return true; } return false; } /** * Change subscription status of this folder * * @param boolean The desired subscription status: true = subscribed, false = not subscribed * @param string Subscription type (kolab_storage::SERVERSIDE_SUBSCRIPTION or kolab_storage::CLIENTSIDE_SUBSCRIPTION) * @return True on success, false on error */ public function subscribe($subscribed, $type = 0) { if ($type == kolab_storage::SERVERSIDE_SUBSCRIPTION) { return $subscribed ? $this->imap->subscribe($this->name) : $this->imap->unsubscribe($this->name); } else { // TODO: implement this } return false; } /** * Get number of objects stored in this folder * * @param mixed Pseudo-SQL query as list of filter parameter triplets * or string with object type (e.g. contact, event, todo, journal, note, configuration) * @return integer The number of objects of the given type * @see self::select() */ public function count($type_or_query = null) { if (!$type_or_query) $query = array(array('type','=',$this->type)); else if (is_string($type_or_query)) $query = array(array('type','=',$type_or_query)); else $query = $this->_prepare_query((array)$type_or_query); // synchronize cache first $this->cache->synchronize(); return $this->cache->count($query); } /** * List all Kolab objects of the given type * * @param string $type Object type (e.g. contact, event, todo, journal, note, configuration) * @return array List of Kolab data objects (each represented as hash array) */ public function get_objects($type = null) { if (!$type) $type = $this->type; // synchronize caches $this->cache->synchronize(); // fetch objects from cache return $this->cache->select(array(array('type','=',$type))); } /** * Select *some* Kolab objects matching the given query * * @param array Pseudo-SQL query as list of filter parameter triplets * triplet: array('', '', '') * @return array List of Kolab data objects (each represented as hash array) */ public function select($query = array()) { // check query argument if (empty($query)) return $this->get_objects(); // synchronize caches $this->cache->synchronize(); // fetch objects from cache return $this->cache->select($this->_prepare_query($query)); } /** * Getter for object UIDs only * * @param array Pseudo-SQL query as list of filter parameter triplets * @return array List of Kolab object UIDs */ public function get_uids($query = array()) { // synchronize caches $this->cache->synchronize(); // fetch UIDs from cache return $this->cache->select($this->_prepare_query($query), true); } /** * Helper method to sanitize query arguments */ private function _prepare_query($query) { $type = null; foreach ($query as $i => $param) { if ($param[0] == 'type') { $type = $param[2]; } else if (($param[0] == 'dtstart' || $param[0] == 'dtend' || $param[0] == 'changed')) { if (is_object($param[2]) && is_a($param[2], 'DateTime')) $param[2] = $param[2]->format('U'); if (is_numeric($param[2])) $query[$i][2] = date('Y-m-d H:i:s', $param[2]); } } // add type selector if not in $query if (!$type) $query[] = array('type','=',$this->type); return $query; } /** * Getter for a single Kolab object, identified by its UID * * @param string Object UID * @return array The Kolab object represented as hash array */ public function get_object($uid) { // synchronize caches $this->cache->synchronize(); $msguid = $this->cache->uid2msguid($uid); if ($msguid && ($object = $this->cache->get($msguid))) return $object; return false; } /** * Fetch a Kolab object attachment which is stored in a separate part * of the mail MIME message that represents the Kolab record. * * @param string Object's UID * @param string The attachment's mime number * @param string IMAP folder where message is stored; * If set, that also implies that the given UID is an IMAP UID * @return mixed The attachment content as binary string */ public function get_attachment($uid, $part, $mailbox = null) { if ($msguid = ($mailbox ? $uid : $this->cache->uid2msguid($uid))) { $this->imap->set_folder($mailbox ? $mailbox : $this->name); return $this->imap->get_message_part($msguid, $part); } return null; } /** * Fetch the mime message from the storage server and extract * the Kolab groupware object from it * * @param string The IMAP message UID to fetch * @param string The object type expected (use wildcard '*' to accept all types) * @param string The folder name where the message is stored * @return mixed Hash array representing the Kolab object, a kolab_format instance or false if not found */ public function read_object($msguid, $type = null, $folder = null) { if (!$type) $type = $this->type; if (!$folder) $folder = $this->name; $this->imap->set_folder($folder); $headers = $this->imap->get_message_headers($msguid); // Message doesn't exist? if (empty($headers)) { return false; } $object_type = kolab_format::mime2object_type($headers->others['x-kolab-type']); $content_type = kolab_format::KTYPE_PREFIX . $object_type; // check object type header and abort on mismatch if ($type != '*' && $object_type != $type) return false; $message = new rcube_message($msguid); $attachments = array(); // get XML part foreach ((array)$message->attachments as $part) { if (!$xml && ($part->mimetype == $content_type || preg_match('!application/([a-z]+\+)?xml!', $part->mimetype))) { $xml = $part->body ? $part->body : $message->get_part_content($part->mime_id); } else if ($part->filename || $part->content_id) { $key = $part->content_id ? trim($part->content_id, '<>') : $part->filename; $size = null; // Use Content-Disposition 'size' as for the Kolab Format spec. if (isset($part->d_parameters['size'])) { $size = $part->d_parameters['size']; } // we can trust part size only if it's not encoded else if ($part->encoding == 'binary' || $part->encoding == '7bit' || $part->encoding == '8bit') { $size = $part->size; } $attachments[$key] = array( 'id' => $part->mime_id, 'name' => $part->filename, 'mimetype' => $part->mimetype, 'size' => $size, ); } } if (!$xml) { rcube::raise_error(array( 'code' => 600, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Could not find Kolab data part in message $msguid ($this->name).", ), true); return false; } // check kolab format version $format_version = $headers->others['x-kolab-mime-version']; if (empty($format_version)) { - list($xmltype, $subtype) = explode('.', $object_type); - $xmlhead = substr($xml, 0, 512); - - // detect old Kolab 2.0 format - if (strpos($xmlhead, '<' . $xmltype) !== false && strpos($xmlhead, 'xmlns=') === false) - $format_version = 2.0; - else - $format_version = 3.0; // assume 3.0 + $format_version = 2.0; // assume 2.0 } // get Kolab format handler for the given type $format = kolab_format::factory($object_type, $format_version); if (is_a($format, 'PEAR_Error')) return false; // load Kolab object from XML part $format->load($xml); if ($format->is_valid()) { $object = $format->to_array(); $object['_type'] = $object_type; $object['_msguid'] = $msguid; $object['_mailbox'] = $this->name; $object['_attachments'] = array_merge((array)$object['_attachments'], $attachments); $object['_formatobj'] = $format; return $object; } else { // try to extract object UID from XML block if (preg_match('!(.+)!Uims', $xml, $m)) $msgadd = " UID = " . trim(strip_tags($m[1])); rcube::raise_error(array( 'code' => 600, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Could not parse Kolab object data in message $msguid ($this->name)." . $msgadd, ), true); } return false; } /** * Save an object in this folder. * * @param array $object The array that holds the data of the object. * @param string $type The type of the kolab object. * @param string $uid The UID of the old object if it existed before * @return boolean True on success, false on error */ public function save(&$object, $type = null, $uid = null) { if (!$type) $type = $this->type; // copy attachments from old message if (!empty($object['_msguid']) && ($old = $this->cache->get($object['_msguid'], $type, $object['_mailbox']))) { foreach ((array)$old['_attachments'] as $key => $att) { if (!isset($object['_attachments'][$key])) { $object['_attachments'][$key] = $old['_attachments'][$key]; } // unset deleted attachment entries if ($object['_attachments'][$key] == false) { unset($object['_attachments'][$key]); } // load photo.attachment from old Kolab2 format to be directly embedded in xcard block else if ($type == 'contact' && ($key == 'photo.attachment' || $key == 'kolab-picture.png') && $att['id']) { if (!isset($object['photo'])) $object['photo'] = $this->get_attachment($object['_msguid'], $att['id'], $object['_mailbox']); unset($object['_attachments'][$key]); } } } // save contact photo to attachment for Kolab2 format if (kolab_storage::$version == 2.0 && $object['photo'] && !$existing_photo) { $attkey = 'kolab-picture.png'; // this file name is hard-coded in libkolab/kolabformatV2/contact.cpp $object['_attachments'][$attkey] = array( 'mimetype'=> rc_image_content_type($object['photo']), 'content' => preg_match('![^a-z0-9/=+-]!i', $object['photo']) ? $object['photo'] : base64_decode($object['photo']), ); } // process attachments if (is_array($object['_attachments'])) { $numatt = count($object['_attachments']); foreach ($object['_attachments'] as $key => $attachment) { // make sure size is set, so object saved in cache contains this info if (!isset($attachment['size'])) { if (!empty($attachment['content'])) { $attachment['size'] = strlen($attachment['content']); } else if (!empty($attachment['path'])) { $attachment['size'] = filesize($attachment['path']); } $object['_attachments'][$key] = $attachment; } // generate unique keys (used as content-id) for attachments if (is_numeric($key) && $key < $numatt) { // derrive content-id from attachment file name $ext = preg_match('/(\.[a-z0-9]{1,6})$/i', $attachment['name'], $m) ? $m[1] : null; $basename = preg_replace('/[^a-z0-9_.-]/i', '', basename($attachment['name'], $ext)); // to 7bit ascii if (!$basename) $basename = 'noname'; $cid = $basename . '.' . microtime(true) . $ext; $object['_attachments'][$cid] = $attachment; unset($object['_attachments'][$key]); } } } if ($raw_msg = $this->build_message($object, $type)) { $result = $this->imap->save_message($this->name, $raw_msg, '', false); // delete old message if ($result && !empty($object['_msguid']) && !empty($object['_mailbox'])) { $this->imap->delete_message($object['_msguid'], $object['_mailbox']); $this->cache->set($object['_msguid'], false, $object['_mailbox']); } else if ($result && $uid && ($msguid = $this->cache->uid2msguid($uid))) { $this->imap->delete_message($msguid, $this->name); $this->cache->set($object['_msguid'], false); } // update cache with new UID if ($result) { $object['_msguid'] = $result; $this->cache->insert($result, $object); } } return $result; } /** * Delete the specified object from this folder. * * @param mixed $object The Kolab object to delete or object UID * @param boolean $expunge Should the folder be expunged? * * @return boolean True if successful, false on error */ public function delete($object, $expunge = true) { $msguid = is_array($object) ? $object['_msguid'] : $this->cache->uid2msguid($object); $success = false; if ($msguid && $expunge) { $success = $this->imap->delete_message($msguid, $this->name); } else if ($msguid) { $success = $this->imap->set_flag($msguid, 'DELETED', $this->name); } if ($success) { $this->cache->set($msguid, false); } return $success; } /** * */ public function delete_all() { $this->cache->purge(); return $this->imap->clear_folder($this->name); } /** * Restore a previously deleted object * * @param string Object UID * @return mixed Message UID on success, false on error */ public function undelete($uid) { if ($msguid = $this->cache->uid2msguid($uid, true)) { if ($this->imap->set_flag($msguid, 'UNDELETED', $this->name)) { return $msguid; } } return false; } /** * Move a Kolab object message to another IMAP folder * * @param string Object UID * @param string IMAP folder to move object to * @return boolean True on success, false on failure */ public function move($uid, $target_folder) { if ($msguid = $this->cache->uid2msguid($uid)) { if ($success = $this->imap->move_message($msguid, $target_folder, $this->name)) { $this->cache->move($msguid, $uid, $target_folder); return true; } else { rcube::raise_error(array( 'code' => 600, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Failed to move message $msguid to $target_folder: " . $this->imap->get_error_str(), ), true); } } return false; } /** * Creates source of the configuration object message */ private function build_message(&$object, $type) { // load old object to preserve data we don't understand/process if (is_object($object['_formatobj'])) $format = $object['_formatobj']; else if ($object['_msguid'] && ($old = $this->cache->get($object['_msguid'], $type, $object['_mailbox']))) $format = $old['_formatobj']; // create new kolab_format instance if (!$format) $format = kolab_format::factory($type, kolab_storage::$version); if (PEAR::isError($format)) return false; $format->set($object); $xml = $format->write(kolab_storage::$version); $object['uid'] = $format->uid; // read UID from format $object['_formatobj'] = $format; if (!$format->is_valid() || empty($object['uid'])) { return false; } $mime = new Mail_mime("\r\n"); $rcmail = rcube::get_instance(); $headers = array(); $part_id = 1; if ($ident = $rcmail->user->get_identity()) { $headers['From'] = $ident['email']; $headers['To'] = $ident['email']; } $headers['Date'] = date('r'); $headers['X-Kolab-Type'] = kolab_format::KTYPE_PREFIX . $type; $headers['X-Kolab-Mime-Version'] = kolab_storage::$version; $headers['Subject'] = $object['uid']; // $headers['Message-ID'] = $rcmail->gen_message_id(); $headers['User-Agent'] = $rcmail->config->get('useragent'); $mime->headers($headers); $mime->setTXTBody('This is a Kolab Groupware object. ' . 'To view this object you will need an email client that understands the Kolab Groupware format. ' . "For a list of such email clients please visit http://www.kolab.org/\n\n"); - $ctype = kolab_storage::$version == 2.0 ? $format->CTYPEv2 : $format->CTYPE; $mime->addAttachment($xml, // file - $ctype, // content-type + $format->CTYPE, // content-type 'kolab.xml', // filename false, // is_file '8bit', // encoding 'attachment', // disposition RCMAIL_CHARSET // charset ); $part_id++; // save object attachments as separate parts // TODO: optimize memory consumption by using tempfiles for transfer foreach ((array)$object['_attachments'] as $key => $att) { if (empty($att['content']) && !empty($att['id'])) { $msguid = !empty($object['_msguid']) ? $object['_msguid'] : $object['uid']; $att['content'] = $this->get_attachment($msguid, $att['id'], $object['_mailbox']); } $headers = array('Content-ID' => Mail_mimePart::encodeHeader('Content-ID', '<' . $key . '>', RCMAIL_CHARSET, 'quoted-printable')); $name = !empty($att['name']) ? $att['name'] : $key; if (!empty($att['content'])) { $mime->addAttachment($att['content'], $att['mimetype'], $name, false, 'base64', 'attachment', '', '', '', null, null, '', RCMAIL_CHARSET, $headers); $part_id++; } else if (!empty($att['path'])) { $mime->addAttachment($att['path'], $att['mimetype'], $name, true, 'base64', 'attachment', '', '', '', null, null, '', RCMAIL_CHARSET, $headers); $part_id++; } $object['_attachments'][$key]['id'] = $part_id; } return $mime->getMessage(); } /** * Triggers any required updates after changes within the * folder. This is currently only required for handling free/busy * information with Kolab. * * @return boolean|PEAR_Error True if successfull. */ public function trigger() { $owner = $this->get_owner(); $result = false; switch($this->type) { case 'event': if ($this->get_namespace() == 'personal') { $result = $this->trigger_url( sprintf('%s/trigger/%s/%s.pfb', kolab_storage::get_freebusy_server(), $owner, $this->imap->mod_folder($this->name)), $this->imap->options['user'], $this->imap->options['password'] ); } break; default: return true; } if ($result && is_object($result) && is_a($result, 'PEAR_Error')) { return PEAR::raiseError(sprintf("Failed triggering folder %s. Error was: %s", $this->name, $result->getMessage())); } return $result; } /** * Triggers a URL. * * @param string $url The URL to be triggered. * @param string $auth_user Username to authenticate with * @param string $auth_passwd Password for basic auth * @return boolean|PEAR_Error True if successfull. */ private function trigger_url($url, $auth_user = null, $auth_passwd = null) { require_once('HTTP/Request2.php'); try { $rcmail = rcube::get_instance(); $request = new HTTP_Request2($url); $request->setConfig(array('ssl_verify_peer' => $rcmail->config->get('kolab_ssl_verify_peer', true))); // set authentication credentials if ($auth_user && $auth_passwd) $request->setAuth($auth_user, $auth_passwd); $result = $request->send(); // rcube::write_log('trigger', $result->getBody()); } catch (Exception $e) { return PEAR::raiseError($e->getMessage()); } return true; } } diff --git a/plugins/libkolab/libkolab.php b/plugins/libkolab/libkolab.php index 887d3082..5e55943e 100644 --- a/plugins/libkolab/libkolab.php +++ b/plugins/libkolab/libkolab.php @@ -1,75 +1,78 @@ * * Copyright (C) 2012, 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 libkolab extends rcube_plugin { /** * Required startup method of a Roundcube plugin */ public function init() { // load local config $this->load_config(); $this->add_hook('storage_init', array($this, 'storage_init')); // extend include path to load bundled lib classes $include_path = $this->home . '/lib' . PATH_SEPARATOR . ini_get('include_path'); set_include_path($include_path); $rcmail = rcube::get_instance(); try { kolab_format::$timezone = new DateTimeZone($rcmail->config->get('timezone', 'GMT')); } catch (Exception $e) { rcube::raise_error($e, true); kolab_format::$timezone = new DateTimeZone('GMT'); } - // load (old) dependencies if available - if (@include_once('Horde/Util.php')) { + // load Horde Kolab_Format dependencies + if (include_once('Horde/Util.php')) { include_once 'Horde/Kolab/Format.php'; include_once 'Horde/Kolab/Format/XML.php'; include_once 'Horde/Kolab/Format/XML/contact.php'; + include_once 'Horde/Kolab/Format/XML/distributionlist.php'; include_once 'Horde/Kolab/Format/XML/event.php'; + include_once 'Horde/Kolab/Format/XML/note.php'; + include_once 'Horde/Kolab/Format/XML/task.php'; include_once 'Horde_Kolab_Format_XML_configuration.php'; String::setDefaultCharset('UTF-8'); } } /** * Hook into IMAP FETCH HEADER.FIELDS command and request Kolab-specific headers */ function storage_init($p) { $p['fetch_headers'] = trim($p['fetch_headers'] .' X-KOLAB-TYPE X-KOLAB-MIME-VERSION'); return $p; } }