Merge branch 'dev-multi-folder-search'

pull/193/head
Thomas Bruederli 11 years ago
commit 66536974fe

@ -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();
}

@ -31,6 +31,7 @@ function rcube_webmail()
this.onloads = [];
this.messages = {};
this.group2expand = {};
this.http_request_jobs = {};
// webmail client settings
this.dblclick_time = 500;
@ -216,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');
@ -689,7 +691,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;
@ -700,8 +702,17 @@ function rcube_webmail()
break;
case 'list':
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') {
@ -712,6 +723,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;
@ -792,9 +807,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;
@ -1077,7 +1092,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');
@ -1105,7 +1120,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);
@ -1122,8 +1137,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
@ -1630,7 +1646,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);
@ -1762,7 +1778,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]);
@ -1774,17 +1790,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); };
}
@ -1831,6 +1847,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
});
@ -1845,7 +1862,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';
@ -1871,7 +1888,7 @@ function rcube_webmail()
if (this.env.threading) {
if (message.depth) {
// This assumes that div width is hardcoded to 15px,
tree += '<span id="rcmtab' + uid + '" class="branch" style="width:' + (message.depth * 15) + 'px;">&nbsp;&nbsp;</span>';
tree += '<span id="rcmtab' + row.id + '" class="branch" style="width:' + (message.depth * 15) + 'px;">&nbsp;&nbsp;</span>';
if ((rows[message.parent_uid] && rows[message.parent_uid].expanded === false)
|| ((this.env.autoexpand_threads == 0 || this.env.autoexpand_threads == 2) &&
@ -1890,7 +1907,7 @@ function rcube_webmail()
message.expanded = true;
}
expando = '<div id="rcmexpando' + uid + '" class="' + (message.expanded ? 'expanded' : 'collapsed') + '">&nbsp;&nbsp;</div>';
expando = '<div id="rcmexpando' + row.id + '" class="' + (message.expanded ? 'expanded' : 'collapsed') + '">&nbsp;&nbsp;</div>';
row_class += ' thread' + (message.expanded? ' expanded' : '');
}
@ -1898,14 +1915,14 @@ function rcube_webmail()
row_class += ' unroot';
}
tree += '<span id="msgicn'+uid+'" class="'+css_class+'">&nbsp;</span>';
tree += '<span id="msgicn'+row.id+'" class="'+css_class+'">&nbsp;</span>';
row.className = row_class;
// build subject link
if (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 = '<a href="./?_task=mail&_action='+action+'&_mbox='+urlencode(flags.mbox)+'&'+uid_param+'='+uid+'"'+
cols.subject = '<a href="./?_task=mail&_action='+action+'&_mbox='+urlencode(flags.mbox)+'&'+uid_param+'='+urlencode(uid)+'"'+
' onclick="return rcube_event.cancel(event)" onmouseover="rcube_webmail.long_subject_title(this,'+(message.depth+1)+')"><span>'+cols.subject+'</span></a>';
}
@ -1916,7 +1933,7 @@ function rcube_webmail()
if (c == 'flag') {
css_class = (flags.flagged ? 'flagged' : 'unflagged');
html = '<span id="flagicn'+uid+'" class="'+css_class+'">&nbsp;</span>';
html = '<span id="flagicn'+row.id+'" class="'+css_class+'">&nbsp;</span>';
}
else if (c == 'attachment') {
if (/application\/|multipart\/(m|signed)/.test(flags.ctype))
@ -1935,7 +1952,7 @@ function rcube_webmail()
css_class = 'unreadchildren';
else
css_class = 'msgicon';
html = '<span id="statusicn'+uid+'" class="'+css_class+'">&nbsp;</span>';
html = '<span id="statusicn'+row.id+'" class="'+css_class+'">&nbsp;</span>';
}
else if (c == 'threads')
html = expando;
@ -2033,7 +2050,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;
@ -2398,7 +2415,7 @@ function rcube_webmail()
}
if (html)
$('#rcmtab'+uid).html(html);
$('#rcmtab'+this.html_identifier(uid, true)).html(html);
};
// update parent in a thread
@ -2462,14 +2479,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); });
@ -4148,15 +4165,17 @@ 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');
}
};
// 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,
mbox = this.env.mailbox;
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;
@ -4170,17 +4189,19 @@ 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(',');
}
}
if (mbox)
if (scope)
url._scope = scope;
if (mbox && scope != 'all')
url._mbox = mbox;
return url;
@ -4198,6 +4219,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)
@ -4376,7 +4399,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);
@ -4403,34 +4426,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<threads; i++) {
source = this.ksearch_data.sources.shift();
if (threads > 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;
@ -4442,7 +4457,6 @@ function rcube_webmail()
// display search results
var i, len, ul, li, text, type, 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
@ -4498,27 +4512,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)
@ -4553,7 +4548,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);
@ -4564,18 +4560,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<len; i++)
this.abort_request({request: ac.requests[i], lock: ac.locks[i]});
};
@ -6482,8 +6466,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) || this.env.search_scope == 'base');
list.init_header();
}
};
// replace content of row count display
@ -7112,6 +7098,130 @@ function rcube_webmail()
clearTimeout(this.submit_timer);
};
/**
Send multi-threaded parallel HTTP requests to the server for a list if items.
The string '%' in either a GET query or POST parameters will be replaced with the respective item value.
This is the argument object expected: {
items: ['foo','bar','gna'], // list of items to send requests for
action: 'task/some-action', // Roudncube action to call
query: { q:'%s' }, // GET query parameters
postdata: { source:'%s' }, // POST data (sends a POST request if present)
threads: 3, // max. number of concurrent requests
onresponse: function(data){ }, // Callback function called for every response received from server
whendone: function(alldata){ } // Callback function called when all requests have been sent
}
*/
this.multi_thread_http_request = function(prop)
{
var reqid = new Date().getTime();
prop.reqid = reqid;
prop.running = 0;
prop.requests = [];
prop.result = [];
prop._items = $.extend([], prop.items); // copy items
if (!prop.lock)
prop.lock = this.display_message(this.get_label('loading'), 'loading');
// add the request arguments to the jobs pool
this.http_request_jobs[reqid] = prop;
// start n threads
var item, threads = prop.threads || 1;
for (var i=0; i < threads; i++) {
item = prop._items.shift();
if (item === undefined)
break;
prop.running++;
prop.requests.push(this.multi_thread_send_request(prop, item));
}
return reqid;
};
// helper method to send an HTTP request with the given iterator value
this.multi_thread_send_request = function(prop, item)
{
var postdata, query;
// replace %s in post data
if (prop.postdata) {
postdata = {};
for (var k in prop.postdata) {
postdata[k] = String(prop.postdata[k]).replace('%s', item);
}
postdata._reqid = prop.reqid;
}
// replace %s in query
else if (typeof prop.query == 'string') {
query = prop.query.replace('%s', item);
query += '&_reqid=' + prop.reqid;
}
else if (typeof prop.query == 'object' && prop.query) {
query = {};
for (var k in prop.query) {
query[k] = String(prop.query[k]).replace('%s', item);
}
query._reqid = prop.reqid;
}
// send HTTP GET or POST request
return postdata ? this.http_post(prop.action, postdata) : this.http_request(prop.action, query);
};
// callback function for multi-threaded http responses
this.multi_thread_http_response = function(data, reqid)
{
var prop = this.http_request_jobs[reqid];
if (!prop || prop.running <= 0 || prop.cancelled)
return;
prop.running--;
// trigger response callback
if (prop.onresponse && typeof prop.onresponse == 'function') {
prop.onresponse(data);
}
prop.result = $.extend(prop.result, data);
// send next request if prop.items is not yet empty
var item = prop._items.shift();
if (item !== undefined) {
prop.running++;
prop.requests.push(this.multi_thread_send_request(prop, item));
}
// trigger whendone callback and mark this request as done
else if (prop.running == 0) {
if (prop.whendone && typeof prop.whendone == 'function') {
prop.whendone(prop.result);
}
this.set_busy(false, '', prop.lock);
// remove from this.http_request_jobs pool
delete this.http_request_jobs[reqid];
}
};
// abort a running multi-thread request with the given identifier
this.multi_thread_request_abort = function(reqid)
{
var prop = this.http_request_jobs[reqid];
if (prop) {
for (var i=0; prop.running > 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)
{
@ -7389,6 +7499,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)
{

@ -107,11 +107,11 @@ init: function()
*/
init_row: function(row)
{
row.uid = this.get_row_uid(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;
row.uid = uid;
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
@ -299,6 +299,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];
@ -389,6 +390,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
*/
@ -583,7 +598,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 {
@ -633,7 +648,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
@ -655,7 +670,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 });
}
}
@ -677,7 +692,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 {
@ -694,7 +709,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 });
}
}
@ -708,13 +723,26 @@ 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';
},
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
@ -750,11 +778,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<len; 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;
@ -763,11 +791,11 @@ get_first_row: function()
get_last_row: function()
{
if (this.rowcount) {
var i, rows = this.tbody.childNodes;
var i, uid, rows = this.tbody.childNodes;
for (i=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;
@ -1261,7 +1289,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;
}
@ -1340,10 +1368,7 @@ 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 (!row || $.inArray(uid, selection) > -1)
return;

@ -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);
}
}
@ -945,6 +949,54 @@ 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 = 1000; // fetch up to 1000 matching messages per folder
$this->threading = false;
$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 && !empty($a_msg_headers)) {
$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 - $from);
$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);
@ -1111,6 +1163,7 @@ class rcube_imap extends rcube_storage
}
foreach ($headers as $h) {
$h->folder = $folder;
$a_msg_headers[$h->uid] = $h;
}
@ -1234,8 +1287,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))
) {
@ -1422,11 +1480,34 @@ 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 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,
$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));
@ -1500,7 +1581,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;
@ -1514,7 +1595,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()) {
@ -1529,7 +1610,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;
@ -1547,7 +1628,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)) {
@ -1583,6 +1664,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);
}
@ -1605,6 +1687,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);
@ -1636,6 +1723,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) {
@ -2397,7 +2489,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);
}
}

@ -0,0 +1,336 @@
<?php
/*
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) 2013, The Roundcube Dev Team |
| Copyright (C) 2013, Kolab Systems AG |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Execute (multi-threaded) searches in multiple IMAP folders |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
// 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 <roundcube@gmail.com>
*/
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()
{
$pthreads = defined('PTHREADS_INHERIT_ALL');
$criteria = $this->search;
$charset = $this->charset;
$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();
}
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');
}
// close IMAP connection again
if ($pthreads)
$imap->closeConnection();
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 || $messages->is_error()) {
$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);
}
}
// close IMAP connection again
if ($pthreads)
$imap->closeConnection();
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;
}
}
/**
* Worker thread to run search jobs while maintaining a common context
*/
class rcube_imap_search_worker extends Worker
{
public $id;
public $options;
private $conn;
private $counts = 0;
/**
* 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
// 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']) {
$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);
return $conn;
}
/**
* @override
*/
public function run()
{
}
/**
* Close IMAP connection
*/
public function close()
{
if ($this->conn) {
$this->conn->close();
}
}
}

@ -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',

@ -0,0 +1,250 @@
<?php
/*
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2011, The Roundcube Dev Team |
| Copyright (C) 2011, Kolab Systems AG |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| SORT/SEARCH/ESEARCH response handler |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
/**
* 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();
public $folder;
protected $meta = array();
protected $index = array();
protected $sorting;
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)
{
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);
}
}
/**
* 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
*
* @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)
{
if (!empty($this->folder)) {
$msgid .= '-' . $this->folder;
}
return array_search($msgid, $this->index);
}
/**
* 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 $this->index;
}
/**
* 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($idx)
{
switch ($idx) {
case 'FIRST': return $this->index[0];
case 'LAST': return end($this->index);
default: return $this->index[$idx];
}
}
/**
* 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)
{
$params = array(
'SORT' => $this->sorting,
'ORDER' => $this->order,
);
if ($param !== null) {
return $params[$param];
}
return $params;
}
/**
* Returns length of internal data representation
*
* @return int Data length
*/
protected function length()
{
return $this->count();
}
}

@ -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)';

@ -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);
@ -155,5 +155,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();

@ -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

@ -68,6 +68,23 @@ 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);
// override mbox
if (!empty($mbox)) {
$_GET['_mbox'] = $mbox;
$_POST['_mbox'] = $mbox;
$RCMAIL->storage->set_folder(($_SESSION['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
@ -88,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();
@ -166,6 +186,34 @@ $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 <ID>-<MBOX>[,<ID>-<MBOX>]*
$_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
@ -315,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;
@ -334,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
@ -342,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);
@ -364,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;
}
@ -380,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();
@ -398,6 +464,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 +489,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))

@ -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);
}
@ -101,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));

@ -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);

@ -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;

@ -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);
@ -35,6 +37,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();
$filter = trim($filter);
@ -102,6 +105,16 @@ 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);
@ -109,9 +122,13 @@ $sort_column = rcmail_sort_column();
// 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);
}
// 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();
@ -122,15 +139,11 @@ if ($search_str) {
$_SESSION['last_text_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');
$_SESSION['search_scope'] = $scope;
// Make sure we got the headers
if (!empty($result_h)) {
rcmail_js_message_list($result_h);
rcmail_js_message_list($result_h, false);
if ($search_str) {
$OUTPUT->show_message('searchsuccessful', 'confirmation', array('nr' => $RCMAIL->storage->count(NULL, 'ALL')));
}
@ -152,6 +165,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'));

@ -514,6 +514,18 @@ table.messagelist.fixedcopy {
width: 155px;
}
.messagelist tr td.folder {
width: 135px;
}
.messagelist tr td.folder {
width: 135px;
}
.messagelist tr td.hidden {
display: none;
}
.messagelist tr.message {
/* background-color: #fff; */
}

@ -76,13 +76,8 @@
<!-- list footer -->
<div id="messagelistfooter">
<div id="listcontrols">
<roundcube:if condition="env:threads" />
<a href="#list" class="iconbutton listmode" id="maillistmode" title="<roundcube:label name='list' />">List</a>
<a href="#threads" class="iconbutton threadmode" id="mailthreadmode" title="<roundcube:label name='threads' />">Threads</a>
<roundcube:else />
<a href="#list" class="iconbutton listmode selected" title="<roundcube:label name='list' />" onclick="return false">List</a>
<a href="#threads" class="iconbutton threadmode disabled" title="<roundcube:label name='threads' />" onclick="return false">Threads</a>
<roundcube:endif />
<roundcube:button href="#list" command="set-listmode" prop="list" class="iconbutton listmode disabled" classAct="iconbutton listmode" id="maillistmode" title="list" content="List" />
<roundcube:button href="#threads" command="set-listmode" prop="threads" class="iconbutton threadmode disabled" classAct="iconbutton threadmode" id="mailthreadmode" title="threads" content="Threads" />
</div>
<div id="listselectors">
@ -132,6 +127,10 @@
<li><label><input type="checkbox" name="s_mods[]" value="bcc" id="s_mod_bcc" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="bcc" /></span></label></li>
<li><label><input type="checkbox" name="s_mods[]" value="body" id="s_mod_body" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="body" /></span></label></li>
<li><label><input type="checkbox" name="s_mods[]" value="text" id="s_mod_text" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="msgtext" /></span></label></li>
<li class="separator" id=""><label><roundcube:label name="searchscope" /></label></li>
<li><label><input type="radio" name="s_scope" value="base" id="s_scope_base" onclick="UI.set_searchscope(this)" /> <span><roundcube:label name="currentfolder" /></span></label></li>
<li><label><input type="radio" name="s_scope" value="sub" id="s_scope_sub" onclick="UI.set_searchscope(this)" /> <span><roundcube:label name="subfolders" /></span></label></li>
<li><label><input type="radio" name="s_scope" value="all" id="s_scope_all" onclick="UI.set_searchscope(this)" /> <span><roundcube:label name="allfolders" /></span></label></li>
</ul>
</div>

@ -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;
@ -138,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) {
@ -729,13 +731,12 @@ function rcube_mail_ui()
/**
*
*/
function switch_view_mode(mode)
function switch_view_mode(mode, force)
{
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 (force || !$('#mail'+mode+'mode').hasClass('disabled')) {
$('#maillistmode, #mailthreadmode').removeClass('selected');
$('#mail'+mode+'mode').addClass('selected');
}
}
@ -761,11 +762,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).attr('checked',true);
}
else {
all = '*';
@ -896,7 +901,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 +946,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!

Loading…
Cancel
Save