From 83a95c9aeea7df7a39557fe89bb1e739fc3a2cb9 Mon Sep 17 00:00:00 2001 From: "Aleksander Machniak (Kolab Systems)" Date: Mon, 18 Jul 2011 15:28:57 +0200 Subject: [PATCH] Added possibility to undo last event delete action (kolab driver only) --- plugins/calendar/calendar.php | 81 ++++++++++++++----- .../calendar/drivers/caldav/caldav_driver.php | 2 +- plugins/calendar/drivers/calendar_driver.php | 25 +++++- .../drivers/database/database_driver.php | 6 +- .../calendar/drivers/kolab/kolab_calendar.php | 70 +++++++++++++++- .../calendar/drivers/kolab/kolab_driver.php | 41 +++++++--- plugins/calendar/localization/en_US.inc | 19 ++--- 7 files changed, 195 insertions(+), 49 deletions(-) diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index 49c3a493..76cb88d6 100644 --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -109,7 +109,17 @@ class calendar extends rcube_plugin $this->register_action('freebusy-times', array($this, 'freebusy_times')); $this->register_action('randomdata', array($this, 'generate_randomdata')); $this->register_action('print',array($this,'print_view')); - } + + // remove undo information... + if ($undo = $_SESSION['calendar_event_undo']) { + // ...after timeout + $undo_time = $this->rc->config->get('undo_timeout', 0); + if ($undo['ts'] < time() - $undo_time) { + $this->rc->session->remove('calendar_event_undo'); + // @TODO: do EXPUNGE on kolab objects? + } + } + } else if ($this->rc->task == 'settings') { // add hooks for Calendar settings $this->add_hook('preferences_sections_list', array($this, 'preferences_sections_list')); @@ -436,10 +446,10 @@ class calendar extends rcube_plugin */ function event_action() { - $action = get_input_value('action', RCUBE_INPUT_POST); - $event = get_input_value('e', RCUBE_INPUT_POST); - $success = $reload = false; - + $action = get_input_value('action', RCUBE_INPUT_GPC); + $event = get_input_value('e', RCUBE_INPUT_POST); + $success = $reload = $got_msg = false; + switch ($action) { case "new": // create UID for new event @@ -475,31 +485,66 @@ class calendar extends rcube_plugin break; case "remove": - $removed = $this->driver->remove_event($event); - $reload = true; + // remove previous deletes + $undo_time = $this->driver->undelete ? $this->rc->config->get('undo_timeout', 0) : 0; + $this->rc->session->remove('calendar_event_undo'); + + $success = $this->driver->remove_event($event, $undo_time < 1); + $reload = true; + + 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'))", + JS_OBJECT_NAME, JS_OBJECT_NAME)), rcube_label('undo')); + $this->rc->output->show_message($msg, 'confirmation', null, true, $undo_time); + } + else if ($success) { + $this->rc->output->show_message('calendar.successremoval', 'confirmation'); + $got_msg = true; + } + break; - + + case "undo": + // Restore deleted event + $event = $_SESSION['calendar_event_undo']['data']; + $reload = true; + + if ($event) + $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; + } + + break; + case "dismiss": foreach (explode(',', $event['id']) as $id) $success |= $this->driver->dismiss_alarm($id, $event['snooze']); break; } - + + // show confirmation/error message + if (!$got_msg) { + if ($success) + $this->rc->output->show_message('successfullysaved', 'confirmation'); + else + $this->rc->output->show_message('calendar.errorsaving', 'error'); + } + // unlock client $this->rc->output->command('plugin.unlock_saving'); - - if ($success) - $this->rc->output->show_message('successfullysaved', 'confirmation'); - else if ($removed) - $this->rc->output->show_message('calendar.successremoval', 'confirmation'); - else - $this->rc->output->show_message('calendar.errorsaving', 'error'); // FIXME: update a single event object on the client instead of reloading the entire source - if ($success && $reload || ($removed && $reload)) + if ($success && $reload) $this->rc->output->command('plugin.reload_calendar', array('source' => $event['calendar'])); } - + /** * Handler for load-requests from fullcalendar * This will return pure JSON formatted output diff --git a/plugins/calendar/drivers/caldav/caldav_driver.php b/plugins/calendar/drivers/caldav/caldav_driver.php index e26f94cd..23ae21e6 100644 --- a/plugins/calendar/drivers/caldav/caldav_driver.php +++ b/plugins/calendar/drivers/caldav/caldav_driver.php @@ -49,7 +49,7 @@ class caldav_driver extends calendar_driver // FIXME Implement. Can be done via editEvent } - public function remove_event($event) { + public function remove_event($event, $force = true) { // FIXME Implement. } diff --git a/plugins/calendar/drivers/calendar_driver.php b/plugins/calendar/drivers/calendar_driver.php index 876fe52f..2beec9cf 100644 --- a/plugins/calendar/drivers/calendar_driver.php +++ b/plugins/calendar/drivers/calendar_driver.php @@ -72,10 +72,11 @@ */ abstract class calendar_driver { - // backend features + // features supported by backend public $alarms = false; public $attendees = false; public $attachments = false; + public $undelete = false; // event undelete action public $categoriesimmutable = false; public $alarm_types = array('DISPLAY'); @@ -156,11 +157,27 @@ abstract class calendar_driver /** * Remove a single event from the database * - * @param array Hash array with event properties: - * id: Event identifier + * @param array Hash array with event properties: + * id: Event identifier + * @param boolean Remove event irreversible (mark as deleted otherwise, + * if supported by the backend) + * * @return boolean True on success, False on error */ - abstract function remove_event($event); + abstract function remove_event($event, $force = true); + + /** + * Restores a single deleted event (if supported) + * + * @param array Hash array with event properties: + * id: Event identifier + * + * @return boolean True on success, False on error + */ + public function restore_event($event) + { + return false; + } /** * Get events from source. diff --git a/plugins/calendar/drivers/database/database_driver.php b/plugins/calendar/drivers/database/database_driver.php index 0a7b1889..438f270b 100644 --- a/plugins/calendar/drivers/database/database_driver.php +++ b/plugins/calendar/drivers/database/database_driver.php @@ -513,10 +513,12 @@ class database_driver extends calendar_driver /** * Remove a single event from the database * - * @param array Hash array with event properties + * @param array Hash array with event properties + * @param boolean Remove record irreversible (@TODO) + * * @see Driver:remove_event() */ - public function remove_event($event) + public function remove_event($event, $force = true) { if (!empty($this->calendars)) { $event += (array)$this->get_event($event['id']); diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php index d6cd3b5b..d7e22c80 100644 --- a/plugins/calendar/drivers/kolab/kolab_calendar.php +++ b/plugins/calendar/drivers/kolab/kolab_calendar.php @@ -294,10 +294,17 @@ class kolab_calendar * @see Driver:remove_event() * @return boolean True on success, False on error */ - public function delete_event($event) + public function delete_event($event, $force = true) { - $deleted = false; - $deleteme = $this->storage->delete($event['id']); + $deleted = false; + + if (!$force) { + // Get IMAP object ID + $imap_uid = $this->storage->_getStorageId($event['id']); + } + + $deleteme = $this->storage->delete($event['id'], $force); + if (PEAR::isError($deleteme)) { raise_error(array( 'code' => 600, 'type' => 'php', @@ -306,12 +313,67 @@ class kolab_calendar true, false); } else { + // Save IMAP object ID in session, will be used for restore action + if ($imap_uid) + $_SESSION['kolab_delete_uids'][$event['id']] = $imap_uid; + $deleted = true; } - + return $deleted; } + /** + * Restore deleted event record + * + * @see Driver:undelete_event() + * @return boolean True on success, False on error + */ + public function restore_event($event) + { + $imap_uid = $_SESSION['kolab_delete_uids'][$event['id']]; + + if (!$imap_uid) + return false; + + $session = &Horde_Kolab_Session::singleton(); + $imap = &$session->getImap(); + + if (is_a($imap, 'PEAR_Error')) { + $error = $imap; + } + else { + $result = $imap->select($this->imap_folder); + if (is_a($result, 'PEAR_Error')) { + $error = $result; + } + else { + $result = $imap->undeleteMessages($imap_uid); + if (is_a($result, 'PEAR_Error')) { + $error = $result; + } + else { + // re-sync the cache + $this->storage->synchronize(); + } + } + } + + if ($error) { + raise_error(array( + 'code' => 600, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Error undeleting an event object(s) from the Kolab server:" . $error->getMessage()), + true, false); + + return false; + } + + $rcmail = rcmail::get_instance(); + $rcmail->session->remove('kolab_delete_uids'); + + return true; + } /** * Simply fetch all records and store them in private member vars diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php index d01a7432..43db91ae 100644 --- a/plugins/calendar/drivers/kolab/kolab_driver.php +++ b/plugins/calendar/drivers/kolab/kolab_driver.php @@ -27,6 +27,7 @@ class kolab_driver extends calendar_driver public $alarms = true; public $attendees = true; public $attachments = true; + public $undelete = true; public $categoriesimmutable = true; private $rc; @@ -292,35 +293,37 @@ class kolab_driver extends calendar_driver } /** - * Remove a single event from the database + * Remove a single event + * + * @param array Hash array with event properties: + * id: Event identifier + * @param boolean Remove record(s) irreversible (mark as deleted otherwise) * - * @param array Hash array with event properties: - * id: Event identifier * @return boolean True on success, False on error */ - public function remove_event($event) + public function remove_event($event, $force = true) { $success = false; - + if (($storage = $this->calendars[$event['calendar']]) && ($event = $storage->get_event($event['id']))) { $savemode = 'all'; $master = $event; - + $GLOBALS['conf']['kolab']['no_triggering'] = true; - + // 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']; } - + switch ($savemode) { case 'current': // add exception to master event $master['recurrence']['EXDATE'][] = $event['start']; $success = $storage->update_event($master); break; - + case 'future': if ($master['id'] != $event['id']) { // set until-date on master event @@ -329,9 +332,9 @@ class kolab_driver extends calendar_driver $success = $storage->update_event($master); break; } - + default: // 'all' is default - $success = $storage->delete_event($master); + $success = $storage->delete_event($master, $force); break; } } @@ -342,6 +345,22 @@ class kolab_driver extends calendar_driver return $success; } + /** + * Restore a single deleted event + * + * @param array Hash array with event properties: + * id: Event identifier + * @return boolean True on success, False on error + */ + public function restore_event($event) + { + if ($storage = $this->calendars[$event['calendar']]) { + return $storage->restore_event($event); + } + + return false; + } + /** * Wrapper to update an event object depending on the given savemode */ diff --git a/plugins/calendar/localization/en_US.inc b/plugins/calendar/localization/en_US.inc index e5fdda74..7126921f 100644 --- a/plugins/calendar/localization/en_US.inc +++ b/plugins/calendar/localization/en_US.inc @@ -100,15 +100,16 @@ $labels['tabattendees'] = 'Participants'; $labels['tabattachments'] = 'Attachments'; // messages -$labels['deleteventconfirm'] = "Do you really want to delete this event?"; -$labels['deletecalendarconfirm'] = "Do you really want to delete this calendar with all its events?"; -$labels['savingdata'] = "Saving data..."; -$labels['errorsaving'] = "Failed to save changes"; -$labels['operationfailed'] = "The requested operation failed"; -$labels['invalideventdates'] = "Invalid dates entered! Please check your input."; -$labels['invalidcalendarproperties'] = "Invalid calendar properties! Please set a valid name."; -$labels['searchnoresults'] = 'No events found in the selected calendars'; -$labels['successremoval'] = "The event was deleted successfully"; +$labels['deleteventconfirm'] = 'Do you really want to delete this event?'; +$labels['deletecalendarconfirm'] = 'Do you really want to delete this calendar with all its events?'; +$labels['savingdata'] = 'Saving data...'; +$labels['errorsaving'] = 'Failed to save changes.'; +$labels['operationfailed'] = 'The requested operation failed.'; +$labels['invalideventdates'] = 'Invalid dates entered! Please check your input.'; +$labels['invalidcalendarproperties'] = 'Invalid calendar properties! Please set a valid name.'; +$labels['searchnoresults'] = 'No events found in the selected calendars.'; +$labels['successremoval'] = 'The event has been deleted successfully.'; +$labels['successrestore'] = 'The event has been restored successfully.'; // recurrence form $labels['repeat'] = 'Repeat';