From 94260b2aebe81645761260f4f5d221bff0e96cf0 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Fri, 27 Feb 2015 17:52:17 +0100 Subject: [PATCH] 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 --- plugins/calendar/calendar.php | 13 +- .../calendar/drivers/kolab/kolab_calendar.php | 79 ++++++---- .../calendar/drivers/kolab/kolab_driver.php | 147 ++++++++---------- 3 files changed, 127 insertions(+), 112 deletions(-) diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index 5d321881..6cf29db4 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 79450af8..10c256aa 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 cfdcb4cd..1bd8caf8 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;