- Added possibility to undo last contact delete operation
15 files modified
1 files added
| | |
| | | CHANGELOG Roundcube Webmail |
| | | =========================== |
| | | |
| | | - Added possibility to undo last contact delete operation |
| | | - Fix sorting of contact groups after group create (#1487747) |
| | | - Add optional textual upload progress indicator (#1486039) |
| | | - Fix parsing URLs containing commas (#1487970) |
| | |
| | | $this->output->set_env('comm_path', $this->comm_path); |
| | | $this->output->set_charset(RCMAIL_CHARSET); |
| | | |
| | | // add some basic label to client |
| | | // add some basic labels to client |
| | | $this->output->add_label('loading', 'servererror'); |
| | | |
| | | return $this->output; |
| | |
| | | public $primary_key; |
| | | public $groups = false; |
| | | public $readonly = true; |
| | | public $undelete = false; |
| | | public $ready = false; |
| | | public $group_id = null; |
| | | public $list_page = 1; |
| | |
| | | } |
| | | |
| | | /** |
| | | * Remove all records from the database |
| | | * Unmark delete flag on contact record(s) |
| | | * |
| | | * @param array Record identifiers |
| | | */ |
| | | function undelete($ids) |
| | | { |
| | | /* empty for read-only address books */ |
| | | } |
| | | |
| | | /** |
| | | * Mark all records in database as deleted |
| | | */ |
| | | function delete_all() |
| | | { |
| | |
| | | public $primary_key = 'contact_id'; |
| | | public $readonly = false; |
| | | public $groups = true; |
| | | public $undelete = true; |
| | | public $list_page = 1; |
| | | public $page_size = 10; |
| | | public $group_id = 0; |
| | |
| | | |
| | | |
| | | /** |
| | | * Undelete one or more contact records |
| | | * |
| | | * @param array Record identifiers |
| | | */ |
| | | function undelete($ids) |
| | | { |
| | | if (!is_array($ids)) |
| | | $ids = explode(',', $ids); |
| | | |
| | | $ids = $this->db->array2list($ids, 'integer'); |
| | | |
| | | // flag record as deleted |
| | | $this->db->query( |
| | | "UPDATE ".get_table_name($this->db_name). |
| | | " SET del=0, changed=".$this->db->now(). |
| | | " WHERE user_id=?". |
| | | " AND contact_id IN ($ids)", |
| | | $this->user_id |
| | | ); |
| | | |
| | | $this->cache = null; |
| | | |
| | | return $this->db->affected_rows(); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Remove all records from the database |
| | | */ |
| | | function delete_all() |
| | | { |
| | | $this->db->query("DELETE FROM ".get_table_name($this->db_name)." WHERE user_id = ?", $this->user_id); |
| | | $this->cache = null; |
| | | |
| | | $this->db->query("UPDATE ".get_table_name($this->db_name). |
| | | " SET del=1, changed=".$this->db->now(). |
| | | " WHERE user_id = ?", $this->user_id); |
| | | |
| | | return $this->db->affected_rows(); |
| | | } |
| | | |
| | |
| | | * @param string $type Message type [notice|confirm|error] |
| | | * @param array $vars Key-value pairs to be replaced in localized text |
| | | * @param boolean $override Override last set message |
| | | * @param int $timeout Message displaying time in seconds |
| | | * @uses self::command() |
| | | */ |
| | | public function show_message($message, $type='notice', $vars=null, $override=true) |
| | | public function show_message($message, $type='notice', $vars=null, $override=true, $timeout=0) |
| | | { |
| | | if ($override || !$this->message) { |
| | | $this->message = $message; |
| | | $msgtext = rcube_label_exists($message) ? rcube_label(array('name' => $message, 'vars' => $vars)) : $message; |
| | | $this->command('display_message', $msgtext, $type); |
| | | $this->command('display_message', $msgtext, $type, $timeout * 1000); |
| | | } |
| | | } |
| | | |
| | |
| | | /** |
| | | * Invoke display_message command |
| | | * |
| | | * @param string Message to display |
| | | * @param string Message type [notice|confirm|error] |
| | | * @param array Key-value pairs to be replaced in localized text |
| | | * @param boolean Override last set message |
| | | * @param string $message Message to display |
| | | * @param string $type Message type [notice|confirm|error] |
| | | * @param array $vars Key-value pairs to be replaced in localized text |
| | | * @param boolean $override Override last set message |
| | | * @param int $timeout Message display time in seconds |
| | | * @uses self::command() |
| | | */ |
| | | public function show_message($message, $type='notice', $vars=null, $override=true) |
| | | public function show_message($message, $type='notice', $vars=null, $override=true, $timeout=0) |
| | | { |
| | | if ($override || !$this->message) { |
| | | $this->message = $message; |
| | | $msgtext = rcube_label_exists($message) ? rcube_label(array('name' => $message, 'vars' => $vars)) : $message; |
| | | $this->command('display_message', $msgtext, $type); |
| | | $this->command('display_message', $msgtext, $type, $timeout * 1000); |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | // enable general commands |
| | | this.enable_command('logout', 'mail', 'addressbook', 'settings', 'save-pref', true); |
| | | this.enable_command('logout', 'mail', 'addressbook', 'settings', 'save-pref', 'undo', true); |
| | | |
| | | if (this.env.permaurl) |
| | | this.enable_command('permaurl', true); |
| | |
| | | |
| | | // show message |
| | | if (this.pending_message) |
| | | this.display_message(this.pending_message[0], this.pending_message[1]); |
| | | this.display_message(this.pending_message[0], this.pending_message[1], this.pending_message[2]); |
| | | |
| | | // map implicit containers |
| | | if (this.gui_objects.folderlist) |
| | |
| | | this.goto_url('settings/' + command); |
| | | break; |
| | | |
| | | case 'undo': |
| | | this.http_request('undo', '', this.display_message('', 'loading')); |
| | | break; |
| | | |
| | | // unified command call (command name == function name) |
| | | default: |
| | | var func = command.replace(/-/g, '_'); |
| | |
| | | var toffset = -moffset-boffset; |
| | | var li, div, pos, mouse, check, oldclass, |
| | | layerclass = 'draglayernormal'; |
| | | |
| | | |
| | | if (this.contact_list && this.contact_list.draglayer) |
| | | oldclass = this.contact_list.draglayer.attr('class'); |
| | | |
| | |
| | | if (elem._placeholder && (!$elem.val() || $elem.val() == elem._placeholder)) |
| | | $elem.addClass('placeholder').attr('spellcheck', false).val(elem._placeholder); |
| | | }; |
| | | |
| | | |
| | | // write to the document/window title |
| | | this.set_pagetitle = function(title) |
| | | { |
| | |
| | | }; |
| | | |
| | | // display a system message, list of types in common.css (below #message definition) |
| | | this.display_message = function(msg, type) |
| | | this.display_message = function(msg, type, timeout) |
| | | { |
| | | // pass command to parent window |
| | | if (this.is_framed()) |
| | | return parent.rcmail.display_message(msg, type); |
| | | return parent.rcmail.display_message(msg, type, timeout); |
| | | |
| | | if (!this.gui_objects.message) { |
| | | // save message in order to display after page loaded |
| | | if (type != 'loading') |
| | | this.pending_message = new Array(msg, type); |
| | | this.pending_message = new Array(msg, type, timeout); |
| | | return false; |
| | | } |
| | | |
| | | type = type ? type : 'notice'; |
| | | |
| | | var ref = this, |
| | | key = msg, |
| | | key = String(msg).replace(this.identifier_expr, '_'), |
| | | date = new Date(), |
| | | id = type + date.getTime(), |
| | | id = type + date.getTime(); |
| | | |
| | | if (!timeout) |
| | | timeout = this.message_time * (type == 'error' || type == 'warning' ? 2 : 1); |
| | | |
| | | |
| | | if (type == 'loading') { |
| | | key = 'loading'; |
| | | timeout = this.env.request_timeout * 1000; |
| | |
| | | $labels['sortby'] = 'Sort by'; |
| | | $labels['sortasc'] = 'Sort ascending'; |
| | | $labels['sortdesc'] = 'Sort descending'; |
| | | $labels['undo'] = 'Undo'; |
| | | |
| | | // units |
| | | $labels['B'] = 'B'; |
| | |
| | | $messages['internalerror'] = 'An internal error occured. Please try again'; |
| | | $messages['contactdelerror'] = 'Could not delete contact(s)'; |
| | | $messages['contactdeleted'] = 'Contact(s) deleted successfully'; |
| | | $messages['contactrestoreerror'] = 'Could not restore deleted contact(s)'; |
| | | $messages['contactrestored'] = 'Contact(s) restored successfully'; |
| | | $messages['groupdeleted'] = 'Group deleted successfully'; |
| | | $messages['grouprenamed'] = 'Group renamed successfully'; |
| | | $messages['groupcreated'] = 'Group created successfully'; |
| | |
| | | $messages['foldercreated'] = 'Folder created successfully'; |
| | | $messages['invalidimageformat'] = 'Not a valid image format'; |
| | | $messages['mispellingsfound'] = 'Spelling errors detected in the message'; |
| | | $messages['itemsdeleted'] = '$num item(s) has been deleted.'; |
| | | |
| | | ?> |
| | |
| | | $labels['importtarget'] = 'Dodaj nowe kontakty do książki adresowej:'; |
| | | $labels['grouprename'] = 'Zmień nazwę grupy'; |
| | | $labels['groupdelete'] = 'Usuń grupę'; |
| | | $labels['undo'] = 'Cofnij'; |
| | | |
| | | ?> |
| | |
| | | $messages['importconfirmskipped'] = '<b>Pominięto $skipped istniejących wpisów</b>'; |
| | | $messages['invalidimageformat'] = 'Niepoprawny format obrazka'; |
| | | $messages['mispellingsfound'] = 'Wykryto błędy pisowni w tej wiadomości'; |
| | | $messages['itemsdeleted'] = '$num elemenów zostało usuniętych.'; |
| | | $messages['contactrestoreerror'] = 'Przywracanie kontaktów nie powiodło się'; |
| | | $messages['contactrestored'] = 'Kontakt(y) zostały przywrócone'; |
| | | |
| | | ?> |
| | |
| | | $cids = rcmail_get_cids(); |
| | | $delcnt = 0; |
| | | |
| | | // remove previous deletes |
| | | $RCMAIL->session->remove('contact_undo'); |
| | | |
| | | foreach ($cids as $source => $cid) |
| | | { |
| | | $CONTACTS = rcmail_contact_source($source); |
| | |
| | | if (count($cids) == 1) { |
| | | $OUTPUT->show_message('contactdelerror', 'error'); |
| | | $OUTPUT->command('list_contacts'); |
| | | $OUTPUT->send(); |
| | | } |
| | | continue; |
| | | } |
| | |
| | | } |
| | | else { |
| | | $delcnt += $deleted; |
| | | |
| | | // store deleted contacts IDs in session for undelete |
| | | if ($CONTACTS->undelete) { |
| | | $_SESSION['contact_undo']['data'][$source] = $cid; |
| | | } |
| | | } |
| | | } |
| | | |
| | | $OUTPUT->show_message('contactdeleted', 'confirmation'); |
| | | |
| | | $page = isset($_SESSION['page']) ? $_SESSION['page'] : 1; |
| | | |
| | |
| | | $OUTPUT->set_env('pagecount', ceil($result->count / $CONFIG['pagesize'])); |
| | | $OUTPUT->command('set_rowcount', rcmail_get_rowcount_text($result)); |
| | | |
| | | if (!empty($_SESSION['contact_undo'])) { |
| | | $_SESSION['contact_undo']['ts'] = time(); |
| | | $msg = html::span(null, rcube_label(array('name' => 'itemsdeleted', 'vars' => array('num' => $deleted)))) |
| | | . ' ' . html::a(array('onclick' => JS_OBJECT_NAME.".command('undo', '', this)"), rcube_label('undo')); |
| | | |
| | | $OUTPUT->show_message($msg, 'confirmation', null, true, $RCMAIL->config->get('undo_timeout', 15)); |
| | | } |
| | | else { |
| | | $OUTPUT->show_message('contactdeleted', 'confirmation'); |
| | | } |
| | | |
| | | // add new rows from next page (if any) |
| | | if (!empty($records)) { |
| | | rcmail_js_contacts_list($records); |
| | |
| | | $CONTACTS = rcmail_contact_source($source, true); |
| | | } |
| | | |
| | | // remove undo information... |
| | | if ($undo = $_SESSION['contact_undo']) { |
| | | // ...after 30 seconds |
| | | if ($undo['ts'] < time() - 30) |
| | | $RCMAIL->session->remove('contact_undo'); |
| | | } |
| | | |
| | | // instantiate a contacts object according to the given source |
| | | function rcmail_contact_source($source=null, $init_env=false) |
New file |
| | |
| | | <?php |
| | | |
| | | /* |
| | | +-----------------------------------------------------------------------+ |
| | | | program/steps/addressbook/undo.inc | |
| | | | | |
| | | | This file is part of the Roundcube Webmail client | |
| | | | Copyright (C) 2011, Kolab Systems AG | |
| | | | Licensed under the GNU GPL | |
| | | | | |
| | | | PURPOSE: | |
| | | | Undelete contacts (CIDs) from last delete action | |
| | | | | |
| | | +-----------------------------------------------------------------------+ |
| | | | Author: Aleksander Machniak <machniak@kolabsys.com> | |
| | | +-----------------------------------------------------------------------+ |
| | | |
| | | $Id$ |
| | | |
| | | */ |
| | | |
| | | // process ajax requests only |
| | | if (!$OUTPUT->ajax_call) |
| | | return; |
| | | |
| | | $undo = $_SESSION['contact_undo']; |
| | | $delcnt = 0; |
| | | |
| | | foreach ((array)$undo['data'] as $source => $cid) |
| | | { |
| | | $CONTACTS = rcmail_contact_source($source); |
| | | |
| | | $plugin = $RCMAIL->plugins->exec_hook('contact_undelete', array( |
| | | 'id' => $cid, 'source' => $source)); |
| | | |
| | | $restored = !$plugin['abort'] ? $CONTACTS->undelete($cid) : $plugin['result']; |
| | | |
| | | if (!$restored) { |
| | | $OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'contactrestoreerror', 'error'); |
| | | $OUTPUT->command('list_contacts'); |
| | | $OUTPUT->send(); |
| | | } |
| | | else { |
| | | $delcnt += $restored; |
| | | } |
| | | } |
| | | |
| | | // update saved search after data changed |
| | | if ($delcnt && ($search_request = $_REQUEST['_search']) && isset($_SESSION['search'][$search_request])) { |
| | | $search = (array)$_SESSION['search'][$search_request]; |
| | | |
| | | foreach ($search as $s => $set) { |
| | | $source = $RCMAIL->get_address_book($s); |
| | | |
| | | // reset page |
| | | $source->set_page(1); |
| | | $source->set_pagesize(9999); |
| | | $source->set_search_set($set); |
| | | |
| | | // get records |
| | | $result = $source->list_records(array('name', 'email')); |
| | | |
| | | if (!$result->count) { |
| | | unset($search[$s]); |
| | | continue; |
| | | } |
| | | |
| | | while ($row = $result->next()) { |
| | | $row['sourceid'] = $s; |
| | | $key = $row['name'] . ':' . $row['sourceid']; |
| | | $records[$key] = $row; |
| | | } |
| | | unset($result); |
| | | |
| | | $search[$s] = $source->get_search_set(); |
| | | } |
| | | |
| | | $_SESSION['search'][$search_request] = $search; |
| | | } |
| | | |
| | | $RCMAIL->session->remove('contact_undo'); |
| | | |
| | | $OUTPUT->show_message('contactrestored', 'confirmation'); |
| | | $OUTPUT->command('list_contacts'); |
| | | |
| | | // send response |
| | | $OUTPUT->send(); |
| | |
| | | border: 1px solid #CCCCCC; |
| | | } |
| | | |
| | | #message a |
| | | { |
| | | cursor: pointer; |
| | | text-decoration: underline; |
| | | } |
| | | |
| | | .box |
| | | { |
| | | border: 1px solid #999; |
| | |
| | | <roundcube:button command="menu-save" id="listmenusave" type="input" class="button mainaction" label="save" /> |
| | | </div> |
| | | </div> |
| | | |
| | | <div id="undelete-message">11 item(s) has been deleted. <a href="#">Undo</a></div> |
| | | </body> |
| | | </html> |