Page MenuHomePhorge

No OneTemporary

Size
96 KB
Referenced Files
None
Subscribers
None
diff --git a/plugins/libkolab/SQL/mysql.sql b/plugins/libkolab/SQL/mysql.sql
new file mode 100644
index 00000000..376eb4fd
--- /dev/null
+++ b/plugins/libkolab/SQL/mysql.sql
@@ -0,0 +1,20 @@
+/**
+ * libkolab database schema
+ *
+ * @version @package_version@
+ * @author Thomas Bruederli
+ * @licence GNU AGPL
+ **/
+
+CREATE TABLE `kolab_cache` (
+ `resource` VARCHAR(255) CHARACTER SET ascii NOT NULL,
+ `type` VARCHAR(32) CHARACTER SET ascii NOT NULL,
+ `msguid` BIGINT UNSIGNED NOT NULL,
+ `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL,
+ `data` TEXT NOT NULL,
+ `xml` TEXT NOT NULL,
+ `dtstart` DATETIME,
+ `dtend` DATETIME,
+ `tags` VARCHAR(255) NOT NULL,
+ PRIMARY KEY(`resource`,`type`,`msguid`)
+) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
diff --git a/plugins/libkolab/lib/kolab_format.php b/plugins/libkolab/lib/kolab_format.php
index 42ecaab8..134df856 100644
--- a/plugins/libkolab/lib/kolab_format.php
+++ b/plugins/libkolab/lib/kolab_format.php
@@ -1,215 +1,235 @@
<?php
/**
* Kolab format model class wrapping libkolabxml bindings
*
* Abstract base class for different Kolab groupware objects read from/written
* to the new Kolab 3 format using the PHP bindings of libkolabxml.
*
* @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;
protected $obj;
protected $data;
+ protected $xmldata;
+ protected $loaded = false;
/**
* Factory method to instantiate a kolab_format object of the given type
+ *
+ * @param string Object type to instantiate
+ * @param string Cached xml data to initialize with
+ * @return object kolab_format
*/
- public static function factory($type)
+ public static function factory($type, $xmldata = null)
{
if (!isset(self::$timezone))
self::$timezone = new DateTimeZone('UTC');
$suffix = preg_replace('/[^a-z]+/', '', $type);
$classname = 'kolab_format_' . $suffix;
if (class_exists($classname))
- return new $classname();
+ return new $classname($xmldata);
return PEAR::raiseError(sprintf("Failed to load Kolab Format wrapper for type %s", $type));
}
/**
* 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 empty
* @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)
{
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);
if (is_a($datetime, '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)
$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;
}
/**
* 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'] = kolabformat::getSerializedUID();
$this->obj->setUid($this->data['uid']);
}
}
+ /**
+ * 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;
+ }
+ }
+
/**
* Direct getter for object properties
*/
- function __get($var)
+ public function __get($var)
{
return $this->data[$var];
}
/**
* Load Kolab object data from the given XML block
*
* @param string XML data
*/
abstract public function load($xml);
/**
* Set properties to the kolabformat object
*
* @param array Object data as hash array
*/
abstract public function set(&$object);
/**
*
*/
abstract public function is_valid();
/**
* Write object data to XML format
*
* @return string XML data
*/
abstract public function write();
/**
* Convert the Kolab object into a hash array data structure
*
* @return array Kolab object data as hash array
*/
abstract public function to_array();
/**
* Load object data from Kolab2 format
*
* @param array Hash array with object properties (produced by Horde Kolab_Format classes)
*/
abstract public function fromkolab2($object);
}
diff --git a/plugins/libkolab/lib/kolab_format_contact.php b/plugins/libkolab/lib/kolab_format_contact.php
index 69db2d15..59a6e0a0 100644
--- a/plugins/libkolab/lib/kolab_format_contact.php
+++ b/plugins/libkolab/lib/kolab_format_contact.php
@@ -1,500 +1,512 @@
<?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 $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,
);
public $addresstypes = array(
'home' => Address::Home,
'work' => Address::Work,
'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()
+ function __construct($xmldata = null)
{
$this->obj = new Contact;
+ $this->xmldata = $xmldata;
// complete phone types
$this->phonetypes['homefax'] |= Telephone::Home;
$this->phonetypes['workfax'] |= Telephone::Work;
}
/**
* Load Contact object data from the given XML block
*
* @param string XML data
*/
public function load($xml)
{
$this->obj = kolabformat::readContact($xml, false);
+ $this->loaded = true;
}
/**
* Write Contact object data to XML format
*
* @return string XML data
*/
public function write()
{
- $xml = kolabformat::writeContact($this->obj);
- parent::update_uid();
- return $xml;
+ $this->init();
+
+ if ($this->obj->isValid()) {
+ $this->xmldata = kolabformat::writeContact($this->obj);
+ parent::update_uid();
+ }
+
+ return $this->xmldata;
}
/**
* 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']);
// 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::None;
$vurls->push(new Url($url['url'], $type));
}
$this->obj->setUrls($vurls);
// addresses
$adrs = new vectoraddress;
foreach ($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'], null, true));
if (isset($object['anniversary']))
$this->obj->setAnniversary(self::get_datetime($object['anniversary'], null, true));
if (!empty($object['photo'])) {
if ($type = rc_image_content_type($object['photo']))
$this->obj->setPhoto($object['photo'], $type);
}
- else if (isset($object['photo'])) {
+ 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.
// cache this data
- unset($object['_formatobj']);
$this->data = $object;
+ unset($this->data['_formatobj']);
}
/**
*
*/
public function is_valid()
{
- return $this->data || (is_object($this->obj) && true /*$this->obj->isValid()*/);
+ 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(),
# 'changed' => $this->obj->lastModified(),
'name' => $this->obj->name(),
);
$nc = $this->obj->nameComponents();
$object['surname'] = join(' ', self::vector2array($nc->surnames()));
$object['firstname'] = join(' ', self::vector2array($nc->given()));
$object['middlename'] = join(' ', self::vector2array($nc->additional()));
$object['prefix'] = join(' ', self::vector2array($nc->prefixes()));
$object['suffix'] = join(' ', self::vector2array($nc->suffixes()));
$object['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();
// 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;
}
/**
* 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'])) {
foreach ($record['address'] as $i => $adr) {
$object['address'][] = array(
'type' => $this->kolab2_addresstypes[$adr['type']] ? $this->kolab2_addresstypes[$adr['type']] : $adr['type'],
'street' => $adr['street'],
'locality' => $adr['locality'],
'code' => $adr['postal-code'],
'region' => $adr['region'],
'country' => $adr['country'],
);
}
}
// 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 3c5047c3..b8d22085 100644
--- a/plugins/libkolab/lib/kolab_format_distributionlist.php
+++ b/plugins/libkolab/lib/kolab_format_distributionlist.php
@@ -1,148 +1,159 @@
<?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';
- function __construct()
+ function __construct($xmldata = null)
{
$this->obj = new DistList;
+ $this->xmldata = $xmldata;
}
/**
* Load Kolab object data from the given XML block
*
* @param string XML data
*/
public function load($xml)
{
$this->obj = kolabformat::readDistlist($xml, false);
+ $this->loaded = true;
}
/**
* Write object data to XML format
*
* @return string XML data
*/
public function write()
{
- $xml = kolabformat::writeDistlist($this->obj);
- parent::update_uid();
- return $xml;
+ $this->init();
+
+ if ($this->obj->isValid()) {
+ $this->xmldata = kolabformat::writeDistlist($this->obj);
+ parent::update_uid();
+ }
+
+ return $this->xmldata;
}
public function set(&$object)
{
+ $this->init();
+
// set some automatic values if missing
if (!empty($object['uid']))
$this->obj->setUid($object['uid']);
$this->obj->setName($object['name']);
$seen = array();
$members = new vectorcontactref;
foreach ($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);
// cache this data
- unset($object['_formatobj']);
$this->data = $object;
+ unset($this->data['_formatobj']);
}
public function is_valid()
{
return $this->data || (is_object($this->obj) && $this->obj->isValid());
}
/**
* Load data from old Kolab2 format
*/
public function fromkolab2($record)
{
$object = array(
'uid' => $record['uid'],
'changed' => $record['last-modification-date'],
'name' => $record['last-name'],
'member' => array(),
);
foreach ($record['member'] as $member) {
$object['member'][] = array(
'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' => $this->obj->lastModified(),
'name' => $this->obj->name(),
'member' => array(),
);
$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 78e51ca4..0552025e 100644
--- a/plugins/libkolab/lib/kolab_format_event.php
+++ b/plugins/libkolab/lib/kolab_format_event.php
@@ -1,576 +1,587 @@
<?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
{
public $CTYPE = 'application/calendar+xml';
private $sensitivity_map = array(
'public' => kolabformat::ClassPublic,
'private' => kolabformat::ClassPrivate,
'confidential' => kolabformat::ClassConfidential,
);
private $role_map = array(
'REQ-PARTICIPANT' => kolabformat::Required,
'OPT-PARTICIPANT' => kolabformat::Optional,
'NON-PARTICIPANT' => kolabformat::NonParticipant,
'CHAIR' => kolabformat::Chair,
);
private $rrule_type_map = array(
'MINUTELY' => RecurrenceRule::Minutely,
'HOURLY' => RecurrenceRule::Hourly,
'DAILY' => RecurrenceRule::Daily,
'WEEKLY' => RecurrenceRule::Weekly,
'MONTHLY' => RecurrenceRule::Monthly,
'YEARLY' => RecurrenceRule::Yearly,
);
private $weekday_map = array(
'MO' => kolabformat::Monday,
'TU' => kolabformat::Tuesday,
'WE' => kolabformat::Wednesday,
'TH' => kolabformat::Thursday,
'FR' => kolabformat::Friday,
'SA' => kolabformat::Saturday,
'SU' => kolabformat::Sunday,
);
private $alarm_type_map = array(
'DISPLAY' => Alarm::DisplayAlarm,
'EMAIL' => Alarm::EMailAlarm,
'AUDIO' => Alarm::AudioAlarm,
);
private $status_map = array(
'UNKNOWN' => kolabformat::PartNeedsAction,
'NEEDS-ACTION' => kolabformat::PartNeedsAction,
'TENTATIVE' => kolabformat::PartTentative,
'ACCEPTED' => kolabformat::PartAccepted,
'DECLINED' => kolabformat::PartDeclined,
'DELEGATED' => kolabformat::PartDelegated,
);
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');
/**
* Default constructor
*/
- function __construct()
+ function __construct($xmldata = null)
{
$this->obj = new Event;
+ $this->xmldata = $xmldata;
}
/**
* Load Contact object data from the given XML block
*
* @param string XML data
*/
public function load($xml)
{
$this->obj = kolabformat::readEvent($xml, false);
+ $this->loaded = true;
}
/**
* Write Contact object data to XML format
*
* @return string XML data
*/
public function write()
{
- $xml = kolabformat::writeEvent($this->obj);
- parent::update_uid();
- return $xml;
+ $this->init();
+
+ if ($this->obj->isValid()) {
+ $this->xmldata = kolabformat::writeEvent($this->obj);
+ parent::update_uid();
+ }
+
+ return $this->xmldata;
}
/**
* 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 (!$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']);
// TODO: increase sequence
// $this->obj->setSequence($this->obj->sequence()+1);
// 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->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']));
$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);
// process event attendees
$organizer = new ContactReference;
$attendees = new vectorattendee;
foreach ((array)$object['attendees'] as $attendee) {
$cr = new ContactReference(ContactReference::EmailReference, $attendee['email']);
$cr->setName($attendee['name']);
if ($attendee['role'] == 'ORGANIZER') {
$organizer = $cr;
}
else {
$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 {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Invalid event attendee: " . json_encode($attendee),
), true);
}
}
}
$this->obj->setOrganizer($organizer);
$this->obj->setAttendees($attendees);
// 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 {
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);
// save attachments
$vattach = new vectorattachment;
foreach ((array)$object['_attachments'] as $name => $attr) {
if (empty($attr))
continue;
$attach = new Attachment;
$attach->setLabel($name);
$attach->setUri('cid:' . $name, $attr['mimetype']);
$vattach->push($attach);
}
$this->obj->setAttachments($vattach);
// cache this data
- unset($object['_formatobj']);
$this->data = $object;
+ unset($this->data['_formatobj']);
}
/**
*
*/
public function is_valid()
{
- return $this->data || (is_object($this->obj) && $this->obj->isValid());
+ return $this->data || (is_object($this->obj) && $this->obj->isValid() && $this->obj->uid());
}
/**
* 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();
+
$sensitivity_map = array_flip($this->sensitivity_map);
// read object properties
$object = array(
'uid' => $this->obj->uid(),
'changed' => self::php_datetime($this->obj->lastModified()),
'title' => $this->obj->summary(),
'location' => $this->obj->location(),
'description' => $this->obj->description(),
'allday' => $this->obj->start()->isDateOnly(),
'start' => self::php_datetime($this->obj->start()),
'end' => self::php_datetime($this->obj->end()),
'categories' => self::vector2array($this->obj->categories()),
'free_busy' => $this->obj->transparency() ? 'free' : 'busy', // TODO: transparency is only boolean
'sensitivity' => $sensitivity_map[$this->obj->classification()],
'priority' => $this->obj->priority(),
);
// 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;
// read organizer and attendees
if ($organizer = $this->obj->organizer()) {
$object['attendees'][] = array(
'role' => 'ORGANIZER',
'email' => $organizer->email(),
'name' => $organizer->name(),
);
}
$role_map = array_flip($this->role_map);
$status_map = array_flip($this->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' => $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->format('U');
}
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->format('U');
}
}
}
// 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;
}
}
// 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') {
$name = $attach->label();
$data = $attach->data();
$object['_attachments'][$name] = array(
'mimetype' => $attach->mimetype(),
'size' => strlen($data),
'content' => $data,
);
}
}
$this->data = $object;
return $this->data;
}
/**
* 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'] = $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'][] = strtotime($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' => $rec['start-date'],
'end' => $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'],
);
}
}
diff --git a/plugins/libkolab/lib/kolab_storage_cache.php b/plugins/libkolab/lib/kolab_storage_cache.php
new file mode 100644
index 00000000..cfcacf51
--- /dev/null
+++ b/plugins/libkolab/lib/kolab_storage_cache.php
@@ -0,0 +1,443 @@
+<?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 $resource_uri;
+ private $enabled = true;
+ private $synched = false;
+ private $ready = false;
+
+ 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 ($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;
+ }
+
+ // strip namespace prefix from folder name
+ $ns = $this->folder->get_namespace();
+ $nsdata = $this->imap->get_namespace($ns);
+ if (is_array($nsdata[0]) && strpos($this->folder->name, $nsdata[0][0]) === 0) {
+ $subpath = substr($this->folder->name, strlen($nsdata[0][0]));
+ if ($ns == 'other') {
+ list($user, $suffix) = explode($nsdata[0][1], $subpath);
+ $subpath = $suffix;
+ }
+ }
+ else {
+ $subpath = $this->folder->name;
+ }
+
+ // compose fully qualified ressource uri for this instance
+ $this->resource_uri = 'imap://' . urlencode($this->folder->get_owner()) . '@' . $this->imap->options['host'] . '/' . $subpath;
+ $this->ready = $this->enabled;
+ }
+
+
+ /**
+ * Synchronize local cache data with remote
+ */
+ public function synchronize()
+ {
+ // only sync once per request cycle
+ if ($this->synched)
+ return;
+
+ // 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=?",
+ $this->resource_uri
+ );
+
+ $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
+ $fetch_index = array_diff($this->index, $old_index);
+ foreach ($this->_fetch($fetch_index, '*') as $object) {
+ $msguid = $object['_msguid'];
+ $this->set($msguid, $object);
+ }
+
+ // 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
+ );
+ }
+ }
+
+ $this->synched = time();
+ }
+
+
+ /**
+ * Read a single entry from cache or
+ */
+ public function get($msguid, $type = null, $folder = null)
+ {
+ // load object if not in memory
+ if (!isset($this->objects[$msguid])) {
+ if ($this->ready) {
+ // TODO: handle $folder != $this->folder->name situations
+
+ $sql_result = $this->db->query(
+ "SELECT * FROM kolab_cache ".
+ "WHERE resource=? AND msguid=?",
+ $this->resource_uri,
+ $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, $folder);
+ $this->objects[$msguid] = $result[0];
+ }
+ }
+
+ return $this->objects[$msguid];
+ }
+
+
+ /**
+ *
+ */
+ public function set($msguid, $object, $folder = null)
+ {
+ // write to cache
+ if ($this->ready) {
+ // TODO: handle $folder != $this->folder->name situations
+
+ // remove old entry
+ $this->db->query("DELETE FROM kolab_cache WHERE resource=? AND msguid=?",
+ $this->resource_uri, $msguid);
+
+ // write new object data if not false (wich means deleted)
+ if ($object) {
+ $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, data, xml, dtstart, dtend)".
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
+ $this->resource_uri,
+ $objtype,
+ $msguid,
+ $object['uid'],
+ $sql_data['data'],
+ $sql_data['xml'],
+ $sql_data['dtstart'],
+ $sql_data['dtend']
+ );
+
+ if (!$this->db->affected_rows($result)) {
+ rcmail::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;
+
+ if ($object)
+ $this->uid2msg[$object['uid']] = $msguid;
+ }
+
+
+ /**
+ * 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>')
+ * @return array List of Kolab data objects (each represented as hash array)
+ */
+ public function select($query = array())
+ {
+ $result = array();
+
+ // read from local cache DB (assume it to be synchronized)
+ if ($this->ready) {
+ $sql_result = $this->db->query(
+ "SELECT * FROM kolab_cache ".
+ "WHERE resource=? " . $this->_sql_where($query),
+ $this->resource_uri
+ );
+
+ while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
+ if ($object = $this->_unserialize($sql_arr))
+ $result[] = $object;
+ }
+ }
+ else {
+ // extract object type from query parameter
+ $filter = $this->_query2assoc($query);
+ $result = $this->_fetch($this->index, $filter['type']);
+
+ // TODO: post-filter result according to query
+ }
+
+ return $result;
+ }
+
+
+ /**
+ * Get number of objects mathing the given query
+ *
+ * @param string $type Object type (e.g. contact, event, todo, journal, note, configuration)
+ * @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_storage_folder::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) {
+ $sql_where .= sprintf(' AND %s%s%s',
+ $this->db->quote_identifier($param[0]),
+ $param[1],
+ $this->db->quote($param[2])
+ );
+ }
+ 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
+ * @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->uid2msg[$object['uid']] = $msguid;
+ }
+ }
+
+ 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('dtstart' => null, 'dtend' => null, 'xml' => '');
+
+ // set type specific values
+ if ($this->folder->type == 'event') {
+ // database runs in server's timetone so using date() is safe
+ $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']);
+ }
+
+ if ($object['_formatobj'])
+ $sql_data['xml'] = (string)$object['_formatobj']->write();
+
+ // 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']);
+ 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'], $sql_arr['xml']);
+
+ return $object;
+ }
+
+ /**
+ * 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 ' . $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 73b6dbba..246e58bb 100644
--- a/plugins/libkolab/lib/kolab_storage_folder.php
+++ b/plugins/libkolab/lib/kolab_storage_folder.php
@@ -1,753 +1,757 @@
<?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
{
const KTYPE_PREFIX = 'application/x-vnd.kolab.';
/**
* The folder name.
*
* @var string
*/
public $name;
/**
* The type of this folder.
*
* @var string
*/
public $type;
private $type_annotation;
- private $subpath;
private $imap;
private $info;
private $owner;
- private $objcache = array();
+ private $cache;
private $uid2msg = array();
/**
* Default constructor
*/
function __construct($name, $imap = null)
{
$this->imap = is_object($imap) ? $imap : rcube::get_instance()->get_storage();
- $this->imap->set_options(array('skip_deleted' => false));
+ $this->imap->set_options(array('skip_deleted' => true));
+ $this->cache = new kolab_storage_cache($this);
$this->set_folder($name);
}
/**
* Set the IMAP folder name this instance connects to
*
* @param string The folder name/path
*/
public function set_folder($name)
{
$this->name = $name;
$this->imap->set_folder($this->name);
$metadata = $this->imap->get_metadata($this->name, array(kolab_storage::CTYPE_KEY));
$this->type_annotation = $metadata[$this->name][kolab_storage::CTYPE_KEY];
$this->type = reset(explode('.', $this->type_annotation));
+
+ $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->user->get_username();
break;
case 'shared':
$this->owner = 'anonymous';
break;
default:
$owner = '';
list($prefix, $user) = explode($this->imap->get_hierarchy_delimiter(), $info['name']);
if (strpos($user, '@') === false) {
$domain = strstr($rcmail->user->get_username(), '@');
if (!empty($domain))
$user .= $domain;
}
$this->owner = $user;
break;
}
return $this->owner;
}
/**
* Getter for the name of the namespace to which the IMAP folder belongs
*
* @return string Name of the namespace (personal, other, shared)
*/
public function get_namespace()
{
return $this->imap->folder_namespace($this->name);
}
/**
* Get IMAP ACL information for this folder
*
* @return string Permissions as string
*/
public function get_myrights()
{
$rights = $this->info['rights'];
if (!is_array($rights))
$rights = $this->imap->my_rights($this->name);
return join('', (array)$rights);
}
/**
* 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 string $type Object type (e.g. contact, event, todo, journal, note, configuration)
* @return integer The number of objects of the given type
*/
public function count($type = null)
{
if (!$type) $type = $this->type;
- // search by object type
- $ctype = self::KTYPE_PREFIX . $type;
- $index = $this->imap->search_once($this->name, 'UNDELETED HEADER X-Kolab-Type ' . $ctype);
+ // TODO: synchronize cache first?
- return $index->count();
+ return $this->cache->count(array(array('type','=',$type)));
}
+
/**
* 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;
- $ctype = self::KTYPE_PREFIX . $type;
+ // synchronize caches
+ $this->cache->synchronize();
+
+ // fetch objects from cache
+ return $this->cache->select(array(array('type','=',$type)));
+
+/*
$results = array();
+ $ctype = self::KTYPE_PREFIX . $type;
// use 'list' for folder's default objects
if ($type == $this->type) {
$index = $this->imap->index($this->name);
}
else { // search by object type
$search = 'UNDELETED HEADER X-Kolab-Type ' . $ctype;
$index = $this->imap->search_once($this->name, $search);
}
// fetch all messages from IMAP
foreach ($index->get() as $msguid) {
if ($object = $this->read_object($msguid, $type)) {
$results[] = $object;
$this->uid2msg[$object['uid']] = $msguid;
}
}
// TODO: write $this->uid2msg to cache
return $results;
+*/
}
/**
* Getter for a single Kolab object, identified by its UID
*
* @param string Object UID
* @return array The Kolab object represented as hash array
*/
public function get_object($uid)
{
- $msguid = $this->uid2msguid($uid);
- if ($msguid && ($object = $this->read_object($msguid)))
+ // 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->uid2msguid($uid))) {
- if ($mailbox)
- $this->imap->set_folder($mailbox);
-
+ 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
+ * @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
*/
- private function read_object($msguid, $type = null, $folder = null)
+ public function read_object($msguid, $type = null, $folder = null)
{
if (!$type) $type = $this->type;
if (!$folder) $folder = $this->name;
- $ctype = self::KTYPE_PREFIX . $type;
-
- // requested message in local cache
- if ($this->objcache[$msguid])
- return $this->objcache[$msguid];
$this->imap->set_folder($folder);
- // check ctype header and abort on mismatch
$headers = $this->imap->get_message_headers($msguid);
- if ($headers->others['x-kolab-type'] != $ctype)
+ $object_type = substr($headers->others['x-kolab-type'], strlen(self::KTYPE_PREFIX));
+ $content_type = self::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 == $ctype || preg_match('!application/([a-z]+\+)?xml!', $part->mimetype))) {
+ 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) {
$attachments[$part->filename] = array(
'id' => $part->mime_id,
'mimetype' => $part->mimetype,
'size' => $part->size,
);
}
}
if (!$xml) {
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;
}
- $format = kolab_format::factory($type);
+ $format = kolab_format::factory($object_type);
+
+ if (is_a($format, 'PEAR_Error'))
+ return false;
// check kolab format version
- if (strpos($xml, '<' . $type) !== false) {
+ if (strpos($xml, '<' . $object_type) !== false) {
// old Kolab 2.0 format detected
- $handler = Horde_Kolab_Format::factory('XML', $type);
- if (is_object($handler) && is_a($handler, 'PEAR_Error')) {
- continue;
+ $handler = class_exists('Horde_Kolab_Format') ? Horde_Kolab_Format::factory('XML', $object_type) : null;
+ if (!is_object($handler) || is_a($handler, 'PEAR_Error')) {
+ return false;
}
// XML-to-array
$object = $handler->load($xml);
$format->fromkolab2($object);
}
else {
// load Kolab 3 format using libkolabxml
$format->load($xml);
}
if ($format->is_valid()) {
$object = $format->to_array();
+ $object['_type'] = $object_type;
$object['_msguid'] = $msguid;
$object['_mailbox'] = $this->name;
$object['_attachments'] = array_merge((array)$object['_attachments'], $attachments);
$object['_formatobj'] = $format;
- $this->objcache[$msguid] = $object;
return $object;
}
return false;
}
/**
* Save an object in this folder.
*
* @param array $object The array that holds the data of the object.
* @param string $type The type of the kolab object.
* @param string $uid The UID of the old object if it existed before
* @return boolean True on success, false on error
*/
public function save(&$object, $type = null, $uid = null)
{
if (!$type)
$type = $this->type;
// copy attachments from old message
- if (!empty($object['_msguid']) && ($old = $this->read_object($object['_msguid'], $type, $object['_mailbox']))) {
- foreach ($old['_attachments'] as $name => $att) {
+ if (!empty($object['_msguid']) && ($old = $this->cache->get($object['_msguid'], $type, $object['_mailbox']))) {
+ foreach ((array)$old['_attachments'] as $name => $att) {
if (!isset($object['_attachments'][$name])) {
$object['_attachments'][$name] = $old['_attachments'][$name];
}
// load photo.attachment from old Kolab2 format to be directly embedded in xcard block
if ($name == 'photo.attachment' && !isset($object['photo']) && !$object['_attachments'][$name]['content'] && $att['id']) {
$object['photo'] = $this->get_attachment($object['_msguid'], $att['id'], $object['_mailbox']);
unset($object['_attachments'][$name]);
}
}
}
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->uid2msguid($uid))) {
+ else if ($result && $uid && ($msguid = $this->cache->uid2msguid($uid))) {
$this->imap->delete_message($msguid, $this->name);
+ $this->cache->set($object['_msguid'], false);
}
- // TODO: update cache with new UID
- $this->uid2msg[$object['uid']] = $result;
+ // update cache with new UID
+ if ($result) {
+ $object['_msguid'] = $result;
+ $this->cache->set($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?
* @param boolean $trigger Should the folder update be triggered?
*
* @return boolean True if successful, false on error
*/
public function delete($object, $expunge = true, $trigger = true)
{
- $msguid = is_array($object) ? $object['_msguid'] : $this->uid2msguid($object);
+ $msguid = is_array($object) ? $object['_msguid'] : $this->cache->uid2msguid($object);
+ $success = false;
if ($msguid && $expunge) {
- return $this->imap->delete_message($msguid, $this->name);
+ $success = $this->imap->delete_message($msguid, $this->name);
}
else if ($msguid) {
- return $this->imap->set_flag($msguid, 'DELETED', $this->name);
+ $success = $this->imap->set_flag($msguid, 'DELETED', $this->name);
}
- return false;
+ if ($success) {
+ $this->cache->set($result, 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->uid2msguid($uid, true)) {
+ 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->uid2msguid($uid)) {
+ if ($msguid = $this->cache->uid2msguid($uid)) {
if ($success = $this->imap->move_message($msguid, $target_folder, $this->name)) {
// TODO: update cache
return true;
}
else {
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;
}
- /**
- * Resolve an object UID into an IMAP message UID
- */
- private function uid2msguid($uid, $deleted = false)
- {
- if (!isset($this->uid2msg[$uid])) {
- // use IMAP SEARCH to get the right message
- $index = $this->imap->search_once($this->name, ($deleted ? '' : 'UNDELETED ') . 'HEADER SUBJECT ' . $uid);
- $results = $index->get();
- $this->uid2msg[$uid] = $results[0];
-
- // TODO: cache this lookup
- }
-
- return $this->uid2msg[$uid];
- }
-
-
/**
* Creates source of the configuration object message
*/
private function build_message(&$object, $type)
{
// 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->read_object($object['_msguid'], $type, $object['_mailbox'])))
+ 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);
$format->set($object);
$xml = $format->write();
$object['uid'] = $format->uid; // get read UID from format
if (!$format->is_valid() || empty($object['uid'])) {
return false;
}
$mime = new Mail_mime("\r\n");
$rcmail = rcube::get_instance();
$headers = array();
if ($ident = $rcmail->user->get_identity()) {
$headers['From'] = $ident['email'];
$headers['To'] = $ident['email'];
}
$headers['Date'] = date('r');
$headers['X-Kolab-Type'] = self::KTYPE_PREFIX . $type;
$headers['Subject'] = $object['uid'];
// $headers['Message-ID'] = $rcmail->gen_message_id();
$headers['User-Agent'] = $rcmail->config->get('useragent');
$mime->headers($headers);
$mime->setTXTBody('This is a Kolab Groupware object. '
. 'To view this object you will need an email client that understands the Kolab Groupware format. '
. "For a list of such email clients please visit http://www.kolab.org/\n\n");
$mime->addAttachment($xml,
$format->CTYPE,
'kolab.xml',
false, '8bit', 'attachment', RCMAIL_CHARSET, '', '',
$rcmail->config->get('mime_param_folding') ? 'quoted-printable' : null,
$rcmail->config->get('mime_param_folding') == 2 ? 'quoted-printable' : null,
'', RCMAIL_CHARSET
);
// save object attachments as separate parts
// TODO: optimize memory consumption by using tempfiles for transfer
foreach ((array)$object['_attachments'] as $name => $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']);
}
if (!empty($att['content'])) {
$mime->addAttachment($att['content'], $att['mimetype'], $name, false);
}
else if (!empty($att['path'])) {
$mime->addAttachment($att['path'], $att['mimetype'], $name, true);
}
}
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_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/Request.php');
$request = new HTTP_Request($url);
// set authentication credentials
if ($auth_user && $auth_passwd)
$request->setBasicAuth($auth_user, $auth_passwd);
$result = $request->sendRequest(true);
// rcube::write_log('trigger', $request->getResponseBody());
return $result;
}
/* Legacy methods to keep compatibility with the old Horde Kolab_Storage classes */
/**
* Compatibility method
*/
public function getOwner()
{
PEAR::raiseError("Call to deprecated method kolab_storage_folder::getOwner()");
return $this->get_owner();
}
/**
* Get IMAP ACL information for this folder
*/
public function getMyRights()
{
PEAR::raiseError("Call to deprecated method kolab_storage_folder::getMyRights()");
return $this->get_myrights();
}
/**
* NOP to stay compatible with the formerly used Horde classes
*/
public function getData()
{
PEAR::raiseError("Call to deprecated method kolab_storage_folder::getData()");
return $this;
}
/**
* List all Kolab objects of the given type
*/
public function getObjects($type = null)
{
PEAR::raiseError("Call to deprecated method kolab_storage_folder::getObjects()");
return $this->get_objects($type);
}
/**
* Getter for a single Kolab object, identified by its UID
*/
public function getObject($uid)
{
PEAR::raiseError("Call to deprecated method kolab_storage_folder::getObject()");
return $this->get_object($uid);
}
/**
*
*/
public function getAttachment($key)
{
PEAR::raiseError("Call to deprecated method not returning anything.");
return null;
}
/**
* Alias function of delete()
*/
public function deleteMessage($id, $trigger = true, $expunge = true)
{
PEAR::raiseError("Call to deprecated method kolab_storage_folder::deleteMessage()");
return $this->delete(array('_msguid' => $id), $trigger, $expunge);
}
/**
*
*/
public function deleteAll()
{
PEAR::raiseError("Call to deprecated method kolab_storage_folder::deleteAll()");
return $this->delete_all();
}
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Aug 17, 5:10 PM (1 d, 2 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1257425
Default Alt Text
(96 KB)

Event Timeline