From d016dcc6f6a3daf8c19e2ececd3c676cd274381a Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 9 Oct 2013 12:02:52 +0200 Subject: Refactor multi-threaded autocomple contact searching to make it available for other purposes, too --- plugins/acl/acl.php | 4 +- program/js/app.js | 200 ++++++++++++++++++++++++++---------- program/steps/mail/autocomplete.inc | 4 +- 3 files changed, 147 insertions(+), 61 deletions(-) diff --git a/plugins/acl/acl.php b/plugins/acl/acl.php index a840bcd58..203340bdb 100644 --- a/plugins/acl/acl.php +++ b/plugins/acl/acl.php @@ -86,7 +86,7 @@ class acl extends rcube_plugin $this->load_config(); $search = rcube_utils::get_input_value('_search', rcube_utils::INPUT_GPC, true); - $sid = rcube_utils::get_input_value('_id', rcube_utils::INPUT_GPC); + $reqid = rcube_utils::get_input_value('_reqid', rcube_utils::INPUT_GPC); $users = array(); if ($this->init_ldap()) { @@ -115,7 +115,7 @@ class acl extends rcube_plugin sort($users, SORT_LOCALE_STRING); - $this->rc->output->command('ksearch_query_results', $users, $search, $sid); + $this->rc->output->command('ksearch_query_results', $users, $search, $reqid); $this->rc->output->send(); } diff --git a/program/js/app.js b/program/js/app.js index 5ea01e750..43ab7be17 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -31,6 +31,7 @@ function rcube_webmail() this.onloads = []; this.messages = {}; this.group2expand = {}; + this.http_request_jobs = {}; // webmail client settings this.dblclick_time = 500; @@ -3963,7 +3964,7 @@ function rcube_webmail() p = inp_value.lastIndexOf(this.env.recipients_separator, cpos-1), q = inp_value.substring(p+1, cpos), min = this.env.autocomplete_min_length, - ac = this.ksearch_data; + data = this.ksearch_data; // trim query string q = $.trim(q); @@ -3990,34 +3991,26 @@ function rcube_webmail() return; // ...new search value contains old one and previous search was not finished or its result was empty - if (old_value && old_value.length && q.indexOf(old_value) == 0 && (!ac || ac.num <= 0) && this.env.contacts && !this.env.contacts.length) + if (old_value && old_value.length && q.indexOf(old_value) == 0 && (!data || data.num <= 0) && this.env.contacts && !this.env.contacts.length) return; - var i, lock, source, xhr, reqid = new Date().getTime(), - post_data = {_search: q, _id: reqid}, - threads = props && props.threads ? props.threads : 1, - sources = props && props.sources ? props.sources : [], - action = props && props.action ? props.action : 'mail/autocomplete'; - - this.ksearch_data = {id: reqid, sources: sources.slice(), action: action, - locks: [], requests: [], num: sources.length}; - - for (i=0; i 1 && source === undefined) - break; - - post_data._source = source ? source : ''; - lock = this.display_message(this.get_label('searching'), 'loading'); - xhr = this.http_post(action, post_data, lock); + var sources = props && props.sources ? props.sources : ['']; + var reqid = this.multi_thread_http_request({ + items: sources, + threads: props && props.threads ? props.threads : 1, + action: props && props.action ? props.action : 'mail/autocomplete', + postdata: { _search:q, _source:'%s' }, + lock: this.display_message(this.get_label('searching'), 'loading') + }); - this.ksearch_data.locks.push(lock); - this.ksearch_data.requests.push(xhr); - } + this.ksearch_data = { id:reqid, sources:sources.slice(), num:sources.length }; }; this.ksearch_query_results = function(results, search, reqid) { + // trigger multi-thread http response callback + this.multi_thread_http_response(results, reqid); + // search stopped in meantime? if (!this.ksearch_value) return; @@ -4029,7 +4022,6 @@ function rcube_webmail() // display search results var i, len, ul, li, text, init, value = this.ksearch_value, - data = this.ksearch_data, maxlen = this.env.autocomplete_max ? this.env.autocomplete_max : 15; // create results pane if not present @@ -4083,27 +4075,8 @@ function rcube_webmail() if (len) this.env.contacts = this.env.contacts.concat(results); - // run next parallel search - if (data.id == reqid) { - data.num--; - if (maxlen > 0 && data.sources.length) { - var lock, xhr, source = data.sources.shift(), post_data; - if (source) { - post_data = {_search: value, _id: reqid, _source: source}; - lock = this.display_message(this.get_label('searching'), 'loading'); - xhr = this.http_post(data.action, post_data, lock); - - this.ksearch_data.locks.push(lock); - this.ksearch_data.requests.push(xhr); - } - } - else if (!maxlen) { - if (!this.ksearch_msg) - this.ksearch_msg = this.display_message(this.get_label('autocompletemore')); - // abort pending searches - this.ksearch_abort(); - } - } + if (this.ksearch_data.id == reqid) + this.ksearch_data.num--; }; this.ksearch_click = function(node) @@ -4138,7 +4111,8 @@ function rcube_webmail() // Clears autocomplete data/requests this.ksearch_destroy = function() { - this.ksearch_abort(); + if (this.ksearch_data) + this.multi_thread_request_abort(this.ksearch_data.id); if (this.ksearch_info) this.hide_message(this.ksearch_info); @@ -4149,18 +4123,6 @@ function rcube_webmail() this.ksearch_data = null; this.ksearch_info = null; this.ksearch_msg = null; - } - - // Aborts pending autocomplete requests - this.ksearch_abort = function() - { - var i, len, ac = this.ksearch_data; - - if (!ac) - return; - - for (i=0, len=ac.locks.length; i 0 && i < prop.requests.length; i++) { + if (prop.requests[i].abort) + prop.requests[i].abort(); + } + + prop.running = 0; + prop.cancelled = true; + this.set_busy(false, '', prop.lock); + } + }; + // post the given form to a hidden iframe this.async_upload_form = function(form, action, onload) { diff --git a/program/steps/mail/autocomplete.inc b/program/steps/mail/autocomplete.inc index f9e8d71a4..c34133a28 100644 --- a/program/steps/mail/autocomplete.inc +++ b/program/steps/mail/autocomplete.inc @@ -47,7 +47,7 @@ $mode = (int) $RCMAIL->config->get('addressbook_search_mode'); $single = (bool) $RCMAIL->config->get('autocomplete_single'); $search = get_input_value('_search', RCUBE_INPUT_GPC, true); $source = get_input_value('_source', RCUBE_INPUT_GPC); -$sid = get_input_value('_id', RCUBE_INPUT_GPC); +$reqid = get_input_value('_reqid', RCUBE_INPUT_GPC); if (strlen($source)) $book_types = array($source); @@ -141,5 +141,5 @@ if (!empty($book_types) && strlen($search)) { } } -$OUTPUT->command('ksearch_query_results', $contacts, $search, $sid); +$OUTPUT->command('ksearch_query_results', $contacts, $search, $reqid); $OUTPUT->send(); -- cgit v1.2.3 From f540f86d19ae1d374ba3c78fe11eaf3f771af150 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 9 Oct 2013 12:04:17 +0200 Subject: Prepare message list to display IMAP folder --- program/js/app.js | 4 +++- program/js/list.js | 14 ++++++++++++++ program/lib/Roundcube/rcube_imap.php | 1 + program/lib/Roundcube/rcube_message_header.php | 9 +++++++++ program/steps/mail/func.inc | 4 +++- skins/larry/mail.css | 8 ++++++++ 6 files changed, 38 insertions(+), 2 deletions(-) diff --git a/program/js/app.js b/program/js/app.js index 43ab7be17..1d59794d7 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -5998,8 +5998,10 @@ function rcube_webmail() if ((n = $.inArray('status', this.env.coltypes)) >= 0) this.env.status_col = n; - if (list) + if (list) { + list.hide_column('folder', !(this.env.search_request || this.env.search_id)); list.init_header(); + } }; // replace content of row count display diff --git a/program/js/list.js b/program/js/list.js index 0f8d56c19..32e34e080 100644 --- a/program/js/list.js +++ b/program/js/list.js @@ -372,6 +372,20 @@ blur: function() }, +/** + * Set/unset the given column as hidden + */ +hide_column: function(col, hide) +{ + var method = hide ? 'addClass' : 'removeClass'; + + if (this.fixed_header) + $(this.row_tagname()+' '+this.col_tagname()+'.'+col, this.fixed_header)[method]('hidden'); + + $(this.row_tagname()+' '+this.col_tagname()+'.'+col, this.list)[method]('hidden'); +}, + + /** * onmousedown-handler of message list column */ diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index 9faf1bbc6..e0dce6f79 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -1076,6 +1076,7 @@ class rcube_imap extends rcube_storage } foreach ($headers as $h) { + $h->folder = $folder; $a_msg_headers[$h->uid] = $h; } diff --git a/program/lib/Roundcube/rcube_message_header.php b/program/lib/Roundcube/rcube_message_header.php index 2c5e2b6c8..2bda930eb 100644 --- a/program/lib/Roundcube/rcube_message_header.php +++ b/program/lib/Roundcube/rcube_message_header.php @@ -166,6 +166,13 @@ class rcube_message_header */ public $mdn_to; + /** + * IMAP folder this message is stored in + * + * @var string + */ + public $folder; + /** * Other message headers * @@ -189,6 +196,8 @@ class rcube_message_header 'reply-to' => 'replyto', 'cc' => 'cc', 'bcc' => 'bcc', + 'mbox' => 'folder', + 'folder' => 'folder', 'content-transfer-encoding' => 'encoding', 'in-reply-to' => 'in_reply_to', 'content-type' => 'ctype', diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index 48afecb60..b677c4675 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -363,6 +363,8 @@ function rcmail_js_message_list($a_headers, $insert_top=FALSE, $a_show_cols=null $cont = show_bytes($header->$col); else if ($col == 'date') $cont = format_date($header->date); + else if ($col == 'folder') + $cont = Q(rcube_charset::convert($header->folder, 'UTF7-IMAP')); else $cont = Q($header->$col); @@ -386,7 +388,7 @@ function rcmail_js_message_list($a_headers, $insert_top=FALSE, $a_show_cols=null $a_msg_flags['prio'] = (int) $header->priority; $a_msg_flags['ctype'] = Q($header->ctype); - $a_msg_flags['mbox'] = $mbox; + $a_msg_flags['mbox'] = $header->folder; // merge with plugin result (Deprecated, use $header->flags) if (!empty($header->list_flags) && is_array($header->list_flags)) diff --git a/skins/larry/mail.css b/skins/larry/mail.css index b65b08112..3c7242b4f 100644 --- a/skins/larry/mail.css +++ b/skins/larry/mail.css @@ -521,6 +521,14 @@ table.messagelist.fixedcopy { width: 135px; } +.messagelist tr td.folder { + width: 135px; +} + +.messagelist tr td.hidden { + display: none; +} + .messagelist tr.message { /* background-color: #fff; */ } -- cgit v1.2.3 From 9e63cd5f24defa521724dfe3dcbbaa4385761836 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 9 Oct 2013 17:12:30 +0200 Subject: Support globally unique message UIDs with IMAP folder name appended --- program/js/app.js | 27 ++++++++++++++++--------- program/steps/mail/copy.inc | 6 +++--- program/steps/mail/func.inc | 44 +++++++++++++++++++++++++++++++++++++++++ program/steps/mail/mark.inc | 10 ++++++---- program/steps/mail/move_del.inc | 21 ++++++++++---------- program/steps/mail/search.inc | 16 ++++++++++++++- 6 files changed, 97 insertions(+), 27 deletions(-) diff --git a/program/js/app.js b/program/js/app.js index 1d59794d7..94f343f9e 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -641,7 +641,7 @@ function rcube_webmail() case 'open': if (uid = this.get_single_uid()) { - obj.href = this.url('show', {_mbox: this.env.mailbox, _uid: uid}); + obj.href = this.url('show', {_mbox: this.get_message_mailbox(uid), _uid: uid}); return true; } break; @@ -737,9 +737,9 @@ function rcube_webmail() this.load_contact(cid, 'edit'); else if (this.task == 'settings' && props) this.load_identity(props, 'edit-identity'); - else if (this.task == 'mail' && (cid = this.get_single_uid())) { - url = { _mbox: this.env.mailbox }; - url[this.env.mailbox == this.env.drafts_mailbox && props != 'new' ? '_draft_uid' : '_uid'] = cid; + else if (this.task == 'mail' && (uid = this.get_single_uid())) { + url = { _mbox: this.get_message_mailbox(uid) }; + url[this.env.mailbox == this.env.drafts_mailbox && props != 'new' ? '_draft_uid' : '_uid'] = uid; this.open_compose_step(url); } break; @@ -1029,7 +1029,7 @@ function rcube_webmail() case 'reply-list': case 'reply': if (uid = this.get_single_uid()) { - url = {_reply_uid: uid, _mbox: this.env.mailbox}; + url = {_reply_uid: uid, _mbox: this.get_message_mailbox(uid)}; if (command == 'reply-all') // do reply-list, when list is detected and popup menu wasn't used url._all = (!props && this.commands['reply-list'] ? 'list' : 'all'); @@ -1057,7 +1057,7 @@ function rcube_webmail() this.gui_objects.messagepartframe.contentWindow.print(); } else if (uid = this.get_single_uid()) { - ref.printwin = this.open_window(this.env.comm_path+'&_action=print&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox)+(this.env.safemode ? '&_safe=1' : ''), true, true); + ref.printwin = this.open_window(this.env.comm_path+'&_action=print&_uid='+uid+'&_mbox='+urlencode(this.get_message_mailbox(uid))+(this.env.safemode ? '&_safe=1' : ''), true, true); if (this.printwin) { if (this.env.action != 'show') this.mark_message('read', uid); @@ -1074,8 +1074,9 @@ function rcube_webmail() if (this.env.action == 'get') { location.href = location.href.replace(/_frame=/, '_download='); } - else if (uid = this.get_single_uid()) - this.goto_url('viewsource', { _uid: uid, _mbox: this.env.mailbox, _save: 1 }); + else if (uid = this.get_single_uid()) { + this.goto_url('viewsource', { _uid: uid, _mbox: this.get_message_mailbox(uid), _save: 1 }); + } break; // quicksearch @@ -1772,6 +1773,7 @@ function rcube_webmail() selected: this.select_all_mode || this.message_list.in_selection(uid), ml: flags.ml?1:0, ctype: flags.ctype, + mbox: flags.mbox, // flags from plugins flags: flags.extra_flags }); @@ -1974,7 +1976,7 @@ function rcube_webmail() var win, target = window, action = preview ? 'preview': 'show', - url = '&_action='+action+'&_uid='+id+'&_mbox='+urlencode(this.env.mailbox); + url = '&_action='+action+'&_uid='+id+'&_mbox='+urlencode(this.get_message_mailbox(id)); if (preview && (win = this.get_frame_window(this.env.contentframe))) { target = win; @@ -6882,6 +6884,13 @@ function rcube_webmail() return this.env.cid ? this.env.cid : (this.contact_list ? this.contact_list.get_single_selection() : null); }; + // get the IMP mailbox of the message with the given UID + this.get_message_mailbox = function(uid) + { + var msg = this.env.messages ? this.env.messages[uid] : {}; + return msg.mbox || this.env.mailbox; + } + // gets cursor position this.get_caret_pos = function(obj) { diff --git a/program/steps/mail/copy.inc b/program/steps/mail/copy.inc index 876657485..0ed0d05a1 100644 --- a/program/steps/mail/copy.inc +++ b/program/steps/mail/copy.inc @@ -25,11 +25,11 @@ if (!$OUTPUT->ajax_call) // move messages if (!empty($_POST['_uid']) && strlen($_POST['_target_mbox'])) { - $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); - $copied = $RCMAIL->storage->copy_message($uids, $target, $mbox); + foreach (rcmail_get_uids() as $mbox => $uids) { + $copied += (int)$RCMAIL->storage->copy_message($uids, $target, $mbox); + } if (!$copied) { // send error message diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index b677c4675..807ec3ce9 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -66,6 +66,21 @@ if (!empty($_REQUEST['_search']) && isset($_SESSION['search']) $OUTPUT->set_env('search_text', $_SESSION['last_text_search']); } +// remove mbox part from _uid +if (($_uid = get_input_value('_uid', RCUBE_INPUT_GPC)) && preg_match('/^\d+-[^,]+$/', $_uid)) { + list($_uid, $mbox) = explode('-', $_uid); + if (isset($_GET['_uid'])) $_GET['_uid'] = $_uid; + if (isset($_POST['_uid'])) $_POST['_uid'] = $_uid; + $_REQUEST['_uid'] = $_uid; + unset($_uid); + + if (empty($_REQUEST['_mbox']) && !empty($mbox)) { + $_GET['_mbox'] = $mbox; + $_POST['_mbox'] = $mbox; + } +} + + // set main env variables, labels and page title if (empty($RCMAIL->action) || $RCMAIL->action == 'list') { // connect to storage server and trigger error on failure @@ -161,6 +176,35 @@ $RCMAIL->register_action_map(array( )); +/** + * Returns message UID(s) and IMAP folder(s) from GET/POST data + * + * @return array List of message UIDs per folder + */ +function rcmail_get_uids() +{ + // message UID (or comma-separated list of IDs) is provided in + // the form of -[,-]* + + $_uid = get_input_value('_uid', RCUBE_INPUT_GPC); + $_mbox = (string)get_input_value('_mbox', RCUBE_INPUT_GPC); + + if (is_array($uid)) { + return $uid; + } + + // create a per-folder UIDs array + $result = array(); + foreach (explode(',', $_uid) as $uid) { + list($uid, $mbox) = explode('-', $uid, 2); + if (empty($mbox)) + $mbox = $_mbox; + $result[$mbox][] = $uid; + } + + return $result; +} + /** * Returns 'to' if current folder is configured Sent or Drafts diff --git a/program/steps/mail/mark.inc b/program/steps/mail/mark.inc index dfc892ea1..fad11d0fc 100644 --- a/program/steps/mail/mark.inc +++ b/program/steps/mail/mark.inc @@ -32,7 +32,7 @@ $a_flags_map = array( $threading = (bool) $RCMAIL->storage->get_threading(); -if (($uids = get_input_value('_uid', RCUBE_INPUT_POST)) && ($flag = get_input_value('_flag', RCUBE_INPUT_POST))) +if (($_uids = get_input_value('_uid', RCUBE_INPUT_POST)) && ($flag = get_input_value('_flag', RCUBE_INPUT_POST))) { $flag = $a_flags_map[$flag] ? $a_flags_map[$flag] : strtoupper($flag); @@ -40,10 +40,12 @@ if (($uids = get_input_value('_uid', RCUBE_INPUT_POST)) && ($flag = get_input_va // count messages before changing anything $old_count = $RCMAIL->storage->count(NULL, $threading ? 'THREADS' : 'ALL'); $old_pages = ceil($old_count / $RCMAIL->storage->get_pagesize()); - $count = sizeof(explode(',', $uids)); } - $marked = $RCMAIL->storage->set_flag($uids, $flag); + foreach (rcmail_get_uids() as $mbox => $uids) { + $marked += (int)$RCMAIL->storage->set_flag($uids, $flag, $mbox); + $count += count($uids); + } if (!$marked) { // send error message @@ -117,7 +119,7 @@ if (($uids = get_input_value('_uid', RCUBE_INPUT_POST)) && ($flag = get_input_va } // add new rows from next page (if any) - if ($count && $uids != '*' && ($jump_back || $nextpage_count > 0)) { + if ($old_count && $_uids != '*' && ($jump_back || $nextpage_count > 0)) { $a_headers = $RCMAIL->storage->list_messages($mbox, NULL, rcmail_sort_column(), rcmail_sort_order(), $jump_back ? NULL : $count); diff --git a/program/steps/mail/move_del.inc b/program/steps/mail/move_del.inc index f15cd2460..e172d7188 100644 --- a/program/steps/mail/move_del.inc +++ b/program/steps/mail/move_del.inc @@ -5,7 +5,7 @@ | program/steps/mail/move_del.inc | | | | This file is part of the Roundcube Webmail client | - | Copyright (C) 2005-2009, The Roundcube Dev Team | + | Copyright (C) 2005-2013, The Roundcube Dev Team | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -30,12 +30,13 @@ $old_pages = ceil($old_count / $RCMAIL->storage->get_pagesize()); // move messages 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); $trash = $RCMAIL->config->get('trash_mbox'); - $moved = $RCMAIL->storage->move_message($uids, $target, $mbox); + foreach (rcmail_get_uids() as $mbox => $uids) { + $moved += (int)$RCMAIL->storage->move_message($uids, $target, $mbox); + $count += count($uids); + } if (!$moved) { // send error message @@ -46,17 +47,17 @@ if ($RCMAIL->action == 'move' && !empty($_POST['_uid']) && strlen($_POST['_targe exit; } else { - $OUTPUT->show_message('messagemoved', 'confirmation'); + $OUTPUT->show_message('messagemoved', 'confirmation'); } $addrows = true; } // delete messages else if ($RCMAIL->action=='delete' && !empty($_POST['_uid'])) { - $count = sizeof(explode(',', ($uids = get_input_value('_uid', RCUBE_INPUT_POST)))); - $mbox = get_input_value('_mbox', RCUBE_INPUT_POST, true); - - $del = $RCMAIL->storage->delete_message($uids, $mbox); + foreach (rcmail_get_uids() as $mbox => $uids) { + $del += (int)$RCMAIL->storage->delete_message($uids, $mbox); + $count += count($uids); + } if (!$del) { // send error message @@ -67,7 +68,7 @@ else if ($RCMAIL->action=='delete' && !empty($_POST['_uid'])) { exit; } else { - $OUTPUT->show_message('messagedeleted', 'confirmation'); + $OUTPUT->show_message('messagedeleted', 'confirmation'); } $addrows = true; diff --git a/program/steps/mail/search.inc b/program/steps/mail/search.inc index fb1b48797..0632b042a 100644 --- a/program/steps/mail/search.inc +++ b/program/steps/mail/search.inc @@ -126,9 +126,23 @@ $_SESSION['search_request'] = $search_request; $result_h = $RCMAIL->storage->list_messages($mbox, 1, $sort_column, rcmail_sort_order()); $count = $RCMAIL->storage->count($mbox, $RCMAIL->storage->get_threading() ? 'THREADS' : 'ALL'); +// Add 'folder' column to list +if ($multi_folder_search) { + $a_show_cols = $_SESSION['list_attrib']['columns'] ? $_SESSION['list_attrib']['columns'] : (array)$CONFIG['list_cols']; + if (!in_array($a_show_cols)) + $a_show_cols[] = 'folder'; + + // make message UIDs unique by appending the folder name + foreach ($result_h as $i => $header) { + $header->uid .= '-'.$header->folder; + if ($header->parent_uid) + $header->parent_uid .= '-'.$header->folder; + } +} + // Make sure we got the headers if (!empty($result_h)) { - rcmail_js_message_list($result_h); + rcmail_js_message_list($result_h, false, $a_show_cols); if ($search_str) $OUTPUT->show_message('searchsuccessful', 'confirmation', array('nr' => $RCMAIL->storage->count(NULL, 'ALL'))); } -- cgit v1.2.3 From 7e3e3ef81ad48f161d01044dcdc2b8cf51811a4f Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 14 Oct 2013 21:57:53 +0200 Subject: First attempt to search in multiple folders; do it multi-threaded using pthreads if available --- program/lib/Roundcube/rcube_imap.php | 80 ++++- program/lib/Roundcube/rcube_imap_search.php | 327 +++++++++++++++++++++ program/lib/Roundcube/rcube_result_multifolder.php | 211 +++++++++++++ program/steps/mail/search.inc | 12 +- 4 files changed, 620 insertions(+), 10 deletions(-) create mode 100644 program/lib/Roundcube/rcube_imap_search.php create mode 100644 program/lib/Roundcube/rcube_result_multifolder.php diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index e0dce6f79..98c8f7a4b 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -910,6 +910,50 @@ class rcube_imap extends rcube_storage return array(); } + // gather messages from a multi-folder search + if ($this->search_set->multi) { + $page_size = $this->page_size; + $sort_field = $this->sort_field; + $search_set = $this->search_set; + + $this->sort_field = null; + $this->page_size = 100; // limit to 100 messages per folder + + $a_msg_headers = array(); + foreach ($search_set->sets as $resultset) { + if (!$resultset->is_empty()) { + $this->search_set = $resultset; + $this->search_threads = $resultset instanceof rcube_result_thread; + $a_msg_headers = array_merge($a_msg_headers, $this->list_search_messages($resultset->get_parameters('MAILBOX'), 1)); + } + } + + // do sorting and paging + $cnt = $search_set->count(); + $from = ($page-1) * $page_size; + $to = $from + $page_size; + + // sort headers + if (!$this->threading) { + $a_msg_headers = $this->conn->sortHeaders($a_msg_headers, $this->sort_field, $this->sort_order); + } + + // only return the requested part of the set + $slice_length = min($page_size, $cnt - ($to > $cnt ? $from : $to)); + $a_msg_headers = array_slice(array_values($a_msg_headers), $from, $slice_length); + + if ($slice) { + $a_msg_headers = array_slice($a_msg_headers, -$slice, $slice); + } + + // restore members + $this->sort_field = $sort_field; + $this->page_size = $page_size; + $this->search_set = $search_set; + + return $a_msg_headers; + } + // use saved messages from searching if ($this->threading) { return $this->list_search_thread_messages($folder, $page, $slice); @@ -1388,11 +1432,33 @@ class rcube_imap extends rcube_storage $str = 'ALL'; } - if (!strlen($folder)) { + if (empty($folder)) { $folder = $this->folder; } - $results = $this->search_index($folder, $str, $charset, $sort_field); + // multi-folder search + if (is_array($folder) && count($folder) > 1 && $str != 'ALL') { + new rcube_result_index; // trigger autoloader and make these classes available for threaded context + new rcube_result_thread; + + // connect IMAP + if (!defined('PTHREADS_INHERIT_ALL')) { + $this->check_connection(); + } + + $searcher = new rcube_imap_search($this->options, $this->conn); + $results = $searcher->exec( + $folder, + $str, + $charset ? $charset : $this->default_charset, + $sort_field && $this->get_capability('SORT') ? $sort_field : null, + $this->threading + ); + } + else { + $folder = is_array($folder) ? $folder[0] : $folder; + $results = $this->search_index($folder, $str, $charset, $sort_field); + } $this->set_search_set(array($str, $results, $charset, $sort_field, $this->threading || $this->search_sorted ? true : false)); @@ -1466,7 +1532,7 @@ class rcube_imap extends rcube_storage // but I've seen that Courier doesn't support UTF-8) if ($threads->is_error() && $charset && $charset != 'US-ASCII') { $threads = $this->conn->thread($folder, $this->threading, - $this->convert_criteria($criteria, $charset), true, 'US-ASCII'); + self::convert_criteria($criteria, $charset), true, 'US-ASCII'); } return $threads; @@ -1480,7 +1546,7 @@ class rcube_imap extends rcube_storage // but I've seen Courier with disabled UTF-8 support) if ($messages->is_error() && $charset && $charset != 'US-ASCII') { $messages = $this->conn->sort($folder, $sort_field, - $this->convert_criteria($criteria, $charset), true, 'US-ASCII'); + self::convert_criteria($criteria, $charset), true, 'US-ASCII'); } if (!$messages->is_error()) { @@ -1495,7 +1561,7 @@ class rcube_imap extends rcube_storage // Error, try with US-ASCII (some servers may support only US-ASCII) if ($messages->is_error() && $charset && $charset != 'US-ASCII') { $messages = $this->conn->search($folder, - $this->convert_criteria($criteria, $charset), true); + self::convert_criteria($criteria, $charset), true); } $this->search_sorted = false; @@ -1513,7 +1579,7 @@ class rcube_imap extends rcube_storage * * @return string Search string */ - protected function convert_criteria($str, $charset, $dest_charset='US-ASCII') + public static function convert_criteria($str, $charset, $dest_charset='US-ASCII') { // convert strings to US_ASCII if (preg_match_all('/\{([0-9]+)\}\r\n/', $str, $matches, PREG_OFFSET_CAPTURE)) { @@ -2375,7 +2441,7 @@ class rcube_imap extends rcube_storage $this->refresh_search(); } else { - $this->search_set->filter(explode(',', $uids)); + $this->search_set->filter(explode(',', $uids), $this->folder); } } diff --git a/program/lib/Roundcube/rcube_imap_search.php b/program/lib/Roundcube/rcube_imap_search.php new file mode 100644 index 000000000..ed4face98 --- /dev/null +++ b/program/lib/Roundcube/rcube_imap_search.php @@ -0,0 +1,327 @@ + | + +-----------------------------------------------------------------------+ +*/ + +// create classes defined by the pthreads module if that isn't installed +if (!defined('PTHREADS_INHERIT_ALL')) { + class Worker { } + class Stackable { } +} + +/** + * Class to control search jobs on multiple IMAP folders. + * This implement a simple threads pool using the pthreads extension. + * + * @package Framework + * @subpackage Storage + * @author Thomas Bruederli + */ +class rcube_imap_search +{ + public $options = array(); + + private $size = 10; + private $next = 0; + private $workers = array(); + private $states = array(); + private $jobs = array(); + private $conn; + + /** + * Default constructor + */ + public function __construct($options, $conn) + { + $this->options = $options; + $this->conn = $conn; + } + + /** + * Invoke search request to IMAP server + * + * @param array $folders List of IMAP folders to search in + * @param string $str Search criteria + * @param string $charset Search charset + * @param string $sort_field Header field to sort by + * @param boolean $threading True if threaded listing is active + */ + public function exec($folders, $str, $charset = null, $sort_field = null, $threading=null) + { + $pthreads = defined('PTHREADS_INHERIT_ALL'); + + // start a search job for every folder to search in + foreach ($folders as $folder) { + $job = new rcube_imap_search_job($folder, $str, $charset, $sort_field, $threading); + if ($pthreads && $this->submit($job)) { + $this->jobs[] = $job; + } + else { + $job->worker = $this; + $job->run(); + $this->jobs[] = $job; + } + } + + // wait for all workers to be done + $this->shutdown(); + + // gather results + $results = new rcube_result_multifolder; + foreach ($this->jobs as $job) { + $results->add($job->get_result()); + } + + return $results; + } + + /** + * Assign the given job object to one of the worker threads for execution + */ + public function submit(Stackable $job) + { + if (count($this->workers) < $this->size) { + $id = count($this->workers); + $this->workers[$id] = new rcube_imap_search_worker($id, $this->options); + $this->workers[$id]->start(PTHREADS_INHERIT_ALL); + + if ($this->workers[$id]->stack($job)) { + return $job; + } + else { + // trigger_error(sprintf("Failed to push Stackable onto %s", $id), E_USER_WARNING); + } + } + if (($worker = $this->workers[$this->next])) { + $this->next = ($this->next+1) % $this->size; + if ($worker->stack($job)) { + return $job; + } + else { + // trigger_error(sprintf("Failed to stack onto selected worker %s", $worker->id), E_USER_WARNING); + } + } + else { + // trigger_error(sprintf("Failed to select a worker for Stackable"), E_USER_WARNING); + } + + return false; + } + + /** + * Shutdown the pool of threads cleanly, retaining exit status locally + */ + public function shutdown() + { + foreach ($this->workers as $worker) { + $this->states[$worker->getThreadId()] = $worker->shutdown(); + $worker->close(); + } + + # console('shutdown', $this->states); + } + + /** + * Get connection to the IMAP server + * (used for single-thread mode) + */ + public function get_imap() + { + return $this->conn; + } +} + + +/** + * Stackable item to run the search on a specific IMAP folder + */ +class rcube_imap_search_job extends Stackable +{ + private $folder; + private $search; + private $charset; + private $sort_field; + private $threading; + private $searchset; + private $result; + private $pagesize = 100; + + public function __construct($folder, $str, $charset = null, $sort_field = null, $threading=false) + { + $this->folder = $folder; + $this->search = $str; + $this->charset = $charset; + $this->sort_field = $sort_field; + $this->threading = $threading; + } + + public function run() + { + #trigger_error("Start search $this->folder", E_USER_NOTICE); + $this->result = $this->search_index(); + #trigger_error("End search $this->folder: " . $this->result->count(), E_USER_NOTICE); + } + + /** + * Copy of rcube_imap::search_index() + */ + protected function search_index() + { + $criteria = $this->search; + $charset = $this->charset; + + $imap = $this->worker->get_imap(); + + if (!$imap->connected()) { + if ($this->threading) { + return new rcube_result_thread(); + } + else { + return new rcube_result_index(); + } + } + + if ($this->worker->options['skip_deleted'] && !preg_match('/UNDELETED/', $criteria)) { + $criteria = 'UNDELETED '.$criteria; + } + + // unset CHARSET if criteria string is ASCII, this way + // SEARCH won't be re-sent after "unsupported charset" response + if ($charset && $charset != 'US-ASCII' && is_ascii($criteria)) { + $charset = 'US-ASCII'; + } + + if ($this->threading) { + $threads = $imap->thread($this->folder, $this->threading, $criteria, true, $charset); + + // Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8, + // but I've seen that Courier doesn't support UTF-8) + if ($threads->is_error() && $charset && $charset != 'US-ASCII') { + $threads = $imap->thread($this->folder, $this->threading, + rcube_imap::convert_criteria($criteria, $charset), true, 'US-ASCII'); + } + + return $threads; + } + + if ($this->sort_field) { + $messages = $imap->sort($this->folder, $this->sort_field, $criteria, true, $charset); + + // Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8, + // but I've seen Courier with disabled UTF-8 support) + if ($messages->is_error() && $charset && $charset != 'US-ASCII') { + $messages = $imap->sort($this->folder, $this->sort_field, + rcube_imap::convert_criteria($criteria, $charset), true, 'US-ASCII'); + } + + if (!$messages->is_error()) { + return $messages; + } + } + + $messages = $imap->search($this->folder, + ($charset && $charset != 'US-ASCII' ? "CHARSET $charset " : '') . $criteria, true); + + // Error, try with US-ASCII (some servers may support only US-ASCII) + if ($messages->is_error() && $charset && $charset != 'US-ASCII') { + $messages = $imap->search($this->folder, + rcube_imap::convert_criteria($criteria, $charset), true); + } + + return $messages; + } + + public function get_search_set() + { + return array( + $this->search, + $this->result, + $this->charset, + $this->sort_field, + $this->threading, + ); + } + + public function get_result() + { + return $this->result; + } +} + + +/** + * Wrker thread to run search jobs while maintaining a common context + */ +class rcube_imap_search_worker extends Worker +{ + public $id; + public $options; + + private $conn; + + /** + * Default constructor + */ + public function __construct($id, $options) + { + $this->id = $id; + $this->options = $options; + } + + /** + * Get a dedicated connection to the IMAP server + */ + public function get_imap() + { + // TODO: make this connection persistent for several jobs + #if ($this->conn) + # return $this->conn; + + $conn = new rcube_imap_generic(); + # $conn->setDebug(true, function($conn, $message){ trigger_error($message, E_USER_NOTICE); }); + + if ($this->options['user'] && $this->options['password']) { + $conn->connect($this->options['host'], $this->options['user'], $this->options['password'], $this->options); + } + + if ($conn->error) + trigger_error($this->conn->error, E_USER_WARNING); + + #$this->conn = $conn; + return $conn; + } + + /** + * @override + */ + public function run() + { + + } + + /** + * Close IMAP connection + */ + public function close() + { + if ($this->conn) { + $this->conn->close(); + } + } +} + diff --git a/program/lib/Roundcube/rcube_result_multifolder.php b/program/lib/Roundcube/rcube_result_multifolder.php new file mode 100644 index 000000000..8d7ae5de8 --- /dev/null +++ b/program/lib/Roundcube/rcube_result_multifolder.php @@ -0,0 +1,211 @@ + | + +-----------------------------------------------------------------------+ +*/ + +/** + * Class holding a set of rcube_result_index instances that together form a + * result set of a multi-folder search + * + * @package Framework + * @subpackage Storage + */ +class rcube_result_multifolder +{ + public $multi = true; + public $sets = array(); + + protected $meta = array(); + protected $order = 'ASC'; + + + /** + * Object constructor. + */ + public function __construct() + { + $this->meta = array('count' => 0); + } + + + /** + * Initializes object with SORT command response + * + * @param string $data IMAP response string + */ + public function add($result) + { + $this->sets[] = $result; + $this->meta['count'] += $result->count(); + } + + + /** + * Checks the result from IMAP command + * + * @return bool True if the result is an error, False otherwise + */ + public function is_error() + { + return false; + } + + + /** + * Checks if the result is empty + * + * @return bool True if the result is empty, False otherwise + */ + public function is_empty() + { + return empty($this->sets) || $this->meta['count'] == 0; + } + + + /** + * Returns number of elements in the result + * + * @return int Number of elements + */ + public function count() + { + return $this->meta['count']; + } + + + /** + * Returns number of elements in the result. + * Alias for count() for compatibility with rcube_result_thread + * + * @return int Number of elements + */ + public function count_messages() + { + return $this->count(); + } + + + /** + * Reverts order of elements in the result + */ + public function revert() + { + $this->order = $this->order == 'ASC' ? 'DESC' : 'ASC'; + } + + + /** + * Check if the given message ID exists in the object + * + * @param int $msgid Message ID + * @param bool $get_index When enabled element's index will be returned. + * Elements are indexed starting with 0 + * @return mixed False if message ID doesn't exist, True if exists or + * index of the element if $get_index=true + */ + public function exists($msgid, $get_index = false) + { + return false; + } + + + /** + * Filters data set. Removes elements listed in $ids list. + * + * @param array $ids List of IDs to remove. + * @param string $folder IMAP folder + */ + public function filter($ids = array(), $folder = null) + { + $this->meta['count'] = 0; + foreach ($this->sets as $set) { + if ($set->get_parameters('MAILBOX') == $folder) { + $set->filter($ids); + } + $this->meta['count'] += $set->count(); + } + } + + /** + * Filters data set. Removes elements not listed in $ids list. + * + * @param array $ids List of IDs to keep. + */ + public function intersect($ids = array()) + { + // not implemented + } + + /** + * Return all messages in the result. + * + * @return array List of message IDs + */ + public function get() + { + return array(); + } + + + /** + * Return all messages in the result. + * + * @return array List of message IDs + */ + public function get_compressed() + { + return ''; + } + + + /** + * Return result element at specified index + * + * @param int|string $index Element's index or "FIRST" or "LAST" + * + * @return int Element value + */ + public function get_element($index) + { + return null; + } + + + /** + * Returns response parameters, e.g. ESEARCH's MIN/MAX/COUNT/ALL/MODSEQ + * or internal data e.g. MAILBOX, ORDER + * + * @param string $param Parameter name + * + * @return array|string Response parameters or parameter value + */ + public function get_parameters($param=null) + { + return $params; + } + + + /** + * Returns length of internal data representation + * + * @return int Data length + */ + protected function length() + { + return $this->count(); + } +} diff --git a/program/steps/mail/search.inc b/program/steps/mail/search.inc index 0632b042a..7d128c73c 100644 --- a/program/steps/mail/search.inc +++ b/program/steps/mail/search.inc @@ -107,9 +107,12 @@ if (!empty($subject)) { $search_str = trim($search_str); $sort_column = rcmail_sort_column(); +// TEMPORARY: search all folders +$mboxes = $RCMAIL->storage->list_folders_subscribed('', '*', 'mail'); + // execute IMAP search if ($search_str) - $RCMAIL->storage->search($mbox, $search_str, $imap_charset, $sort_column); + $RCMAIL->storage->search($mboxes, $search_str, $imap_charset, $sort_column); // save search results in session if (!is_array($_SESSION['search'])) @@ -127,17 +130,20 @@ $result_h = $RCMAIL->storage->list_messages($mbox, 1, $sort_column, rcmail_sort_ $count = $RCMAIL->storage->count($mbox, $RCMAIL->storage->get_threading() ? 'THREADS' : 'ALL'); // Add 'folder' column to list -if ($multi_folder_search) { +if ($_SESSION['search'][1]->multi) { $a_show_cols = $_SESSION['list_attrib']['columns'] ? $_SESSION['list_attrib']['columns'] : (array)$CONFIG['list_cols']; - if (!in_array($a_show_cols)) + if (!in_array('folder', $a_show_cols)) $a_show_cols[] = 'folder'; // make message UIDs unique by appending the folder name foreach ($result_h as $i => $header) { $header->uid .= '-'.$header->folder; + $header->flags['skip_mbox_check'] = true; if ($header->parent_uid) $header->parent_uid .= '-'.$header->folder; } + + $OUTPUT->command('select_folder', ''); } // Make sure we got the headers -- cgit v1.2.3 From 010a350715f1a36eab666fe26d3118ed025133c1 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Tue, 15 Oct 2013 11:44:34 +0200 Subject: Minor improvements to threaded searching --- program/lib/Roundcube/rcube_imap.php | 8 +++---- program/lib/Roundcube/rcube_imap_search.php | 36 ++++++++++++++++++----------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index 98c8f7a4b..ff88bdccb 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -934,7 +934,7 @@ class rcube_imap extends rcube_storage $to = $from + $page_size; // sort headers - if (!$this->threading) { + if (!$this->threading && !empty($a_msg_headers)) { $a_msg_headers = $this->conn->sortHeaders($a_msg_headers, $this->sort_field, $this->sort_order); } @@ -1441,10 +1441,8 @@ class rcube_imap extends rcube_storage new rcube_result_index; // trigger autoloader and make these classes available for threaded context new rcube_result_thread; - // connect IMAP - if (!defined('PTHREADS_INHERIT_ALL')) { - $this->check_connection(); - } + // connect IMAP to have all the required classes and settings loaded + $this->check_connection(); $searcher = new rcube_imap_search($this->options, $this->conn); $results = $searcher->exec( diff --git a/program/lib/Roundcube/rcube_imap_search.php b/program/lib/Roundcube/rcube_imap_search.php index ed4face98..d82ec8a24 100644 --- a/program/lib/Roundcube/rcube_imap_search.php +++ b/program/lib/Roundcube/rcube_imap_search.php @@ -172,9 +172,9 @@ class rcube_imap_search_job extends Stackable public function run() { - #trigger_error("Start search $this->folder", E_USER_NOTICE); + // trigger_error("Start search $this->folder", E_USER_NOTICE); $this->result = $this->search_index(); - #trigger_error("End search $this->folder: " . $this->result->count(), E_USER_NOTICE); + // trigger_error("End search $this->folder: " . $this->result->count(), E_USER_NOTICE); } /** @@ -182,6 +182,7 @@ class rcube_imap_search_job extends Stackable */ protected function search_index() { + $pthreads = defined('PTHREADS_INHERIT_ALL'); $criteria = $this->search; $charset = $this->charset; @@ -216,6 +217,10 @@ class rcube_imap_search_job extends Stackable rcube_imap::convert_criteria($criteria, $charset), true, 'US-ASCII'); } + // close IMAP connection again + if ($pthreads) + $imap->closeConnection(); + return $threads; } @@ -228,21 +233,23 @@ class rcube_imap_search_job extends Stackable $messages = $imap->sort($this->folder, $this->sort_field, rcube_imap::convert_criteria($criteria, $charset), true, 'US-ASCII'); } - - if (!$messages->is_error()) { - return $messages; - } } - $messages = $imap->search($this->folder, - ($charset && $charset != 'US-ASCII' ? "CHARSET $charset " : '') . $criteria, true); - - // Error, try with US-ASCII (some servers may support only US-ASCII) - if ($messages->is_error() && $charset && $charset != 'US-ASCII') { + if (!$messages || !$messages->is_error()) { $messages = $imap->search($this->folder, - rcube_imap::convert_criteria($criteria, $charset), true); + ($charset && $charset != 'US-ASCII' ? "CHARSET $charset " : '') . $criteria, true); + + // Error, try with US-ASCII (some servers may support only US-ASCII) + if ($messages->is_error() && $charset && $charset != 'US-ASCII') { + $messages = $imap->search($this->folder, + rcube_imap::convert_criteria($criteria, $charset), true); + } } + // close IMAP connection again + if ($pthreads) + $imap->closeConnection(); + return $messages; } @@ -279,6 +286,8 @@ class rcube_imap_search_worker extends Worker */ public function __construct($id, $options) { + $options['ident']['command'] = 'search-'.$id; + $this->id = $id; $this->options = $options; } @@ -296,11 +305,12 @@ class rcube_imap_search_worker extends Worker # $conn->setDebug(true, function($conn, $message){ trigger_error($message, E_USER_NOTICE); }); if ($this->options['user'] && $this->options['password']) { + // TODO: do this synchronized to avoid warnings like "Only one Id allowed in non-authenticated state" $conn->connect($this->options['host'], $this->options['user'], $this->options['password'], $this->options); } if ($conn->error) - trigger_error($this->conn->error, E_USER_WARNING); + trigger_error($conn->error, E_USER_WARNING); #$this->conn = $conn; return $conn; -- cgit v1.2.3 From 017c4f19fd91aa1f658131fae272617a14598a12 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 9 Oct 2013 12:02:52 +0200 Subject: Refactor multi-threaded autocomple contact searching to make it available for other purposes, too --- plugins/acl/acl.php | 4 +- program/js/app.js | 200 ++++++++++++++++++++++++++---------- program/steps/mail/autocomplete.inc | 4 +- 3 files changed, 147 insertions(+), 61 deletions(-) diff --git a/plugins/acl/acl.php b/plugins/acl/acl.php index a840bcd58..203340bdb 100644 --- a/plugins/acl/acl.php +++ b/plugins/acl/acl.php @@ -86,7 +86,7 @@ class acl extends rcube_plugin $this->load_config(); $search = rcube_utils::get_input_value('_search', rcube_utils::INPUT_GPC, true); - $sid = rcube_utils::get_input_value('_id', rcube_utils::INPUT_GPC); + $reqid = rcube_utils::get_input_value('_reqid', rcube_utils::INPUT_GPC); $users = array(); if ($this->init_ldap()) { @@ -115,7 +115,7 @@ class acl extends rcube_plugin sort($users, SORT_LOCALE_STRING); - $this->rc->output->command('ksearch_query_results', $users, $search, $sid); + $this->rc->output->command('ksearch_query_results', $users, $search, $reqid); $this->rc->output->send(); } diff --git a/program/js/app.js b/program/js/app.js index e89e85e52..654faa024 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -31,6 +31,7 @@ function rcube_webmail() this.onloads = []; this.messages = {}; this.group2expand = {}; + this.http_request_jobs = {}; // webmail client settings this.dblclick_time = 500; @@ -4338,7 +4339,7 @@ function rcube_webmail() p = inp_value.lastIndexOf(this.env.recipients_separator, cpos-1), q = inp_value.substring(p+1, cpos), min = this.env.autocomplete_min_length, - ac = this.ksearch_data; + data = this.ksearch_data; // trim query string q = $.trim(q); @@ -4365,34 +4366,26 @@ function rcube_webmail() return; // ...new search value contains old one and previous search was not finished or its result was empty - if (old_value && old_value.length && q.startsWith(old_value) && (!ac || ac.num <= 0) && this.env.contacts && !this.env.contacts.length) + if (old_value && old_value.length && q.startsWith(old_value) && (!data || data.num <= 0) && this.env.contacts && !this.env.contacts.length) return; - var i, lock, source, xhr, reqid = new Date().getTime(), - post_data = {_search: q, _id: reqid}, - threads = props && props.threads ? props.threads : 1, - sources = props && props.sources ? props.sources : [], - action = props && props.action ? props.action : 'mail/autocomplete'; - - this.ksearch_data = {id: reqid, sources: sources.slice(), action: action, - locks: [], requests: [], num: sources.length}; - - for (i=0; i 1 && source === undefined) - break; - - post_data._source = source ? source : ''; - lock = this.display_message(this.get_label('searching'), 'loading'); - xhr = this.http_post(action, post_data, lock); + var sources = props && props.sources ? props.sources : ['']; + var reqid = this.multi_thread_http_request({ + items: sources, + threads: props && props.threads ? props.threads : 1, + action: props && props.action ? props.action : 'mail/autocomplete', + postdata: { _search:q, _source:'%s' }, + lock: this.display_message(this.get_label('searching'), 'loading') + }); - this.ksearch_data.locks.push(lock); - this.ksearch_data.requests.push(xhr); - } + this.ksearch_data = { id:reqid, sources:sources.slice(), num:sources.length }; }; this.ksearch_query_results = function(results, search, reqid) { + // trigger multi-thread http response callback + this.multi_thread_http_response(results, reqid); + // search stopped in meantime? if (!this.ksearch_value) return; @@ -4404,7 +4397,6 @@ function rcube_webmail() // display search results var i, len, ul, li, text, init, value = this.ksearch_value, - data = this.ksearch_data, maxlen = this.env.autocomplete_max ? this.env.autocomplete_max : 15; // create results pane if not present @@ -4458,27 +4450,8 @@ function rcube_webmail() if (len) this.env.contacts = this.env.contacts.concat(results); - // run next parallel search - if (data.id == reqid) { - data.num--; - if (maxlen > 0 && data.sources.length) { - var lock, xhr, source = data.sources.shift(), post_data; - if (source) { - post_data = {_search: value, _id: reqid, _source: source}; - lock = this.display_message(this.get_label('searching'), 'loading'); - xhr = this.http_post(data.action, post_data, lock); - - this.ksearch_data.locks.push(lock); - this.ksearch_data.requests.push(xhr); - } - } - else if (!maxlen) { - if (!this.ksearch_msg) - this.ksearch_msg = this.display_message(this.get_label('autocompletemore')); - // abort pending searches - this.ksearch_abort(); - } - } + if (this.ksearch_data.id == reqid) + this.ksearch_data.num--; }; this.ksearch_click = function(node) @@ -4513,7 +4486,8 @@ function rcube_webmail() // Clears autocomplete data/requests this.ksearch_destroy = function() { - this.ksearch_abort(); + if (this.ksearch_data) + this.multi_thread_request_abort(this.ksearch_data.id); if (this.ksearch_info) this.hide_message(this.ksearch_info); @@ -4524,18 +4498,6 @@ function rcube_webmail() this.ksearch_data = null; this.ksearch_info = null; this.ksearch_msg = null; - } - - // Aborts pending autocomplete requests - this.ksearch_abort = function() - { - var i, len, ac = this.ksearch_data; - - if (!ac) - return; - - for (i=0, len=ac.locks.length; i 0 && i < prop.requests.length; i++) { + if (prop.requests[i].abort) + prop.requests[i].abort(); + } + + prop.running = 0; + prop.cancelled = true; + this.set_busy(false, '', prop.lock); + } + }; + // post the given form to a hidden iframe this.async_upload_form = function(form, action, onload) { diff --git a/program/steps/mail/autocomplete.inc b/program/steps/mail/autocomplete.inc index c15de92cf..5b8b49dba 100644 --- a/program/steps/mail/autocomplete.inc +++ b/program/steps/mail/autocomplete.inc @@ -49,7 +49,7 @@ $mode = (int) $RCMAIL->config->get('addressbook_search_mode'); $single = (bool) $RCMAIL->config->get('autocomplete_single'); $search = rcube_utils::get_input_value('_search', rcube_utils::INPUT_GPC, true); $source = rcube_utils::get_input_value('_source', rcube_utils::INPUT_GPC); -$sid = rcube_utils::get_input_value('_id', rcube_utils::INPUT_GPC); +$reqid = rcube_utils::get_input_value('_reqid', rcube_utils::INPUT_GPC); if (strlen($source)) { $book_types = array($source); @@ -154,5 +154,5 @@ if (!empty($book_types) && strlen($search)) { } } -$OUTPUT->command('ksearch_query_results', $contacts, $search, $sid); +$OUTPUT->command('ksearch_query_results', $contacts, $search, $reqid); $OUTPUT->send(); -- cgit v1.2.3 From 628706acdcb973154161b5862c30ce706a08455a Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 9 Oct 2013 12:04:17 +0200 Subject: Prepare message list to display IMAP folder --- program/js/app.js | 4 +++- program/js/list.js | 14 ++++++++++++++ program/lib/Roundcube/rcube_imap.php | 1 + program/lib/Roundcube/rcube_message_header.php | 9 +++++++++ program/steps/mail/func.inc | 4 +++- skins/larry/mail.css | 8 ++++++++ 6 files changed, 38 insertions(+), 2 deletions(-) diff --git a/program/js/app.js b/program/js/app.js index 654faa024..55387c0ed 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -6399,8 +6399,10 @@ function rcube_webmail() if ((n = $.inArray('status', this.env.coltypes)) >= 0) this.env.status_col = n; - if (list) + if (list) { + list.hide_column('folder', !(this.env.search_request || this.env.search_id)); list.init_header(); + } }; // replace content of row count display diff --git a/program/js/list.js b/program/js/list.js index 022ed3180..319807eae 100644 --- a/program/js/list.js +++ b/program/js/list.js @@ -381,6 +381,20 @@ blur: function() }, +/** + * Set/unset the given column as hidden + */ +hide_column: function(col, hide) +{ + var method = hide ? 'addClass' : 'removeClass'; + + if (this.fixed_header) + $(this.row_tagname()+' '+this.col_tagname()+'.'+col, this.fixed_header)[method]('hidden'); + + $(this.row_tagname()+' '+this.col_tagname()+'.'+col, this.list)[method]('hidden'); +}, + + /** * onmousedown-handler of message list column */ diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index 432227091..adac77b23 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -1111,6 +1111,7 @@ class rcube_imap extends rcube_storage } foreach ($headers as $h) { + $h->folder = $folder; $a_msg_headers[$h->uid] = $h; } diff --git a/program/lib/Roundcube/rcube_message_header.php b/program/lib/Roundcube/rcube_message_header.php index 2c5e2b6c8..2bda930eb 100644 --- a/program/lib/Roundcube/rcube_message_header.php +++ b/program/lib/Roundcube/rcube_message_header.php @@ -166,6 +166,13 @@ class rcube_message_header */ public $mdn_to; + /** + * IMAP folder this message is stored in + * + * @var string + */ + public $folder; + /** * Other message headers * @@ -189,6 +196,8 @@ class rcube_message_header 'reply-to' => 'replyto', 'cc' => 'cc', 'bcc' => 'bcc', + 'mbox' => 'folder', + 'folder' => 'folder', 'content-transfer-encoding' => 'encoding', 'in-reply-to' => 'in_reply_to', 'content-type' => 'ctype', diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index 0211fabc4..7436544be 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -398,6 +398,8 @@ function rcmail_js_message_list($a_headers, $insert_top=FALSE, $a_show_cols=null $cont = show_bytes($header->$col); else if ($col == 'date') $cont = $RCMAIL->format_date($header->date); + else if ($col == 'folder') + $cont = rcube::Q(rcube_charset::convert($header->folder, 'UTF7-IMAP')); else $cont = rcube::Q($header->$col); @@ -421,7 +423,7 @@ function rcmail_js_message_list($a_headers, $insert_top=FALSE, $a_show_cols=null $a_msg_flags['prio'] = (int) $header->priority; $a_msg_flags['ctype'] = rcube::Q($header->ctype); - $a_msg_flags['mbox'] = $mbox; + $a_msg_flags['mbox'] = $header->folder; // merge with plugin result (Deprecated, use $header->flags) if (!empty($header->list_flags) && is_array($header->list_flags)) diff --git a/skins/larry/mail.css b/skins/larry/mail.css index 8e6e78e77..bf77201fd 100644 --- a/skins/larry/mail.css +++ b/skins/larry/mail.css @@ -497,6 +497,14 @@ table.messagelist.fixedcopy { width: 155px; } +.messagelist tr td.folder { + width: 135px; +} + +.messagelist tr td.hidden { + display: none; +} + .messagelist tr.message { /* background-color: #fff; */ } -- cgit v1.2.3 From 9684dc018f68b037e8ee369e7ed08f4c760fe736 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 9 Oct 2013 17:12:30 +0200 Subject: Support globally unique message UIDs with IMAP folder name appended --- program/js/app.js | 27 ++++++++++++++++--------- program/steps/mail/copy.inc | 6 +++--- program/steps/mail/func.inc | 44 +++++++++++++++++++++++++++++++++++++++++ program/steps/mail/mark.inc | 10 ++++++---- program/steps/mail/move_del.inc | 22 +++++++++++---------- program/steps/mail/search.inc | 16 ++++++++++++++- 6 files changed, 98 insertions(+), 27 deletions(-) diff --git a/program/js/app.js b/program/js/app.js index 55387c0ed..2717e35d5 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -685,7 +685,7 @@ function rcube_webmail() case 'open': if (uid = this.get_single_uid()) { - obj.href = this.url('show', {_mbox: this.env.mailbox, _uid: uid}); + obj.href = this.url('show', {_mbox: this.get_message_mailbox(uid), _uid: uid}); return true; } break; @@ -788,9 +788,9 @@ function rcube_webmail() this.load_contact(cid, 'edit'); else if (this.task == 'settings' && props) this.load_identity(props, 'edit-identity'); - else if (this.task == 'mail' && (cid = this.get_single_uid())) { - url = { _mbox: this.env.mailbox }; - url[this.env.mailbox == this.env.drafts_mailbox && props != 'new' ? '_draft_uid' : '_uid'] = cid; + else if (this.task == 'mail' && (uid = this.get_single_uid())) { + url = { _mbox: this.get_message_mailbox(uid) }; + url[this.env.mailbox == this.env.drafts_mailbox && props != 'new' ? '_draft_uid' : '_uid'] = uid; this.open_compose_step(url); } break; @@ -1070,7 +1070,7 @@ function rcube_webmail() case 'reply-list': case 'reply': if (uid = this.get_single_uid()) { - url = {_reply_uid: uid, _mbox: this.env.mailbox}; + url = {_reply_uid: uid, _mbox: this.get_message_mailbox(uid)}; if (command == 'reply-all') // do reply-list, when list is detected and popup menu wasn't used url._all = (!props && this.env.reply_all_mode == 1 && this.commands['reply-list'] ? 'list' : 'all'); @@ -1098,7 +1098,7 @@ function rcube_webmail() this.gui_objects.messagepartframe.contentWindow.print(); } else if (uid = this.get_single_uid()) { - ref.printwin = this.open_window(this.env.comm_path+'&_action=print&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox)+(this.env.safemode ? '&_safe=1' : ''), true, true); + ref.printwin = this.open_window(this.env.comm_path+'&_action=print&_uid='+uid+'&_mbox='+urlencode(this.get_message_mailbox(uid))+(this.env.safemode ? '&_safe=1' : ''), true, true); if (this.printwin) { if (this.env.action != 'show') this.mark_message('read', uid); @@ -1115,8 +1115,9 @@ function rcube_webmail() if (this.env.action == 'get') { location.href = location.href.replace(/_frame=/, '_download='); } - else if (uid = this.get_single_uid()) - this.goto_url('viewsource', { _uid: uid, _mbox: this.env.mailbox, _save: 1 }); + else if (uid = this.get_single_uid()) { + this.goto_url('viewsource', { _uid: uid, _mbox: this.get_message_mailbox(uid), _save: 1 }); + } break; // quicksearch @@ -1820,6 +1821,7 @@ function rcube_webmail() selected: this.select_all_mode || this.message_list.in_selection(uid), ml: flags.ml?1:0, ctype: flags.ctype, + mbox: flags.mbox, // flags from plugins flags: flags.extra_flags }); @@ -2022,7 +2024,7 @@ function rcube_webmail() var win, target = window, action = preview ? 'preview': 'show', - url = '&_action='+action+'&_uid='+id+'&_mbox='+urlencode(this.env.mailbox); + url = '&_action='+action+'&_uid='+id+'&_mbox='+urlencode(this.get_message_mailbox(id)); if (preview && (win = this.get_frame_window(this.env.contentframe))) { target = win; @@ -7424,6 +7426,13 @@ function rcube_webmail() return this.env.cid ? this.env.cid : (this.contact_list ? this.contact_list.get_single_selection() : null); }; + // get the IMP mailbox of the message with the given UID + this.get_message_mailbox = function(uid) + { + var msg = this.env.messages ? this.env.messages[uid] : {}; + return msg.mbox || this.env.mailbox; + } + // gets cursor position this.get_caret_pos = function(obj) { diff --git a/program/steps/mail/copy.inc b/program/steps/mail/copy.inc index a392f309f..0f7b1a03a 100644 --- a/program/steps/mail/copy.inc +++ b/program/steps/mail/copy.inc @@ -26,11 +26,11 @@ if (!$OUTPUT->ajax_call) { // move messages if (!empty($_POST['_uid']) && strlen($_POST['_target_mbox'])) { - $uids = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST); $target = rcube_utils::get_input_value('_target_mbox', rcube_utils::INPUT_POST, true); - $mbox = rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_POST, true); - $copied = $RCMAIL->storage->copy_message($uids, $target, $mbox); + foreach (rcmail_get_uids() as $mbox => $uids) { + $copied += (int)$RCMAIL->storage->copy_message($uids, $target, $mbox); + } if (!$copied) { // send error message diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index 7436544be..cc2c7e0b5 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -68,6 +68,21 @@ if (!empty($_REQUEST['_search']) && isset($_SESSION['search']) $OUTPUT->set_env('search_text', $_SESSION['last_text_search']); } +// remove mbox part from _uid +if (($_uid = get_input_value('_uid', RCUBE_INPUT_GPC)) && preg_match('/^\d+-[^,]+$/', $_uid)) { + list($_uid, $mbox) = explode('-', $_uid); + if (isset($_GET['_uid'])) $_GET['_uid'] = $_uid; + if (isset($_POST['_uid'])) $_POST['_uid'] = $_uid; + $_REQUEST['_uid'] = $_uid; + unset($_uid); + + if (empty($_REQUEST['_mbox']) && !empty($mbox)) { + $_GET['_mbox'] = $mbox; + $_POST['_mbox'] = $mbox; + } +} + + // set main env variables, labels and page title if (empty($RCMAIL->action) || $RCMAIL->action == 'list') { // connect to storage server and trigger error on failure @@ -166,6 +181,35 @@ $RCMAIL->register_action_map(array( )); +/** + * Returns message UID(s) and IMAP folder(s) from GET/POST data + * + * @return array List of message UIDs per folder + */ +function rcmail_get_uids() +{ + // message UID (or comma-separated list of IDs) is provided in + // the form of -[,-]* + + $_uid = get_input_value('_uid', RCUBE_INPUT_GPC); + $_mbox = (string)get_input_value('_mbox', RCUBE_INPUT_GPC); + + if (is_array($uid)) { + return $uid; + } + + // create a per-folder UIDs array + $result = array(); + foreach (explode(',', $_uid) as $uid) { + list($uid, $mbox) = explode('-', $uid, 2); + if (empty($mbox)) + $mbox = $_mbox; + $result[$mbox][] = $uid; + } + + return $result; +} + /** * Returns default search mods diff --git a/program/steps/mail/mark.inc b/program/steps/mail/mark.inc index daa8c7e54..50243c636 100644 --- a/program/steps/mail/mark.inc +++ b/program/steps/mail/mark.inc @@ -36,7 +36,7 @@ $a_flags_map = array( 'unflagged' => 'UNFLAGGED', ); -if (($uids = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST)) +if (($_uids = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST)) && ($flag = rcube_utils::get_input_value('_flag', rcube_utils::INPUT_POST)) ) { $flag = $a_flags_map[$flag] ? $a_flags_map[$flag] : strtoupper($flag); @@ -45,10 +45,12 @@ if (($uids = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST)) // count messages before changing anything $old_count = $RCMAIL->storage->count(NULL, $threading ? 'THREADS' : 'ALL'); $old_pages = ceil($old_count / $RCMAIL->storage->get_pagesize()); - $count = sizeof(explode(',', $uids)); } - $marked = $RCMAIL->storage->set_flag($uids, $flag); + foreach (rcmail_get_uids() as $mbox => $uids) { + $marked += (int)$RCMAIL->storage->set_flag($uids, $flag, $mbox); + $count += count($uids); + } if (!$marked) { // send error message @@ -128,7 +130,7 @@ if (($uids = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST)) } // add new rows from next page (if any) - if ($count && $uids != '*' && ($jump_back || $nextpage_count > 0)) { + if ($old_count && $_uids != '*' && ($jump_back || $nextpage_count > 0)) { $a_headers = $RCMAIL->storage->list_messages($mbox, NULL, rcmail_sort_column(), rcmail_sort_order(), $jump_back ? NULL : $count); diff --git a/program/steps/mail/move_del.inc b/program/steps/mail/move_del.inc index 7564bb89d..26c724597 100644 --- a/program/steps/mail/move_del.inc +++ b/program/steps/mail/move_del.inc @@ -5,7 +5,7 @@ | program/steps/mail/move_del.inc | | | | This file is part of the Roundcube Webmail client | - | Copyright (C) 2005-2009, The Roundcube Dev Team | + | Copyright (C) 2005-2013, The Roundcube Dev Team | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -32,11 +32,13 @@ $trash = $RCMAIL->config->get('trash_mbox'); // move messages if ($RCMAIL->action == 'move' && !empty($_POST['_uid']) && strlen($_POST['_target_mbox'])) { - $count = sizeof(explode(',', ($uids = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST)))); $target = rcube_utils::get_input_value('_target_mbox', rcube_utils::INPUT_POST, true); - $mbox = rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_POST, true); + $trash = $RCMAIL->config->get('trash_mbox'); - $moved = $RCMAIL->storage->move_message($uids, $target, $mbox); + foreach (rcmail_get_uids() as $mbox => $uids) { + $moved += (int)$RCMAIL->storage->move_message($uids, $target, $mbox); + $count += count($uids); + } if (!$moved) { // send error message @@ -47,17 +49,17 @@ if ($RCMAIL->action == 'move' && !empty($_POST['_uid']) && strlen($_POST['_targe exit; } else { - $OUTPUT->show_message('messagemoved', 'confirmation'); + $OUTPUT->show_message('messagemoved', 'confirmation'); } $addrows = true; } // delete messages else if ($RCMAIL->action=='delete' && !empty($_POST['_uid'])) { - $count = sizeof(explode(',', ($uids = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST)))); - $mbox = rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_POST, true); - - $del = $RCMAIL->storage->delete_message($uids, $mbox); + foreach (rcmail_get_uids() as $mbox => $uids) { + $del += (int)$RCMAIL->storage->delete_message($uids, $mbox); + $count += count($uids); + } if (!$del) { // send error message @@ -68,7 +70,7 @@ else if ($RCMAIL->action=='delete' && !empty($_POST['_uid'])) { exit; } else { - $OUTPUT->show_message('messagedeleted', 'confirmation'); + $OUTPUT->show_message('messagedeleted', 'confirmation'); } $addrows = true; diff --git a/program/steps/mail/search.inc b/program/steps/mail/search.inc index a80887254..9f4cdc9c9 100644 --- a/program/steps/mail/search.inc +++ b/program/steps/mail/search.inc @@ -127,9 +127,23 @@ $_SESSION['search_request'] = $search_request; $result_h = $RCMAIL->storage->list_messages($mbox, 1, $sort_column, rcmail_sort_order()); $count = $RCMAIL->storage->count($mbox, $RCMAIL->storage->get_threading() ? 'THREADS' : 'ALL'); +// Add 'folder' column to list +if ($multi_folder_search) { + $a_show_cols = $_SESSION['list_attrib']['columns'] ? $_SESSION['list_attrib']['columns'] : (array)$CONFIG['list_cols']; + if (!in_array($a_show_cols)) + $a_show_cols[] = 'folder'; + + // make message UIDs unique by appending the folder name + foreach ($result_h as $i => $header) { + $header->uid .= '-'.$header->folder; + if ($header->parent_uid) + $header->parent_uid .= '-'.$header->folder; + } +} + // Make sure we got the headers if (!empty($result_h)) { - rcmail_js_message_list($result_h); + rcmail_js_message_list($result_h, false, $a_show_cols); if ($search_str) { $OUTPUT->show_message('searchsuccessful', 'confirmation', array('nr' => $RCMAIL->storage->count(NULL, 'ALL'))); } -- cgit v1.2.3 From 566747af00ae413c942a7c6702e24c044af36f17 Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 14 Oct 2013 21:57:53 +0200 Subject: First attempt to search in multiple folders; do it multi-threaded using pthreads if available --- program/lib/Roundcube/rcube_imap.php | 80 ++++- program/lib/Roundcube/rcube_imap_search.php | 327 +++++++++++++++++++++ program/lib/Roundcube/rcube_result_multifolder.php | 211 +++++++++++++ program/steps/mail/search.inc | 10 +- 4 files changed, 619 insertions(+), 9 deletions(-) create mode 100644 program/lib/Roundcube/rcube_imap_search.php create mode 100644 program/lib/Roundcube/rcube_result_multifolder.php diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index adac77b23..db94e7678 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -945,6 +945,50 @@ class rcube_imap extends rcube_storage return array(); } + // gather messages from a multi-folder search + if ($this->search_set->multi) { + $page_size = $this->page_size; + $sort_field = $this->sort_field; + $search_set = $this->search_set; + + $this->sort_field = null; + $this->page_size = 100; // limit to 100 messages per folder + + $a_msg_headers = array(); + foreach ($search_set->sets as $resultset) { + if (!$resultset->is_empty()) { + $this->search_set = $resultset; + $this->search_threads = $resultset instanceof rcube_result_thread; + $a_msg_headers = array_merge($a_msg_headers, $this->list_search_messages($resultset->get_parameters('MAILBOX'), 1)); + } + } + + // do sorting and paging + $cnt = $search_set->count(); + $from = ($page-1) * $page_size; + $to = $from + $page_size; + + // sort headers + if (!$this->threading) { + $a_msg_headers = $this->conn->sortHeaders($a_msg_headers, $this->sort_field, $this->sort_order); + } + + // only return the requested part of the set + $slice_length = min($page_size, $cnt - ($to > $cnt ? $from : $to)); + $a_msg_headers = array_slice(array_values($a_msg_headers), $from, $slice_length); + + if ($slice) { + $a_msg_headers = array_slice($a_msg_headers, -$slice, $slice); + } + + // restore members + $this->sort_field = $sort_field; + $this->page_size = $page_size; + $this->search_set = $search_set; + + return $a_msg_headers; + } + // use saved messages from searching if ($this->threading) { return $this->list_search_thread_messages($folder, $page, $slice); @@ -1423,11 +1467,33 @@ class rcube_imap extends rcube_storage $str = 'ALL'; } - if (!strlen($folder)) { + if (empty($folder)) { $folder = $this->folder; } - $results = $this->search_index($folder, $str, $charset, $sort_field); + // multi-folder search + if (is_array($folder) && count($folder) > 1 && $str != 'ALL') { + new rcube_result_index; // trigger autoloader and make these classes available for threaded context + new rcube_result_thread; + + // connect IMAP + if (!defined('PTHREADS_INHERIT_ALL')) { + $this->check_connection(); + } + + $searcher = new rcube_imap_search($this->options, $this->conn); + $results = $searcher->exec( + $folder, + $str, + $charset ? $charset : $this->default_charset, + $sort_field && $this->get_capability('SORT') ? $sort_field : null, + $this->threading + ); + } + else { + $folder = is_array($folder) ? $folder[0] : $folder; + $results = $this->search_index($folder, $str, $charset, $sort_field); + } $this->set_search_set(array($str, $results, $charset, $sort_field, $this->threading || $this->search_sorted ? true : false)); @@ -1501,7 +1567,7 @@ class rcube_imap extends rcube_storage // but I've seen that Courier doesn't support UTF-8) if ($threads->is_error() && $charset && $charset != 'US-ASCII') { $threads = $this->conn->thread($folder, $this->threading, - $this->convert_criteria($criteria, $charset), true, 'US-ASCII'); + self::convert_criteria($criteria, $charset), true, 'US-ASCII'); } return $threads; @@ -1515,7 +1581,7 @@ class rcube_imap extends rcube_storage // but I've seen Courier with disabled UTF-8 support) if ($messages->is_error() && $charset && $charset != 'US-ASCII') { $messages = $this->conn->sort($folder, $sort_field, - $this->convert_criteria($criteria, $charset), true, 'US-ASCII'); + self::convert_criteria($criteria, $charset), true, 'US-ASCII'); } if (!$messages->is_error()) { @@ -1530,7 +1596,7 @@ class rcube_imap extends rcube_storage // Error, try with US-ASCII (some servers may support only US-ASCII) if ($messages->is_error() && $charset && $charset != 'US-ASCII') { $messages = $this->conn->search($folder, - $this->convert_criteria($criteria, $charset), true); + self::convert_criteria($criteria, $charset), true); } $this->search_sorted = false; @@ -1548,7 +1614,7 @@ class rcube_imap extends rcube_storage * * @return string Search string */ - protected function convert_criteria($str, $charset, $dest_charset='US-ASCII') + public static function convert_criteria($str, $charset, $dest_charset='US-ASCII') { // convert strings to US_ASCII if (preg_match_all('/\{([0-9]+)\}\r\n/', $str, $matches, PREG_OFFSET_CAPTURE)) { @@ -2410,7 +2476,7 @@ class rcube_imap extends rcube_storage $this->refresh_search(); } else { - $this->search_set->filter(explode(',', $uids)); + $this->search_set->filter(explode(',', $uids), $this->folder); } } diff --git a/program/lib/Roundcube/rcube_imap_search.php b/program/lib/Roundcube/rcube_imap_search.php new file mode 100644 index 000000000..ed4face98 --- /dev/null +++ b/program/lib/Roundcube/rcube_imap_search.php @@ -0,0 +1,327 @@ + | + +-----------------------------------------------------------------------+ +*/ + +// create classes defined by the pthreads module if that isn't installed +if (!defined('PTHREADS_INHERIT_ALL')) { + class Worker { } + class Stackable { } +} + +/** + * Class to control search jobs on multiple IMAP folders. + * This implement a simple threads pool using the pthreads extension. + * + * @package Framework + * @subpackage Storage + * @author Thomas Bruederli + */ +class rcube_imap_search +{ + public $options = array(); + + private $size = 10; + private $next = 0; + private $workers = array(); + private $states = array(); + private $jobs = array(); + private $conn; + + /** + * Default constructor + */ + public function __construct($options, $conn) + { + $this->options = $options; + $this->conn = $conn; + } + + /** + * Invoke search request to IMAP server + * + * @param array $folders List of IMAP folders to search in + * @param string $str Search criteria + * @param string $charset Search charset + * @param string $sort_field Header field to sort by + * @param boolean $threading True if threaded listing is active + */ + public function exec($folders, $str, $charset = null, $sort_field = null, $threading=null) + { + $pthreads = defined('PTHREADS_INHERIT_ALL'); + + // start a search job for every folder to search in + foreach ($folders as $folder) { + $job = new rcube_imap_search_job($folder, $str, $charset, $sort_field, $threading); + if ($pthreads && $this->submit($job)) { + $this->jobs[] = $job; + } + else { + $job->worker = $this; + $job->run(); + $this->jobs[] = $job; + } + } + + // wait for all workers to be done + $this->shutdown(); + + // gather results + $results = new rcube_result_multifolder; + foreach ($this->jobs as $job) { + $results->add($job->get_result()); + } + + return $results; + } + + /** + * Assign the given job object to one of the worker threads for execution + */ + public function submit(Stackable $job) + { + if (count($this->workers) < $this->size) { + $id = count($this->workers); + $this->workers[$id] = new rcube_imap_search_worker($id, $this->options); + $this->workers[$id]->start(PTHREADS_INHERIT_ALL); + + if ($this->workers[$id]->stack($job)) { + return $job; + } + else { + // trigger_error(sprintf("Failed to push Stackable onto %s", $id), E_USER_WARNING); + } + } + if (($worker = $this->workers[$this->next])) { + $this->next = ($this->next+1) % $this->size; + if ($worker->stack($job)) { + return $job; + } + else { + // trigger_error(sprintf("Failed to stack onto selected worker %s", $worker->id), E_USER_WARNING); + } + } + else { + // trigger_error(sprintf("Failed to select a worker for Stackable"), E_USER_WARNING); + } + + return false; + } + + /** + * Shutdown the pool of threads cleanly, retaining exit status locally + */ + public function shutdown() + { + foreach ($this->workers as $worker) { + $this->states[$worker->getThreadId()] = $worker->shutdown(); + $worker->close(); + } + + # console('shutdown', $this->states); + } + + /** + * Get connection to the IMAP server + * (used for single-thread mode) + */ + public function get_imap() + { + return $this->conn; + } +} + + +/** + * Stackable item to run the search on a specific IMAP folder + */ +class rcube_imap_search_job extends Stackable +{ + private $folder; + private $search; + private $charset; + private $sort_field; + private $threading; + private $searchset; + private $result; + private $pagesize = 100; + + public function __construct($folder, $str, $charset = null, $sort_field = null, $threading=false) + { + $this->folder = $folder; + $this->search = $str; + $this->charset = $charset; + $this->sort_field = $sort_field; + $this->threading = $threading; + } + + public function run() + { + #trigger_error("Start search $this->folder", E_USER_NOTICE); + $this->result = $this->search_index(); + #trigger_error("End search $this->folder: " . $this->result->count(), E_USER_NOTICE); + } + + /** + * Copy of rcube_imap::search_index() + */ + protected function search_index() + { + $criteria = $this->search; + $charset = $this->charset; + + $imap = $this->worker->get_imap(); + + if (!$imap->connected()) { + if ($this->threading) { + return new rcube_result_thread(); + } + else { + return new rcube_result_index(); + } + } + + if ($this->worker->options['skip_deleted'] && !preg_match('/UNDELETED/', $criteria)) { + $criteria = 'UNDELETED '.$criteria; + } + + // unset CHARSET if criteria string is ASCII, this way + // SEARCH won't be re-sent after "unsupported charset" response + if ($charset && $charset != 'US-ASCII' && is_ascii($criteria)) { + $charset = 'US-ASCII'; + } + + if ($this->threading) { + $threads = $imap->thread($this->folder, $this->threading, $criteria, true, $charset); + + // Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8, + // but I've seen that Courier doesn't support UTF-8) + if ($threads->is_error() && $charset && $charset != 'US-ASCII') { + $threads = $imap->thread($this->folder, $this->threading, + rcube_imap::convert_criteria($criteria, $charset), true, 'US-ASCII'); + } + + return $threads; + } + + if ($this->sort_field) { + $messages = $imap->sort($this->folder, $this->sort_field, $criteria, true, $charset); + + // Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8, + // but I've seen Courier with disabled UTF-8 support) + if ($messages->is_error() && $charset && $charset != 'US-ASCII') { + $messages = $imap->sort($this->folder, $this->sort_field, + rcube_imap::convert_criteria($criteria, $charset), true, 'US-ASCII'); + } + + if (!$messages->is_error()) { + return $messages; + } + } + + $messages = $imap->search($this->folder, + ($charset && $charset != 'US-ASCII' ? "CHARSET $charset " : '') . $criteria, true); + + // Error, try with US-ASCII (some servers may support only US-ASCII) + if ($messages->is_error() && $charset && $charset != 'US-ASCII') { + $messages = $imap->search($this->folder, + rcube_imap::convert_criteria($criteria, $charset), true); + } + + return $messages; + } + + public function get_search_set() + { + return array( + $this->search, + $this->result, + $this->charset, + $this->sort_field, + $this->threading, + ); + } + + public function get_result() + { + return $this->result; + } +} + + +/** + * Wrker thread to run search jobs while maintaining a common context + */ +class rcube_imap_search_worker extends Worker +{ + public $id; + public $options; + + private $conn; + + /** + * Default constructor + */ + public function __construct($id, $options) + { + $this->id = $id; + $this->options = $options; + } + + /** + * Get a dedicated connection to the IMAP server + */ + public function get_imap() + { + // TODO: make this connection persistent for several jobs + #if ($this->conn) + # return $this->conn; + + $conn = new rcube_imap_generic(); + # $conn->setDebug(true, function($conn, $message){ trigger_error($message, E_USER_NOTICE); }); + + if ($this->options['user'] && $this->options['password']) { + $conn->connect($this->options['host'], $this->options['user'], $this->options['password'], $this->options); + } + + if ($conn->error) + trigger_error($this->conn->error, E_USER_WARNING); + + #$this->conn = $conn; + return $conn; + } + + /** + * @override + */ + public function run() + { + + } + + /** + * Close IMAP connection + */ + public function close() + { + if ($this->conn) { + $this->conn->close(); + } + } +} + diff --git a/program/lib/Roundcube/rcube_result_multifolder.php b/program/lib/Roundcube/rcube_result_multifolder.php new file mode 100644 index 000000000..8d7ae5de8 --- /dev/null +++ b/program/lib/Roundcube/rcube_result_multifolder.php @@ -0,0 +1,211 @@ + | + +-----------------------------------------------------------------------+ +*/ + +/** + * Class holding a set of rcube_result_index instances that together form a + * result set of a multi-folder search + * + * @package Framework + * @subpackage Storage + */ +class rcube_result_multifolder +{ + public $multi = true; + public $sets = array(); + + protected $meta = array(); + protected $order = 'ASC'; + + + /** + * Object constructor. + */ + public function __construct() + { + $this->meta = array('count' => 0); + } + + + /** + * Initializes object with SORT command response + * + * @param string $data IMAP response string + */ + public function add($result) + { + $this->sets[] = $result; + $this->meta['count'] += $result->count(); + } + + + /** + * Checks the result from IMAP command + * + * @return bool True if the result is an error, False otherwise + */ + public function is_error() + { + return false; + } + + + /** + * Checks if the result is empty + * + * @return bool True if the result is empty, False otherwise + */ + public function is_empty() + { + return empty($this->sets) || $this->meta['count'] == 0; + } + + + /** + * Returns number of elements in the result + * + * @return int Number of elements + */ + public function count() + { + return $this->meta['count']; + } + + + /** + * Returns number of elements in the result. + * Alias for count() for compatibility with rcube_result_thread + * + * @return int Number of elements + */ + public function count_messages() + { + return $this->count(); + } + + + /** + * Reverts order of elements in the result + */ + public function revert() + { + $this->order = $this->order == 'ASC' ? 'DESC' : 'ASC'; + } + + + /** + * Check if the given message ID exists in the object + * + * @param int $msgid Message ID + * @param bool $get_index When enabled element's index will be returned. + * Elements are indexed starting with 0 + * @return mixed False if message ID doesn't exist, True if exists or + * index of the element if $get_index=true + */ + public function exists($msgid, $get_index = false) + { + return false; + } + + + /** + * Filters data set. Removes elements listed in $ids list. + * + * @param array $ids List of IDs to remove. + * @param string $folder IMAP folder + */ + public function filter($ids = array(), $folder = null) + { + $this->meta['count'] = 0; + foreach ($this->sets as $set) { + if ($set->get_parameters('MAILBOX') == $folder) { + $set->filter($ids); + } + $this->meta['count'] += $set->count(); + } + } + + /** + * Filters data set. Removes elements not listed in $ids list. + * + * @param array $ids List of IDs to keep. + */ + public function intersect($ids = array()) + { + // not implemented + } + + /** + * Return all messages in the result. + * + * @return array List of message IDs + */ + public function get() + { + return array(); + } + + + /** + * Return all messages in the result. + * + * @return array List of message IDs + */ + public function get_compressed() + { + return ''; + } + + + /** + * Return result element at specified index + * + * @param int|string $index Element's index or "FIRST" or "LAST" + * + * @return int Element value + */ + public function get_element($index) + { + return null; + } + + + /** + * Returns response parameters, e.g. ESEARCH's MIN/MAX/COUNT/ALL/MODSEQ + * or internal data e.g. MAILBOX, ORDER + * + * @param string $param Parameter name + * + * @return array|string Response parameters or parameter value + */ + public function get_parameters($param=null) + { + return $params; + } + + + /** + * Returns length of internal data representation + * + * @return int Data length + */ + protected function length() + { + return $this->count(); + } +} diff --git a/program/steps/mail/search.inc b/program/steps/mail/search.inc index 9f4cdc9c9..9b5aa2fc1 100644 --- a/program/steps/mail/search.inc +++ b/program/steps/mail/search.inc @@ -106,6 +106,9 @@ if (!empty($subject)) { $search_str = trim($search_str); $sort_column = rcmail_sort_column(); +// TEMPORARY: search all folders +$mboxes = $RCMAIL->storage->list_folders_subscribed('', '*', 'mail'); + // execute IMAP search if ($search_str) { $RCMAIL->storage->search($mbox, $search_str, $imap_charset, $sort_column); @@ -128,17 +131,20 @@ $result_h = $RCMAIL->storage->list_messages($mbox, 1, $sort_column, rcmail_sort_ $count = $RCMAIL->storage->count($mbox, $RCMAIL->storage->get_threading() ? 'THREADS' : 'ALL'); // Add 'folder' column to list -if ($multi_folder_search) { +if ($_SESSION['search'][1]->multi) { $a_show_cols = $_SESSION['list_attrib']['columns'] ? $_SESSION['list_attrib']['columns'] : (array)$CONFIG['list_cols']; - if (!in_array($a_show_cols)) + if (!in_array('folder', $a_show_cols)) $a_show_cols[] = 'folder'; // make message UIDs unique by appending the folder name foreach ($result_h as $i => $header) { $header->uid .= '-'.$header->folder; + $header->flags['skip_mbox_check'] = true; if ($header->parent_uid) $header->parent_uid .= '-'.$header->folder; } + + $OUTPUT->command('select_folder', ''); } // Make sure we got the headers -- cgit v1.2.3 From b6e24c6946606cd504d522451c36b6dc574fe75d Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Tue, 15 Oct 2013 11:44:34 +0200 Subject: Minor improvements to threaded searching --- program/lib/Roundcube/rcube_imap.php | 8 +++---- program/lib/Roundcube/rcube_imap_search.php | 36 ++++++++++++++++++----------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index db94e7678..0cf34b2ca 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -969,7 +969,7 @@ class rcube_imap extends rcube_storage $to = $from + $page_size; // sort headers - if (!$this->threading) { + if (!$this->threading && !empty($a_msg_headers)) { $a_msg_headers = $this->conn->sortHeaders($a_msg_headers, $this->sort_field, $this->sort_order); } @@ -1476,10 +1476,8 @@ class rcube_imap extends rcube_storage new rcube_result_index; // trigger autoloader and make these classes available for threaded context new rcube_result_thread; - // connect IMAP - if (!defined('PTHREADS_INHERIT_ALL')) { - $this->check_connection(); - } + // connect IMAP to have all the required classes and settings loaded + $this->check_connection(); $searcher = new rcube_imap_search($this->options, $this->conn); $results = $searcher->exec( diff --git a/program/lib/Roundcube/rcube_imap_search.php b/program/lib/Roundcube/rcube_imap_search.php index ed4face98..d82ec8a24 100644 --- a/program/lib/Roundcube/rcube_imap_search.php +++ b/program/lib/Roundcube/rcube_imap_search.php @@ -172,9 +172,9 @@ class rcube_imap_search_job extends Stackable public function run() { - #trigger_error("Start search $this->folder", E_USER_NOTICE); + // trigger_error("Start search $this->folder", E_USER_NOTICE); $this->result = $this->search_index(); - #trigger_error("End search $this->folder: " . $this->result->count(), E_USER_NOTICE); + // trigger_error("End search $this->folder: " . $this->result->count(), E_USER_NOTICE); } /** @@ -182,6 +182,7 @@ class rcube_imap_search_job extends Stackable */ protected function search_index() { + $pthreads = defined('PTHREADS_INHERIT_ALL'); $criteria = $this->search; $charset = $this->charset; @@ -216,6 +217,10 @@ class rcube_imap_search_job extends Stackable rcube_imap::convert_criteria($criteria, $charset), true, 'US-ASCII'); } + // close IMAP connection again + if ($pthreads) + $imap->closeConnection(); + return $threads; } @@ -228,21 +233,23 @@ class rcube_imap_search_job extends Stackable $messages = $imap->sort($this->folder, $this->sort_field, rcube_imap::convert_criteria($criteria, $charset), true, 'US-ASCII'); } - - if (!$messages->is_error()) { - return $messages; - } } - $messages = $imap->search($this->folder, - ($charset && $charset != 'US-ASCII' ? "CHARSET $charset " : '') . $criteria, true); - - // Error, try with US-ASCII (some servers may support only US-ASCII) - if ($messages->is_error() && $charset && $charset != 'US-ASCII') { + if (!$messages || !$messages->is_error()) { $messages = $imap->search($this->folder, - rcube_imap::convert_criteria($criteria, $charset), true); + ($charset && $charset != 'US-ASCII' ? "CHARSET $charset " : '') . $criteria, true); + + // Error, try with US-ASCII (some servers may support only US-ASCII) + if ($messages->is_error() && $charset && $charset != 'US-ASCII') { + $messages = $imap->search($this->folder, + rcube_imap::convert_criteria($criteria, $charset), true); + } } + // close IMAP connection again + if ($pthreads) + $imap->closeConnection(); + return $messages; } @@ -279,6 +286,8 @@ class rcube_imap_search_worker extends Worker */ public function __construct($id, $options) { + $options['ident']['command'] = 'search-'.$id; + $this->id = $id; $this->options = $options; } @@ -296,11 +305,12 @@ class rcube_imap_search_worker extends Worker # $conn->setDebug(true, function($conn, $message){ trigger_error($message, E_USER_NOTICE); }); if ($this->options['user'] && $this->options['password']) { + // TODO: do this synchronized to avoid warnings like "Only one Id allowed in non-authenticated state" $conn->connect($this->options['host'], $this->options['user'], $this->options['password'], $this->options); } if ($conn->error) - trigger_error($this->conn->error, E_USER_WARNING); + trigger_error($conn->error, E_USER_WARNING); #$this->conn = $conn; return $conn; -- cgit v1.2.3 From d53b60406c8070f363d42b32a21670ae68f56cc1 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Thu, 16 Jan 2014 11:12:43 +0100 Subject: Fix typos --- program/lib/Roundcube/rcube_imap_search.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/program/lib/Roundcube/rcube_imap_search.php b/program/lib/Roundcube/rcube_imap_search.php index d82ec8a24..70a11bc1c 100644 --- a/program/lib/Roundcube/rcube_imap_search.php +++ b/program/lib/Roundcube/rcube_imap_search.php @@ -235,7 +235,7 @@ class rcube_imap_search_job extends Stackable } } - if (!$messages || !$messages->is_error()) { + if (!$messages || $messages->is_error()) { $messages = $imap->search($this->folder, ($charset && $charset != 'US-ASCII' ? "CHARSET $charset " : '') . $criteria, true); @@ -272,7 +272,7 @@ class rcube_imap_search_job extends Stackable /** - * Wrker thread to run search jobs while maintaining a common context + * Worker thread to run search jobs while maintaining a common context */ class rcube_imap_search_worker extends Worker { -- cgit v1.2.3 From 25a244908b21b097f744c7194e4862ca6ebda391 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Thu, 16 Jan 2014 11:43:11 +0100 Subject: Remove merge conflict --- program/steps/mail/move_del.inc | 4 ---- 1 file changed, 4 deletions(-) diff --git a/program/steps/mail/move_del.inc b/program/steps/mail/move_del.inc index 9a8b4a3ac..26c724597 100644 --- a/program/steps/mail/move_del.inc +++ b/program/steps/mail/move_del.inc @@ -32,11 +32,7 @@ $trash = $RCMAIL->config->get('trash_mbox'); // move messages if ($RCMAIL->action == 'move' && !empty($_POST['_uid']) && strlen($_POST['_target_mbox'])) { -<<<<<<< HEAD $target = rcube_utils::get_input_value('_target_mbox', rcube_utils::INPUT_POST, true); -======= - $target = get_input_value('_target_mbox', RCUBE_INPUT_POST, true); ->>>>>>> 010a350715f1a36eab666fe26d3118ed025133c1 $trash = $RCMAIL->config->get('trash_mbox'); foreach (rcmail_get_uids() as $mbox => $uids) { -- cgit v1.2.3 From 2baeac116abef9d5bcb748c687577d16dce868a0 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Thu, 16 Jan 2014 14:17:08 +0100 Subject: Fix sorting and paging in cross-folder searches --- program/lib/Roundcube/rcube_imap.php | 4 ++-- program/steps/mail/list.inc | 16 +++++++++++++ program/steps/mail/search.inc | 44 +++++++++++------------------------- 3 files changed, 31 insertions(+), 33 deletions(-) diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index 0cf34b2ca..e265946f2 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -952,7 +952,7 @@ class rcube_imap extends rcube_storage $search_set = $this->search_set; $this->sort_field = null; - $this->page_size = 100; // limit to 100 messages per folder + $this->page_size = 1000; // fetch up to 1000 matching messages per folder $a_msg_headers = array(); foreach ($search_set->sets as $resultset) { @@ -970,7 +970,7 @@ class rcube_imap extends rcube_storage // sort headers if (!$this->threading && !empty($a_msg_headers)) { - $a_msg_headers = $this->conn->sortHeaders($a_msg_headers, $this->sort_field, $this->sort_order); + $a_msg_headers = $this->conn->sortHeaders($a_msg_headers, $sort_field, $this->sort_order); } // only return the requested part of the set diff --git a/program/steps/mail/list.inc b/program/steps/mail/list.inc index 277564c38..4b2a955b7 100644 --- a/program/steps/mail/list.inc +++ b/program/steps/mail/list.inc @@ -75,6 +75,22 @@ if (!empty($_REQUEST['_search']) && isset($_SESSION['search']) && $_SESSION['search_request'] == $_REQUEST['_search'] ) { $_SESSION['search'] = $RCMAIL->storage->get_search_set(); + + // multi-folder search + if ($_SESSION['search'][1]->multi) { + if (empty($cols)) + $cols = $_SESSION['list_attrib']['columns'] ? $_SESSION['list_attrib']['columns'] : (array)$CONFIG['list_cols']; + if (!in_array('folder', $cols)) + $cols[] = 'folder'; // Add 'folder' column to list + + // make message UIDs unique by appending the folder name + foreach ($a_headers as $i => $header) { + $header->uid .= '-'.$header->folder; + $header->flags['skip_mbox_check'] = true; + if ($header->parent_uid) + $header->parent_uid .= '-'.$header->folder; + } + } } // remove old search data else if (empty($_REQUEST['_search']) && isset($_SESSION['search'])) { diff --git a/program/steps/mail/search.inc b/program/steps/mail/search.inc index 67fee755d..5ce9fe6e2 100644 --- a/program/steps/mail/search.inc +++ b/program/steps/mail/search.inc @@ -111,7 +111,7 @@ $mboxes = $RCMAIL->storage->list_folders_subscribed('', '*', 'mail'); // execute IMAP search if ($search_str) { - $RCMAIL->storage->search($mbox, $search_str, $imap_charset, $sort_column); + $RCMAIL->storage->search($mboxes, $search_str, $imap_charset, $sort_column); } // save search results in session @@ -125,43 +125,25 @@ if ($search_str) { } $_SESSION['search_request'] = $search_request; - // Get the headers $result_h = $RCMAIL->storage->list_messages($mbox, 1, $sort_column, rcmail_sort_order()); $count = $RCMAIL->storage->count($mbox, $RCMAIL->storage->get_threading() ? 'THREADS' : 'ALL'); // Add 'folder' column to list if ($_SESSION['search'][1]->multi) { - $a_show_cols = $_SESSION['list_attrib']['columns'] ? $_SESSION['list_attrib']['columns'] : (array)$CONFIG['list_cols']; - if (!in_array('folder', $a_show_cols)) - $a_show_cols[] = 'folder'; - - // make message UIDs unique by appending the folder name - foreach ($result_h as $i => $header) { - $header->uid .= '-'.$header->folder; - $header->flags['skip_mbox_check'] = true; - if ($header->parent_uid) - $header->parent_uid .= '-'.$header->folder; - } - - $OUTPUT->command('select_folder', ''); -} + $a_show_cols = $_SESSION['list_attrib']['columns'] ? $_SESSION['list_attrib']['columns'] : (array)$CONFIG['list_cols']; + if (!in_array('folder', $a_show_cols)) + $a_show_cols[] = 'folder'; + + // make message UIDs unique by appending the folder name + foreach ($result_h as $i => $header) { + $header->uid .= '-'.$header->folder; + $header->flags['skip_mbox_check'] = true; + if ($header->parent_uid) + $header->parent_uid .= '-'.$header->folder; + } -// Add 'folder' column to list -if ($_SESSION['search'][1]->multi) { - $a_show_cols = $_SESSION['list_attrib']['columns'] ? $_SESSION['list_attrib']['columns'] : (array)$CONFIG['list_cols']; - if (!in_array('folder', $a_show_cols)) - $a_show_cols[] = 'folder'; - - // make message UIDs unique by appending the folder name - foreach ($result_h as $i => $header) { - $header->uid .= '-'.$header->folder; - $header->flags['skip_mbox_check'] = true; - if ($header->parent_uid) - $header->parent_uid .= '-'.$header->folder; - } - - $OUTPUT->command('select_folder', ''); + $OUTPUT->command('select_folder', ''); } // Make sure we got the headers -- cgit v1.2.3 From 2c33c7e38bc767330b4eebdc9e4d234caca72966 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Thu, 16 Jan 2014 15:41:19 +0100 Subject: Make message pagenav (prev/next) work with sorted multi-folder search results --- program/js/app.js | 3 +- program/lib/Roundcube/rcube_imap.php | 10 +++++- program/lib/Roundcube/rcube_result_multifolder.php | 40 +++++++++++++++++++--- program/steps/mail/func.inc | 4 ++- program/steps/mail/list.inc | 2 ++ program/steps/mail/search.inc | 8 ++--- 6 files changed, 56 insertions(+), 11 deletions(-) diff --git a/program/js/app.js b/program/js/app.js index 2717e35d5..5eae82351 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -696,6 +696,7 @@ function rcube_webmail() break; case 'list': + // TODO: don't reset search but re-send for the new folder if (props && props != '') this.reset_qsearch(); if (this.env.action == 'compose' && this.env.extwin) @@ -1620,7 +1621,7 @@ function rcube_webmail() var uid = list.get_single_selection(); - if (uid && this.env.mailbox == this.env.drafts_mailbox) + if (uid && (this.env.messages[uid].mbox || this.env.mailbox) == this.env.drafts_mailbox) this.open_compose_step({ _draft_uid: uid, _mbox: this.env.mailbox }); else if (uid) this.show_message(uid, false, false); diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index e265946f2..847bcfa70 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -973,6 +973,9 @@ class rcube_imap extends rcube_storage $a_msg_headers = $this->conn->sortHeaders($a_msg_headers, $sort_field, $this->sort_order); } + // store (sorted) message index + $search_set->set_message_index($a_msg_headers, $sort_field, $this->sort_order); + // only return the requested part of the set $slice_length = min($page_size, $cnt - ($to > $cnt ? $from : $to)); $a_msg_headers = array_slice(array_values($a_msg_headers), $from, $slice_length); @@ -1279,8 +1282,13 @@ class rcube_imap extends rcube_storage return new rcube_result_index($folder, '* SORT'); } + if ($this->search_set instanceof rcube_result_multifolder) { + $index = $this->search_set; + $index->folder = $folder; + // TODO: handle changed sorting + } // search result is an index with the same sorting? - if (($this->search_set instanceof rcube_result_index) + else if (($this->search_set instanceof rcube_result_index) && ((!$this->sort_field && !$this->search_sorted) || ($this->search_sorted && $this->search_sort_field == $this->sort_field)) ) { diff --git a/program/lib/Roundcube/rcube_result_multifolder.php b/program/lib/Roundcube/rcube_result_multifolder.php index 8d7ae5de8..277a6d1ea 100644 --- a/program/lib/Roundcube/rcube_result_multifolder.php +++ b/program/lib/Roundcube/rcube_result_multifolder.php @@ -28,8 +28,11 @@ class rcube_result_multifolder { public $multi = true; public $sets = array(); + public $folder; protected $meta = array(); + protected $index = array(); + protected $sorting; protected $order = 'ASC'; @@ -53,6 +56,19 @@ class rcube_result_multifolder $this->meta['count'] += $result->count(); } + /** + * Store a global index of (sorted) message UIDs + */ + public function set_message_index($headers, $sort_field, $sort_order) + { + $this->index = array(); + foreach ($headers as $header) { + $this->index[] = $header->uid . '-' . $header->folder; + } + + $this->sorting = $sort_field; + $this->order = $sort_order; + } /** * Checks the result from IMAP command @@ -119,7 +135,10 @@ class rcube_result_multifolder */ public function exists($msgid, $get_index = false) { - return false; + if (!empty($this->folder)) { + $msgid .= '-' . $this->folder; + } + return array_search($msgid, $this->index); } @@ -157,7 +176,7 @@ class rcube_result_multifolder */ public function get() { - return array(); + return $this->index; } @@ -179,9 +198,13 @@ class rcube_result_multifolder * * @return int Element value */ - public function get_element($index) + public function get_element($idx) { - return null; + switch ($idx) { + case 'FIRST': return $this->index[0]; + case 'LAST': return end($this->index); + default: return $this->index[$idx]; + } } @@ -195,6 +218,15 @@ class rcube_result_multifolder */ public function get_parameters($param=null) { + $params = array( + 'SORT' => $this->sorting, + 'ORDER' => $this->order, + ); + + if ($param !== null) { + return $params[$param]; + } + return $params; } diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index 3848ec540..45d4242f9 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -76,9 +76,11 @@ if (($_uid = get_input_value('_uid', RCUBE_INPUT_GPC)) && preg_match('/^\d+-[^, $_REQUEST['_uid'] = $_uid; unset($_uid); - if (empty($_REQUEST['_mbox']) && !empty($mbox)) { + // override mbox + if (!empty($mbox)) { $_GET['_mbox'] = $mbox; $_POST['_mbox'] = $mbox; + $RCMAIL->storage->set_folder(($_SESSION['mbox'] = $mbox)); } } diff --git a/program/steps/mail/list.inc b/program/steps/mail/list.inc index 4b2a955b7..a77d60f32 100644 --- a/program/steps/mail/list.inc +++ b/program/steps/mail/list.inc @@ -90,6 +90,8 @@ if (!empty($_REQUEST['_search']) && isset($_SESSION['search']) if ($header->parent_uid) $header->parent_uid .= '-'.$header->folder; } + + $OUTPUT->command('select_folder', ''); } } // remove old search data diff --git a/program/steps/mail/search.inc b/program/steps/mail/search.inc index 5ce9fe6e2..b45cdc0de 100644 --- a/program/steps/mail/search.inc +++ b/program/steps/mail/search.inc @@ -114,6 +114,10 @@ if ($search_str) { $RCMAIL->storage->search($mboxes, $search_str, $imap_charset, $sort_column); } +// Get the headers +$result_h = $RCMAIL->storage->list_messages($mbox, 1, $sort_column, rcmail_sort_order()); +$count = $RCMAIL->storage->count($mbox, $RCMAIL->storage->get_threading() ? 'THREADS' : 'ALL'); + // save search results in session if (!is_array($_SESSION['search'])) { $_SESSION['search'] = array(); @@ -125,10 +129,6 @@ if ($search_str) { } $_SESSION['search_request'] = $search_request; -// Get the headers -$result_h = $RCMAIL->storage->list_messages($mbox, 1, $sort_column, rcmail_sort_order()); -$count = $RCMAIL->storage->count($mbox, $RCMAIL->storage->get_threading() ? 'THREADS' : 'ALL'); - // Add 'folder' column to list if ($_SESSION['search'][1]->multi) { $a_show_cols = $_SESSION['list_attrib']['columns'] ? $_SESSION['list_attrib']['columns'] : (array)$CONFIG['list_cols']; -- cgit v1.2.3 From ff3eb834920e2ec04e24423b5f87bdf4d3064eeb Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Thu, 16 Jan 2014 15:50:48 +0100 Subject: Handle combined UID-folder identifiers in rcube_imap class --- program/lib/Roundcube/rcube_imap.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index 847bcfa70..698d0daf3 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -1656,6 +1656,7 @@ class rcube_imap extends rcube_storage public function refresh_search() { if (!empty($this->search_string)) { + // FIXME: make this work with saved multi-folder searches $this->search('', $this->search_string, $this->search_charset, $this->search_sort_field); } @@ -1678,6 +1679,11 @@ class rcube_imap extends rcube_storage $folder = $this->folder; } + // decode combined UID-folder identifier + if (preg_match('/^\d+-[^,]+$/', $uid)) { + list($uid, $folder) = explode('-', $uid); + } + // get cached headers if (!$force && $uid && ($mcache = $this->get_mcache_engine())) { $headers = $mcache->get_message($folder, $uid); @@ -1709,6 +1715,11 @@ class rcube_imap extends rcube_storage $folder = $this->folder; } + // decode combined UID-folder identifier + if (preg_match('/^\d+-[^,]+$/', $uid)) { + list($uid, $folder) = explode('-', $uid); + } + // Check internal cache if (!empty($this->icache['message'])) { if (($headers = $this->icache['message']) && $headers->uid == $uid) { -- cgit v1.2.3 From 689a222170aed32165cbe8bc8fc89fd428b29186 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Mon, 20 Jan 2014 09:33:58 +0100 Subject: Append per-folder search results to global index but only if it has matches --- program/lib/Roundcube/rcube_result_multifolder.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/program/lib/Roundcube/rcube_result_multifolder.php b/program/lib/Roundcube/rcube_result_multifolder.php index 277a6d1ea..188fb26f2 100644 --- a/program/lib/Roundcube/rcube_result_multifolder.php +++ b/program/lib/Roundcube/rcube_result_multifolder.php @@ -52,8 +52,15 @@ class rcube_result_multifolder */ public function add($result) { - $this->sets[] = $result; - $this->meta['count'] += $result->count(); + if ($count = $result->count()) { + $this->sets[] = $result; + $this->meta['count'] += $count; + + // append UIDs to global index + $folder = $result->get_parameters('MAILBOX'); + $index = array_map(function($uid) use ($folder) { return $uid . '-' . $folder; }, $result->get()); + $this->index = array_merge($this->index, $index); + } } /** -- cgit v1.2.3 From 1bbf8c48868efb87baab7ae71721f2c9ad408e65 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Mon, 20 Jan 2014 10:05:36 +0100 Subject: - Make search scope selectable in UI - Disable thread mode when searching over multiple folders - Encode UID values for HTML message row identifiers --- program/js/app.js | 45 ++++++++++++++++++++++------------- program/js/list.js | 29 ++++++++++++---------- program/lib/Roundcube/rcube_imap.php | 4 ++++ program/localization/en_US/labels.inc | 4 ++++ program/steps/mail/func.inc | 3 +++ program/steps/mail/search.inc | 13 ++++++++-- skins/larry/templates/mail.html | 13 +++++----- skins/larry/ui.js | 27 +++++++++++++++------ 8 files changed, 93 insertions(+), 45 deletions(-) diff --git a/program/js/app.js b/program/js/app.js index 5eae82351..2401e1e0c 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -217,6 +217,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); // load messages this.command('list'); @@ -709,6 +710,10 @@ function rcube_webmail() this.list_contacts(props); break; + case 'set-listmode': + this.set_list_options(null, undefined, undefined, props == 'threads' ? 1 : 0); + break; + case 'sort': var sort_order = this.env.sort_order, sort_col = !this.env.disabled_sort_col ? props : this.env.sort_col; @@ -1753,7 +1758,7 @@ function rcube_webmail() this.init_message_row = function(row) { var i, fn = {}, self = this, uid = row.uid, - status_icon = (this.env.status_col != null ? 'status' : 'msg') + 'icn' + row.uid; + status_icon = (this.env.status_col != null ? 'status' : 'msg') + 'icn' + row.id; if (uid && this.env.messages[uid]) $.extend(row, this.env.messages[uid]); @@ -1765,17 +1770,17 @@ function rcube_webmail() // save message icon position too if (this.env.status_col != null) - row.msgicon = document.getElementById('msgicn'+row.uid); + row.msgicon = document.getElementById('msgicn'+row.id); else row.msgicon = row.icon; // set eventhandler to flag icon - if (this.env.flagged_col != null && (row.flagicon = document.getElementById('flagicn'+row.uid))) { + if (this.env.flagged_col != null && (row.flagicon = document.getElementById('flagicn'+row.id))) { fn.flagicon = function(e) { self.command('toggle_flag', uid); }; } // set event handler to thread expand/collapse icon - if (!row.depth && row.has_children && (row.expando = document.getElementById('rcmexpando'+row.uid))) { + if (!row.depth && row.has_children && (row.expando = document.getElementById('rcmexpando'+row.id))) { fn.expando = function(e) { self.expand_message_row(e, uid); }; } @@ -1837,7 +1842,7 @@ function rcube_webmail() + (flags.deleted ? ' deleted' : '') + (flags.flagged ? ' flagged' : '') + (message.selected ? ' selected' : ''), - row = { cols:[], style:{}, id:'rcmrow'+uid }; + row = { cols:[], style:{}, id:'rcmrow'+this.html_identifier(uid,true), uid:uid }; // message status icons css_class = 'msgicon'; @@ -1863,7 +1868,7 @@ function rcube_webmail() if (this.env.threading) { if (message.depth) { // This assumes that div width is hardcoded to 15px, - tree += '  '; + tree += '  '; if ((rows[message.parent_uid] && rows[message.parent_uid].expanded === false) || ((this.env.autoexpand_threads == 0 || this.env.autoexpand_threads == 2) && @@ -1882,7 +1887,7 @@ function rcube_webmail() message.expanded = true; } - expando = '
  
'; + expando = '
  
'; row_class += ' thread' + (message.expanded? ' expanded' : ''); } @@ -1890,14 +1895,14 @@ function rcube_webmail() row_class += ' unroot'; } - tree += ' '; + tree += ' '; row.className = row_class; // build subject link if (!bw.ie && cols.subject) { var action = flags.mbox == this.env.drafts_mailbox ? 'compose' : 'show'; var uid_param = flags.mbox == this.env.drafts_mailbox ? '_draft_uid' : '_uid'; - cols.subject = ''+cols.subject+''; } @@ -1908,7 +1913,7 @@ function rcube_webmail() if (c == 'flag') { css_class = (flags.flagged ? 'flagged' : 'unflagged'); - html = ' '; + html = ' '; } else if (c == 'attachment') { if (/application\/|multipart\/(m|signed)/.test(flags.ctype)) @@ -1927,7 +1932,7 @@ function rcube_webmail() css_class = 'unreadchildren'; else css_class = 'msgicon'; - html = ' '; + html = ' '; } else if (c == 'threads') html = expando; @@ -2390,7 +2395,7 @@ function rcube_webmail() } if (html) - $('#rcmtab'+uid).html(html); + $('#rcmtab'+this.html_identifier(uid, true)).html(html); }; // update parent in a thread @@ -2454,14 +2459,14 @@ function rcube_webmail() r.depth--; // move left // reset width and clear the content of a tab, icons will be added later - $('#rcmtab'+r.uid).width(r.depth * 15).html(''); + $('#rcmtab'+r.id).width(r.depth * 15).html(''); if (!r.depth) { // a new root count++; // increase roots count r.parent_uid = 0; if (r.has_children) { // replace 'leaf' with 'collapsed' - $('#rcmrow'+r.uid+' '+'.leaf:first') - .attr('id', 'rcmexpando' + r.uid) + $('#'+r.id+' .leaf:first') + .attr('id', 'rcmexpando' + r.id) .attr('class', (r.obj.style.display != 'none' ? 'expanded' : 'collapsed')) .bind('mousedown', {uid:r.uid, p:this}, function(e) { return e.data.p.expand_message_row(e, e.data.uid); }); @@ -4118,6 +4123,7 @@ function rcube_webmail() r = this.http_request(action, url, lock); this.env.qsearch = {lock: lock, request: r}; + this.enable_command('set-listmode', this.env.threads && (this.env.search_scope || 'base') == 'base'); } }; @@ -4126,7 +4132,8 @@ function rcube_webmail() { var n, url = {}, mods_arr = [], mods = this.env.search_mods, - mbox = this.env.mailbox; + mbox = this.env.mailbox, + scope = this.env.search_scope || 'base'; if (!filter && this.gui_objects.search_filter) filter = this.gui_objects.search_filter.value; @@ -4150,7 +4157,9 @@ function rcube_webmail() } } - if (mbox) + if (scope) + url._scope = scope; + if (mbox && scope != 'all') url._mbox = mbox; return url; @@ -4168,6 +4177,8 @@ function rcube_webmail() this.env.qsearch = null; this.env.search_request = null; this.env.search_id = null; + + this.enable_command('set-listmode', this.env.threads); }; this.sent_successfully = function(type, msg, folders) diff --git a/program/js/list.js b/program/js/list.js index 319807eae..c026ccb4a 100644 --- a/program/js/list.js +++ b/program/js/list.js @@ -107,11 +107,15 @@ init: function() */ init_row: function(row) { - // make references in internal array and set event handlers - if (row && String(row.id).match(this.id_regexp)) { - var self = this, - uid = RegExp.$1; + var uid; + if (row && (uid = $(row).data('uid'))) row.uid = uid; + else if (row && String(row.id).match(this.id_regexp)) + row.uid = RegExp.$1; + + // make references in internal array and set event handlers + if (row && row.uid) { + var self = this, uid = row.uid; this.rows[uid] = {uid:uid, id:row.id, obj:row}; // set eventhandlers to table row @@ -291,6 +295,7 @@ insert_row: function(row, before) if (row.id) domrow.id = row.id; if (row.className) domrow.className = row.className; if (row.style) $.extend(domrow.style, row.style); + if (row.uid) $(domrow).data('uid', row.uid); for (var domcell, col, i=0; row.cols && i < row.cols.length; i++) { col = row.cols[i]; @@ -589,7 +594,7 @@ expand: function(row) row.expanded = true; depth = row.depth; new_row = row.obj.nextSibling; - this.update_expando(row.uid, true); + this.update_expando(row.id, true); this.triggerEvent('expandcollapse', { uid:row.uid, expanded:row.expanded, obj:row.obj }); } else { @@ -639,7 +644,7 @@ collapse_all: function(row) row.expanded = false; depth = row.depth; new_row = row.obj.nextSibling; - this.update_expando(row.uid); + this.update_expando(row.id); this.triggerEvent('expandcollapse', { uid:row.uid, expanded:row.expanded, obj:row.obj }); // don't collapse sub-root tree in multiexpand mode @@ -661,7 +666,7 @@ collapse_all: function(row) $(new_row).css('display', 'none'); if (r.has_children && r.expanded) { r.expanded = false; - this.update_expando(r.uid, false); + this.update_expando(r.id, false); this.triggerEvent('expandcollapse', { uid:r.uid, expanded:r.expanded, obj:new_row }); } } @@ -683,7 +688,7 @@ expand_all: function(row) row.expanded = true; depth = row.depth; new_row = row.obj.nextSibling; - this.update_expando(row.uid, true); + this.update_expando(row.id, true); this.triggerEvent('expandcollapse', { uid:row.uid, expanded:row.expanded, obj:row.obj }); } else { @@ -700,7 +705,7 @@ expand_all: function(row) $(new_row).css('display', ''); if (r.has_children && !r.expanded) { r.expanded = true; - this.update_expando(r.uid, true); + this.update_expando(r.id, true); this.triggerEvent('expandcollapse', { uid:r.uid, expanded:r.expanded, obj:new_row }); } } @@ -714,9 +719,9 @@ expand_all: function(row) }, -update_expando: function(uid, expanded) +update_expando: function(id, expanded) { - var expando = document.getElementById('rcmexpando' + uid); + var expando = document.getElementById('rcmexpando' + id); if (expando) expando.className = expanded ? 'expanded' : 'collapsed'; }, @@ -1267,7 +1272,7 @@ use_arrow_key: function(keyCode, mod_key) this.collapse(selected_row); } - this.update_expando(selected_row.uid, selected_row.expanded); + this.update_expando(selected_row.id, selected_row.expanded); return false; } diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index 698d0daf3..dd0501c2d 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -953,6 +953,7 @@ class rcube_imap extends rcube_storage $this->sort_field = null; $this->page_size = 1000; // fetch up to 1000 matching messages per folder + $this->threading = false; $a_msg_headers = array(); foreach ($search_set->sets as $resultset) { @@ -1487,6 +1488,9 @@ class rcube_imap extends rcube_storage // connect IMAP to have all the required classes and settings loaded $this->check_connection(); + // disable threading + $this->threading = false; + $searcher = new rcube_imap_search($this->options, $this->conn); $results = $searcher->exec( $folder, diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc index 61890a642..05eab6713 100644 --- a/program/localization/en_US/labels.inc +++ b/program/localization/en_US/labels.inc @@ -208,6 +208,10 @@ $labels['msgtext'] = 'Entire message'; $labels['body'] = 'Body'; $labels['type'] = 'Type'; $labels['namex'] = 'Name'; +$labels['searchscope'] = 'Scope'; +$labels['currentfolder'] = 'Current folder'; +$labels['subfolders'] = 'This and subfolders'; +$labels['allfolders'] = 'All folders'; $labels['openinextwin'] = 'Open in new window'; $labels['emlsave'] = 'Download (.eml)'; diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index 45d4242f9..fd321e294 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -105,6 +105,9 @@ if (empty($RCMAIL->action) || $RCMAIL->action == 'list') { } $OUTPUT->set_env('search_mods', rcmail_search_mods()); + + if (!empty($_SESSION['search_scope'])) + $OUTPUT->set_env('search_scope', $_SESSION['search_scope']); } $threading = (bool) $RCMAIL->storage->get_threading(); diff --git a/program/steps/mail/search.inc b/program/steps/mail/search.inc index b45cdc0de..88bbe6eda 100644 --- a/program/steps/mail/search.inc +++ b/program/steps/mail/search.inc @@ -35,6 +35,7 @@ $str = rcube_utils::get_input_value('_q', rcube_utils::INPUT_GET, true); $mbox = rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_GET, true); $filter = rcube_utils::get_input_value('_filter', rcube_utils::INPUT_GET); $headers = rcube_utils::get_input_value('_headers', rcube_utils::INPUT_GET); +$scope = rcube_utils::get_input_value('_scope', rcube_utils::INPUT_GET); $subject = array(); $search_request = md5($mbox.$filter.$str); @@ -106,8 +107,15 @@ if (!empty($subject)) { $search_str = trim($search_str); $sort_column = rcmail_sort_column(); -// TEMPORARY: search all folders -$mboxes = $RCMAIL->storage->list_folders_subscribed('', '*', 'mail'); +// search all, current or subfolders folders +if ($scope == 'all') { + $mboxes = $RCMAIL->storage->list_folders_subscribed('', '*', 'mail'); +} +else if ($scope == 'sub') { + $mboxes = $RCMAIL->storage->list_folders_subscribed($mbox, '*', 'mail'); + if ($mbox != 'INBOX' && $mboxes[0] == 'INBOX') + array_shift($mboxes); +} // execute IMAP search if ($search_str) { @@ -128,6 +136,7 @@ if ($search_str) { $_SESSION['last_text_search'] = $str; } $_SESSION['search_request'] = $search_request; +$_SESSION['search_scope'] = $scope; // Add 'folder' column to list if ($_SESSION['search'][1]->multi) { diff --git a/skins/larry/templates/mail.html b/skins/larry/templates/mail.html index ff5f7549c..e2bf8901c 100644 --- a/skins/larry/templates/mail.html +++ b/skins/larry/templates/mail.html @@ -76,13 +76,8 @@
- - List - Threads - - List - Threads - + +
@@ -132,6 +127,10 @@
  • +
  • +
  • +
  • +
  • diff --git a/skins/larry/ui.js b/skins/larry/ui.js index 44fc727f6..2030b05cd 100644 --- a/skins/larry/ui.js +++ b/skins/larry/ui.js @@ -41,6 +41,7 @@ function rcube_mail_ui() this.show_popup = show_popup; this.add_popup = add_popup; this.set_searchmod = set_searchmod; + this.set_searchscope = set_searchscope; this.show_uploadform = show_uploadform; this.show_header_row = show_header_row; this.hide_header_row = hide_header_row; @@ -731,11 +732,10 @@ function rcube_mail_ui() */ function switch_view_mode(mode) { - if (rcmail.env.threading != (mode == 'thread')) - rcmail.set_list_options(null, undefined, undefined, mode == 'thread' ? 1 : 0); - - $('#maillistmode, #mailthreadmode').removeClass('selected'); - $('#mail'+mode+'mode').addClass('selected'); + if (!$('#mail'+mode+'mode').hasClass('disabled')) { + $('#maillistmode, #mailthreadmode').removeClass('selected'); + $('#mail'+mode+'mode').addClass('selected'); + } } @@ -761,11 +761,15 @@ function rcube_mail_ui() obj = popups['searchmenu'], list = $('input:checkbox[name="s_mods[]"]', obj), mbox = rcmail.env.mailbox, - mods = rcmail.env.search_mods; + mods = rcmail.env.search_mods, + scope = rcmail.env.search_scope || 'base'; if (rcmail.env.task == 'mail') { + if (scope == 'all') + mbox = '*'; mods = mods[mbox] ? mods[mbox] : mods['*']; all = 'text'; + $('#s_scope_'+scope).prop('checked', true); } else { all = '*'; @@ -896,7 +900,11 @@ function rcube_mail_ui() { var all, m, task = rcmail.env.task, mods = rcmail.env.search_mods, - mbox = rcmail.env.mailbox; + mbox = rcmail.env.mailbox, + scope = $('input[name="s_scope"]:checked').val(); + + if (scope == 'all') + mbox = '*'; if (!mods) mods = {}; @@ -937,6 +945,11 @@ function rcube_mail_ui() }); } + function set_searchscope(elem) + { + rcmail.env.search_scope = elem.value; + } + function push_contactgroup(p) { // lets the contacts list swipe to the left, nice! -- cgit v1.2.3 From 6f170902580c8cef342c0c65df1b98cbd293a79d Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Mon, 20 Jan 2014 10:18:22 +0100 Subject: Fix row UID handling in list widget --- program/js/list.js | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/program/js/list.js b/program/js/list.js index c026ccb4a..c822aa43b 100644 --- a/program/js/list.js +++ b/program/js/list.js @@ -107,11 +107,7 @@ init: function() */ init_row: function(row) { - var uid; - if (row && (uid = $(row).data('uid'))) - row.uid = uid; - else if (row && String(row.id).match(this.id_regexp)) - row.uid = RegExp.$1; + row.uid = this.get_row_uid(row); // make references in internal array and set event handlers if (row && row.uid) { @@ -726,6 +722,19 @@ update_expando: function(id, expanded) expando.className = expanded ? 'expanded' : 'collapsed'; }, +get_row_uid: function(row) +{ + if (row && row.uid) + return row.uid; + + var uid; + if (row && (uid = $(row).data('uid'))) + row.uid = uid; + else if (row && String(row.id).match(this.id_regexp)) + row.uid = RegExp.$1; + + return row.uid; +}, /** * get first/next/previous/last rows that are not hidden @@ -761,11 +770,11 @@ get_prev_row: function() get_first_row: function() { if (this.rowcount) { - var i, len, rows = this.tbody.childNodes; + var i, len, uid, rows = this.tbody.childNodes; for (i=0, len=rows.length-1; i=0; i--) - if (rows[i].id && String(rows[i].id).match(this.id_regexp) && this.rows[RegExp.$1] != null) - return RegExp.$1; + if (rows[i].id && (uid = this.get_row_uid(rows[i]))) + return uid; } return null; @@ -1351,12 +1360,9 @@ drag_mouse_move: function(e) // get selected rows (in display order), don't use this.selection here $(this.row_tagname() + '.selected', this.tbody).each(function() { - if (!String(this.id).match(self.id_regexp)) - return; - - var uid = RegExp.$1, row = self.rows[uid]; + var uid = self.get_row_uid(this), row = self.rows[uid]; - if ($.inArray(uid, selection) > -1) + if (!uid || $.inArray(uid, selection) > -1) return; selection.push(uid); -- cgit v1.2.3 From 6884f3f1f7542eb1dab8bb39cab33888d3fd84c5 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Mon, 20 Jan 2014 11:41:58 +0100 Subject: Re-submit search query when switching folders --- program/js/app.js | 28 ++++++++++++++++++---------- program/steps/mail/search.inc | 2 ++ skins/larry/ui.js | 2 +- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/program/js/app.js b/program/js/app.js index 2401e1e0c..e276f6f96 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -697,9 +697,17 @@ function rcube_webmail() break; case 'list': - // TODO: don't reset search but re-send for the new folder - if (props && props != '') - this.reset_qsearch(); + // re-send for the selected folder + if (props && props != '' && this.env.search_request) { + 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 + this.env.mailbox = props; + this.env.search_scope = 'base'; + this.qsearch(this.gui_objects.qsearchbox.value); + this.select_folder(this.env.mailbox, '', true); + break; + } + if (this.env.action == 'compose' && this.env.extwin) window.close(); else if (this.task == 'mail') { @@ -4100,11 +4108,11 @@ function rcube_webmail() }; // send remote request to search mail or contacts - this.qsearch = function(value) + this.qsearch = function(value, mods) { if (value != '') { var r, lock = this.set_busy(true, 'searching'), - url = this.search_params(value); + url = this.search_params(value, null, mods); if (this.message_list) this.clear_message_list(); @@ -4128,7 +4136,7 @@ function rcube_webmail() }; // build URL params for search - this.search_params = function(search, filter) + this.search_params = function(search, filter, smods) { var n, url = {}, mods_arr = [], mods = this.env.search_mods, @@ -4147,11 +4155,11 @@ function rcube_webmail() if (search) { url._q = search; - if (mods && this.message_list) - mods = mods[mbox] ? mods[mbox] : mods['*']; + if (!smods && mods && this.message_list) + smods = mods[mbox] || mods['*']; - if (mods) { - for (n in mods) + if (smods) { + for (n in smods) mods_arr.push(n); url._headers = mods_arr.join(','); } diff --git a/program/steps/mail/search.inc b/program/steps/mail/search.inc index 88bbe6eda..51dab7dc4 100644 --- a/program/steps/mail/search.inc +++ b/program/steps/mail/search.inc @@ -21,6 +21,8 @@ $REMOTE_REQUEST = TRUE; +@set_time_limit(170); // extend default max_execution_time to ~3 minutes + // reset list_page and old search results $RCMAIL->storage->set_page(1); $RCMAIL->storage->set_search_set(NULL); diff --git a/skins/larry/ui.js b/skins/larry/ui.js index 2030b05cd..e5fce9197 100644 --- a/skins/larry/ui.js +++ b/skins/larry/ui.js @@ -769,7 +769,7 @@ function rcube_mail_ui() mbox = '*'; mods = mods[mbox] ? mods[mbox] : mods['*']; all = 'text'; - $('#s_scope_'+scope).prop('checked', true); + $('#s_scope_'+scope).attr('checked',true); } else { all = '*'; -- cgit v1.2.3 From 9202e93b754d07c0e6312f8b51bd4092ea14961d Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Mon, 20 Jan 2014 11:59:57 +0100 Subject: Fix slicing of returned search result headers --- program/lib/Roundcube/rcube_imap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index dd0501c2d..794f25a37 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -978,7 +978,7 @@ class rcube_imap extends rcube_storage $search_set->set_message_index($a_msg_headers, $sort_field, $this->sort_order); // only return the requested part of the set - $slice_length = min($page_size, $cnt - ($to > $cnt ? $from : $to)); + $slice_length = min($page_size, $cnt - $from); $a_msg_headers = array_slice(array_values($a_msg_headers), $from, $slice_length); if ($slice) { -- cgit v1.2.3 From 1d6082c5e2ad4f12986c59fee49bec5bc68bd829 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Mon, 20 Jan 2014 12:12:21 +0100 Subject: Fix thread/list mode update and display --- program/lib/Roundcube/rcube_imap.php | 4 ++++ program/steps/mail/search.inc | 1 + skins/larry/ui.js | 7 ++++--- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index 794f25a37..6bb922d90 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -332,6 +332,10 @@ class rcube_imap extends rcube_storage $this->search_sort_field = $set[3]; $this->search_sorted = $set[4]; $this->search_threads = is_a($this->search_set, 'rcube_result_thread'); + + if (is_a($this->search_set, 'rcube_result_multifolder')) { + $this->set_threading(false); + } } diff --git a/program/steps/mail/search.inc b/program/steps/mail/search.inc index 51dab7dc4..e13bc2ce5 100644 --- a/program/steps/mail/search.inc +++ b/program/steps/mail/search.inc @@ -181,6 +181,7 @@ else { // update message count display $OUTPUT->set_env('search_request', $search_str ? $search_request : ''); +$OUTPUT->set_env('threading', $RCMAIL->storage->get_threading()); $OUTPUT->set_env('messagecount', $count); $OUTPUT->set_env('pagecount', ceil($count/$RCMAIL->storage->get_pagesize())); $OUTPUT->set_env('exists', $RCMAIL->storage->count($mbox_name, 'EXISTS')); diff --git a/skins/larry/ui.js b/skins/larry/ui.js index e5fce9197..b95e1f1c6 100644 --- a/skins/larry/ui.js +++ b/skins/larry/ui.js @@ -139,7 +139,8 @@ function rcube_mail_ui() if (rcmail.env.task == 'mail') { rcmail.addEventListener('menu-open', menu_open) .addEventListener('menu-save', menu_save) - .addEventListener('responseafterlist', function(e){ switch_view_mode(rcmail.env.threading ? 'thread' : 'list') }); + .addEventListener('responseafterlist', function(e){ switch_view_mode(rcmail.env.threading ? 'thread' : 'list', true) }) + .addEventListener('responseaftersearch', function(e){ switch_view_mode(rcmail.env.threading ? 'thread' : 'list', true) }); var dragmenu = $('#dragmessagemenu'); if (dragmenu.length) { @@ -730,9 +731,9 @@ function rcube_mail_ui() /** * */ - function switch_view_mode(mode) + function switch_view_mode(mode, force) { - if (!$('#mail'+mode+'mode').hasClass('disabled')) { + if (force || !$('#mail'+mode+'mode').hasClass('disabled')) { $('#maillistmode, #mailthreadmode').removeClass('selected'); $('#mail'+mode+'mode').addClass('selected'); } -- cgit v1.2.3 From 19262e6eab19951393154d8e52fe141c63b5cd5d Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Mon, 20 Jan 2014 15:53:28 +0100 Subject: Code cleanup: remove duplicated code, don't trigger multi-folder search on simple filter requests --- program/js/app.js | 2 +- program/steps/mail/func.inc | 24 +++++++++++++++++++++--- program/steps/mail/list.inc | 22 +++------------------- program/steps/mail/search.inc | 39 +++++++++++---------------------------- 4 files changed, 36 insertions(+), 51 deletions(-) diff --git a/program/js/app.js b/program/js/app.js index e276f6f96..675372107 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -6422,7 +6422,7 @@ function rcube_webmail() this.env.status_col = n; if (list) { - list.hide_column('folder', !(this.env.search_request || this.env.search_id)); + list.hide_column('folder', !(this.env.search_request || this.env.search_id) || this.env.search_scope == 'base'); list.init_header(); } }; diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index fd321e294..3487ec73f 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -363,7 +363,7 @@ function rcmail_message_list($attrib) /** * return javascript commands to add rows to the message list */ -function rcmail_js_message_list($a_headers, $insert_top=FALSE, $a_show_cols=null) +function rcmail_js_message_list($a_headers, $insert_top=false, $a_show_cols=null) { global $RCMAIL, $OUTPUT; @@ -382,6 +382,14 @@ function rcmail_js_message_list($a_headers, $insert_top=FALSE, $a_show_cols=null $head_replace = true; } + // add 'folder' column to list on multi-folder searches + $search_set = $RCMAIL->storage->get_search_set(); + $multifolder = $search_set && $search_set[1]->multi; + if ($multifolder && !in_array('folder', $a_show_cols)) { + $a_show_cols[] = 'folder'; + $head_replace = true; + } + $mbox = $RCMAIL->storage->get_folder(); // make sure 'threads' and 'subject' columns are present @@ -390,8 +398,6 @@ function rcmail_js_message_list($a_headers, $insert_top=FALSE, $a_show_cols=null if (!in_array('threads', $a_show_cols)) array_unshift($a_show_cols, 'threads'); - $_SESSION['list_attrib']['columns'] = $a_show_cols; - // Make sure there are no duplicated columns (#1486999) $a_show_cols = array_unique($a_show_cols); @@ -412,6 +418,10 @@ function rcmail_js_message_list($a_headers, $insert_top=FALSE, $a_show_cols=null $OUTPUT->command('set_message_coltypes', $a_show_cols, $thead, $smart_col); + if ($multifolder) { + $OUTPUT->command('select_folder', ''); + } + if (empty($a_headers)) { return; } @@ -428,6 +438,14 @@ function rcmail_js_message_list($a_headers, $insert_top=FALSE, $a_show_cols=null if (empty($header)) continue; + // make message UIDs unique by appending the folder name + if ($multifolder) { + $header->uid .= '-'.$header->folder; + $header->flags['skip_mbox_check'] = true; + if ($header->parent_uid) + $header->parent_uid .= '-'.$header->folder; + } + $a_msg_cols = array(); $a_msg_flags = array(); diff --git a/program/steps/mail/list.inc b/program/steps/mail/list.inc index a77d60f32..18f771d8b 100644 --- a/program/steps/mail/list.inc +++ b/program/steps/mail/list.inc @@ -42,6 +42,7 @@ if ($sort = rcube_utils::get_input_value('_sort', rcube_utils::INPUT_GET)) { // is there a set of columns for this request? if ($cols = rcube_utils::get_input_value('_cols', rcube_utils::INPUT_GET)) { + $_SESSION['list_attrib']['columns'] = $cols; if (!in_array('list_cols', $dont_override)) { $save_arr['list_cols'] = explode(',', $cols); } @@ -75,24 +76,6 @@ if (!empty($_REQUEST['_search']) && isset($_SESSION['search']) && $_SESSION['search_request'] == $_REQUEST['_search'] ) { $_SESSION['search'] = $RCMAIL->storage->get_search_set(); - - // multi-folder search - if ($_SESSION['search'][1]->multi) { - if (empty($cols)) - $cols = $_SESSION['list_attrib']['columns'] ? $_SESSION['list_attrib']['columns'] : (array)$CONFIG['list_cols']; - if (!in_array('folder', $cols)) - $cols[] = 'folder'; // Add 'folder' column to list - - // make message UIDs unique by appending the folder name - foreach ($a_headers as $i => $header) { - $header->uid .= '-'.$header->folder; - $header->flags['skip_mbox_check'] = true; - if ($header->parent_uid) - $header->parent_uid .= '-'.$header->folder; - } - - $OUTPUT->command('select_folder', ''); - } } // remove old search data else if (empty($_REQUEST['_search']) && isset($_SESSION['search'])) { @@ -119,7 +102,8 @@ $OUTPUT->set_env('exists', $exists); $OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($count), $mbox_name); // add message rows -rcmail_js_message_list($a_headers, FALSE, $cols); +rcmail_js_message_list($a_headers, false, $cols); + if (isset($a_headers) && count($a_headers)) { if ($search_request) { $OUTPUT->show_message('searchsuccessful', 'confirmation', array('nr' => $count)); diff --git a/program/steps/mail/search.inc b/program/steps/mail/search.inc index e13bc2ce5..e17552f7d 100644 --- a/program/steps/mail/search.inc +++ b/program/steps/mail/search.inc @@ -104,21 +104,21 @@ if (!empty($subject)) { foreach ($subject as $sub) { $search_str .= ' ' . $sub . ' ' . rcube_imap_generic::escape($search); } + + // search all, current or subfolders folders + if ($scope == 'all') { + $mboxes = $RCMAIL->storage->list_folders_subscribed('', '*', 'mail'); + } + else if ($scope == 'sub') { + $mboxes = $RCMAIL->storage->list_folders_subscribed($mbox, '*', 'mail'); + if ($mbox != 'INBOX' && $mboxes[0] == 'INBOX') + array_shift($mboxes); + } } $search_str = trim($search_str); $sort_column = rcmail_sort_column(); -// search all, current or subfolders folders -if ($scope == 'all') { - $mboxes = $RCMAIL->storage->list_folders_subscribed('', '*', 'mail'); -} -else if ($scope == 'sub') { - $mboxes = $RCMAIL->storage->list_folders_subscribed($mbox, '*', 'mail'); - if ($mbox != 'INBOX' && $mboxes[0] == 'INBOX') - array_shift($mboxes); -} - // execute IMAP search if ($search_str) { $RCMAIL->storage->search($mboxes, $search_str, $imap_charset, $sort_column); @@ -140,26 +140,9 @@ if ($search_str) { $_SESSION['search_request'] = $search_request; $_SESSION['search_scope'] = $scope; -// Add 'folder' column to list -if ($_SESSION['search'][1]->multi) { - $a_show_cols = $_SESSION['list_attrib']['columns'] ? $_SESSION['list_attrib']['columns'] : (array)$CONFIG['list_cols']; - if (!in_array('folder', $a_show_cols)) - $a_show_cols[] = 'folder'; - - // make message UIDs unique by appending the folder name - foreach ($result_h as $i => $header) { - $header->uid .= '-'.$header->folder; - $header->flags['skip_mbox_check'] = true; - if ($header->parent_uid) - $header->parent_uid .= '-'.$header->folder; - } - - $OUTPUT->command('select_folder', ''); -} - // Make sure we got the headers if (!empty($result_h)) { - rcmail_js_message_list($result_h, false, $a_show_cols); + rcmail_js_message_list($result_h, false); if ($search_str) { $OUTPUT->show_message('searchsuccessful', 'confirmation', array('nr' => $RCMAIL->storage->count(NULL, 'ALL'))); } -- cgit v1.2.3 From d93ce5cde23b7170b96fd9816e8d5e8cfdf6e0f6 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Tue, 21 Jan 2014 17:18:28 +0100 Subject: Fix concurrent connections to IMAP while searching --- program/lib/Roundcube/rcube_imap_search.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/program/lib/Roundcube/rcube_imap_search.php b/program/lib/Roundcube/rcube_imap_search.php index 70a11bc1c..c88198140 100644 --- a/program/lib/Roundcube/rcube_imap_search.php +++ b/program/lib/Roundcube/rcube_imap_search.php @@ -189,6 +189,8 @@ class rcube_imap_search_job extends Stackable $imap = $this->worker->get_imap(); if (!$imap->connected()) { + trigger_error("No IMAP connection for $this->folder", E_USER_WARNING); + if ($this->threading) { return new rcube_result_thread(); } @@ -280,14 +282,13 @@ class rcube_imap_search_worker extends Worker public $options; private $conn; + private $counts = 0; /** * Default constructor */ public function __construct($id, $options) { - $options['ident']['command'] = 'search-'.$id; - $this->id = $id; $this->options = $options; } @@ -298,21 +299,19 @@ class rcube_imap_search_worker extends Worker public function get_imap() { // TODO: make this connection persistent for several jobs - #if ($this->conn) - # return $this->conn; + // This doesn't seem to work. Socket connections don't survive serialization which is used in pthreads $conn = new rcube_imap_generic(); # $conn->setDebug(true, function($conn, $message){ trigger_error($message, E_USER_NOTICE); }); if ($this->options['user'] && $this->options['password']) { - // TODO: do this synchronized to avoid warnings like "Only one Id allowed in non-authenticated state" + $this->options['ident']['command'] = 'search-' . $this->id . 't' . ++$this->counts; $conn->connect($this->options['host'], $this->options['user'], $this->options['password'], $this->options); } if ($conn->error) trigger_error($conn->error, E_USER_WARNING); - #$this->conn = $conn; return $conn; } -- cgit v1.2.3 From 4a7a861912e7b7353a5575fe7217be3626a8dfef Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Tue, 21 Jan 2014 17:19:02 +0100 Subject: Apply the right flags for searching in all folders --- program/js/app.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/program/js/app.js b/program/js/app.js index 675372107..1cbc36a76 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -4108,11 +4108,11 @@ function rcube_webmail() }; // send remote request to search mail or contacts - this.qsearch = function(value, mods) + this.qsearch = function(value) { if (value != '') { var r, lock = this.set_busy(true, 'searching'), - url = this.search_params(value, null, mods); + url = this.search_params(value); if (this.message_list) this.clear_message_list(); @@ -4140,8 +4140,8 @@ function rcube_webmail() { var n, url = {}, mods_arr = [], mods = this.env.search_mods, - mbox = this.env.mailbox, - scope = this.env.search_scope || 'base'; + scope = this.env.search_scope || 'base', + mbox = scope == 'all' ? '*' : this.env.mailbox; if (!filter && this.gui_objects.search_filter) filter = this.gui_objects.search_filter.value; -- cgit v1.2.3 From e3857bb6c2a2f9a4331a72b0bd74e0d70ee8a8a1 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Tue, 4 Feb 2014 10:22:29 +0100 Subject: Fix infinite loop when converting invalid html to plaintext (#1489566) --- program/lib/Roundcube/rcube_html2text.php | 4 ++++ tests/Framework/Html2text.php | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/program/lib/Roundcube/rcube_html2text.php b/program/lib/Roundcube/rcube_html2text.php index 01362e6fb..3b4508da9 100644 --- a/program/lib/Roundcube/rcube_html2text.php +++ b/program/lib/Roundcube/rcube_html2text.php @@ -616,6 +616,10 @@ class rcube_html2text break; } + // abort on invalid tag structure (e.g. no closing tag found) + else { + break; + } } while ($end || $next); } diff --git a/tests/Framework/Html2text.php b/tests/Framework/Html2text.php index 3e0df48d9..2c7759f7d 100644 --- a/tests/Framework/Html2text.php +++ b/tests/Framework/Html2text.php @@ -75,4 +75,32 @@ EOF; $this->assertContains('>> INNER 3', $res, 'Quote inner'); $this->assertContains('> OUTER END', $res, 'Quote outer'); } + + function test_broken_blockquotes() + { + // no end tag + $html = << +
    QUOTED TEXT +
    +NO END TAG FOUND +EOF; + $ht = new rcube_html2text($html, false, false); + $res = $ht->get_text(); + + $this->assertContains('QUOTED TEXT NO END TAG FOUND', $res, 'No quoating on invalid html'); + + // with some (nested) end tags + $html = << +
    QUOTED TEXT +
    INNER 1
    +
    INNER 2
    +NO END TAG FOUND +EOF; + $ht = new rcube_html2text($html, false, false); + $res = $ht->get_text(); + + $this->assertContains('QUOTED TEXT INNER 1 INNER 2 NO END', $res, 'No quoating on invalid html'); + } } -- cgit v1.2.3