diff --git a/plugins/libkolab/lib/kolab_format.php b/plugins/libkolab/lib/kolab_format.php new file mode 100644 index 00000000..8681fb77 --- /dev/null +++ b/plugins/libkolab/lib/kolab_format.php @@ -0,0 +1,133 @@ + + * + * 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; + + /** + * Factory method to instantiate a kolab_format object of the given type + */ + public static function factory($type) + { + if (!isset(self::$timezone)) + self::$timezone = new DateTimeZone('UTC'); + + $suffix = preg_replace('/[^a-z]+/', '', $type); + $classname = 'kolab_format_' . $suffix; + if (class_exists($classname)) + return new $classname(); + + return PEAR::raiseError(sprintf("Failed to load Kolab Format wrapper for type %s", $type)); + } + + /** + * Generate random UID for Kolab objects + * + * @return string MD5 hash with a unique value + */ + public static function generate_uid() + { + return md5(uniqid(mt_rand(), true)); + } + + /** + * Convert the given date/time value into a c_DateTime 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 empty + * @param boolean True of the given date has no time component + * @return c_DateTime The libkolabxml date/time object or null on error + */ + public static function getDateTime($datetime, $tz = null, $dateonly = false) + { + if (!$tz) $tz = self::$timezone; + $result = null; + + if (is_numeric($datetime)) + $datetime = new DateTime('@'.$datetime, $tz); + else if (is_string($datetime)) + $datetime = new DateTime($datetime, $tz); + + if (is_a($datetime, 'DateTime')) { + $result = new KolabDateTime(); + $result->setDate($datetime->format('Y'), $datetime->format('n'), $datetime->format('j'), 0, 0, 0); + + if (!$dateonly) + $result->setTime($datetime->format('G'), $datetime->format('i'), $datetime->format('s')); + if ($tz) + $result->setTimezone($tz->getName()); + } + + return $result; + } + + /** + * Convert a libkolabxml vector to a PHP array + * + * @param object vector Object + * @return array Indexed array contaning vector elements + */ + public static function vector2array($vec) + { + $arr = array(); + for ($i=0; $i < $vec->size(); $i++) + $arr[] = $vec->get($i); + return $arr; + } + + /** + * Load Kolab object data from the given XML block + * + * @param string XML data + */ + abstract public function load($xml); + + /** + * Set properties to the kolabformat object + * + * @param array Object data as hash array + */ + abstract public function set(&$object); + + /** + * + */ + abstract public function is_valid(); + + /** + * Write object data to XML format + * + * @return string XML data + */ + abstract public function write(); + + /** + * Convert the Kolab object into a hash array data structure + * + * @return array Kolab object data as hash array + */ + abstract public function to_array(); + +} diff --git a/plugins/libkolab/lib/kolab_format_contact.php b/plugins/libkolab/lib/kolab_format_contact.php new file mode 100644 index 00000000..918355f8 --- /dev/null +++ b/plugins/libkolab/lib/kolab_format_contact.php @@ -0,0 +1,244 @@ + 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', + 'initials' => 'initials', + 'birthday' => 'birthday', + 'anniversary' => 'anniversary', + 'phone' => 'phone', + 'im-address' => 'im', + 'web-page' => 'website', + 'office-location' => 'officelocation', + 'profession' => 'profession', + 'manager-name' => 'manager', + 'assistant' => 'assistant', + 'spouse-name' => 'spouse', + 'children' => 'children', + 'body' => 'notes', + 'pgp-publickey' => 'pgppublickey', + 'free-busy-url' => 'freebusyurl', + 'gender' => 'gender', + ); + + function __construct() + { + $this->obj = new Contact; + } + + /** + * Load Contact object data from the given XML block + * + * @param string XML data + */ + public function load($xml) + { + $this->obj = kolabformat::readContact($xml, false); + } + + /** + * Write Contact object data to XML format + * + * @return string XML data + */ + public function write() + { + return kolabformat::writeContact($this->obj); + } + + /** + * Set contact properties to the kolabformat object + * + * @param array Contact data as hash array + */ + public function set(&$object) + { + // set some automatic values if missing + if (empty($object['uid'])) + $object['uid'] = self::generate_uid(); + + if (false && !$this->obj->created()) { + if (!empty($object['created'])) + $object['created'] = new DateTime('now', self::$timezone); + $this->obj->setCreated(self::getDateTime($object['created'])); + } + + // do the hard work of setting object values + $this->obj->setUid($object['uid']); + + $nc = new NameComponents; + // surname + $sn = new vectors(); + $sn->push($object['surname']); + $nc->setSurnames($sn); + // firstname + $gn = new vectors(); + $gn->push($object['firstname']); + $nc->setGiven($gn); + // middle name + $mn = new vectors(); + if ($object['middlename']) + $mn->push($object['middlename']); + $nc->setAdditional($mn); + // prefix + $px = new vectors(); + if ($object['prefix']) + $px->push($object['prefix']); + $nc->setPrefixes($px); + // suffix + $sx = new vectors(); + if ($object['suffix']) + $sx->push($object['suffix']); + $nc->setSuffixes($sx); + + $this->obj->setNameComponents($nc); + $this->obj->setName($object['name']); + + // email addresses + $emails = new vectors; + foreach ($object['email'] as $em) + $emails->push($em); + $this->obj->setEmailAddresses($emails); + + // addresses + $adrs = new vectoraddress; + foreach ($object['address'] as $address) { + $adr = new Address; + $adr->setTypes($address['type'] == 'work' ? Address::Work : Address::Home); + 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']); + + $adrs->push($adr); + } + $this->obj->setAddresses($adrs); + + // cache this data + $this->data = $object; + } + + /** + * + */ + public function is_valid() + { + return $this->data || (is_object($this->obj) && true /*$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; + + // TODO: read object properties into local data object + $object = array( + 'uid' => $this->obj->uid(), + # 'changed' => $this->obj->lastModified(), + 'name' => $this->obj->name(), + ); + + $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['email'] = self::vector2array($this->obj->emailAddresses()); + + $addresses = $this->obj->addresses(); + for ($i=0; $i < $addresses->size(); $i++) { + $adr = $addresses->get($i); + $object['address'][] = array( + 'type' => $adr->types() == Address::Work ? 'work' : 'home', + 'street' => $adr->street(), + 'code' => $adr->code(), + 'locality' => $adr->locality(), + 'region' => $adr->region(), + 'country' => $adr->country() + ); + } + + $this->data = $object; + return $this->data; + } + + /** + * Load data from old Kolab2 format + */ + 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->gender_map[$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'])) { + foreach ($record['address'] as $i => $adr) { + $object['address'][] = array( + 'type' => $adr['type'], + 'street' => $adr['street'], + 'locality' => $adr['locality'], + 'code' => $adr['postal-code'], + 'region' => $adr['region'], + 'country' => $adr['country'], + ); + } + } + + // photo is stored as separate attachment + if ($record['picture'] && ($att = $record['_attachments'][$record['picture']])) { + $object['photo'] = $att['content'] ? $att['content'] : $this->contactstorage->getAttachment($att['key']); + } + + // remove empty fields + $this->data = array_filter($object); + } +} diff --git a/plugins/libkolab/lib/kolab_format_distributionlist.php b/plugins/libkolab/lib/kolab_format_distributionlist.php new file mode 100644 index 00000000..a1f5891b --- /dev/null +++ b/plugins/libkolab/lib/kolab_format_distributionlist.php @@ -0,0 +1,126 @@ +obj = kolabformat::readDistlist($xml, false); + } + + /** + * Write object data to XML format + * + * @return string XML data + */ + public function write() + { + return kolabformat::writeDistlist($this->obj); + } + + public function set(&$object) + { + // TODO: do the hard work of setting object values + } + + public function is_valid() + { + return $this->data || (is_object($this->obj) && true /*$this->obj->isValid()*/); + } + + /** + * 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 ($record['member'] as $member) { + $object['member'][] = array( + 'mailto' => $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; + + // read object properties + $object = array( + 'uid' => $this->obj->uid(), +# 'changed' => $this->obj->lastModified(), + 'name' => $this->obj->name(), + 'member' => array(), + ); + + $members = $this->obj->members(); + for ($i=0; $i < $members->size(); $i++) { + $adr = self::decode_member($members->get($i)); + if ($adr[0]['mailto']) + $object['member'][] = array( + 'mailto' => $adr[0]['mailto'], + 'name' => $adr[0]['name'], + 'uid' => '????', + ); + } + + return $this->data; + } + + /** + * Compose a valid Mailto URL according to RFC 822 + * + * @param string E-mail address + * @param string Person name + * @return string Formatted string + */ + public static function format_member($email, $name = '') + { + // let Roundcube internals do the job + return 'mailto:' . format_email_recipient($email, $name); + } + + /** + * Split a mailto: url into a structured member component + * + * @param string RFC 822 mailto: string + * @return array Hash array with member properties + */ + public static function decode_member($str) + { + $adr = rcube_mime::decode_address_list(preg_replace('/^mailto:/', '', $str)); + return $adr[0]; + } +} diff --git a/plugins/libkolab/lib/kolab_format_event.php b/plugins/libkolab/lib/kolab_format_event.php new file mode 100644 index 00000000..d8ab2764 --- /dev/null +++ b/plugins/libkolab/lib/kolab_format_event.php @@ -0,0 +1,46 @@ +obj = kolabformat::readEvent($xml, false); + } + + public function write() + { + return kolabformat::writeEvent($this->obj); + } + + public function set(&$object) + { + // TODO: do the hard work of setting object values + } + + public function is_valid() + { + return is_object($this->obj) && $this->obj->isValid(); + } + + public function fromkolab2($object) + { + $this->data = $object; + } + + public function to_array() + { + // TODO: read object properties + return $this->data; + } +} diff --git a/plugins/libkolab/lib/kolab_storage.php b/plugins/libkolab/lib/kolab_storage.php new file mode 100644 index 00000000..ec93213f --- /dev/null +++ b/plugins/libkolab/lib/kolab_storage.php @@ -0,0 +1,206 @@ + + * + * 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'; + + 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; + + $rcmail = rcmail::get_instance(); + self::$config = $rcmail->config; + self::$imap = $rcmail->get_storage(); + self::$ready = class_exists('kolabformat') && $rcmail->storage_connect() && + (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); + } + } + + + /** + * Get a list of storage folders for the given data type + * + * @param string Data type to list folders for (contact,event,task,note) + * + * @return array List of Kolab_Folder objects (folder names in UTF7-IMAP) + */ + public static function get_folders($type) + { + self::setup(); + $folders = array(); + + if (self::$ready) { + foreach ((array)self::$imap->list_folders('', '*', $type) as $foldername) { + $folders[$foldername] = new kolab_storage_folder($foldername, self::$imap); + } + } + + return $folders; + } + + + /** + * Getter for a specific storage folder + * + * @param string IMAP folder to access (UTF7-IMAP) + * @return object Kolab_Folder The folder object + */ + public static function get_folder($folder) + { + self::setup(); + return self::$ready ? new kolab_storage_folder($folder, null, self::$imap) : null; + } + + + /** + * + */ + 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, '/.-', '___')); + } + + + /** + * 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 = str_replace($delim, ' » ', $folder); + + if ($prefix) + $folder = $prefix . ' ' . $folder; + + if (!$folder_ns) + $folder_ns = 'personal'; + + return $folder; + } + +} diff --git a/plugins/libkolab/lib/kolab_storage_folder.php b/plugins/libkolab/lib/kolab_storage_folder.php new file mode 100644 index 00000000..0c15be74 --- /dev/null +++ b/plugins/libkolab/lib/kolab_storage_folder.php @@ -0,0 +1,498 @@ + + * + * 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 +{ + const KTYPE_PREFIX = 'application/x-vnd.kolab.'; + + /** + * The folder name. + * + * @var string + */ + public $name; + + /** + * The type of this folder. + * + * @var string + */ + public $type; + + private $type_annotation; + private $subpath; + private $imap; + private $info; + private $owner; + private $uid2msg = array(); + + + /** + * Default constructor + */ + function __construct($name, $imap = null) + { + $this->name = $name; + $this->imap = is_object($imap) ? $imap : rcmail::get_instance()->get_storage(); + $this->imap->set_folder($this->name); + + $metadata = $this->imap->get_metadata($this->name, array(kolab_storage::CTYPE_KEY)); + $this->type_annotation = $metadata[$this->name][kolab_storage::CTYPE_KEY]; + $this->type = reset(explode('.', $this->type_annotation)); + } + + + /** + * + */ + private function get_folder_info() + { + if (!isset($this->info)) + $this->info = $this->imap->folder_info($this->name); + + return $this->info; + } + + + /** + * 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 = rcmail::get_instance(); + + switch ($info['namespace']) { + case 'personal': + $this->owner = $rcmail->user->get_username(); + 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->user->get_username(), '@'); + 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 + */ + function get_acl() + { + return join('', (array)$this->imap->get_acl($this->name)); + } + + + /** + * 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) + */ + function get_objects($type = null) + { + if (!$type) $type = $this->type; + + // search by object type + $ctype = self::KTYPE_PREFIX . $type; + $search = 'HEADER X-Kolab-Type ' . $ctype; + + $index = $this->imap->search_once($this->name, $search); + $results = array(); + + // fetch all messages from IMAP + foreach ($index->get() as $msguid) { + if ($object = $this->read_object($msguid, $type)) { + $results[] = $object; + $this->uid2msg[$object['uid']] = $msguid; + } + } + + // TODO: write $this->uid2msg to cache + + return $results; + } + + + /** + * 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) + { + $msguid = $this->uid2msguid($uid); + if ($msguid && ($object = $this->read_object($msguid))) + return $object; + + return array('uid' => $uid); + } + + + /** + * 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 key stored in the Kolab XML + * @return mixed The attachment content as binary string + */ + public function get_attachment($uid, $key) + { + // TODO: implement this + + if ($msguid = $this->uid2msguid($uid)) { + $message = new rcube_message($msguid); + } + + return null; + } + + + /** + * + */ + private function read_object($msguid, $type = null) + { + if (!$type) $type = $this->type; + $ctype= self::KTYPE_PREFIX . $type; + + $this->imap->set_folder($this->name); + $message = new rcube_message($msguid); + + // get XML part + foreach ((array)$message->attachments as $part) { + if ($part->mimetype == $ctype || preg_match('!application/([a-z]+\+)?xml!', $part->mimetype)) { + $xml = $part->body ? $part->body : $message->get_part_content($part->mime_id); + break; + } + } + + if (!$xml) { + raise_error(array( + 'code' => 600, + 'type' => 'php', + 'file' => __FILE__, + 'line' => __LINE__, + 'message' => "Could not find Kolab data part in message " . $this->name . ':' . $uid, + ), true); + return false; + } + + $format = kolab_format::factory($type); + + // check kolab format version + if (strpos($xml, '<' . $type) !== false) { + // old Kolab 2.0 format detected + $handler = Horde_Kolab_Format::factory('XML', $type); + if (is_object($handler) && is_a($handler, 'PEAR_Error')) { + continue; + } + + // XML-to-array + $object = $handler->load($xml); + $format->fromkolab2($object); + } + else { + // load Kolab 3 format using libkolabxml + $format->load($xml); + } + + if ($format->is_valid()) { + $object = $format->to_array(); + $object['_msguid'] = $msguid; + $object['_mailbox'] = $this->name; + return $object; + } + + 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, $uid = null) + { + if (!$type) + $type = $this->type; + + 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']); + } + else if ($result && $uid && ($msguid = $this->uid2msguid($uid))) { + $this->imap->delete_message($msguid, $this->name); + } + + // TODO: update cache with new UID + } + + return $result; + } + + + /** + * Delete the specified object from this folder. + * + * @param array $object The Kolab object to delete + * @param boolean $trigger Should the folder be triggered? + * @param boolean $expunge Should the folder be expunged? + * + * @return boolean True if successful, false on error + */ + function delete($object, $trigger = true, $expunge = true) + { + if (!empty($object['_msguid'])) { + return $this->imap->delete_message($object['_msguid'], $this->name); + } + + return false; + } + + + /** + * + */ + public function delete_all() + { + return $this->imap->clear_folder($this->name); + } + + + /** + * Resolve an object UID into an IMAP message UID + */ + private function uid2msguid($uid) + { + if (!isset($this->uid2msg[$uid])) { + // use IMAP SEARCH to get the right message + $index = $this->imap->search_once($this->name, 'HEADER SUBJECT ' . $uid); + $results = $index->get(); + $this->uid2msg[$uid] = $results[0]; + + // TODO: cache this lookup + } + + return $this->uid2msg[$uid]; + } + + + /** + * Creates source of the configuration object message + */ + private function build_message(&$object, $type) + { + $format = kolab_format::factory($type); + $format->set($object); + $xml = $format->write(); + + if (!$format->is_valid()) { + return false; + } + + $mime = new Mail_mime("\r\n"); + $rcmail = rcmail::get_instance(); + $headers = array(); + + if ($ident = $rcmail->user->get_identity()) { + $headers['From'] = $ident['email']; + $headers['To'] = $ident['email']; + } + $headers['Date'] = date('r'); + $headers['X-Kolab-Type'] = self::KTYPE_PREFIX . $type; + $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/kolab2-clients.html\n\n"); + + $mime->addAttachment($xml, + $format->CTYPE, + 'kolab.xml', + false, '8bit', 'attachment', RCMAIL_CHARSET, '', '', + $rcmail->config->get('mime_param_folding') ? 'quoted-printable' : null, + $rcmail->config->get('mime_param_folding') == 2 ? 'quoted-printable' : null, + '', RCMAIL_CHARSET + ); + + 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(); + + switch($this->type) { + case 'event': + $url = sprintf('%s/trigger/%s/%s.pfb', kolab_storage::get_freebusy_server(), $owner, $this->subpath); + break; + + default: + return true; + } + + $result = $this->trigger_url($url); + if (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. + * @return boolean|PEAR_Error True if successfull. + */ + private function trigger_url($url) + { + // TBD. + return PEAR::raiseError("Feature not implemented."); + } + + + /* Legacy methods to keep compatibility with the old Horde Kolab_Storage classes */ + + /** + * Compatibility method + */ + public function getOwner() + { + console("Call to deprecated method kolab_storage_folder::getOwner()"); + return $this->get_owner(); + } + + /** + * Get IMAP ACL information for this folder + */ + public function getMyRights() + { + return $this->get_acl(); + } + + /** + * NOP to stay compatible with the formerly used Horde classes + */ + public function getData() + { + return $this; + } + + /** + * List all Kolab objects of the given type + */ + public function getObjects($type = null) + { + return $this->get_objects($type); + } + + /** + * Getter for a single Kolab object, identified by its UID + */ + public function getObject($uid) + { + return $this->get_object($uid); + } + + /** + * + */ + public function getAttachment($key) + { + PEAR::raiseError("Call to deprecated method not returning anything."); + return null; + } + + /** + * Alias function of delete() + */ + public function deleteMessage($id, $trigger = true, $expunge = true) + { + return $this->delete(array('_msguid' => $id), $trigger, $expunge); + } + + /** + * + */ + public function deleteAll() + { + return $this->delete_all(); + } + + +} + diff --git a/plugins/libkolab/libkolab.php b/plugins/libkolab/libkolab.php new file mode 100644 index 00000000..9092f463 --- /dev/null +++ b/plugins/libkolab/libkolab.php @@ -0,0 +1,56 @@ + + * + * 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(); + + // 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 = rcmail::get_instance(); + kolab_format::$timezone = new DateTimeZone($rcmail->config->get('timezone', 'GMT')); + + // load (old) dependencies + require_once 'Horde/Util.php'; + require_once 'Horde/Kolab/Format.php'; + require_once 'Horde/Kolab/Format/XML.php'; + require_once 'Horde/Kolab/Format/XML/contact.php'; + require_once 'Horde/Kolab/Format/XML/event.php'; + + String::setDefaultCharset('UTF-8'); + } + + +}