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
This commit is contained in:
Thomas Bruederli 2015-02-27 17:52:17 +01:00
parent 414a571af0
commit 94260b2aeb
3 changed files with 127 additions and 112 deletions

View file

@ -1175,9 +1175,13 @@ class calendar extends rcube_plugin
$event['attendees'] = $master['attendees']; // this tricks us into the next if clause $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['id'] = $success;
$event['_savemode'] = 'all'; $event['_savemode'] = 'all';
$old = null;
} }
// send out notifications // send out notifications
@ -1566,6 +1570,9 @@ class calendar extends rcube_plugin
$filename = asciiwords(html_entity_decode($filename)); // to 7bit ascii $filename = asciiwords(html_entity_decode($filename)); // to 7bit ascii
if (!empty($event_id)) { if (!empty($event_id)) {
if ($event = $this->driver->get_event(array('calendar' => $calid, 'id' => $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); $events = array($event);
$filename = asciiwords($event['title']); $filename = asciiwords($event['title']);
if (empty($filename)) if (empty($filename))
@ -2276,9 +2283,9 @@ class calendar extends rcube_plugin
public static function event_diff($a, $b) public static function event_diff($a, $b)
{ {
$diff = array(); $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) { 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[] = $key;
} }

View file

@ -187,7 +187,7 @@ class kolab_calendar extends kolab_storage_folder_api
{ {
// directly access storage object // directly access storage object
if (!$this->events[$id] && ($record = $this->storage->get_object($id))) 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 // event not found, maybe a recurring instance is requested
if (!$this->events[$id]) { if (!$this->events[$id]) {
@ -195,17 +195,16 @@ class kolab_calendar extends kolab_storage_folder_api
$instance_id = substr($id, strlen($master_id) + 1); $instance_id = substr($id, strlen($master_id) + 1);
if ($master_id != $id && ($record = $this->storage->get_object($master_id))) { 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 // check for match on the first instance already
if ($master['_instance'] && $master['_instance'] == $instance_id) { 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) // check for match in top-level exceptions (aka loose single occurrences)
else if ($master && $master['_formatobj'] && ($instance = $master['_formatobj']->get_instance($instance_id))) { else if ($master && $master['_formatobj'] && ($instance = $master['_formatobj']->get_instance($instance_id))) {
$instance = $this->_to_rcube_event($instance); $this->events[$id] = $this->_to_driver_event($instance);
$this->events[$instance['id']] = $instance;
} }
else if ($master && is_array($master['recurrence'])) { else if ($master && is_array($master['recurrence'])) {
$this->get_recurring_events($record, $master['start'], null, $id); $this->get_recurring_events($record, $master['start'], null, $id);
@ -294,12 +293,13 @@ class kolab_calendar extends kolab_storage_folder_api
$events = array(); $events = array();
foreach ($this->storage->select($query) as $record) { foreach ($this->storage->select($query) as $record) {
$event = $this->_to_rcube_event($record); $event = $this->_to_driver_event($record, !$virtual);
$this->events[$event['id']] = $event;
// remember seen categories // remember seen categories
if ($event['categories']) if ($event['categories']) {
$this->categories[$event['categories']]++; $cat = is_array($event['categories']) ? $event['categories'][0] : $event['categories'];
$this->categories[$cat]++;
}
// list events in requested time window // list events in requested time window
if ($event['start'] <= $end && $event['end'] >= $start) { 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 // find and merge exception for the first instance
if ($virtual && !empty($event['recurrence']) && is_array($event['recurrence']['EXCEPTIONS'])) { if ($virtual && !empty($event['recurrence']) && is_array($event['recurrence']['EXCEPTIONS'])) {
$event_date = $event['start']->format('Ymd');
foreach ($event['recurrence']['EXCEPTIONS'] as $exception) { foreach ($event['recurrence']['EXCEPTIONS'] as $exception) {
$exdate = $exception['recurrence_date'] ? $exception['recurrence_date']->format('Ymd') : substr($exception['_instance'], 0, 8); if ($event['_instance'] == $exception['_instance']) {
if ($exdate == $event_date) { // clone date objects from main event before adjusting them with exception data
$event['_instance'] = $exception['_instance']; 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); 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) // add top-level exceptions (aka loose single occurrences)
else if (is_array($record['exceptions'])) { else if (is_array($record['exceptions'])) {
foreach ($record['exceptions'] as $ex) { foreach ($record['exceptions'] as $ex) {
$component = $this->_to_rcube_event($ex); $component = $this->_to_driver_event($ex);
if ($component['start'] <= $end && $component['end'] >= $start) { if ($component['start'] <= $end && $component['end'] >= $start) {
$events[] = $component; $events[] = $component;
} }
@ -445,7 +445,7 @@ class kolab_calendar extends kolab_storage_folder_api
unset($event['links']); unset($event['links']);
//generate new event from RC input //generate new event from RC input
$object = $this->_from_rcube_event($event); $object = $this->_from_driver_event($event);
$saved = $this->storage->save($object, 'event'); $saved = $this->storage->save($object, 'event');
if (!$saved) { if (!$saved) {
@ -460,8 +460,7 @@ class kolab_calendar extends kolab_storage_folder_api
// save links in configuration.relation object // save links in configuration.relation object
$this->save_links($event['uid'], $links); $this->save_links($event['uid'], $links);
$event['id'] = $event['uid']; $this->events = array($event['uid'] => $this->_to_driver_event($object, true));
$this->events = array($event['uid'] => $this->_to_rcube_event($object));
} }
return $saved; return $saved;
@ -485,7 +484,7 @@ class kolab_calendar extends kolab_storage_folder_api
$links = $event['links']; $links = $event['links'];
unset($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']); $saved = $this->storage->save($object, 'event', $old['uid']);
if (!$saved) { if (!$saved) {
@ -500,7 +499,7 @@ class kolab_calendar extends kolab_storage_folder_api
$this->save_links($event['uid'], $links); $this->save_links($event['uid'], $links);
$updated = true; $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 // refresh local cache with recurring instances
if ($exception_id) { if ($exception_id) {
@ -626,7 +625,7 @@ class kolab_calendar extends kolab_storage_folder_api
else if (!$exception['_instance'] && is_a($exception['start'], 'DateTime')) else if (!$exception['_instance'] && is_a($exception['start'], 'DateTime'))
$exception['_instance'] = $exception['start']->format($recurrence_id_format); $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['id'] = $event['uid'] . '-' . $exception['_instance'];
$rec_event['isexception'] = 1; $rec_event['isexception'] = 1;
@ -675,7 +674,7 @@ class kolab_calendar extends kolab_storage_folder_api
// add to output if in range // add to output if in range
$rec_id = $event['uid'] . '-' . $instance_id; $rec_id = $event['uid'] . '-' . $instance_id;
if (($next_event['start'] <= $end && $next_event['end'] >= $start) || ($event_id && $rec_id == $event_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['_instance'] = $instance_id;
$rec_event['_count'] = $i + 1; $rec_event['_count'] = $i + 1;
@ -707,7 +706,7 @@ class kolab_calendar extends kolab_storage_folder_api
/** /**
* Convert from Kolab_Format to internal representation * 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['calendar'] = $this->id;
$record['links'] = $this->get_links($record['uid']); $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()); $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 * 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 // set current user as ORGANIZER
$identity = $this->cal->rc->user->list_emails(true); $identity = $this->cal->rc->user->list_emails(true);
if (empty($event['attendees']) && $identity['email']) if (empty($event['attendees']) && $identity['email'])
@ -750,8 +763,18 @@ class kolab_calendar extends kolab_storage_folder_api
$event['comment'] = $old['comment']; $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 // 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 // copy meta data (starting with _) from old object
foreach ((array)$old as $key => $val) { foreach ((array)$old as $key => $val) {

View file

@ -549,9 +549,7 @@ class kolab_driver extends calendar_driver
if ($cal) { if ($cal) {
if ($storage = $this->get_calendar($cal)) { if ($storage = $this->get_calendar($cal)) {
$result = $storage->get_event($id); $result = $storage->get_event($id);
if (is_array($result)) return self::to_rcube_event($result);
self::clean_rcube_event_out($result);
return $result;
} }
// get event from the address books birthday calendar // get event from the address books birthday calendar
else if ($cal == self::BIRTHDAY_CALENDAR_ID) { else if ($cal == self::BIRTHDAY_CALENDAR_ID) {
@ -562,8 +560,7 @@ class kolab_driver extends calendar_driver
else { else {
foreach ($this->filter_calendars($writeable, $active, $personal) as $calendar) { foreach ($this->filter_calendars($writeable, $active, $personal) as $calendar) {
if ($result = $calendar->get_event($id)) { if ($result = $calendar->get_event($id)) {
self::clean_rcube_event_out($result); return self::to_rcube_event($result);
return $result;
} }
} }
} }
@ -581,10 +578,12 @@ class kolab_driver extends calendar_driver
if (!$this->validate($event)) if (!$this->validate($event))
return false; return false;
$event = self::from_rcube_event($event);
$cid = $event['calendar'] ? $event['calendar'] : reset(array_keys($this->calendars)); $cid = $event['calendar'] ? $event['calendar'] : reset(array_keys($this->calendars));
if ($storage = $this->get_calendar($cid)) { if ($storage = $this->get_calendar($cid)) {
// if this is a recurrence instance, append as exception to an already existing object for this UID // 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); self::add_exception($master, $event);
$success = $storage->update_event($master); $success = $storage->update_event($master);
} }
@ -611,7 +610,10 @@ class kolab_driver extends calendar_driver
*/ */
public function edit_event($event) 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'])) { if ($storage = $this->get_calendar($event['calendar'])) {
$update_event = $storage->get_event($event['recurrence_id']); $update_event = $storage->get_event($event['recurrence_id']);
$update_event['_savemode'] = $event['_savemode']; $update_event['_savemode'] = $event['_savemode'];
$update_event['id'] = $update_event['uid'];
unset($update_event['recurrence_id']); unset($update_event['recurrence_id']);
self::merge_attendee_data($update_event, $attendees); self::merge_attendee_data($update_event, $attendees);
} }
} }
if (($ret = $this->update_attendees($update_event, $attendees)) && $this->rc->config->get('kolab_invitation_calendars')) { 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 // re-assign to the according (virtual) calendar
if (strtoupper($status) == 'DECLINED') if (strtoupper($status) == 'DECLINED')
$event['calendar'] = self::INVITATIONS_CALENDAR_DECLINED; $event['calendar'] = self::INVITATIONS_CALENDAR_DECLINED;
@ -668,7 +674,7 @@ class kolab_driver extends calendar_driver
$saved = false; $saved = false;
foreach ($master['recurrence']['EXCEPTIONS'] as $i => $exception) { foreach ($master['recurrence']['EXCEPTIONS'] as $i => $exception) {
// merge the new event properties onto future exceptions // 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); self::merge_attendee_data($master['recurrence']['EXCEPTIONS'][$i], $attendees);
} }
// update a specific instance // update a specific instance
@ -741,7 +747,7 @@ class kolab_driver extends calendar_driver
{ {
$success = false; $success = false;
$savemode = $event['_savemode']; $savemode = $event['_savemode'];
$decline = $event['decline']; $decline = $event['_decline'];
if (($storage = $this->get_calendar($event['calendar'])) && ($event = $storage->get_event($event['id']))) { if (($storage = $this->get_calendar($event['calendar'])) && ($event = $storage->get_event($event['id']))) {
$event['_savemode'] = $savemode; $event['_savemode'] = $savemode;
@ -752,7 +758,7 @@ class kolab_driver extends calendar_driver
// read master if deleting a recurring event // read master if deleting a recurring event
if ($event['recurrence'] || $event['recurrence_id'] || $event['isexception']) { 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'); $savemode = $event['_savemode'] ?: ($event['_instance'] || $event['isexception'] ? 'current' : 'all');
// force 'current' mode for single occurrences stored as exception // 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 // set event date back to the actual occurrence
if ($exception['recurrence_date']) if ($exception['recurrence_date'])
$event['start'] = $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; $_SESSION['calendar_restore_event_data'] = $master;
// removing the first instance => just move to next occurence // 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')); $recurring = reset($storage->get_recurring_events($event, $event['start'], null, $event['id'].'-1'));
// no future instances found: delete the master event (bug #1677) // no future instances found: delete the master event (bug #1677)
@ -840,7 +846,7 @@ class kolab_driver extends calendar_driver
default: // 'all' is default default: // 'all' is default
// removing the master event with loose exceptions (not recurring though) // 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 // make the first exception the new master
$newmaster = array_shift($master['exceptions']); $newmaster = array_shift($master['exceptions']);
$newmaster['exceptions'] = $master['exceptions']; $newmaster['exceptions'] = $master['exceptions'];
@ -906,9 +912,12 @@ class kolab_driver extends calendar_driver
if (!($fromcalendar = $this->get_calendar($event['_fromcalendar']))) if (!($fromcalendar = $this->get_calendar($event['_fromcalendar'])))
return false; return false;
$old = $fromcalendar->get_event($event['id']);
if ($event['_savemode'] != 'new') { if ($event['_savemode'] != 'new') {
if (!$fromcalendar->storage->move($event['id'], $storage->storage)) if (!$fromcalendar->storage->move($old['uid'], $storage->storage)) {
return false; return false;
}
$fromcalendar = $storage; $fromcalendar = $storage;
} }
@ -919,7 +928,7 @@ class kolab_driver extends calendar_driver
$success = false; $success = false;
$savemode = 'all'; $savemode = 'all';
$attachments = array(); $attachments = array();
$old = $master = $fromcalendar->get_event($event['id']); $old = $master = $storage->get_event($event['id']);
if (!$old || !$old['start']) { if (!$old || !$old['start']) {
rcube::raise_error(array( rcube::raise_error(array(
@ -930,45 +939,14 @@ class kolab_driver extends calendar_driver
return false; 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 // modify a recurring event, check submitted savemode to do the right things
if ($old['recurrence'] || $old['recurrence_id'] || $old['isexception']) { 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'); $savemode = $event['_savemode'] ?: ($old['recurrence_id'] || $old['isexception'] ? 'current' : 'all');
// this-and-future on the first instance equals to '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'; $savemode = 'all';
// force 'current' mode for single occurrences stored as exception // force 'current' mode for single occurrences stored as exception
else if (!$old['recurrence'] && !$old['recurrence_id'] && $old['isexception']) 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 // remove recurrence exceptions on re-scheduling
if ($reschedule) { 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'])) { else if (is_array($event['recurrence']['EXCEPTIONS'])) {
// only keep relevant exceptions // only keep relevant exceptions
@ -1033,6 +1011,8 @@ class kolab_driver extends calendar_driver
return $exdate > $event['start']; return $exdate > $event['start'];
}); });
} }
// set link to top-level exceptions
$event['exceptions'] = &$event['recurrence']['EXCEPTIONS'];
} }
// compute remaining occurrences // 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) { $master['recurrence']['EXCEPTIONS'] = array_filter($master['recurrence']['EXCEPTIONS'], function($exception) use ($event) {
return $exception['start'] < $event['start']; return $exception['start'] < $event['start'];
}); });
// set link to top-level exceptions
$master['exceptions'] = &$master['recurrence']['EXCEPTIONS'];
} }
if (is_array($master['recurrence']['EXDATE'])) { if (is_array($master['recurrence']['EXDATE'])) {
$master['recurrence']['EXDATE'] = array_filter($master['recurrence']['EXDATE'], function($exdate) use ($event) { $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; $add_exception = true;
// adjust matching RDATE entry if dates changed // 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) { foreach ($master['recurrence']['RDATE'] as $j => $rdate) {
if ($rdate->format('Ymd') == $old_date) { if ($rdate->format('Ymd') == $old_date) {
$master['recurrence']['RDATE'][$j] = $event['start']; $master['recurrence']['RDATE'][$j] = $event['start'];
@ -1122,7 +1104,7 @@ class kolab_driver extends calendar_driver
break; break;
default: // 'all' is default default: // 'all' is default
$event['id'] = $master['id']; $event['id'] = $master['uid'];
$event['uid'] = $master['uid']; $event['uid'] = $master['uid'];
// use start date from master but try to be smart on time or duration changes // 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 // 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['start'] = $master['start'];
$event['end'] = $master['end']; $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 _dateonly flags in (cached) date objects
unset($event['start']->_dateonly, $event['end']->_dateonly); unset($event['start']->_dateonly, $event['end']->_dateonly);
$success = $storage->update_event($event); $success = $storage->update_event($event) ? $event['id'] : false; // return master UID
break; break;
} }
@ -1500,7 +1485,7 @@ class kolab_driver extends calendar_driver
$this->rc->user->save_prefs(array('calendar_categories' => $old_categories)); $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; return $events;
} }
@ -1663,8 +1648,8 @@ class kolab_driver extends calendar_driver
$event = $storage->get_event($event['id']); $event = $storage->get_event($event['id']);
if ($event && !empty($event['attachments'])) { if ($event && !empty($event['_attachments'])) {
foreach ($event['attachments'] as $att) { foreach ($event['_attachments'] as $att) {
if ($att['id'] == $id) { if ($att['id'] == $id) {
return $att; 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']; $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 // all-day events go from 12:00 - 13:00
if (is_a($record['start'], 'DateTime') && $record['end'] <= $record['start'] && $record['allday']) { if (is_a($record['start'], 'DateTime') && $record['end'] <= $record['start'] && $record['allday']) {
$record['end'] = clone $record['start']; $record['end'] = clone $record['start'];
@ -1932,17 +1927,6 @@ class kolab_driver extends calendar_driver
if (empty($record['recurrence'])) if (empty($record['recurrence']))
unset($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 // clean up exception data
if (is_array($record['recurrence']['EXCEPTIONS'])) { if (is_array($record['recurrence']['EXCEPTIONS'])) {
array_walk($record['recurrence']['EXCEPTIONS'], function(&$exception) { 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'], unset($record['_mailbox'], $record['_msguid'], $record['_type'], $record['_size'],
$record['_formatobj'], $record['_attachments'], $record['exceptions'], $record['x-custom']); $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()) public static function from_rcube_event($event, $old = array())
{ {
// in kolab_storage attachments are indexed by content-id // 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(); $event['_attachments'] = array();
foreach ($event['attachments'] as $attachment) { foreach ($event['attachments'] as $attachment) {
@ -1985,7 +1963,7 @@ class kolab_driver extends calendar_driver
} }
// flagged for deletion => set to false // flagged for deletion => set to false
if ($attachment['_deleted']) { if ($attachment['_deleted'] || in_array($attachment['id'], (array)$event['deleted_attachments'])) {
$event['_attachments'][$key] = false; $event['_attachments'][$key] = false;
} }
// replace existing entry // 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; return $event;