From 1e9a59ab89ed69d4bf3cd2d306e1c3f8821f4018 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Tue, 8 Apr 2014 14:38:54 +0200 Subject: [PATCH 1/9] Don't remove messages from list when moving to another folder in multi-folder search mode, just update the list --- program/js/app.js | 67 ++++++++++++++++++---------- program/lib/Roundcube/rcube_ldap.php | 9 ++-- program/steps/mail/list.inc | 5 +++ program/steps/mail/move_del.inc | 8 +++- 4 files changed, 60 insertions(+), 29 deletions(-) diff --git a/program/js/app.js b/program/js/app.js index e81abceb4..81829ab13 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -707,7 +707,7 @@ function rcube_webmail() 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 @@ -1713,7 +1713,9 @@ function rcube_webmail() { 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_search())) ? 1 : 0; case 'settings': return id != this.env.mailbox ? 1 : 0; @@ -2191,8 +2193,16 @@ function rcube_webmail() 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() }; // 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 @@ function rcube_webmail() 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_search())) return; var lock = false, post_data = this.selection_post_data({_target_mbox: mbox}); @@ -2757,7 +2766,8 @@ function rcube_webmail() // @private this._with_selected_messages = function(action, post_data, lock) { - var count = 0, msg; + var count = 0, msg, + remove = (action == 'delete' || !this.is_multifolder_search()); // update the list (remove rows, clear selection) if (this.message_list) { @@ -2774,10 +2784,11 @@ function rcube_webmail() 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 0) + else if (count > 0 && remove) this.delete_excessive_thread_rows(); + if (!remove) + post_data._refresh = 1; + if (!lock) { msg = action == 'move' ? 'movingmessage' : 'deletingmessage'; lock = this.display_message(this.get_label(msg), 'loading'); @@ -4259,6 +4273,11 @@ function rcube_webmail() this.env.search_mods[mbox] = mods; }; + this.is_multifolder_search = function() + { + return this.env.search_request && (this.env.search_scope || 'base') != 'base'; + } + this.sent_successfully = function(type, msg, folders) { this.display_message(msg, type); diff --git a/program/lib/Roundcube/rcube_ldap.php b/program/lib/Roundcube/rcube_ldap.php index 55a64acec..64fa45bc2 100644 --- a/program/lib/Roundcube/rcube_ldap.php +++ b/program/lib/Roundcube/rcube_ldap.php @@ -377,10 +377,11 @@ class rcube_ldap extends rcube_addressbook // 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/steps/mail/list.inc b/program/steps/mail/list.inc index a8fc9eb8a..36c79fb29 100644 --- a/program/steps/mail/list.inc +++ b/program/steps/mail/list.inc @@ -101,6 +101,11 @@ $OUTPUT->set_env('current_page', $count ? $RCMAIL->storage->get_page() : 1); $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/move_del.inc b/program/steps/mail/move_del.inc index ae367b4f7..9c1acd5d2 100644 --- a/program/steps/mail/move_del.inc +++ b/program/steps/mail/move_del.inc @@ -60,7 +60,13 @@ if ($RCMAIL->action == 'move' && !empty($_POST['_uid']) && strlen($_POST['_targe $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'])) { From 94e797c1fbc5ae1ce3c57bdc5cdaabd3a9f8f911 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Tue, 8 Apr 2014 15:17:19 +0200 Subject: [PATCH 2/9] Consider sort order in UID sort mode of multi-folder search results --- program/lib/Roundcube/rcube_imap.php | 4 ++++ program/lib/Roundcube/rcube_result_multifolder.php | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index 23cacd4d2..a2f610a0a 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -988,6 +988,10 @@ class rcube_imap extends rcube_storage $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) { diff --git a/program/lib/Roundcube/rcube_result_multifolder.php b/program/lib/Roundcube/rcube_result_multifolder.php index 74a3d7805..b5473b841 100644 --- a/program/lib/Roundcube/rcube_result_multifolder.php +++ b/program/lib/Roundcube/rcube_result_multifolder.php @@ -130,6 +130,17 @@ class rcube_result_multifolder 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); + } } From aafbe8efdca6dfef736e623dd5da83d64ae10aef Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Tue, 8 Apr 2014 15:48:27 +0200 Subject: [PATCH 3/9] Fix forwarding all ('*') messages of a search set --- program/js/app.js | 2 +- program/steps/mail/compose.inc | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/program/js/app.js b/program/js/app.js index 81829ab13..2a7783fad 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -1113,7 +1113,7 @@ function rcube_webmail() 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); diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc index 040d81689..22ebaed8d 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -463,6 +463,11 @@ function rcmail_process_compose_params(&$COMPOSE) } } + // 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 @@ function rcmail_write_forward_attachments() $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']); } From f50a66c616bdc46ac0c568e1efdeb979477db4db Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Tue, 8 Apr 2014 16:26:56 +0200 Subject: [PATCH 4/9] Enable listmode switch if listing is not multi-folder --- program/js/app.js | 18 ++++++++++-------- program/steps/mail/func.inc | 2 ++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/program/js/app.js b/program/js/app.js index 2a7783fad..952c542a0 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 @@ function rcube_webmail() 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'); @@ -1715,7 +1715,7 @@ function rcube_webmail() case 'mail': return (this.env.mailboxes[id] && !this.env.mailboxes[id].virtual - && (this.env.mailboxes[id].id != this.env.mailbox || this.is_multifolder_search())) ? 1 : 0; + && (this.env.mailboxes[id].id != this.env.mailbox || this.is_multifolder_listing())) ? 1 : 0; case 'settings': return id != this.env.mailbox ? 1 : 0; @@ -2698,7 +2698,7 @@ function rcube_webmail() 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.is_multifolder_search())) + if (!mbox || (mbox == this.env.mailbox && !this.is_multifolder_listing())) return; var lock = false, post_data = this.selection_post_data({_target_mbox: mbox}); @@ -2767,7 +2767,7 @@ function rcube_webmail() this._with_selected_messages = function(action, post_data, lock) { var count = 0, msg, - remove = (action == 'delete' || !this.is_multifolder_search()); + remove = (action == 'delete' || !this.is_multifolder_listing()); // update the list (remove rows, clear selection) if (this.message_list) { @@ -4273,9 +4273,10 @@ function rcube_webmail() this.env.search_mods[mbox] = mods; }; - this.is_multifolder_search = function() + this.is_multifolder_listing = function() { - return this.env.search_request && (this.env.search_scope || 'base') != 'base'; + 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) @@ -7060,6 +7061,7 @@ function rcube_webmail() 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/steps/mail/func.inc b/program/steps/mail/func.inc index a541fca96..630086a68 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -393,6 +393,8 @@ function rcmail_js_message_list($a_headers, $insert_top=false, $a_show_cols=null $OUTPUT->command('select_folder', ''); } + $OUTPUT->set_env('multifolder_listing', $multifolder); + if (empty($a_headers)) { return; } From 05e3d11554f751ed6a614abe343141aa1e6ea6da Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Tue, 8 Apr 2014 17:06:59 +0200 Subject: [PATCH 5/9] Update Changelog --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 43cf899e2..d33e545ee 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) From 188247894f6aff3a11f68bbdf94626b8bf58b852 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Tue, 8 Apr 2014 19:11:12 +0200 Subject: [PATCH 6/9] Make multi-folder message identifiers work with folders containing commas --- program/include/rcmail.php | 10 +++++++--- program/js/app.js | 5 +++-- program/lib/Roundcube/rcube_imap.php | 4 ++-- program/lib/Roundcube/rcube_message.php | 2 +- program/steps/mail/copy.inc | 7 +++++-- program/steps/mail/func.inc | 2 +- program/steps/mail/mark.inc | 4 +++- 7 files changed, 22 insertions(+), 12 deletions(-) diff --git a/program/include/rcmail.php b/program/include/rcmail.php index 2ccad03c6..5da181438 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -2025,8 +2025,9 @@ class rcmail extends rcube $_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(); @@ -2040,8 +2041,11 @@ class rcmail extends rcube } } 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 952c542a0..7f19382e0 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -3014,7 +3014,8 @@ function rcube_webmail() 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 $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 630086a68..f711da3e1 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -69,7 +69,7 @@ if (!empty($_REQUEST['_search']) && isset($_SESSION['search']) } // 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; diff --git a/program/steps/mail/mark.inc b/program/steps/mail/mark.inc index e81f6c9f5..4e83f975c 100644 --- a/program/steps/mail/mark.inc +++ b/program/steps/mail/mark.inc @@ -68,7 +68,9 @@ if (($_uids = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST)) 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); From b08306ec1fe1a8e444a87cdb1a0304042ba0f7d4 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Tue, 8 Apr 2014 19:12:12 +0200 Subject: [PATCH 7/9] Disable time limit for folder download requests --- plugins/zipdownload/zipdownload.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/zipdownload/zipdownload.php b/plugins/zipdownload/zipdownload.php index 32ff1b88b..029eecfb8 100644 --- a/plugins/zipdownload/zipdownload.php +++ b/plugins/zipdownload/zipdownload.php @@ -169,6 +169,8 @@ class zipdownload extends rcube_plugin */ public function download_folder() { + @set_time_limit(0); + $imap = rcmail::get_instance()->get_storage(); $mbox_name = $imap->get_folder(); From bc088fdc907d1d88982429608e7d75507feee784 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Tue, 8 Apr 2014 20:10:54 +0200 Subject: [PATCH 8/9] Hide PHP Warning: substr_compare() The length cannot exceed initial string length --- program/lib/Roundcube/html.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/program/lib/Roundcube/html.php b/program/lib/Roundcube/html.php index 64324dd8e..f47ef299a 100644 --- a/program/lib/Roundcube/html.php +++ b/program/lib/Roundcube/html.php @@ -285,7 +285,7 @@ class html // 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; } From a2cf7c41b97a587d90188b83e4d15da1567a54b4 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Wed, 9 Apr 2014 08:48:28 +0200 Subject: [PATCH 9/9] Fix accidental key replacements --- program/lib/Roundcube/rcube_ldap.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/program/lib/Roundcube/rcube_ldap.php b/program/lib/Roundcube/rcube_ldap.php index 64fa45bc2..5a4b9dd61 100644 --- a/program/lib/Roundcube/rcube_ldap.php +++ b/program/lib/Roundcube/rcube_ldap.php @@ -95,8 +95,8 @@ class rcube_ldap extends rcube_addressbook 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'];