From eddaf0b5f68c9863181b62db33bc468d38071e8a Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Thu, 10 Apr 2014 02:41:34 -0400
Subject: [PATCH] Merge branch 'master' of github.com:roundcube/roundcubemail

---
 CHANGELOG                                          |    1 
 program/steps/mail/compose.inc                     |    9 ++
 program/lib/Roundcube/rcube_imap.php               |    8 ++
 program/lib/Roundcube/rcube_message.php            |    2 
 program/lib/Roundcube/html.php                     |    2 
 program/steps/mail/copy.inc                        |    7 +
 program/steps/mail/move_del.inc                    |    8 ++
 program/steps/mail/list.inc                        |    5 +
 program/lib/Roundcube/rcube_result_multifolder.php |   11 +++
 plugins/zipdownload/zipdownload.php                |    2 
 program/include/rcmail.php                         |   10 ++-
 program/lib/Roundcube/rcube_ldap.php               |   13 ++--
 program/steps/mail/func.inc                        |    4 +
 program/steps/mail/mark.inc                        |    4 +
 program/js/app.js                                  |   82 +++++++++++++++++----------
 15 files changed, 118 insertions(+), 50 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 43cf899..d33e545 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Search across multiple folders (#1485234)
 - Improve UI integration of ACL settings
 - Drop support for PHP < 5.3.7
 - Set In-Reply-To and References for forwarded messages (#1489593)
diff --git a/plugins/zipdownload/zipdownload.php b/plugins/zipdownload/zipdownload.php
index 32ff1b8..029eecf 100644
--- a/plugins/zipdownload/zipdownload.php
+++ b/plugins/zipdownload/zipdownload.php
@@ -169,6 +169,8 @@
      */
     public function download_folder()
     {
+        @set_time_limit(0);
+
         $imap      = rcmail::get_instance()->get_storage();
         $mbox_name = $imap->get_folder();
 
diff --git a/program/include/rcmail.php b/program/include/rcmail.php
index a812452..431b772 100644
--- a/program/include/rcmail.php
+++ b/program/include/rcmail.php
@@ -2028,8 +2028,9 @@
         $_uid  = $uids ?: rcube_utils::get_input_value('_uid', RCUBE_INPUT_GPC);
         $_mbox = $mbox ?: (string)rcube_utils::get_input_value('_mbox', RCUBE_INPUT_GPC);
 
-        if (is_array($uid)) {
-            return $uid;
+        // already a hash array
+        if (is_array($_uid) && !isset($_uid[0])) {
+            return $_uid;
         }
 
         $result = array();
@@ -2043,8 +2044,11 @@
             }
         }
         else {
+            if (is_string($_uid))
+                $_uid = explode(',', $_uid);
+
             // create a per-folder UIDs array
-            foreach (explode(',', $_uid) as $uid) {
+            foreach ((array)$_uid as $uid) {
                 list($uid, $mbox) = explode('-', $uid, 2);
                 if (empty($mbox))
                     $mbox = $_mbox;
diff --git a/program/js/app.js b/program/js/app.js
index e81abce..7f19382 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -3,8 +3,8 @@
  | Roundcube Webmail Client Script                                       |
  |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
- | Copyright (C) 2005-2013, The Roundcube Dev Team                       |
- | Copyright (C) 2011-2013, Kolab Systems AG                             |
+ | Copyright (C) 2005-2014, The Roundcube Dev Team                       |
+ | Copyright (C) 2011-2014, Kolab Systems AG                             |
  |                                                                       |
  | Licensed under the GNU General Public License version 3 or            |
  | any later version with exceptions for skins & plugins.                |
@@ -222,7 +222,7 @@
           this.gui_objects.messagelist.parentNode.onmousedown = function(e){ return p.click_on_list(e); };
 
           this.enable_command('toggle_status', 'toggle_flag', 'sort', true);
-          this.enable_command('set-listmode', this.env.threads && !this.env.search_request);
+          this.enable_command('set-listmode', this.env.threads && !this.is_multifolder_listing());
 
           // load messages
           this.command('list');
@@ -707,7 +707,7 @@
         break;
 
       case 'list':
-        // re-send for the selected folder
+        // re-send search query for the selected folder
         if (props && props != '' && this.env.search_request && this.gui_objects.qsearchbox.value) {
           var oldmbox = this.env.search_scope == 'all' ? '*' : this.env.mailbox;
           this.env.search_mods[props] = this.env.search_mods[oldmbox];  // copy search mods from active search
@@ -1113,7 +1113,7 @@
       case 'forward':
         var uids = this.env.uid ? [this.env.uid] : (this.message_list ? this.message_list.get_selection() : []);
         if (uids.length) {
-          url = { _forward_uid: this.uids_to_list(uids), _mbox: this.env.mailbox };
+          url = { _forward_uid: this.uids_to_list(uids), _mbox: this.env.mailbox, _search: this.env.search_request };
           if (command == 'forward-attachment' || (!props && this.env.forward_attachment) || uids.length > 1)
             url._attachment = 1;
           this.open_compose_step(url);
@@ -1713,7 +1713,9 @@
   {
     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;
+        return (this.env.mailboxes[id]
+            && !this.env.mailboxes[id].virtual
+            && (this.env.mailboxes[id].id != this.env.mailbox || this.is_multifolder_listing())) ? 1 : 0;
 
       case 'settings':
         return id != this.env.mailbox ? 1 : 0;
@@ -2191,8 +2193,16 @@
     this.http_request('search', this.search_params(false, filter), lock);
   };
 
+  // reload the current message listing
+  this.refresh_list = function()
+  {
+    this.list_mailbox(this.env.mailbox, this.env.current_page || 1, null, { _clear:1 }, true);
+    if (this.message_list)
+      this.message_list.clear_selection();
+  };
+
   // list messages of a specific mailbox
-  this.list_mailbox = function(mbox, page, sort, url)
+  this.list_mailbox = function(mbox, page, sort, url, update_only)
   {
     var win, target = window;
 
@@ -2217,15 +2227,17 @@
       this.select_all_mode = false;
     }
 
-    // unselect selected messages and clear the list and message data
-    this.clear_message_list();
+    if (!update_only) {
+      // unselect selected messages and clear the list and message data
+      this.clear_message_list();
 
-    if (mbox != this.env.mailbox || (mbox == this.env.mailbox && !page && !sort))
-      url._refresh = 1;
+      if (mbox != this.env.mailbox || (mbox == this.env.mailbox && !page && !sort))
+        url._refresh = 1;
 
-    this.select_folder(mbox, '', true);
-    this.unmark_folder(mbox, 'recent', '', true);
-    this.env.mailbox = mbox;
+      this.select_folder(mbox, '', true);
+      this.unmark_folder(mbox, 'recent', '', true);
+      this.env.mailbox = mbox;
+    }
 
     // load message list remotely
     if (this.gui_objects.messagelist) {
@@ -2259,20 +2271,17 @@
   };
 
   // send remote request to load message list
-  this.list_mailbox_remote = function(mbox, page, post_data)
+  this.list_mailbox_remote = function(mbox, page, url)
   {
-    // clear message list first
-    this.message_list.clear();
-
     var lock = this.set_busy(true, 'loading');
 
-    if (typeof post_data != 'object')
-      post_data = {};
-    post_data._mbox = mbox;
+    if (typeof url != 'object')
+      url = {};
+    url._mbox = mbox;
     if (page)
-      post_data._page = page;
+      url._page = page;
 
-    this.http_request('list', post_data, lock);
+    this.http_request('list', url, lock);
   };
 
   // removes messages that doesn't exists from list selection array
@@ -2689,7 +2698,7 @@
       return this.folder_selector(obj, function(folder) { ref.command('move', folder); });
 
     // exit if current or no mailbox specified
-    if (!mbox || (mbox == this.env.mailbox && (!this.env.search_request || this.env.search_scope == 'base')))
+    if (!mbox || (mbox == this.env.mailbox && !this.is_multifolder_listing()))
       return;
 
     var lock = false, post_data = this.selection_post_data({_target_mbox: mbox});
@@ -2757,7 +2766,8 @@
   // @private
   this._with_selected_messages = function(action, post_data, lock)
   {
-    var count = 0, msg;
+    var count = 0, msg,
+      remove = (action == 'delete' || !this.is_multifolder_listing());
 
     // update the list (remove rows, clear selection)
     if (this.message_list) {
@@ -2774,10 +2784,11 @@
             roots.push(root);
           }
         }
-        this.message_list.remove_row(id, (this.env.display_next && n == selection.length-1));
+        if (remove)
+          this.message_list.remove_row(id, (this.env.display_next && n == selection.length-1));
       }
       // make sure there are no selected rows
-      if (!this.env.display_next)
+      if (!this.env.display_next && remove)
         this.message_list.clear_selection();
       // update thread tree icons
       for (n=0, len=roots.length; n<len; n++) {
@@ -2788,8 +2799,11 @@
     if (count < 0)
       post_data._count = (count*-1);
     // remove threads from the end of the list
-    else if (count > 0)
+    else if (count > 0 && remove)
       this.delete_excessive_thread_rows();
+
+    if (!remove)
+      post_data._refresh = 1;
 
     if (!lock) {
       msg = action == 'move' ? 'movingmessage' : 'deletingmessage';
@@ -3000,7 +3014,8 @@
     var icn_src, uid, i, len,
       rows = this.message_list ? this.message_list.rows : {};
 
-    uids = String(uids).split(',');
+    if (typeof uids == 'string')
+      uids = String(uids).split(',');
 
     for (i=0, len=uids.length; i<len; i++) {
       uid = uids[i];
@@ -3013,7 +3028,7 @@
   // with select_all mode checking
   this.uids_to_list = function(uids)
   {
-    return this.select_all_mode ? '*' : uids.join(',');
+    return this.select_all_mode ? '*' : (uids.length <= 1 ? uids.join(',') : uids);
   };
 
   // Sets title of the delete button
@@ -4258,6 +4273,12 @@
 
     this.env.search_mods[mbox] = mods;
   };
+
+  this.is_multifolder_listing = function()
+  {
+    return typeof this.env.multifolder_listing != 'undefined' ? this.env.multifolder_listing :
+      (this.env.search_request && (this.env.search_scope || 'base') != 'base');
+  }
 
   this.sent_successfully = function(type, msg, folders)
   {
@@ -7041,6 +7062,7 @@
           this.enable_command('expunge', this.env.exists);
           this.enable_command('purge', this.purge_mailbox_test());
           this.enable_command('expand-all', 'expand-unread', 'collapse-all', this.env.threading && this.env.messagecount);
+          this.enable_command('set-listmode', this.env.threads && !this.is_multifolder_listing());
 
           if ((response.action == 'list' || response.action == 'search') && this.message_list) {
             this.msglist_select(this.message_list);
diff --git a/program/lib/Roundcube/html.php b/program/lib/Roundcube/html.php
index 64324dd..f47ef29 100644
--- a/program/lib/Roundcube/html.php
+++ b/program/lib/Roundcube/html.php
@@ -285,7 +285,7 @@
 
             // ignore not allowed attributes
             if (!empty($allowed)) {
-                $is_data_attr = substr_compare($key, 'data-', 0, 5) === 0;
+                $is_data_attr = @substr_compare($key, 'data-', 0, 5) === 0;
                 if (!isset($allowed_f[$key]) && (!$is_data_attr || !isset($allowed_f['data-*']))) {
                     continue;
                 }
diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php
index 23cacd4..5c30327 100644
--- a/program/lib/Roundcube/rcube_imap.php
+++ b/program/lib/Roundcube/rcube_imap.php
@@ -988,6 +988,10 @@
                 $a_msg_headers = array_slice(array_values($a_msg_headers), $from, $slice_length);
             }
             else {
+                if ($this->sort_order != $search_set->get_parameters('ORDER')) {
+                    $search_set->revert();
+                }
+
                 // slice resultset first...
                 $fetch = array();
                 foreach (array_slice($search_set->get(), $from, $slice_length) as $msg_id) {
@@ -1708,7 +1712,7 @@
         }
 
         // decode combined UID-folder identifier
-        if (preg_match('/^\d+-[^,]+$/', $uid)) {
+        if (preg_match('/^\d+-.+/', $uid)) {
             list($uid, $folder) = explode('-', $uid, 2);
         }
 
@@ -1744,7 +1748,7 @@
         }
 
         // decode combined UID-folder identifier
-        if (preg_match('/^\d+-[^,]+$/', $uid)) {
+        if (preg_match('/^\d+-.+/', $uid)) {
             list($uid, $folder) = explode('-', $uid, 2);
         }
 
diff --git a/program/lib/Roundcube/rcube_ldap.php b/program/lib/Roundcube/rcube_ldap.php
index 55a64ac..5a4b9dd 100644
--- a/program/lib/Roundcube/rcube_ldap.php
+++ b/program/lib/Roundcube/rcube_ldap.php
@@ -95,8 +95,8 @@
             if (empty($this->prop['groups']['scope']))
                 $this->prop['groups']['scope'] = 'sub';
             // extend group objectclass => member attribute mapping
-            if (!empty($this->prop['groups']['event-panel-summary']))
-                $this->group_types = array_merge($this->group_types, $this->prop['groups']['event-panel-summary']);
+            if (!empty($this->prop['groups']['class_member_attr']))
+                $this->group_types = array_merge($this->group_types, $this->prop['groups']['class_member_attr']);
 
             // add group name attrib to the list of attributes to be fetched
             $fetch_attributes[] = $this->prop['groups']['name_attr'];
@@ -377,10 +377,11 @@
                 // replace placeholders in filter settings
                 if (!empty($this->prop['filter']))
                     $this->prop['filter'] = strtr($this->prop['filter'], $replaces);
-                if (!empty($this->prop['groups']['filter']))
-                    $this->prop['groups']['filter'] = strtr($this->prop['groups']['filter'], $replaces);
-                if (!empty($this->prop['groups']['member_filter']))
-                    $this->prop['groups']['member_filter'] = strtr($this->prop['groups']['member_filter'], $replaces);
+
+                foreach (array('base_dn','filter','member_filter') as $k) {
+                    if (!empty($this->prop['groups'][$k]))
+                        $this->prop['groups'][$k] = strtr($this->prop['groups'][$k], $replaces);
+                }
 
                 if (!empty($this->prop['group_filters'])) {
                     foreach ($this->prop['group_filters'] as $i => $gf) {
diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php
index edfd339..a648ae7 100644
--- a/program/lib/Roundcube/rcube_message.php
+++ b/program/lib/Roundcube/rcube_message.php
@@ -75,7 +75,7 @@
     function __construct($uid, $folder = null)
     {
         // decode combined UID-folder identifier
-        if (preg_match('/^\d+-[^,]+$/', $uid)) {
+        if (preg_match('/^\d+-.+/', $uid)) {
             list($uid, $folder) = explode('-', $uid, 2);
         }
 
diff --git a/program/lib/Roundcube/rcube_result_multifolder.php b/program/lib/Roundcube/rcube_result_multifolder.php
index 74a3d78..b5473b8 100644
--- a/program/lib/Roundcube/rcube_result_multifolder.php
+++ b/program/lib/Roundcube/rcube_result_multifolder.php
@@ -130,6 +130,17 @@
     public function revert()
     {
         $this->order = $this->order == 'ASC' ? 'DESC' : 'ASC';
+        $this->index = array();
+
+        // revert order in all sub-sets
+        foreach ($this->sets as $set) {
+            if ($this->order != $set->get_parameters('ORDER')) {
+                $set->revert();
+            }
+            $folder = $set->get_parameters('MAILBOX');
+            $index = array_map(function($uid) use ($folder) { return $uid . '-' . $folder; }, $set->get());
+            $this->index = array_merge($this->index, $index);
+        }
     }
 
 
diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc
index 040d816..22ebaed 100644
--- a/program/steps/mail/compose.inc
+++ b/program/steps/mail/compose.inc
@@ -463,6 +463,11 @@
         }
     }
 
+    // resolve _forward_uid=* to an absolute list of messages from a search result
+    if ($COMPOSE['param']['forward_uid'] == '*' && is_object($_SESSION['search'][1])) {
+        $COMPOSE['param']['forward_uid'] = $_SESSION['search'][1]->get();
+    }
+
     // clean HTML message body which can be submitted by URL
     if (!empty($COMPOSE['param']['body'])) {
         $COMPOSE['param']['body'] = rcmail_wash_html($COMPOSE['param']['body'], array('safe' => false, 'inline_html' => true), array());
@@ -1259,10 +1264,10 @@
         $index = $storage->index(null, rcmail_sort_column(), rcmail_sort_order());
         $COMPOSE['forward_uid'] = $index->get();
     }
-    else if (strpos($COMPOSE['forward_uid'], ':')) {
+    else if (!is_array($COMPOSE['forward_uid']) && strpos($COMPOSE['forward_uid'], ':')) {
         $COMPOSE['forward_uid'] = rcube_imap_generic::uncompressMessageSet($COMPOSE['forward_uid']);
     }
-    else {
+    else if (is_string($COMPOSE['forward_uid'])) {
         $COMPOSE['forward_uid'] = explode(',', $COMPOSE['forward_uid']);
     }
 
diff --git a/program/steps/mail/copy.inc b/program/steps/mail/copy.inc
index 79e207a..5668f7c 100644
--- a/program/steps/mail/copy.inc
+++ b/program/steps/mail/copy.inc
@@ -5,7 +5,7 @@
  | program/steps/mail/copy.inc                                           |
  |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
- | Copyright (C) 2005-2013, The Roundcube Dev Team                       |
+ | Copyright (C) 2005-2014, The Roundcube Dev Team                       |
  |                                                                       |
  | Licensed under the GNU General Public License version 3 or            |
  | any later version with exceptions for skins & plugins.                |
@@ -29,7 +29,10 @@
     $target = rcube_utils::get_input_value('_target_mbox', rcube_utils::INPUT_POST, true);
 
     foreach (rcmail::get_uids() as $mbox => $uids) {
-      $copied += (int)$RCMAIL->storage->copy_message($uids, $target, $mbox);
+        if ($mbox == $target)
+            $copied++;
+        else
+            $copied += (int)$RCMAIL->storage->copy_message($uids, $target, $mbox);
     }
 
     if (!$copied) {
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index a541fca..f711da3 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -69,7 +69,7 @@
 }
 
 // remove mbox part from _uid
-if (($_uid  = get_input_value('_uid', RCUBE_INPUT_GPC)) && preg_match('/^\d+-[^,]+$/', $_uid)) {
+if (($_uid  = rcube_utils::get_input_value('_uid', RCUBE_INPUT_GPC)) && !is_array($_uid) && preg_match('/^\d+-.+/', $_uid)) {
   list($_uid, $mbox) = explode('-', $_uid, 2);
   if (isset($_GET['_uid']))  $_GET['_uid']  = $_uid;
   if (isset($_POST['_uid'])) $_POST['_uid'] = $_uid;
@@ -393,6 +393,8 @@
         $OUTPUT->command('select_folder', '');
     }
 
+    $OUTPUT->set_env('multifolder_listing', $multifolder);
+
     if (empty($a_headers)) {
         return;
     }
diff --git a/program/steps/mail/list.inc b/program/steps/mail/list.inc
index a8fc9eb..36c79fb 100644
--- a/program/steps/mail/list.inc
+++ b/program/steps/mail/list.inc
@@ -101,6 +101,11 @@
 $OUTPUT->set_env('exists', $exists);
 $OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($count), $mbox_name);
 
+// remove old message rows if commanded by the client
+if (!empty($_REQUEST['_clear'])) {
+  $OUTPUT->command('clear_message_list');
+}
+
 // add message rows
 rcmail_js_message_list($a_headers, false, $cols);
 
diff --git a/program/steps/mail/mark.inc b/program/steps/mail/mark.inc
index e81f6c9..4e83f97 100644
--- a/program/steps/mail/mark.inc
+++ b/program/steps/mail/mark.inc
@@ -68,7 +68,9 @@
 
     if ($flag == 'DELETED' && $read_deleted && !empty($_POST['_ruid'])) {
         $ruids = rcube_utils::get_input_value('_ruid', rcube_utils::INPUT_POST);
-        $read  = $RCMAIL->storage->set_flag($ruids, 'SEEN');
+        foreach (rcmail::get_uids($ruids) as $mbox => $uids) {
+            $read += (int)$RCMAIL->storage->set_flag($uids, 'SEEN', $mbox);
+        }
 
         if ($read && !$skip_deleted) {
             $OUTPUT->command('flag_deleted_as_read', $ruids);
diff --git a/program/steps/mail/move_del.inc b/program/steps/mail/move_del.inc
index ae367b4..9c1acd5 100644
--- a/program/steps/mail/move_del.inc
+++ b/program/steps/mail/move_del.inc
@@ -60,7 +60,13 @@
         $OUTPUT->show_message('messagemoved', 'confirmation');
     }
 
-    $addrows = true;
+    if (!empty($_POST['_refresh'])) {
+        // FIXME: send updated message rows instead of releading the entire list
+        $OUTPUT->command('refresh_list');
+    }
+    else {
+        $addrows = true;
+    }
 }
 // delete messages 
 else if ($RCMAIL->action=='delete' && !empty($_POST['_uid'])) {

--
Gitblit v1.9.1