diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index 67466b66..41f43d99 100644 --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -979,8 +979,7 @@ class calendar extends rcube_plugin case "remove": // remove previous deletes $undo_time = $this->driver->undelete ? $this->rc->config->get('undo_timeout', 0) : 0; - $this->rc->session->remove('calendar_event_undo'); - + // search for event if only UID is given if (!isset($event['calendar']) && $event['uid']) { if (!($event = $this->driver->get_event($event, calendar_driver::FILTER_WRITEABLE))) { @@ -989,11 +988,12 @@ class calendar extends rcube_plugin $undo_time = 0; } + // Note: the driver is responsible for setting $_SESSION['calendar_event_undo'] + // containing 'ts' and 'data' elements $success = $this->driver->remove_event($event, $undo_time < 1); $reload = (!$success || $event['_savemode']) ? 2 : 1; if ($undo_time > 0 && $success) { - $_SESSION['calendar_event_undo'] = array('ts' => time(), 'data' => $event); // display message with Undo link. $msg = html::span(null, $this->gettext('successremoval')) . ' ' . html::a(array('onclick' => sprintf("%s.http_request('event', 'action=undo', %s.display_message('', 'loading'))", @@ -1047,16 +1047,14 @@ class calendar extends rcube_plugin case "undo": // Restore deleted event - $event = $_SESSION['calendar_event_undo']['data']; - - if ($event) + if ($event = $_SESSION['calendar_event_undo']['data']) $success = $this->driver->restore_event($event); if ($success) { $this->rc->session->remove('calendar_event_undo'); $this->rc->output->show_message('calendar.successrestore', 'confirmation'); $got_msg = true; - $reload = 2; + $reload = 2; } break; diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js index 5bfaf3f3..ab23605c 100644 --- a/plugins/calendar/calendar_ui.js +++ b/plugins/calendar/calendar_ui.js @@ -527,7 +527,13 @@ function rcube_calendar_ui(settings) } } - var buttons = []; + var buttons = [], is_removable_event = function(event, calendar) { + // for invitation calendars check permissions of the original folder + if (event._folder_id) + calendar = me.calendars[event._folder_id]; + return calendar && me.has_permission(calendar, 'td'); + }; + if (!temp && calendar.editable && event.editable !== false) { buttons.push({ text: rcmail.gettext('edit', 'calendar'), @@ -537,7 +543,8 @@ function rcube_calendar_ui(settings) } }); } - if (!temp && me.has_permission(calendar, 'td') && event.editable !== false) { + + if (!temp && is_removable_event(event, calendar) && event.editable !== false) { buttons.push({ text: rcmail.gettext('delete', 'calendar'), 'class': 'delete', @@ -565,7 +572,7 @@ function rcube_calendar_ui(settings) open: function() { $dialog.attr('aria-hidden', 'false'); setTimeout(function(){ - $dialog.parent().find('button:not(.ui-dialog-titlebar-close)').first().focus(); + $dialog.parent().find('button:not(.ui-dialog-titlebar-close,.delete)').first().focus(); }, 5); }, beforeClose: function(e) { @@ -2587,6 +2594,7 @@ function rcube_calendar_ui(settings) if (!data) data = event; var decline = false, notify = false, html = '', cal = me.calendars[event.calendar], + _is_invitation = String(event.calendar).match(/^--invitation--(declined|pending)/) && RegExp.$1, _has_attendees = me.has_attendees(event), _is_attendee = _has_attendees && me.is_attendee(event), _is_organizer = me.is_organizer(event); @@ -2595,19 +2603,19 @@ function rcube_calendar_ui(settings) if (_has_attendees) { var checked = (settings.itip_notify & 1 ? ' checked="checked"' : ''); - if (action == 'remove' && cal.group != 'shared' && !_is_organizer && _is_attendee) { + if (action == 'remove' && cal.group != 'shared' && !_is_organizer && _is_attendee && _is_invitation != 'declined') { decline = true; checked = event.status != 'CANCELLED' ? checked : ''; html += '
' + - '
'; } else if (_is_organizer) { notify = true; if (settings.itip_notify & 2) { html += '
' + - '
'; } @@ -2615,7 +2623,7 @@ function rcube_calendar_ui(settings) data._notify = settings.itip_notify; } } - else if (cal.group != 'shared') { + else if (cal.group != 'shared' && !_is_invitation) { html += '
' + $('#edit-localchanges-warning').html() + '
'; data._notify = 0; } @@ -3217,7 +3225,10 @@ function rcube_calendar_ui(settings) function update_view(view, event, source) { var existing = view.fullCalendar('clientEvents', event._id); if (existing.length) { + delete existing[0].temp; + delete existing[0].editable; $.extend(existing[0], event); + view.fullCalendar('updateEvent', existing[0]); // remove old recurrence instances if (event.recurrence && !event.recurrence_id) @@ -3254,16 +3265,13 @@ function rcube_calendar_ui(settings) // add/update single event object else if (source && p.update) { var event = p.update; - event.temp = false; - event.editable = 0; // update main view - event.editable = source.editable; update_view(fc, event, source); // update the currently displayed event dialog if ($('#eventshow').is(':visible') && me.selected_event && me.selected_event.id == event.id) - event_show_dialog(event) + event_show_dialog(event); } // refetch all calendars else if (p.refetch) { diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php index e4459761..39cffe73 100644 --- a/plugins/calendar/drivers/kolab/kolab_calendar.php +++ b/plugins/calendar/drivers/kolab/kolab_calendar.php @@ -406,10 +406,12 @@ class kolab_calendar extends kolab_storage_folder_api } /** + * Get number of events in the given calendar * * @param integer Date range start (unix timestamp) * @param integer Date range end (unix timestamp) * @param array Additional query to filter events + * * @return integer Count */ public function count_events($start, $end = null, $filter_query = null) @@ -432,7 +434,7 @@ class kolab_calendar extends kolab_storage_folder_api // query Kolab storage $query[] = array('dtend', '>=', $start); - + if ($end) $query[] = array('dtstart', '<=', $end); @@ -455,7 +457,7 @@ class kolab_calendar extends kolab_storage_folder_api * Create a new event record * * @see calendar_driver::new_event() - * + * * @return mixed The created record ID on success, False on error */ public function insert_event($event) @@ -495,9 +497,9 @@ class kolab_calendar extends kolab_storage_folder_api * Update a specific event record * * @see calendar_driver::new_event() + * * @return boolean True on success, False on error */ - public function update_event($event, $exception_id = null) { $updated = false; @@ -541,6 +543,7 @@ class kolab_calendar extends kolab_storage_folder_api * Delete an event record * * @see calendar_driver::remove_event() + * * @return boolean True on success, False on error */ public function delete_event($event, $force = true) @@ -549,9 +552,8 @@ class kolab_calendar extends kolab_storage_folder_api if (!$deleted) { rcube::raise_error(array( - 'code' => 600, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => sprintf("Error deleting event object '%s' from Kolab server", $event['id'])), + 'code' => 600, 'file' => __FILE__, 'line' => __LINE__, + 'message' => sprintf("Error deleting event object '%s' from Kolab server", $event['id'])), true, false); } @@ -562,18 +564,21 @@ class kolab_calendar extends kolab_storage_folder_api * Restore deleted event record * * @see calendar_driver::undelete_event() + * * @return boolean True on success, False on error */ public function restore_event($event) { - if ($this->storage->undelete($event['id'])) { + // Make sure this is not an instance identifier + $uid = preg_replace('/-\d{8}(T\d{6})?$/', '', $event['id']); + + if ($this->storage->undelete($uid)) { return true; } else { rcube::raise_error(array( - 'code' => 600, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Error undeleting the event object $event[id] from the Kolab server"), + 'code' => 600, 'file' => __FILE__, 'line' => __LINE__, + 'message' => sprintf("Error undeleting the event object '%s' from the Kolab server", $event['id'])), true, false); } @@ -613,7 +618,7 @@ class kolab_calendar extends kolab_storage_folder_api { $object = $event['_formatobj']; if (!$object) { - $rec = $this->storage->get_object($event['id']); + $rec = $this->storage->get_object($event['uid'] ?: $event['id']); $object = $rec['_formatobj']; } diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php index dbfd87a9..cf6c2ff1 100644 --- a/plugins/calendar/drivers/kolab/kolab_driver.php +++ b/plugins/calendar/drivers/kolab/kolab_driver.php @@ -779,21 +779,26 @@ class kolab_driver extends calendar_driver */ public function remove_event($event, $force = true) { - $ret = true; - $success = false; + $ret = true; + $success = false; $savemode = $event['_savemode']; $decline = $event['_decline']; + if (!$force) { + unset($event['attendees']); + $this->rc->session->remove('calendar_event_undo'); + $this->rc->session->remove('calendar_restore_event_data'); + $sess_data = $event; + } + if (($storage = $this->get_calendar($event['calendar'])) && ($event = $storage->get_event($event['id']))) { $event['_savemode'] = $savemode; $savemode = 'all'; - $master = $event; - - $this->rc->session->remove('calendar_restore_event_data'); + $master = $event; // read master if deleting a recurring event if ($event['recurrence'] || $event['recurrence_id'] || $event['isexception']) { - $master = $storage->get_event($event['uid']); + $master = $storage->get_event($event['uid']); $savemode = $event['_savemode'] ?: ($event['_instance'] || $event['isexception'] ? 'current' : 'all'); // force 'current' mode for single occurrences stored as exception @@ -821,23 +826,8 @@ class kolab_driver extends calendar_driver case 'current': $_SESSION['calendar_restore_event_data'] = $master; - // removing the first instance => just move to next occurence - if ($master['recurrence'] && $event['_instance'] == libcalendaring::recurrence_instance_identifier($master)) { - $recurring = reset($storage->get_recurring_events($event, $event['start'], null, $event['id'] . '-1', 1)); - - // no future instances found: delete the master event (bug #1677) - if (!$recurring['start']) { - $success = $storage->delete_event($master, $force); - break; - } - - $master['start'] = $recurring['start']; - $master['end'] = $recurring['end']; - if ($master['recurrence']['COUNT']) - $master['recurrence']['COUNT']--; - } // remove the matching RDATE entry - else if ($master['recurrence']['RDATE']) { + if ($master['recurrence']['RDATE']) { foreach ($master['recurrence']['RDATE'] as $j => $rdate) { if ($rdate->format('Ymd') == $event['start']->format('Ymd')) { unset($master['recurrence']['RDATE'][$j]); @@ -845,9 +835,10 @@ class kolab_driver extends calendar_driver } } } - else { // add exception to master event - $master['recurrence']['EXDATE'][] = $event['start']; - } + + // add exception to master event + $master['recurrence']['EXDATE'][] = $event['start']; + $success = $storage->update_event($master); break; @@ -855,7 +846,7 @@ class kolab_driver extends calendar_driver $master['_instance'] = libcalendaring::recurrence_instance_identifier($master); if ($master['_instance'] != $event['_instance']) { $_SESSION['calendar_restore_event_data'] = $master; - + // set until-date on master event $master['recurrence']['UNTIL'] = clone $event['start']; $master['recurrence']['UNTIL']->sub(new DateInterval('P1D')); @@ -885,10 +876,10 @@ class kolab_driver extends calendar_driver 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']; + $newmaster['exceptions'] = $master['exceptions']; $newmaster['_attachments'] = $master['_attachments']; - $newmaster['_mailbox'] = $master['_mailbox']; - $newmaster['_msguid'] = $master['_msguid']; + $newmaster['_mailbox'] = $master['_mailbox']; + $newmaster['_msguid'] = $master['_msguid']; $success = $storage->update_event($newmaster); } @@ -905,8 +896,18 @@ class kolab_driver extends calendar_driver } } + if ($success && !$force) { + if ($master['_folder_id']) + $sess_data['_folder_id'] = $master['_folder_id']; + $_SESSION['calendar_event_undo'] = array('ts' => time(), 'data' => $sess_data); + } + if ($success && $this->freebusy_trigger) - $this->rc->output->command('plugin.ping_url', array('action' => 'calendar/push-freebusy', 'source' => $storage->id)); + $this->rc->output->command('plugin.ping_url', array( + 'action' => 'calendar/push-freebusy', + // _folder_id may be set by invitations calendar + 'source' => $master['_folder_id'] ?: $storage->id, + )); return $success ? $ret : false; } @@ -915,20 +916,26 @@ class kolab_driver extends calendar_driver * Restore a single deleted event * * @param array Hash array with event properties: - * id: Event identifier + * id: Event identifier + * calendar: Event calendar + * * @return boolean True on success, False on error */ public function restore_event($event) { if ($storage = $this->get_calendar($event['calendar'])) { if (!empty($_SESSION['calendar_restore_event_data'])) - $success = $storage->update_event($_SESSION['calendar_restore_event_data']); + $success = $storage->update_event($event = $_SESSION['calendar_restore_event_data']); else $success = $storage->restore_event($event); - + if ($success && $this->freebusy_trigger) - $this->rc->output->command('plugin.ping_url', array('action' => 'calendar/push-freebusy', 'source' => $storage->id)); - + $this->rc->output->command('plugin.ping_url', array( + 'action' => 'calendar/push-freebusy', + // _folder_id may be set by invitations calendar + 'source' => $event['_folder_id'] ?: $storage->id, + )); + return $success; } diff --git a/plugins/calendar/drivers/kolab/kolab_invitation_calendar.php b/plugins/calendar/drivers/kolab/kolab_invitation_calendar.php index ac676796..adb73e3c 100644 --- a/plugins/calendar/drivers/kolab/kolab_invitation_calendar.php +++ b/plugins/calendar/drivers/kolab/kolab_invitation_calendar.php @@ -176,16 +176,31 @@ class kolab_invitation_calendar $event = $this->cal->driver->get_event($id, calendar_driver::FILTER_WRITEABLE); if (is_array($event)) { - // add pointer to original calendar folder - $event['_folder_id'] = $event['calendar']; - $event = $this->_mod_event($event); + $event = $this->_mod_event($event, $event['calendar']); } return $event; } + /** + * Create instances of a recurring event + * + * @see kolab_calendar::get_recurring_events() + */ + public function get_recurring_events($event, $start, $end = null, $event_id = null, $limit = null) + { + // forward call to the actual storage folder + if ($event['_folder_id']) { + $cal = $this->cal->driver->get_calendar($event['_folder_id']); + if ($cal && $cal->ready) { + return $cal->get_recurring_events($event, $start, $end, $event_id, $limit); + } + } + } + /** * Get attachment body + * * @see calendar_driver::get_attachment_body() */ public function get_attachment_body($id, $event) @@ -212,11 +227,12 @@ class kolab_invitation_calendar } /** - * @param integer Event's new start (unix timestamp) - * @param integer Event's new end (unix timestamp) - * @param string Search query (optional) - * @param boolean Include virtual events (optional) - * @param array Additional parameters to query storage + * @param integer Event's new start (unix timestamp) + * @param integer Event's new end (unix timestamp) + * @param string Search query (optional) + * @param boolean Include virtual events (optional) + * @param array Additional parameters to query storage + * * @return array A list of event records */ public function list_events($start, $end, $search = null, $virtual = 1, $query = array()) @@ -251,7 +267,7 @@ class kolab_invitation_calendar } if ($match) { - $events[$event['id'] ?: $event['uid']] = $this->_mod_event($event); + $events[$event['id'] ?: $event['uid']] = $this->_mod_event($event, $cal->id); } } @@ -263,12 +279,15 @@ class kolab_invitation_calendar } /** + * Get number of events in the given calendar + * + * @param integer Date range start (unix timestamp) + * @param integer Date range end (unix timestamp) + * @param array Additional query to filter events * - * @param integer Date range start (unix timestamp) - * @param integer Date range end (unix timestamp) * @return integer Count */ - public function count_events($start, $end = null) + public function count_events($start, $end = null, $filter = null) { // get email addresses of the current user $user_emails = $this->cal->get_user_emails(); @@ -309,7 +328,7 @@ class kolab_invitation_calendar /** * Helper method to modify some event properties */ - private function _mod_event($event) + private function _mod_event($event, $calendar_id = null) { // set classes according to PARTSTAT $event = kolab_driver::add_partstat_class($event, $this->partstats); @@ -318,15 +337,18 @@ class kolab_invitation_calendar $event['calendar'] = $this->id; } + // add pointer to original calendar folder + if ($calendar_id) { + $event['_folder_id'] = $calendar_id; + } + return $event; } /** * Create a new event record * - * @see calendar_driver::new_event() - * - * @return mixed The created record ID on success, False on error + * @see kolab_calendar::insert_event() */ public function insert_event($event) { @@ -336,8 +358,7 @@ class kolab_invitation_calendar /** * Update a specific event record * - * @see calendar_driver::new_event() - * @return boolean True on success, False on error + * @see kolab_calendar::update_event() */ public function update_event($event, $exception_id = null) { @@ -355,22 +376,36 @@ class kolab_invitation_calendar /** * Delete an event record * - * @see calendar_driver::remove_event() - * @return boolean True on success, False on error + * @see kolab_calendar::delete_event() */ public function delete_event($event, $force = true) { + // forward call to the actual storage folder + if ($event['_folder_id']) { + $cal = $this->cal->driver->get_calendar($event['_folder_id']); + if ($cal && $cal->ready) { + return $cal->delete_event($event, $force); + } + } + return false; } /** * Restore deleted event record * - * @see calendar_driver::undelete_event() - * @return boolean True on success, False on error + * @see kolab_calendar::restore_event() */ public function restore_event($event) { + // forward call to the actual storage folder + if ($event['_folder_id']) { + $cal = $this->cal->driver->get_calendar($event['_folder_id']); + if ($cal && $cal->ready) { + return $cal->restore_event($event); + } + } + return false; } } diff --git a/plugins/calendar/drivers/kolab/kolab_user_calendar.php b/plugins/calendar/drivers/kolab/kolab_user_calendar.php index 185b6099..b3c1cd1a 100644 --- a/plugins/calendar/drivers/kolab/kolab_user_calendar.php +++ b/plugins/calendar/drivers/kolab/kolab_user_calendar.php @@ -260,10 +260,12 @@ class kolab_user_calendar extends kolab_calendar } /** + * Get number of events in the given calendar + * + * @param integer Date range start (unix timestamp) + * @param integer Date range end (unix timestamp) + * @param array Additional query to filter events * - * @param integer Date range start (unix timestamp) - * @param integer Date range end (unix timestamp) - * @param array Additional query to filter events * @return integer Count */ public function count_events($start, $end = null, $filter_query = null)