Merge branch 'master' of github.com:roundcube/roundcubemail
2 files added
16 files modified
| | |
| | | $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()) { |
| | |
| | | |
| | | 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(); |
| | | } |
| | | |
| | |
| | | this.onloads = []; |
| | | this.messages = {}; |
| | | this.group2expand = {}; |
| | | this.http_request_jobs = {}; |
| | | |
| | | // webmail client settings |
| | | this.dblclick_time = 500; |
| | |
| | | .addEventListener('listupdate', function(o) { p.triggerEvent('listupdate', o); }) |
| | | .init(); |
| | | |
| | | // TODO: this should go into the list-widget code |
| | | $(this.message_list.thead).on('click', 'a.sortcol', function(e){ |
| | | return rcmail.command('sort', $(this).attr('rel'), this); |
| | | }); |
| | | |
| | | document.onmouseup = function(e){ return p.doc_mouse_up(e); }; |
| | | 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'); |
| | |
| | | |
| | | 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; |
| | |
| | | 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') { |
| | |
| | | } |
| | | else if (this.task == 'addressbook') |
| | | this.list_contacts(props); |
| | | break; |
| | | |
| | | case 'set-listmode': |
| | | this.set_list_options(null, undefined, undefined, props == 'threads' ? 1 : 0); |
| | | break; |
| | | |
| | | case 'sort': |
| | |
| | | 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; |
| | |
| | | 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'); |
| | |
| | | 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); |
| | |
| | | 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 |
| | |
| | | |
| | | 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); |
| | |
| | | { |
| | | var i, found, name, cols = list.thead.rows[0].cells; |
| | | |
| | | this.env.coltypes = []; |
| | | this.env.listcols = []; |
| | | |
| | | for (i=0; i<cols.length; i++) |
| | | if (cols[i].id && cols[i].id.startsWith('rcm')) { |
| | | name = cols[i].id.slice(3); |
| | | this.env.coltypes.push(name); |
| | | this.env.listcols.push(name); |
| | | } |
| | | |
| | | if ((found = $.inArray('flag', this.env.coltypes)) >= 0) |
| | | if ((found = $.inArray('flag', this.env.listcols)) >= 0) |
| | | this.env.flagged_col = found; |
| | | |
| | | if ((found = $.inArray('subject', this.env.coltypes)) >= 0) |
| | | if ((found = $.inArray('subject', this.env.listcols)) >= 0) |
| | | this.env.subject_col = found; |
| | | |
| | | this.command('save-pref', { name: 'list_cols', value: this.env.coltypes, session: 'list_attrib/columns' }); |
| | | this.command('save-pref', { name: 'list_cols', value: this.env.listcols, session: 'list_attrib/columns' }); |
| | | }; |
| | | |
| | | this.check_droptarget = function(id) |
| | |
| | | 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]); |
| | |
| | | |
| | | // 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); }; |
| | | } |
| | | |
| | |
| | | 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 |
| | | }); |
| | |
| | | + (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'; |
| | |
| | | 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;"> </span>'; |
| | | tree += '<span id="rcmtab' + row.id + '" class="branch" style="width:' + (message.depth * 15) + 'px;"> </span>'; |
| | | |
| | | if ((rows[message.parent_uid] && rows[message.parent_uid].expanded === false) |
| | | || ((this.env.autoexpand_threads == 0 || this.env.autoexpand_threads == 2) && |
| | |
| | | message.expanded = true; |
| | | } |
| | | |
| | | expando = '<div id="rcmexpando' + uid + '" class="' + (message.expanded ? 'expanded' : 'collapsed') + '"> </div>'; |
| | | expando = '<div id="rcmexpando' + row.id + '" class="' + (message.expanded ? 'expanded' : 'collapsed') + '"> </div>'; |
| | | row_class += ' thread' + (message.expanded? ' expanded' : ''); |
| | | } |
| | | |
| | |
| | | row_class += ' unroot'; |
| | | } |
| | | |
| | | tree += '<span id="msgicn'+uid+'" class="'+css_class+'"> </span>'; |
| | | tree += '<span id="msgicn'+row.id+'" class="'+css_class+'"> </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>'; |
| | | } |
| | | |
| | | // add each submitted col |
| | | for (n in this.env.coltypes) { |
| | | c = this.env.coltypes[n]; |
| | | for (n in this.env.listcols) { |
| | | c = this.env.listcols[n]; |
| | | col = { className: String(c).toLowerCase() }; |
| | | |
| | | if (this.env.coltypes[c] && this.env.coltypes[c].hidden) { |
| | | col.className += ' hidden'; |
| | | } |
| | | |
| | | if (c == 'flag') { |
| | | css_class = (flags.flagged ? 'flagged' : 'unflagged'); |
| | | html = '<span id="flagicn'+uid+'" class="'+css_class+'"> </span>'; |
| | | html = '<span id="flagicn'+row.id+'" class="'+css_class+'"> </span>'; |
| | | } |
| | | else if (c == 'attachment') { |
| | | if (/application\/|multipart\/(m|signed)/.test(flags.ctype)) |
| | |
| | | css_class = 'unreadchildren'; |
| | | else |
| | | css_class = 'msgicon'; |
| | | html = '<span id="statusicn'+uid+'" class="'+css_class+'"> </span>'; |
| | | html = '<span id="statusicn'+row.id+'" class="'+css_class+'"> </span>'; |
| | | } |
| | | else if (c == 'threads') |
| | | html = expando; |
| | |
| | | |
| | | if (cols && cols.length) { |
| | | // make sure new columns are added at the end of the list |
| | | var i, idx, name, newcols = [], oldcols = this.env.coltypes; |
| | | var i, idx, name, newcols = [], oldcols = this.env.listcols; |
| | | for (i=0; i<oldcols.length; i++) { |
| | | name = oldcols[i]; |
| | | idx = $.inArray(name, cols); |
| | |
| | | |
| | | 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; |
| | |
| | | } |
| | | |
| | | if (html) |
| | | $('#rcmtab'+uid).html(html); |
| | | $('#rcmtab'+this.html_identifier(uid, true)).html(html); |
| | | }; |
| | | |
| | | // update parent in a thread |
| | |
| | | |
| | | 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); }); |
| | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | this.env.qsearch = null; |
| | | this.env.search_request = null; |
| | | this.env.search_id = null; |
| | | |
| | | this.enable_command('set-listmode', this.env.threads); |
| | | }; |
| | | |
| | | this.set_searchscope = function(scope) |
| | | { |
| | | var old = this.env.search_scope; |
| | | this.env.search_scope = scope; |
| | | |
| | | // re-send search query with new scope |
| | | if (scope != old && this.env.search_request) { |
| | | this.qsearch(this.gui_objects.qsearchbox.value); |
| | | if (scope == 'base') |
| | | this.select_folder(this.env.mailbox, '', true); |
| | | } |
| | | }; |
| | | |
| | | this.set_searchmods = function(mods) |
| | | { |
| | | var mbox = rcmail.env.mailbox, |
| | | scope = this.env.search_scope || 'base'; |
| | | |
| | | if (scope == 'all') |
| | | mbox = '*'; |
| | | |
| | | if (!this.env.search_mods) |
| | | this.env.search_mods = {}; |
| | | |
| | | this.env.search_mods[mbox] = mods; |
| | | }; |
| | | |
| | | this.sent_successfully = function(type, msg, folders) |
| | |
| | | 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); |
| | |
| | | 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'; |
| | | 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 = {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); |
| | | |
| | | 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; |
| | |
| | | // 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 |
| | |
| | | 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) |
| | |
| | | // 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); |
| | |
| | | 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]}); |
| | | }; |
| | | |
| | | |
| | |
| | | |
| | | // for reordering column array (Konqueror workaround) |
| | | // and for setting some message list global variables |
| | | this.set_message_coltypes = function(coltypes, repl, smart_col) |
| | | this.set_message_coltypes = function(listcols, repl, smart_col) |
| | | { |
| | | var list = this.message_list, |
| | | thead = list ? list.thead : null, |
| | | cell, col, n, len, th, tr; |
| | | repl, cell, col, n, len, tr; |
| | | |
| | | this.env.coltypes = coltypes; |
| | | this.env.listcols = listcols; |
| | | |
| | | // replace old column headers |
| | | if (thead) { |
| | | if (repl) { |
| | | th = document.createElement('thead'); |
| | | thead.innerHTML = ''; |
| | | tr = document.createElement('tr'); |
| | | |
| | | for (c=0, len=repl.length; c < len; c++) { |
| | |
| | | if (repl[c].className) cell.className = repl[c].className; |
| | | tr.appendChild(cell); |
| | | } |
| | | th.appendChild(tr); |
| | | thead.parentNode.replaceChild(th, thead); |
| | | list.thead = thead = th; |
| | | thead.appendChild(tr); |
| | | } |
| | | |
| | | for (n=0, len=this.env.coltypes.length; n<len; n++) { |
| | | col = this.env.coltypes[n]; |
| | | for (n=0, len=this.env.listcols.length; n<len; n++) { |
| | | col = this.env.listcols[n]; |
| | | if ((cell = thead.rows[0].cells[n]) && (col == 'from' || col == 'to' || col == 'fromto')) { |
| | | cell.id = 'rcm'+col; |
| | | $('span,a', cell).text(this.get_label(col == 'fromto' ? smart_col : col)); |
| | | // if we have links for sorting, it's a bit more complicated... |
| | | $('a', cell).click(function(){ |
| | | return rcmail.command('sort', this.id.replace(/^rcm/, ''), this); |
| | | }); |
| | | $(cell).attr('rel', col).find('span,a').text(this.get_label(col == 'fromto' ? smart_col : col)); |
| | | } |
| | | } |
| | | } |
| | |
| | | this.env.flagged_col = null; |
| | | this.env.status_col = null; |
| | | |
| | | if ((n = $.inArray('subject', this.env.coltypes)) >= 0) { |
| | | if (this.env.coltypes.folder) |
| | | this.env.coltypes.folder.hidden = !(this.env.search_request || this.env.search_id) || this.env.search_scope == 'base'; |
| | | |
| | | if ((n = $.inArray('subject', this.env.listcols)) >= 0) { |
| | | this.env.subject_col = n; |
| | | if (list) |
| | | list.subject_col = n; |
| | | } |
| | | if ((n = $.inArray('flag', this.env.coltypes)) >= 0) |
| | | if ((n = $.inArray('flag', this.env.listcols)) >= 0) |
| | | this.env.flagged_col = n; |
| | | if ((n = $.inArray('status', this.env.coltypes)) >= 0) |
| | | if ((n = $.inArray('status', this.env.listcols)) >= 0) |
| | | this.env.status_col = n; |
| | | |
| | | if (list) |
| | | if (list) { |
| | | list.hide_column('folder', this.env.coltypes.folder && this.env.coltypes.folder.hidden); |
| | | list.init_header(); |
| | | } |
| | | }; |
| | | |
| | | // replace content of row count display |
| | |
| | | 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) |
| | | { |
| | |
| | | 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) |
| | | { |
| | |
| | | */ |
| | | 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 |
| | |
| | | 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]; |
| | |
| | | $(this.rows[id].obj).removeClass('selected focused').addClass('unfocused'); |
| | | } |
| | | } |
| | | }, |
| | | |
| | | |
| | | /** |
| | | * 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'); |
| | | }, |
| | | |
| | | |
| | |
| | | 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 { |
| | |
| | | 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 |
| | |
| | | $(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 }); |
| | | } |
| | | } |
| | |
| | | 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 { |
| | |
| | | $(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 }); |
| | | } |
| | | } |
| | |
| | | }, |
| | | |
| | | |
| | | 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 |
| | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | this.collapse(selected_row); |
| | | } |
| | | |
| | | this.update_expando(selected_row.uid, selected_row.expanded); |
| | | this.update_expando(selected_row.id, selected_row.expanded); |
| | | |
| | | return false; |
| | | } |
| | |
| | | |
| | | // 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; |
| | |
| | | $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); |
| | | } |
| | | } |
| | | |
| | | |
| | |
| | | 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); |
| | |
| | | } |
| | | |
| | | foreach ($headers as $h) { |
| | | $h->folder = $folder; |
| | | $a_msg_headers[$h->uid] = $h; |
| | | } |
| | | |
| | |
| | | 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)) |
| | | ) { |
| | |
| | | $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)); |
| | |
| | | // 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; |
| | |
| | | // 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()) { |
| | |
| | | // 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; |
| | |
| | | * |
| | | * @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)) { |
| | |
| | | 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); |
| | | } |
| | | |
| | |
| | | { |
| | | if (!strlen($folder)) { |
| | | $folder = $this->folder; |
| | | } |
| | | |
| | | // decode combined UID-folder identifier |
| | | if (preg_match('/^\d+-[^,]+$/', $uid)) { |
| | | list($uid, $folder) = explode('-', $uid); |
| | | } |
| | | |
| | | // get cached headers |
| | |
| | | { |
| | | if (!strlen($folder)) { |
| | | $folder = $this->folder; |
| | | } |
| | | |
| | | // decode combined UID-folder identifier |
| | | if (preg_match('/^\d+-[^,]+$/', $uid)) { |
| | | list($uid, $folder) = explode('-', $uid); |
| | | } |
| | | |
| | | // Check internal cache |
| | |
| | | $this->refresh_search(); |
| | | } |
| | | else { |
| | | $this->search_set->filter(explode(',', $uids)); |
| | | $this->search_set->filter(explode(',', $uids), $this->folder); |
| | | } |
| | | } |
| | | |
New file |
| | |
| | | <?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(); |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | public $mdn_to; |
| | | |
| | | /** |
| | | * IMAP folder this message is stored in |
| | | * |
| | | * @var string |
| | | */ |
| | | public $folder; |
| | | |
| | | /** |
| | | * Other message headers |
| | | * |
| | | * @var array |
| | |
| | | 'reply-to' => 'replyto', |
| | | 'cc' => 'cc', |
| | | 'bcc' => 'bcc', |
| | | 'mbox' => 'folder', |
| | | 'folder' => 'folder', |
| | | 'content-transfer-encoding' => 'encoding', |
| | | 'in-reply-to' => 'in_reply_to', |
| | | 'content-type' => 'ctype', |
New file |
| | |
| | | <?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(); |
| | | } |
| | | } |
| | |
| | | $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)'; |
| | |
| | | $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); |
| | |
| | | } |
| | | } |
| | | |
| | | $OUTPUT->command('ksearch_query_results', $contacts, $search, $sid); |
| | | $OUTPUT->command('ksearch_query_results', $contacts, $search, $reqid); |
| | | $OUTPUT->send(); |
| | |
| | | |
| | | // 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 |
| | |
| | | $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 |
| | |
| | | } |
| | | |
| | | $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(); |
| | |
| | | )); |
| | | |
| | | |
| | | /** |
| | | * 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 |
| | |
| | | $OUTPUT->set_env('sort_col', $_SESSION['sort_col']); |
| | | $OUTPUT->set_env('sort_order', $_SESSION['sort_order']); |
| | | $OUTPUT->set_env('messages', array()); |
| | | $OUTPUT->set_env('coltypes', $a_show_cols); |
| | | $OUTPUT->set_env('listcols', $a_show_cols); |
| | | |
| | | $OUTPUT->include_script('list.js'); |
| | | |
| | |
| | | /** |
| | | * 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; |
| | | |
| | |
| | | $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 |
| | |
| | | array_unshift($a_show_cols, 'subject'); |
| | | 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); |
| | |
| | | |
| | | $OUTPUT->command('set_message_coltypes', $a_show_cols, $thead, $smart_col); |
| | | |
| | | if ($multifolder) { |
| | | $OUTPUT->command('select_folder', ''); |
| | | } |
| | | |
| | | if (empty($a_headers)) { |
| | | return; |
| | | } |
| | |
| | | foreach ($a_headers as $header) { |
| | | 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(); |
| | |
| | | $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); |
| | | |
| | |
| | | $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)) |
| | |
| | | $list_menu = ''; |
| | | } |
| | | |
| | | $cells = array(); |
| | | $cells = $coltypes = array(); |
| | | |
| | | // get name of smart From/To column in folder context |
| | | if (array_search('fromto', $a_show_cols) !== false) { |
| | |
| | | } |
| | | |
| | | foreach ($a_show_cols as $col) { |
| | | $label = ''; |
| | | $sortable = false; |
| | | |
| | | // get column name |
| | | switch ($col) { |
| | | case 'flag': |
| | | $col_name = '<span class="flagged"> </span>'; |
| | | $col_name = html::span('flagged', ' '); |
| | | break; |
| | | case 'attachment': |
| | | case 'priority': |
| | | case 'status': |
| | | $col_name = '<span class="' . $col .'"> </span>'; |
| | | $col_name = html::span($col, ' '); |
| | | break; |
| | | case 'threads': |
| | | $col_name = $list_menu; |
| | | break; |
| | | case 'fromto': |
| | | $col_name = rcube::Q($RCMAIL->gettext($smart_col)); |
| | | $label = $RCMAIL->gettext($smart_col); |
| | | $col_name = rcube::Q($label); |
| | | break; |
| | | default: |
| | | $col_name = rcube::Q($RCMAIL->gettext($col)); |
| | | $label = $RCMAIL->gettext($col); |
| | | $col_name = rcube::Q($label); |
| | | } |
| | | |
| | | // make sort links |
| | | if (in_array($col, $a_sort_cols)) { |
| | | $sortable = true; |
| | | $col_name = html::a(array( |
| | | 'href' => "./#sort", |
| | | 'onclick' => 'return '.rcmail_output::JS_OBJECT_NAME.".command('sort','".$col."',this)", |
| | | 'title' => $RCMAIL->gettext('sortby') |
| | | 'href' => "./#sort", |
| | | 'class' => 'sortcol', |
| | | 'rel' => $col, |
| | | 'title' => $RCMAIL->gettext('sortby') |
| | | ), $col_name); |
| | | } |
| | | else if ($col_name[0] != '<') { |
| | |
| | | |
| | | // put it all together |
| | | $cells[] = array('className' => $class_name, 'id' => "rcm$col", 'html' => $col_name); |
| | | $coltypes[$col] = array('className' => $class_name, 'id' => "rcm$col", 'label' => $label, 'sortable' => $sortable); |
| | | } |
| | | |
| | | $RCMAIL->output->set_env('coltypes', $coltypes); |
| | | return $cells; |
| | | } |
| | | |
| | |
| | | | program/steps/mail/list.inc | |
| | | | | |
| | | | This file is part of the Roundcube Webmail client | |
| | | | Copyright (C) 2005-2007, The Roundcube Dev Team | |
| | | | Copyright (C) 2005-2014, The Roundcube Dev Team | |
| | | | | |
| | | | Licensed under the GNU General Public License version 3 or | |
| | | | any later version with exceptions for skins & plugins. | |
| | |
| | | |
| | | // 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'] = explode(',', $cols); |
| | | if (!in_array('list_cols', $dont_override)) { |
| | | $save_arr['list_cols'] = explode(',', $cols); |
| | | } |
| | |
| | | $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)); |
| | |
| | | '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); |
| | |
| | | // 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 |
| | |
| | | } |
| | | |
| | | // 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); |
| | | |
| | |
| | | | 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. | |
| | |
| | | |
| | | // 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 |
| | |
| | | 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 |
| | |
| | | exit; |
| | | } |
| | | else { |
| | | $OUTPUT->show_message('messagedeleted', 'confirmation'); |
| | | $OUTPUT->show_message('messagedeleted', 'confirmation'); |
| | | } |
| | | |
| | | $addrows = true; |
| | |
| | | |
| | | $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); |
| | |
| | | $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); |
| | |
| | | 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); |
| | |
| | | |
| | | // 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['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'))); |
| | | } |
| | |
| | | |
| | | // 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')); |
| | |
| | | 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; */ |
| | | } |
| | |
| | | <!-- 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"> |
| | |
| | | <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> |
| | | |
| | |
| | | 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; |
| | |
| | | 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) { |
| | |
| | | /** |
| | | * |
| | | */ |
| | | 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'); |
| | | } |
| | | } |
| | | |
| | | |
| | |
| | | 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'; |
| | | $('input:radio[name="s_scope"]').prop('checked', false).filter('#s_scope_'+scope).prop('checked', true); |
| | | } |
| | | else { |
| | | all = '*'; |
| | |
| | | |
| | | // set checkboxes |
| | | $('input[name="list_col[]"]').each(function() { |
| | | $(this).prop('checked', $.inArray(this.value, rcmail.env.coltypes) != -1); |
| | | $(this).prop('checked', $.inArray(this.value, rcmail.env.listcols) != -1); |
| | | }); |
| | | |
| | | $dialog.dialog({ |
| | |
| | | { |
| | | 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 = {}; |
| | |
| | | m[elem.value] = 1; |
| | | |
| | | // mark all fields |
| | | if (elem.value != all) |
| | | return; |
| | | if (elem.value == all) { |
| | | $('input:checkbox[name="s_mods[]"]').map(function() { |
| | | if (this == elem) |
| | | return; |
| | | |
| | | $('input:checkbox[name="s_mods[]"]').map(function() { |
| | | if (this == elem) |
| | | return; |
| | | this.checked = true; |
| | | if (elem.checked) { |
| | | this.disabled = true; |
| | | delete m[this.value]; |
| | | } |
| | | else { |
| | | this.disabled = false; |
| | | m[this.value] = 1; |
| | | } |
| | | }); |
| | | } |
| | | |
| | | this.checked = true; |
| | | if (elem.checked) { |
| | | this.disabled = true; |
| | | delete m[this.value]; |
| | | } |
| | | else { |
| | | this.disabled = false; |
| | | m[this.value] = 1; |
| | | } |
| | | }); |
| | | rcmail.set_searchmods(m); |
| | | } |
| | | |
| | | function set_searchscope(elem) |
| | | { |
| | | rcmail.set_searchscope(elem.value); |
| | | } |
| | | |
| | | function push_contactgroup(p) |