From a45f9b7bf58475ccc812e819f159638403c00419 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Mon, 01 Jul 2013 04:22:14 -0400
Subject: [PATCH] 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'

---
 program/steps/addressbook/list.inc         |   44 ---
 skins/classic/templates/addressbook.html   |    7 
 CHANGELOG                                  |    2 
 program/steps/addressbook/move.inc         |  206 ++++++++++++++++++
 program/steps/addressbook/undo.inc         |   25 --
 skins/larry/templates/addressbook.html     |    7 
 skins/larry/templates/message.html         |    2 
 program/steps/addressbook/func.inc         |   50 ++++
 program/steps/addressbook/delete.inc       |   38 ---
 program/steps/mail/move_del.inc            |    4 
 skins/larry/templates/mail.html            |    2 
 skins/classic/templates/mail.html          |    4 
 skins/classic/functions.js                 |   10 
 program/localization/en_US/messages.inc    |    7 
 program/steps/mail/func.inc                |    2 
 program/js/app.js                          |  176 +++++++++++----
 program/steps/addressbook/copy.inc         |    2 
 skins/classic/includes/messagetoolbar.html |    2 
 skins/larry/ui.js                          |   12 
 19 files changed, 434 insertions(+), 168 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 67b2986..14dc4c7 100644
--- a/CHANGELOG
+++ b/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)
diff --git a/program/js/app.js b/program/js/app.js
index c923f68..917fd8a 100644
--- a/program/js/app.js
+++ b/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);
diff --git a/program/localization/en_US/messages.inc b/program/localization/en_US/messages.inc
index 16f4c67..47b0f79 100644
--- a/program/localization/en_US/messages.inc
+++ b/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...';
diff --git a/program/steps/addressbook/copy.inc b/program/steps/addressbook/copy.inc
index 59b4ffc..82917e2 100644
--- a/program/steps/addressbook/copy.inc
+++ b/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));
diff --git a/program/steps/addressbook/delete.inc b/program/steps/addressbook/delete.inc
index 5611858..3bb2ef5 100644
--- a/program/steps/addressbook/delete.inc
+++ b/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);
diff --git a/program/steps/addressbook/func.inc b/program/steps/addressbook/func.inc
index c7f7fb4..8ec581f 100644
--- a/program/steps/addressbook/func.inc
+++ b/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
diff --git a/program/steps/addressbook/list.inc b/program/steps/addressbook/list.inc
index 6f3a3e0..aca58d2 100644
--- a/program/steps/addressbook/list.inc
+++ b/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
diff --git a/program/steps/addressbook/move.inc b/program/steps/addressbook/move.inc
new file mode 100644
index 0000000..42180d7
--- /dev/null
+++ b/program/steps/addressbook/move.inc
@@ -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();
diff --git a/program/steps/addressbook/undo.inc b/program/steps/addressbook/undo.inc
index 9c17114..c23bd1c 100644
--- a/program/steps/addressbook/undo.inc
+++ b/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');
 
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index 07c315e..4546506 100644
--- a/program/steps/mail/func.inc
+++ b/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',
diff --git a/program/steps/mail/move_del.inc b/program/steps/mail/move_del.inc
index e21ba2c..f15cd24 100644
--- a/program/steps/mail/move_del.inc
+++ b/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);
   }
 
diff --git a/skins/classic/functions.js b/skins/classic/functions.js
index d10812c..0c3b142 100644
--- a/skins/classic/functions.js
+++ b/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)
diff --git a/skins/classic/includes/messagetoolbar.html b/skins/classic/includes/messagetoolbar.html
index bd14f49..2940cd6 100644
--- a/skins/classic/includes/messagetoolbar.html
+++ b/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>
 
diff --git a/skins/classic/templates/addressbook.html b/skins/classic/templates/addressbook.html
index fdcd184..9bd6848 100644
--- a/skins/classic/templates/addressbook.html
+++ b/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>
diff --git a/skins/classic/templates/mail.html b/skins/classic/templates/mail.html
index c3b4004..3535976 100644
--- a/skins/classic/templates/mail.html
+++ b/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>
diff --git a/skins/larry/templates/addressbook.html b/skins/larry/templates/addressbook.html
index d9e491f..b33ebd9 100644
--- a/skins/larry/templates/addressbook.html
+++ b/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>
diff --git a/skins/larry/templates/mail.html b/skins/larry/templates/mail.html
index 575cb79..6b6ffd7 100644
--- a/skins/larry/templates/mail.html
+++ b/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>
diff --git a/skins/larry/templates/message.html b/skins/larry/templates/message.html
index 7ac7e5b..32dc1a4 100644
--- a/skins/larry/templates/message.html
+++ b/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>
 
diff --git a/skins/larry/ui.js b/skins/larry/ui.js
index 7dc9b57..19f05cc 100644
--- a/skins/larry/ui.js
+++ b/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

--
Gitblit v1.9.1