diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php index faa92d22..bfdaf7b1 100644 --- a/plugins/calendar/drivers/kolab/kolab_calendar.php +++ b/plugins/calendar/drivers/kolab/kolab_calendar.php @@ -192,14 +192,22 @@ class kolab_calendar extends kolab_storage_folder_api // event not found, maybe a recurring instance is requested if (!$this->events[$id]) { $master_id = preg_replace('/-\d+(T\d{6})?$/', '', $id); - if ($master_id != $id && ($record = $this->storage->get_object($master_id))) - $this->events[$master_id] = $this->_to_rcube_event($record); + $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); + } // check for match on the first instance already - if (($_instance = $this->events[$master_id]['_instance']) && $id == $master_id . '-' . $_instance) { + if ($master['_instance'] && $master['_instance'] == $instance_id) { $this->events[$id] = $this->events[$master_id]; } - else if (($master = $this->events[$master_id]) && $master['recurrence']) { + // 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; + } + else if ($master && is_array($master['recurrence'])) { $this->get_recurring_events($record, $master['start'], null, $id); } } @@ -318,6 +326,15 @@ class kolab_calendar extends kolab_storage_folder_api if ($record['recurrence'] && $virtual == 1) { $events = array_merge($events, $this->get_recurring_events($record, $start, $end)); } + // 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); + if ($component['start'] <= $end && $component['end'] >= $start) { + $events[] = $component; + } + } + } } // post-filter all events by fulltext search and partstat values @@ -447,7 +464,7 @@ class kolab_calendar extends kolab_storage_folder_api public function update_event($event, $exception_id = null) { $updated = false; - $old = $this->storage->get_object($event['id']); + $old = $this->storage->get_object($event['uid'] ?: $event['id']); if (!$old || PEAR::isError($old)) return false; @@ -456,7 +473,7 @@ class kolab_calendar extends kolab_storage_folder_api unset($event['links']); $object = $this->_from_rcube_event($event, $old); - $saved = $this->storage->save($object, 'event', $event['id']); + $saved = $this->storage->save($object, 'event', $old['uid']); if (!$saved) { rcube::raise_error(array( @@ -751,14 +768,6 @@ class kolab_calendar extends kolab_storage_folder_api } } - // search in recurrence exceptions - if (!$hits && $recursive && !empty($event['recurrence']['EXCEPTIONS'])) { - foreach ($event['recurrence']['EXCEPTIONS'] as $exception) { - $hits = $this->fulltext_match($exception, $word, false); - if ($hits) break; - } - } - return $hits; } diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php index 6549381a..cfdcb4cd 100644 --- a/plugins/calendar/drivers/kolab/kolab_driver.php +++ b/plugins/calendar/drivers/kolab/kolab_driver.php @@ -583,8 +583,15 @@ class kolab_driver extends calendar_driver $cid = $event['calendar'] ? $event['calendar'] : reset(array_keys($this->calendars)); if ($storage = $this->get_calendar($cid)) { - $success = $storage->insert_event($event); - + // 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']))) { + self::add_exception($master, $event); + $success = $storage->update_event($master); + } + else { + $success = $storage->insert_event($event); + } + if ($success && $this->freebusy_trigger) { $this->rc->output->command('plugin.ping_url', array('action' => 'calendar/push-freebusy', 'source' => $storage->id)); $this->freebusy_trigger = false; // disable after first execution (#2355) @@ -676,6 +683,9 @@ class kolab_driver extends calendar_driver $master['recurrence']['EXCEPTIONS'][] = $event; } + // set link to top-level exceptions + $master['exceptions'] = &$master['recurrence']['EXCEPTIONS']; + return $this->update_event($master); } } @@ -741,22 +751,30 @@ class kolab_driver extends calendar_driver $this->rc->session->remove('calendar_restore_event_data'); // read master if deleting a recurring event - if ($event['recurrence'] || $event['recurrence_id']) { - $master = $event['recurrence_id'] ? $storage->get_event($event['recurrence_id']) : $event; - $savemode = $event['_savemode'] ?: ($event['_instance'] ? 'current' : 'all'); + if ($event['recurrence'] || $event['recurrence_id'] || $event['isexception']) { + $master = $event['recurrence_id'] || $event['isexception'] ? $storage->get_event($event['uid']) : $event; + $savemode = $event['_savemode'] ?: ($event['_instance'] || $event['isexception'] ? 'current' : 'all'); + + // force 'current' mode for single occurrences stored as exception + if (!$event['recurrence'] && !$event['recurrence_id'] && $event['isexception']) + $savemode = 'current'; } // removing an exception instance - if ($event['recurrence_id'] && $master['recurrence'] && is_array($master['recurrence']['EXCEPTIONS'])) { - foreach ($master['recurrence']['EXCEPTIONS'] as $i => $exception) { + if (($event['recurrence_id'] || $event['isexception']) && is_array($master['exceptions'])) { + foreach ($master['exceptions'] as $i => $exception) { if ($exception['_instance'] == $event['_instance']) { - unset($master['recurrence']['EXCEPTIONS'][$i]); + unset($master['exceptions'][$i]); // set event date back to the actual occurrence if ($exception['recurrence_date']) $event['start'] = $exception['recurrence_date']; break; } } + + if (is_array($master['recurrence'])) { + $master['recurrence']['EXCEPTIONS'] = &$master['exceptions']; + } } switch ($savemode) { @@ -764,7 +782,7 @@ 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']) { + if ($master['id'] == $event['id'] && $master['recurrence']) { $recurring = reset($storage->get_recurring_events($event, $event['start'], null, $event['id'].'-1')); // no future instances found: delete the master event (bug #1677) @@ -821,7 +839,18 @@ class kolab_driver extends calendar_driver } default: // 'all' is default - if ($decline && $this->rc->config->get('kolab_invitation_calendars')) { + // removing the master event with loose exceptions (not recurring though) + if (!empty($event['recurrence_date']) && !empty($master['exceptions'])) { + // make the first exception the new master + $newmaster = array_shift($master['exceptions']); + $newmaster['exceptions'] = $master['exceptions']; + $newmaster['_attachments'] = $master['_attachments']; + $newmaster['_mailbox'] = $master['_mailbox']; + $newmaster['_msguid'] = $master['_msguid']; + + $success = $storage->update_event($newmaster); + } + else if ($decline && $this->rc->config->get('kolab_invitation_calendars')) { // don't delete but set PARTSTAT=DECLINED if ($this->cal->lib->set_partstat($master, 'DECLINED')) { $success = $storage->update_event($master); @@ -934,13 +963,16 @@ class kolab_driver extends calendar_driver $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']) { - $master = $old['recurrence_id'] ? $fromcalendar->get_event($old['recurrence_id']) : $old; - $savemode = $event['_savemode'] ?: ($old['recurrence_id'] ? 'current' : 'all'); + if ($old['recurrence'] || $old['recurrence_id'] || $old['isexception']) { + $master = $old['recurrence_id'] || $old['isexception'] ? $fromcalendar->get_event($old['uid']) : $old; + $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') $savemode = 'all'; + // force 'current' mode for single occurrences stored as exception + else if (!$old['recurrence'] && !$old['recurrence_id'] && $old['isexception']) + $savemode = 'current'; } // check if update affects scheduling and update attendee status accordingly @@ -953,6 +985,8 @@ class kolab_driver extends calendar_driver $with_exceptions = true; // exceptions already provided (e.g. from iCal import) else if ($old['recurrence']['EXCEPTIONS']) $event['recurrence']['EXCEPTIONS'] = $old['recurrence']['EXCEPTIONS']; + else if ($old['exceptions']) + $event['exceptions'] = $old['exceptions']; // remove some internal properties which should not be saved unset($event['_savemode'], $event['_fromcalendar'], $event['_identity'], $event['_owner'], @@ -1039,7 +1073,7 @@ class kolab_driver extends calendar_driver // update master event (no rescheduling!) self::clear_attandee_noreply($master); - $udated = $storage->update_event($master); + $storage->update_event($master); } break; @@ -1054,7 +1088,7 @@ class kolab_driver extends calendar_driver $event['sequence'] = max($old['sequence'], $master['sequence']) + 1; } else if (!isset($event['sequence'])) { - $event['sequence'] = $master['sequence']; + $event['sequence'] = $old['sequence'] ?: $master['sequence']; } // save properties to a recurrence exception instance @@ -1081,10 +1115,9 @@ class kolab_driver extends calendar_driver // save as new exception to master event if ($add_exception) { - $event['_instance'] = $old['_instance']; - $event['recurrence_date'] = $old['recurrence_date'] ?: $old['start']; - $master['recurrence']['EXCEPTIONS'][] = $event; + self::add_exception($master, $event, $old); } + $success = $storage->update_event($master); break; @@ -1128,6 +1161,10 @@ class kolab_driver extends calendar_driver if ($old['recurrence_id']) { $event['recurrence']['EXCEPTIONS'] = $master['recurrence']['EXCEPTIONS']; } + else if ($master['_instance']) { + $event['_instance'] = $master['_instance']; + $event['recurrence_date'] = $master['recurrence_date']; + } // TODO: forward changes to exceptions (which do not yet have differing values stored) if (is_array($event['recurrence']) && is_array($event['recurrence']['EXCEPTIONS']) && !$with_exceptions) { @@ -1286,10 +1323,51 @@ class kolab_driver extends calendar_driver } */ + // set link to top-level exceptions + $master['exceptions'] = &$master['recurrence']['EXCEPTIONS']; + // returning false here will add a new exception return $saved; } + /** + * Add or update the given event as an exception to $master + */ + public static function add_exception(&$master, $event, $old = null) + { + if ($old) { + $event['_instance'] = $old['_instance']; + if (!$event['recurrence_date']) + $event['recurrence_date'] = $old['recurrence_date'] ?: $old['start']; + } + else if (!$event['recurrence_date']) { + $event['recurrence_date'] = $event['start']; + } + + if (!$event['_instance'] && is_a($event['recurrence_date'], 'DateTime')) { + $recurrence_id_format = $event['allday'] ? 'Ymd' : 'Ymd\THis'; + $event['_instance'] = $event['recurrence_date']->format($recurrence_id_format); + } + + if (!is_array($master['exceptions']) && is_array($master['recurrence']['EXCEPTIONS'])) { + $master['exceptions'] = &$master['recurrence']['EXCEPTIONS']; + } + + $existing = false; + foreach ((array)$master['exceptions'] as $i => $exception) { + if ($exception['_instance'] == $event['_instance']) { + $master['exceptions'][$i] = $event; + $existing = true; + } + } + + if (!$existing) { + $master['exceptions'][] = $event; + } + + return true; + } + /** * Remove the noreply flags from attendees */ @@ -1856,10 +1934,14 @@ class kolab_driver extends calendar_driver // 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']) { - $recurrence_id_format = $record['allday'] ? 'Ymd' : 'Ymd\THis'; $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'])) { @@ -1877,7 +1959,7 @@ class kolab_driver extends calendar_driver public static function clean_rcube_event_out(&$record) { unset($record['_mailbox'], $record['_msguid'], $record['_type'], $record['_size'], - $record['_formatobj'], $record['_attachments'], $record['x-custom']); + $record['_formatobj'], $record['_attachments'], $record['exceptions'], $record['x-custom']); } /**