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