Aleksander Machniak
2013-07-01 a45f9b7bf58475ccc812e819f159638403c00419
Contacts drag-n-drop default action is to move contacts (#1488751)
Added possibility to choose to move or copy contacts from drag-n-drop menu (#1488751)
Use consistent naming: 'moveto' -> 'move'
1 files added
18 files modified
602 ■■■■ changed files
CHANGELOG 2 ●●●●● patch | view | raw | blame | history
program/js/app.js 176 ●●●●● patch | view | raw | blame | history
program/localization/en_US/messages.inc 7 ●●●● patch | view | raw | blame | history
program/steps/addressbook/copy.inc 2 ●●● patch | view | raw | blame | history
program/steps/addressbook/delete.inc 38 ●●●●● patch | view | raw | blame | history
program/steps/addressbook/func.inc 50 ●●●●● patch | view | raw | blame | history
program/steps/addressbook/list.inc 44 ●●●● patch | view | raw | blame | history
program/steps/addressbook/move.inc 206 ●●●●● patch | view | raw | blame | history
program/steps/addressbook/undo.inc 25 ●●●●● patch | view | raw | blame | history
program/steps/mail/func.inc 2 ●●● patch | view | raw | blame | history
program/steps/mail/move_del.inc 4 ●●●● patch | view | raw | blame | history
skins/classic/functions.js 10 ●●●●● patch | view | raw | blame | history
skins/classic/includes/messagetoolbar.html 2 ●●● patch | view | raw | blame | history
skins/classic/templates/addressbook.html 7 ●●●●● patch | view | raw | blame | history
skins/classic/templates/mail.html 4 ●●●● patch | view | raw | blame | history
skins/larry/templates/addressbook.html 7 ●●●●● patch | view | raw | blame | history
skins/larry/templates/mail.html 2 ●●● patch | view | raw | blame | history
skins/larry/templates/message.html 2 ●●● patch | view | raw | blame | history
skins/larry/ui.js 12 ●●●● patch | view | raw | blame | history
CHANGELOG
@@ -1,6 +1,8 @@
CHANGELOG Roundcube Webmail
===========================
- Contacts drag-n-drop default action is to move contacts (#1488751)
- Added possibility to choose to move or copy contacts from drag-n-drop menu (#1488751)
- Fix messages list sorting with THREAD=REFS
- Fix Close link and remove About link on error pages (#1489109)
- Remove deprecated (in PHP 5.5) PREG /e modifier usage (#1489174)
program/js/app.js
@@ -229,7 +229,7 @@
        this.set_button_titles();
        this.env.message_commands = ['show', 'reply', 'reply-all', 'reply-list',
          'moveto', 'copy', 'delete', 'open', 'mark', 'edit', 'viewsource',
          'move', 'copy', 'delete', 'open', 'mark', 'edit', 'viewsource',
          'print', 'load-attachment', 'download-attachment', 'show-headers', 'hide-headers', 'download',
          'forward', 'forward-inline', 'forward-attachment', 'change-format'];
@@ -376,7 +376,7 @@
        }
        if (this.gui_objects.qsearchbox)
          this.enable_command('search', 'reset-search', 'moveto', true);
          this.enable_command('search', 'reset-search', true);
        break;
@@ -795,16 +795,18 @@
      // mail task commands
      case 'move':
      case 'moveto':
      case 'moveto': // deprecated
        if (this.task == 'mail')
          this.move_messages(props);
        else if (this.task == 'addressbook')
          this.copy_contact(null, props);
          this.move_contacts(props);
        break;
      case 'copy':
        if (this.task == 'mail')
          this.copy_messages(props);
        else if (this.task == 'addressbook')
          this.copy_contacts(props);
        break;
      case 'mark':
@@ -1350,7 +1352,7 @@
  this.drag_menu = function(e, target)
  {
    var modkey = rcube_event.get_modifier(e),
      menu = this.gui_objects.message_dragmenu;
      menu = this.gui_objects.dragmenu;
    if (menu && modkey == SHIFT_KEY && this.commands['copy']) {
      var pos = rcube_event.get_mouse_pos(e);
@@ -1364,7 +1366,7 @@
  this.drag_menu_action = function(action)
  {
    var menu = this.gui_objects.message_dragmenu;
    var menu = this.gui_objects.dragmenu;
    if (menu) {
      $(menu).hide();
    }
@@ -1479,8 +1481,12 @@
      list.draglayer.hide();
      this.drag_end(e);
      if (!this.drag_menu(e, target))
        this.command('moveto', target);
      if (this.contact_list) {
        if (!this.contacts_drag_menu(e, target))
          this.command('move', target);
      }
      else if (!this.drag_menu(e, target))
        this.command('move', target);
    }
    // reset 'pressed' buttons
@@ -1527,7 +1533,7 @@
      }
    }
    // Multi-message commands
    this.enable_command('delete', 'moveto', 'copy', 'mark', 'forward', 'forward-attachment', list.selection.length > 0);
    this.enable_command('delete', 'move', 'copy', 'mark', 'forward', 'forward-attachment', list.selection.length > 0);
    // reset all-pages-selection
    if (selected || (list.selection.length && list.selection.length != list.rowcount))
@@ -1630,28 +1636,28 @@
  this.check_droptarget = function(id)
  {
    if (this.task == 'mail')
      return (this.env.mailboxes[id] && this.env.mailboxes[id].id != this.env.mailbox && !this.env.mailboxes[id].virtual) ? 1 : 0;
    switch (this.task) {
      case 'mail':
        return (this.env.mailboxes[id] && this.env.mailboxes[id].id != this.env.mailbox && !this.env.mailboxes[id].virtual) ? 1 : 0;
    if (this.task == 'settings')
      return id != this.env.mailbox ? 1 : 0;
      case 'settings':
        return id != this.env.mailbox ? 1 : 0;
    if (this.task == 'addressbook') {
      if (id != this.env.source && this.env.contactfolders[id]) {
        // droptarget is a group - contact add to group action
        if (this.env.contactfolders[id].type == 'group') {
          var target_abook = this.env.contactfolders[id].source;
          if (this.env.contactfolders[id].id != this.env.group && !this.env.contactfolders[target_abook].readonly) {
            // search result may contain contacts from many sources
            return (this.env.selection_sources.length > 1 || $.inArray(target_abook, this.env.selection_sources) == -1) ? 2 : 1;
      case 'addressbook':
        var target;
        if (id != this.env.source && (target = this.env.contactfolders[id])) {
          // droptarget is a group
          if (target.type == 'group') {
            if (target.id != this.env.group && !this.env.contactfolders[target.source].readonly) {
              var is_other = this.env.selection_sources.length > 1 || $.inArray(target.source, this.env.selection_sources) == -1;
              return !is_other || this.commands.move ? 1 : 2;
            }
          }
          // droptarget is a (writable) addressbook and it's not the source
          else if (!target.readonly && (this.env.selection_sources.length > 1 || $.inArray(id, this.env.selection_sources) == -1)) {
            return this.commands.move ? 1 : 2;
          }
        }
        // droptarget is a (writable) addressbook - contact copy action
        else if (!this.env.contactfolders[id].readonly) {
          // search result may contain contacts from many sources
          return (this.env.selection_sources.length > 1 || $.inArray(id, this.env.selection_sources) == -1) ? 2 : 0;
        }
      }
    }
    return 0;
@@ -2593,7 +2599,7 @@
    // Hide message command buttons until a message is selected
    this.enable_command(this.env.message_commands, false);
    this._with_selected_messages('moveto', post_data, lock);
    this._with_selected_messages('move', post_data, lock);
  };
  // delete selected messages from the current mailbox
@@ -2652,7 +2658,7 @@
    this._with_selected_messages('delete', post_data);
  };
  // Send a specifc moveto/delete request with UIDs of all selected messages
  // Send a specifc move/delete request with UIDs of all selected messages
  // @private
  this._with_selected_messages = function(action, post_data, lock)
  {
@@ -2694,7 +2700,7 @@
      this.delete_excessive_thread_rows();
    if (!lock) {
      msg = action == 'moveto' ? 'movingmessage' : 'deletingmessage';
      msg = action == 'move' ? 'movingmessage' : 'deletingmessage';
      lock = this.display_message(this.get_label(msg), 'loading');
    }
@@ -4186,9 +4192,9 @@
    // thend we can enable the group-remove-selected command
    this.enable_command('group-remove-selected', this.env.group && list.selection.length > 0 && writable);
    this.enable_command('compose', this.env.group || list.selection.length > 0);
    this.enable_command('export-selected', list.selection.length > 0);
    this.enable_command('export-selected', 'copy', list.selection.length > 0);
    this.enable_command('edit', id && writable);
    this.enable_command('delete', list.selection.length > 0 && writable);
    this.enable_command('delete', 'move', list.selection.length > 0 && writable);
    return false;
  };
@@ -4296,7 +4302,7 @@
    this.contact_list.data = {};
    this.contact_list.clear(true);
    this.show_contentframe(false);
    this.enable_command('delete', false);
    this.enable_command('delete', 'move', 'copy', false);
    this.enable_command('compose', this.env.group ? true : false);
  };
@@ -4366,14 +4372,38 @@
    this.http_post('group-'+what+'members', post_data, lock);
  };
  // copy a contact to the specified target (group or directory)
  this.copy_contact = function(cid, to)
  this.contacts_drag_menu = function(e, to)
  {
    var dest = to.type == 'group' ? to.source : to.id,
      source = this.env.source;
    if (!this.env.address_sources[dest] || this.env.address_sources[dest].readonly)
      return true;
    // search result may contain contacts from many sources, but if there is only one...
    if (source == '' && this.env.selection_sources.length == 1)
      source = this.env.selection_sources[0];
    if (to.type == 'group' && dest == source) {
      var cid = this.contact_list.get_selection().join(',');
      this.group_member_change('add', cid, dest, to.id);
      return true;
    }
    // move action is not possible, "redirect" to copy if menu wasn't requested
    else if (!this.commands.move && rcube_event.get_modifier(e) != SHIFT_KEY) {
      this.copy_contacts(to);
      return true;
    }
    return this.drag_menu(e, to);
  };
  // copy contact(s) to the specified target (group or directory)
  this.copy_contacts = function(to)
  {
    var n, dest = to.type == 'group' ? to.source : to.id,
      source = this.env.source,
      group = this.env.group ? this.env.group : '';
    if (!cid)
      group = this.env.group ? this.env.group : '',
      cid = this.contact_list.get_selection().join(',');
    if (!cid || !this.env.address_sources[dest] || this.env.address_sources[dest].readonly)
@@ -4386,13 +4416,12 @@
    // tagret is a group
    if (to.type == 'group') {
      if (dest == source)
        this.group_member_change('add', cid, dest, to.id);
      else {
        var lock = this.display_message(this.get_label('copyingcontact'), 'loading'),
          post_data = {_cid: cid, _source: this.env.source, _to: dest, _togid: to.id, _gid: group};
        return;
        this.http_post('copy', post_data, lock);
      }
      var lock = this.display_message(this.get_label('copyingcontact'), 'loading'),
        post_data = {_cid: cid, _source: this.env.source, _to: dest, _togid: to.id, _gid: group};
      this.http_post('copy', post_data, lock);
    }
    // target is an addressbook
    else if (to.id != source) {
@@ -4403,19 +4432,53 @@
    }
  };
  this.delete_contacts = function()
  // move contact(s) to the specified target (group or directory)
  this.move_contacts = function(to)
  {
    var selection = this.contact_list.get_selection(),
      undelete = this.env.source && this.env.address_sources[this.env.source].undelete;
    var dest = to.type == 'group' ? to.source : to.id,
      source = this.env.source,
      group = this.env.group ? this.env.group : '';
    // exit if no mailbox specified or if selection is empty
    if (!(selection.length || this.env.cid) || (!undelete && !confirm(this.get_label('deletecontactconfirm'))))
    if (!this.env.address_sources[dest] || this.env.address_sources[dest].readonly)
      return;
    var id, n, a_cids = [],
      post_data = {_source: this.env.source, _from: (this.env.action ? this.env.action : '')},
      lock = this.display_message(this.get_label('contactdeleting'), 'loading');
    // search result may contain contacts from many sources, but if there is only one...
    if (source == '' && this.env.selection_sources.length == 1)
      source = this.env.selection_sources[0];
    if (to.type == 'group') {
      if (dest == source)
        return;
      this._with_selected_contacts('move', {_to: dest, _togid: to.id});
    }
    // target is an addressbook
    else if (to.id != source)
      this._with_selected_contacts('move', {_to: to.id});
  };
  // delete contact(s)
  this.delete_contacts = function()
  {
    var undelete = this.env.source && this.env.address_sources[this.env.source].undelete;
    if (!undelete && !confirm(this.get_label('deletecontactconfirm')))
      return;
    return this._with_selected_contacts('delete');
  };
  this._with_selected_contacts = function(action, post_data)
  {
    var selection = this.contact_list ? this.contact_list.get_selection() : [];
    // exit if no mailbox specified or if selection is empty
    if (!selection.length && !this.env.cid)
      return;
    var n, a_cids = [],
      label = action == 'delete' ? 'contactdeleting' : 'movingcontact',
      lock = this.display_message(this.get_label(label), 'loading');
    if (this.env.cid)
      a_cids.push(this.env.cid);
    else {
@@ -4430,6 +4493,11 @@
        this.show_contentframe(false);
    }
    if (!post_data)
      post_data = {};
    post_data._source = this.env.source;
    post_data._from = this.env.action;
    post_data._cid = a_cids.join(',');
    if (this.env.group)
@@ -4440,7 +4508,7 @@
      post_data._search = this.env.search_request;
    // send request to server
    this.http_post('delete', post_data, lock)
    this.http_post(action, post_data, lock)
    return true;
  };
@@ -6315,7 +6383,7 @@
          this.enable_command('export-selected', false);
        }
      case 'moveto':
      case 'move':
        if (this.env.action == 'show') {
          // re-enable commands on move/delete error
          this.enable_command(this.env.message_commands, true);
program/localization/en_US/messages.inc
@@ -101,13 +101,16 @@
$messages['messageopenerror'] = 'Could not load message from server.';
$messages['fileuploaderror'] = 'File upload failed.';
$messages['filesizeerror'] = 'The uploaded file exceeds the maximum size of $size.';
$messages['copysuccess'] = 'Successfully copied $nr addresses.';
$messages['copyerror'] = 'Could not copy any addresses.';
$messages['copysuccess'] = 'Successfully copied $nr contacts.';
$messages['movesuccess'] = 'Successfully moved $nr contacts.';
$messages['copyerror'] = 'Could not copy any contacts.';
$messages['moveerror'] = 'Could not move any contacts.';
$messages['sourceisreadonly'] = 'This address source is read only.';
$messages['errorsavingcontact'] = 'Could not save the contact address.';
$messages['movingmessage'] = 'Moving message(s)...';
$messages['copyingmessage'] = 'Copying message(s)...';
$messages['copyingcontact'] = 'Copying contact(s)...';
$messages['movingcontact'] = 'Moving contact(s)...';
$messages['deletingmessage'] = 'Deleting message(s)...';
$messages['markingmessage'] = 'Marking message(s)...';
$messages['addingmember'] = 'Adding contact(s) to the group...';
program/steps/addressbook/copy.inc
@@ -118,7 +118,7 @@
    }
}
if ($success == 0)
if (!$success)
    $OUTPUT->show_message($errormsg, 'error');
else
    $OUTPUT->show_message('copysuccess', 'notice', array('nr' => $success));
program/steps/addressbook/delete.inc
@@ -68,48 +68,14 @@
$page = isset($_SESSION['page']) ? $_SESSION['page'] : 1;
// update saved search after data changed
if (($search_request = $_REQUEST['_search']) && isset($_SESSION['search'][$search_request])) {
    $sort_col = $RCMAIL->config->get('addressbook_sort_col', 'name');
    $afields = $RCMAIL->config->get('contactlist_fields');
    $search  = (array)$_SESSION['search'][$search_request];
    $records = array();
    // Get records from all sources (refresh search)
    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($afields);
        if (!$result->count) {
            unset($search[$s]);
            continue;
        }
        while ($row = $result->next()) {
            $row['sourceid'] = $s;
            $key = rcube_addressbook::compose_contact_key($row, $sort_col);
            $records[$key] = $row;
        }
        unset($result);
        $search[$s] = $source->get_search_set();
    }
    $_SESSION['search'][$search_request] = $search;
if (($records = rcmail_search_update(true)) !== false) {
    // create resultset object
    $count  = count($records);
    $first  = ($page-1) * $PAGE_SIZE;
    $result = new rcube_result_set($count, $first);
    $pages  = ceil((count($records) + $delcnt) / $PAGE_SIZE);
    // get records from the next page to add to the list
    $pages = ceil((count($records) + $delcnt) / $PAGE_SIZE);
    if ($_GET['_from'] != 'show' && $pages > 1 && $page < $pages) {
        // sort the records
        ksort($records, SORT_LOCALE_STRING);
program/steps/addressbook/func.inc
@@ -322,7 +322,7 @@
    $OUTPUT->include_script('list.js');
    // add some labels to client
    $OUTPUT->add_label('deletecontactconfirm', 'copyingcontact', 'contactdeleting');
    $OUTPUT->add_label('deletecontactconfirm', 'copyingcontact', 'movingcontact', 'contactdeleting');
    return $out;
}
@@ -779,6 +779,54 @@
    return format_date($val, $RCMAIL->config->get('date_format', 'Y-m-d'), false);
}
/**
 * Updates saved search after data changed
 */
function rcmail_search_update($return = false)
{
    global $RCMAIL;
    if (($search_request = $_REQUEST['_search']) && isset($_SESSION['search'][$search_request])) {
        $search   = (array)$_SESSION['search'][$search_request];
        $sort_col = $RCMAIL->config->get('addressbook_sort_col', 'name');
        $afields  = $return ? $RCMAIL->config->get('contactlist_fields') : array('name', 'email');
        $records  = array();
        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($afields);
            if (!$result->count) {
                unset($search[$s]);
                continue;
            }
            if ($return) {
                while ($row = $result->next()) {
                    $row['sourceid'] = $s;
                    $key = rcube_addressbook::compose_contact_key($row, $sort_col);
                    $records[$key] = $row;
                }
                unset($result);
            }
            $search[$s] = $source->get_search_set();
        }
        $_SESSION['search'][$search_request] = $search;
        return $records;
    }
    return false;
}
/**
 * Returns contact ID(s) and source(s) from GET/POST data
program/steps/addressbook/list.inc
@@ -19,47 +19,20 @@
 +-----------------------------------------------------------------------+
*/
$afields = $RCMAIL->config->get('contactlist_fields');
if (!empty($_GET['_page']))
    $page = intval($_GET['_page']);
else
    $page = !empty($_SESSION['page']) ? $_SESSION['page'] : 1;
$_SESSION['page'] = $page;
// Use search result
if (!empty($_REQUEST['_search']) && isset($_SESSION['search'][$_REQUEST['_search']]))
{
    $search  = (array)$_SESSION['search'][$_REQUEST['_search']];
    $records = array();
    if (!empty($_GET['_page']))
        $page = intval($_GET['_page']);
    else
        $page = isset($_SESSION['page']) ? $_SESSION['page'] : 1;
    $_SESSION['page'] = $page;
    $sort_col = $RCMAIL->config->get('addressbook_sort_col', 'name');
    // Get records from all sources
    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($afields);
        while ($row = $result->next()) {
            $row['sourceid'] = $s;
            $key = rcube_addressbook::compose_contact_key($row, $sort_col);
            $records[$key] = $row;
        }
        unset($result);
    }
if (($records = rcmail_search_update(true)) !== false) {
    // sort the records
    ksort($records, SORT_LOCALE_STRING);
    // create resultset object
    $count    = count($records);
    $count  = count($records);
    $first  = ($page-1) * $PAGE_SIZE;
    $result = new rcube_result_set($count, $first);
@@ -72,6 +45,7 @@
}
// List selected directory
else {
    $afields  = $RCMAIL->config->get('contactlist_fields');
    $CONTACTS = rcmail_contact_source(null, true);
    // get contacts for this user
program/steps/addressbook/move.inc
New file
@@ -0,0 +1,206 @@
<?php
/*
 +-----------------------------------------------------------------------+
 | program/steps/addressbook/move.inc                                    |
 |                                                                       |
 | This file is part of the Roundcube Webmail client                     |
 | Copyright (C) 2007-2013, The Roundcube Dev Team                       |
 |                                                                       |
 | Licensed under the GNU General Public License version 3 or            |
 | any later version with exceptions for skins & plugins.                |
 | See the README file for a full license statement.                     |
 |                                                                       |
 | PURPOSE:                                                              |
 |   Move a contact record from one direcotry to another                 |
 +-----------------------------------------------------------------------+
 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
 | Author: Aleksander Machniak <alec@alec.pl>                            |
 +-----------------------------------------------------------------------+
*/
// only process ajax requests
if (!$OUTPUT->ajax_call) {
    return;
}
$cids         = rcmail_get_cids();
$target       = get_input_value('_to', RCUBE_INPUT_POST);
$target_group = get_input_value('_togid', RCUBE_INPUT_POST);
$all      = 0;
$deleted  = 0;
$success  = 0;
$errormsg = 'moveerror';
$maxnum   = $RCMAIL->config->get('max_group_members', 0);
$page     = !empty($_SESSION['page']) ? $_SESSION['page'] : 1;
foreach ($cids as $source => $source_cids) {
    // Something wrong, target not specified
    if (!strlen($target)) {
        break;
    }
    // It maight happen when moving records from search result
    // Do nothing, go to next source
    if ((string)$target === (string)$source) {
        continue;
    }
    $CONTACTS = $RCMAIL->get_address_book($source);
    $TARGET   = $RCMAIL->get_address_book($target);
    if (!$TARGET || !$TARGET->ready || $TARGET->readonly) {
        break;
    }
    if (!$CONTACTS || !$CONTACTS->ready || $CONTACTS->readonly) {
        continue;
    }
    $ids = array();
    foreach ($source_cids as $idx => $cid) {
        $a_record = $CONTACTS->get_record($cid, true);
        // avoid moving groups
        if ($a_record['_type'] == 'group') {
            unset($source_cids[$idx]);
            continue;
        }
        // Check if contact exists, if so, we'll need it's ID
        // Note: Some addressbooks allows empty email address field
        if (!empty($a_record['email']))
            $result = $TARGET->search('email', $a_record['email'], 1, true, true);
        else if (!empty($a_record['name']))
            $result = $TARGET->search('name', $a_record['name'], 1, true, true);
        else
            $result = new rcube_result_set();
        // insert contact record
        if (!$result->count) {
            $plugin = $RCMAIL->plugins->exec_hook('contact_create', array(
                'record' => $a_record, 'source' => $target, 'group' => $target_group));
            if (!$plugin['abort']) {
                if ($insert_id = $TARGET->insert($plugin['record'], false)) {
                    $ids[] = $insert_id;
                    $success++;
                }
            }
            else if ($plugin['result']) {
                $ids = array_merge($ids, $plugin['result']);
                $success++;
            }
        }
        else {
            $record = $result->first();
            $ids[] = $record['ID'];
            $errormsg = empty($a_record['email']) ? 'contactnameexists' : 'contactexists';
        }
    }
    // remove source contacts
    if ($success && !empty($source_cids)) {
        $all   += count($source_cids);
        $plugin = $RCMAIL->plugins->exec_hook('contact_delete', array(
            'id' => $source_cids, 'source' => $source));
        $del_status = !$plugin['abort'] ? $CONTACTS->delete($source_cids) : $plugin['result'];
        if ($del_status) {
            $deleted += $del_status;
        }
    }
    // assign to group
    if ($target_group && $TARGET->groups && !empty($ids)) {
        $plugin = $RCMAIL->plugins->exec_hook('group_addmembers', array(
            'group_id' => $target_group, 'ids' => $ids, 'source' => $target));
        if (!$plugin['abort']) {
            $TARGET->reset();
            $TARGET->set_group($target_group);
            if ($maxnum && ($TARGET->count()->count + count($plugin['ids']) > $maxnum)) {
                $OUTPUT->show_message('maxgroupmembersreached', 'warning', array('max' => $maxnum));
                $OUTPUT->send();
            }
            if (($cnt = $TARGET->add_to_group($target_group, $plugin['ids'])) && $cnt > $success)
                $success = $cnt;
        }
        else if ($plugin['result']) {
            $success = $plugin['result'];
        }
        $errormsg = $plugin['message'] ? $plugin['message'] : 'moveerror';
    }
}
if (!$deleted || $deleted != $all) {
    // update saved search after data changed
    if ($deleted) {
        rcmail_search_update();
    }
    $OUTPUT->command('list_contacts');
}
else {
    // update saved search after data changed
    if (($records = rcmail_search_update(true)) !== false) {
        // create resultset object
        $count  = count($records);
        $first  = ($page-1) * $PAGE_SIZE;
        $result = new rcube_result_set($count, $first);
        $pages  = ceil((count($records) + $delcnt) / $PAGE_SIZE);
        // get records from the next page to add to the list
        if ($_GET['_from'] != 'show' && $pages > 1 && $page < $pages) {
            // sort the records
            ksort($records, SORT_LOCALE_STRING);
            $first += $PAGE_SIZE;
            // create resultset object
            $res = new rcube_result_set($count, $first - $deleted);
            if ($PAGE_SIZE < $count) {
                $records = array_slice($records, $first - $deleted, $deleted);
            }
            $res->records = array_values($records);
            $records = $res;
        }
        else {
            unset($records);
        }
    }
    else {
        // count contacts for this user
        $result = $CONTACTS->count();
        // get records from the next page to add to the list
        $pages = ceil(($result->count + $deleted) / $PAGE_SIZE);
        if ($_GET['_from'] != 'show' && $pages > 1 && $page < $pages) {
            $CONTACTS->set_page($page);
            $records = $CONTACTS->list_records(null, -$deleted);
        }
    }
    // update message count display
    $OUTPUT->set_env('pagecount', ceil($result->count / $PAGE_SIZE));
    $OUTPUT->command('set_rowcount', rcmail_get_rowcount_text($result));
    // add new rows from next page (if any)
    if (!empty($records)) {
        rcmail_js_contacts_list($records);
    }
}
if (!$success)
    $OUTPUT->show_message($errormsg, 'error');
else
    $OUTPUT->show_message('movesuccess', 'notice', array('nr' => $success));
// send response
$OUTPUT->send();
program/steps/addressbook/undo.inc
@@ -46,30 +46,7 @@
}
// 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;
        }
        $search[$s] = $source->get_search_set();
    }
    $_SESSION['search'][$search_request] = $search;
}
rcmail_search_update();
$RCMAIL->session->remove('contact_undo');
program/steps/mail/func.inc
@@ -149,7 +149,7 @@
    'refresh' => 'check_recent.inc',
    'preview' => 'show.inc',
    'print'   => 'show.inc',
    'moveto'  => 'move_del.inc',
    'move'    => 'move_del.inc',
    'delete'  => 'move_del.inc',
    'send'    => 'sendmail.inc',
    'expunge' => 'folders.inc',
program/steps/mail/move_del.inc
@@ -29,7 +29,7 @@
$old_pages = ceil($old_count / $RCMAIL->storage->get_pagesize());
// move messages
if ($RCMAIL->action == 'moveto' && !empty($_POST['_uid']) && strlen($_POST['_target_mbox'])) {
if ($RCMAIL->action == 'move' && !empty($_POST['_uid']) && strlen($_POST['_target_mbox'])) {
    $count  = sizeof(explode(',', ($uids = get_input_value('_uid', RCUBE_INPUT_POST))));
    $target = get_input_value('_target_mbox', RCUBE_INPUT_POST, true);
    $mbox   = get_input_value('_mbox', RCUBE_INPUT_POST, true);
@@ -126,7 +126,7 @@
    rcmail_set_unseen_count($mbox, $unseen_count);
  }
  if ($RCMAIL->action == 'moveto' && strlen($target)) {
  if ($RCMAIL->action == 'move' && strlen($target)) {
    rcmail_send_unread_count($target, true);
  }
skins/classic/functions.js
@@ -94,7 +94,7 @@
    messagemenu:    {id:'messagemenu'},
    attachmentmenu: {id:'attachmentmenu'},
    listmenu:       {id:'listmenu', editable:1},
    dragmessagemenu:{id:'dragmessagemenu', sticky:1},
    dragmenu:       {id:'dragmenu', sticky:1},
    groupmenu:      {id:'groupoptionsmenu', above:1},
    mailboxmenu:    {id:'mailboxoptionsmenu', above:1},
    composemenu:    {id:'composeoptionsmenu', editable:1, overlap:1},
@@ -162,9 +162,9 @@
  }
},
dragmessagemenu: function(show)
dragmenu: function(show)
{
  this.popups.dragmessagemenu.obj[show?'show':'hide']();
  this.popups.dragmenu.obj[show?'show':'hide']();
},
forwardmenu: function(show)
@@ -960,7 +960,7 @@
    rcmail.addEventListener('menu-save', 'menu_save', rcmail_ui);
    rcmail.addEventListener('aftersend-attachment', 'uploadmenu', rcmail_ui);
    rcmail.addEventListener('aftertoggle-editor', 'resize_compose_body_ev', rcmail_ui);
    rcmail.gui_object('message_dragmenu', 'dragmessagemenu');
    rcmail.gui_object('dragmenu', 'dragmenu');
    if (rcmail.gui_objects.mailboxlist) {
      rcmail.addEventListener('responseaftermark', rcube_render_mailboxlist);
@@ -985,6 +985,8 @@
    if (rcmail.gui_objects.folderlist)
      new rcmail_scroller('#directorylist-content', '#directorylist-title', '#directorylist-footer');
    rcmail.gui_object('dragmenu', 'dragmenu');
  }
  else if (rcmail.env.task == 'settings') {
    if (rcmail.gui_objects.subscriptionlist)
skins/classic/includes/messagetoolbar.html
@@ -21,7 +21,7 @@
<roundcube:button name="markmenulink" id="markmenulink" type="link" class="button markmessage" title="markmessages" onclick="rcmail_ui.show_popup('markmenu');return false" content=" " />
<roundcube:button name="messagemenulink" id="messagemenulink" type="link" class="button messagemenu" title="moreactions" onclick="rcmail_ui.show_popup('messagemenu');return false" content=" " />
<roundcube:if condition="template:name == 'message'" />
<roundcube:object name="mailboxlist" type="select" noSelection="moveto" maxlength="25" onchange="rcmail.command('moveto', this.options[this.selectedIndex].value)" class="mboxlist" folder_filter="mail" />
<roundcube:object name="mailboxlist" type="select" noSelection="move" maxlength="25" onchange="rcmail.command('move', this.options[this.selectedIndex].value)" class="mboxlist" folder_filter="mail" />
<roundcube:endif />
</div>
skins/classic/templates/addressbook.html
@@ -116,5 +116,12 @@
  </ul>
</div>
<div id="dragmenu" class="popupmenu">
  <ul>
    <li><roundcube:button command="move" onclick="return rcmail.drag_menu_action('move')" label="move" classAct="active" /></li>
    <li><roundcube:button command="copy" onclick="return rcmail.drag_menu_action('copy')" label="copy" classAct="active" /></li>
  </ul>
</div>
</body>
</html>
skins/classic/templates/mail.html
@@ -130,9 +130,9 @@
<roundcube:button command="reset-search" id="searchreset" image="/images/icons/reset.gif" title="resetsearch" width="13" height="13" />
</div>
<div id="dragmessagemenu" class="popupmenu">
<div id="dragmenu" class="popupmenu">
  <ul>
    <li><roundcube:button command="moveto" onclick="return rcmail.drag_menu_action('moveto')" label="move" classAct="active" /></li>
    <li><roundcube:button command="move" onclick="return rcmail.drag_menu_action('move')" label="move" classAct="active" /></li>
    <li><roundcube:button command="copy" onclick="return rcmail.drag_menu_action('copy')" label="copy" classAct="active" /></li>
  </ul>
</div>
skins/larry/templates/addressbook.html
@@ -106,6 +106,13 @@
    </ul>
</div>
<div id="dragcontactmenu" class="popupmenu">
    <ul class="toolbarmenu">
        <li><roundcube:button command="move" onclick="return rcmail.drag_menu_action('move')" label="move" classAct="active" /></li>
        <li><roundcube:button command="copy" onclick="return rcmail.drag_menu_action('copy')" label="copy" classAct="active" /></li>
    </ul>
</div>
<roundcube:include file="/includes/footer.html" />
</body>
skins/larry/templates/mail.html
@@ -140,7 +140,7 @@
<div id="dragmessagemenu" class="popupmenu">
    <ul class="toolbarmenu">
        <li><roundcube:button command="moveto" onclick="return rcmail.drag_menu_action('moveto')" label="move" classAct="active" /></li>
        <li><roundcube:button command="move" onclick="return rcmail.drag_menu_action('move')" label="move" classAct="active" /></li>
        <li><roundcube:button command="copy" onclick="return rcmail.drag_menu_action('copy')" label="copy" classAct="active" /></li>
    </ul>
</div>
skins/larry/templates/message.html
@@ -17,7 +17,7 @@
<roundcube:endif />
    <roundcube:include file="/includes/mailtoolbar.html" />
    <div class="toolbarselect">
        <roundcube:object name="mailboxlist" type="select" noSelection="moveto" maxlength="25" onchange="rcmail.command('moveto', this.options[this.selectedIndex].value)" class="mailboxlist decorated" folder_filter="mail" />
        <roundcube:object name="mailboxlist" type="select" noSelection="move" maxlength="25" onchange="rcmail.command('move', this.options[this.selectedIndex].value)" class="mailboxlist decorated" folder_filter="mail" />
    </div>
</div>
skins/larry/ui.js
@@ -19,7 +19,7 @@
    searchmenu:         { editable:1, callback:searchmenu },
    attachmentmenu:     { },
    listoptions:        { editable:1 },
    dragmessagemenu:    { sticky:1 },
    dragmenu:           { sticky:1 },
    groupmenu:          { above:1 },
    mailboxmenu:        { above:1 },
    spellmenu:          { callback: spellmenu },
@@ -90,8 +90,8 @@
      var dragmenu = $('#dragmessagemenu');
      if (dragmenu.length) {
        rcmail.gui_object('message_dragmenu', 'dragmessagemenu');
        popups.dragmessagemenu = dragmenu;
        rcmail.gui_object('dragmenu', 'dragmessagemenu');
        popups.dragmenu = dragmenu;
      }
      if (rcmail.env.action == 'show' || rcmail.env.action == 'preview') {
@@ -206,6 +206,12 @@
        new rcube_scroller('#directorylist-content', '#directorylist-header', '#directorylist-footer');
      }
      var dragmenu = $('#dragcontactmenu');
      if (dragmenu.length) {
        rcmail.gui_object('dragmenu', 'dragcontactmenu');
        popups.dragmenu = dragmenu;
      }
    }
    // turn a group of fieldsets into tabs