7 commits - plugins/calendar plugins/libcalendaring plugins/libkolab

Thomas Brüderli bruederli at kolabsys.com
Thu Feb 28 14:20:55 CET 2013


 plugins/calendar/calendar.php                     |    3 
 plugins/calendar/calendar_ui.js                   |    3 
 plugins/calendar/drivers/calendar_driver.php      |    1 
 plugins/calendar/drivers/kolab/kolab_calendar.php |   85 +++++++++++++++++++---
 plugins/calendar/drivers/kolab/kolab_driver.php   |   73 ++++++------------
 plugins/calendar/lib/calendar_ical.php            |   57 +++++++++-----
 plugins/libcalendaring/libcalendaring.php         |    4 +
 plugins/libkolab/lib/kolab_format_event.php       |   71 ++++++++++++++++++
 plugins/libkolab/lib/kolab_format_xcal.php        |    8 +-
 9 files changed, 224 insertions(+), 81 deletions(-)

New commits:
commit 01dcd96c7438aec72371beee0c683cf8d8368974
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu Feb 28 14:18:08 2013 +0100

    Really skip this property

diff --git a/plugins/libcalendaring/libcalendaring.php b/plugins/libcalendaring/libcalendaring.php
index 5e30b73..0760277 100644
--- a/plugins/libcalendaring/libcalendaring.php
+++ b/plugins/libcalendaring/libcalendaring.php
@@ -729,7 +729,7 @@ class libcalendaring extends rcube_plugin
                 $val = join(',', (array)$val);
                 break;
             case 'EXCEPTIONS':
-                continue;
+                continue 2;
             }
             $rrule .= $k . '=' . $val . ';';
         }


commit 354a18795f351670bf676eabdf2cfac7b5c45301
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu Feb 28 10:44:15 2013 +0100

    Properly export recurrence exceptions to iCal

diff --git a/plugins/calendar/lib/calendar_ical.php b/plugins/calendar/lib/calendar_ical.php
index 6d7474e..3dfe4c1 100644
--- a/plugins/calendar/lib/calendar_ical.php
+++ b/plugins/calendar/lib/calendar_ical.php
@@ -347,27 +347,32 @@ class calendar_ical
    * @param  boolean Directly send data to stdout instead of returning
    * @return string  Events in iCalendar format (http://tools.ietf.org/html/rfc5545)
    */
-  public function export($events, $method = null, $write = false)
+  public function export($events, $method = null, $write = false, $recurrence_id = null)
   {
-      $ical = "BEGIN:VCALENDAR" . self::EOL;
-      $ical .= "VERSION:2.0" . self::EOL;
-      $ical .= "PRODID:-//Roundcube Webmail " . RCMAIL_VERSION . "//NONSGML Calendar//EN" . self::EOL;
-      $ical .= "CALSCALE:GREGORIAN" . self::EOL;
-      
-      if ($method)
-        $ical .= "METHOD:" . strtoupper($method) . self::EOL;
-        
-      if ($write) {
-        echo $ical;
-        $ical = '';
+      if (!$recurrence_id) {
+        $ical = "BEGIN:VCALENDAR" . self::EOL;
+        $ical .= "VERSION:2.0" . self::EOL;
+        $ical .= "PRODID:-//Roundcube Webmail " . RCMAIL_VERSION . "//NONSGML Calendar//EN" . self::EOL;
+        $ical .= "CALSCALE:GREGORIAN" . self::EOL;
+
+        if ($method)
+          $ical .= "METHOD:" . strtoupper($method) . self::EOL;
+
+        if ($write) {
+          echo $ical;
+          $ical = '';
+        }
       }
-      
+
       foreach ($events as $event) {
         $vevent = "BEGIN:VEVENT" . self::EOL;
         $vevent .= "UID:" . self::escpape($event['uid']) . self::EOL;
         $vevent .= $this->format_datetime("DTSTAMP", $event['changed'] ?: new DateTime(), false, true) . self::EOL;
         if ($event['sequence'])
-            $vevent .= "SEQUENCE:" . intval($event['sequence']) . self::EOL;
+          $vevent .= "SEQUENCE:" . intval($event['sequence']) . self::EOL;
+        if ($recurrence_id)
+          $vevent .= $recurrence_id . self::EOL;
+        
         // correctly set all-day dates
         if ($event['allday']) {
           $event['end'] = clone $event['end'];
@@ -389,7 +394,7 @@ class calendar_ical
         if (!empty($event['location'])) {
           $vevent .= "LOCATION:" . self::escpape($event['location']) . self::EOL;
         }
-        if ($event['recurrence']) {
+        if ($event['recurrence'] && !$recurrence_id) {
           $vevent .= "RRULE:" . libcalendaring::to_rrule($event['recurrence'], self::EOL) . self::EOL;
         }
         if(!empty($event['categories'])) {
@@ -426,18 +431,30 @@ class calendar_ical
         // TODO: export attachments
         
         $vevent .= "END:VEVENT" . self::EOL;
-        
+
+        // append recurrence exceptions
+        if ($event['recurrence']['EXCEPTIONS'] && !$recurrence_id) {
+          foreach ($event['recurrence']['EXCEPTIONS'] as $ex) {
+            $exdate = clone $event['start'];
+            $exdate->setDate($ex['start']->format('Y'), $ex['start']->format('n'), $ex['start']->format('j'));
+            $vevent .= $this->export(array($ex), null, false,
+              $this->format_datetime('RECURRENCE-ID', $exdate, $event['allday']));
+          }
+        }
+
         if ($write)
           echo rcube_vcard::rfc2425_fold($vevent);
         else
           $ical .= $vevent;
       }
       
-      $ical .= "END:VCALENDAR" . self::EOL;
+      if (!$recurrence_id) {
+        $ical .= "END:VCALENDAR" . self::EOL;
       
-      if ($write) {
-        echo $ical;
-        return true;
+        if ($write) {
+          echo $ical;
+          return true;
+        }
       }
 
       // fold lines to 75 chars


commit e588b7fe09138526d68c9ce3690fe575be0f7431
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu Feb 28 08:26:54 2013 +0100

    Don't attempt to serialize recurrence EXCEPTIONS

diff --git a/plugins/libcalendaring/libcalendaring.php b/plugins/libcalendaring/libcalendaring.php
index 35255b9..5e30b73 100644
--- a/plugins/libcalendaring/libcalendaring.php
+++ b/plugins/libcalendaring/libcalendaring.php
@@ -728,6 +728,8 @@ class libcalendaring extends rcube_plugin
                     $val[$i] = $ex->format('Ymd\THis');
                 $val = join(',', (array)$val);
                 break;
+            case 'EXCEPTIONS':
+                continue;
             }
             $rrule .= $k . '=' . $val . ';';
         }


commit f320a772b0688db85ce261d090c8613df4592500
Merge: 465465d 36bbcc6
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu Feb 28 08:25:52 2013 +0100

    Merge branch 'dev/recurrence-exceptions'

diff --cc plugins/libkolab/lib/kolab_format_event.php
index cd357fc,4e2cccc..2b3f0c8
--- a/plugins/libkolab/lib/kolab_format_event.php
+++ b/plugins/libkolab/lib/kolab_format_event.php
@@@ -86,15 -101,20 +99,27 @@@ class kolab_format_event extends kolab_
              $attach->setUri('cid:' . $cid, $attr['mimetype']);
              $vattach->push($attach);
          }
 +
 +        foreach ((array)$object['links'] as $link) {
 +            $attach = new Attachment;
 +            $attach->setUri($link, null);
 +            $vattach->push($attach);
 +        }
 +
          $this->obj->setAttachments($vattach);
  
+         // save recurrence exceptions
+         if ($object['recurrence']['EXCEPTIONS']) {
+             $vexceptions = new vectorevent;
+             foreach((array)$object['recurrence']['EXCEPTIONS'] as $exception) {
+                 $exevent = new kolab_format_event;
+                 $exevent->set($this->compact_exception($exception, $object));  // only save differing values
+                 $exevent->obj->setRecurrenceID(self::get_datetime($exception['start'], null, true), (bool)$exception['thisandfuture']);
+                 $vexceptions->push($exevent->obj);
+             }
+             $this->obj->setExceptions($vexceptions);
+         }
+ 
          // cache this data
          $this->data = $object;
          unset($this->data['_formatobj']);
@@@ -161,11 -183,24 +186,27 @@@
                      'content'  => $data,
                  );
              }
 +            else if (substr($attach->uri(), 0, 4) == 'http') {
 +                $object['links'][] = $attach->uri();
 +            }
          }
  
+         // read exception event objects
+         if (($exceptions = $this->obj->exceptions()) && $exceptions->size()) {
+             for ($i=0; $i < $exceptions->size(); $i++) {
+                 if (($exobj = $exceptions->get($i))) {
+                     $exception = new kolab_format_event($exobj);
+                     if ($exception->is_valid()) {
+                         $object['recurrence']['EXCEPTIONS'][] = $this->expand_exception($exception->to_array(), $object);
+                     }
+                 }
+             }
+         }
+         // this is an exception object
+         else if ($this->obj->recurrenceID()->isValid()) {
+           $object['thisandfuture'] = $this->obj->thisAndFuture();
+         }
+ 
          // merge with additional data, e.g. attachments from the message
          if ($data) {
              foreach ($data as $idx => $value) {


commit 36bbcc6499f8ead8b5dee0d29060a2c0dc627566
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu Feb 14 16:17:02 2013 +0100

    Make this-and-future recurrence exceptions work

diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
index 24d7bd6..f4c7749 100644
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -507,8 +507,9 @@ function rcube_calendar_ui(settings)
       
       // show warning if editing a recurring event
       if (event.id && event.recurrence) {
+        var sel = event.thisandfuture ? 'future' : 'all';
         $('#edit-recurring-warning').show();
-        $('input.edit-recurring-savemode[value="all"]').prop('checked', true);
+        $('input.edit-recurring-savemode[value="'+sel+'"]').prop('checked', true);
       }
       else
         $('#edit-recurring-warning').hide();
diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php
index 53c47ad..764f619 100644
--- a/plugins/calendar/drivers/kolab/kolab_calendar.php
+++ b/plugins/calendar/drivers/kolab/kolab_calendar.php
@@ -422,23 +422,33 @@ class kolab_calendar
     $i = 0;
     $events = array();
     $exdates = array();
+    $futuredata = array();
     if (is_array($event['recurrence']['EXCEPTIONS'])) {
-        foreach ($event['recurrence']['EXCEPTIONS'] as $exception) {
-            $rec_event = $this->_to_rcube_event($exception);
-            $rec_event['id'] = $event['uid'] . '-' . ++$i;
-            $rec_event['recurrence_id'] = $event['uid'];
-            $rec_event['_instance'] = $i;
-            $events[] = $rec_event;
-
-            // found the specifically requested instance, exiting...
-            if ($rec_event['id'] == $event_id) {
-              $this->events[$rec_event['id']] = $rec_event;
-              return $events;
-            }
+      // copy the recurrence rule from the master event (to be used in the UI)
+      $recurrence_rule = $event['recurrence'];
+      unset($recurrence_rule['EXCEPTIONS'], $recurrence_rule['EXDATE']);
+
+      foreach ($event['recurrence']['EXCEPTIONS'] as $exception) {
+        $rec_event = $this->_to_rcube_event($exception);
+        $rec_event['id'] = $event['uid'] . '-' . ++$i;
+        $rec_event['recurrence_id'] = $event['uid'];
+        $rec_event['recurrence'] = $recurrence_rule;
+        $rec_event['_instance'] = $i;
+        $events[] = $rec_event;
 
-            // remember this exception's date
-            $exdates[$rec_event['start']->format('Y-m-d')] = $rec_event['id'];
+        // found the specifically requested instance, exiting...
+        if ($rec_event['id'] == $event_id) {
+          $this->events[$rec_event['id']] = $rec_event;
+          return $events;
         }
+
+        // remember this exception's date
+        $exdate = $rec_event['start']->format('Y-m-d');
+        $exdates[$exdate] = $rec_event['id'];
+        if ($rec_event['thisandfuture']) {
+          $futuredata[$exdate] = $rec_event;
+        }
+      }
     }
 
     // use libkolab to compute recurring events
@@ -446,20 +456,29 @@ class kolab_calendar
         $recurrence = new kolab_date_recurrence($object);
     }
     else {
-        // fallback to local recurrence implementation
-        require_once($this->cal->home . '/lib/calendar_recurrence.php');
-        $recurrence = new calendar_recurrence($this->cal, $event);
+      // fallback to local recurrence implementation
+      require_once($this->cal->home . '/lib/calendar_recurrence.php');
+      $recurrence = new calendar_recurrence($this->cal, $event);
     }
 
     while ($next_event = $recurrence->next_instance()) {
       // skip if there's an exception at this date
-      if ($exdates[$next_event['start']->format('Y-m-d')])
+      $datestr = $next_event['start']->format('Y-m-d');
+      if ($exdates[$datestr]) {
+        // use this event data for future recurring instances
+        if ($futuredata[$datestr])
+          $overlay_data = $futuredata[$datestr];
         continue;
+      }
 
       // add to output if in range
       $rec_id = $event['uid'] . '-' . ++$i;
       if (($next_event['start'] <= $end && $next_event['end'] >= $start) || ($event_id && $rec_id == $event_id)) {
         $rec_event = $this->_to_rcube_event($next_event);
+
+        if ($overlay_data)  // copy data from a 'this-and-future' exception
+          $this->_merge_event_data($rec_event, $overlay_data);
+
         $rec_event['id'] = $rec_id;
         $rec_event['recurrence_id'] = $event['uid'];
         $rec_event['_instance'] = $i;
@@ -483,6 +502,27 @@ class kolab_calendar
   }
 
   /**
+   * Merge certain properties from the overlay event to the base event object
+   *
+   * @param array The event object to be altered
+   * @param array The overlay event object to be merged over $event
+   */
+  private function _merge_event_data(&$event, $overlay)
+  {
+    static $forbidden = array('id','uid','created','changed','recurrence','organizer','attendees','sequence');
+
+    foreach ($overlay as $prop => $value) {
+      // adjust time of the recurring event instance
+      if ($prop == 'start' || $prop == 'end') {
+        if (is_object($event[$prop]) && is_a($event[$prop], 'DateTime'))
+          $event[$prop]->setTime($value->format('G'), intval($value->format('i')), intval($value->format('s')));
+      }
+      else if ($prop[0] != '_' && !in_array($prop, $forbidden))
+        $event[$prop] = $value;
+    }
+  }
+
+  /**
    * Convert from Kolab_Format to internal representation
    */
   private function _to_rcube_event($record)
diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php
index 869837f..bd4a855 100644
--- a/plugins/calendar/drivers/kolab/kolab_driver.php
+++ b/plugins/calendar/drivers/kolab/kolab_driver.php
@@ -421,6 +421,14 @@ class kolab_driver extends calendar_driver
         $savemode = $event['_savemode'];
       }
 
+      // removing an exception instance
+      if ($event['recurrence_id']) {
+        $i = $event['_instance'] - 1;
+        if (!empty($master['recurrence']['EXCEPTIONS'][$i])) {
+          unset($master['recurrence']['EXCEPTIONS'][$i]);
+        }
+      }
+
       switch ($savemode) {
         case 'current':
           $_SESSION['calendar_restore_event_data'] = $master;
@@ -582,43 +590,12 @@ class kolab_driver extends calendar_driver
         
         $success = $storage->insert_event($event);
         break;
-        
+
+      case 'future':
       case 'current':
-        // save as exception to master event
+        // recurring instances shall not store recurrence rules
         $event['recurrence'] = array();
-        $master['recurrence']['EXCEPTIONS'][] = $event;
-#       $master['recurrence']['EXDATE'][] = $event['start'];
-        $success = $storage->update_event($master);
-        break;
-        
-      case 'future':
-        if ($master['id'] != $event['id']) {
-          // set until-date on master event
-          $master['recurrence']['UNTIL'] = clone $old['start'];
-          $master['recurrence']['UNTIL']->sub(new DateInterval('P1D'));
-          unset($master['recurrence']['COUNT']);
-          $storage->update_event($master);
-          
-          // save this instance as new recurring event
-          $event += $old;
-          $event['uid'] = $this->cal->generate_uid();
-          
-          // if recurrence COUNT, update value to the correct number of future occurences
-          if ($event['recurrence']['COUNT']) {
-            $event['recurrence']['COUNT'] -= $old['_instance'];
-          }
-          
-          // remove fixed weekday, will be re-set to the new weekday in kolab_calendar::insert_event()
-          if (strlen($event['recurrence']['BYDAY']) == 2)
-            unset($event['recurrence']['BYDAY']);
-          if ($master['recurrence']['BYMONTH'] == $master['start']->format('n'))
-            unset($event['recurrence']['BYMONTH']);
-          
-          $success = $storage->insert_event($event);
-          break;
-        }
-
-      default:  // 'all' is default
+        $event['thisandfuture'] = $savemode == 'future';
 
         // save properties to a recurrence exception instance
         if ($old['recurrence_id']) {
@@ -630,6 +607,12 @@ class kolab_driver extends calendar_driver
             }
         }
 
+        // save as new exception to master event
+        $master['recurrence']['EXCEPTIONS'][] = $event;
+        $success = $storage->update_event($master);
+        break;
+
+      default:  // 'all' is default
         $event['id'] = $master['id'];
         $event['uid'] = $master['uid'];
 
diff --git a/plugins/libkolab/lib/kolab_format_event.php b/plugins/libkolab/lib/kolab_format_event.php
index 7efd06d..4e2cccc 100644
--- a/plugins/libkolab/lib/kolab_format_event.php
+++ b/plugins/libkolab/lib/kolab_format_event.php
@@ -109,7 +109,7 @@ class kolab_format_event extends kolab_format_xcal
             foreach((array)$object['recurrence']['EXCEPTIONS'] as $exception) {
                 $exevent = new kolab_format_event;
                 $exevent->set($this->compact_exception($exception, $object));  // only save differing values
-                $exevent->obj->setRecurrenceID(self::get_datetime($exception['start'], null, true), false);
+                $exevent->obj->setRecurrenceID(self::get_datetime($exception['start'], null, true), (bool)$exception['thisandfuture']);
                 $vexceptions->push($exevent->obj);
             }
             $this->obj->setExceptions($vexceptions);
@@ -186,7 +186,7 @@ class kolab_format_event extends kolab_format_xcal
         }
 
         // read exception event objects
-        if ($exceptions = $this->obj->exceptions()) {
+        if (($exceptions = $this->obj->exceptions()) && $exceptions->size()) {
             for ($i=0; $i < $exceptions->size(); $i++) {
                 if (($exobj = $exceptions->get($i))) {
                     $exception = new kolab_format_event($exobj);
@@ -196,6 +196,10 @@ class kolab_format_event extends kolab_format_xcal
                 }
             }
         }
+        // this is an exception object
+        else if ($this->obj->recurrenceID()->isValid()) {
+          $object['thisandfuture'] = $this->obj->thisAndFuture();
+        }
 
         // merge with additional data, e.g. attachments from the message
         if ($data) {
@@ -233,24 +237,15 @@ class kolab_format_event extends kolab_format_xcal
     }
 
     /**
-     * Reduce the exception container to attributes which differ from the master event
+     * Remove some attributes from the exception container
      */
     private function compact_exception($exception, $master)
     {
-      static $mandatory = array('uid','created','start');
-      static $forbidden = array('recurrence','attendees','sequence');
+      static $forbidden = array('recurrence','organizer','attendees','sequence');
 
       $out = $exception;
       foreach ($exception as $prop => $val) {
-        if (in_array($prop, $mandatory))
-          continue;
-
-        if (is_object($exception[$prop]) && is_a($exception[$prop], 'DateTime'))
-          $equals = $exception[$prop] <> $master[$prop];
-        else
-          $equals = $exception[$prop] == $master[$prop];
-
-        if ($equals || in_array($prop, $forbidden)) {
+        if (in_array($prop, $forbidden)) {
           unset($out[$prop]);
         }
       }
@@ -263,10 +258,8 @@ class kolab_format_event extends kolab_format_xcal
      */
     private function expand_exception($exception, $master)
     {
-      static $forbidden = array('recurrence');
-
       foreach ($master as $prop => $value) {
-        if (empty($exception[$prop]) && !in_array($prop, $forbidden))
+        if (empty($exception[$prop]) && !empty($value))
           $exception[$prop] = $value;
       }
 


commit 1b4b6bb9659c2525f53b29d0186b8c817588cdb8
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Wed Jan 23 17:17:05 2013 +0100

    Skip regular recurrences on exception dates

diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php
index cfe46f9..53c47ad 100644
--- a/plugins/calendar/drivers/kolab/kolab_calendar.php
+++ b/plugins/calendar/drivers/kolab/kolab_calendar.php
@@ -421,6 +421,7 @@ class kolab_calendar
     // add recurrence exceptions to output
     $i = 0;
     $events = array();
+    $exdates = array();
     if (is_array($event['recurrence']['EXCEPTIONS'])) {
         foreach ($event['recurrence']['EXCEPTIONS'] as $exception) {
             $rec_event = $this->_to_rcube_event($exception);
@@ -429,10 +430,14 @@ class kolab_calendar
             $rec_event['_instance'] = $i;
             $events[] = $rec_event;
 
+            // found the specifically requested instance, exiting...
             if ($rec_event['id'] == $event_id) {
               $this->events[$rec_event['id']] = $rec_event;
               return $events;
             }
+
+            // remember this exception's date
+            $exdates[$rec_event['start']->format('Y-m-d')] = $rec_event['id'];
         }
     }
 
@@ -447,11 +452,12 @@ class kolab_calendar
     }
 
     while ($next_event = $recurrence->next_instance()) {
-      $rec_start = $next_event['start']->format('U');
-      $rec_end = $next_event['end']->format('U');
-      $rec_id = $event['uid'] . '-' . ++$i;
+      // skip if there's an exception at this date
+      if ($exdates[$next_event['start']->format('Y-m-d')])
+        continue;
 
       // add to output if in range
+      $rec_id = $event['uid'] . '-' . ++$i;
       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['id'] = $rec_id;


commit 91779df09a5a19d7a4a401d8f5c556b6cf6fa8d6
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Wed Jan 23 14:45:41 2013 +0100

    Save changes in a recurring event as exception to the master event

diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php
index 5f4cc6b..058b801 100644
--- a/plugins/calendar/calendar.php
+++ b/plugins/calendar/calendar.php
@@ -1096,6 +1096,7 @@ class calendar extends rcube_plugin
       $event['recurrence_text'] = $this->_recurrence_text($event['recurrence']);
       if ($event['recurrence']['UNTIL'])
         $event['recurrence']['UNTIL'] = $this->lib->adjust_timezone($event['recurrence']['UNTIL'])->format('c');
+      unset($event['recurrence']['EXCEPTIONS']);
     }
 
     foreach ((array)$event['attachments'] as $k => $attachment) {
@@ -1109,7 +1110,7 @@ class calendar extends rcube_plugin
       'title'       => strval($event['title']),
       'description' => strval($event['description']),
       'location'    => strval($event['location']),
-      'className'   => ($addcss ? 'fc-event-cal-'.asciiwords($event['calendar'], true).' ' : '') . 'fc-event-cat-' . asciiwords(strtolower($event['categories']), true),
+      'className'   => ($addcss ? 'fc-event-cal-'.asciiwords($event['calendar'], true).' ' : '') . 'fc-event-cat-' . asciiwords(strtolower(join('-', (array)$event['categories'])), true),
       'allDay'      => ($event['allday'] == 1),
     ) + $event;
   }
diff --git a/plugins/calendar/drivers/calendar_driver.php b/plugins/calendar/drivers/calendar_driver.php
index a0d0c52..478a08c 100644
--- a/plugins/calendar/drivers/calendar_driver.php
+++ b/plugins/calendar/drivers/calendar_driver.php
@@ -46,6 +46,7 @@
  *           'COUNT' => 1..n,   // number of times
  *                      // + more properties (see http://www.kanzaki.com/docs/ical/recur.html)
  *          'EXDATE' => array(),  // list of DateTime objects of exception Dates/Times
+ *      'EXCEPTIONS' => array(<event>),  list of event objects which denote exceptions in the recurrence chain
  *    ),
  * 'recurrence_id' => 'ID of the recurrence group',   // usually the ID of the starting event
  *    'categories' => 'Event category',
diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php
index 45db638..cfe46f9 100644
--- a/plugins/calendar/drivers/kolab/kolab_calendar.php
+++ b/plugins/calendar/drivers/kolab/kolab_calendar.php
@@ -312,7 +312,7 @@ class kolab_calendar
    * @return boolean True on success, False on error
    */
 
-  public function update_event($event)
+  public function update_event($event, $exception_id = null)
   {
     $updated = false;
     $old = $this->storage->get_object($event['id']);
@@ -333,6 +333,11 @@ class kolab_calendar
     else {
       $updated = true;
       $this->events[$event['id']] = $this->_to_rcube_event($object);
+
+      // refresh local cache with recurring instances
+      if ($exception_id) {
+        $this->_get_recurring_events($object, $event['start'], $event['end'], $exception_id);
+      }
     }
 
     return $updated;
@@ -413,6 +418,24 @@ class kolab_calendar
       $end->add(new DateInterval($intvl));
     }
 
+    // add recurrence exceptions to output
+    $i = 0;
+    $events = array();
+    if (is_array($event['recurrence']['EXCEPTIONS'])) {
+        foreach ($event['recurrence']['EXCEPTIONS'] as $exception) {
+            $rec_event = $this->_to_rcube_event($exception);
+            $rec_event['id'] = $event['uid'] . '-' . ++$i;
+            $rec_event['recurrence_id'] = $event['uid'];
+            $rec_event['_instance'] = $i;
+            $events[] = $rec_event;
+
+            if ($rec_event['id'] == $event_id) {
+              $this->events[$rec_event['id']] = $rec_event;
+              return $events;
+            }
+        }
+    }
+
     // use libkolab to compute recurring events
     if (class_exists('kolabcalendaring')) {
         $recurrence = new kolab_date_recurrence($object);
@@ -423,8 +446,6 @@ class kolab_calendar
         $recurrence = new calendar_recurrence($this->cal, $event);
     }
 
-    $i = 0;
-    $events = array();
     while ($next_event = $recurrence->next_instance()) {
       $rec_start = $next_event['start']->format('U');
       $rec_end = $next_event['end']->format('U');
diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php
index 886f280..869837f 100644
--- a/plugins/calendar/drivers/kolab/kolab_driver.php
+++ b/plugins/calendar/drivers/kolab/kolab_driver.php
@@ -565,6 +565,8 @@ class kolab_driver extends calendar_driver
     // keep saved exceptions (not submitted by the client)
     if ($old['recurrence']['EXDATE'])
       $event['recurrence']['EXDATE'] = $old['recurrence']['EXDATE'];
+    if ($old['recurrence']['EXCEPTIONS'])
+      $event['recurrence']['EXCEPTIONS'] = $old['recurrence']['EXCEPTIONS'];
 
     switch ($savemode) {
       case 'new':
@@ -582,26 +584,11 @@ class kolab_driver extends calendar_driver
         break;
         
       case 'current':
-        // modifying the first instance => just move to next occurence
-        if ($master['id'] == $event['id']) {
-          $recurring = reset($storage->_get_recurring_events($event, $event['start'], null, $event['id'].'-1'));
-          $master['start'] = $recurring['start'];
-          $master['end'] = $recurring['end'];
-          if ($master['recurrence']['COUNT'])
-            $master['recurrence']['COUNT']--;
-        }
-        else {  // add exception to master event
-          $master['recurrence']['EXDATE'][] = $old['start'];
-		}
-
-        $storage->update_event($master);
-        
-        // insert new event for this occurence
-        $event += $old;
+        // save as exception to master event
         $event['recurrence'] = array();
-        unset($event['recurrence_id']);
-        $event['uid'] = $this->cal->generate_uid();
-        $success = $storage->insert_event($event);
+        $master['recurrence']['EXCEPTIONS'][] = $event;
+#       $master['recurrence']['EXDATE'][] = $event['start'];
+        $success = $storage->update_event($master);
         break;
         
       case 'future':
@@ -632,6 +619,17 @@ class kolab_driver extends calendar_driver
         }
 
       default:  // 'all' is default
+
+        // save properties to a recurrence exception instance
+        if ($old['recurrence_id']) {
+            $i = $old['_instance'] - 1;
+            if (!empty($master['recurrence']['EXCEPTIONS'][$i])) {
+                $master['recurrence']['EXCEPTIONS'][$i] = $event;
+                $success = $storage->update_event($master, $old['id']);
+                break;
+            }
+        }
+
         $event['id'] = $master['id'];
         $event['uid'] = $master['uid'];
 
diff --git a/plugins/libcalendaring/libcalendaring.php b/plugins/libcalendaring/libcalendaring.php
index 24f98cb..aa4ec5e 100644
--- a/plugins/libcalendaring/libcalendaring.php
+++ b/plugins/libcalendaring/libcalendaring.php
@@ -107,6 +107,8 @@ class libcalendaring extends rcube_plugin
             $dt = new DateTime('@'.$td);
         else if (is_string($dt))
             $dt = new DateTime($dt);
+        else
+            return $dt;
 
         $dt->setTimezone($this->timezone);
         return $dt;
diff --git a/plugins/libkolab/lib/kolab_format_event.php b/plugins/libkolab/lib/kolab_format_event.php
index d750f8e..7efd06d 100644
--- a/plugins/libkolab/lib/kolab_format_event.php
+++ b/plugins/libkolab/lib/kolab_format_event.php
@@ -30,6 +30,19 @@ class kolab_format_event extends kolab_format_xcal
     protected $read_func = 'readEvent';
     protected $write_func = 'writeEvent';
 
+    /**
+     * Default constructor
+     */
+    function __construct($data = null, $version = 3.0)
+    {
+        parent::__construct(is_string($data) ? $data : null, $version);
+
+        // got an Event object as argument
+        if (is_object($data) && is_a($data, $this->objclass)) {
+            $this->obj = $data;
+            $this->loaded = true;
+        }
+    }
 
     /**
      * Clones into an instance of libcalendaring's extended EventCal class
@@ -90,6 +103,18 @@ class kolab_format_event extends kolab_format_xcal
         }
         $this->obj->setAttachments($vattach);
 
+        // save recurrence exceptions
+        if ($object['recurrence']['EXCEPTIONS']) {
+            $vexceptions = new vectorevent;
+            foreach((array)$object['recurrence']['EXCEPTIONS'] as $exception) {
+                $exevent = new kolab_format_event;
+                $exevent->set($this->compact_exception($exception, $object));  // only save differing values
+                $exevent->obj->setRecurrenceID(self::get_datetime($exception['start'], null, true), false);
+                $vexceptions->push($exevent->obj);
+            }
+            $this->obj->setExceptions($vexceptions);
+        }
+
         // cache this data
         $this->data = $object;
         unset($this->data['_formatobj']);
@@ -160,6 +185,18 @@ class kolab_format_event extends kolab_format_xcal
             }
         }
 
+        // read exception event objects
+        if ($exceptions = $this->obj->exceptions()) {
+            for ($i=0; $i < $exceptions->size(); $i++) {
+                if (($exobj = $exceptions->get($i))) {
+                    $exception = new kolab_format_event($exobj);
+                    if ($exception->is_valid()) {
+                        $object['recurrence']['EXCEPTIONS'][] = $this->expand_exception($exception->to_array(), $object);
+                    }
+                }
+            }
+        }
+
         // merge with additional data, e.g. attachments from the message
         if ($data) {
             foreach ($data as $idx => $value) {
@@ -195,4 +232,45 @@ class kolab_format_event extends kolab_format_xcal
         return $tags;
     }
 
+    /**
+     * Reduce the exception container to attributes which differ from the master event
+     */
+    private function compact_exception($exception, $master)
+    {
+      static $mandatory = array('uid','created','start');
+      static $forbidden = array('recurrence','attendees','sequence');
+
+      $out = $exception;
+      foreach ($exception as $prop => $val) {
+        if (in_array($prop, $mandatory))
+          continue;
+
+        if (is_object($exception[$prop]) && is_a($exception[$prop], 'DateTime'))
+          $equals = $exception[$prop] <> $master[$prop];
+        else
+          $equals = $exception[$prop] == $master[$prop];
+
+        if ($equals || in_array($prop, $forbidden)) {
+          unset($out[$prop]);
+        }
+      }
+
+      return $out;
+    }
+
+    /**
+     * Copy attributes not specified by the exception from the master event
+     */
+    private function expand_exception($exception, $master)
+    {
+      static $forbidden = array('recurrence');
+
+      foreach ($master as $prop => $value) {
+        if (empty($exception[$prop]) && !in_array($prop, $forbidden))
+          $exception[$prop] = $value;
+      }
+
+      return $exception;
+    }
+
 }
diff --git a/plugins/libkolab/lib/kolab_format_xcal.php b/plugins/libkolab/lib/kolab_format_xcal.php
index a6450af..bc50c4b 100644
--- a/plugins/libkolab/lib/kolab_format_xcal.php
+++ b/plugins/libkolab/lib/kolab_format_xcal.php
@@ -113,7 +113,7 @@ abstract class kolab_format_xcal extends kolab_format
         );
 
         // read organizer and attendees
-        if ($organizer = $this->obj->organizer()) {
+        if (($organizer = $this->obj->organizer()) && ($organizer->email() || $organizer->name())) {
             $object['organizer'] = array(
                 'email' => $organizer->email(),
                 'name' => $organizer->name(),
@@ -170,9 +170,9 @@ abstract class kolab_format_xcal extends kolab_format
                 $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)))
+            if ($exdates = $this->obj->exceptionDates()) {
+                for ($i=0; $i < $exdates->size(); $i++) {
+                    if ($exdate = self::php_datetime($exdates->get($i)))
                         $object['recurrence']['EXDATE'][] = $exdate;
                 }
             }





More information about the commits mailing list