alecpl
2011-07-07 7f5a849e7816e7b4c7b13a72d38a9c777632d7cd
- Added possibility to undo last contact delete operation


15 files modified
1 files added
225 ■■■■ changed files
CHANGELOG 1 ●●●● patch | view | raw | blame | history
program/include/rcmail.php 2 ●●● patch | view | raw | blame | history
program/include/rcube_addressbook.php 13 ●●●●● patch | view | raw | blame | history
program/include/rcube_contacts.php 34 ●●●●● patch | view | raw | blame | history
program/include/rcube_json_output.php 5 ●●●●● patch | view | raw | blame | history
program/include/rcube_template.php 13 ●●●● patch | view | raw | blame | history
program/js/app.js 26 ●●●●● patch | view | raw | blame | history
program/localization/en_US/labels.inc 1 ●●●● patch | view | raw | blame | history
program/localization/en_US/messages.inc 3 ●●●●● patch | view | raw | blame | history
program/localization/pl_PL/labels.inc 1 ●●●● patch | view | raw | blame | history
program/localization/pl_PL/messages.inc 3 ●●●●● patch | view | raw | blame | history
program/steps/addressbook/delete.inc 22 ●●●●● patch | view | raw | blame | history
program/steps/addressbook/func.inc 6 ●●●●● patch | view | raw | blame | history
program/steps/addressbook/undo.inc 87 ●●●●● patch | view | raw | blame | history
skins/default/common.css 6 ●●●●● patch | view | raw | blame | history
skins/default/templates/mail.html 2 ●●● patch | view | raw | blame | history
CHANGELOG
@@ -1,6 +1,7 @@
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)
program/include/rcmail.php
@@ -507,7 +507,7 @@
    $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;
program/include/rcube_addressbook.php
@@ -38,6 +38,7 @@
    public $primary_key;
    public $groups = false;
    public $readonly = true;
    public $undelete = false;
    public $ready = false;
    public $group_id = null;
    public $list_page = 1;
@@ -256,7 +257,17 @@
    }
    /**
     * 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()
    {
program/include/rcube_contacts.php
@@ -52,6 +52,7 @@
    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;
@@ -692,12 +693,43 @@
    /**
     * 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();
    }
program/include/rcube_json_output.php
@@ -164,14 +164,15 @@
     * @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);
        }
    }
program/include/rcube_template.php
@@ -238,18 +238,19 @@
    /**
     * 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);
        }
    }
program/js/app.js
@@ -164,7 +164,7 @@
    }
    // 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);
@@ -411,7 +411,7 @@
    // 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)
@@ -1046,6 +1046,10 @@
        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, '_');
@@ -1296,7 +1300,7 @@
      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');
@@ -4980,7 +4984,7 @@
    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)
  {
@@ -4989,27 +4993,29 @@
  };
  // 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;
program/localization/en_US/labels.inc
@@ -448,6 +448,7 @@
$labels['sortby'] = 'Sort by';
$labels['sortasc']  = 'Sort ascending';
$labels['sortdesc'] = 'Sort descending';
$labels['undo'] = 'Undo';
// units
$labels['B'] = 'B';
program/localization/en_US/messages.inc
@@ -128,6 +128,8 @@
$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';
@@ -142,5 +144,6 @@
$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.';
?>
program/localization/pl_PL/labels.inc
@@ -416,5 +416,6 @@
$labels['importtarget'] = 'Dodaj nowe kontakty do książki adresowej:';
$labels['grouprename'] = 'Zmień nazwę grupy';
$labels['groupdelete'] = 'Usuń grupę';
$labels['undo'] = 'Cofnij';
?>
program/localization/pl_PL/messages.inc
@@ -146,5 +146,8 @@
$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';
?>
program/steps/addressbook/delete.inc
@@ -26,6 +26,9 @@
$cids   = rcmail_get_cids();
$delcnt = 0;
// remove previous deletes
$RCMAIL->session->remove('contact_undo');
foreach ($cids as $source => $cid)
{
    $CONTACTS = rcmail_contact_source($source);
@@ -36,6 +39,7 @@
        if (count($cids) == 1) {
            $OUTPUT->show_message('contactdelerror', 'error');
            $OUTPUT->command('list_contacts');
            $OUTPUT->send();
        }
        continue;
    }
@@ -52,10 +56,13 @@
    }
    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;
@@ -135,6 +142,17 @@
$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);
program/steps/addressbook/func.inc
@@ -88,6 +88,12 @@
    $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)
program/steps/addressbook/undo.inc
New file
@@ -0,0 +1,87 @@
<?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();
skins/default/common.css
@@ -252,6 +252,12 @@
  border: 1px solid #CCCCCC;
}
#message a
{
  cursor: pointer;
  text-decoration: underline;
}
.box
{
  border: 1px solid #999;
skins/default/templates/mail.html
@@ -196,6 +196,6 @@
  <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>