2 commits - plugins/calendar plugins/libcalendaring

Thomas Brüderli bruederli at kolabsys.com
Fri Feb 27 18:14:01 CET 2015


 plugins/calendar/calendar.php                     |   13 +
 plugins/calendar/drivers/kolab/kolab_calendar.php |   79 +++++++----
 plugins/calendar/drivers/kolab/kolab_driver.php   |  147 +++++++++-------------
 plugins/libcalendaring/libcalendaring.php         |   17 ++
 4 files changed, 141 insertions(+), 115 deletions(-)

New commits:
commit 16ad0e6215a0b6d72d254fc0680e0a7a777e7745
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Fri Feb 27 18:13:57 2015 +0100

    Update parstat recursively on all recurrence exceptions

diff --git a/plugins/libcalendaring/libcalendaring.php b/plugins/libcalendaring/libcalendaring.php
index f215ce6..94ff7f2 100644
--- a/plugins/libcalendaring/libcalendaring.php
+++ b/plugins/libcalendaring/libcalendaring.php
@@ -401,17 +401,28 @@ class libcalendaring extends rcube_plugin
      * @param string  The PARTSTAT value to set
      * @return mixed  Email address of the updated attendee or False if none matching found
      */
-    public function set_partstat(&$event, $status)
+    public function set_partstat(&$event, $status, $recursive = true)
     {
+        $success = false;
         $emails = $this->get_user_emails();
         foreach ((array)$event['attendees'] as $i => $attendee) {
             if ($attendee['email'] && in_array(strtolower($attendee['email']), $emails)) {
                 $event['attendees'][$i]['status'] = strtoupper($status);
-                return $attendee['email'];
+                $success = $attendee['email'];
             }
         }
 
-        return false;
+        // apply partstat update to each existing exception
+        if ($event['recurrence'] && is_array($event['recurrence']['EXCEPTIONS'])) {
+            foreach ($event['recurrence']['EXCEPTIONS'] as $i => $exception) {
+                $this->set_partstat($event['recurrence']['EXCEPTIONS'][$i], $status, false);
+            }
+
+            // set link to top-level exceptions
+            $event['exceptions'] = &$event['recurrence']['EXCEPTIONS'];
+        }
+
+        return $success;
     }
 
 


commit 94260b2aebe81645761260f4f5d221bff0e96cf0
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Fri Feb 27 17:52:17 2015 +0100

    Refactor identification of recurring event instances (#4722):
    
    - All instances of a recurring series have -YmdTHis appended to their ID
    - In 'all' savemode, the master event identified by UID is loaded and updated
    - kolab_driver::update_event() returns the UID of the master event in 'all' mode.
      This is then used to send iTip messages for the entire series

diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php
index 5d32188..6cf29db 100644
--- a/plugins/calendar/calendar.php
+++ b/plugins/calendar/calendar.php
@@ -1175,9 +1175,13 @@ class calendar extends rcube_plugin
         $event['attendees'] = $master['attendees'];  // this tricks us into the next if clause
       }
 
+      // delete old reference if saved as new
+      if ($event['_savemode'] == 'future' || $event['_savemode'] == 'new') {
+        $old = null;
+      }
+
       $event['id'] = $success;
       $event['_savemode'] = 'all';
-      $old = null;
     }
 
     // send out notifications
@@ -1566,6 +1570,9 @@ class calendar extends rcube_plugin
       $filename = asciiwords(html_entity_decode($filename));  // to 7bit ascii
       if (!empty($event_id)) {
         if ($event = $this->driver->get_event(array('calendar' => $calid, 'id' => $event_id))) {
+          if ($event['recurrence_id']) {
+            $event = $this->driver->get_event(array('calendar' => $calid, 'id' => $event['recurrence_id']));
+          }
           $events = array($event);
           $filename = asciiwords($event['title']);
           if (empty($filename))
@@ -2276,9 +2283,9 @@ class calendar extends rcube_plugin
   public static function event_diff($a, $b)
   {
     $diff = array();
-    $ignore = array('changed' => 1, 'attachments' => 1, '_notify' => 1, '_owner' => 1, '_savemode' => 1);
+    $ignore = array('changed' => 1, 'attachments' => 1);
     foreach (array_unique(array_merge(array_keys($a), array_keys($b))) as $key) {
-      if (!$ignore[$key] && $a[$key] != $b[$key])
+      if (!$ignore[$key] && $key[0] != '_' && $a[$key] != $b[$key])
         $diff[] = $key;
     }
     
diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php
index 79450af..10c256a 100644
--- a/plugins/calendar/drivers/kolab/kolab_calendar.php
+++ b/plugins/calendar/drivers/kolab/kolab_calendar.php
@@ -187,7 +187,7 @@ class kolab_calendar extends kolab_storage_folder_api
   {
     // directly access storage object
     if (!$this->events[$id] && ($record = $this->storage->get_object($id)))
-        $this->events[$id] = $this->_to_rcube_event($record);
+        $this->events[$id] = $this->_to_driver_event($record, true);
 
     // event not found, maybe a recurring instance is requested
     if (!$this->events[$id]) {
@@ -195,17 +195,16 @@ class kolab_calendar extends kolab_storage_folder_api
       $instance_id = substr($id, strlen($master_id) + 1);
 
       if ($master_id != $id && ($record = $this->storage->get_object($master_id))) {
-        $master = $this->events[$master_id] = $this->_to_rcube_event($record);
+        $master = $this->_to_driver_event($record);
       }
 
       // check for match on the first instance already
       if ($master['_instance'] && $master['_instance'] == $instance_id) {
-        $this->events[$id] = $this->events[$master_id];
+        $this->events[$id] = $master;
       }
       // check for match in top-level exceptions (aka loose single occurrences)
       else if ($master && $master['_formatobj'] && ($instance = $master['_formatobj']->get_instance($instance_id))) {
-        $instance = $this->_to_rcube_event($instance);
-        $this->events[$instance['id']] = $instance;
+        $this->events[$id] = $this->_to_driver_event($instance);
       }
       else if ($master && is_array($master['recurrence'])) {
         $this->get_recurring_events($record, $master['start'], null, $id);
@@ -294,12 +293,13 @@ class kolab_calendar extends kolab_storage_folder_api
 
     $events = array();
     foreach ($this->storage->select($query) as $record) {
-      $event = $this->_to_rcube_event($record);
-      $this->events[$event['id']] = $event;
+      $event = $this->_to_driver_event($record, !$virtual);
 
       // remember seen categories
-      if ($event['categories'])
-        $this->categories[$event['categories']]++;
+      if ($event['categories']) {
+        $cat = is_array($event['categories']) ? $event['categories'][0] : $event['categories'];
+        $this->categories[$cat]++;
+    }
 
       // list events in requested time window
       if ($event['start'] <= $end && $event['end'] >= $start) {
@@ -321,11 +321,11 @@ class kolab_calendar extends kolab_storage_folder_api
 
         // find and merge exception for the first instance
         if ($virtual && !empty($event['recurrence']) && is_array($event['recurrence']['EXCEPTIONS'])) {
-          $event_date = $event['start']->format('Ymd');
           foreach ($event['recurrence']['EXCEPTIONS'] as $exception) {
-            $exdate = $exception['recurrence_date'] ? $exception['recurrence_date']->format('Ymd') : substr($exception['_instance'], 0, 8);
-            if ($exdate == $event_date) {
-              $event['_instance'] = $exception['_instance'];
+            if ($event['_instance'] == $exception['_instance']) {
+              // clone date objects from main event before adjusting them with exception data
+              if (is_object($event['start'])) $event['start'] = clone $record['start'];
+              if (is_object($event['end']))   $event['end']   = clone $record['end'];
               kolab_driver::merge_exception_data($event, $exception);
             }
           }
@@ -342,7 +342,7 @@ class kolab_calendar extends kolab_storage_folder_api
       // add top-level exceptions (aka loose single occurrences)
       else if (is_array($record['exceptions'])) {
         foreach ($record['exceptions'] as $ex) {
-          $component = $this->_to_rcube_event($ex);
+          $component = $this->_to_driver_event($ex);
           if ($component['start'] <= $end && $component['end'] >= $start) {
             $events[] = $component;
           }
@@ -445,7 +445,7 @@ class kolab_calendar extends kolab_storage_folder_api
     unset($event['links']);
 
     //generate new event from RC input
-    $object = $this->_from_rcube_event($event);
+    $object = $this->_from_driver_event($event);
     $saved = $this->storage->save($object, 'event');
     
     if (!$saved) {
@@ -460,8 +460,7 @@ class kolab_calendar extends kolab_storage_folder_api
       // save links in configuration.relation object
       $this->save_links($event['uid'], $links);
 
-      $event['id'] = $event['uid'];
-      $this->events = array($event['uid'] => $this->_to_rcube_event($object));
+      $this->events = array($event['uid'] => $this->_to_driver_event($object, true));
     }
     
     return $saved;
@@ -485,7 +484,7 @@ class kolab_calendar extends kolab_storage_folder_api
     $links = $event['links'];
     unset($event['links']);
 
-    $object = $this->_from_rcube_event($event, $old);
+    $object = $this->_from_driver_event($event, $old);
     $saved = $this->storage->save($object, 'event', $old['uid']);
 
     if (!$saved) {
@@ -500,7 +499,7 @@ class kolab_calendar extends kolab_storage_folder_api
       $this->save_links($event['uid'], $links);
 
       $updated = true;
-      $this->events = array($event['id'] => $this->_to_rcube_event($object));
+      $this->events = array($event['uid'] => $this->_to_driver_event($object, true));
 
       // refresh local cache with recurring instances
       if ($exception_id) {
@@ -626,7 +625,7 @@ class kolab_calendar extends kolab_storage_folder_api
         else if (!$exception['_instance'] && is_a($exception['start'], 'DateTime'))
           $exception['_instance'] = $exception['start']->format($recurrence_id_format);
 
-        $rec_event = $this->_to_rcube_event($exception);
+        $rec_event = $this->_to_driver_event($exception);
         $rec_event['id'] = $event['uid'] . '-' . $exception['_instance'];
         $rec_event['isexception'] = 1;
 
@@ -675,7 +674,7 @@ class kolab_calendar extends kolab_storage_folder_api
       // add to output if in range
       $rec_id = $event['uid'] . '-' . $instance_id;
       if (($next_event['start'] <= $end && $next_event['end'] >= $start) || ($event_id && $rec_id == $event_id)) {
-        $rec_event = $this->_to_rcube_event($next_event);
+        $rec_event = $this->_to_driver_event($next_event);
         $rec_event['_instance'] = $instance_id;
         $rec_event['_count'] = $i + 1;
 
@@ -707,7 +706,7 @@ class kolab_calendar extends kolab_storage_folder_api
   /**
    * Convert from Kolab_Format to internal representation
    */
-  private function _to_rcube_event($record)
+  private function _to_driver_event($record, $noinst = false)
   {
     $record['calendar'] = $this->id;
     $record['links'] = $this->get_links($record['uid']);
@@ -717,17 +716,31 @@ class kolab_calendar extends kolab_storage_folder_api
       $record = kolab_driver::add_partstat_class($record, array('NEEDS-ACTION','DECLINED'), $this->get_owner());
     }
 
-    return kolab_driver::to_rcube_event($record);
+    // add instance identifier to first occurrence (master event)
+    $recurrence_id_format = $record['allday'] ? 'Ymd' : 'Ymd\THis';
+    if (!$noinst && $record['recurrence'] && !$record['recurrence_id'] && !$record['_instance']) {
+      $record['_instance'] = $record['start']->format($recurrence_id_format);
+    }
+    else if (is_a($record['recurrence_date'], 'DateTime')) {
+      $record['_instance'] = $record['recurrence_date']->format($recurrence_id_format);
+    }
+
+    // clean up exception data
+    if ($record['recurrence'] && is_array($record['recurrence']['EXCEPTIONS'])) {
+      array_walk($record['recurrence']['EXCEPTIONS'], function(&$exception) {
+        unset($exception['_mailbox'], $exception['_msguid'], $exception['_formatobj'], $exception['_attachments']);
+      });
+    }
+
+    return $record;
   }
 
    /**
    * Convert the given event record into a data structure that can be passed to Kolab_Storage backend for saving
-   * (opposite of self::_to_rcube_event())
+   * (opposite of self::_to_driver_event())
    */
-  private function _from_rcube_event($event, $old = array())
+  private function _from_driver_event($event, $old = array())
   {
-    $event = kolab_driver::from_rcube_event($event, $old);
-
     // set current user as ORGANIZER
     $identity = $this->cal->rc->user->list_emails(true);
     if (empty($event['attendees']) && $identity['email'])
@@ -750,8 +763,18 @@ class kolab_calendar extends kolab_storage_folder_api
       $event['comment'] = $old['comment'];
     }
 
+    // clean up exception data
+    if (is_array($event['exceptions'])) {
+      array_walk($event['exceptions'], function(&$exception) {
+        unset($exception['_mailbox'], $exception['_msguid'], $exception['_formatobj'], $exception['_attachments'],
+          $event['attachments'], $event['deleted_attachments'], $event['recurrence_id']);
+      });
+    }
+
+
     // remove some internal properties which should not be saved
-    unset($event['_savemode'], $event['_fromcalendar'], $event['_identity'], $event['_folder_id'], $event['className']);
+    unset($event['_savemode'], $event['_fromcalendar'], $event['_identity'], $event['_folder_id'],
+      $event['recurrence_id'], $event['attachments'], $event['deleted_attachments'], $event['className']);
 
     // copy meta data (starting with _) from old object
     foreach ((array)$old as $key => $val) {
diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php
index cfdcb4c..1bd8caf 100644
--- a/plugins/calendar/drivers/kolab/kolab_driver.php
+++ b/plugins/calendar/drivers/kolab/kolab_driver.php
@@ -549,9 +549,7 @@ class kolab_driver extends calendar_driver
     if ($cal) {
       if ($storage = $this->get_calendar($cal)) {
         $result = $storage->get_event($id);
-        if (is_array($result))
-          self::clean_rcube_event_out($result);
-        return $result;
+        return self::to_rcube_event($result);
       }
       // get event from the address books birthday calendar
       else if ($cal == self::BIRTHDAY_CALENDAR_ID) {
@@ -562,8 +560,7 @@ class kolab_driver extends calendar_driver
     else {
       foreach ($this->filter_calendars($writeable, $active, $personal) as $calendar) {
         if ($result = $calendar->get_event($id)) {
-          self::clean_rcube_event_out($result);
-          return $result;
+          return self::to_rcube_event($result);
         }
       }
     }
@@ -581,10 +578,12 @@ class kolab_driver extends calendar_driver
     if (!$this->validate($event))
       return false;
 
+    $event = self::from_rcube_event($event);
+
     $cid = $event['calendar'] ? $event['calendar'] : reset(array_keys($this->calendars));
     if ($storage = $this->get_calendar($cid)) {
       // if this is a recurrence instance, append as exception to an already existing object for this UID
-      if (!empty($event['recurrence_date']) && ($master = $this->get_event($event['uid']))) {
+      if (!empty($event['recurrence_date']) && ($master = $storage->get_event($event['uid']))) {
         self::add_exception($master, $event);
         $success = $storage->update_event($master);
       }
@@ -611,7 +610,10 @@ class kolab_driver extends calendar_driver
    */
   public function edit_event($event)
   {
-    return $this->update_event($event);
+     if (!($storage = $this->get_calendar($event['calendar'])))
+       return false;
+
+    return $this->update_event(self::from_rcube_event($event, $storage->get_event($event['id'])));
   }
 
   /**
@@ -630,12 +632,16 @@ class kolab_driver extends calendar_driver
       if ($storage = $this->get_calendar($event['calendar'])) {
         $update_event = $storage->get_event($event['recurrence_id']);
         $update_event['_savemode'] = $event['_savemode'];
+        $update_event['id'] = $update_event['uid'];
         unset($update_event['recurrence_id']);
         self::merge_attendee_data($update_event, $attendees);
       }
     }
 
     if (($ret = $this->update_attendees($update_event, $attendees)) && $this->rc->config->get('kolab_invitation_calendars')) {
+      // replace with master event (for iTip reply)
+      $event = self::to_rcube_event($update_event);
+
       // re-assign to the according (virtual) calendar
       if (strtoupper($status) == 'DECLINED')
         $event['calendar'] = self::INVITATIONS_CALENDAR_DECLINED;
@@ -668,7 +674,7 @@ class kolab_driver extends calendar_driver
         $saved = false;
         foreach ($master['recurrence']['EXCEPTIONS'] as $i => $exception) {
           // merge the new event properties onto future exceptions
-          if ($exception['_instance'] >= $event['_instance']) {
+          if ($exception['_instance'] >= strval($event['_instance'])) {
             self::merge_attendee_data($master['recurrence']['EXCEPTIONS'][$i], $attendees);
           }
           // update a specific instance
@@ -741,7 +747,7 @@ class kolab_driver extends calendar_driver
   {
     $success = false;
     $savemode = $event['_savemode'];
-    $decline  = $event['decline'];
+    $decline  = $event['_decline'];
 
     if (($storage = $this->get_calendar($event['calendar'])) && ($event = $storage->get_event($event['id']))) {
       $event['_savemode'] = $savemode;
@@ -752,7 +758,7 @@ class kolab_driver extends calendar_driver
 
       // read master if deleting a recurring event
       if ($event['recurrence'] || $event['recurrence_id'] || $event['isexception']) {
-        $master = $event['recurrence_id'] || $event['isexception'] ? $storage->get_event($event['uid']) : $event;
+        $master = $storage->get_event($event['uid']);
         $savemode = $event['_savemode'] ?: ($event['_instance'] || $event['isexception'] ? 'current' : 'all');
 
         // force 'current' mode for single occurrences stored as exception
@@ -768,7 +774,6 @@ class kolab_driver extends calendar_driver
             // set event date back to the actual occurrence
             if ($exception['recurrence_date'])
               $event['start'] = $exception['recurrence_date'];
-            break;
           }
         }
 
@@ -782,7 +787,8 @@ class kolab_driver extends calendar_driver
           $_SESSION['calendar_restore_event_data'] = $master;
 
           // removing the first instance => just move to next occurence
-          if ($master['id'] == $event['id'] && $master['recurrence']) {
+          $recurrence_id_format = $master['allday'] ? 'Ymd' : 'Ymd\THis';
+          if ($master['recurrence'] && $event['_instance'] == $master['start']->format($recurrence_id_format)) {
             $recurring = reset($storage->get_recurring_events($event, $event['start'], null, $event['id'].'-1'));
 
             // no future instances found: delete the master event (bug #1677)
@@ -840,7 +846,7 @@ class kolab_driver extends calendar_driver
 
         default:  // 'all' is default
           // removing the master event with loose exceptions (not recurring though)
-          if (!empty($event['recurrence_date']) && !empty($master['exceptions'])) {
+          if (!empty($event['recurrence_date']) && empty($master['recurrence']) && !empty($master['exceptions'])) {
             // make the first exception the new master
             $newmaster = array_shift($master['exceptions']);
             $newmaster['exceptions'] = $master['exceptions'];
@@ -906,9 +912,12 @@ class kolab_driver extends calendar_driver
       if (!($fromcalendar = $this->get_calendar($event['_fromcalendar'])))
         return false;
 
+      $old = $fromcalendar->get_event($event['id']);
+
       if ($event['_savemode'] != 'new') {
-        if (!$fromcalendar->storage->move($event['id'], $storage->storage))
+        if (!$fromcalendar->storage->move($old['uid'], $storage->storage)) {
           return false;
+        }
 
         $fromcalendar = $storage;
       }
@@ -919,7 +928,7 @@ class kolab_driver extends calendar_driver
     $success = false;
     $savemode = 'all';
     $attachments = array();
-    $old = $master = $fromcalendar->get_event($event['id']);
+    $old = $master = $storage->get_event($event['id']);
 
     if (!$old || !$old['start']) {
       rcube::raise_error(array(
@@ -930,45 +939,14 @@ class kolab_driver extends calendar_driver
       return false;
     }
 
-    // delete existing attachment(s)
-    if (!empty($event['deleted_attachments'])) {
-      foreach ($event['deleted_attachments'] as $attachment) {
-        if (!empty($old['attachments'])) {
-          foreach ($old['attachments'] as $idx => $att) {
-            if ($att['id'] == $attachment) {
-              $old['attachments'][$idx]['_deleted'] = true;
-            }
-          }
-        }
-      }
-      unset($event['deleted_attachments']);
-    }
-
-    // handle attachments to add
-    if (!empty($event['attachments'])) {
-      foreach ($event['attachments'] as $attachment) {
-        // skip entries without content (could be existing ones)
-        if (!$attachment['data'] && !$attachment['path'])
-          continue;
-
-        $attachments[] = array(
-          'name' => $attachment['name'],
-          'mimetype' => $attachment['mimetype'],
-          'content' => $attachment['data'],
-          'path' => $attachment['path'],
-        );
-      }
-    }
-
-    $event['attachments'] = array_merge((array)$old['attachments'], $attachments);
-
     // modify a recurring event, check submitted savemode to do the right things
     if ($old['recurrence'] || $old['recurrence_id'] || $old['isexception']) {
-      $master = $old['recurrence_id'] || $old['isexception'] ? $fromcalendar->get_event($old['uid']) : $old;
+      $master = $storage->get_event($old['uid']);
       $savemode = $event['_savemode'] ?: ($old['recurrence_id'] || $old['isexception'] ? 'current' : 'all');
 
       // this-and-future on the first instance equals to 'all'
-      if (!$old['recurrence_id'] && $savemode == 'future')
+      $recurrence_id_format = $master['allday'] ? 'Ymd' : 'Ymd\THis';
+      if ($savemode == 'future' && $master['start'] && $old['_instance'] == $master['start']->format($recurrence_id_format))
         $savemode = 'all';
       // force 'current' mode for single occurrences stored as exception
       else if (!$old['recurrence'] && !$old['recurrence_id'] && $old['isexception'])
@@ -1021,7 +999,7 @@ class kolab_driver extends calendar_driver
   
         // remove recurrence exceptions on re-scheduling
         if ($reschedule) {
-          unset($event['recurrence']['EXCEPTIONS'], $master['recurrence']['EXDATE']);
+          unset($event['recurrence']['EXCEPTIONS'], $event['exceptions'], $master['recurrence']['EXDATE']);
         }
         else if (is_array($event['recurrence']['EXCEPTIONS'])) {
           // only keep relevant exceptions
@@ -1033,6 +1011,8 @@ class kolab_driver extends calendar_driver
               return $exdate > $event['start'];
             });
           }
+          // set link to top-level exceptions
+          $event['exceptions'] = &$event['recurrence']['EXCEPTIONS'];
         }
 
         // compute remaining occurrences
@@ -1060,6 +1040,8 @@ class kolab_driver extends calendar_driver
           $master['recurrence']['EXCEPTIONS'] = array_filter($master['recurrence']['EXCEPTIONS'], function($exception) use ($event) {
             return $exception['start'] < $event['start'];
           });
+          // set link to top-level exceptions
+          $master['exceptions'] = &$master['recurrence']['EXCEPTIONS'];
         }
         if (is_array($master['recurrence']['EXDATE'])) {
           $master['recurrence']['EXDATE'] = array_filter($master['recurrence']['EXDATE'], function($exdate) use ($event) {
@@ -1102,7 +1084,7 @@ class kolab_driver extends calendar_driver
         $add_exception = true;
 
         // adjust matching RDATE entry if dates changed
-        if ($savemode == 'current' && $master['recurrence']['RDATE'] && ($old_date = $old['start']->format('Ymd')) != $event['start']->format('Ymd')) {
+        if (is_array($master['recurrence']['RDATE']) && ($old_date = $old['start']->format('Ymd')) != $event['start']->format('Ymd')) {
           foreach ($master['recurrence']['RDATE'] as $j => $rdate) {
             if ($rdate->format('Ymd') == $old_date) {
               $master['recurrence']['RDATE'][$j] = $event['start'];
@@ -1122,7 +1104,7 @@ class kolab_driver extends calendar_driver
         break;
 
       default:  // 'all' is default
-        $event['id'] = $master['id'];
+        $event['id'] = $master['uid'];
         $event['uid'] = $master['uid'];
 
         // use start date from master but try to be smart on time or duration changes
@@ -1152,7 +1134,7 @@ class kolab_driver extends calendar_driver
           }
         }
         // dates did not change, use the ones from master
-        else if ($event['start'] == $old['start'] && $event['end'] == $old['end']) {
+        else if ($new_start_date . $new_start_time == $old_start_date . $old_start_time) {
           $event['start'] = $master['start'];
           $event['end'] = $master['end'];
         }
@@ -1198,12 +1180,15 @@ class kolab_driver extends calendar_driver
               }
             }
           }
+
+          // set link to top-level exceptions
+          $event['exceptions'] = &$event['recurrence']['EXCEPTIONS'];
         }
 
         // unset _dateonly flags in (cached) date objects
         unset($event['start']->_dateonly, $event['end']->_dateonly);
 
-        $success = $storage->update_event($event);
+        $success = $storage->update_event($event) ? $event['id'] : false;  // return master UID
         break;
     }
 
@@ -1500,7 +1485,7 @@ class kolab_driver extends calendar_driver
       $this->rc->user->save_prefs(array('calendar_categories' => $old_categories));
     }
 
-    array_walk($events, 'kolab_driver::clean_rcube_event_out');
+    array_walk($events, 'kolab_driver::to_rcube_event');
     return $events;
   }
 
@@ -1663,8 +1648,8 @@ class kolab_driver extends calendar_driver
 
     $event = $storage->get_event($event['id']);
 
-    if ($event && !empty($event['attachments'])) {
-      foreach ($event['attachments'] as $att) {
+    if ($event && !empty($event['_attachments'])) {
+      foreach ($event['_attachments'] as $att) {
         if ($att['id'] == $id) {
           return $att;
         }
@@ -1878,12 +1863,22 @@ class kolab_driver extends calendar_driver
 
 
   /**
-   * Convert from Kolab_Format to internal representation
+   * Convert from driver format to external caledar app data
    */
-  public static function to_rcube_event($record)
+  public static function to_rcube_event(&$record)
   {
+    if (!is_array($record))
+      return $record;
+
     $record['id'] = $record['uid'];
 
+    if ($record['_instance']) {
+      $record['id'] .= '-' . $record['_instance'];
+
+      if (!$record['recurrence_id'] && !empty($record['recurrence']))
+        $record['recurrence_id'] = $record['uid'];
+    }
+
     // all-day events go from 12:00 - 13:00
     if (is_a($record['start'], 'DateTime') && $record['end'] <= $record['start'] && $record['allday']) {
       $record['end'] = clone $record['start'];
@@ -1932,17 +1927,6 @@ class kolab_driver extends calendar_driver
     if (empty($record['recurrence']))
       unset($record['recurrence']);
 
-    // add instance identifier to first occurrence (master event)
-    // do not add 'recurrence_date' though in order to keep the master even being exported as such
-    $recurrence_id_format = $record['allday'] ? 'Ymd' : 'Ymd\THis';
-    if ($record['recurrence'] && !$record['recurrence_id'] && !$record['_instance']) {
-      $record['_instance'] = $record['start']->format($recurrence_id_format);
-    }
-    else if (is_a($record['recurrence_date'], 'DateTime')) {
-      $record['_instance'] = $record['recurrence_date']->format($recurrence_id_format);
-      $record['id'] = $record['uid'] . '-' . $record['_instance'];
-    }
-
     // clean up exception data
     if (is_array($record['recurrence']['EXCEPTIONS'])) {
       array_walk($record['recurrence']['EXCEPTIONS'], function(&$exception) {
@@ -1950,16 +1934,10 @@ class kolab_driver extends calendar_driver
       });
     }
 
-    return $record;
-  }
-
-  /**
-   * Remove some internal properties before sending to event out to the calendar app
-   */
-  public static function clean_rcube_event_out(&$record)
-  {
     unset($record['_mailbox'], $record['_msguid'], $record['_type'], $record['_size'],
       $record['_formatobj'], $record['_attachments'], $record['exceptions'], $record['x-custom']);
+
+    return $record;
   }
 
   /**
@@ -1968,7 +1946,7 @@ class kolab_driver extends calendar_driver
   public static function from_rcube_event($event, $old = array())
   {
     // in kolab_storage attachments are indexed by content-id
-    if (is_array($event['attachments'])) {
+    if (is_array($event['attachments']) || !empty($event['deleted_attachments'])) {
       $event['_attachments'] = array();
 
       foreach ($event['attachments'] as $attachment) {
@@ -1985,7 +1963,7 @@ class kolab_driver extends calendar_driver
         }
 
         // flagged for deletion => set to false
-        if ($attachment['_deleted']) {
+        if ($attachment['_deleted'] || in_array($attachment['id'], (array)$event['deleted_attachments'])) {
           $event['_attachments'][$key] = false;
         }
         // replace existing entry
@@ -1998,7 +1976,14 @@ class kolab_driver extends calendar_driver
         }
       }
 
-      unset($event['attachments']);
+      $event['_attachments'] = array_merge((array)$old['_attachments'], $event['_attachments']);
+
+      // attachments flagged for deletion => set to false
+      foreach ($event['_attachments'] as $key => $attachment) {
+        if ($attachment['_deleted'] || in_array($attachment['id'], (array)$event['deleted_attachments'])) {
+          $event['_attachments'][$key] = false;
+        }
+      }
     }
 
     return $event;




More information about the commits mailing list