diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index 5f4cc6bb..058b8011 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 a0d0c527..478a08c0 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(), 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 45db6383..cfe46f9e 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 886f2802..869837fa 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 24f98cb4..aa4ec5ef 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 d750f8e0..7efd06d7 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 a6450afb..bc50c4b8 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; } }