Branch 'roundcubemail-plugins-kolab-format2' - 3 commits - plugins/libkolab
Thomas Brüderli
bruederli at kolabsys.com
Tue Nov 13 13:29:01 CET 2012
plugins/libkolab/lib/kolab_date_recurrence.php | 113 ++++++++++----
plugins/libkolab/lib/kolab_format.php | 95 ++++-------
plugins/libkolab/lib/kolab_format_configuration.php | 21 --
plugins/libkolab/lib/kolab_format_contact.php | 135 ++++++++++++++++-
plugins/libkolab/lib/kolab_format_distributionlist.php | 17 +-
plugins/libkolab/lib/kolab_storage.php | 3
plugins/libkolab/lib/kolab_storage_folder.php | 21 +-
7 files changed, 278 insertions(+), 127 deletions(-)
New commits:
commit 077ad03b70e913b9c679178dbeba606d35964d5b
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date: Tue Nov 13 13:28:35 2012 +0100
Restore recurrence computation code to a version before the refactoring for libkolab bindings
diff --git a/plugins/libkolab/lib/kolab_date_recurrence.php b/plugins/libkolab/lib/kolab_date_recurrence.php
index 3aaa399..9eeeca1 100644
--- a/plugins/libkolab/lib/kolab_date_recurrence.php
+++ b/plugins/libkolab/lib/kolab_date_recurrence.php
@@ -3,8 +3,7 @@
/**
* Recurrence computation class for xcal-based Kolab format objects
*
- * Utility class to compute instances of recurring events.
- * It requires the libcalendaring PHP module to be installed and loaded.
+ * Uitility class to compute instances of recurring events.
*
* @version @package_version@
* @author Thomas Bruederli <bruederli at kolabsys.com>
@@ -26,31 +25,43 @@
*/
class kolab_date_recurrence
{
- private /* EventCal */ $engine;
- private /* kolab_format_xcal */ $object;
- private /* DateTime */ $start;
- private /* DateTime */ $next;
- private /* cDateTime */ $cnext;
- private /* DateInterval */ $duration;
+ private $engine;
+ private $object;
+ private $next;
+ private $duration;
+ private $tz_offset = 0;
+ private $dst_start = 0;
+ private $allday = false;
+ private $hour = 0;
/**
* Default constructor
*
* @param array The Kolab object to operate on
*/
- function __construct($object)
+ function __construct($event)
{
- $data = $object->to_array();
+ $this->object = $object = $event->to_array();
+ $this->next = new Horde_Date($object['start'], kolab_format::$timezone->getName());
- $this->object = $object;
- $this->engine = $object->to_libcal();
- $this->start = $this->next = $data['start'];
- $this->cnext = kolab_format::get_datetime($this->next);
-
- if (is_object($data['start']) && is_object($data['end']))
- $this->duration = $data['start']->diff($data['end']);
+ if (is_object($object['start']) && is_object($object['end']))
+ $this->duration = $object['start']->diff($object['end']);
else
- $this->duration = new DateInterval('PT' . ($data['end'] - $data['start']) . 'S');
+ $this->duration = new DateInterval('PT' . ($object['end'] - $object['start']) . 'S');
+
+ // use (copied) Horde classes to compute recurring instances
+ // TODO: replace with something that has less than 6'000 lines of code
+ $this->engine = new Horde_Date_Recurrence($this->next);
+ $this->engine->fromRRule20($this->to_rrule($object['recurrence'])); // TODO: get that string directly from libkolabxml
+
+ foreach ((array)$object['recurrence']['EXDATE'] as $exdate)
+ $this->engine->addException($exdate->format('Y'), $exdate->format('n'), $exdate->format('j'));
+
+ $now = new DateTime('now', kolab_format::$timezone);
+ $this->tz_offset = $object['allday'] ? $now->getOffset() - date('Z') : 0;
+ $this->dst_start = $this->next->format('I');
+ $this->allday = $object['allday'];
+ $this->hour = $this->next->hour;
}
/**
@@ -62,14 +73,20 @@ class kolab_date_recurrence
public function next_start($timestamp = false)
{
$time = false;
-
- if ($this->engine && $this->next) {
- if (($cnext = new cDateTime($this->engine->getNextOccurence($this->cnext))) && $cnext->isValid()) {
- $next = kolab_format::php_datetime($cnext);
- $time = $timestamp ? $next->format('U') : $next;
- $this->cnext = $cnext;
- $this->next = $next;
+ if ($this->next && ($next = $this->engine->nextActiveRecurrence(array('year' => $this->next->year, 'month' => $this->next->month, 'mday' => $this->next->mday + 1, 'hour' => $this->next->hour, 'min' => $this->next->min, 'sec' => $this->next->sec)))) {
+ if ($this->allday) {
+ $next->hour = $this->hour; # fix time for all-day events
+ $next->min = 0;
+ }
+ if ($timestamp) {
+ # consider difference in daylight saving between base event and recurring instance
+ $dst_diff = ($this->dst_start - $next->format('I')) * 3600;
+ $time = $next->timestamp() - $this->tz_offset - $dst_diff;
}
+ else {
+ $time = $next->toDateTime();
+ }
+ $this->next = $next;
}
return $time;
@@ -86,7 +103,7 @@ class kolab_date_recurrence
$next_end = clone $next_start;
$next_end->add($this->duration);
- $next = $this->object->to_array();
+ $next = $this->object;
$next['recurrence_id'] = $next_start->format('Y-m-d');
$next['start'] = $next_start;
$next['end'] = $next_end;
@@ -106,11 +123,49 @@ class kolab_date_recurrence
*/
public function end($limit = 'now +1 year')
{
- $limit_dt = new DateTime($limit);
- if ($this->engine && ($cend = $this->engine->getLastOccurrence()) && ($end_dt = kolab_format::php_datetime(new cDateTime($cend))) && $end_dt < $limit_dt) {
- return $end_dt->format('U');
+ if ($this->object['recurrence']['UNTIL'])
+ return $this->object['recurrence']['UNTIL']->format('U');
+
+ $limit_time = strtotime($limit);
+ while ($next_start = $this->next_start(true)) {
+ if ($next_start > $limit_time)
+ break;
+ }
+
+ if ($this->next) {
+ $next_end = $this->next->toDateTime();
+ $next_end->add($this->duration);
+ return $next_end->format('U');
}
return false;
}
+
+ /**
+ * Convert the internal structured data into a vcalendar RRULE 2.0 string
+ */
+ private function to_rrule($recurrence)
+ {
+ if (is_string($recurrence))
+ return $recurrence;
+
+ $rrule = '';
+ foreach ((array)$recurrence as $k => $val) {
+ $k = strtoupper($k);
+ switch ($k) {
+ case 'UNTIL':
+ $val = $val->format('Ymd\THis');
+ break;
+ case 'EXDATE':
+ foreach ((array)$val as $i => $ex)
+ $val[$i] = $ex->format('Ymd\THis');
+ $val = join(',', (array)$val);
+ break;
+ }
+ $rrule .= $k . '=' . $val . ';';
+ }
+
+ return $rrule;
+ }
+
}
commit 8d4e3e04e0948d7489502062dfab69f14290ecc5
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date: Tue Nov 13 12:25:44 2012 +0100
Fix configuration object handling
diff --git a/plugins/libkolab/lib/kolab_format_configuration.php b/plugins/libkolab/lib/kolab_format_configuration.php
index edf22f7..a099c59 100644
--- a/plugins/libkolab/lib/kolab_format_configuration.php
+++ b/plugins/libkolab/lib/kolab_format_configuration.php
@@ -28,12 +28,6 @@ class kolab_format_configuration extends kolab_format
protected $xmltype = 'configuration';
- private $type_map = array(
- 'dictionary' => Configuration::TypeDictionary,
- 'category' => Configuration::TypeCategoryColor,
- );
-
-
/**
* Set properties to the kolabformat object
*
@@ -46,19 +40,6 @@ class kolab_format_configuration extends kolab_format
if ($object['type'])
$this->subtype = $object['type'];
- // read type-specific properties
- switch ($this->subtype) {
- case 'dictionary':
- // TODO: implement this
- break;
-
- case 'category':
- // TODO: implement this
- break;
- default:
- return false;
- }
-
// adjust content-type string
$this->CTYPE = 'application/x-vnd.kolab.configuration.' . $this->subtype;
commit c1fce33bc37a72ef13dffed046aa5dd5f0f6fae7
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date: Tue Nov 13 12:04:34 2012 +0100
Implement writing of contacts and distribution-lists using Horde classes
diff --git a/plugins/libkolab/lib/kolab_format.php b/plugins/libkolab/lib/kolab_format.php
index 622b411..d976328 100644
--- a/plugins/libkolab/lib/kolab_format.php
+++ b/plugins/libkolab/lib/kolab_format.php
@@ -37,6 +37,7 @@ abstract class kolab_format
protected $handler;
protected $data;
protected $xmldata;
+ protected $kolab_object;
protected $loaded = false;
protected $version = 2.0;
@@ -165,36 +166,6 @@ abstract class kolab_format
}
/**
- * 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
@@ -215,7 +186,7 @@ abstract class kolab_format
$handler = Horde_Kolab_Format::factory('XML', $this->xmltype, array('subtype' => $this->subtype));
if (!is_object($handler) || is_a($handler, 'PEAR_Error')) {
- return false;
+ return;
}
$this->handler = $handler;
@@ -227,45 +198,32 @@ abstract class kolab_format
*
* @return boolean True if there were errors, False if OK
*/
- protected function format_errors()
+ protected function format_errors($p)
{
- $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";
- }
+ $ret = false;
- if ($log) {
+ if (is_object($p) && is_a($p, 'PEAR_Error')) {
rcube::raise_error(array(
'code' => 660,
'type' => 'php',
'file' => __FILE__,
'line' => __LINE__,
- 'message' => "kolabformat $log: " . kolabformat::errorMessage(),
+ 'message' => "Horde_Kolab_Format error: " . $p->getMessage(),
), true);
+
+ $ret = true;
}
return $ret;
}
/**
- * Save the last generated UID to the object properties.
- * Should be called after kolabformat::writeXXXX();
+ * Generate a unique identifier for a Kolab object
*/
- protected function update_uid()
+ protected function generate_uid()
{
- // get generated UID
- if (!$this->data['uid']) {
- $this->data['uid'] = 'TODO';
- }
+ $rc = rcube::get_instance();
+ return strtoupper(md5(time() . uniqid(rand())) . '-' . substr(md5($rc->user ? $rc->user->get_username() : rand()), 0, 16));
}
/**
@@ -298,11 +256,15 @@ abstract class kolab_format
*/
public function load($xml)
{
+ $this->loaded = false;
+
// XML-to-array
$object = $this->handler->load($xml);
- $this->fromkolab2($object);
-
- $this->loaded = !$this->format_errors();
+ if (!$this->format_errors($object)) {
+ $this->kolab_object = $object;
+ $this->fromkolab2($object);
+ $this->loaded = true;
+ }
}
/**
@@ -315,12 +277,23 @@ abstract class kolab_format
{
$this->init();
- // TODO: implement his
+ if ($version && !self::supports($version))
+ return false;
+
+ // generate UID if not set
+ if (!$this->kolab_object['uid']) {
+ $this->kolab_object['uid'] = $this->generate_uid();
+ }
+
+ $xml = $this->handler->save($this->kolab_object);
- if (!$this->format_errors())
- $this->update_uid();
- else
+ if (!$this->format_errors($xml) && strlen($xml)) {
+ $this->xmldata = $xml;
+ $this->data['uid'] = $this->kolab_object['uid'];
+ }
+ else {
$this->xmldata = null;
+ }
return $this->xmldata;
}
diff --git a/plugins/libkolab/lib/kolab_format_configuration.php b/plugins/libkolab/lib/kolab_format_configuration.php
index 2970b10..edf22f7 100644
--- a/plugins/libkolab/lib/kolab_format_configuration.php
+++ b/plugins/libkolab/lib/kolab_format_configuration.php
@@ -63,7 +63,7 @@ class kolab_format_configuration extends kolab_format
$this->CTYPE = 'application/x-vnd.kolab.configuration.' . $this->subtype;
// cache this data
- $this->data = $object;
+ $this->data = $this->kolab_object = $object;
unset($this->data['_formatobj']);
}
diff --git a/plugins/libkolab/lib/kolab_format_contact.php b/plugins/libkolab/lib/kolab_format_contact.php
index fb033e3..cab7ded 100644
--- a/plugins/libkolab/lib/kolab_format_contact.php
+++ b/plugins/libkolab/lib/kolab_format_contact.php
@@ -94,6 +94,13 @@ class kolab_format_contact extends kolab_format
private $kolab2_addresstypes = array(
'business' => 'work'
);
+ private $kolab2_arrays = array(
+ 'web-page' => 'url',
+ 'im-address' => true,
+ 'manager-name' => true,
+ 'assistant' => true,
+ 'children' => true,
+ );
private $kolab2_gender = array(0 => 'male', 1 => 'female');
@@ -114,7 +121,112 @@ class kolab_format_contact extends kolab_format
{
$this->init();
- // TODO: implement this
+ if ($object['uid'])
+ $this->kolab_object['uid'] = $object['uid'];
+
+ $this->kolab_object['last-modification-date'] = time();
+
+ // map fields rcube => $kolab
+ foreach ($this->kolab2_fieldmap as $kolab => $rcube) {
+ $this->kolab_object[$kolab] = $object[$rcube];
+ }
+
+ // map gener values
+ if (isset($object['gender'])) {
+ $gender_map = array_flip($this->kolab2_gender);
+ $this->kolab_object['gender'] = $gender_map[$object['gender']];
+ }
+
+ // format dates
+ if ($object['birthday'] && ($date = @strtotime($object['birthday'])))
+ $this->kolab_object['birthday'] = date('Y-m-d', $date);
+ if ($object['anniversary'] && ($date = @strtotime($object['anniversary'])))
+ $this->kolab_object['anniversary'] = date('Y-m-d', $date);
+
+ // make sure these attributes are single string values
+ foreach ($this->kolab2_arrays as $col => $field) {
+ if (!is_array($this->kolab_object[$col]))
+ continue;
+ if ($field === true) {
+ $values = $this->kolab_object[$col];
+ }
+ else {
+ $values = array();
+ foreach ($this->kolab_object[$col] as $v)
+ $values[] = $v[$field];
+ }
+ $this->kolab_object[$col] = join('; ', $values);
+ }
+
+ // save email addresses to field 'emails'
+ $emails = array();
+ foreach ((array)$object['email'] as $email)
+ $emails[] = $email;
+ $this->kolab_object['emails'] = join(', ', array_filter($emails));
+ unset($this->kolab_object['email']);
+
+ // map phone types
+ foreach ((array)$this->kolab_object['phone'] as $i => $phone) {
+ if ($type = $this->phonetypes[$phone['type']])
+ $this->kolab_object['phone'][$i]['type'] = $type;
+ }
+
+ // save addresses (how weird is that?!)
+ $this->kolab_object['address'] = array();
+ $seen_types = array();
+ foreach ((array)$object['address'] as $adr) {
+ if ($type = $this->addresstypes[$adr['type']]) {
+ $updated = false;
+ $basekey = 'addr-' . $type . '-';
+
+ $this->kolab_object[$basekey . 'type'] = $type;
+ $this->kolab_object[$basekey . 'street'] = $adr['street'];
+ $this->kolab_object[$basekey . 'locality'] = $adr['locality'];
+ $this->kolab_object[$basekey . 'postal-code'] = $adr['zipcode'];
+ $this->kolab_object[$basekey . 'region'] = $adr['region'];
+ $this->kolab_object[$basekey . 'country'] = $adr['country'];
+
+ // check if we updates an existing address entry of this type...
+ foreach($this->kolab_object['address'] as $index => $address) {
+ if ($this->kolab_object['type'] == $type) {
+ $this->kolab_object['address'][$index] = $new_address;
+ $updated = true;
+ }
+ }
+
+ // ... add as new if not
+ if (!$updated) {
+ $this->kolab_object['address'][] = array(
+ 'type' => $type,
+ 'street' => $adr['street'],
+ 'locality' => $adr['locality'],
+ 'postal-code' => $adr['code'],
+ 'region' => $adr['region'],
+ 'country' => $adr['country'],
+ );
+ }
+
+ $seen_types[$type] = true;
+ }
+ else if ($adr['type'] == 'office') {
+ $this->kolab_object['office-location'] = $adr['locality'];
+ }
+ }
+
+ // unset removed address properties
+ foreach ($this->addresstypes as $type) {
+ if (!$seen_types[$type]) {
+ $basekey = 'addr-' . $type . '-';
+ unset(
+ $this->kolab_object[$basekey . 'type'],
+ $this->kolab_object[$basekey . 'street'],
+ $this->kolab_object[$basekey . 'locality'],
+ $this->kolab_object[$basekey . 'postal-code'],
+ $this->kolab_object[$basekey . 'region'],
+ $this->kolab_object[$basekey . 'country']
+ );
+ }
+ }
// cache this data
$this->data = $object;
@@ -126,7 +238,7 @@ class kolab_format_contact extends kolab_format
*/
public function is_valid()
{
- return $this->data;
+ return strlen($this->data['uid']);
}
/**
@@ -155,13 +267,28 @@ class kolab_format_contact extends kolab_format
{
$object = array(
'uid' => $record['uid'],
+ 'changed' => $record['last-modification-date'],
'email' => array(),
'phone' => array(),
);
foreach ($this->kolab2_fieldmap as $kolab => $rcube) {
- if (is_array($record[$kolab]) || strlen($record[$kolab]))
- $object[$rcube] = $record[$kolab];
+ if (is_array($record[$kolab]) || strlen($record[$kolab])) {
+ $object[$rcube] = $record[$kolab];
+
+ // split pseudo-arry values
+ if ($field = $this->kolab2_arrays[$kolab]) {
+ if ($field === true) {
+ $object[$rcube] = explode('; ', $record[$kolab]);
+ }
+ else {
+ $values = array();
+ foreach (explode('; ', $record[$kolab]) as $v)
+ $values[] = array($field => $v);
+ $object[$rcube] = $values;
+ }
+ }
+ }
}
if (isset($record['gender']))
diff --git a/plugins/libkolab/lib/kolab_format_distributionlist.php b/plugins/libkolab/lib/kolab_format_distributionlist.php
index ce44f38..b6fc566 100644
--- a/plugins/libkolab/lib/kolab_format_distributionlist.php
+++ b/plugins/libkolab/lib/kolab_format_distributionlist.php
@@ -38,7 +38,20 @@ class kolab_format_distributionlist extends kolab_format
{
$this->init();
- // TODO: implement this
+ if ($object['uid'])
+ $this->kolab_object['uid'] = $object['uid'];
+
+ $this->kolab_object['last-modification-date'] = time();
+ $this->kolab_object['last-name'] = $object['name'];
+ $this->kolab_object['member'] = array();
+
+ foreach ($object['member'] as $member) {
+ $this->kolab_object['member'][] = array(
+ 'uid' => $member['uid'],
+ 'smtp-address' => $member['email'],
+ 'display-name' => $member['name'],
+ );
+ }
// set type property for proper caching
$object['_type'] = 'distribution-list';
@@ -50,7 +63,7 @@ class kolab_format_distributionlist extends kolab_format
public function is_valid()
{
- return $this->data;
+ return !empty($this->data['uid']);
}
/**
diff --git a/plugins/libkolab/lib/kolab_storage.php b/plugins/libkolab/lib/kolab_storage.php
index 79f949a..5b8eca7 100644
--- a/plugins/libkolab/lib/kolab_storage.php
+++ b/plugins/libkolab/lib/kolab_storage.php
@@ -31,7 +31,7 @@ class kolab_storage
const SERVERSIDE_SUBSCRIPTION = 0;
const CLIENTSIDE_SUBSCRIPTION = 1;
- public static $version = 3.0;
+ public static $version = 2.0;
public static $last_error;
private static $ready = false;
@@ -50,7 +50,6 @@ class kolab_storage
$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('Horde_Kolab_Format') &&
(self::$imap->get_capability('METADATA') || self::$imap->get_capability('ANNOTATEMORE') || self::$imap->get_capability('ANNOTATEMORE2'));
diff --git a/plugins/libkolab/lib/kolab_storage_folder.php b/plugins/libkolab/lib/kolab_storage_folder.php
index 5ba5fa4..23bb3a6 100644
--- a/plugins/libkolab/lib/kolab_storage_folder.php
+++ b/plugins/libkolab/lib/kolab_storage_folder.php
@@ -543,22 +543,25 @@ class kolab_storage_folder
if ($object['_attachments'][$key] == false) {
unset($object['_attachments'][$key]);
}
- // load photo.attachment from old Kolab2 format to be directly embedded in xcard block
+ // load photo.attachment from old Kolab2 format
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]);
+ $existing_photo = true;
+ if (isset($object['photo']))
+ unset($object['_attachments'][$key]);
+ else
+ $object['photo'] = $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
+ // save new contact photo to attachment for Kolab2 format
+ if ($object['photo'] && !$existing_photo) {
+ $attkey = 'photo.attachment'; // 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']),
);
+ $object['photo'] = $attkey;
}
// process attachments
@@ -716,11 +719,11 @@ class kolab_storage_folder
return false;
$format->set($object);
- $xml = $format->write(kolab_storage::$version);
+ $xml = $format->write();
$object['uid'] = $format->uid; // read UID from format
$object['_formatobj'] = $format;
- if (!$format->is_valid() || empty($object['uid'])) {
+ if (empty($xml) || !$format->is_valid() || empty($object['uid'])) {
return false;
}
More information about the commits
mailing list