Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
165 KB
Referenced Files
None
Subscribers
None
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/<yourdatabase>.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://<imap-server->/freebusy
$rcmail_config['kolab_freebusy_server'] = 'https://<some-host>/<freebusy-path>';
// 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 @@
<?php
/* Configuration for libkolab */
$rcmail_config['kolab_cache'] = true;
- $rcmail_config['kolab_format_version'] = 3.0;
+ $rcmail_config['kolab_format_version'] = 3.0;
$rcmail_config['kolab_freebusy_server'] = 'https://' . $_SESSION['imap_host'] . '/freebusy';
$rcmail_config['kolab_ssl_verify_peer'] = true;
?>
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 @@
<?php
/**
- * Kolab format model class wrapping libkolabxml bindings
+ * Kolab format model class
*
* Abstract base class for different Kolab groupware objects read from/written
- * to the new Kolab 3 format using the PHP bindings of libkolabxml.
+ * to the Kolab 2 format using Horde_Kolab_Format classes.
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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 @@
<?php
/**
* Kolab Configuration data model class
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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 @@
<?php
/**
* Kolab Contact model class
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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 @@
<?php
/**
* Kolab Distribution List model class
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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 @@
<?php
/**
* Kolab Event model class
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
-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 @@
<?php
/**
* Kolab Journal model class
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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 @@
<?php
/**
* Kolab Note model class
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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 @@
<?php
/**
* Kolab Task (ToDo) model class
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
-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 @@
-<?php
-
-/**
- * Xcal based Kolab format class wrapping libkolabxml bindings
- *
- * Base class for xcal-based Kolab groupware objects such as event, todo, journal
- *
- * @version @package_version@
- * @author Thomas Bruederli <bruederli@kolabsys.com>
- *
- * Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 @@
<?php
/**
* Kolab storage class providing static methods to access groupware objects on a Kolab server.
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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), ' &raquo; ', $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] . ' &raquo; ') === 0) {
$length = strlen($names[$i] . ' &raquo; ');
$prefix = substr($name, 0, $length);
$count = count(explode(' &raquo; ', $prefix));
$name = str_repeat('&nbsp;&nbsp;', $count-1) . '&raquo; ' . 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].' &raquo; ') === 0) {
$length = strlen($names[$i].' &raquo; ');
$prefix = substr($name, 0, $length);
$count = count(explode(' &raquo; ', $prefix));
$name = str_repeat('&nbsp;&nbsp;', $count-1) . '&raquo; ' . 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 @@
<?php
/**
* Kolab storage cache class providing a local caching layer for Kolab groupware objects.
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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('<colname>', '<comparator>', '<value>')
* @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('!(</?[a-z0-9:-]+>)[\n\r\t\s]+!ms', '$1', (string)$object['_formatobj']->write(3.0));
+ $sql_data['xml'] = preg_replace('!(</?[a-z0-9:-]+>)[\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 @@
<?php
/**
* The kolab_storage_folder class represents an IMAP folder on the Kolab server.
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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('<colname>', '<comparator>', '<value>')
* @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('!<uid>(.+)</uid>!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 @@
<?php
/**
* Kolab core library
*
* Plugin to setup a basic environment for the interaction with a Kolab server.
* Other Kolab-related plugins will depend on it and can use the library classes
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Fri, Apr 24, 12:59 PM (1 w, 2 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18719382
Default Alt Text
(165 KB)

Event Timeline