Page MenuHomePhorge

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/program/js/app.js b/program/js/app.js
index 0dea59216..37aee8d88 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -1,6826 +1,6833 @@
/*
+-----------------------------------------------------------------------+
| Roundcube Webmail Client Script |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2013, The Roundcube Dev Team |
| Copyright (C) 2011-2013, Kolab Systems AG |
| |
| 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. |
| |
+-----------------------------------------------------------------------+
| Authors: Thomas Bruederli <roundcube@gmail.com> |
| Aleksander 'A.L.E.C' Machniak <alec@alec.pl> |
| Charles McNulty <charles@charlesmcnulty.com> |
+-----------------------------------------------------------------------+
| Requires: jquery.js, common.js, list.js |
+-----------------------------------------------------------------------+
*/
function rcube_webmail()
{
this.labels = {};
this.buttons = {};
this.buttons_sel = {};
this.gui_objects = {};
this.gui_containers = {};
this.commands = {};
this.command_handlers = {};
this.onloads = [];
this.messages = {};
this.group2expand = {};
// webmail client settings
this.dblclick_time = 500;
this.message_time = 4000;
this.identifier_expr = new RegExp('[^0-9a-z\-_]', 'gi');
// environment defaults
this.env = {
request_timeout: 180, // seconds
draft_autosave: 0, // seconds
comm_path: './',
blankpage: 'program/resources/blank.gif',
recipients_separator: ',',
recipients_delimiter: ', ',
popup_width: 1150,
popup_width_small: 900
};
// create protected reference to myself
this.ref = 'rcmail';
var ref = this;
// set jQuery ajax options
$.ajaxSetup({
cache: false,
timeout: this.env.request_timeout * 1000,
error: function(request, status, err){ ref.http_error(request, status, err); },
beforeSend: function(xmlhttp){ xmlhttp.setRequestHeader('X-Roundcube-Request', ref.env.request_token); }
});
// unload fix
$(window).bind('beforeunload', function() { rcmail.unload = true; });
// set environment variable(s)
this.set_env = function(p, value)
{
if (p != null && typeof p === 'object' && !value)
for (var n in p)
this.env[n] = p[n];
else
this.env[p] = value;
};
// add a localized label to the client environment
this.add_label = function(p, value)
{
if (typeof p == 'string')
this.labels[p] = value;
else if (typeof p == 'object')
$.extend(this.labels, p);
};
// add a button to the button list
this.register_button = function(command, id, type, act, sel, over)
{
var button_prop = {id:id, type:type};
if (act) button_prop.act = act;
if (sel) button_prop.sel = sel;
if (over) button_prop.over = over;
if (!this.buttons[command])
this.buttons[command] = [];
this.buttons[command].push(button_prop);
if (this.loaded)
init_button(command, button_prop);
};
// register a specific gui object
this.gui_object = function(name, id)
{
this.gui_objects[name] = this.loaded ? rcube_find_object(id) : id;
};
// register a container object
this.gui_container = function(name, id)
{
this.gui_containers[name] = id;
};
// add a GUI element (html node) to a specified container
this.add_element = function(elm, container)
{
if (this.gui_containers[container] && this.gui_containers[container].jquery)
this.gui_containers[container].append(elm);
};
// register an external handler for a certain command
this.register_command = function(command, callback, enable)
{
this.command_handlers[command] = callback;
if (enable)
this.enable_command(command, true);
};
// execute the given script on load
this.add_onload = function(f)
{
this.onloads.push(f);
};
// initialize webmail client
this.init = function()
{
var n, p = this;
this.task = this.env.task;
// check browser
if (!bw.dom || !bw.xmlhttp_test() || (bw.mz && bw.vendver < 1.9)) {
this.goto_url('error', '_code=0x199');
return;
}
// find all registered gui containers
for (n in this.gui_containers)
this.gui_containers[n] = $('#'+this.gui_containers[n]);
// find all registered gui objects
for (n in this.gui_objects)
this.gui_objects[n] = rcube_find_object(this.gui_objects[n]);
// clickjacking protection
if (this.env.x_frame_options) {
try {
// bust frame if not allowed
if (this.env.x_frame_options == 'deny' && top.location.href != self.location.href)
top.location.href = self.location.href;
else if (top.location.hostname != self.location.hostname)
throw 1;
} catch (e) {
// possible clickjacking attack: disable all form elements
$('form').each(function(){ ref.lock_form(this, true); });
this.display_message("Blocked: possible clickjacking attack!", 'error');
return;
}
}
// init registered buttons
this.init_buttons();
// tell parent window that this frame is loaded
if (this.is_framed()) {
parent.rcmail.set_busy(false, null, parent.rcmail.env.frame_lock);
parent.rcmail.env.frame_lock = null;
}
// enable general commands
this.enable_command('close', 'logout', 'mail', 'addressbook', 'settings', 'save-pref',
'compose', 'undo', 'about', 'switch-task', 'menu-open', 'menu-save', true);
if (this.env.permaurl)
this.enable_command('permaurl', 'extwin', true);
switch (this.task) {
case 'mail':
// enable mail commands
- this.enable_command('list', 'checkmail', 'add-contact', 'search', 'reset-search', 'collapse-folder', true);
+ this.enable_command('list', 'checkmail', 'add-contact', 'search', 'reset-search', 'collapse-folder', 'import-messages', true);
if (this.gui_objects.messagelist) {
this.message_list = new rcube_list_widget(this.gui_objects.messagelist, {
multiselect:true, multiexpand:true, draggable:true, keyboard:true,
column_movable:this.env.col_movable, dblclick_time:this.dblclick_time
});
this.message_list.row_init = function(o){ p.init_message_row(o); };
this.message_list.addEventListener('dblclick', function(o){ p.msglist_dbl_click(o); });
this.message_list.addEventListener('click', function(o){ p.msglist_click(o); });
this.message_list.addEventListener('keypress', function(o){ p.msglist_keypress(o); });
this.message_list.addEventListener('select', function(o){ p.msglist_select(o); });
this.message_list.addEventListener('dragstart', function(o){ p.drag_start(o); });
this.message_list.addEventListener('dragmove', function(e){ p.drag_move(e); });
this.message_list.addEventListener('dragend', function(e){ p.drag_end(e); });
this.message_list.addEventListener('expandcollapse', function(e){ p.msglist_expand(e); });
this.message_list.addEventListener('column_replace', function(e){ p.msglist_set_coltypes(e); });
this.message_list.addEventListener('listupdate', function(e){ p.triggerEvent('listupdate', e); });
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.message_list.init();
this.enable_command('toggle_status', 'toggle_flag', 'sort', true);
// load messages
this.command('list');
}
if (this.gui_objects.qsearchbox) {
if (this.env.search_text != null)
this.gui_objects.qsearchbox.value = this.env.search_text;
$(this.gui_objects.qsearchbox).focusin(function() { rcmail.message_list && rcmail.message_list.blur(); });
}
this.set_button_titles();
this.env.message_commands = ['show', 'reply', 'reply-all', 'reply-list',
'moveto', 'copy', 'delete', 'open', 'mark', 'edit', 'viewsource',
'print', 'load-attachment', 'download-attachment', 'show-headers', 'hide-headers', 'download',
'forward', 'forward-inline', 'forward-attachment', 'change-format'];
if (this.env.action == 'show' || this.env.action == 'preview') {
this.enable_command(this.env.message_commands, this.env.uid);
this.enable_command('reply-list', this.env.list_post);
if (this.env.action == 'show') {
this.http_request('pagenav', {_uid: this.env.uid, _mbox: this.env.mailbox, _search: this.env.search_request},
this.display_message('', 'loading'));
}
if (this.env.blockedobjects) {
if (this.gui_objects.remoteobjectsmsg)
this.gui_objects.remoteobjectsmsg.style.display = 'block';
this.enable_command('load-images', 'always-load', true);
}
// make preview/message frame visible
if (this.env.action == 'preview' && this.is_framed()) {
this.enable_command('compose', 'add-contact', false);
parent.rcmail.show_contentframe(true);
}
}
else if (this.env.action == 'compose') {
this.env.compose_commands = ['send-attachment', 'remove-attachment', 'send', 'cancel', 'toggle-editor', 'list-adresses', 'search', 'reset-search', 'extwin'];
if (this.env.drafts_mailbox)
this.env.compose_commands.push('savedraft')
this.enable_command(this.env.compose_commands, 'identities', true);
// add more commands (not enabled)
$.merge(this.env.compose_commands, ['add-recipient', 'firstpage', 'previouspage', 'nextpage', 'lastpage']);
if (this.env.spellcheck) {
this.env.spellcheck.spelling_state_observer = function(s) { ref.spellcheck_state(); };
this.env.compose_commands.push('spellcheck')
this.enable_command('spellcheck', true);
}
document.onmouseup = function(e){ return p.doc_mouse_up(e); };
// init message compose form
this.init_messageform();
}
// show printing dialog
- else if (this.env.action == 'print' && this.env.uid)
+ else if (this.env.action == 'print' && this.env.uid) {
if (bw.safari)
setTimeout('window.print()', 10);
else
window.print();
+ }
// get unread count for each mailbox
if (this.gui_objects.mailboxlist) {
this.env.unread_counts = {};
this.gui_objects.folderlist = this.gui_objects.mailboxlist;
this.http_request('getunread');
}
// init address book widget
if (this.gui_objects.contactslist) {
this.contact_list = new rcube_list_widget(this.gui_objects.contactslist,
{ multiselect:true, draggable:false, keyboard:false });
this.contact_list.addEventListener('select', function(o){ ref.compose_recipient_select(o); });
this.contact_list.addEventListener('dblclick', function(o){ ref.compose_add_recipient('to'); });
this.contact_list.init();
}
if (this.gui_objects.addressbookslist) {
this.gui_objects.folderlist = this.gui_objects.addressbookslist;
this.enable_command('list-adresses', true);
}
// ask user to send MDN
if (this.env.mdn_request && this.env.uid) {
var postact = 'sendmdn',
postdata = {_uid: this.env.uid, _mbox: this.env.mailbox};
if (!confirm(this.get_label('mdnrequest'))) {
postdata._flag = 'mdnsent';
postact = 'mark';
}
this.http_post(postact, postdata);
}
// detect browser capabilities
if (!this.is_framed() && !this.env.extwin)
this.browser_capabilities_check();
break;
case 'addressbook':
if (this.gui_objects.folderlist)
this.env.contactfolders = $.extend($.extend({}, this.env.address_sources), this.env.contactgroups);
this.enable_command('add', 'import', this.env.writable_source);
this.enable_command('list', 'listgroup', 'listsearch', 'advanced-search', true);
if (this.gui_objects.contactslist) {
this.contact_list = new rcube_list_widget(this.gui_objects.contactslist,
{multiselect:true, draggable:this.gui_objects.folderlist?true:false, keyboard:true});
this.contact_list.row_init = function(row){ p.triggerEvent('insertrow', { cid:row.uid, row:row }); };
this.contact_list.addEventListener('keypress', function(o){ p.contactlist_keypress(o); });
this.contact_list.addEventListener('select', function(o){ p.contactlist_select(o); });
this.contact_list.addEventListener('dragstart', function(o){ p.drag_start(o); });
this.contact_list.addEventListener('dragmove', function(e){ p.drag_move(e); });
this.contact_list.addEventListener('dragend', function(e){ p.drag_end(e); });
this.contact_list.init();
if (this.env.cid)
this.contact_list.highlight_row(this.env.cid);
this.gui_objects.contactslist.parentNode.onmousedown = function(e){ return p.click_on_list(e); };
document.onmouseup = function(e){ return p.doc_mouse_up(e); };
if (this.gui_objects.qsearchbox)
$(this.gui_objects.qsearchbox).focusin(function() { rcmail.contact_list.blur(); });
this.update_group_commands();
this.command('list');
}
this.set_page_buttons();
if (this.env.cid) {
this.enable_command('show', 'edit', true);
// register handlers for group assignment via checkboxes
if (this.gui_objects.editform) {
$('input.groupmember').change(function() {
ref.group_member_change(this.checked ? 'add' : 'del', ref.env.cid, ref.env.source, this.value);
});
}
}
if (this.gui_objects.editform) {
this.enable_command('save', true);
if (this.env.action == 'add' || this.env.action == 'edit' || this.env.action == 'search')
this.init_contact_form();
}
if (this.gui_objects.qsearchbox)
this.enable_command('search', 'reset-search', 'moveto', true);
break;
case 'settings':
this.enable_command('preferences', 'identities', 'save', 'folders', true);
if (this.env.action == 'identities') {
this.enable_command('add', this.env.identities_level < 2);
}
else if (this.env.action == 'edit-identity' || this.env.action == 'add-identity') {
this.enable_command('save', 'edit', 'toggle-editor', true);
this.enable_command('delete', this.env.identities_level < 2);
if (this.env.action == 'add-identity')
$("input[type='text']").first().select();
}
else if (this.env.action == 'folders') {
this.enable_command('subscribe', 'unsubscribe', 'create-folder', 'rename-folder', true);
}
else if (this.env.action == 'edit-folder' && this.gui_objects.editform) {
this.enable_command('save', 'folder-size', true);
parent.rcmail.env.messagecount = this.env.messagecount;
parent.rcmail.enable_command('purge', this.env.messagecount);
$("input[type='text']").first().select();
}
if (this.gui_objects.identitieslist) {
this.identity_list = new rcube_list_widget(this.gui_objects.identitieslist, {multiselect:false, draggable:false, keyboard:false});
this.identity_list.addEventListener('select', function(o){ p.identity_select(o); });
this.identity_list.init();
this.identity_list.focus();
if (this.env.iid)
this.identity_list.highlight_row(this.env.iid);
}
else if (this.gui_objects.sectionslist) {
this.sections_list = new rcube_list_widget(this.gui_objects.sectionslist, {multiselect:false, draggable:false, keyboard:false});
this.sections_list.addEventListener('select', function(o){ p.section_select(o); });
this.sections_list.init();
this.sections_list.focus();
}
else if (this.gui_objects.subscriptionlist)
this.init_subscription_list();
break;
case 'login':
var input_user = $('#rcmloginuser');
input_user.bind('keyup', function(e){ return rcmail.login_user_keyup(e); });
if (input_user.val() == '')
input_user.focus();
else
$('#rcmloginpwd').focus();
// detect client timezone
if (window.jstz && !bw.ie6) {
var timezone = jstz.determine();
if (timezone.name())
$('#rcmlogintz').val(timezone.name());
}
else {
$('#rcmlogintz').val(new Date().getStdTimezoneOffset() / -60);
}
// display 'loading' message on form submit, lock submit button
$('form').submit(function () {
$('input[type=submit]', this).prop('disabled', true);
rcmail.clear_messages();
rcmail.display_message('', 'loading');
});
this.enable_command('login', true);
break;
}
// unset contentframe variable if preview_pane is enabled
if (this.env.contentframe && !$('#' + this.env.contentframe).is(':visible'))
this.env.contentframe = null;
// prevent from form submit with Enter key in file input fields
if (bw.ie)
$('input[type=file]').keydown(function(e) { if (e.keyCode == '13') e.preventDefault(); });
// flag object as complete
this.loaded = true;
// show message
if (this.pending_message)
this.display_message(this.pending_message[0], this.pending_message[1], this.pending_message[2]);
// map implicit containers
if (this.gui_objects.folderlist) {
this.gui_containers.foldertray = $(this.gui_objects.folderlist);
// init treelist widget
if (window.rcube_treelist_widget) {
this.treelist = new rcube_treelist_widget(this.gui_objects.folderlist, {
id_prefix: 'rcmli',
id_encode: this.html_identifier_encode,
id_decode: this.html_identifier_decode,
check_droptarget: function(node){ return !node.virtual && ref.check_droptarget(node.id) }
});
this.treelist.addEventListener('collapse', function(node){ ref.folder_collapsed(node) });
this.treelist.addEventListener('expand', function(node){ ref.folder_collapsed(node) });
this.treelist.addEventListener('select', function(node){ ref.triggerEvent('selectfolder', { folder:node.id, prefix:'rcmli' }) });
}
}
// activate html5 file drop feature (if browser supports it and if configured)
if (this.gui_objects.filedrop && this.env.filedrop && ((window.XMLHttpRequest && XMLHttpRequest.prototype && XMLHttpRequest.prototype.sendAsBinary) || window.FormData)) {
$(document.body).bind('dragover dragleave drop', function(e){ return ref.document_drag_hover(e, e.type == 'dragover'); });
$(this.gui_objects.filedrop).addClass('droptarget')
.bind('dragover dragleave', function(e){ return ref.file_drag_hover(e, e.type == 'dragover'); })
.get(0).addEventListener('drop', function(e){ return ref.file_dropped(e); }, false);
}
// trigger init event hook
this.triggerEvent('init', { task:this.task, action:this.env.action });
// execute all foreign onload scripts
// @deprecated
for (var i in this.onloads) {
if (typeof this.onloads[i] === 'string')
eval(this.onloads[i]);
else if (typeof this.onloads[i] === 'function')
this.onloads[i]();
}
// start keep-alive and refresh intervals
this.start_refresh();
this.start_keepalive();
};
this.log = function(msg)
{
if (window.console && console.log)
console.log(msg);
};
/*********************************************************/
/********* client command interface *********/
/*********************************************************/
// execute a specific command on the web client
this.command = function(command, props, obj, event)
{
var ret, uid, cid, url, flag;
if (obj && obj.blur)
obj.blur();
if (this.busy)
return false;
// let the browser handle this click (shift/ctrl usually opens the link in a new window/tab)
if ((obj && obj.href && String(obj.href).indexOf('#') < 0) && rcube_event.get_modifier(event)) {
return true;
}
// command not supported or allowed
if (!this.commands[command]) {
// pass command to parent window
if (this.is_framed())
parent.rcmail.command(command, props);
return false;
}
// check input before leaving compose step
if (this.task == 'mail' && this.env.action == 'compose' && $.inArray(command, this.env.compose_commands)<0) {
if (this.cmp_hash != this.compose_field_hash() && !confirm(this.get_label('notsentwarning')))
return false;
}
// process external commands
if (typeof this.command_handlers[command] === 'function') {
ret = this.command_handlers[command](props, obj);
return ret !== undefined ? ret : (obj ? false : true);
}
else if (typeof this.command_handlers[command] === 'string') {
ret = window[this.command_handlers[command]](props, obj);
return ret !== undefined ? ret : (obj ? false : true);
}
// trigger plugin hooks
this.triggerEvent('actionbefore', {props:props, action:command});
ret = this.triggerEvent('before'+command, props);
if (ret !== undefined) {
// abort if one of the handlers returned false
if (ret === false)
return false;
else
props = ret;
}
ret = undefined;
// process internal command
switch (command) {
case 'login':
if (this.gui_objects.loginform)
this.gui_objects.loginform.submit();
break;
// commands to switch task
case 'mail':
case 'addressbook':
case 'settings':
case 'logout':
this.switch_task(command);
break;
case 'about':
this.redirect('?_task=settings&_action=about', false);
break;
case 'permaurl':
if (obj && obj.href && obj.target)
return true;
else if (this.env.permaurl)
parent.location.href = this.env.permaurl;
break;
case 'extwin':
if (this.env.action == 'compose') {
var form = this.gui_objects.messageform,
win = this.open_window('');
$("input[name='_action']", form).val('compose');
form.action = this.url('mail/compose', { _id: this.env.compose_id, _extwin: 1 });
form.target = win.name;
form.submit();
}
else {
this.open_window(this.env.permaurl, true);
}
break;
case 'change-format':
url = this.env.permaurl + '&_format=' + props;
if (this.env.action == 'preview')
url = url.replace(/_action=show/, '_action=preview') + '&_framed=1';
if (this.env.extwin)
url += '&_extwin=1';
location.href = url;
break;
case 'menu-open':
if (props && props.menu == 'attachmentmenu') {
var mimetype = this.env.attachments[props.id];
this.enable_command('open-attachment', mimetype && this.env.mimetypes && $.inArray(mimetype, this.env.mimetypes) >= 0);
}
case 'menu-save':
this.triggerEvent(command, {props:props});
return false;
case 'open':
if (uid = this.get_single_uid()) {
obj.href = this.url('show', {_mbox: this.env.mailbox, _uid: uid});
return true;
}
break;
case 'close':
if (this.env.extwin)
window.close();
break;
case 'list':
if (props && props != '')
this.reset_qsearch();
if (this.env.action == 'compose' && this.env.extwin)
window.close();
else if (this.task == 'mail') {
this.list_mailbox(props);
this.set_button_titles();
}
else if (this.task == 'addressbook')
this.list_contacts(props);
break;
case 'sort':
var sort_order = this.env.sort_order,
sort_col = !this.env.disabled_sort_col ? props : this.env.sort_col;
if (!this.env.disabled_sort_order)
sort_order = this.env.sort_col == sort_col && sort_order == 'ASC' ? 'DESC' : 'ASC';
// set table header and update env
this.set_list_sorting(sort_col, sort_order);
// reload message list
this.list_mailbox('', '', sort_col+'_'+sort_order);
break;
case 'nextpage':
this.list_page('next');
break;
case 'lastpage':
this.list_page('last');
break;
case 'previouspage':
this.list_page('prev');
break;
case 'firstpage':
this.list_page('first');
break;
case 'expunge':
if (this.env.exists)
this.expunge_mailbox(this.env.mailbox);
break;
case 'purge':
case 'empty-mailbox':
if (this.env.exists)
this.purge_mailbox(this.env.mailbox);
break;
// common commands used in multiple tasks
case 'show':
if (this.task == 'mail') {
uid = this.get_single_uid();
if (uid && (!this.env.uid || uid != this.env.uid)) {
if (this.env.mailbox == this.env.drafts_mailbox)
this.open_compose_step({ _draft_uid: uid, _mbox: this.env.mailbox });
else
this.show_message(uid);
}
}
else if (this.task == 'addressbook') {
cid = props ? props : this.get_single_cid();
if (cid && !(this.env.action == 'show' && cid == this.env.cid))
this.load_contact(cid, 'show');
}
break;
case 'add':
if (this.task == 'addressbook')
this.load_contact(0, 'add');
else if (this.task == 'settings') {
this.identity_list.clear_selection();
this.load_identity(0, 'add-identity');
}
break;
case 'edit':
if (this.task == 'addressbook' && (cid = this.get_single_cid()))
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;
this.open_compose_step(url);
}
break;
case 'save':
var input, form = this.gui_objects.editform;
if (form) {
// adv. search
if (this.env.action == 'search') {
}
// user prefs
else if ((input = $("input[name='_pagesize']", form)) && input.length && isNaN(parseInt(input.val()))) {
alert(this.get_label('nopagesizewarning'));
input.focus();
break;
}
// contacts/identities
else {
// reload form
if (props == 'reload') {
form.action += '?_reload=1';
}
else if (this.task == 'settings' && (this.env.identities_level % 2) == 0 &&
(input = $("input[name='_email']", form)) && input.length && !rcube_check_email(input.val())
) {
alert(this.get_label('noemailwarning'));
input.focus();
break;
}
// clear empty input fields
$('input.placeholder').each(function(){ if (this.value == this._placeholder) this.value = ''; });
}
// add selected source (on the list)
if (parent.rcmail && parent.rcmail.env.source)
form.action = this.add_url(form.action, '_orig_source', parent.rcmail.env.source);
form.submit();
}
break;
case 'delete':
// mail task
if (this.task == 'mail')
this.delete_messages(event);
// addressbook task
else if (this.task == 'addressbook')
this.delete_contacts();
// user settings task
else if (this.task == 'settings')
this.delete_identity();
break;
// mail task commands
case 'move':
case 'moveto':
if (this.task == 'mail')
this.move_messages(props);
else if (this.task == 'addressbook')
this.copy_contact(null, props);
break;
case 'copy':
if (this.task == 'mail')
this.copy_messages(props);
break;
case 'mark':
if (props)
this.mark_message(props);
break;
case 'toggle_status':
if (props && !props._row)
break;
flag = 'read';
if (props._row.uid) {
uid = props._row.uid;
// toggle read/unread
if (this.message_list.rows[uid].deleted)
flag = 'undelete';
else if (!this.message_list.rows[uid].unread)
flag = 'unread';
}
this.mark_message(flag, uid);
break;
case 'toggle_flag':
if (props && !props._row)
break;
flag = 'flagged';
if (props._row.uid) {
uid = props._row.uid;
// toggle flagged/unflagged
if (this.message_list.rows[uid].flagged)
flag = 'unflagged';
}
this.mark_message(flag, uid);
break;
case 'always-load':
if (this.env.uid && this.env.sender) {
this.add_contact(this.env.sender);
setTimeout(function(){ ref.command('load-images'); }, 300);
break;
}
case 'load-images':
if (this.env.uid)
this.show_message(this.env.uid, true, this.env.action=='preview');
break;
case 'load-attachment':
case 'open-attachment':
case 'download-attachment':
var qstring = '_mbox='+urlencode(this.env.mailbox)+'&_uid='+this.env.uid+'&_part='+props,
mimetype = this.env.attachments[props];
// open attachment in frame if it's of a supported mimetype
if (command != 'download-attachment' && mimetype && this.env.mimetypes && $.inArray(mimetype, this.env.mimetypes) >= 0) {
if (this.open_window(this.env.comm_path+'&_action=get&'+qstring+'&_frame=1', true, true))
break;
}
this.goto_url('get', qstring+'&_download=1', false);
break;
case 'select-all':
this.select_all_mode = props ? false : true;
this.dummy_select = true; // prevent msg opening if there's only one msg on the list
if (props == 'invert')
this.message_list.invert_selection();
else
this.message_list.select_all(props == 'page' ? '' : props);
this.dummy_select = null;
break;
case 'select-none':
this.select_all_mode = false;
this.message_list.clear_selection();
break;
case 'expand-all':
this.env.autoexpand_threads = 1;
this.message_list.expand_all();
break;
case 'expand-unread':
this.env.autoexpand_threads = 2;
this.message_list.collapse_all();
this.expand_unread();
break;
case 'collapse-all':
this.env.autoexpand_threads = 0;
this.message_list.collapse_all();
break;
case 'nextmessage':
if (this.env.next_uid)
this.show_message(this.env.next_uid, false, this.env.action == 'preview');
break;
case 'lastmessage':
if (this.env.last_uid)
this.show_message(this.env.last_uid);
break;
case 'previousmessage':
if (this.env.prev_uid)
this.show_message(this.env.prev_uid, false, this.env.action == 'preview');
break;
case 'firstmessage':
if (this.env.first_uid)
this.show_message(this.env.first_uid);
break;
case 'compose':
url = {};
if (this.task == 'mail') {
url._mbox = this.env.mailbox;
if (props)
url._to = props;
// also send search request so we can go back to search result after message is sent
if (this.env.search_request)
url._search = this.env.search_request;
}
// modify url if we're in addressbook
else if (this.task == 'addressbook') {
// switch to mail compose step directly
if (props && props.indexOf('@') > 0) {
url._to = props;
}
else {
var a_cids = [];
// use contact id passed as command parameter
if (props)
a_cids.push(props);
// get selected contacts
else if (this.contact_list)
a_cids = this.contact_list.get_selection();
if (a_cids.length)
this.http_post('mailto', { _cid: a_cids.join(','), _source: this.env.source }, true);
else if (this.env.group)
this.http_post('mailto', { _gid: this.env.group, _source: this.env.source }, true);
break;
}
}
else if (props)
url._to = props;
this.open_compose_step(url);
break;
case 'spellcheck':
if (this.spellcheck_state()) {
this.stop_spellchecking();
}
else {
if (window.tinyMCE && tinyMCE.get(this.env.composebody)) {
tinyMCE.execCommand('mceSpellCheck', true);
}
else if (this.env.spellcheck && this.env.spellcheck.spellCheck) {
this.env.spellcheck.spellCheck();
}
}
this.spellcheck_state();
break;
case 'savedraft':
// Reset the auto-save timer
clearTimeout(this.save_timer);
// compose form did not change (and draft wasn't saved already)
if (this.env.draft_id && this.cmp_hash == this.compose_field_hash()) {
this.auto_save_start();
break;
}
this.submit_messageform(true);
break;
case 'send':
if (!props.nocheck && !this.check_compose_input(command))
break;
// Reset the auto-save timer
clearTimeout(this.save_timer);
this.submit_messageform();
break;
case 'send-attachment':
// Reset the auto-save timer
clearTimeout(this.save_timer);
- this.upload_file(props || this.gui_objects.uploadform);
+ this.upload_file(props || this.gui_objects.uploadform, 'upload');
break;
case 'insert-sig':
this.change_identity($("[name='_from']")[0], true);
break;
case 'list-adresses':
this.list_contacts(props);
this.enable_command('add-recipient', false);
break;
case 'add-recipient':
this.compose_add_recipient(props);
break;
case 'reply-all':
case 'reply-list':
case 'reply':
if (uid = this.get_single_uid()) {
url = {_reply_uid: uid, _mbox: this.env.mailbox};
if (command == 'reply-all')
// do reply-list, when list is detected and popup menu wasn't used
url._all = (!props && this.commands['reply-list'] ? 'list' : 'all');
else if (command == 'reply-list')
url._all = 'list';
this.open_compose_step(url);
}
break;
case 'forward-attachment':
case 'forward-inline':
case 'forward':
var uids = this.env.uid ? [this.env.uid] : (this.message_list ? this.message_list.get_selection() : []);
if (uids.length) {
url = { _forward_uid: this.uids_to_list(uids), _mbox: this.env.mailbox };
if (command == 'forward-attachment' || (!props && this.env.forward_attachment) || uids.length > 1)
url._attachment = 1;
this.open_compose_step(url);
}
break;
case 'print':
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);
if (this.printwin) {
if (this.env.action != 'show')
this.mark_message('read', uid);
}
}
break;
case 'viewsource':
if (uid = this.get_single_uid())
this.open_window(this.env.comm_path+'&_action=viewsource&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox), true, true);
break;
case 'download':
if (uid = this.get_single_uid())
this.goto_url('viewsource', { _uid: uid, _mbox: this.env.mailbox, _save: 1 });
break;
// quicksearch
case 'search':
if (!props && this.gui_objects.qsearchbox)
props = this.gui_objects.qsearchbox.value;
if (props) {
this.qsearch(props);
break;
}
// reset quicksearch
case 'reset-search':
var n, s = this.env.search_request || this.env.qsearch;
this.reset_qsearch();
this.select_all_mode = false;
if (s && this.env.action == 'compose') {
if (this.contact_list)
this.list_contacts_clear();
}
else if (s && this.env.mailbox) {
this.list_mailbox(this.env.mailbox, 1);
}
else if (s && this.task == 'addressbook') {
if (this.env.source == '') {
for (n in this.env.address_sources) break;
this.env.source = n;
this.env.group = '';
}
this.list_contacts(this.env.source, this.env.group, 1);
}
break;
case 'listgroup':
this.reset_qsearch();
this.list_contacts(props.source, props.id);
break;
+ case 'import-messages':
+ var form = props || this.gui_objects.importform;
+ $('input[name="_unlock"]', form).val(this.set_busy(true, 'importwait'));
+ this.upload_file(form, 'import');
+ break;
+
case 'import':
if (this.env.action == 'import' && this.gui_objects.importform) {
var file = document.getElementById('rcmimportfile');
if (file && !file.value) {
alert(this.get_label('selectimportfile'));
break;
}
this.gui_objects.importform.submit();
this.set_busy(true, 'importwait');
this.lock_form(this.gui_objects.importform, true);
}
else
this.goto_url('import', (this.env.source ? '_target='+urlencode(this.env.source)+'&' : ''));
break;
case 'export':
if (this.contact_list.rowcount > 0) {
this.goto_url('export', { _source: this.env.source, _gid: this.env.group, _search: this.env.search_request });
}
break;
case 'export-selected':
if (this.contact_list.rowcount > 0) {
this.goto_url('export', { _source: this.env.source, _gid: this.env.group, _cid: this.contact_list.get_selection().join(',') });
}
break;
case 'upload-photo':
this.upload_contact_photo(props || this.gui_objects.uploadform);
break;
case 'delete-photo':
this.replace_contact_photo('-del-');
break;
// user settings commands
case 'preferences':
case 'identities':
case 'folders':
this.goto_url('settings/' + command);
break;
case 'undo':
this.http_request('undo', '', this.display_message('', 'loading'));
break;
// unified command call (command name == function name)
default:
var func = command.replace(/-/g, '_');
if (this[func] && typeof this[func] === 'function') {
ret = this[func](props, obj);
}
break;
}
if (this.triggerEvent('after'+command, props) === false)
ret = false;
this.triggerEvent('actionafter', {props:props, action:command});
return ret === false ? false : obj ? false : true;
};
// set command(s) enabled or disabled
this.enable_command = function()
{
var i, n, args = Array.prototype.slice.call(arguments),
enable = args.pop(), cmd;
for (n=0; n<args.length; n++) {
cmd = args[n];
// argument of type array
if (typeof cmd === 'string') {
this.commands[cmd] = enable;
this.set_button(cmd, (enable ? 'act' : 'pas'));
this.triggerEvent('enable-command', {command: cmd, status: enable});
}
// push array elements into commands array
else {
for (i in cmd)
args.push(cmd[i]);
}
}
};
// lock/unlock interface
this.set_busy = function(a, message, id)
{
if (a && message) {
var msg = this.get_label(message);
if (msg == message)
msg = 'Loading...';
id = this.display_message(msg, 'loading');
}
else if (!a && id) {
this.hide_message(id);
}
this.busy = a;
//document.body.style.cursor = a ? 'wait' : 'default';
if (this.gui_objects.editform)
this.lock_form(this.gui_objects.editform, a);
return id;
};
// return a localized string
this.get_label = function(name, domain)
{
if (domain && this.labels[domain+'.'+name])
return this.labels[domain+'.'+name];
else if (this.labels[name])
return this.labels[name];
else
return name;
};
// alias for convenience reasons
this.gettext = this.get_label;
// switch to another application task
this.switch_task = function(task)
{
if (this.task===task && task!='mail')
return;
var url = this.get_task_url(task);
if (task=='mail')
url += '&_mbox=INBOX';
this.redirect(url);
};
this.get_task_url = function(task, url)
{
if (!url)
url = this.env.comm_path;
return url.replace(/_task=[a-z0-9_-]+/i, '_task='+task);
};
this.reload = function(delay)
{
if (this.is_framed())
parent.rcmail.reload(delay);
else if (delay)
setTimeout(function(){ rcmail.reload(); }, delay);
else if (window.location)
location.href = this.env.comm_path + (this.env.action ? '&_action='+this.env.action : '');
};
// Add variable to GET string, replace old value if exists
this.add_url = function(url, name, value)
{
value = urlencode(value);
if (/(\?.*)$/.test(url)) {
var urldata = RegExp.$1,
datax = RegExp('((\\?|&)'+RegExp.escape(name)+'=[^&]*)');
if (datax.test(urldata)) {
urldata = urldata.replace(datax, RegExp.$2 + name + '=' + value);
}
else
urldata += '&' + name + '=' + value
return url.replace(/(\?.*)$/, urldata);
}
return url + '?' + name + '=' + value;
};
this.is_framed = function()
{
return (this.env.framed && parent.rcmail && parent.rcmail != this && parent.rcmail.command);
};
this.save_pref = function(prop)
{
var request = {'_name': prop.name, '_value': prop.value};
if (prop.session)
request['_session'] = prop.session;
if (prop.env)
this.env[prop.env] = prop.value;
this.http_post('save-pref', request);
};
this.html_identifier = function(str, encode)
{
return encode ? this.html_identifier_encode(str) : String(str).replace(this.identifier_expr, '_');
};
this.html_identifier_encode = function(str)
{
return Base64.encode(String(str)).replace(/=+$/, '').replace(/\+/g, '-').replace(/\//g, '_');
};
this.html_identifier_decode = function(str)
{
str = String(str).replace(/-/g, '+').replace(/_/g, '/');
while (str.length % 4) str += '=';
return Base64.decode(str);
};
/*********************************************************/
/********* event handling methods *********/
/*********************************************************/
this.drag_menu = function(e, target)
{
var modkey = rcube_event.get_modifier(e),
menu = this.gui_objects.message_dragmenu;
if (menu && modkey == SHIFT_KEY && this.commands['copy']) {
var pos = rcube_event.get_mouse_pos(e);
this.env.drag_target = target;
$(menu).css({top: (pos.y-10)+'px', left: (pos.x-10)+'px'}).show();
return true;
}
return false;
};
this.drag_menu_action = function(action)
{
var menu = this.gui_objects.message_dragmenu;
if (menu) {
$(menu).hide();
}
this.command(action, this.env.drag_target);
this.env.drag_target = null;
};
this.drag_start = function(list)
{
var model = this.task == 'mail' ? this.env.mailboxes : this.env.contactfolders;
this.drag_active = true;
if (this.preview_timer)
clearTimeout(this.preview_timer);
if (this.preview_read_timer)
clearTimeout(this.preview_read_timer);
// prepare treelist widget for dragging interactions
if (this.treelist)
this.treelist.drag_start();
};
this.drag_end = function(e)
{
this.drag_active = false;
this.env.last_folder_target = null;
if (this.treelist)
this.treelist.drag_end();
};
this.drag_move = function(e)
{
if (this.gui_objects.folderlist) {
var drag_target, oldclass,
layerclass = 'draglayernormal',
mouse = rcube_event.get_mouse_pos(e);
if (this.contact_list && this.contact_list.draglayer)
oldclass = this.contact_list.draglayer.attr('class');
// mouse intersects a valid drop target on the treelist
if (this.treelist && (drag_target = this.treelist.intersects(mouse, true))) {
this.env.last_folder_target = drag_target;
layerclass = 'draglayer' + (this.check_droptarget(drag_target) > 1 ? 'copy' : 'normal');
}
else {
// Clear target, otherwise drag end will trigger move into last valid droptarget
this.env.last_folder_target = null;
}
if (layerclass != oldclass && this.contact_list && this.contact_list.draglayer)
this.contact_list.draglayer.attr('class', layerclass);
}
};
this.collapse_folder = function(name)
{
if (this.treelist)
this.treelist.toggle(name);
};
this.folder_collapsed = function(node)
{
var prefname = this.env.task == 'addressbook' ? 'collapsed_abooks' : 'collapsed_folders';
if (node.collapsed) {
this.env[prefname] = this.env[prefname] + '&'+urlencode(node.id)+'&';
// select the folder if one of its childs is currently selected
// don't select if it's virtual (#1488346)
if (this.env.mailbox && this.env.mailbox.indexOf(name + this.env.delimiter) == 0 && !node.virtual)
this.command('list', name);
}
else {
var reg = new RegExp('&'+urlencode(node.id)+'&');
this.env[prefname] = this.env[prefname].replace(reg, '');
}
if (!this.drag_active) {
this.command('save-pref', { name: prefname, value: this.env[prefname] });
if (this.env.unread_counts)
this.set_unread_count_display(node.id, false);
}
};
this.doc_mouse_up = function(e)
{
var model, list, id;
// ignore event if jquery UI dialog is open
if ($(rcube_event.get_target(e)).closest('.ui-dialog, .ui-widget-overlay').length)
return;
if (list = this.message_list)
model = this.env.mailboxes;
else if (list = this.contact_list)
model = this.env.contactfolders;
else if (this.ksearch_value)
this.ksearch_blur();
if (list && !rcube_mouse_is_over(e, list.list.parentNode))
list.blur();
// handle mouse release when dragging
if (this.drag_active && model && this.env.last_folder_target) {
var target = model[this.env.last_folder_target];
this.env.last_folder_target = null;
list.draglayer.hide();
this.drag_end(e);
if (!this.drag_menu(e, target))
this.command('moveto', target);
}
// reset 'pressed' buttons
if (this.buttons_sel) {
for (id in this.buttons_sel)
if (typeof id !== 'function')
this.button_out(this.buttons_sel[id], id);
this.buttons_sel = {};
}
};
this.click_on_list = function(e)
{
if (this.gui_objects.qsearchbox)
this.gui_objects.qsearchbox.blur();
if (this.message_list)
this.message_list.focus();
else if (this.contact_list)
this.contact_list.focus();
return true;
};
this.msglist_select = function(list)
{
if (this.preview_timer)
clearTimeout(this.preview_timer);
if (this.preview_read_timer)
clearTimeout(this.preview_read_timer);
var selected = list.get_single_selection();
this.enable_command(this.env.message_commands, selected != null);
if (selected) {
// Hide certain command buttons when Drafts folder is selected
if (this.env.mailbox == this.env.drafts_mailbox)
this.enable_command('reply', 'reply-all', 'reply-list', 'forward', 'forward-attachment', 'forward-inline', false);
// Disable reply-list when List-Post header is not set
else {
var msg = this.env.messages[selected];
if (!msg.ml)
this.enable_command('reply-list', false);
}
}
// Multi-message commands
this.enable_command('delete', 'moveto', 'copy', 'mark', 'forward', 'forward-attachment', list.selection.length > 0);
// reset all-pages-selection
if (selected || (list.selection.length && list.selection.length != list.rowcount))
this.select_all_mode = false;
// start timer for message preview (wait for double click)
if (selected && this.env.contentframe && !list.multi_selecting && !this.dummy_select)
this.preview_timer = setTimeout(function() { ref.msglist_get_preview(); }, this.dblclick_time);
else if (this.env.contentframe)
this.show_contentframe(false);
};
// This allow as to re-select selected message and display it in preview frame
this.msglist_click = function(list)
{
if (list.multi_selecting || !this.env.contentframe)
return;
if (list.get_single_selection())
return;
var win = this.get_frame_window(this.env.contentframe);
if (win && win.location.href.indexOf(this.env.blankpage) >= 0) {
if (this.preview_timer)
clearTimeout(this.preview_timer);
if (this.preview_read_timer)
clearTimeout(this.preview_read_timer);
this.preview_timer = setTimeout(function() { ref.msglist_get_preview(); }, this.dblclick_time);
}
};
this.msglist_dbl_click = function(list)
{
if (this.preview_timer)
clearTimeout(this.preview_timer);
if (this.preview_read_timer)
clearTimeout(this.preview_read_timer);
var uid = list.get_single_selection();
if (uid && 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);
};
this.msglist_keypress = function(list)
{
if (list.modkey == CONTROL_KEY)
return;
if (list.key_pressed == list.ENTER_KEY)
this.command('show');
else if (list.key_pressed == list.DELETE_KEY || list.key_pressed == list.BACKSPACE_KEY)
this.command('delete');
else if (list.key_pressed == 33)
this.command('previouspage');
else if (list.key_pressed == 34)
this.command('nextpage');
};
this.msglist_get_preview = function()
{
var uid = this.get_single_uid();
if (uid && this.env.contentframe && !this.drag_active)
this.show_message(uid, false, true);
else if (this.env.contentframe)
this.show_contentframe(false);
};
this.msglist_expand = function(row)
{
if (this.env.messages[row.uid])
this.env.messages[row.uid].expanded = row.expanded;
$(row.obj)[row.expanded?'addClass':'removeClass']('expanded');
};
this.msglist_set_coltypes = function(list)
{
var i, found, name, cols = list.thead.rows[0].cells;
this.env.coltypes = [];
for (i=0; i<cols.length; i++)
if (cols[i].id && cols[i].id.match(/^rcm/)) {
name = cols[i].id.replace(/^rcm/, '');
this.env.coltypes.push(name);
}
if ((found = $.inArray('flag', this.env.coltypes)) >= 0)
this.env.flagged_col = found;
if ((found = $.inArray('subject', this.env.coltypes)) >= 0)
this.env.subject_col = found;
this.command('save-pref', { name: 'list_cols', value: this.env.coltypes, session: 'list_attrib/columns' });
};
this.check_droptarget = function(id)
{
if (this.task == 'mail')
return (this.env.mailboxes[id] && this.env.mailboxes[id].id != this.env.mailbox && !this.env.mailboxes[id].virtual) ? 1 : 0;
if (this.task == 'settings')
return id != this.env.mailbox ? 1 : 0;
if (this.task == 'addressbook') {
if (id != this.env.source && this.env.contactfolders[id]) {
// droptarget is a group - contact add to group action
if (this.env.contactfolders[id].type == 'group') {
var target_abook = this.env.contactfolders[id].source;
if (this.env.contactfolders[id].id != this.env.group && !this.env.contactfolders[target_abook].readonly) {
// search result may contain contacts from many sources
return (this.env.selection_sources.length > 1 || $.inArray(target_abook, this.env.selection_sources) == -1) ? 2 : 1;
}
}
// droptarget is a (writable) addressbook - contact copy action
else if (!this.env.contactfolders[id].readonly) {
// search result may contain contacts from many sources
return (this.env.selection_sources.length > 1 || $.inArray(id, this.env.selection_sources) == -1) ? 2 : 0;
}
}
}
return 0;
};
// open popup window
this.open_window = function(url, small, toolbar)
{
var wname = 'rcmextwin' + new Date().getTime();
url += (url.match(/\?/) ? '&' : '?') + '_extwin=1';
if (this.env.standard_windows)
extwin = window.open(url, wname);
else {
var win = this.is_framed() ? parent.window : window,
page = $(win),
page_width = page.width(),
page_height = bw.mz ? $('body', win).height() : page.height(),
w = Math.min(small ? this.env.popup_width_small : this.env.popup_width, page_width),
h = page_height, // always use same height
l = (win.screenLeft || win.screenX) + 20,
t = (win.screenTop || win.screenY) + 20,
extwin = window.open(url, wname,
'width='+w+',height='+h+',top='+t+',left='+l+',resizable=yes,location=no,scrollbars=yes'
+(toolbar ? ',toolbar=yes,menubar=yes,status=yes' : ',toolbar=no,menubar=no,status=no'));
}
// write loading... message to empty windows
if (!url && extwin.document) {
extwin.document.write('<html><body>' + this.get_label('loading') + '</body></html>');
}
// focus window, delayed to bring to front
window.setTimeout(function() { extwin.focus(); }, 10);
return extwin;
};
/*********************************************************/
/********* (message) list functionality *********/
/*********************************************************/
this.init_message_row = function(row)
{
var expando, self = this, uid = row.uid,
status_icon = (this.env.status_col != null ? 'status' : 'msg') + 'icn' + row.uid;
if (uid && this.env.messages[uid])
$.extend(row, this.env.messages[uid]);
// set eventhandler to status icon
if (row.icon = document.getElementById(status_icon)) {
row.icon._row = row.obj;
row.icon.onmousedown = function(e) { self.command('toggle_status', this); rcube_event.cancel(e); };
}
// save message icon position too
if (this.env.status_col != null)
row.msgicon = document.getElementById('msgicn'+row.uid);
else
row.msgicon = row.icon;
// set eventhandler to flag icon, if icon found
if (this.env.flagged_col != null && (row.flagicon = document.getElementById('flagicn'+row.uid))) {
row.flagicon._row = row.obj;
row.flagicon.onmousedown = function(e) { self.command('toggle_flag', this); rcube_event.cancel(e); };
}
if (!row.depth && row.has_children && (expando = document.getElementById('rcmexpando'+row.uid))) {
row.expando = expando;
expando.onmousedown = function(e) { return self.expand_message_row(e, uid); };
}
this.triggerEvent('insertrow', { uid:uid, row:row });
};
// create a table row in the message list
this.add_message_row = function(uid, cols, flags, attop)
{
if (!this.gui_objects.messagelist || !this.message_list)
return false;
// Prevent from adding messages from different folder (#1487752)
if (flags.mbox != this.env.mailbox && !flags.skip_mbox_check)
return false;
if (!this.env.messages[uid])
this.env.messages[uid] = {};
// merge flags over local message object
$.extend(this.env.messages[uid], {
deleted: flags.deleted?1:0,
replied: flags.answered?1:0,
unread: !flags.seen?1:0,
forwarded: flags.forwarded?1:0,
flagged: flags.flagged?1:0,
has_children: flags.has_children?1:0,
depth: flags.depth?flags.depth:0,
unread_children: flags.unread_children?flags.unread_children:0,
parent_uid: flags.parent_uid?flags.parent_uid:0,
selected: this.select_all_mode || this.message_list.in_selection(uid),
ml: flags.ml?1:0,
ctype: flags.ctype,
// flags from plugins
flags: flags.extra_flags
});
var c, n, col, html, css_class,
tree = '', expando = '',
list = this.message_list,
rows = list.rows,
message = this.env.messages[uid],
row_class = 'message'
+ (!flags.seen ? ' unread' : '')
+ (flags.deleted ? ' deleted' : '')
+ (flags.flagged ? ' flagged' : '')
+ (flags.unread_children && flags.seen && !this.env.autoexpand_threads ? ' unroot' : '')
+ (message.selected ? ' selected' : ''),
row = { cols:[], style:{}, id:'rcmrow'+uid };
// message status icons
css_class = 'msgicon';
if (this.env.status_col === null) {
css_class += ' status';
if (flags.deleted)
css_class += ' deleted';
else if (!flags.seen)
css_class += ' unread';
else if (flags.unread_children > 0)
css_class += ' unreadchildren';
}
if (flags.answered)
css_class += ' replied';
if (flags.forwarded)
css_class += ' forwarded';
// update selection
if (message.selected && !list.in_selection(uid))
list.selection.push(uid);
// threads
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>';
if ((rows[message.parent_uid] && rows[message.parent_uid].expanded === false)
|| ((this.env.autoexpand_threads == 0 || this.env.autoexpand_threads == 2) &&
(!rows[message.parent_uid] || !rows[message.parent_uid].expanded))
) {
row.style.display = 'none';
message.expanded = false;
}
else
message.expanded = true;
row_class += ' thread expanded';
}
else if (message.has_children) {
if (message.expanded === undefined && (this.env.autoexpand_threads == 1 || (this.env.autoexpand_threads == 2 && message.unread_children))) {
message.expanded = true;
}
expando = '<div id="rcmexpando' + uid + '" class="' + (message.expanded ? 'expanded' : 'collapsed') + '">&nbsp;&nbsp;</div>';
row_class += ' thread' + (message.expanded? ' expanded' : '');
}
}
tree += '<span id="msgicn'+uid+'" class="'+css_class+'">&nbsp;</span>';
row.className = row_class;
// build subject link
if (!bw.ie && cols.subject) {
var action = flags.mbox == this.env.drafts_mailbox ? 'compose' : 'show';
var uid_param = flags.mbox == this.env.drafts_mailbox ? '_draft_uid' : '_uid';
cols.subject = '<a href="./?_task=mail&_action='+action+'&_mbox='+urlencode(flags.mbox)+'&'+uid_param+'='+uid+'"'+
' onclick="return rcube_event.cancel(event)" onmouseover="rcube_webmail.long_subject_title(this,'+(message.depth+1)+')">'+cols.subject+'</a>';
}
// add each submitted col
for (n in this.env.coltypes) {
c = this.env.coltypes[n];
col = { className: String(c).toLowerCase() };
if (c == 'flag') {
css_class = (flags.flagged ? 'flagged' : 'unflagged');
html = '<span id="flagicn'+uid+'" class="'+css_class+'">&nbsp;</span>';
}
else if (c == 'attachment') {
if (/application\/|multipart\/(m|signed)/.test(flags.ctype))
html = '<span class="attachment">&nbsp;</span>';
else if (/multipart\/report/.test(flags.ctype))
html = '<span class="report">&nbsp;</span>';
else
html = '&nbsp;';
}
else if (c == 'status') {
if (flags.deleted)
css_class = 'deleted';
else if (!flags.seen)
css_class = 'unread';
else if (flags.unread_children > 0)
css_class = 'unreadchildren';
else
css_class = 'msgicon';
html = '<span id="statusicn'+uid+'" class="'+css_class+'">&nbsp;</span>';
}
else if (c == 'threads')
html = expando;
else if (c == 'subject') {
if (bw.ie) {
col.onmouseover = function() { rcube_webmail.long_subject_title_ie(this, message.depth+1); };
if (bw.ie8)
tree = '<span></span>' + tree; // #1487821
}
html = tree + cols[c];
}
else if (c == 'priority') {
if (flags.prio > 0 && flags.prio < 6)
html = '<span class="prio'+flags.prio+'">&nbsp;</span>';
else
html = '&nbsp;';
}
else
html = cols[c];
col.innerHTML = html;
row.cols.push(col);
}
list.insert_row(row, attop);
// remove 'old' row
if (attop && this.env.pagesize && list.rowcount > this.env.pagesize) {
var uid = list.get_last_row();
list.remove_row(uid);
list.clear_selection(uid);
}
};
this.set_list_sorting = function(sort_col, sort_order)
{
// set table header class
$('#rcm'+this.env.sort_col).removeClass('sorted'+(this.env.sort_order.toUpperCase()));
if (sort_col)
$('#rcm'+sort_col).addClass('sorted'+sort_order);
this.env.sort_col = sort_col;
this.env.sort_order = sort_order;
};
this.set_list_options = function(cols, sort_col, sort_order, threads)
{
var update, post_data = {};
if (sort_col === undefined)
sort_col = this.env.sort_col;
if (!sort_order)
sort_order = this.env.sort_order;
if (this.env.sort_col != sort_col || this.env.sort_order != sort_order) {
update = 1;
this.set_list_sorting(sort_col, sort_order);
}
if (this.env.threading != threads) {
update = 1;
post_data._threads = threads;
}
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;
for (i=0; i<oldcols.length; i++) {
name = oldcols[i];
idx = $.inArray(name, cols);
if (idx != -1) {
newcols.push(name);
delete cols[idx];
}
}
for (i=0; i<cols.length; i++)
if (cols[i])
newcols.push(cols[i]);
if (newcols.join() != oldcols.join()) {
update = 1;
post_data._cols = newcols.join(',');
}
}
if (update)
this.list_mailbox('', '', sort_col+'_'+sort_order, post_data);
};
// when user double-clicks on a row
this.show_message = function(id, safe, preview)
{
if (!id)
return;
var win, target = window,
action = preview ? 'preview': 'show',
url = '&_action='+action+'&_uid='+id+'&_mbox='+urlencode(this.env.mailbox);
if (preview && (win = this.get_frame_window(this.env.contentframe))) {
target = win;
url += '&_framed=1';
}
if (safe)
url += '&_safe=1';
// also send search request to get the right messages
if (this.env.search_request)
url += '&_search='+this.env.search_request;
// add browser capabilities, so we can properly handle attachments
url += '&_caps='+urlencode(this.browser_capabilities());
if (this.env.extwin)
url += '&_extwin=1';
if (preview && String(target.location.href).indexOf(url) >= 0) {
this.show_contentframe(true);
}
else {
if (!preview && this.env.message_extwin && !this.env.extwin)
this.open_window(this.env.comm_path+url, true);
else
this.location_href(this.env.comm_path+url, target, true);
// mark as read and change mbox unread counter
if (preview && this.message_list && this.message_list.rows[id] && this.message_list.rows[id].unread && this.env.preview_pane_mark_read >= 0) {
this.preview_read_timer = setTimeout(function() {
ref.set_message(id, 'unread', false);
ref.update_thread_root(id, 'read');
if (ref.env.unread_counts[ref.env.mailbox]) {
ref.env.unread_counts[ref.env.mailbox] -= 1;
ref.set_unread_count(ref.env.mailbox, ref.env.unread_counts[ref.env.mailbox], ref.env.mailbox == 'INBOX');
}
if (ref.env.preview_pane_mark_read > 0)
ref.http_post('mark', {_uid: id, _flag: 'read', _quiet: 1});
}, this.env.preview_pane_mark_read * 1000);
}
}
};
this.show_contentframe = function(show)
{
var frame, win, name = this.env.contentframe;
if (name && (frame = this.get_frame_element(name))) {
if (!show && (win = this.get_frame_window(name))) {
if (win.location && win.location.href.indexOf(this.env.blankpage)<0)
win.location.href = this.env.blankpage;
}
else if (!bw.safari && !bw.konq)
$(frame)[show ? 'show' : 'hide']();
}
if (!show && this.busy)
this.set_busy(false, null, this.env.frame_lock);
};
this.get_frame_element = function(id)
{
var frame;
if (id && (frame = document.getElementById(id)))
return frame;
};
this.get_frame_window = function(id)
{
var frame = this.get_frame_element(id);
if (frame && frame.name && window.frames)
return window.frames[frame.name];
};
this.lock_frame = function()
{
if (!this.env.frame_lock)
(this.is_framed() ? parent.rcmail : this).env.frame_lock = this.set_busy(true, 'loading');
};
// list a specific page
this.list_page = function(page)
{
if (page == 'next')
page = this.env.current_page+1;
else if (page == 'last')
page = this.env.pagecount;
else if (page == 'prev' && this.env.current_page > 1)
page = this.env.current_page-1;
else if (page == 'first' && this.env.current_page > 1)
page = 1;
if (page > 0 && page <= this.env.pagecount) {
this.env.current_page = page;
if (this.task == 'addressbook' || this.contact_list)
this.list_contacts(this.env.source, this.env.group, page);
else if (this.task == 'mail')
this.list_mailbox(this.env.mailbox, page);
}
};
// sends request to check for recent messages
this.checkmail = function()
{
var lock = this.set_busy(true, 'checkingmail'),
params = this.check_recent_params();
this.http_request('check-recent', params, lock);
};
// list messages of a specific mailbox using filter
this.filter_mailbox = function(filter)
{
var lock = this.set_busy(true, 'searching');
this.clear_message_list();
// reset vars
this.env.current_page = 1;
this.http_request('search', this.search_params(false, filter), lock);
};
// list messages of a specific mailbox
this.list_mailbox = function(mbox, page, sort, url)
{
var win, target = window;
if (typeof url != 'object')
url = {};
if (!mbox)
mbox = this.env.mailbox ? this.env.mailbox : 'INBOX';
// add sort to url if set
if (sort)
url._sort = sort;
// also send search request to get the right messages
if (this.env.search_request)
url._search = this.env.search_request;
// set page=1 if changeing to another mailbox
if (this.env.mailbox != mbox) {
page = 1;
this.env.current_page = page;
this.select_all_mode = false;
}
// unselect selected messages and clear the list and message data
this.clear_message_list();
if (mbox != this.env.mailbox || (mbox == this.env.mailbox && !page && !sort))
url._refresh = 1;
this.select_folder(mbox, '', true);
this.unmark_folder(mbox, 'recent', '', true);
this.env.mailbox = mbox;
// load message list remotely
if (this.gui_objects.messagelist) {
this.list_mailbox_remote(mbox, page, url);
return;
}
if (win = this.get_frame_window(this.env.contentframe)) {
target = win;
url._framed = 1;
}
// load message list to target frame/window
if (mbox) {
this.set_busy(true, 'loading');
url._mbox = mbox;
if (page)
url._page = page;
this.location_href(url, target);
}
};
this.clear_message_list = function()
{
this.env.messages = {};
this.last_selected = 0;
this.show_contentframe(false);
if (this.message_list)
this.message_list.clear(true);
};
// send remote request to load message list
this.list_mailbox_remote = function(mbox, page, post_data)
{
// clear message list first
this.message_list.clear();
var lock = this.set_busy(true, 'loading');
if (typeof post_data != 'object')
post_data = {};
post_data._mbox = mbox;
if (page)
post_data._page = page;
this.http_request('list', post_data, lock);
};
// removes messages that doesn't exists from list selection array
this.update_selection = function()
{
var selected = this.message_list.selection,
rows = this.message_list.rows,
i, selection = [];
for (i in selected)
if (rows[selected[i]])
selection.push(selected[i]);
this.message_list.selection = selection;
}
// expand all threads with unread children
this.expand_unread = function()
{
var r, tbody = this.gui_objects.messagelist.tBodies[0],
new_row = tbody.firstChild;
while (new_row) {
if (new_row.nodeType == 1 && (r = this.message_list.rows[new_row.uid]) && r.unread_children) {
this.message_list.expand_all(r);
this.set_unread_children(r.uid);
}
new_row = new_row.nextSibling;
}
return false;
};
// thread expanding/collapsing handler
this.expand_message_row = function(e, uid)
{
var row = this.message_list.rows[uid];
// handle unread_children mark
row.expanded = !row.expanded;
this.set_unread_children(uid);
row.expanded = !row.expanded;
this.message_list.expand_row(e, uid);
};
// message list expanding
this.expand_threads = function()
{
if (!this.env.threading || !this.env.autoexpand_threads || !this.message_list)
return;
switch (this.env.autoexpand_threads) {
case 2: this.expand_unread(); break;
case 1: this.message_list.expand_all(); break;
}
};
// Initializes threads indicators/expanders after list update
this.init_threads = function(roots, mbox)
{
// #1487752
if (mbox && mbox != this.env.mailbox)
return false;
for (var n=0, len=roots.length; n<len; n++)
this.add_tree_icons(roots[n]);
this.expand_threads();
};
// adds threads tree icons to the list (or specified thread)
this.add_tree_icons = function(root)
{
var i, l, r, n, len, pos, tmp = [], uid = [],
row, rows = this.message_list.rows;
if (root)
row = rows[root] ? rows[root].obj : null;
else
row = this.message_list.tbody.firstChild;
while (row) {
if (row.nodeType == 1 && (r = rows[row.uid])) {
if (r.depth) {
for (i=tmp.length-1; i>=0; i--) {
len = tmp[i].length;
if (len > r.depth) {
pos = len - r.depth;
if (!(tmp[i][pos] & 2))
tmp[i][pos] = tmp[i][pos] ? tmp[i][pos]+2 : 2;
}
else if (len == r.depth) {
if (!(tmp[i][0] & 2))
tmp[i][0] += 2;
}
if (r.depth > len)
break;
}
tmp.push(new Array(r.depth));
tmp[tmp.length-1][0] = 1;
uid.push(r.uid);
}
else {
if (tmp.length) {
for (i in tmp) {
this.set_tree_icons(uid[i], tmp[i]);
}
tmp = [];
uid = [];
}
if (root && row != rows[root].obj)
break;
}
}
row = row.nextSibling;
}
if (tmp.length) {
for (i in tmp) {
this.set_tree_icons(uid[i], tmp[i]);
}
}
};
// adds tree icons to specified message row
this.set_tree_icons = function(uid, tree)
{
var i, divs = [], html = '', len = tree.length;
for (i=0; i<len; i++) {
if (tree[i] > 2)
divs.push({'class': 'l3', width: 15});
else if (tree[i] > 1)
divs.push({'class': 'l2', width: 15});
else if (tree[i] > 0)
divs.push({'class': 'l1', width: 15});
// separator div
else if (divs.length && !divs[divs.length-1]['class'])
divs[divs.length-1].width += 15;
else
divs.push({'class': null, width: 15});
}
for (i=divs.length-1; i>=0; i--) {
if (divs[i]['class'])
html += '<div class="tree '+divs[i]['class']+'" />';
else
html += '<div style="width:'+divs[i].width+'px" />';
}
if (html)
$('#rcmtab'+uid).html(html);
};
// update parent in a thread
this.update_thread_root = function(uid, flag)
{
if (!this.env.threading)
return;
var root = this.message_list.find_root(uid);
if (uid == root)
return;
var p = this.message_list.rows[root];
if (flag == 'read' && p.unread_children) {
p.unread_children--;
}
else if (flag == 'unread' && p.has_children) {
// unread_children may be undefined
p.unread_children = p.unread_children ? p.unread_children + 1 : 1;
}
else {
return;
}
this.set_message_icon(root);
this.set_unread_children(root);
};
// update thread indicators for all messages in a thread below the specified message
// return number of removed/added root level messages
this.update_thread = function (uid)
{
if (!this.env.threading)
return 0;
var r, parent, count = 0,
rows = this.message_list.rows,
row = rows[uid],
depth = rows[uid].depth,
roots = [];
if (!row.depth) // root message: decrease roots count
count--;
else if (row.unread) {
// update unread_children for thread root
parent = this.message_list.find_root(uid);
rows[parent].unread_children--;
this.set_unread_children(parent);
}
parent = row.parent_uid;
// childrens
row = row.obj.nextSibling;
while (row) {
if (row.nodeType == 1 && (r = rows[row.uid])) {
if (!r.depth || r.depth <= depth)
break;
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('');
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)
.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.unread_children = 0;
roots.push(r);
}
// show if it was hidden
if (r.obj.style.display == 'none')
$(r.obj).show();
}
else {
if (r.depth == depth)
r.parent_uid = parent;
if (r.unread && roots.length)
roots[roots.length-1].unread_children++;
}
}
row = row.nextSibling;
}
// update unread_children for roots
for (var i=0; i<roots.length; i++)
this.set_unread_children(roots[i].uid);
return count;
};
this.delete_excessive_thread_rows = function()
{
var rows = this.message_list.rows,
tbody = this.message_list.tbody,
row = tbody.firstChild,
cnt = this.env.pagesize + 1;
while (row) {
if (row.nodeType == 1 && (r = rows[row.uid])) {
if (!r.depth && cnt)
cnt--;
if (!cnt)
this.message_list.remove_row(row.uid);
}
row = row.nextSibling;
}
};
// set message icon
this.set_message_icon = function(uid)
{
var css_class,
row = this.message_list.rows[uid];
if (!row)
return false;
if (row.icon) {
css_class = 'msgicon';
if (row.deleted)
css_class += ' deleted';
else if (row.unread)
css_class += ' unread';
else if (row.unread_children)
css_class += ' unreadchildren';
if (row.msgicon == row.icon) {
if (row.replied)
css_class += ' replied';
if (row.forwarded)
css_class += ' forwarded';
css_class += ' status';
}
row.icon.className = css_class;
}
if (row.msgicon && row.msgicon != row.icon) {
css_class = 'msgicon';
if (!row.unread && row.unread_children)
css_class += ' unreadchildren';
if (row.replied)
css_class += ' replied';
if (row.forwarded)
css_class += ' forwarded';
row.msgicon.className = css_class;
}
if (row.flagicon) {
css_class = (row.flagged ? 'flagged' : 'unflagged');
row.flagicon.className = css_class;
}
};
// set message status
this.set_message_status = function(uid, flag, status)
{
var row = this.message_list.rows[uid];
if (!row)
return false;
if (flag == 'unread')
row.unread = status;
else if(flag == 'deleted')
row.deleted = status;
else if (flag == 'replied')
row.replied = status;
else if (flag == 'forwarded')
row.forwarded = status;
else if (flag == 'flagged')
row.flagged = status;
};
// set message row status, class and icon
this.set_message = function(uid, flag, status)
{
var row = this.message_list && this.message_list.rows[uid];
if (!row)
return false;
if (flag)
this.set_message_status(uid, flag, status);
var rowobj = $(row.obj);
if (row.unread && !rowobj.hasClass('unread'))
rowobj.addClass('unread');
else if (!row.unread && rowobj.hasClass('unread'))
rowobj.removeClass('unread');
if (row.deleted && !rowobj.hasClass('deleted'))
rowobj.addClass('deleted');
else if (!row.deleted && rowobj.hasClass('deleted'))
rowobj.removeClass('deleted');
if (row.flagged && !rowobj.hasClass('flagged'))
rowobj.addClass('flagged');
else if (!row.flagged && rowobj.hasClass('flagged'))
rowobj.removeClass('flagged');
this.set_unread_children(uid);
this.set_message_icon(uid);
};
// sets unroot (unread_children) class of parent row
this.set_unread_children = function(uid)
{
var row = this.message_list.rows[uid];
if (row.parent_uid)
return;
if (!row.unread && row.unread_children && !row.expanded)
$(row.obj).addClass('unroot');
else
$(row.obj).removeClass('unroot');
};
// copy selected messages to the specified mailbox
this.copy_messages = function(mbox)
{
if (mbox && typeof mbox === 'object')
mbox = mbox.id;
// exit if current or no mailbox specified
if (!mbox || mbox == this.env.mailbox)
return;
var post_data = this.selection_post_data({_target_mbox: mbox});
// exit if selection is empty
if (!post_data._uid)
return;
// send request to server
this.http_post('copy', post_data, this.display_message(this.get_label('copyingmessage'), 'loading'));
};
// move selected messages to the specified mailbox
this.move_messages = function(mbox)
{
if (mbox && typeof mbox === 'object')
mbox = mbox.id;
// exit if current or no mailbox specified
if (!mbox || mbox == this.env.mailbox)
return;
var lock = false, post_data = this.selection_post_data({_target_mbox: mbox});
// exit if selection is empty
if (!post_data._uid)
return;
// show wait message
if (this.env.action == 'show')
lock = this.set_busy(true, 'movingmessage');
else
this.show_contentframe(false);
// Hide message command buttons until a message is selected
this.enable_command(this.env.message_commands, false);
this._with_selected_messages('moveto', post_data, lock);
};
// delete selected messages from the current mailbox
this.delete_messages = function(event)
{
var uid, i, len, trash = this.env.trash_mailbox,
list = this.message_list,
selection = list ? list.get_selection() : [];
// exit if no mailbox specified or if selection is empty
if (!this.env.uid && !selection.length)
return;
// also select childs of collapsed rows
for (i=0, len=selection.length; i<len; i++) {
uid = selection[i];
if (list.rows[uid].has_children && !list.rows[uid].expanded)
list.select_children(uid);
}
// if config is set to flag for deletion
if (this.env.flag_for_deletion) {
this.mark_message('delete');
return false;
}
// if there isn't a defined trash mailbox or we are in it
else if (!trash || this.env.mailbox == trash)
this.permanently_remove_messages();
// we're in Junk folder and delete_junk is enabled
else if (this.env.delete_junk && this.env.junk_mailbox && this.env.mailbox == this.env.junk_mailbox)
this.permanently_remove_messages();
// if there is a trash mailbox defined and we're not currently in it
else {
// if shift was pressed delete it immediately
if ((list && list.modkey == SHIFT_KEY) || (event && rcube_event.get_modifier(event) == SHIFT_KEY)) {
if (confirm(this.get_label('deletemessagesconfirm')))
this.permanently_remove_messages();
}
else
this.move_messages(trash);
}
return true;
};
// delete the selected messages permanently
this.permanently_remove_messages = function()
{
var post_data = this.selection_post_data();
// exit if selection is empty
if (!post_data._uid)
return;
this.show_contentframe(false);
this._with_selected_messages('delete', post_data);
};
// Send a specifc moveto/delete request with UIDs of all selected messages
// @private
this._with_selected_messages = function(action, post_data, lock)
{
var count = 0, msg;
// update the list (remove rows, clear selection)
if (this.message_list) {
var n, id, root, roots = [],
selection = this.message_list.get_selection();
for (n=0, len=selection.length; n<len; n++) {
id = selection[n];
if (this.env.threading) {
count += this.update_thread(id);
root = this.message_list.find_root(id);
if (root != id && $.inArray(root, roots) < 0) {
roots.push(root);
}
}
this.message_list.remove_row(id, (this.env.display_next && n == selection.length-1));
}
// make sure there are no selected rows
if (!this.env.display_next)
this.message_list.clear_selection();
// update thread tree icons
for (n=0, len=roots.length; n<len; n++) {
this.add_tree_icons(roots[n]);
}
}
if (this.env.display_next && this.env.next_uid)
post_data._next_uid = this.env.next_uid;
if (count < 0)
post_data._count = (count*-1);
// remove threads from the end of the list
else if (count > 0)
this.delete_excessive_thread_rows();
if (!lock) {
msg = action == 'moveto' ? 'movingmessage' : 'deletingmessage';
lock = this.display_message(this.get_label(msg), 'loading');
}
// send request to server
this.http_post(action, post_data, lock);
};
// build post data for message delete/move/copy/flag requests
this.selection_post_data = function(data)
{
if (typeof(data) != 'object')
data = {};
data._mbox = this.env.mailbox;
if (!data._uid) {
var uids = this.env.uid ? [this.env.uid] : this.message_list.get_selection();
data._uid = this.uids_to_list(uids);
}
if (this.env.action)
data._from = this.env.action;
// also send search request to get the right messages
if (this.env.search_request)
data._search = this.env.search_request;
return data;
};
// set a specific flag to one or more messages
this.mark_message = function(flag, uid)
{
var a_uids = [], r_uids = [], len, n, id,
list = this.message_list;
if (uid)
a_uids[0] = uid;
else if (this.env.uid)
a_uids[0] = this.env.uid;
else if (list)
a_uids = list.get_selection();
if (!list)
r_uids = a_uids;
else {
list.focus();
for (n=0, len=a_uids.length; n<len; n++) {
id = a_uids[n];
if ((flag == 'read' && list.rows[id].unread)
|| (flag == 'unread' && !list.rows[id].unread)
|| (flag == 'delete' && !list.rows[id].deleted)
|| (flag == 'undelete' && list.rows[id].deleted)
|| (flag == 'flagged' && !list.rows[id].flagged)
|| (flag == 'unflagged' && list.rows[id].flagged))
{
r_uids.push(id);
}
}
}
// nothing to do
if (!r_uids.length && !this.select_all_mode)
return;
switch (flag) {
case 'read':
case 'unread':
this.toggle_read_status(flag, r_uids);
break;
case 'delete':
case 'undelete':
this.toggle_delete_status(r_uids);
break;
case 'flagged':
case 'unflagged':
this.toggle_flagged_status(flag, a_uids);
break;
}
};
// set class to read/unread
this.toggle_read_status = function(flag, a_uids)
{
var i, len = a_uids.length,
post_data = this.selection_post_data({_uid: this.uids_to_list(a_uids), _flag: flag}),
lock = this.display_message(this.get_label('markingmessage'), 'loading');
// mark all message rows as read/unread
for (i=0; i<len; i++)
this.set_message(a_uids[i], 'unread', (flag == 'unread' ? true : false));
this.http_post('mark', post_data, lock);
for (i=0; i<len; i++)
this.update_thread_root(a_uids[i], flag);
};
// set image to flagged or unflagged
this.toggle_flagged_status = function(flag, a_uids)
{
var i, len = a_uids.length,
post_data = this.selection_post_data({_uid: this.uids_to_list(a_uids), _flag: flag}),
lock = this.display_message(this.get_label('markingmessage'), 'loading');
// mark all message rows as flagged/unflagged
for (i=0; i<len; i++)
this.set_message(a_uids[i], 'flagged', (flag == 'flagged' ? true : false));
this.http_post('mark', post_data, lock);
};
// mark all message rows as deleted/undeleted
this.toggle_delete_status = function(a_uids)
{
var len = a_uids.length,
i, uid, all_deleted = true,
rows = this.message_list ? this.message_list.rows : [];
if (len == 1) {
if (!rows.length || (rows[a_uids[0]] && !rows[a_uids[0]].deleted))
this.flag_as_deleted(a_uids);
else
this.flag_as_undeleted(a_uids);
return true;
}
for (i=0; i<len; i++) {
uid = a_uids[i];
if (rows[uid] && !rows[uid].deleted) {
all_deleted = false;
break;
}
}
if (all_deleted)
this.flag_as_undeleted(a_uids);
else
this.flag_as_deleted(a_uids);
return true;
};
this.flag_as_undeleted = function(a_uids)
{
var i, len = a_uids.length,
post_data = this.selection_post_data({_uid: this.uids_to_list(a_uids), _flag: 'undelete'}),
lock = this.display_message(this.get_label('markingmessage'), 'loading');
for (i=0; i<len; i++)
this.set_message(a_uids[i], 'deleted', false);
this.http_post('mark', post_data, lock);
};
this.flag_as_deleted = function(a_uids)
{
var r_uids = [],
post_data = this.selection_post_data({_uid: this.uids_to_list(a_uids), _flag: 'delete'}),
lock = this.display_message(this.get_label('markingmessage'), 'loading'),
rows = this.message_list ? this.message_list.rows : [],
count = 0;
for (var i=0, len=a_uids.length; i<len; i++) {
uid = a_uids[i];
if (rows[uid]) {
if (rows[uid].unread)
r_uids[r_uids.length] = uid;
if (this.env.skip_deleted) {
count += this.update_thread(uid);
this.message_list.remove_row(uid, (this.env.display_next && i == this.message_list.selection.length-1));
}
else
this.set_message(uid, 'deleted', true);
}
}
// make sure there are no selected rows
if (this.env.skip_deleted && this.message_list) {
if(!this.env.display_next)
this.message_list.clear_selection();
if (count < 0)
post_data._count = (count*-1);
else if (count > 0)
// remove threads from the end of the list
this.delete_excessive_thread_rows();
}
// ??
if (r_uids.length)
post_data._ruid = this.uids_to_list(r_uids);
if (this.env.skip_deleted && this.env.display_next && this.env.next_uid)
post_data._next_uid = this.env.next_uid;
this.http_post('mark', post_data, lock);
};
// flag as read without mark request (called from backend)
// argument should be a coma-separated list of uids
this.flag_deleted_as_read = function(uids)
{
var icn_src, uid, i, len,
rows = this.message_list ? this.message_list.rows : [];
uids = String(uids).split(',');
for (i=0, len=uids.length; i<len; i++) {
uid = uids[i];
if (rows[uid])
this.set_message(uid, 'unread', false);
}
};
// Converts array of message UIDs to comma-separated list for use in URL
// with select_all mode checking
this.uids_to_list = function(uids)
{
return this.select_all_mode ? '*' : uids.join(',');
};
// Sets title of the delete button
this.set_button_titles = function()
{
var label = 'deletemessage';
if (!this.env.flag_for_deletion
&& this.env.trash_mailbox && this.env.mailbox != this.env.trash_mailbox
&& (!this.env.delete_junk || !this.env.junk_mailbox || this.env.mailbox != this.env.junk_mailbox)
)
label = 'movemessagetotrash';
this.set_alttext('delete', label);
};
/*********************************************************/
/********* mailbox folders methods *********/
/*********************************************************/
this.expunge_mailbox = function(mbox)
{
var lock, post_data = {_mbox: mbox};
// lock interface if it's the active mailbox
if (mbox == this.env.mailbox) {
lock = this.set_busy(true, 'loading');
post_data._reload = 1;
if (this.env.search_request)
post_data._search = this.env.search_request;
}
// send request to server
this.http_post('expunge', post_data, lock);
};
this.purge_mailbox = function(mbox)
{
var lock, post_data = {_mbox: mbox};
if (!confirm(this.get_label('purgefolderconfirm')))
return false;
// lock interface if it's the active mailbox
if (mbox == this.env.mailbox) {
lock = this.set_busy(true, 'loading');
post_data._reload = 1;
}
// send request to server
this.http_post('purge', post_data, lock);
};
// test if purge command is allowed
this.purge_mailbox_test = function()
{
return (this.env.exists && (this.env.mailbox == this.env.trash_mailbox || this.env.mailbox == this.env.junk_mailbox
|| this.env.mailbox.match('^' + RegExp.escape(this.env.trash_mailbox) + RegExp.escape(this.env.delimiter))
|| this.env.mailbox.match('^' + RegExp.escape(this.env.junk_mailbox) + RegExp.escape(this.env.delimiter))));
};
/*********************************************************/
/********* login form methods *********/
/*********************************************************/
// handler for keyboard events on the _user field
this.login_user_keyup = function(e)
{
var key = rcube_event.get_keycode(e);
var passwd = $('#rcmloginpwd');
// enter
if (key == 13 && passwd.length && !passwd.val()) {
passwd.focus();
return rcube_event.cancel(e);
}
return true;
};
/*********************************************************/
/********* message compose methods *********/
/*********************************************************/
this.open_compose_step = function(p)
{
var url = this.url('mail/compose', p);
// open new compose window
if (this.env.compose_extwin && !this.env.extwin) {
this.open_window(url);
}
else {
this.redirect(url);
if (this.env.extwin)
window.resizeTo(Math.max(this.env.popup_width, $(window).width()), $(window).height() + 24);
}
};
// init message compose form: set focus and eventhandlers
this.init_messageform = function()
{
if (!this.gui_objects.messageform)
return false;
var input_from = $("[name='_from']"),
input_to = $("[name='_to']"),
input_subject = $("input[name='_subject']"),
input_message = $("[name='_message']").get(0),
html_mode = $("input[name='_is_html']").val() == '1',
ac_fields = ['cc', 'bcc', 'replyto', 'followupto'],
ac_props, opener_rc = this.opener();
// close compose step in opener
if (opener_rc && opener_rc.env.action == 'compose') {
setTimeout(function(){ opener.history.back(); }, 100);
this.env.opened_extwin = true;
}
// configure parallel autocompletion
if (this.env.autocomplete_threads > 0) {
ac_props = {
threads: this.env.autocomplete_threads,
sources: this.env.autocomplete_sources
};
}
// init live search events
this.init_address_input_events(input_to, ac_props);
for (var i in ac_fields) {
this.init_address_input_events($("[name='_"+ac_fields[i]+"']"), ac_props);
}
if (!html_mode) {
this.set_caret_pos(input_message, this.env.top_posting ? 0 : $(input_message).val().length);
// add signature according to selected identity
// if we have HTML editor, signature is added in callback
if (input_from.prop('type') == 'select-one' && !this.env.opened_extwin) {
this.change_identity(input_from[0]);
}
}
if (input_to.val() == '')
input_to.focus();
else if (input_subject.val() == '')
input_subject.focus();
else if (input_message)
input_message.focus();
this.env.compose_focus_elem = document.activeElement;
// get summary of all field values
this.compose_field_hash(true);
// start the auto-save timer
this.auto_save_start();
};
this.init_address_input_events = function(obj, props)
{
this.env.recipients_delimiter = this.env.recipients_separator + ' ';
obj[bw.ie || bw.safari || bw.chrome ? 'keydown' : 'keypress'](function(e) { return ref.ksearch_keydown(e, this, props); })
.attr('autocomplete', 'off');
};
this.submit_messageform = function(draft)
{
var form = this.gui_objects.messageform;
if (!form)
return;
// all checks passed, send message
var msgid = this.set_busy(true, draft ? 'savingmessage' : 'sendingmessage'),
lang = this.spellcheck_lang(),
files = [];
// send files list
$('li', this.gui_objects.attachmentlist).each(function() { files.push(this.id.replace(/^rcmfile/, '')); });
$('input[name="_attachments"]', form).val(files.join());
form.target = 'savetarget';
form._draft.value = draft ? '1' : '';
form.action = this.add_url(form.action, '_unlock', msgid);
form.action = this.add_url(form.action, '_lang', lang);
// register timer to notify about connection timeout
this.submit_timer = setTimeout(function(){
ref.set_busy(false, null, msgid);
ref.display_message(ref.get_label('requesttimedout'), 'error');
}, this.env.request_timeout * 1000);
form.submit();
};
this.compose_recipient_select = function(list)
{
this.enable_command('add-recipient', list.selection.length > 0);
};
this.compose_add_recipient = function(field)
{
var recipients = [], input = $('#_'+field), delim = this.env.recipients_delimiter;
if (this.contact_list && this.contact_list.selection.length) {
for (var id, n=0; n < this.contact_list.selection.length; n++) {
id = this.contact_list.selection[n];
if (id && this.env.contactdata[id]) {
recipients.push(this.env.contactdata[id]);
// group is added, expand it
if (id.charAt(0) == 'E' && this.env.contactdata[id].indexOf('@') < 0 && input.length) {
var gid = id.substr(1);
this.group2expand[gid] = { name:this.env.contactdata[id], input:input.get(0) };
this.http_request('group-expand', {_source: this.env.source, _gid: gid}, false);
}
}
}
}
if (recipients.length && input.length) {
var oldval = input.val(), rx = new RegExp(RegExp.escape(delim) + '\\s*$');
if (oldval && !rx.test(oldval))
oldval += delim + ' ';
input.val(oldval + recipients.join(delim + ' ') + delim + ' ');
this.triggerEvent('add-recipient', { field:field, recipients:recipients });
}
};
// checks the input fields before sending a message
this.check_compose_input = function(cmd)
{
// check input fields
var ed, input_to = $("[name='_to']"),
input_cc = $("[name='_cc']"),
input_bcc = $("[name='_bcc']"),
input_from = $("[name='_from']"),
input_subject = $("[name='_subject']"),
input_message = $("[name='_message']");
// check sender (if have no identities)
if (input_from.prop('type') == 'text' && !rcube_check_email(input_from.val(), true)) {
alert(this.get_label('nosenderwarning'));
input_from.focus();
return false;
}
// check for empty recipient
var recipients = input_to.val() ? input_to.val() : (input_cc.val() ? input_cc.val() : input_bcc.val());
if (!rcube_check_email(recipients.replace(/^\s+/, '').replace(/[\s,;]+$/, ''), true)) {
alert(this.get_label('norecipientwarning'));
input_to.focus();
return false;
}
// check if all files has been uploaded
for (var key in this.env.attachments) {
if (typeof this.env.attachments[key] === 'object' && !this.env.attachments[key].complete) {
alert(this.get_label('notuploadedwarning'));
return false;
}
}
// display localized warning for missing subject
if (input_subject.val() == '') {
var myprompt = $('<div class="prompt">').html('<div class="message">' + this.get_label('nosubjectwarning') + '</div>').appendTo(document.body);
var prompt_value = $('<input>').attr('type', 'text').attr('size', 30).appendTo(myprompt).val(this.get_label('nosubject'));
var buttons = {};
buttons[this.get_label('cancel')] = function(){
input_subject.focus();
$(this).dialog('close');
};
buttons[this.get_label('sendmessage')] = function(){
input_subject.val(prompt_value.val());
$(this).dialog('close');
ref.command(cmd, { nocheck:true }); // repeat command which triggered this
};
myprompt.dialog({
modal: true,
resizable: false,
buttons: buttons,
close: function(event, ui) { $(this).remove() }
});
prompt_value.select();
return false;
}
// Apply spellcheck changes if spell checker is active
this.stop_spellchecking();
if (window.tinyMCE)
ed = tinyMCE.get(this.env.composebody);
// check for empty body
if (!ed && input_message.val() == '' && !confirm(this.get_label('nobodywarning'))) {
input_message.focus();
return false;
}
else if (ed) {
if (!ed.getContent() && !confirm(this.get_label('nobodywarning'))) {
ed.focus();
return false;
}
// move body from html editor to textarea (just to be sure, #1485860)
tinyMCE.triggerSave();
}
return true;
};
this.toggle_editor = function(props)
{
this.stop_spellchecking();
if (props.mode == 'html') {
this.plain2html($('#'+props.id).val(), props.id);
tinyMCE.execCommand('mceAddControl', false, props.id);
if (this.env.default_font)
setTimeout(function() {
$(tinyMCE.get(props.id).getBody()).css('font-family', rcmail.env.default_font);
}, 500);
}
else {
var thisMCE = tinyMCE.get(props.id), existingHtml;
if (existingHtml = thisMCE.getContent()) {
if (!confirm(this.get_label('editorwarning'))) {
return false;
}
this.html2plain(existingHtml, props.id);
}
tinyMCE.execCommand('mceRemoveControl', false, props.id);
}
return true;
};
this.stop_spellchecking = function()
{
var ed;
if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody))) {
if (ed.plugins && ed.plugins.spellchecker && ed.plugins.spellchecker.active)
ed.execCommand('mceSpellCheck');
}
else if (ed = this.env.spellcheck) {
if (ed.state && ed.state != 'ready' && ed.state != 'no_error_found')
$(ed.spell_span).trigger('click');
}
this.spellcheck_state();
};
this.spellcheck_state = function()
{
var ed, active;
if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody)) && ed.plugins && ed.plugins.spellchecker)
active = ed.plugins.spellchecker.active;
else if ((ed = this.env.spellcheck) && ed.state)
active = ed.state != 'ready' && ed.state != 'no_error_found';
if (rcmail.buttons.spellcheck)
$('#'+rcmail.buttons.spellcheck[0].id)[active ? 'addClass' : 'removeClass']('selected');
return active;
};
// get selected language
this.spellcheck_lang = function()
{
var ed;
if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody)) && ed.plugins && ed.plugins.spellchecker)
return ed.plugins.spellchecker.selectedLang;
else if (this.env.spellcheck)
return GOOGIE_CUR_LANG;
};
this.spellcheck_lang_set = function(lang)
{
var ed;
if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody)) && ed.plugins)
ed.plugins.spellchecker.selectedLang = lang;
else if (this.env.spellcheck)
this.env.spellcheck.setCurrentLanguage(lang);
};
// resume spellchecking, highlight provided mispellings without new ajax request
this.spellcheck_resume = function(ishtml, data)
{
if (ishtml) {
var ed = tinyMCE.get(this.env.composebody);
sp = ed.plugins.spellchecker;
sp.active = 1;
sp._markWords(data);
ed.nodeChanged();
}
else {
var sp = this.env.spellcheck;
sp.prepare(false, true);
sp.processData(data);
}
this.spellcheck_state();
}
this.set_draft_id = function(id)
{
var rc;
if (!this.env.draft_id && id && (rc = this.opener())) {
// refresh the drafts folder in opener window
if (rc.env.task == 'mail' && rc.env.action == '' && rc.env.mailbox == this.env.drafts_mailbox)
rc.command('checkmail');
}
this.env.draft_id = id;
$("input[name='_draft_saveid']").val(id);
};
this.auto_save_start = function()
{
if (this.env.draft_autosave)
this.save_timer = setTimeout(function(){ ref.command("savedraft"); }, this.env.draft_autosave * 1000);
// Unlock interface now that saving is complete
this.busy = false;
};
this.compose_field_hash = function(save)
{
// check input fields
var ed, i, val, str = '', hash_fields = ['to', 'cc', 'bcc', 'subject'];
for (i=0; i<hash_fields.length; i++)
if (val = $('[name="_' + hash_fields[i] + '"]').val())
str += val + ':';
if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody)))
str += ed.getContent();
else
str += $("[name='_message']").val();
if (this.env.attachments)
for (var upload_id in this.env.attachments)
str += upload_id;
if (save)
this.cmp_hash = str;
return str;
};
this.change_identity = function(obj, show_sig)
{
if (!obj || !obj.options)
return false;
if (!show_sig)
show_sig = this.env.show_sig;
var i, rx, cursor_pos, p = -1,
id = obj.options[obj.selectedIndex].value,
input_message = $("[name='_message']"),
message = input_message.val(),
is_html = ($("input[name='_is_html']").val() == '1'),
sig = this.env.identity,
delim = this.env.recipients_delimiter,
headers = ['replyto', 'bcc'];
// update reply-to/bcc fields with addresses defined in identities
for (i in headers) {
var key = headers[i],
old_val = sig && this.env.identities[sig] ? this.env.identities[sig][key] : '',
new_val = id && this.env.identities[id] ? this.env.identities[id][key] : '',
input = $('[name="_'+key+'"]'), input_val = input.val();
// remove old address(es)
if (old_val && input_val) {
rx = new RegExp('\\s*' + RegExp.escape(old_val) + '\\s*');
input_val = input_val.replace(rx, '');
}
// cleanup
rx = new RegExp(RegExp.escape(delim) + '\\s*' + RegExp(delim), 'g');
input_val = input_val.replace(rx, delim)
rx = new RegExp('^\\s*' + RegExp.escape(delim) + '\\s*$');
input_val = input_val.replace(rx, '')
// add new address(es)
if (new_val) {
rx = new RegExp(RegExp.escape(delim) + '\\s*$');
if (input_val && !rx.test(input_val))
input_val += delim + ' ';
input_val += new_val + delim + ' ';
}
if (old_val || new_val)
input.val(input_val).change();
}
// enable manual signature insert
if (this.env.signatures && this.env.signatures[id]) {
this.enable_command('insert-sig', true);
this.env.compose_commands.push('insert-sig');
}
else
this.enable_command('insert-sig', false);
if (!is_html) {
// remove the 'old' signature
if (show_sig && sig && this.env.signatures && this.env.signatures[sig]) {
sig = this.env.signatures[sig].text;
sig = sig.replace(/\r\n/g, '\n');
p = this.env.top_posting ? message.indexOf(sig) : message.lastIndexOf(sig);
if (p >= 0)
message = message.substring(0, p) + message.substring(p+sig.length, message.length);
}
// add the new signature string
if (show_sig && this.env.signatures && this.env.signatures[id]) {
sig = this.env.signatures[id].text;
sig = sig.replace(/\r\n/g, '\n');
if (this.env.top_posting) {
if (p >= 0) { // in place of removed signature
message = message.substring(0, p) + sig + message.substring(p, message.length);
cursor_pos = p - 1;
}
else if (!message) { // empty message
cursor_pos = 0;
message = '\n\n' + sig;
}
else if (pos = this.get_caret_pos(input_message.get(0))) { // at cursor position
message = message.substring(0, pos) + '\n' + sig + '\n\n' + message.substring(pos, message.length);
cursor_pos = pos;
}
else { // on top
cursor_pos = 0;
message = '\n\n' + sig + '\n\n' + message.replace(/^[\r\n]+/, '');
}
}
else {
message = message.replace(/[\r\n]+$/, '');
cursor_pos = !this.env.top_posting && message.length ? message.length+1 : 0;
message += '\n\n' + sig;
}
}
else
cursor_pos = this.env.top_posting ? 0 : message.length;
input_message.val(message);
// move cursor before the signature
this.set_caret_pos(input_message.get(0), cursor_pos);
}
else if (show_sig && this.env.signatures) { // html
var editor = tinyMCE.get(this.env.composebody),
sigElem = editor.dom.get('_rc_sig');
// Append the signature as a div within the body
if (!sigElem) {
var body = editor.getBody(),
doc = editor.getDoc();
sigElem = doc.createElement('div');
sigElem.setAttribute('id', '_rc_sig');
if (this.env.top_posting) {
// if no existing sig and top posting then insert at caret pos
editor.getWin().focus(); // correct focus in IE & Chrome
var node = editor.selection.getNode();
if (node.nodeName == 'BODY') {
// no real focus, insert at start
body.insertBefore(sigElem, body.firstChild);
body.insertBefore(doc.createElement('br'), body.firstChild);
}
else {
body.insertBefore(sigElem, node.nextSibling);
body.insertBefore(doc.createElement('br'), node.nextSibling);
}
}
else {
if (bw.ie) // add empty line before signature on IE
body.appendChild(doc.createElement('br'));
body.appendChild(sigElem);
}
}
if (this.env.signatures[id])
sigElem.innerHTML = this.env.signatures[id].html;
}
this.env.identity = id;
return true;
};
- // upload attachment file
- this.upload_file = function(form)
+ // upload (attachment) file
+ this.upload_file = function(form, action)
{
if (!form)
return false;
// count files and size on capable browser
var size = 0, numfiles = 0;
$('input[type=file]', form).each(function(i, field) {
var files = field.files ? field.files.length : (field.value ? 1 : 0);
// check file size
if (field.files) {
for (var i=0; i < files; i++)
size += field.files[i].size;
}
numfiles += files;
});
// create hidden iframe and post upload form
if (numfiles) {
if (this.env.max_filesize && this.env.filesizeerror && size > this.env.max_filesize) {
this.display_message(this.env.filesizeerror, 'error');
return;
}
- var frame_name = this.async_upload_form(form, 'upload', function(e) {
+ var frame_name = this.async_upload_form(form, action || 'upload', function(e) {
var d, content = '';
try {
if (this.contentDocument) {
d = this.contentDocument;
} else if (this.contentWindow) {
d = this.contentWindow.document;
}
content = d.childNodes[0].innerHTML;
} catch (err) {}
if (!content.match(/add2attachment/) && (!bw.opera || (rcmail.env.uploadframe && rcmail.env.uploadframe == e.data.ts))) {
if (!content.match(/display_message/))
rcmail.display_message(rcmail.get_label('fileuploaderror'), 'error');
rcmail.remove_from_attachment_list(e.data.ts);
}
// Opera hack: handle double onload
if (bw.opera)
rcmail.env.uploadframe = e.data.ts;
});
// display upload indicator and cancel button
var content = '<span>' + this.get_label('uploading' + (numfiles > 1 ? 'many' : '')) + '</span>',
ts = frame_name.replace(/^rcmupload/, '');
this.add2attachment_list(ts, { name:'', html:content, classname:'uploading', frame:frame_name, complete:false });
// upload progress support
if (this.env.upload_progress_time) {
this.upload_progress_start('upload', ts);
}
}
// set reference to the form object
this.gui_objects.attachmentform = form;
return true;
};
// add file name to attachment list
// called from upload page
this.add2attachment_list = function(name, att, upload_id)
{
if (!this.gui_objects.attachmentlist)
return false;
if (!att.complete && ref.env.loadingicon)
att.html = '<img src="'+ref.env.loadingicon+'" alt="" class="uploading" />' + att.html;
if (!att.complete && att.frame)
att.html = '<a title="'+this.get_label('cancel')+'" onclick="return rcmail.cancel_attachment_upload(\''+name+'\', \''+att.frame+'\');" href="#cancelupload" class="cancelupload">'
+ (this.env.cancelicon ? '<img src="'+this.env.cancelicon+'" alt="" />' : this.get_label('cancel')) + '</a>' + att.html;
var indicator, li = $('<li>').attr('id', name).addClass(att.classname).html(att.html);
// replace indicator's li
if (upload_id && (indicator = document.getElementById(upload_id))) {
li.replaceAll(indicator);
}
else { // add new li
li.appendTo(this.gui_objects.attachmentlist);
}
if (upload_id && this.env.attachments[upload_id])
delete this.env.attachments[upload_id];
this.env.attachments[name] = att;
return true;
};
this.remove_from_attachment_list = function(name)
{
delete this.env.attachments[name];
$('#'+name).remove();
};
this.remove_attachment = function(name)
{
if (name && this.env.attachments[name])
this.http_post('remove-attachment', { _id:this.env.compose_id, _file:name });
return true;
};
this.cancel_attachment_upload = function(name, frame_name)
{
if (!name || !frame_name)
return false;
this.remove_from_attachment_list(name);
$("iframe[name='"+frame_name+"']").remove();
return false;
};
this.upload_progress_start = function(action, name)
{
setTimeout(function() { rcmail.http_request(action, {_progress: name}); },
this.env.upload_progress_time * 1000);
};
this.upload_progress_update = function(param)
{
var elem = $('#'+param.name + '> span');
if (!elem.length || !param.text)
return;
elem.text(param.text);
if (!param.done)
this.upload_progress_start(param.action, param.name);
};
// send remote request to add a new contact
this.add_contact = function(value)
{
if (value)
this.http_post('addcontact', {_address: value});
return true;
};
// send remote request to search mail or contacts
this.qsearch = function(value)
{
if (value != '') {
var r, lock = this.set_busy(true, 'searching'),
url = this.search_params(value);
if (this.message_list)
this.clear_message_list();
else if (this.contact_list)
this.list_contacts_clear();
if (this.env.source)
url._source = this.env.source;
if (this.env.group)
url._gid = this.env.group;
// reset vars
this.env.current_page = 1;
var action = this.env.action == 'compose' && this.contact_list ? 'search-contacts' : 'search';
r = this.http_request(action, url, lock);
this.env.qsearch = {lock: lock, request: r};
}
};
// build URL params for search
this.search_params = function(search, filter)
{
var n, url = {}, mods_arr = [],
mods = this.env.search_mods,
mbox = this.env.mailbox;
if (!filter && this.gui_objects.search_filter)
filter = this.gui_objects.search_filter.value;
if (!search && this.gui_objects.qsearchbox)
search = this.gui_objects.qsearchbox.value;
if (filter)
url._filter = filter;
if (search) {
url._q = search;
if (mods && this.message_list)
mods = mods[mbox] ? mods[mbox] : mods['*'];
if (mods) {
for (n in mods)
mods_arr.push(n);
url._headers = mods_arr.join(',');
}
}
if (mbox)
url._mbox = mbox;
return url;
};
// reset quick-search form
this.reset_qsearch = function()
{
if (this.gui_objects.qsearchbox)
this.gui_objects.qsearchbox.value = '';
if (this.env.qsearch)
this.abort_request(this.env.qsearch);
this.env.qsearch = null;
this.env.search_request = null;
this.env.search_id = null;
};
this.sent_successfully = function(type, msg, target)
{
this.display_message(msg, type);
if (this.env.extwin) {
var rc = this.opener();
this.lock_form(this.gui_objects.messageform);
if (rc) {
rc.display_message(msg, type);
// refresh the folder where sent message was saved
if (target && rc.env.task == 'mail' && rc.env.action == '' && rc.env.mailbox == target)
rc.command('checkmail');
}
setTimeout(function(){ window.close() }, 1000);
}
else {
// before redirect we need to wait some time for Chrome (#1486177)
setTimeout(function(){ ref.list_mailbox(); }, 500);
}
};
/*********************************************************/
/********* keyboard live-search methods *********/
/*********************************************************/
// handler for keyboard events on address-fields
this.ksearch_keydown = function(e, obj, props)
{
if (this.ksearch_timer)
clearTimeout(this.ksearch_timer);
var highlight,
key = rcube_event.get_keycode(e),
mod = rcube_event.get_modifier(e);
switch (key) {
case 38: // arrow up
case 40: // arrow down
if (!this.ksearch_visible())
break;
var dir = key==38 ? 1 : 0;
highlight = document.getElementById('rcmksearchSelected');
if (!highlight)
highlight = this.ksearch_pane.__ul.firstChild;
if (highlight)
this.ksearch_select(dir ? highlight.previousSibling : highlight.nextSibling);
return rcube_event.cancel(e);
case 9: // tab
if (mod == SHIFT_KEY || !this.ksearch_visible()) {
this.ksearch_hide();
return;
}
case 13: // enter
if (!this.ksearch_visible())
return false;
// insert selected address and hide ksearch pane
this.insert_recipient(this.ksearch_selected);
this.ksearch_hide();
return rcube_event.cancel(e);
case 27: // escape
this.ksearch_hide();
return;
case 37: // left
case 39: // right
if (mod != SHIFT_KEY)
return;
}
// start timer
this.ksearch_timer = setTimeout(function(){ ref.ksearch_get_results(props); }, 200);
this.ksearch_input = obj;
return true;
};
this.ksearch_visible = function()
{
return (this.ksearch_selected !== null && this.ksearch_selected !== undefined && this.ksearch_value);
};
this.ksearch_select = function(node)
{
var current = $('#rcmksearchSelected');
if (current[0] && node) {
current.removeAttr('id').removeClass('selected');
}
if (node) {
$(node).attr('id', 'rcmksearchSelected').addClass('selected');
this.ksearch_selected = node._rcm_id;
}
};
this.insert_recipient = function(id)
{
if (id === null || !this.env.contacts[id] || !this.ksearch_input)
return;
// get cursor pos
var inp_value = this.ksearch_input.value,
cpos = this.get_caret_pos(this.ksearch_input),
p = inp_value.lastIndexOf(this.ksearch_value, cpos),
trigger = false,
insert = '',
// replace search string with full address
pre = inp_value.substring(0, p),
end = inp_value.substring(p+this.ksearch_value.length, inp_value.length);
this.ksearch_destroy();
// insert all members of a group
if (typeof this.env.contacts[id] === 'object' && this.env.contacts[id].id) {
insert += this.env.contacts[id].name + this.env.recipients_delimiter;
this.group2expand[this.env.contacts[id].id] = $.extend({ input: this.ksearch_input }, this.env.contacts[id]);
this.http_request('mail/group-expand', {_source: this.env.contacts[id].source, _gid: this.env.contacts[id].id}, false);
}
else if (typeof this.env.contacts[id] === 'string') {
insert = this.env.contacts[id] + this.env.recipients_delimiter;
trigger = true;
}
this.ksearch_input.value = pre + insert + end;
// set caret to insert pos
cpos = p+insert.length;
if (this.ksearch_input.setSelectionRange)
this.ksearch_input.setSelectionRange(cpos, cpos);
if (trigger)
this.triggerEvent('autocomplete_insert', { field:this.ksearch_input, insert:insert });
};
this.replace_group_recipients = function(id, recipients)
{
if (this.group2expand[id]) {
this.group2expand[id].input.value = this.group2expand[id].input.value.replace(this.group2expand[id].name, recipients);
this.triggerEvent('autocomplete_insert', { field:this.group2expand[id].input, insert:recipients });
this.group2expand[id] = null;
}
};
// address search processor
this.ksearch_get_results = function(props)
{
var inp_value = this.ksearch_input ? this.ksearch_input.value : null;
if (inp_value === null)
return;
if (this.ksearch_pane && this.ksearch_pane.is(":visible"))
this.ksearch_pane.hide();
// get string from current cursor pos to last comma
var cpos = this.get_caret_pos(this.ksearch_input),
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;
// trim query string
q = $.trim(q);
// Don't (re-)search if the last results are still active
if (q == this.ksearch_value)
return;
this.ksearch_destroy();
if (q.length && q.length < min) {
if (!this.ksearch_info) {
this.ksearch_info = this.display_message(
this.get_label('autocompletechars').replace('$min', min));
}
return;
}
var old_value = this.ksearch_value;
this.ksearch_value = q;
// ...string is empty
if (!q.length)
return;
// ...new search value contains old one and previous search was not finished or its result was empty
if (old_value && old_value.length && q.indexOf(old_value) == 0 && (!ac || ac.num <= 0) && this.env.contacts && !this.env.contacts.length)
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);
this.ksearch_data.locks.push(lock);
this.ksearch_data.requests.push(xhr);
}
};
this.ksearch_query_results = function(results, search, reqid)
{
// search stopped in meantime?
if (!this.ksearch_value)
return;
// ignore this outdated search response
if (this.ksearch_input && search != this.ksearch_value)
return;
// display search results
var i, len, ul, li, text, init,
value = this.ksearch_value,
data = this.ksearch_data,
maxlen = this.env.autocomplete_max ? this.env.autocomplete_max : 15;
// create results pane if not present
if (!this.ksearch_pane) {
ul = $('<ul>');
this.ksearch_pane = $('<div>').attr('id', 'rcmKSearchpane')
.css({ position:'absolute', 'z-index':30000 }).append(ul).appendTo(document.body);
this.ksearch_pane.__ul = ul[0];
}
ul = this.ksearch_pane.__ul;
// remove all search results or add to existing list if parallel search
if (reqid && this.ksearch_pane.data('reqid') == reqid) {
maxlen -= ul.childNodes.length;
}
else {
this.ksearch_pane.data('reqid', reqid);
init = 1;
// reset content
ul.innerHTML = '';
this.env.contacts = [];
// move the results pane right under the input box
var pos = $(this.ksearch_input).offset();
this.ksearch_pane.css({ left:pos.left+'px', top:(pos.top + this.ksearch_input.offsetHeight)+'px', display: 'none'});
}
// add each result line to list
if (results && (len = results.length)) {
for (i=0; i < len && maxlen > 0; i++) {
text = typeof results[i] === 'object' ? results[i].name : results[i];
li = document.createElement('LI');
li.innerHTML = text.replace(new RegExp('('+RegExp.escape(value)+')', 'ig'), '##$1%%').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/##([^%]+)%%/g, '<b>$1</b>');
li.onmouseover = function(){ ref.ksearch_select(this); };
li.onmouseup = function(){ ref.ksearch_click(this) };
li._rcm_id = this.env.contacts.length + i;
ul.appendChild(li);
maxlen -= 1;
}
}
if (ul.childNodes.length) {
this.ksearch_pane.show();
// select the first
if (!this.env.contacts.length) {
$('li:first', ul).attr('id', 'rcmksearchSelected').addClass('selected');
this.ksearch_selected = 0;
}
}
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();
}
}
};
this.ksearch_click = function(node)
{
if (this.ksearch_input)
this.ksearch_input.focus();
this.insert_recipient(node._rcm_id);
this.ksearch_hide();
};
this.ksearch_blur = function()
{
if (this.ksearch_timer)
clearTimeout(this.ksearch_timer);
this.ksearch_input = null;
this.ksearch_hide();
};
this.ksearch_hide = function()
{
this.ksearch_selected = null;
this.ksearch_value = '';
if (this.ksearch_pane)
this.ksearch_pane.hide();
this.ksearch_destroy();
};
// Clears autocomplete data/requests
this.ksearch_destroy = function()
{
this.ksearch_abort();
if (this.ksearch_info)
this.hide_message(this.ksearch_info);
if (this.ksearch_msg)
this.hide_message(this.ksearch_msg);
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]});
};
/*********************************************************/
/********* address book methods *********/
/*********************************************************/
this.contactlist_keypress = function(list)
{
if (list.key_pressed == list.DELETE_KEY)
this.command('delete');
};
this.contactlist_select = function(list)
{
if (this.preview_timer)
clearTimeout(this.preview_timer);
var n, id, sid, ref = this, writable = false,
source = this.env.source ? this.env.address_sources[this.env.source] : null;
// we don't have dblclick handler here, so use 200 instead of this.dblclick_time
if (id = list.get_single_selection())
this.preview_timer = setTimeout(function(){ ref.load_contact(id, 'show'); }, 200);
else if (this.env.contentframe)
this.show_contentframe(false);
if (list.selection.length) {
// no source = search result, we'll need to detect if any of
// selected contacts are in writable addressbook to enable edit/delete
// we'll also need to know sources used in selection for copy
// and group-addmember operations (drag&drop)
this.env.selection_sources = [];
if (!source) {
for (n in list.selection) {
sid = String(list.selection[n]).replace(/^[^-]+-/, '');
if (sid && this.env.address_sources[sid]) {
writable = writable || !this.env.address_sources[sid].readonly;
this.env.selection_sources.push(sid);
}
}
this.env.selection_sources = $.unique(this.env.selection_sources);
}
else {
this.env.selection_sources.push(this.env.source);
writable = !source.readonly;
}
}
// if a group is currently selected, and there is at least one contact selected
// thend we can enable the group-remove-selected command
this.enable_command('group-remove-selected', this.env.group && list.selection.length > 0);
this.enable_command('compose', this.env.group || list.selection.length > 0);
this.enable_command('export-selected', list.selection.length > 0);
this.enable_command('edit', id && writable);
this.enable_command('delete', list.selection.length && writable);
return false;
};
this.list_contacts = function(src, group, page)
{
var win, folder, url = {},
target = window;
if (!src)
src = this.env.source;
if (page && this.current_page == page && src == this.env.source && group == this.env.group)
return false;
if (src != this.env.source) {
page = this.env.current_page = 1;
this.reset_qsearch();
}
else if (group != this.env.group)
page = this.env.current_page = 1;
if (this.env.search_id)
folder = 'S'+this.env.search_id;
else if (!this.env.search_request)
folder = group ? 'G'+src+group : src;
this.select_folder(folder, '', true);
this.env.source = src;
this.env.group = group;
// load contacts remotely
if (this.gui_objects.contactslist) {
this.list_contacts_remote(src, group, page);
return;
}
if (win = this.get_frame_window(this.env.contentframe)) {
target = win;
url._framed = 1;
}
if (group)
url._gid = group;
if (page)
url._page = page;
if (src)
url._source = src;
// also send search request to get the correct listing
if (this.env.search_request)
url._search = this.env.search_request;
this.set_busy(true, 'loading');
this.location_href(url, target);
};
// send remote request to load contacts list
this.list_contacts_remote = function(src, group, page)
{
// clear message list first
this.list_contacts_clear();
// send request to server
var url = {}, lock = this.set_busy(true, 'loading');
if (src)
url._source = src;
if (page)
url._page = page;
if (group)
url._gid = group;
this.env.source = src;
this.env.group = group;
// also send search request to get the right records
if (this.env.search_request)
url._search = this.env.search_request;
this.http_request(this.env.task == 'mail' ? 'list-contacts' : 'list', url, lock);
};
this.list_contacts_clear = function()
{
this.contact_list.clear(true);
this.show_contentframe(false);
this.enable_command('delete', false);
this.enable_command('compose', this.env.group ? true : false);
};
// load contact record
this.load_contact = function(cid, action, framed)
{
var win, url = {}, target = window;
if (win = this.get_frame_window(this.env.contentframe)) {
url._framed = 1;
target = win;
this.show_contentframe(true);
// load dummy content, unselect selected row(s)
if (!cid)
this.contact_list.clear_selection();
this.enable_command('delete', 'compose', 'export-selected', cid);
}
else if (framed)
return false;
if (action && (cid || action=='add') && !this.drag_active) {
if (this.env.group)
url._gid = this.env.group;
url._action = action;
url._source = this.env.source;
url._cid = cid;
this.location_href(url, target, true);
}
return true;
};
// add/delete member to/from the group
this.group_member_change = function(what, cid, source, gid)
{
what = what == 'add' ? 'add' : 'del';
var label = this.get_label(what == 'add' ? 'addingmember' : 'removingmember'),
lock = this.display_message(label, 'loading'),
post_data = {_cid: cid, _source: source, _gid: gid};
this.http_post('group-'+what+'members', post_data, lock);
};
// copy a contact to the specified target (group or directory)
this.copy_contact = function(cid, to)
{
var n, dest = to.type == 'group' ? to.source : to.id,
source = this.env.source,
group = this.env.group ? this.env.group : '';
if (!cid)
cid = this.contact_list.get_selection().join(',');
if (!cid || !this.env.address_sources[dest] || this.env.address_sources[dest].readonly)
return;
// search result may contain contacts from many sources, but if there is only one...
if (source == '' && this.env.selection_sources.length == 1)
source = this.env.selection_sources[0];
// tagret is a group
if (to.type == 'group') {
if (dest == source)
this.group_member_change('add', cid, dest, to.id);
else {
var lock = this.display_message(this.get_label('copyingcontact'), 'loading'),
post_data = {_cid: cid, _source: this.env.source, _to: dest, _togid: to.id, _gid: group};
this.http_post('copy', post_data, lock);
}
}
// target is an addressbook
else if (to.id != source) {
var lock = this.display_message(this.get_label('copyingcontact'), 'loading'),
post_data = {_cid: cid, _source: this.env.source, _to: to.id, _gid: group};
this.http_post('copy', post_data, lock);
}
};
this.delete_contacts = function()
{
var selection = this.contact_list.get_selection(),
undelete = this.env.source && this.env.address_sources[this.env.source].undelete;
// exit if no mailbox specified or if selection is empty
if (!(selection.length || this.env.cid) || (!undelete && !confirm(this.get_label('deletecontactconfirm'))))
return;
var id, n, a_cids = [],
post_data = {_source: this.env.source, _from: (this.env.action ? this.env.action : '')},
lock = this.display_message(this.get_label('contactdeleting'), 'loading');
if (this.env.cid)
a_cids.push(this.env.cid);
else {
for (n=0; n<selection.length; n++) {
id = selection[n];
a_cids.push(id);
this.contact_list.remove_row(id, (n == selection.length-1));
}
// hide content frame if we delete the currently displayed contact
if (selection.length == 1)
this.show_contentframe(false);
}
post_data._cid = a_cids.join(',');
if (this.env.group)
post_data._gid = this.env.group;
// also send search request to get the right records from the next page
if (this.env.search_request)
post_data._search = this.env.search_request;
// send request to server
this.http_post('delete', post_data, lock)
return true;
};
// update a contact record in the list
this.update_contact_row = function(cid, cols_arr, newcid, source)
{
var c, row, list = this.contact_list;
cid = this.html_identifier(cid);
// when in searching mode, concat cid with the source name
if (!list.rows[cid]) {
cid = cid+'-'+source;
if (newcid)
newcid = newcid+'-'+source;
}
list.update_row(cid, cols_arr, newcid, true);
};
// add row to contacts list
this.add_contact_row = function(cid, cols, classes)
{
if (!this.gui_objects.contactslist)
return false;
var c, col, list = this.contact_list,
row = { cols:[] };
row.id = 'rcmrow'+this.html_identifier(cid);
row.className = 'contact ' + (classes || '');
if (list.in_selection(cid))
row.className += ' selected';
// add each submitted col
for (c in cols) {
col = {};
col.className = String(c).toLowerCase();
col.innerHTML = cols[c];
row.cols.push(col);
}
list.insert_row(row);
this.enable_command('export', list.rowcount > 0);
};
this.init_contact_form = function()
{
var ref = this, col;
if (this.env.coltypes) {
this.set_photo_actions($('#ff_photo').val());
for (col in this.env.coltypes)
this.init_edit_field(col, null);
}
$('.contactfieldgroup .row a.deletebutton').click(function() {
ref.delete_edit_field(this);
return false;
});
$('select.addfieldmenu').change(function(e) {
ref.insert_edit_field($(this).val(), $(this).attr('rel'), this);
this.selectedIndex = 0;
});
// enable date pickers on date fields
if ($.datepicker && this.env.date_format) {
$.datepicker.setDefaults({
dateFormat: this.env.date_format,
changeMonth: true,
changeYear: true,
yearRange: '-100:+10',
showOtherMonths: true,
selectOtherMonths: true,
onSelect: function(dateText) { $(this).focus().val(dateText) }
});
$('input.datepicker').datepicker();
}
$("input[type='text']:visible").first().focus();
// Submit search form on Enter
if (this.env.action == 'search')
$(this.gui_objects.editform).append($('<input type="submit">').hide())
.submit(function() { $('input.mainaction').click(); return false; });
};
this.group_create = function()
{
this.add_input_row('contactgroup');
};
this.group_rename = function()
{
if (!this.env.group || !this.gui_objects.folderlist)
return;
if (!this.name_input) {
this.enable_command('list', 'listgroup', false);
this.name_input = $('<input>').attr('type', 'text').val(this.env.contactgroups['G'+this.env.source+this.env.group].name);
this.name_input.bind('keydown', function(e){ return rcmail.add_input_keydown(e); });
this.env.group_renaming = true;
var link, li = this.get_folder_li('G'+this.env.source+this.env.group,'',true);
if (li && (link = li.firstChild)) {
$(link).hide().before(this.name_input);
}
}
this.name_input.select().focus();
};
this.group_delete = function()
{
if (this.env.group && confirm(this.get_label('deletegroupconfirm'))) {
var lock = this.set_busy(true, 'groupdeleting');
this.http_post('group-delete', {_source: this.env.source, _gid: this.env.group}, lock);
}
};
// callback from server upon group-delete command
this.remove_group_item = function(prop)
{
var key = 'G'+prop.source+prop.id;
if (this.treelist.remove(key)) {
delete this.env.contactfolders[key];
delete this.env.contactgroups[key];
}
this.list_contacts(prop.source, 0);
};
// @TODO: maybe it would be better to use popup instead of inserting input to the list?
this.add_input_row = function(type)
{
if (!this.gui_objects.folderlist)
return;
if (!this.name_input) {
this.name_input = $('<input>').attr('type', 'text').data('tt', type);
this.name_input.bind('keydown', function(e){ return rcmail.add_input_keydown(e); });
this.name_input_li = $('<li>').addClass(type).append(this.name_input);
var ul, li;
// find list (UL) element
if (type == 'contactsearch')
ul = this.gui_objects.folderlist;
else
ul = $('ul.groups', this.get_folder_li(this.env.source,'',true));
// append to the list
li = $('li:last', ul);
if (li.length)
this.name_input_li.insertAfter(li);
else {
this.name_input_li.appendTo(ul);
ul.show(); // make sure the list is visible
}
}
this.name_input.select().focus();
};
//remove selected contacts from current active group
this.group_remove_selected = function()
{
ref.http_post('group-delmembers', {_cid: this.contact_list.selection,
_source: this.env.source, _gid: this.env.group});
};
//callback after deleting contact(s) from current group
this.remove_group_contacts = function(props)
{
if('undefined' != typeof this.env.group && (this.env.group === props.gid)){
var n, selection = this.contact_list.get_selection();
for (n=0; n<selection.length; n++) {
id = selection[n];
this.contact_list.remove_row(id, (n == selection.length-1));
}
}
}
// handler for keyboard events on the input field
this.add_input_keydown = function(e)
{
var key = rcube_event.get_keycode(e),
input = $(e.target), itype = input.data('tt');
// enter
if (key == 13) {
var newname = input.val();
if (newname) {
var lock = this.set_busy(true, 'loading');
if (itype == 'contactsearch')
this.http_post('search-create', {_search: this.env.search_request, _name: newname}, lock);
else if (this.env.group_renaming)
this.http_post('group-rename', {_source: this.env.source, _gid: this.env.group, _name: newname}, lock);
else
this.http_post('group-create', {_source: this.env.source, _name: newname}, lock);
}
return false;
}
// escape
else if (key == 27)
this.reset_add_input();
return true;
};
this.reset_add_input = function()
{
if (this.name_input) {
var li = this.name_input.parent();
if (this.env.group_renaming) {
li.children().last().show();
this.env.group_renaming = false;
}
else if ($('li', li.parent()).length == 1)
li.parent().hide();
this.name_input.remove();
if (this.name_input_li)
this.name_input_li.remove();
this.name_input = this.name_input_li = null;
}
this.enable_command('list', 'listgroup', true);
};
// callback for creating a new contact group
this.insert_contact_group = function(prop)
{
this.reset_add_input();
prop.type = 'group';
var key = 'G'+prop.source+prop.id,
link = $('<a>').attr('href', '#')
.attr('rel', prop.source+':'+prop.id)
.click(function() { return rcmail.command('listgroup', prop, this); })
.html(prop.name);
this.env.contactfolders[key] = this.env.contactgroups[key] = prop;
this.treelist.insert({ id:key, html:link, classes:['contactgroup'] }, prop.source, true);
this.triggerEvent('group_insert', { id:prop.id, source:prop.source, name:prop.name, li:this.treelist.get_item(key) });
};
// callback for renaming a contact group
this.update_contact_group = function(prop)
{
this.reset_add_input();
var key = 'G'+prop.source+prop.id,
newnode = {};
// group ID has changed, replace link node and identifiers
if (prop.newid) {
var newkey = 'G'+prop.source+prop.newid,
newprop = $.extend({}, prop);
this.env.contactfolders[newkey] = this.env.contactfolders[key];
this.env.contactfolders[newkey].id = prop.newid;
this.env.group = prop.newid;
delete this.env.contactfolders[key];
delete this.env.contactgroups[key];
newprop.id = prop.newid;
newprop.type = 'group';
newnode.id = newkey;
newnode.html = $('<a>').attr('href', '#')
.attr('rel', prop.source+':'+prop.newid)
.click(function() { return rcmail.command('listgroup', newprop, this); })
.html(prop.name);
}
// update displayed group name
else {
$(this.treelist.get_item(key)).children().first().html(prop.name);
this.env.contactfolders[key].name = this.env.contactgroups[key].name = prop.name;
}
// update list node and re-sort it
this.treelist.update(key, newnode, true);
this.triggerEvent('group_update', { id:prop.id, source:prop.source, name:prop.name, li:this.treelist.get_item(key), newid:prop.newid });
};
this.update_group_commands = function()
{
var source = this.env.source != '' ? this.env.address_sources[this.env.source] : null;
this.enable_command('group-create', (source && source.groups && !source.readonly));
this.enable_command('group-rename', 'group-delete', (source && source.groups && this.env.group && !source.readonly));
};
this.init_edit_field = function(col, elem)
{
var label = this.env.coltypes[col].label;
if (!elem)
elem = $('.ff_' + col);
if (label)
elem.placeholder(label);
};
this.insert_edit_field = function(col, section, menu)
{
// just make pre-defined input field visible
var elem = $('#ff_'+col);
if (elem.length) {
elem.show().focus();
$(menu).children('option[value="'+col+'"]').prop('disabled', true);
}
else {
var lastelem = $('.ff_'+col),
appendcontainer = $('#contactsection'+section+' .contactcontroller'+col);
if (!appendcontainer.length) {
var sect = $('#contactsection'+section),
lastgroup = $('.contactfieldgroup', sect).last();
appendcontainer = $('<fieldset>').addClass('contactfieldgroup contactcontroller'+col);
if (lastgroup.length)
appendcontainer.insertAfter(lastgroup);
else
sect.prepend(appendcontainer);
}
if (appendcontainer.length && appendcontainer.get(0).nodeName == 'FIELDSET') {
var input, colprop = this.env.coltypes[col],
row = $('<div>').addClass('row'),
cell = $('<div>').addClass('contactfieldcontent data'),
label = $('<div>').addClass('contactfieldlabel label');
if (colprop.subtypes_select)
label.html(colprop.subtypes_select);
else
label.html(colprop.label);
var name_suffix = colprop.limit != 1 ? '[]' : '';
if (colprop.type == 'text' || colprop.type == 'date') {
input = $('<input>')
.addClass('ff_'+col)
.attr({type: 'text', name: '_'+col+name_suffix, size: colprop.size})
.appendTo(cell);
this.init_edit_field(col, input);
if (colprop.type == 'date' && $.datepicker)
input.datepicker();
}
else if (colprop.type == 'textarea') {
input = $('<textarea>')
.addClass('ff_'+col)
.attr({ name: '_'+col+name_suffix, cols:colprop.size, rows:colprop.rows })
.appendTo(cell);
this.init_edit_field(col, input);
}
else if (colprop.type == 'composite') {
var childcol, cp, first, templ, cols = [], suffices = [];
// read template for composite field order
if ((templ = this.env[col+'_template'])) {
for (var j=0; j < templ.length; j++) {
cols.push(templ[j][1]);
suffices.push(templ[j][2]);
}
}
else { // list fields according to appearance in colprop
for (childcol in colprop.childs)
cols.push(childcol);
}
for (var i=0; i < cols.length; i++) {
childcol = cols[i];
cp = colprop.childs[childcol];
input = $('<input>')
.addClass('ff_'+childcol)
.attr({ type: 'text', name: '_'+childcol+name_suffix, size: cp.size })
.appendTo(cell);
cell.append(suffices[i] || " ");
this.init_edit_field(childcol, input);
if (!first) first = input;
}
input = first; // set focus to the first of this composite fields
}
else if (colprop.type == 'select') {
input = $('<select>')
.addClass('ff_'+col)
.attr('name', '_'+col+name_suffix)
.appendTo(cell);
var options = input.attr('options');
options[options.length] = new Option('---', '');
if (colprop.options)
$.each(colprop.options, function(i, val){ options[options.length] = new Option(val, i); });
}
if (input) {
var delbutton = $('<a href="#del"></a>')
.addClass('contactfieldbutton deletebutton')
.attr({title: this.get_label('delete'), rel: col})
.html(this.env.delbutton)
.click(function(){ ref.delete_edit_field(this); return false })
.appendTo(cell);
row.append(label).append(cell).appendTo(appendcontainer.show());
input.first().focus();
// disable option if limit reached
if (!colprop.count) colprop.count = 0;
if (++colprop.count == colprop.limit && colprop.limit)
$(menu).children('option[value="'+col+'"]').prop('disabled', true);
}
}
}
};
this.delete_edit_field = function(elem)
{
var col = $(elem).attr('rel'),
colprop = this.env.coltypes[col],
fieldset = $(elem).parents('fieldset.contactfieldgroup'),
addmenu = fieldset.parent().find('select.addfieldmenu');
// just clear input but don't hide the last field
if (--colprop.count <= 0 && colprop.visible)
$(elem).parent().children('input').val('').blur();
else {
$(elem).parents('div.row').remove();
// hide entire fieldset if no more rows
if (!fieldset.children('div.row').length)
fieldset.hide();
}
// enable option in add-field selector or insert it if necessary
if (addmenu.length) {
var option = addmenu.children('option[value="'+col+'"]');
if (option.length)
option.prop('disabled', false);
else
option = $('<option>').attr('value', col).html(colprop.label).appendTo(addmenu);
addmenu.show();
}
};
this.upload_contact_photo = function(form)
{
if (form && form.elements._photo.value) {
this.async_upload_form(form, 'upload-photo', function(e) {
rcmail.set_busy(false, null, rcmail.file_upload_id);
});
// display upload indicator
this.file_upload_id = this.set_busy(true, 'uploading');
}
};
this.replace_contact_photo = function(id)
{
var img_src = id == '-del-' ? this.env.photo_placeholder :
this.env.comm_path + '&_action=photo&_source=' + this.env.source + '&_cid=' + this.env.cid + '&_photo=' + id;
this.set_photo_actions(id);
$(this.gui_objects.contactphoto).children('img').attr('src', img_src);
};
this.photo_upload_end = function()
{
this.set_busy(false, null, this.file_upload_id);
delete this.file_upload_id;
};
this.set_photo_actions = function(id)
{
var n, buttons = this.buttons['upload-photo'];
for (n=0; buttons && n < buttons.length; n++)
$('a#'+buttons[n].id).html(this.get_label(id == '-del-' ? 'addphoto' : 'replacephoto'));
$('#ff_photo').val(id);
this.enable_command('upload-photo', this.env.coltypes.photo ? true : false);
this.enable_command('delete-photo', this.env.coltypes.photo && id != '-del-');
};
// load advanced search page
this.advanced_search = function()
{
var win, url = {_form: 1, _action: 'search'}, target = window;
if (win = this.get_frame_window(this.env.contentframe)) {
url._framed = 1;
target = win;
this.contact_list.clear_selection();
}
this.location_href(url, target, true);
return true;
};
// unselect directory/group
this.unselect_directory = function()
{
this.select_folder('');
this.enable_command('search-delete', false);
};
// callback for creating a new saved search record
this.insert_saved_search = function(name, id)
{
this.reset_add_input();
var key = 'S'+id,
link = $('<a>').attr('href', '#')
.attr('rel', id)
.click(function() { return rcmail.command('listsearch', id, this); })
.html(name),
prop = { name:name, id:id };
this.treelist.insert({ id:key, html:link, classes:['contactsearch'] }, null, 'contactsearch');
this.select_folder(key,'',true);
this.enable_command('search-delete', true);
this.env.search_id = id;
this.triggerEvent('abook_search_insert', prop);
};
// creates an input for saved search name
this.search_create = function()
{
this.add_input_row('contactsearch');
};
this.search_delete = function()
{
if (this.env.search_request) {
var lock = this.set_busy(true, 'savedsearchdeleting');
this.http_post('search-delete', {_sid: this.env.search_id}, lock);
}
};
// callback from server upon search-delete command
this.remove_search_item = function(id)
{
var li, key = 'S'+id;
if (this.treelist.remove(key)) {
this.triggerEvent('search_delete', { id:id, li:li });
}
this.env.search_id = null;
this.env.search_request = null;
this.list_contacts_clear();
this.reset_qsearch();
this.enable_command('search-delete', 'search-create', false);
};
this.listsearch = function(id)
{
var folder, lock = this.set_busy(true, 'searching');
if (this.contact_list) {
this.list_contacts_clear();
}
this.reset_qsearch();
this.select_folder('S'+id, '', true);
// reset vars
this.env.current_page = 1;
this.http_request('search', {_sid: id}, lock);
};
/*********************************************************/
/********* user settings methods *********/
/*********************************************************/
// preferences section select and load options frame
this.section_select = function(list)
{
var win, id = list.get_single_selection(), target = window,
url = {_action: 'edit-prefs', _section: id};
if (id) {
if (win = this.get_frame_window(this.env.contentframe)) {
url._framed = 1;
target = win;
}
this.location_href(url, target, true);
}
return true;
};
this.identity_select = function(list)
{
var id;
if (id = list.get_single_selection()) {
this.enable_command('delete', list.rowcount > 1 && this.env.identities_level < 2);
this.load_identity(id, 'edit-identity');
}
};
// load identity record
this.load_identity = function(id, action)
{
if (action == 'edit-identity' && (!id || id == this.env.iid))
return false;
var win, target = window,
url = {_action: action, _iid: id};
if (win = this.get_frame_window(this.env.contentframe)) {
url._framed = 1;
target = win;
}
if (action && (id || action == 'add-identity')) {
this.set_busy(true);
this.location_href(url, target);
}
return true;
};
this.delete_identity = function(id)
{
// exit if no identity is specified or if selection is empty
var selection = this.identity_list.get_selection();
if (!(selection.length || this.env.iid))
return;
if (!id)
id = this.env.iid ? this.env.iid : selection[0];
// submit request with appended token
if (confirm(this.get_label('deleteidentityconfirm')))
this.goto_url('delete-identity', { _iid: id, _token: this.env.request_token }, true);
return true;
};
this.update_identity_row = function(id, name, add)
{
var list = this.identity_list,
rid = this.html_identifier(id);
if (add) {
list.insert_row({ id:'rcmrow'+rid, cols:[ { className:'mail', innerHTML:name } ] });
list.select(rid);
}
else {
list.update_row(rid, [ name ]);
}
};
/*********************************************************/
/********* folder manager methods *********/
/*********************************************************/
this.init_subscription_list = function()
{
var p = this;
this.subscription_list = new rcube_list_widget(this.gui_objects.subscriptionlist,
{multiselect:false, draggable:true, keyboard:false, toggleselect:true});
this.subscription_list.addEventListener('select', function(o){ p.subscription_select(o); });
this.subscription_list.addEventListener('dragstart', function(o){ p.drag_active = true; });
this.subscription_list.addEventListener('dragend', function(o){ p.subscription_move_folder(o); });
this.subscription_list.row_init = function (row) {
row.obj.onmouseover = function() { p.focus_subscription(row.id); };
row.obj.onmouseout = function() { p.unfocus_subscription(row.id); };
};
this.subscription_list.init();
$('#mailboxroot')
.mouseover(function(){ p.focus_subscription(this.id); })
.mouseout(function(){ p.unfocus_subscription(this.id); })
};
this.focus_subscription = function(id)
{
var row, folder,
delim = RegExp.escape(this.env.delimiter),
reg = RegExp('['+delim+']?[^'+delim+']+$');
if (this.drag_active && this.env.mailbox && (row = document.getElementById(id)))
if (this.env.subscriptionrows[id] &&
(folder = this.env.subscriptionrows[id][0]) !== null
) {
if (this.check_droptarget(folder) &&
!this.env.subscriptionrows[this.get_folder_row_id(this.env.mailbox)][2] &&
(folder != this.env.mailbox.replace(reg, '')) &&
(!folder.match(new RegExp('^'+RegExp.escape(this.env.mailbox+this.env.delimiter))))
) {
this.env.dstfolder = folder;
$(row).addClass('droptarget');
}
}
};
this.unfocus_subscription = function(id)
{
var row = $('#'+id);
this.env.dstfolder = null;
if (this.env.subscriptionrows[id] && row[0])
row.removeClass('droptarget');
else
$(this.subscription_list.frame).removeClass('droptarget');
};
this.subscription_select = function(list)
{
var id, folder;
if (list && (id = list.get_single_selection()) &&
(folder = this.env.subscriptionrows['rcmrow'+id])
) {
this.env.mailbox = folder[0];
this.show_folder(folder[0]);
this.enable_command('delete-folder', !folder[2]);
}
else {
this.env.mailbox = null;
this.show_contentframe(false);
this.enable_command('delete-folder', 'purge', false);
}
};
this.subscription_move_folder = function(list)
{
var delim = RegExp.escape(this.env.delimiter),
reg = RegExp('['+delim+']?[^'+delim+']+$');
if (this.env.mailbox && this.env.dstfolder !== null && (this.env.dstfolder != this.env.mailbox) &&
(this.env.dstfolder != this.env.mailbox.replace(reg, ''))
) {
reg = new RegExp('[^'+delim+']*['+delim+']', 'g');
var basename = this.env.mailbox.replace(reg, ''),
newname = this.env.dstfolder === '' ? basename : this.env.dstfolder+this.env.delimiter+basename;
if (newname != this.env.mailbox) {
this.http_post('rename-folder', {_folder_oldname: this.env.mailbox, _folder_newname: newname}, this.set_busy(true, 'foldermoving'));
this.subscription_list.draglayer.hide();
}
}
this.drag_active = false;
this.unfocus_subscription(this.get_folder_row_id(this.env.dstfolder));
};
// tell server to create and subscribe a new mailbox
this.create_folder = function()
{
this.show_folder('', this.env.mailbox);
};
// delete a specific mailbox with all its messages
this.delete_folder = function(name)
{
var id = this.get_folder_row_id(name ? name : this.env.mailbox),
folder = this.env.subscriptionrows[id][0];
if (folder && confirm(this.get_label('deletefolderconfirm'))) {
var lock = this.set_busy(true, 'folderdeleting');
this.http_post('delete-folder', {_mbox: folder}, lock);
}
};
// Add folder row to the table and initialize it
this.add_folder_row = function (name, display_name, is_protected, subscribed, skip_init, class_name)
{
if (!this.gui_objects.subscriptionlist)
return false;
var row, n, i, tmp, tmp_name, folders, rowid, list = [], slist = [],
tbody = this.gui_objects.subscriptionlist.tBodies[0],
refrow = $('tr', tbody).get(1),
id = 'rcmrow'+((new Date).getTime());
if (!refrow) {
// Refresh page if we don't have a table row to clone
this.goto_url('folders');
return false;
}
// clone a table row if there are existing rows
row = $(refrow).clone(true);
// set ID, reset css class
row.attr('id', id);
row.attr('class', class_name);
// set folder name
row.find('td:first').html(display_name);
// update subscription checkbox
$('input[name="_subscribed[]"]', row).val(name)
.prop({checked: subscribed ? true : false, disabled: is_protected ? true : false});
// add to folder/row-ID map
this.env.subscriptionrows[id] = [name, display_name, 0];
// sort folders, to find a place where to insert the row
folders = [];
$.each(this.env.subscriptionrows, function(k,v){ folders.push(v) });
folders.sort(function(a,b){ return a[0] < b[0] ? -1 : (a[0] > b[0] ? 1 : 0) });
for (n in folders) {
// protected folder
if (folders[n][2]) {
tmp_name = folders[n][0] + this.env.delimiter;
// prefix namespace cannot have subfolders (#1488349)
if (tmp_name == this.env.prefix_ns)
continue;
slist.push(folders[n][0]);
tmp = tmp_name;
}
// protected folder's child
else if (tmp && folders[n][0].indexOf(tmp) == 0)
slist.push(folders[n][0]);
// other
else {
list.push(folders[n][0]);
tmp = null;
}
}
// check if subfolder of a protected folder
for (n=0; n<slist.length; n++) {
if (name.indexOf(slist[n]+this.env.delimiter) == 0)
rowid = this.get_folder_row_id(slist[n]);
}
// find folder position after sorting
for (n=0; !rowid && n<list.length; n++) {
if (n && list[n] == name)
rowid = this.get_folder_row_id(list[n-1]);
}
// add row to the table
if (rowid)
$('#'+rowid).after(row);
else
row.appendTo(tbody);
// update list widget
this.subscription_list.clear_selection();
if (!skip_init)
this.init_subscription_list();
row = row.get(0);
if (row.scrollIntoView)
row.scrollIntoView();
return row;
};
// replace an existing table row with a new folder line (with subfolders)
this.replace_folder_row = function(oldfolder, newfolder, display_name, is_protected, class_name)
{
if (!this.gui_objects.subscriptionlist)
return false;
var i, n, len, name, dispname, oldrow, tmprow, row, level,
tbody = this.gui_objects.subscriptionlist.tBodies[0],
folders = this.env.subscriptionrows,
id = this.get_folder_row_id(oldfolder),
regex = new RegExp('^'+RegExp.escape(oldfolder)),
subscribed = $('input[name="_subscribed[]"]', $('#'+id)).prop('checked'),
// find subfolders of renamed folder
list = this.get_subfolders(oldfolder);
// replace an existing table row
this._remove_folder_row(id);
row = $(this.add_folder_row(newfolder, display_name, is_protected, subscribed, true, class_name));
// detect tree depth change
if (len = list.length) {
level = (oldfolder.split(this.env.delimiter)).length - (newfolder.split(this.env.delimiter)).length;
}
// move subfolders to the new branch
for (n=0; n<len; n++) {
id = list[n];
name = this.env.subscriptionrows[id][0];
dispname = this.env.subscriptionrows[id][1];
oldrow = $('#'+id);
tmprow = oldrow.clone(true);
oldrow.remove();
row.after(tmprow);
row = tmprow;
// update folder index
name = name.replace(regex, newfolder);
$('input[name="_subscribed[]"]', row).val(name);
this.env.subscriptionrows[id][0] = name;
// update the name if level is changed
if (level != 0) {
if (level > 0) {
for (i=level; i>0; i--)
dispname = dispname.replace(/^&nbsp;&nbsp;&nbsp;&nbsp;/, '');
}
else {
for (i=level; i<0; i++)
dispname = '&nbsp;&nbsp;&nbsp;&nbsp;' + dispname;
}
row.find('td:first').html(dispname);
this.env.subscriptionrows[id][1] = dispname;
}
}
// update list widget
this.init_subscription_list();
};
// remove the table row of a specific mailbox from the table
this.remove_folder_row = function(folder, subs)
{
var n, len, list = [], id = this.get_folder_row_id(folder);
// get subfolders if any
if (subs)
list = this.get_subfolders(folder);
// remove old row
this._remove_folder_row(id);
// remove subfolders
for (n=0, len=list.length; n<len; n++)
this._remove_folder_row(list[n]);
};
this._remove_folder_row = function(id)
{
this.subscription_list.remove_row(id.replace(/^rcmrow/, ''));
$('#'+id).remove();
delete this.env.subscriptionrows[id];
}
this.get_subfolders = function(folder)
{
var name, list = [],
regex = new RegExp('^'+RegExp.escape(folder)+RegExp.escape(this.env.delimiter)),
row = $('#'+this.get_folder_row_id(folder)).get(0);
while (row = row.nextSibling) {
if (row.id) {
name = this.env.subscriptionrows[row.id][0];
if (regex.test(name)) {
list.push(row.id);
}
else
break;
}
}
return list;
}
this.subscribe = function(folder)
{
if (folder) {
var lock = this.display_message(this.get_label('foldersubscribing'), 'loading');
this.http_post('subscribe', {_mbox: folder}, lock);
}
};
this.unsubscribe = function(folder)
{
if (folder) {
var lock = this.display_message(this.get_label('folderunsubscribing'), 'loading');
this.http_post('unsubscribe', {_mbox: folder}, lock);
}
};
// helper method to find a specific mailbox row ID
this.get_folder_row_id = function(folder)
{
var id, folders = this.env.subscriptionrows;
for (id in folders)
if (folders[id] && folders[id][0] == folder)
break;
return id;
};
// when user select a folder in manager
this.show_folder = function(folder, path, force)
{
var win, target = window,
url = '&_action=edit-folder&_mbox='+urlencode(folder);
if (path)
url += '&_path='+urlencode(path);
if (win = this.get_frame_window(this.env.contentframe)) {
target = win;
url += '&_framed=1';
}
if (String(target.location.href).indexOf(url) >= 0 && !force)
this.show_contentframe(true);
else
this.location_href(this.env.comm_path+url, target, true);
};
// disables subscription checkbox (for protected folder)
this.disable_subscription = function(folder)
{
var id = this.get_folder_row_id(folder);
if (id)
$('input[name="_subscribed[]"]', $('#'+id)).prop('disabled', true);
};
this.folder_size = function(folder)
{
var lock = this.set_busy(true, 'loading');
this.http_post('folder-size', {_mbox: folder}, lock);
};
this.folder_size_update = function(size)
{
$('#folder-size').replaceWith(size);
};
/*********************************************************/
/********* GUI functionality *********/
/*********************************************************/
var init_button = function(cmd, prop)
{
var elm = document.getElementById(prop.id);
if (!elm)
return;
var preload = false;
if (prop.type == 'image') {
elm = elm.parentNode;
preload = true;
}
elm._command = cmd;
elm._id = prop.id;
if (prop.sel) {
elm.onmousedown = function(e){ return rcmail.button_sel(this._command, this._id); };
elm.onmouseup = function(e){ return rcmail.button_out(this._command, this._id); };
if (preload)
new Image().src = prop.sel;
}
if (prop.over) {
elm.onmouseover = function(e){ return rcmail.button_over(this._command, this._id); };
elm.onmouseout = function(e){ return rcmail.button_out(this._command, this._id); };
if (preload)
new Image().src = prop.over;
}
};
// set event handlers on registered buttons
this.init_buttons = function()
{
for (var cmd in this.buttons) {
if (typeof cmd !== 'string')
continue;
for (var i=0; i<this.buttons[cmd].length; i++) {
init_button(cmd, this.buttons[cmd][i]);
}
}
// set active task button
this.set_button(this.task, 'sel');
};
// set button to a specific state
this.set_button = function(command, state)
{
var n, button, obj, a_buttons = this.buttons[command],
len = a_buttons ? a_buttons.length : 0;
for (n=0; n<len; n++) {
button = a_buttons[n];
obj = document.getElementById(button.id);
if (!obj)
continue;
// get default/passive setting of the button
if (button.type == 'image' && !button.status) {
button.pas = obj._original_src ? obj._original_src : obj.src;
// respect PNG fix on IE browsers
if (obj.runtimeStyle && obj.runtimeStyle.filter && obj.runtimeStyle.filter.match(/src=['"]([^'"]+)['"]/))
button.pas = RegExp.$1;
}
else if (!button.status)
button.pas = String(obj.className);
// set image according to button state
if (button.type == 'image' && button[state]) {
button.status = state;
obj.src = button[state];
}
// set class name according to button state
else if (button[state] !== undefined) {
button.status = state;
obj.className = button[state];
}
// disable/enable input buttons
if (button.type == 'input') {
button.status = state;
obj.disabled = !state;
}
}
};
// display a specific alttext
this.set_alttext = function(command, label)
{
var n, button, obj, link, a_buttons = this.buttons[command],
len = a_buttons ? a_buttons.length : 0;
for (n=0; n<len; n++) {
button = a_buttons[n];
obj = document.getElementById(button.id);
if (button.type == 'image' && obj) {
obj.setAttribute('alt', this.get_label(label));
if ((link = obj.parentNode) && link.tagName.toLowerCase() == 'a')
link.setAttribute('title', this.get_label(label));
}
else if (obj)
obj.setAttribute('title', this.get_label(label));
}
};
// mouse over button
this.button_over = function(command, id)
{
var n, button, obj, a_buttons = this.buttons[command],
len = a_buttons ? a_buttons.length : 0;
for (n=0; n<len; n++) {
button = a_buttons[n];
if (button.id == id && button.status == 'act') {
obj = document.getElementById(button.id);
if (obj && button.over) {
if (button.type == 'image')
obj.src = button.over;
else
obj.className = button.over;
}
}
}
};
// mouse down on button
this.button_sel = function(command, id)
{
var n, button, obj, a_buttons = this.buttons[command],
len = a_buttons ? a_buttons.length : 0;
for (n=0; n<len; n++) {
button = a_buttons[n];
if (button.id == id && button.status == 'act') {
obj = document.getElementById(button.id);
if (obj && button.sel) {
if (button.type == 'image')
obj.src = button.sel;
else
obj.className = button.sel;
}
this.buttons_sel[id] = command;
}
}
};
// mouse out of button
this.button_out = function(command, id)
{
var n, button, obj, a_buttons = this.buttons[command],
len = a_buttons ? a_buttons.length : 0;
for (n=0; n<len; n++) {
button = a_buttons[n];
if (button.id == id && button.status == 'act') {
obj = document.getElementById(button.id);
if (obj && button.act) {
if (button.type == 'image')
obj.src = button.act;
else
obj.className = button.act;
}
}
}
};
// write to the document/window title
this.set_pagetitle = function(title)
{
if (title && document.title)
document.title = title;
};
// display a system message, list of types in common.css (below #message definition)
this.display_message = function(msg, type, timeout)
{
// pass command to parent window
if (this.is_framed())
return parent.rcmail.display_message(msg, type, timeout);
if (!this.gui_objects.message) {
// save message in order to display after page loaded
if (type != 'loading')
this.pending_message = [msg, type, timeout];
return 1;
}
type = type ? type : 'notice';
var ref = this,
key = this.html_identifier(msg),
date = new Date(),
id = type + date.getTime();
if (!timeout)
timeout = this.message_time * (type == 'error' || type == 'warning' ? 2 : 1);
if (type == 'loading') {
key = 'loading';
timeout = this.env.request_timeout * 1000;
if (!msg)
msg = this.get_label('loading');
}
// The same message is already displayed
if (this.messages[key]) {
// replace label
if (this.messages[key].obj)
this.messages[key].obj.html(msg);
// store label in stack
if (type == 'loading') {
this.messages[key].labels.push({'id': id, 'msg': msg});
}
// add element and set timeout
this.messages[key].elements.push(id);
setTimeout(function() { ref.hide_message(id, type == 'loading'); }, timeout);
return id;
}
// create DOM object and display it
var obj = $('<div>').addClass(type).html(msg).data('key', key),
cont = $(this.gui_objects.message).append(obj).show();
this.messages[key] = {'obj': obj, 'elements': [id]};
if (type == 'loading') {
this.messages[key].labels = [{'id': id, 'msg': msg}];
}
else {
obj.click(function() { return ref.hide_message(obj); });
}
this.triggerEvent('message', { message:msg, type:type, timeout:timeout, object:obj });
if (timeout > 0)
setTimeout(function() { ref.hide_message(id, type == 'loading'); }, timeout);
return id;
};
// make a message to disapear
this.hide_message = function(obj, fade)
{
// pass command to parent window
if (this.is_framed())
return parent.rcmail.hide_message(obj, fade);
if (!this.gui_objects.message)
return;
var k, n, i, o, m = this.messages;
// Hide message by object, don't use for 'loading'!
if (typeof obj === 'object') {
o = $(obj);
k = o.data('key');
this.hide_message_object(o, fade);
if (m[k])
delete m[k];
}
// Hide message by id
else {
for (k in m) {
for (n in m[k].elements) {
if (m[k] && m[k].elements[n] == obj) {
m[k].elements.splice(n, 1);
// hide DOM element if last instance is removed
if (!m[k].elements.length) {
this.hide_message_object(m[k].obj, fade);
delete m[k];
}
// set pending action label for 'loading' message
else if (k == 'loading') {
for (i in m[k].labels) {
if (m[k].labels[i].id == obj) {
delete m[k].labels[i];
}
else {
o = m[k].labels[i].msg;
m[k].obj.html(o);
}
}
}
}
}
}
}
};
// hide message object and remove from the DOM
this.hide_message_object = function(o, fade)
{
if (fade)
o.fadeOut(600, function() {$(this).remove(); });
else
o.hide().remove();
};
// remove all messages immediately
this.clear_messages = function()
{
// pass command to parent window
if (this.is_framed())
return parent.rcmail.clear_messages();
var k, n, m = this.messages;
for (k in m)
for (n in m[k].elements)
if (m[k].obj)
this.hide_message_object(m[k].obj);
this.messages = {};
};
// open a jquery UI dialog with the given content
this.show_popup_dialog = function(html, title, buttons)
{
// forward call to parent window
if (this.is_framed()) {
parent.rcmail.show_popup_dialog(html, title, buttons);
return;
}
var popup = $('<div class="popup">')
.html(html)
.dialog({
title: title,
buttons: buttons,
modal: true,
resizable: true,
width: 500,
close: function(event, ui) { $(this).remove() }
});
// resize and center popup
var win = $(window), w = win.width(), h = win.height(),
width = popup.width(), height = popup.height();
popup.dialog('option', {
height: Math.min(h - 40, height + 75 + (buttons ? 50 : 0)),
width: Math.min(w - 20, width + 20)
});
};
// enable/disable buttons for page shifting
this.set_page_buttons = function()
{
this.enable_command('nextpage', 'lastpage', (this.env.pagecount > this.env.current_page));
this.enable_command('previouspage', 'firstpage', (this.env.current_page > 1));
};
// mark a mailbox as selected and set environment variable
this.select_folder = function(name, prefix, encode)
{
if (this.treelist) {
this.treelist.select(name);
}
else if (this.gui_objects.folderlist) {
var current_li, target_li;
if ((current_li = $('li.selected', this.gui_objects.folderlist))) {
current_li.removeClass('selected').addClass('unfocused');
}
if ((target_li = this.get_folder_li(name, prefix, encode))) {
$(target_li).removeClass('unfocused').addClass('selected');
}
// trigger event hook
this.triggerEvent('selectfolder', { folder:name, prefix:prefix });
}
};
// adds a class to selected folder
this.mark_folder = function(name, class_name, prefix, encode)
{
$(this.get_folder_li(name, prefix, encode)).addClass(class_name);
};
// adds a class to selected folder
this.unmark_folder = function(name, class_name, prefix, encode)
{
$(this.get_folder_li(name, prefix, encode)).removeClass(class_name);
};
// helper method to find a folder list item
this.get_folder_li = function(name, prefix, encode)
{
if (!prefix)
prefix = 'rcmli';
if (this.gui_objects.folderlist) {
name = this.html_identifier(name, encode);
return document.getElementById(prefix+name);
}
return null;
};
// for reordering column array (Konqueror workaround)
// and for setting some message list global variables
this.set_message_coltypes = function(coltypes, repl, smart_col)
{
var list = this.message_list,
thead = list ? list.thead : null,
cell, col, n, len, th, tr;
this.env.coltypes = coltypes;
// replace old column headers
if (thead) {
if (repl) {
th = document.createElement('thead');
tr = document.createElement('tr');
for (c=0, len=repl.length; c < len; c++) {
cell = document.createElement('td');
cell.innerHTML = repl[c].html || '';
if (repl[c].id) cell.id = repl[c].id;
if (repl[c].className) cell.className = repl[c].className;
tr.appendChild(cell);
}
th.appendChild(tr);
thead.parentNode.replaceChild(th, thead);
thead = th;
}
for (n=0, len=this.env.coltypes.length; n<len; n++) {
col = this.env.coltypes[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);
});
}
}
}
this.env.subject_col = null;
this.env.flagged_col = null;
this.env.status_col = null;
if ((n = $.inArray('subject', this.env.coltypes)) >= 0) {
this.env.subject_col = n;
if (list)
list.subject_col = n;
}
if ((n = $.inArray('flag', this.env.coltypes)) >= 0)
this.env.flagged_col = n;
if ((n = $.inArray('status', this.env.coltypes)) >= 0)
this.env.status_col = n;
if (list)
list.init_header();
};
// replace content of row count display
this.set_rowcount = function(text, mbox)
{
// #1487752
if (mbox && mbox != this.env.mailbox)
return false;
$(this.gui_objects.countdisplay).html(text);
// update page navigation buttons
this.set_page_buttons();
};
// replace content of mailboxname display
this.set_mailboxname = function(content)
{
if (this.gui_objects.mailboxname && content)
this.gui_objects.mailboxname.innerHTML = content;
};
// replace content of quota display
this.set_quota = function(content)
{
if (this.gui_objects.quotadisplay && content && content.type == 'text')
$(this.gui_objects.quotadisplay).html(content.percent+'%').attr('title', content.title);
this.triggerEvent('setquota', content);
this.env.quota_content = content;
};
// update the mailboxlist
this.set_unread_count = function(mbox, count, set_title, mark)
{
if (!this.gui_objects.mailboxlist)
return false;
this.env.unread_counts[mbox] = count;
this.set_unread_count_display(mbox, set_title);
if (mark)
this.mark_folder(mbox, mark, '', true);
else if (!count)
this.unmark_folder(mbox, 'recent', '', true);
};
// update the mailbox count display
this.set_unread_count_display = function(mbox, set_title)
{
var reg, link, text_obj, item, mycount, childcount, div;
if (item = this.get_folder_li(mbox, '', true)) {
mycount = this.env.unread_counts[mbox] ? this.env.unread_counts[mbox] : 0;
link = $(item).children('a').eq(0);
text_obj = link.children('span.unreadcount');
if (!text_obj.length && mycount)
text_obj = $('<span>').addClass('unreadcount').appendTo(link);
reg = /\s+\([0-9]+\)$/i;
childcount = 0;
if ((div = item.getElementsByTagName('div')[0]) &&
div.className.match(/collapsed/)) {
// add children's counters
for (var k in this.env.unread_counts)
if (k.indexOf(mbox + this.env.delimiter) == 0)
childcount += this.env.unread_counts[k];
}
if (mycount && text_obj.length)
text_obj.html(this.env.unreadwrap.replace(/%[sd]/, mycount));
else if (text_obj.length)
text_obj.remove();
// set parent's display
reg = new RegExp(RegExp.escape(this.env.delimiter) + '[^' + RegExp.escape(this.env.delimiter) + ']+$');
if (mbox.match(reg))
this.set_unread_count_display(mbox.replace(reg, ''), false);
// set the right classes
if ((mycount+childcount)>0)
$(item).addClass('unread');
else
$(item).removeClass('unread');
}
// set unread count to window title
reg = /^\([0-9]+\)\s+/i;
if (set_title && document.title) {
var new_title = '',
doc_title = String(document.title);
if (mycount && doc_title.match(reg))
new_title = doc_title.replace(reg, '('+mycount+') ');
else if (mycount)
new_title = '('+mycount+') '+doc_title;
else
new_title = doc_title.replace(reg, '');
this.set_pagetitle(new_title);
}
};
// display fetched raw headers
this.set_headers = function(content)
{
if (this.gui_objects.all_headers_row && this.gui_objects.all_headers_box && content)
$(this.gui_objects.all_headers_box).html(content).show();
};
// display all-headers row and fetch raw message headers
this.show_headers = function(props, elem)
{
if (!this.gui_objects.all_headers_row || !this.gui_objects.all_headers_box || !this.env.uid)
return;
$(elem).removeClass('show-headers').addClass('hide-headers');
$(this.gui_objects.all_headers_row).show();
elem.onclick = function() { rcmail.command('hide-headers', '', elem); };
// fetch headers only once
if (!this.gui_objects.all_headers_box.innerHTML) {
var lock = this.display_message(this.get_label('loading'), 'loading');
this.http_post('headers', {_uid: this.env.uid}, lock);
}
};
// hide all-headers row
this.hide_headers = function(props, elem)
{
if (!this.gui_objects.all_headers_row || !this.gui_objects.all_headers_box)
return;
$(elem).removeClass('hide-headers').addClass('show-headers');
$(this.gui_objects.all_headers_row).hide();
elem.onclick = function() { rcmail.command('show-headers', '', elem); };
};
/********************************************************/
/********* html to text conversion functions *********/
/********************************************************/
this.html2plain = function(htmlText, id)
{
var rcmail = this,
url = '?_task=utils&_action=html2text',
lock = this.set_busy(true, 'converting');
this.log('HTTP POST: ' + url);
$.ajax({ type: 'POST', url: url, data: htmlText, contentType: 'application/octet-stream',
error: function(o, status, err) { rcmail.http_error(o, status, err, lock); },
success: function(data) { rcmail.set_busy(false, null, lock); $('#'+id).val(data); rcmail.log(data); }
});
};
this.plain2html = function(plain, id)
{
var lock = this.set_busy(true, 'converting');
plain = plain.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
$('#'+id).val(plain ? '<pre>'+plain+'</pre>' : '');
this.set_busy(false, null, lock);
};
/********************************************************/
/********* remote request methods *********/
/********************************************************/
// compose a valid url with the given parameters
this.url = function(action, query)
{
var querystring = typeof query === 'string' ? '&' + query : '';
if (typeof action !== 'string')
query = action;
else if (!query || typeof query !== 'object')
query = {};
if (action)
query._action = action;
else
query._action = this.env.action;
var base = this.env.comm_path, k, param = {};
// overwrite task name
if (query._action.match(/([a-z0-9_-]+)\/([a-z0-9-_.]+)/)) {
query._action = RegExp.$2;
base = base.replace(/\_task=[a-z0-9_-]+/, '_task='+RegExp.$1);
}
// remove undefined values
for (k in query) {
if (query[k] !== undefined && query[k] !== null)
param[k] = query[k];
}
return base + '&' + $.param(param) + querystring;
};
this.redirect = function(url, lock)
{
if (lock || lock === null)
this.set_busy(true);
if (this.is_framed()) {
parent.rcmail.redirect(url, lock);
}
else {
if (this.env.extwin) {
if (typeof url == 'string')
url += (url.indexOf('?') < 0 ? '?' : '&') + '_extwin=1';
else
url._extwin = 1;
}
this.location_href(url, window);
}
};
this.goto_url = function(action, query, lock)
{
this.redirect(this.url(action, query));
};
this.location_href = function(url, target, frame)
{
if (frame)
this.lock_frame();
if (typeof url == 'object')
url = this.env.comm_path + '&' + $.param(url);
// simulate real link click to force IE to send referer header
if (bw.ie && target == window)
$('<a>').attr('href', url).appendTo(document.body).get(0).click();
else
target.location.href = url;
// reset keep-alive interval
this.start_keepalive();
};
// send a http request to the server
this.http_request = function(action, query, lock)
{
var url = this.url(action, query);
// trigger plugin hook
var result = this.triggerEvent('request'+action, query);
if (result !== undefined) {
// abort if one the handlers returned false
if (result === false)
return false;
else
query = result;
}
url += '&_remote=1';
// send request
this.log('HTTP GET: ' + url);
// reset keep-alive interval
this.start_keepalive();
return $.ajax({
type: 'GET', url: url, data: { _unlock:(lock?lock:0) }, dataType: 'json',
success: function(data){ ref.http_response(data); },
error: function(o, status, err) { ref.http_error(o, status, err, lock, action); }
});
};
// send a http POST request to the server
this.http_post = function(action, postdata, lock)
{
var url = this.url(action);
if (postdata && typeof postdata === 'object') {
postdata._remote = 1;
postdata._unlock = (lock ? lock : 0);
}
else
postdata += (postdata ? '&' : '') + '_remote=1' + (lock ? '&_unlock='+lock : '');
// trigger plugin hook
var result = this.triggerEvent('request'+action, postdata);
if (result !== undefined) {
// abort if one of the handlers returned false
if (result === false)
return false;
else
postdata = result;
}
// send request
this.log('HTTP POST: ' + url);
// reset keep-alive interval
this.start_keepalive();
return $.ajax({
type: 'POST', url: url, data: postdata, dataType: 'json',
success: function(data){ ref.http_response(data); },
error: function(o, status, err) { ref.http_error(o, status, err, lock, action); }
});
};
// aborts ajax request
this.abort_request = function(r)
{
if (r.request)
r.request.abort();
if (r.lock)
this.set_busy(false, null, r.lock);
};
// handle HTTP response
this.http_response = function(response)
{
if (!response)
return;
if (response.unlock)
this.set_busy(false);
this.triggerEvent('responsebefore', {response: response});
this.triggerEvent('responsebefore'+response.action, {response: response});
// set env vars
if (response.env)
this.set_env(response.env);
// we have labels to add
if (typeof response.texts === 'object') {
for (var name in response.texts)
if (typeof response.texts[name] === 'string')
this.add_label(name, response.texts[name]);
}
// if we get javascript code from server -> execute it
if (response.exec) {
this.log(response.exec);
eval(response.exec);
}
// execute callback functions of plugins
if (response.callbacks && response.callbacks.length) {
for (var i=0; i < response.callbacks.length; i++)
this.triggerEvent(response.callbacks[i][0], response.callbacks[i][1]);
}
// process the response data according to the sent action
switch (response.action) {
case 'delete':
if (this.task == 'addressbook') {
var sid, uid = this.contact_list.get_selection(), writable = false;
if (uid && this.contact_list.rows[uid]) {
// search results, get source ID from record ID
if (this.env.source == '') {
sid = String(uid).replace(/^[^-]+-/, '');
writable = sid && this.env.address_sources[sid] && !this.env.address_sources[sid].readonly;
}
else {
writable = !this.env.address_sources[this.env.source].readonly;
}
}
this.enable_command('compose', (uid && this.contact_list.rows[uid]));
this.enable_command('delete', 'edit', writable);
this.enable_command('export', (this.contact_list && this.contact_list.rowcount > 0));
this.enable_command('export-selected', false);
}
case 'moveto':
if (this.env.action == 'show') {
// re-enable commands on move/delete error
this.enable_command(this.env.message_commands, true);
if (!this.env.list_post)
this.enable_command('reply-list', false);
}
else if (this.task == 'addressbook') {
this.triggerEvent('listupdate', { folder:this.env.source, rowcount:this.contact_list.rowcount });
}
case 'purge':
case 'expunge':
if (this.task == 'mail') {
if (!this.env.exists) {
// clear preview pane content
if (this.env.contentframe)
this.show_contentframe(false);
// disable commands useless when mailbox is empty
this.enable_command(this.env.message_commands, 'purge', 'expunge',
'select-all', 'select-none', 'expand-all', 'expand-unread', 'collapse-all', false);
}
if (this.message_list)
this.triggerEvent('listupdate', { folder:this.env.mailbox, rowcount:this.message_list.rowcount });
}
break;
case 'refresh':
case 'check-recent':
case 'getunread':
case 'search':
this.env.qsearch = null;
case 'list':
if (this.task == 'mail') {
this.enable_command('show', 'select-all', 'select-none', this.env.messagecount > 0);
this.enable_command('expunge', this.env.exists);
this.enable_command('purge', this.purge_mailbox_test());
this.enable_command('expand-all', 'expand-unread', 'collapse-all', this.env.threading && this.env.messagecount);
if ((response.action == 'list' || response.action == 'search') && this.message_list) {
this.msglist_select(this.message_list);
this.triggerEvent('listupdate', { folder:this.env.mailbox, rowcount:this.message_list.rowcount });
}
}
else if (this.task == 'addressbook') {
this.enable_command('export', (this.contact_list && this.contact_list.rowcount > 0));
if (response.action == 'list' || response.action == 'search') {
this.enable_command('search-create', this.env.source == '');
this.enable_command('search-delete', this.env.search_id);
this.update_group_commands();
this.triggerEvent('listupdate', { folder:this.env.source, rowcount:this.contact_list.rowcount });
}
}
break;
}
if (response.unlock)
this.hide_message(response.unlock);
this.triggerEvent('responseafter', {response: response});
this.triggerEvent('responseafter'+response.action, {response: response});
// reset keep-alive interval
this.start_keepalive();
};
// handle HTTP request errors
this.http_error = function(request, status, err, lock, action)
{
var errmsg = request.statusText;
this.set_busy(false, null, lock);
request.abort();
// don't display error message on page unload (#1488547)
if (this.unload)
return;
if (request.status && errmsg)
this.display_message(this.get_label('servererror') + ' (' + errmsg + ')', 'error');
else if (status == 'timeout')
this.display_message(this.get_label('requesttimedout'), 'error');
else if (request.status == 0 && status != 'abort')
this.display_message(this.get_label('servererror') + ' (No connection)', 'error');
// redirect to url specified in location header if not empty
var location_url = request.getResponseHeader("Location");
if (location_url && this.env.action != 'compose') // don't redirect on compose screen, contents might get lost (#1488926)
this.redirect(location_url);
// 403 Forbidden response (CSRF prevention) - reload the page.
// In case there's a new valid session it will be used, otherwise
// login form will be presented (#1488960).
if (request.status == 403) {
(this.is_framed() ? parent : window).location.reload();
return;
}
// re-send keep-alive requests after 30 seconds
if (action == 'keep-alive')
setTimeout(function(){ ref.keep_alive(); ref.start_keepalive(); }, 30000);
};
// callback when an iframe finished loading
this.iframe_loaded = function(unlock)
{
this.set_busy(false, null, unlock);
if (this.submit_timer)
clearTimeout(this.submit_timer);
};
// post the given form to a hidden iframe
this.async_upload_form = function(form, action, onload)
{
var ts = new Date().getTime(),
frame_name = 'rcmupload'+ts;
// upload progress support
if (this.env.upload_progress_name) {
var fname = this.env.upload_progress_name,
field = $('input[name='+fname+']', form);
if (!field.length) {
field = $('<input>').attr({type: 'hidden', name: fname});
field.prependTo(form);
}
field.val(ts);
}
// have to do it this way for IE
// otherwise the form will be posted to a new window
if (document.all) {
var html = '<iframe name="'+frame_name+'" src="program/resources/blank.gif" style="width:0;height:0;visibility:hidden;"></iframe>';
document.body.insertAdjacentHTML('BeforeEnd', html);
}
else { // for standards-compilant browsers
var frame = document.createElement('iframe');
frame.name = frame_name;
frame.style.border = 'none';
frame.style.width = 0;
frame.style.height = 0;
frame.style.visibility = 'hidden';
document.body.appendChild(frame);
}
// handle upload errors, parsing iframe content in onload
$(frame_name).bind('load', {ts:ts}, onload);
$(form).attr({
target: frame_name,
action: this.url(action, { _id:this.env.compose_id||'', _uploadid:ts }),
method: 'POST'})
.attr(form.encoding ? 'encoding' : 'enctype', 'multipart/form-data')
.submit();
return frame_name;
};
// html5 file-drop API
this.document_drag_hover = function(e, over)
{
e.preventDefault();
$(ref.gui_objects.filedrop)[(over?'addClass':'removeClass')]('active');
};
this.file_drag_hover = function(e, over)
{
e.preventDefault();
e.stopPropagation();
$(ref.gui_objects.filedrop)[(over?'addClass':'removeClass')]('hover');
};
// handler when files are dropped to a designated area.
// compose a multipart form data and submit it to the server
this.file_dropped = function(e)
{
// abort event and reset UI
this.file_drag_hover(e, false);
// prepare multipart form data composition
var files = e.target.files || e.dataTransfer.files,
formdata = window.FormData ? new FormData() : null,
fieldname = (this.env.filedrop.fieldname || '_file') + (this.env.filedrop.single ? '' : '[]'),
boundary = '------multipartformboundary' + (new Date).getTime(),
dashdash = '--', crlf = '\r\n',
multipart = dashdash + boundary + crlf;
if (!files || !files.length)
return;
// inline function to submit the files to the server
var submit_data = function() {
var multiple = files.length > 1,
ts = new Date().getTime(),
content = '<span>' + (multiple ? ref.get_label('uploadingmany') : files[0].name) + '</span>';
// add to attachments list
if (!ref.add2attachment_list(ts, { name:'', html:content, classname:'uploading', complete:false }))
ref.file_upload_id = ref.set_busy(true, 'uploading');
// complete multipart content and post request
multipart += dashdash + boundary + dashdash + crlf;
$.ajax({
type: 'POST',
dataType: 'json',
url: ref.url(ref.env.filedrop.action||'upload', { _id:ref.env.compose_id||ref.env.cid||'', _uploadid:ts, _remote:1 }),
contentType: formdata ? false : 'multipart/form-data; boundary=' + boundary,
processData: false,
timeout: 0, // disable default timeout set in ajaxSetup()
data: formdata || multipart,
headers: {'X-Roundcube-Request': ref.env.request_token},
beforeSend: function(xhr, s) { if (!formdata && xhr.sendAsBinary) xhr.send = xhr.sendAsBinary; },
success: function(data){ ref.http_response(data); },
error: function(o, status, err) { ref.http_error(o, status, err, null, 'attachment'); }
});
};
// get contents of all dropped files
var last = this.env.filedrop.single ? 0 : files.length - 1;
for (var j=0, i=0, f; j <= last && (f = files[i]); i++) {
if (!f.name) f.name = f.fileName;
if (!f.size) f.size = f.fileSize;
if (!f.type) f.type = 'application/octet-stream';
// file name contains non-ASCII characters, do UTF8-binary string conversion.
if (!formdata && /[^\x20-\x7E]/.test(f.name))
f.name_bin = unescape(encodeURIComponent(f.name));
// filter by file type if requested
if (this.env.filedrop.filter && !f.type.match(new RegExp(this.env.filedrop.filter))) {
// TODO: show message to user
continue;
}
// do it the easy way with FormData (FF 4+, Chrome 5+, Safari 5+)
if (formdata) {
formdata.append(fieldname, f);
if (j == last)
return submit_data();
}
// use FileReader supporetd by Firefox 3.6
else if (window.FileReader) {
var reader = new FileReader();
// closure to pass file properties to async callback function
reader.onload = (function(file, j) {
return function(e) {
multipart += 'Content-Disposition: form-data; name="' + fieldname + '"';
multipart += '; filename="' + (f.name_bin || file.name) + '"' + crlf;
multipart += 'Content-Length: ' + file.size + crlf;
multipart += 'Content-Type: ' + file.type + crlf + crlf;
multipart += e.target.result + crlf;
multipart += dashdash + boundary + crlf;
if (j == last) // we're done, submit the data
return submit_data();
}
})(f,j);
reader.readAsBinaryString(f);
}
// Firefox 3
else if (f.getAsBinary) {
multipart += 'Content-Disposition: form-data; name="' + fieldname + '"';
multipart += '; filename="' + (f.name_bin || f.name) + '"' + crlf;
multipart += 'Content-Length: ' + f.size + crlf;
multipart += 'Content-Type: ' + f.type + crlf + crlf;
multipart += f.getAsBinary() + crlf;
multipart += dashdash + boundary +crlf;
if (j == last)
return submit_data();
}
j++;
}
};
// starts interval for keep-alive signal
this.start_keepalive = function()
{
if (!this.env.session_lifetime || this.env.framed || this.env.extwin || this.task == 'login' || this.env.action == 'print')
return;
if (this._keepalive)
clearInterval(this._keepalive);
this._keepalive = setInterval(function(){ ref.keep_alive(); }, this.env.session_lifetime * 0.5 * 1000);
};
// starts interval for refresh signal
this.start_refresh = function()
{
if (!this.env.refresh_interval || this.env.framed || this.env.extwin || this.task == 'login' || this.env.action == 'print')
return;
if (this._refresh)
clearInterval(this._refresh);
this._refresh = setInterval(function(){ ref.refresh(); }, this.env.refresh_interval * 1000);
};
// sends keep-alive signal
this.keep_alive = function()
{
if (!this.busy)
this.http_request('keep-alive');
};
// sends refresh signal
this.refresh = function()
{
if (this.busy) {
// try again after 10 seconds
setTimeout(function(){ ref.refresh(); ref.start_refresh(); }, 10000);
return;
}
var params = {}, lock = this.set_busy(true, 'refreshing');
if (this.task == 'mail' && this.gui_objects.mailboxlist)
params = this.check_recent_params();
// plugins should bind to 'requestrefresh' event to add own params
this.http_request('refresh', params, lock);
};
// returns check-recent request parameters
this.check_recent_params = function()
{
var params = {_mbox: this.env.mailbox};
if (this.gui_objects.mailboxlist)
params._folderlist = 1;
if (this.gui_objects.messagelist)
params._list = 1;
if (this.gui_objects.quotadisplay)
params._quota = 1;
if (this.env.search_request)
params._search = this.env.search_request;
return params;
};
/********************************************************/
/********* helper methods *********/
/********************************************************/
// get window.opener.rcmail if available
this.opener = function()
{
// catch Error: Permission denied to access property rcmail
try {
if (window.opener && !opener.closed && opener.rcmail)
return opener.rcmail;
}
catch (e) {}
};
// check if we're in show mode or if we have a unique selection
// and return the message uid
this.get_single_uid = function()
{
return this.env.uid ? this.env.uid : (this.message_list ? this.message_list.get_single_selection() : null);
};
// same as above but for contacts
this.get_single_cid = function()
{
return this.env.cid ? this.env.cid : (this.contact_list ? this.contact_list.get_single_selection() : null);
};
// gets cursor position
this.get_caret_pos = function(obj)
{
if (obj.selectionEnd !== undefined)
return obj.selectionEnd;
if (document.selection && document.selection.createRange) {
var range = document.selection.createRange();
if (range.parentElement() != obj)
return 0;
var gm = range.duplicate();
if (obj.tagName == 'TEXTAREA')
gm.moveToElementText(obj);
else
gm.expand('textedit');
gm.setEndPoint('EndToStart', range);
var p = gm.text.length;
return p <= obj.value.length ? p : -1;
}
return obj.value.length;
};
// moves cursor to specified position
this.set_caret_pos = function(obj, pos)
{
if (obj.setSelectionRange)
obj.setSelectionRange(pos, pos);
else if (obj.createTextRange) {
var range = obj.createTextRange();
range.collapse(true);
range.moveEnd('character', pos);
range.moveStart('character', pos);
range.select();
}
};
// disable/enable all fields of a form
this.lock_form = function(form, lock)
{
if (!form || !form.elements)
return;
var n, len, elm;
if (lock)
this.disabled_form_elements = [];
for (n=0, len=form.elements.length; n<len; n++) {
elm = form.elements[n];
if (elm.type == 'hidden')
continue;
// remember which elem was disabled before lock
if (lock && elm.disabled)
this.disabled_form_elements.push(elm);
// check this.disabled_form_elements before inArray() as a workaround for FF5 bug
// http://bugs.jquery.com/ticket/9873
else if (lock || (this.disabled_form_elements && $.inArray(elm, this.disabled_form_elements)<0))
elm.disabled = lock;
}
};
this.mailto_handler_uri = function()
{
return location.href.split('?')[0] + '?_task=mail&_action=compose&_to=%s';
};
this.register_protocol_handler = function(name)
{
try {
window.navigator.registerProtocolHandler('mailto', this.mailto_handler_uri(), name);
}
catch(e) {};
};
this.check_protocol_handler = function(name, elem)
{
var nav = window.navigator;
if (!nav
|| (typeof nav.registerProtocolHandler != 'function')
|| ((typeof nav.isProtocolHandlerRegistered == 'function')
&& nav.isProtocolHandlerRegistered('mailto', this.mailto_handler_uri()) == 'registered')
)
$(elem).addClass('disabled');
else
$(elem).click(function() { rcmail.register_protocol_handler(name); return false; });
};
// Checks browser capabilities eg. PDF support, TIF support
this.browser_capabilities_check = function()
{
if (!this.env.browser_capabilities)
this.env.browser_capabilities = {};
if (this.env.browser_capabilities.pdf === undefined)
this.env.browser_capabilities.pdf = this.pdf_support_check();
if (this.env.browser_capabilities.flash === undefined)
this.env.browser_capabilities.flash = this.flash_support_check();
if (this.env.browser_capabilities.tif === undefined)
this.tif_support_check();
};
// Returns browser capabilities string
this.browser_capabilities = function()
{
if (!this.env.browser_capabilities)
return '';
var n, ret = [];
for (n in this.env.browser_capabilities)
ret.push(n + '=' + this.env.browser_capabilities[n]);
return ret.join();
};
this.tif_support_check = function()
{
var img = new Image();
img.onload = function() { rcmail.env.browser_capabilities.tif = 1; };
img.onerror = function() { rcmail.env.browser_capabilities.tif = 0; };
img.src = 'program/resources/blank.tif';
};
this.pdf_support_check = function()
{
var plugin = navigator.mimeTypes ? navigator.mimeTypes["application/pdf"] : {},
plugins = navigator.plugins,
len = plugins.length,
regex = /Adobe Reader|PDF|Acrobat/i;
if (plugin && plugin.enabledPlugin)
return 1;
if (window.ActiveXObject) {
try {
if (axObj = new ActiveXObject("AcroPDF.PDF"))
return 1;
}
catch (e) {}
try {
if (axObj = new ActiveXObject("PDF.PdfCtrl"))
return 1;
}
catch (e) {}
}
for (i=0; i<len; i++) {
plugin = plugins[i];
if (typeof plugin === 'String') {
if (regex.test(plugin))
return 1;
}
else if (plugin.name && regex.test(plugin.name))
return 1;
}
return 0;
};
this.flash_support_check = function()
{
var plugin = navigator.mimeTypes ? navigator.mimeTypes["application/x-shockwave-flash"] : {};
if (plugin && plugin.enabledPlugin)
return 1;
if (window.ActiveXObject) {
try {
if (axObj = new ActiveXObject("ShockwaveFlash.ShockwaveFlash"))
return 1;
}
catch (e) {}
}
return 0;
};
// Cookie setter
this.set_cookie = function(name, value, expires)
{
setCookie(name, value, expires, this.env.cookie_path, this.env.cookie_domain, this.env.cookie_secure);
}
} // end object rcube_webmail
// some static methods
rcube_webmail.long_subject_title = function(elem, indent)
{
if (!elem.title) {
var $elem = $(elem);
if ($elem.width() + indent * 15 > $elem.parent().width())
elem.title = $elem.html();
}
};
rcube_webmail.long_subject_title_ie = function(elem, indent)
{
if (!elem.title) {
var $elem = $(elem),
txt = $.trim($elem.text()),
tmp = $('<span>').text(txt)
.css({'position': 'absolute', 'float': 'left', 'visibility': 'hidden',
'font-size': $elem.css('font-size'), 'font-weight': $elem.css('font-weight')})
.appendTo($('body')),
w = tmp.width();
tmp.remove();
if (w + indent * 15 > $elem.width())
elem.title = txt;
}
};
rcube_webmail.prototype.get_cookie = getCookie;
// copy event engine prototype
rcube_webmail.prototype.addEventListener = rcube_event_engine.prototype.addEventListener;
rcube_webmail.prototype.removeEventListener = rcube_event_engine.prototype.removeEventListener;
rcube_webmail.prototype.triggerEvent = rcube_event_engine.prototype.triggerEvent;
diff --git a/program/lib/Roundcube/html.php b/program/lib/Roundcube/html.php
index eb23c8b2e..3e6e47a56 100644
--- a/program/lib/Roundcube/html.php
+++ b/program/lib/Roundcube/html.php
@@ -1,894 +1,894 @@
<?php
/*
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2011, The Roundcube Dev Team |
| |
| 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: |
| Helper class to create valid XHTML code |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
/**
* Class for HTML code creation
*
* @package Framework
* @subpackage View
*/
class html
{
protected $tagname;
protected $attrib = array();
protected $allowed = array();
protected $content;
public static $doctype = 'xhtml';
public static $lc_tags = true;
public static $common_attrib = array('id','class','style','title','align');
public static $containers = array('iframe','div','span','p','h1','h2','h3','form','textarea','table','thead','tbody','tr','th','td','style','script');
/**
* Constructor
*
* @param array $attrib Hash array with tag attributes
*/
public function __construct($attrib = array())
{
if (is_array($attrib)) {
$this->attrib = $attrib;
}
}
/**
* Return the tag code
*
* @return string The finally composed HTML tag
*/
public function show()
{
return self::tag($this->tagname, $this->attrib, $this->content, array_merge(self::$common_attrib, $this->allowed));
}
/****** STATIC METHODS *******/
/**
* Generic method to create a HTML tag
*
* @param string $tagname Tag name
* @param array $attrib Tag attributes as key/value pairs
* @param string $content Optinal Tag content (creates a container tag)
* @param array $allowed_attrib List with allowed attributes, omit to allow all
* @return string The XHTML tag
*/
public static function tag($tagname, $attrib = array(), $content = null, $allowed_attrib = null)
{
if (is_string($attrib))
$attrib = array('class' => $attrib);
$inline_tags = array('a','span','img');
$suffix = $attrib['nl'] || ($content && $attrib['nl'] !== false && !in_array($tagname, $inline_tags)) ? "\n" : '';
$tagname = self::$lc_tags ? strtolower($tagname) : $tagname;
if (isset($content) || in_array($tagname, self::$containers)) {
$suffix = $attrib['noclose'] ? $suffix : '</' . $tagname . '>' . $suffix;
unset($attrib['noclose'], $attrib['nl']);
return '<' . $tagname . self::attrib_string($attrib, $allowed_attrib) . '>' . $content . $suffix;
}
else {
return '<' . $tagname . self::attrib_string($attrib, $allowed_attrib) . '>' . $suffix;
}
}
/**
*
*/
public static function doctype($type)
{
$doctypes = array(
'html5' => '<!DOCTYPE html>',
'xhtml' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
'xhtml-trans' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
'xhtml-strict' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
);
if ($doctypes[$type]) {
self::$doctype = preg_replace('/-\w+$/', '', $type);
return $doctypes[$type];
}
return '';
}
/**
* Derrived method for <div> containers
*
* @param mixed $attr Hash array with tag attributes or string with class name
* @param string $cont Div content
* @return string HTML code
* @see html::tag()
*/
public static function div($attr = null, $cont = null)
{
if (is_string($attr)) {
$attr = array('class' => $attr);
}
return self::tag('div', $attr, $cont, array_merge(self::$common_attrib, array('onclick')));
}
/**
* Derrived method for <p> blocks
*
* @param mixed $attr Hash array with tag attributes or string with class name
* @param string $cont Paragraph content
* @return string HTML code
* @see html::tag()
*/
public static function p($attr = null, $cont = null)
{
if (is_string($attr)) {
$attr = array('class' => $attr);
}
return self::tag('p', $attr, $cont, self::$common_attrib);
}
/**
* Derrived method to create <img />
*
* @param mixed $attr Hash array with tag attributes or string with image source (src)
* @return string HTML code
* @see html::tag()
*/
public static function img($attr = null)
{
if (is_string($attr)) {
$attr = array('src' => $attr);
}
return self::tag('img', $attr + array('alt' => ''), null, array_merge(self::$common_attrib,
array('src','alt','width','height','border','usemap','onclick')));
}
/**
* Derrived method for link tags
*
* @param mixed $attr Hash array with tag attributes or string with link location (href)
* @param string $cont Link content
* @return string HTML code
* @see html::tag()
*/
public static function a($attr, $cont)
{
if (is_string($attr)) {
$attr = array('href' => $attr);
}
return self::tag('a', $attr, $cont, array_merge(self::$common_attrib,
array('href','target','name','rel','onclick','onmouseover','onmouseout','onmousedown','onmouseup')));
}
/**
* Derrived method for inline span tags
*
* @param mixed $attr Hash array with tag attributes or string with class name
* @param string $cont Tag content
* @return string HTML code
* @see html::tag()
*/
public static function span($attr, $cont)
{
if (is_string($attr)) {
$attr = array('class' => $attr);
}
return self::tag('span', $attr, $cont, self::$common_attrib);
}
/**
* Derrived method for form element labels
*
* @param mixed $attr Hash array with tag attributes or string with 'for' attrib
* @param string $cont Tag content
* @return string HTML code
* @see html::tag()
*/
public static function label($attr, $cont)
{
if (is_string($attr)) {
$attr = array('for' => $attr);
}
return self::tag('label', $attr, $cont, array_merge(self::$common_attrib, array('for')));
}
/**
* Derrived method to create <iframe></iframe>
*
* @param mixed $attr Hash array with tag attributes or string with frame source (src)
* @return string HTML code
* @see html::tag()
*/
public static function iframe($attr = null, $cont = null)
{
if (is_string($attr)) {
$attr = array('src' => $attr);
}
return self::tag('iframe', $attr, $cont, array_merge(self::$common_attrib,
array('src','name','width','height','border','frameborder','onload')));
}
/**
* Derrived method to create <script> tags
*
* @param mixed $attr Hash array with tag attributes or string with script source (src)
* @param string $cont Javascript code to be placed as tag content
* @return string HTML code
* @see html::tag()
*/
public static function script($attr, $cont = null)
{
if (is_string($attr)) {
$attr = array('src' => $attr);
}
if ($cont) {
if (self::$doctype == 'xhtml')
$cont = "\n/* <![CDATA[ */\n" . $cont . "\n/* ]]> */\n";
else
$cont = "\n" . $cont . "\n";
}
return self::tag('script', $attr + array('type' => 'text/javascript', 'nl' => true),
$cont, array_merge(self::$common_attrib, array('src','type','charset')));
}
/**
* Derrived method for line breaks
*
* @return string HTML code
* @see html::tag()
*/
public static function br($attrib = array())
{
return self::tag('br', $attrib);
}
/**
* Create string with attributes
*
* @param array $attrib Associative arry with tag attributes
* @param array $allowed List of allowed attributes
* @return string Valid attribute string
*/
public static function attrib_string($attrib = array(), $allowed = null)
{
if (empty($attrib)) {
return '';
}
$allowed_f = array_flip((array)$allowed);
$attrib_arr = array();
foreach ($attrib as $key => $value) {
// skip size if not numeric
if ($key == 'size' && !is_numeric($value)) {
continue;
}
// ignore "internal" or not allowed attributes
if ($key == 'nl' || ($allowed && !isset($allowed_f[$key])) || $value === null) {
continue;
}
// skip empty eventhandlers
if (preg_match('/^on[a-z]+/', $key) && !$value) {
continue;
}
// attributes with no value
if (in_array($key, array('checked', 'multiple', 'disabled', 'selected', 'autofocus'))) {
if ($value) {
$attrib_arr[] = $key . '="' . $key . '"';
}
}
else {
$attrib_arr[] = $key . '="' . self::quote($value) . '"';
}
}
return count($attrib_arr) ? ' '.implode(' ', $attrib_arr) : '';
}
/**
* Convert a HTML attribute string attributes to an associative array (name => value)
*
* @param string Input string
* @return array Key-value pairs of parsed attributes
*/
public static function parse_attrib_string($str)
{
$attrib = array();
$regexp = '/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui';
preg_match_all($regexp, stripslashes($str), $regs, PREG_SET_ORDER);
// convert attributes to an associative array (name => value)
if ($regs) {
foreach ($regs as $attr) {
$attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
}
}
return $attrib;
}
/**
* Replacing specials characters in html attribute value
*
* @param string $str Input string
*
* @return string The quoted string
*/
public static function quote($str)
{
static $flags;
if (!$flags) {
$flags = ENT_COMPAT;
if (defined('ENT_SUBSTITUTE')) {
$flags |= ENT_SUBSTITUTE;
}
}
return @htmlspecialchars($str, $flags, RCUBE_CHARSET);
}
}
/**
* Class to create an HTML input field
*
* @package Framework
* @subpackage View
*/
class html_inputfield extends html
{
protected $tagname = 'input';
protected $type = 'text';
protected $allowed = array(
'type','name','value','size','tabindex','autocapitalize',
'autocomplete','checked','onchange','onclick','disabled','readonly',
- 'spellcheck','results','maxlength','src','multiple','placeholder',
- 'autofocus',
+ 'spellcheck','results','maxlength','src','multiple','accept',
+ 'placeholder','autofocus',
);
/**
* Object constructor
*
* @param array $attrib Associative array with tag attributes
*/
public function __construct($attrib = array())
{
if (is_array($attrib)) {
$this->attrib = $attrib;
}
if ($attrib['type']) {
$this->type = $attrib['type'];
}
}
/**
* Compose input tag
*
* @param string $value Field value
* @param array $attrib Additional attributes to override
* @return string HTML output
*/
public function show($value = null, $attrib = null)
{
// overwrite object attributes
if (is_array($attrib)) {
$this->attrib = array_merge($this->attrib, $attrib);
}
// set value attribute
if ($value !== null) {
$this->attrib['value'] = $value;
}
// set type
$this->attrib['type'] = $this->type;
return parent::show();
}
}
/**
* Class to create an HTML password field
*
* @package Framework
* @subpackage View
*/
class html_passwordfield extends html_inputfield
{
protected $type = 'password';
}
/**
* Class to create an hidden HTML input field
*
* @package Framework
* @subpackage View
*/
class html_hiddenfield extends html
{
protected $tagname = 'input';
protected $type = 'hidden';
protected $fields_arr = array();
protected $allowed = array('type','name','value','onchange','disabled','readonly');
/**
* Constructor
*
* @param array $attrib Named tag attributes
*/
public function __construct($attrib = null)
{
if (is_array($attrib)) {
$this->add($attrib);
}
}
/**
* Add a hidden field to this instance
*
* @param array $attrib Named tag attributes
*/
public function add($attrib)
{
$this->fields_arr[] = $attrib;
}
/**
* Create HTML code for the hidden fields
*
* @return string Final HTML code
*/
public function show()
{
$out = '';
foreach ($this->fields_arr as $attrib) {
$out .= self::tag($this->tagname, array('type' => $this->type) + $attrib);
}
return $out;
}
}
/**
* Class to create HTML radio buttons
*
* @package Framework
* @subpackage View
*/
class html_radiobutton extends html_inputfield
{
protected $type = 'radio';
/**
* Get HTML code for this object
*
* @param string $value Value of the checked field
* @param array $attrib Additional attributes to override
* @return string HTML output
*/
public function show($value = '', $attrib = null)
{
// overwrite object attributes
if (is_array($attrib)) {
$this->attrib = array_merge($this->attrib, $attrib);
}
// set value attribute
$this->attrib['checked'] = ((string)$value == (string)$this->attrib['value']);
return parent::show();
}
}
/**
* Class to create HTML checkboxes
*
* @package Framework
* @subpackage View
*/
class html_checkbox extends html_inputfield
{
protected $type = 'checkbox';
/**
* Get HTML code for this object
*
* @param string $value Value of the checked field
* @param array $attrib Additional attributes to override
* @return string HTML output
*/
public function show($value = '', $attrib = null)
{
// overwrite object attributes
if (is_array($attrib)) {
$this->attrib = array_merge($this->attrib, $attrib);
}
// set value attribute
$this->attrib['checked'] = ((string)$value == (string)$this->attrib['value']);
return parent::show();
}
}
/**
* Class to create an HTML textarea
*
* @package Framework
* @subpackage View
*/
class html_textarea extends html
{
protected $tagname = 'textarea';
protected $allowed = array('name','rows','cols','wrap','tabindex',
'onchange','disabled','readonly','spellcheck');
/**
* Get HTML code for this object
*
* @param string $value Textbox value
* @param array $attrib Additional attributes to override
* @return string HTML output
*/
public function show($value = '', $attrib = null)
{
// overwrite object attributes
if (is_array($attrib)) {
$this->attrib = array_merge($this->attrib, $attrib);
}
// take value attribute as content
if (empty($value) && !empty($this->attrib['value'])) {
$value = $this->attrib['value'];
}
// make shure we don't print the value attribute
if (isset($this->attrib['value'])) {
unset($this->attrib['value']);
}
if (!empty($value) && empty($this->attrib['is_escaped'])) {
$value = self::quote($value);
}
return self::tag($this->tagname, $this->attrib, $value,
array_merge(self::$common_attrib, $this->allowed));
}
}
/**
* Builder for HTML drop-down menus
* Syntax:<pre>
* // create instance. arguments are used to set attributes of select-tag
* $select = new html_select(array('name' => 'fieldname'));
*
* // add one option
* $select->add('Switzerland', 'CH');
*
* // add multiple options
* $select->add(array('Switzerland','Germany'), array('CH','DE'));
*
* // generate pulldown with selection 'Switzerland' and return html-code
* // as second argument the same attributes available to instanciate can be used
* print $select->show('CH');
* </pre>
*
* @package Framework
* @subpackage View
*/
class html_select extends html
{
protected $tagname = 'select';
protected $options = array();
protected $allowed = array('name','size','tabindex','autocomplete',
'multiple','onchange','disabled','rel');
/**
* Add a new option to this drop-down
*
* @param mixed $names Option name or array with option names
* @param mixed $values Option value or array with option values
*/
public function add($names, $values = null)
{
if (is_array($names)) {
foreach ($names as $i => $text) {
$this->options[] = array('text' => $text, 'value' => $values[$i]);
}
}
else {
$this->options[] = array('text' => $names, 'value' => $values);
}
}
/**
* Get HTML code for this object
*
* @param string $select Value of the selection option
* @param array $attrib Additional attributes to override
* @return string HTML output
*/
public function show($select = array(), $attrib = null)
{
// overwrite object attributes
if (is_array($attrib)) {
$this->attrib = array_merge($this->attrib, $attrib);
}
$this->content = "\n";
$select = (array)$select;
foreach ($this->options as $option) {
$attr = array(
'value' => $option['value'],
'selected' => (in_array($option['value'], $select, true) ||
in_array($option['text'], $select, true)) ? 1 : null);
$option_content = $option['text'];
if (empty($this->attrib['is_escaped'])) {
$option_content = self::quote($option_content);
}
$this->content .= self::tag('option', $attr, $option_content);
}
return parent::show();
}
}
/**
* Class to build an HTML table
*
* @package Framework
* @subpackage View
*/
class html_table extends html
{
protected $tagname = 'table';
protected $allowed = array('id','class','style','width','summary',
'cellpadding','cellspacing','border');
private $header = array();
private $rows = array();
private $rowindex = 0;
private $colindex = 0;
/**
* Constructor
*
* @param array $attrib Named tag attributes
*/
public function __construct($attrib = array())
{
$default_attrib = self::$doctype == 'xhtml' ? array('summary' => '', 'border' => 0) : array();
$this->attrib = array_merge($attrib, $default_attrib);
if (!empty($attrib['tagname']) && $attrib['tagname'] != 'table') {
$this->tagname = $attrib['tagname'];
$this->allowed = self::$common_attrib;
}
}
/**
* Add a table cell
*
* @param array $attr Cell attributes
* @param string $cont Cell content
*/
public function add($attr, $cont)
{
if (is_string($attr)) {
$attr = array('class' => $attr);
}
$cell = new stdClass;
$cell->attrib = $attr;
$cell->content = $cont;
$this->rows[$this->rowindex]->cells[$this->colindex] = $cell;
$this->colindex += max(1, intval($attr['colspan']));
if ($this->attrib['cols'] && $this->colindex >= $this->attrib['cols']) {
$this->add_row();
}
}
/**
* Add a table header cell
*
* @param array $attr Cell attributes
* @param string $cont Cell content
*/
public function add_header($attr, $cont)
{
if (is_string($attr)) {
$attr = array('class' => $attr);
}
$cell = new stdClass;
$cell->attrib = $attr;
$cell->content = $cont;
$this->header[] = $cell;
}
/**
* Remove a column from a table
* Useful for plugins making alterations
*
* @param string $class
*/
public function remove_column($class)
{
// Remove the header
foreach ($this->header as $index=>$header){
if ($header->attrib['class'] == $class){
unset($this->header[$index]);
break;
}
}
// Remove cells from rows
foreach ($this->rows as $i=>$row){
foreach ($row->cells as $j=>$cell){
if ($cell->attrib['class'] == $class){
unset($this->rows[$i]->cells[$j]);
break;
}
}
}
}
/**
* Jump to next row
*
* @param array $attr Row attributes
*/
public function add_row($attr = array())
{
$this->rowindex++;
$this->colindex = 0;
$this->rows[$this->rowindex] = new stdClass;
$this->rows[$this->rowindex]->attrib = $attr;
$this->rows[$this->rowindex]->cells = array();
}
/**
* Set row attributes
*
* @param array $attr Row attributes
* @param int $index Optional row index (default current row index)
*/
public function set_row_attribs($attr = array(), $index = null)
{
if (is_string($attr)) {
$attr = array('class' => $attr);
}
if ($index === null) {
$index = $this->rowindex;
}
// make sure row object exists (#1489094)
if (!$this->rows[$index]) {
$this->rows[$index] = new stdClass;
}
$this->rows[$index]->attrib = $attr;
}
/**
* Get row attributes
*
* @param int $index Row index
*
* @return array Row attributes
*/
public function get_row_attribs($index = null)
{
if ($index === null) {
$index = $this->rowindex;
}
return $this->rows[$index] ? $this->rows[$index]->attrib : null;
}
/**
* Build HTML output of the table data
*
* @param array $attrib Table attributes
* @return string The final table HTML code
*/
public function show($attrib = null)
{
if (is_array($attrib)) {
$this->attrib = array_merge($this->attrib, $attrib);
}
$thead = $tbody = "";
// include <thead>
if (!empty($this->header)) {
$rowcontent = '';
foreach ($this->header as $c => $col) {
$rowcontent .= self::tag($this->_col_tagname(), $col->attrib, $col->content);
}
$thead = $this->tagname == 'table' ? self::tag('thead', null, self::tag('tr', null, $rowcontent, parent::$common_attrib)) :
self::tag($this->_row_tagname(), array('class' => 'thead'), $rowcontent, parent::$common_attrib);
}
foreach ($this->rows as $r => $row) {
$rowcontent = '';
foreach ($row->cells as $c => $col) {
$rowcontent .= self::tag($this->_col_tagname(), $col->attrib, $col->content);
}
if ($r < $this->rowindex || count($row->cells)) {
$tbody .= self::tag($this->_row_tagname(), $row->attrib, $rowcontent, parent::$common_attrib);
}
}
if ($this->attrib['rowsonly']) {
return $tbody;
}
// add <tbody>
$this->content = $thead . ($this->tagname == 'table' ? self::tag('tbody', null, $tbody) : $tbody);
unset($this->attrib['cols'], $this->attrib['rowsonly']);
return parent::show();
}
/**
* Count number of rows
*
* @return The number of rows
*/
public function size()
{
return count($this->rows);
}
/**
* Remove table body (all rows)
*/
public function remove_body()
{
$this->rows = array();
$this->rowindex = 0;
}
/**
* Getter for the corresponding tag name for table row elements
*/
private function _row_tagname()
{
static $row_tagnames = array('table' => 'tr', 'ul' => 'li', '*' => 'div');
return $row_tagnames[$this->tagname] ?: $row_tagnames['*'];
}
/**
* Getter for the corresponding tag name for table cell elements
*/
private function _col_tagname()
{
static $col_tagnames = array('table' => 'td', '*' => 'span');
return $col_tagnames[$this->tagname] ?: $col_tagnames['*'];
}
}
diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc
index d8e517387..c5e6cae4c 100644
--- a/program/localization/en_US/labels.inc
+++ b/program/localization/en_US/labels.inc
@@ -1,540 +1,541 @@
<?php
/*
+-----------------------------------------------------------------------+
| localization/<lang>/labels.inc |
| |
| Localization file of the Roundcube Webmail client |
| 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. |
| See the README file for a full license statement. |
| |
+-----------------------------------------------------------------------+
For translation see https://www.transifex.com/projects/p/roundcube-webmail/resource/labels/
*/
$labels = array();
// login page
$labels['welcome'] = 'Welcome to $product';
$labels['username'] = 'Username';
$labels['password'] = 'Password';
$labels['server'] = 'Server';
$labels['login'] = 'Login';
// taskbar
$labels['logout'] = 'Logout';
$labels['mail'] = 'Mail';
$labels['settings'] = 'Settings';
$labels['addressbook'] = 'Address Book';
// mailbox names
$labels['inbox'] = 'Inbox';
$labels['drafts'] = 'Drafts';
$labels['sent'] = 'Sent';
$labels['trash'] = 'Trash';
$labels['junk'] = 'Junk';
$labels['show_real_foldernames'] = 'Show real names for special folders';
// message listing
$labels['subject'] = 'Subject';
$labels['from'] = 'From';
$labels['sender'] = 'Sender';
$labels['to'] = 'To';
$labels['cc'] = 'Cc';
$labels['bcc'] = 'Bcc';
$labels['replyto'] = 'Reply-To';
$labels['followupto'] = 'Followup-To';
$labels['date'] = 'Date';
$labels['size'] = 'Size';
$labels['priority'] = 'Priority';
$labels['organization'] = 'Organization';
$labels['readstatus'] = 'Read status';
$labels['listoptions'] = 'List options...';
$labels['mailboxlist'] = 'Folders';
$labels['messagesfromto'] = 'Messages $from to $to of $count';
$labels['threadsfromto'] = 'Threads $from to $to of $count';
$labels['messagenrof'] = 'Message $nr of $count';
$labels['fromtoshort'] = '$from – $to of $count';
$labels['copy'] = 'Copy';
$labels['move'] = 'Move';
$labels['moveto'] = 'Move to...';
$labels['download'] = 'Download';
$labels['open'] = 'Open';
$labels['showattachment'] = 'Show';
$labels['showanyway'] = 'Show it anyway';
$labels['filename'] = 'File name';
$labels['filesize'] = 'File size';
$labels['addtoaddressbook'] = 'Add to address book';
// weekdays short
$labels['sun'] = 'Sun';
$labels['mon'] = 'Mon';
$labels['tue'] = 'Tue';
$labels['wed'] = 'Wed';
$labels['thu'] = 'Thu';
$labels['fri'] = 'Fri';
$labels['sat'] = 'Sat';
// weekdays long
$labels['sunday'] = 'Sunday';
$labels['monday'] = 'Monday';
$labels['tuesday'] = 'Tuesday';
$labels['wednesday'] = 'Wednesday';
$labels['thursday'] = 'Thursday';
$labels['friday'] = 'Friday';
$labels['saturday'] = 'Saturday';
// months short
$labels['jan'] = 'Jan';
$labels['feb'] = 'Feb';
$labels['mar'] = 'Mar';
$labels['apr'] = 'Apr';
$labels['may'] = 'May';
$labels['jun'] = 'Jun';
$labels['jul'] = 'Jul';
$labels['aug'] = 'Aug';
$labels['sep'] = 'Sep';
$labels['oct'] = 'Oct';
$labels['nov'] = 'Nov';
$labels['dec'] = 'Dec';
// months long
$labels['longjan'] = 'January';
$labels['longfeb'] = 'February';
$labels['longmar'] = 'March';
$labels['longapr'] = 'April';
$labels['longmay'] = 'May';
$labels['longjun'] = 'June';
$labels['longjul'] = 'July';
$labels['longaug'] = 'August';
$labels['longsep'] = 'September';
$labels['longoct'] = 'October';
$labels['longnov'] = 'November';
$labels['longdec'] = 'December';
$labels['today'] = 'Today';
// toolbar buttons
$labels['refresh'] = 'Refresh';
$labels['checkmail'] = 'Check for new messages';
$labels['compose'] = 'Compose';
$labels['writenewmessage'] = 'Create a new message';
$labels['reply'] = 'Reply';
$labels['replytomessage'] = 'Reply to sender';
$labels['replytoallmessage'] = 'Reply to list or to sender and all recipients';
$labels['replyall'] = 'Reply all';
$labels['replylist'] = 'Reply list';
$labels['forward'] = 'Forward';
$labels['forwardinline'] = 'Forward inline';
$labels['forwardattachment'] = 'Forward as attachment';
$labels['forwardmessage'] = 'Forward the message';
$labels['deletemessage'] = 'Delete message';
$labels['movemessagetotrash'] = 'Move message to trash';
$labels['printmessage'] = 'Print this message';
$labels['previousmessage'] = 'Show previous message';
$labels['firstmessage'] = 'Show first message';
$labels['nextmessage'] = 'Show next message';
$labels['lastmessage'] = 'Show last message';
$labels['backtolist'] = 'Back to message list';
$labels['viewsource'] = 'Show source';
$labels['mark'] = 'Mark';
$labels['markmessages'] = 'Mark messages';
$labels['markread'] = 'As read';
$labels['markunread'] = 'As unread';
$labels['markflagged'] = 'As flagged';
$labels['markunflagged'] = 'As unflagged';
$labels['moreactions'] = 'More actions...';
$labels['more'] = 'More';
$labels['back'] = 'Back';
$labels['options'] = 'Options';
$labels['select'] = 'Select';
$labels['all'] = 'All';
$labels['none'] = 'None';
$labels['currpage'] = 'Current page';
$labels['unread'] = 'Unread';
$labels['flagged'] = 'Flagged';
$labels['unanswered'] = 'Unanswered';
$labels['withattachment'] = 'With attachment';
$labels['deleted'] = 'Deleted';
$labels['undeleted'] = 'Not deleted';
$labels['invert'] = 'Invert';
$labels['filter'] = 'Filter';
$labels['list'] = 'List';
$labels['threads'] = 'Threads';
$labels['expand-all'] = 'Expand All';
$labels['expand-unread'] = 'Expand Unread';
$labels['collapse-all'] = 'Collapse All';
$labels['threaded'] = 'Threaded';
$labels['autoexpand_threads'] = 'Expand message threads';
$labels['do_expand'] = 'all threads';
$labels['expand_only_unread'] = 'only with unread messages';
$labels['fromto'] = 'From/To';
$labels['flag'] = 'Flag';
$labels['attachment'] = 'Attachment';
$labels['nonesort'] = 'None';
$labels['sentdate'] = 'Sent date';
$labels['arrival'] = 'Arrival date';
$labels['asc'] = 'ascending';
$labels['desc'] = 'descending';
$labels['listcolumns'] = 'List columns';
$labels['listsorting'] = 'Sorting column';
$labels['listorder'] = 'Sorting order';
$labels['listmode'] = 'List view mode';
$labels['folderactions'] = 'Folder actions...';
$labels['compact'] = 'Compact';
$labels['empty'] = 'Empty';
+$labels['importmessages'] = 'Import messages';
$labels['quota'] = 'Disk usage';
$labels['unknown'] = 'unknown';
$labels['unlimited'] = 'unlimited';
$labels['quicksearch'] = 'Quick search';
$labels['resetsearch'] = 'Reset search';
$labels['searchmod'] = 'Search modifiers';
$labels['msgtext'] = 'Entire message';
$labels['body'] = 'Body';
$labels['openinextwin'] = 'Open in new window';
$labels['emlsave'] = 'Download (.eml)';
$labels['changeformattext'] = 'Display in plain text format';
$labels['changeformathtml'] = 'Display in HTML format';
// message compose
$labels['editasnew'] = 'Edit as new';
$labels['send'] = 'Send';
$labels['sendmessage'] = 'Send message';
$labels['savemessage'] = 'Save as draft';
$labels['addattachment'] = 'Attach a file';
$labels['charset'] = 'Charset';
$labels['editortype'] = 'Editor type';
$labels['returnreceipt'] = 'Return receipt';
$labels['dsn'] = 'Delivery status notification';
$labels['mailreplyintro'] = 'On $date, $sender wrote:';
$labels['originalmessage'] = 'Original Message';
$labels['editidents'] = 'Edit identities';
$labels['spellcheck'] = 'Spell';
$labels['checkspelling'] = 'Check spelling';
$labels['resumeediting'] = 'Resume editing';
$labels['revertto'] = 'Revert to';
$labels['attach'] = 'Attach';
$labels['attachments'] = 'Attachments';
$labels['upload'] = 'Upload';
$labels['uploadprogress'] = '$percent ($current from $total)';
$labels['close'] = 'Close';
$labels['messageoptions'] = 'Message options...';
$labels['low'] = 'Low';
$labels['lowest'] = 'Lowest';
$labels['normal'] = 'Normal';
$labels['high'] = 'High';
$labels['highest'] = 'Highest';
$labels['nosubject'] = '(no subject)';
$labels['showimages'] = 'Display images';
$labels['alwaysshow'] = 'Always show images from $sender';
$labels['isdraft'] = 'This is a draft message.';
$labels['andnmore'] = '$nr more...';
$labels['togglemoreheaders'] = 'Show more message headers';
$labels['togglefullheaders'] = 'Toggle raw message headers';
$labels['htmltoggle'] = 'HTML';
$labels['plaintoggle'] = 'Plain text';
$labels['savesentmessagein'] = 'Save sent message in';
$labels['dontsave'] = 'don\'t save';
$labels['maxuploadsize'] = 'Maximum allowed file size is $size';
$labels['addcc'] = 'Add Cc';
$labels['addbcc'] = 'Add Bcc';
$labels['addreplyto'] = 'Add Reply-To';
$labels['addfollowupto'] = 'Add Followup-To';
// mdn
$labels['mdnrequest'] = 'The sender of this message has asked to be notified when you read this message. Do you wish to notify the sender?';
$labels['receiptread'] = 'Return Receipt (read)';
$labels['yourmessage'] = 'This is a Return Receipt for your message';
$labels['receiptnote'] = 'Note: This receipt only acknowledges that the message was displayed on the recipient\'s computer. There is no guarantee that the recipient has read or understood the message contents.';
// address boook
$labels['name'] = 'Display Name';
$labels['firstname'] = 'First Name';
$labels['surname'] = 'Last Name';
$labels['middlename'] = 'Middle Name';
$labels['nameprefix'] = 'Prefix';
$labels['namesuffix'] = 'Suffix';
$labels['nickname'] = 'Nickname';
$labels['jobtitle'] = 'Job Title';
$labels['department'] = 'Department';
$labels['gender'] = 'Gender';
$labels['maidenname'] = 'Maiden Name';
$labels['email'] = 'Email';
$labels['phone'] = 'Phone';
$labels['address'] = 'Address';
$labels['street'] = 'Street';
$labels['locality'] = 'City';
$labels['zipcode'] = 'ZIP Code';
$labels['region'] = 'State/Province';
$labels['country'] = 'Country';
$labels['birthday'] = 'Birthday';
$labels['anniversary'] = 'Anniversary';
$labels['website'] = 'Website';
$labels['instantmessenger'] = 'IM';
$labels['notes'] = 'Notes';
$labels['male'] = 'male';
$labels['female'] = 'female';
$labels['manager'] = 'Manager';
$labels['assistant'] = 'Assistant';
$labels['spouse'] = 'Spouse';
$labels['allfields'] = 'All fields';
$labels['search'] = 'Search';
$labels['advsearch'] = 'Advanced Search';
$labels['advanced'] = 'Advanced';
$labels['other'] = 'Other';
$labels['typehome'] = 'Home';
$labels['typework'] = 'Work';
$labels['typeother'] = 'Other';
$labels['typemobile'] = 'Mobile';
$labels['typemain'] = 'Main';
$labels['typehomefax'] = 'Home Fax';
$labels['typeworkfax'] = 'Work Fax';
$labels['typecar'] = 'Car';
$labels['typepager'] = 'Pager';
$labels['typevideo'] = 'Video';
$labels['typeassistant'] = 'Assistant';
$labels['typehomepage'] = 'Home Page';
$labels['typeblog'] = 'Blog';
$labels['typeprofile'] = 'Profile';
$labels['addfield'] = 'Add field...';
$labels['addcontact'] = 'Add new contact';
$labels['editcontact'] = 'Edit contact';
$labels['contacts'] = 'Contacts';
$labels['contactproperties'] = 'Contact properties';
$labels['personalinfo'] = 'Personal information';
$labels['edit'] = 'Edit';
$labels['cancel'] = 'Cancel';
$labels['save'] = 'Save';
$labels['delete'] = 'Delete';
$labels['rename'] = 'Rename';
$labels['addphoto'] = 'Add';
$labels['replacephoto'] = 'Replace';
$labels['uploadphoto'] = 'Upload photo';
$labels['newcontact'] = 'Create new contact card';
$labels['deletecontact'] = 'Delete selected contacts';
$labels['composeto'] = 'Compose mail to';
$labels['contactsfromto'] = 'Contacts $from to $to of $count';
$labels['print'] = 'Print';
$labels['export'] = 'Export';
$labels['exportall'] = 'Export all';
$labels['exportsel'] = 'Export selected';
$labels['exportvcards'] = 'Export contacts in vCard format';
$labels['newcontactgroup'] = 'Create new contact group';
$labels['grouprename'] = 'Rename group';
$labels['groupdelete'] = 'Delete group';
$labels['groupremoveselected'] = 'Remove selected contacts from group';
$labels['previouspage'] = 'Show previous page';
$labels['firstpage'] = 'Show first page';
$labels['nextpage'] = 'Show next page';
$labels['lastpage'] = 'Show last page';
$labels['group'] = 'Group';
$labels['groups'] = 'Groups';
$labels['personaladrbook'] = 'Personal Addresses';
$labels['searchsave'] = 'Save search';
$labels['searchdelete'] = 'Delete search';
$labels['import'] = 'Import';
$labels['importcontacts'] = 'Import contacts';
$labels['importfromfile'] = 'Import from file:';
$labels['importtarget'] = 'Add new contacts to address book:';
$labels['importreplace'] = 'Replace the entire address book';
$labels['importdesc'] = 'You can upload contacts from an existing address book.<br/>We currently support importing addresses from the <a href="http://en.wikipedia.org/wiki/VCard">vCard</a> or CSV (comma-separated) data format.';
$labels['done'] = 'Done';
// settings
$labels['settingsfor'] = 'Settings for';
$labels['about'] = 'About';
$labels['preferences'] = 'Preferences';
$labels['userpreferences'] = 'User preferences';
$labels['editpreferences'] = 'Edit user preferences';
$labels['identities'] = 'Identities';
$labels['manageidentities'] = 'Manage identities for this account';
$labels['newidentity'] = 'New identity';
$labels['newitem'] = 'New item';
$labels['edititem'] = 'Edit item';
$labels['preferhtml'] = 'Display HTML';
$labels['defaultcharset'] = 'Default Character Set';
$labels['htmlmessage'] = 'HTML Message';
$labels['messagepart'] = 'Part';
$labels['digitalsig'] = 'Digital Signature';
$labels['dateformat'] = 'Date format';
$labels['timeformat'] = 'Time format';
$labels['prettydate'] = 'Pretty dates';
$labels['setdefault'] = 'Set default';
$labels['autodetect'] = 'Auto';
$labels['language'] = 'Language';
$labels['timezone'] = 'Time zone';
$labels['pagesize'] = 'Rows per page';
$labels['signature'] = 'Signature';
$labels['dstactive'] = 'Daylight saving time';
$labels['showinextwin'] = 'Open message in a new window';
$labels['composeextwin'] = 'Compose in a new window';
$labels['htmleditor'] = 'Compose HTML messages';
$labels['htmlonreply'] = 'on reply to HTML message';
$labels['htmlonreplyandforward'] = 'on forward or reply to HTML message';
$labels['htmlsignature'] = 'HTML signature';
$labels['showemail'] = 'Show email address with display name';
$labels['previewpane'] = 'Show preview pane';
$labels['skin'] = 'Interface skin';
$labels['logoutclear'] = 'Clear Trash on logout';
$labels['logoutcompact'] = 'Compact Inbox on logout';
$labels['uisettings'] = 'User Interface';
$labels['serversettings'] = 'Server Settings';
$labels['mailboxview'] = 'Mailbox View';
$labels['mdnrequests'] = 'On request for return receipt';
$labels['askuser'] = 'ask me';
$labels['autosend'] = 'send receipt';
$labels['autosendknown'] = 'send receipt to my contacts, otherwise ask me';
$labels['autosendknownignore'] = 'send receipt to my contacts, otherwise ignore';
$labels['ignore'] = 'ignore';
$labels['readwhendeleted'] = 'Mark the message as read on delete';
$labels['flagfordeletion'] = 'Flag the message for deletion instead of delete';
$labels['skipdeleted'] = 'Do not show deleted messages';
$labels['deletealways'] = 'If moving messages to Trash fails, delete them';
$labels['deletejunk'] = 'Directly delete messages in Junk';
$labels['showremoteimages'] = 'Display remote inline images';
$labels['fromknownsenders'] = 'from known senders';
$labels['always'] = 'always';
$labels['showinlineimages'] = 'Display attached images below the message';
$labels['autosavedraft'] = 'Automatically save draft';
$labels['everynminutes'] = 'every $n minute(s)';
$labels['refreshinterval'] = 'Refresh (check for new messages, etc.)';
$labels['never'] = 'never';
$labels['immediately'] = 'immediately';
$labels['messagesdisplaying'] = 'Displaying Messages';
$labels['messagescomposition'] = 'Composing Messages';
$labels['mimeparamfolding'] = 'Attachment names';
$labels['2231folding'] = 'Full RFC 2231 (Thunderbird)';
$labels['miscfolding'] = 'RFC 2047/2231 (MS Outlook)';
$labels['2047folding'] = 'Full RFC 2047 (other)';
$labels['force7bit'] = 'Use MIME encoding for 8-bit characters';
$labels['advancedoptions'] = 'Advanced options';
$labels['focusonnewmessage'] = 'Focus browser window on new message';
$labels['checkallfolders'] = 'Check all folders for new messages';
$labels['displaynext'] = 'After message delete/move display the next message';
$labels['defaultfont'] = 'Default font of HTML message';
$labels['mainoptions'] = 'Main Options';
$labels['browseroptions'] = 'Browser Options';
$labels['section'] = 'Section';
$labels['maintenance'] = 'Maintenance';
$labels['newmessage'] = 'New Message';
$labels['signatureoptions'] = 'Signature Options';
$labels['whenreplying'] = 'When replying';
$labels['replyempty'] = 'do not quote the original message';
$labels['replytopposting'] = 'start new message above the quote';
$labels['replybottomposting'] = 'start new message below the quote';
$labels['replyremovesignature'] = 'When replying remove original signature from message';
$labels['autoaddsignature'] = 'Automatically add signature';
$labels['newmessageonly'] = 'new message only';
$labels['replyandforwardonly'] = 'replies and forwards only';
$labels['insertsignature'] = 'Insert signature';
$labels['previewpanemarkread'] = 'Mark previewed messages as read';
$labels['afternseconds'] = 'after $n seconds';
$labels['reqmdn'] = 'Always request a return receipt';
$labels['reqdsn'] = 'Always request a delivery status notification';
$labels['replysamefolder'] = 'Place replies in the folder of the message being replied to';
$labels['defaultabook'] = 'Default address book';
$labels['autocompletesingle'] = 'Skip alternative email addresses in autocompletion';
$labels['listnamedisplay'] = 'List contacts as';
$labels['spellcheckbeforesend'] = 'Check spelling before sending a message';
$labels['spellcheckoptions'] = 'Spellcheck Options';
$labels['spellcheckignoresyms'] = 'Ignore words with symbols';
$labels['spellcheckignorenums'] = 'Ignore words with numbers';
$labels['spellcheckignorecaps'] = 'Ignore words with all letters capitalized';
$labels['addtodict'] = 'Add to dictionary';
$labels['mailtoprotohandler'] = 'Register protocol handler for mailto: links';
$labels['standardwindows'] = 'Handle popups as standard windows';
$labels['forwardmode'] = 'Messages forwarding';
$labels['inline'] = 'inline';
$labels['asattachment'] = 'as attachment';
$labels['folder'] = 'Folder';
$labels['folders'] = 'Folders';
$labels['foldername'] = 'Folder name';
$labels['subscribed'] = 'Subscribed';
$labels['messagecount'] = 'Messages';
$labels['create'] = 'Create';
$labels['createfolder'] = 'Create new folder';
$labels['managefolders'] = 'Manage folders';
$labels['specialfolders'] = 'Special Folders';
$labels['properties'] = 'Properties';
$labels['folderproperties'] = 'Folder properties';
$labels['parentfolder'] = 'Parent folder';
$labels['location'] = 'Location';
$labels['info'] = 'Information';
$labels['getfoldersize'] = 'Click to get folder size';
$labels['changesubscription'] = 'Click to change subscription';
$labels['foldertype'] = 'Folder Type';
$labels['personalfolder'] = 'Private Folder';
$labels['otherfolder'] = 'Other User\'s Folder';
$labels['sharedfolder'] = 'Public Folder';
$labels['sortby'] = 'Sort by';
$labels['sortasc'] = 'Sort ascending';
$labels['sortdesc'] = 'Sort descending';
$labels['undo'] = 'Undo';
$labels['installedplugins'] = 'Installed plugins';
$labels['plugin'] = 'Plugin';
$labels['version'] = 'Version';
$labels['source'] = 'Source';
$labels['license'] = 'License';
$labels['support'] = 'Get support';
// units
$labels['B'] = 'B';
$labels['KB'] = 'KB';
$labels['MB'] = 'MB';
$labels['GB'] = 'GB';
// character sets
$labels['unicode'] = 'Unicode';
$labels['english'] = 'English';
$labels['westerneuropean'] = 'Western European';
$labels['easterneuropean'] = 'Eastern European';
$labels['southeasterneuropean'] = 'South-Eastern European';
$labels['baltic'] = 'Baltic';
$labels['cyrillic'] = 'Cyrillic';
$labels['arabic'] = 'Arabic';
$labels['greek'] = 'Greek';
$labels['hebrew'] = 'Hebrew';
$labels['turkish'] = 'Turkish';
$labels['nordic'] = 'Nordic';
$labels['thai'] = 'Thai';
$labels['celtic'] = 'Celtic';
$labels['vietnamese'] = 'Vietnamese';
$labels['japanese'] = 'Japanese';
$labels['korean'] = 'Korean';
$labels['chinese'] = 'Chinese';
?>
diff --git a/program/localization/en_US/messages.inc b/program/localization/en_US/messages.inc
index f9b5e00a6..16f4c67bf 100644
--- a/program/localization/en_US/messages.inc
+++ b/program/localization/en_US/messages.inc
@@ -1,171 +1,173 @@
<?php
/*
+-----------------------------------------------------------------------+
| localization/<lang>/messages.inc |
| |
| Localization file of the Roundcube Webmail client |
| 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. |
| See the README file for a full license statement. |
| |
+-----------------------------------------------------------------------+
For translation see https://www.transifex.com/projects/p/roundcube-webmail/resource/messages/
*/
$messages = array();
$messages['errortitle'] = 'An error occurred!';
$messages['loginfailed'] = 'Login failed.';
$messages['cookiesdisabled'] = 'Your browser does not accept cookies.';
$messages['sessionerror'] = 'Your session is invalid or expired.';
$messages['storageerror'] = 'Connection to storage server failed.';
$messages['servererror'] = 'Server Error!';
$messages['servererrormsg'] = 'Server Error: $msg';
$messages['dberror'] = 'Database Error!';
$messages['requesttimedout'] = 'Request timed out';
$messages['errorreadonly'] = 'Unable to perform operation. Folder is read-only.';
$messages['errornoperm'] = 'Unable to perform operation. Permission denied.';
$messages['erroroverquota'] = 'Unable to perform operation. No free disk space.';
$messages['erroroverquotadelete'] = 'No free disk space. Use SHIFT+DEL to delete a message.';
$messages['invalidrequest'] = 'Invalid request! No data was saved.';
$messages['invalidhost'] = 'Invalid server name.';
$messages['nomessagesfound'] = 'No messages found in this mailbox.';
$messages['loggedout'] = 'You have successfully terminated the session. Good bye!';
$messages['mailboxempty'] = 'Mailbox is empty.';
$messages['refreshing'] = 'Refreshing...';
$messages['loading'] = 'Loading...';
$messages['uploading'] = 'Uploading file...';
$messages['uploadingmany'] = 'Uploading files...';
$messages['loadingdata'] = 'Loading data...';
$messages['checkingmail'] = 'Checking for new messages...';
$messages['sendingmessage'] = 'Sending message...';
$messages['messagesent'] = 'Message sent successfully.';
$messages['savingmessage'] = 'Saving message...';
$messages['messagesaved'] = 'Message saved to Drafts.';
$messages['successfullysaved'] = 'Successfully saved.';
$messages['addedsuccessfully'] = 'Contact added successfully to address book.';
$messages['contactexists'] = 'A contact with the same e-mail address already exists.';
$messages['contactnameexists'] = 'A contact with the same name already exists.';
$messages['blockedimages'] = 'To protect your privacy, remote images are blocked in this message.';
$messages['encryptedmessage'] = 'This is an encrypted message and can not be displayed. Sorry!';
$messages['nocontactsfound'] = 'No contacts found.';
$messages['contactnotfound'] = 'The requested contact was not found.';
$messages['contactsearchonly'] = 'Enter some search terms to find contacts';
$messages['sendingfailed'] = 'Failed to send message.';
$messages['senttooquickly'] = 'Please wait $sec sec(s). before sending this message.';
$messages['errorsavingsent'] = 'An error occured while saving sent message.';
$messages['errorsaving'] = 'An error occured while saving.';
$messages['errormoving'] = 'Could not move the message(s).';
$messages['errorcopying'] = 'Could not copy the message(s).';
$messages['errordeleting'] = 'Could not delete the message(s).';
$messages['errormarking'] = 'Could not mark the message(s).';
$messages['deletecontactconfirm'] = 'Do you really want to delete selected contact(s)?';
$messages['deletegroupconfirm'] = 'Do you really want to delete selected group?';
$messages['deletemessagesconfirm'] = 'Do you really want to delete selected message(s)?';
$messages['deletefolderconfirm'] = 'Do you really want to delete this folder?';
$messages['purgefolderconfirm'] = 'Do you really want to delete all messages in this folder?';
$messages['contactdeleting'] = 'Deleting contact(s)...';
$messages['groupdeleting'] = 'Deleting group...';
$messages['folderdeleting'] = 'Deleting folder...';
$messages['foldermoving'] = 'Moving folder...';
$messages['foldersubscribing'] = 'Subscribing folder...';
$messages['folderunsubscribing'] = 'Unsubscribing folder...';
$messages['formincomplete'] = 'The form was not completely filled out.';
$messages['noemailwarning'] = 'Please enter a valid email address.';
$messages['nonamewarning'] = 'Please enter a name.';
$messages['nopagesizewarning'] = 'Please enter a page size.';
$messages['nosenderwarning'] = 'Please enter sender e-mail address.';
$messages['norecipientwarning'] = 'Please enter at least one recipient.';
$messages['nosubjectwarning'] = 'The "Subject" field is empty. Would you like to enter one now?';
$messages['nobodywarning'] = 'Send this message without text?';
$messages['notsentwarning'] = 'Message has not been sent. Do you want to discard your message?';
$messages['noldapserver'] = 'Please select an ldap server to search.';
$messages['nosearchname'] = 'Please enter a contact name or email address.';
$messages['notuploadedwarning'] = 'Not all attachments have been uploaded yet. Please wait or cancel the upload.';
$messages['searchsuccessful'] = '$nr messages found.';
$messages['contactsearchsuccessful'] = '$nr contacts found.';
$messages['searchnomatch'] = 'Search returned no matches.';
$messages['searching'] = 'Searching...';
$messages['checking'] = 'Checking...';
$messages['nospellerrors'] = 'No spelling errors found.';
$messages['folderdeleted'] = 'Folder successfully deleted.';
$messages['foldersubscribed'] = 'Folder successfully subscribed.';
$messages['folderunsubscribed'] = 'Folder successfully unsubscribed.';
$messages['folderpurged'] = 'Folder has successfully been emptied.';
$messages['folderexpunged'] = 'Folder has successfully been compacted.';
$messages['deletedsuccessfully'] = 'Successfully deleted.';
$messages['converting'] = 'Removing formatting...';
$messages['messageopenerror'] = 'Could not load message from server.';
$messages['fileuploaderror'] = 'File upload failed.';
$messages['filesizeerror'] = 'The uploaded file exceeds the maximum size of $size.';
$messages['copysuccess'] = 'Successfully copied $nr addresses.';
$messages['copyerror'] = 'Could not copy any addresses.';
$messages['sourceisreadonly'] = 'This address source is read only.';
$messages['errorsavingcontact'] = 'Could not save the contact address.';
$messages['movingmessage'] = 'Moving message(s)...';
$messages['copyingmessage'] = 'Copying message(s)...';
$messages['copyingcontact'] = 'Copying contact(s)...';
$messages['deletingmessage'] = 'Deleting message(s)...';
$messages['markingmessage'] = 'Marking message(s)...';
$messages['addingmember'] = 'Adding contact(s) to the group...';
$messages['removingmember'] = 'Removing contact(s) from the group...';
$messages['receiptsent'] = 'Successfully sent a read receipt.';
$messages['errorsendingreceipt'] = 'Could not send the receipt.';
$messages['deleteidentityconfirm'] = 'Do you really want to delete this identity?';
$messages['nodeletelastidentity'] = 'You cannot delete this identity, it\'s your last one.';
$messages['forbiddencharacter'] = 'Folder name contains a forbidden character.';
$messages['selectimportfile'] = 'Please select a file to upload.';
$messages['addresswriterror'] = 'The selected address book is not writeable.';
$messages['contactaddedtogroup'] = 'Successfully added the contacts to this group.';
$messages['contactremovedfromgroup'] = 'Successfully removed contacts from this group.';
$messages['nogroupassignmentschanged'] = 'No group assignments changed.';
$messages['importwait'] = 'Importing, please wait...';
$messages['importformaterror'] = 'Import failed! The uploaded file is not a valid import data file.';
$messages['importconfirm'] = '<b>Successfully imported $inserted contacts</b>';
$messages['importconfirmskipped'] = '<b>Skipped $skipped existing entries</b>';
+$messages['importmessagesuccess'] = 'Successfully imported $nr messages';
+$messages['importmessageerror'] = 'Import failed! The uploaded file is not a valid message or mailbox file';
$messages['opnotpermitted'] = 'Operation not permitted!';
$messages['nofromaddress'] = 'Missing e-mail address in selected identity.';
$messages['editorwarning'] = 'Switching to the plain text editor will cause all text formatting to be lost. Do you wish to continue?';
$messages['httpreceivedencrypterror'] = 'A fatal configuration error occurred. Contact your administrator immediately. <b>Your message can not be sent.</b>';
$messages['smtpconnerror'] = 'SMTP Error ($code): Connection to server failed.';
$messages['smtpautherror'] = 'SMTP Error ($code): Authentication failed.';
$messages['smtpfromerror'] = 'SMTP Error ($code): Failed to set sender "$from" ($msg).';
$messages['smtptoerror'] = 'SMTP Error ($code): Failed to add recipient "$to" ($msg).';
$messages['smtprecipientserror'] = 'SMTP Error: Unable to parse recipients list.';
$messages['smtperror'] = 'SMTP Error: $msg';
$messages['emailformaterror'] = 'Invalid e-mail address: $email';
$messages['toomanyrecipients'] = 'Too many recipients. Reduce the number of recipients to $max.';
$messages['maxgroupmembersreached'] = 'The number of group members exceeds the maximum of $max.';
$messages['internalerror'] = 'An internal error occured. Please try again.';
$messages['contactdelerror'] = 'Could not delete contact(s).';
$messages['contactdeleted'] = 'Contact(s) deleted successfully.';
$messages['contactrestoreerror'] = 'Could not restore deleted contact(s).';
$messages['contactrestored'] = 'Contact(s) restored successfully.';
$messages['groupdeleted'] = 'Group deleted successfully.';
$messages['grouprenamed'] = 'Group renamed successfully.';
$messages['groupcreated'] = 'Group created successfully.';
$messages['savedsearchdeleted'] = 'Saved search deleted successfully.';
$messages['savedsearchdeleteerror'] = 'Could not delete saved search.';
$messages['savedsearchcreated'] = 'Saved search created successfully.';
$messages['savedsearchcreateerror'] = 'Could not create saved search.';
$messages['messagedeleted'] = 'Message(s) deleted successfully.';
$messages['messagemoved'] = 'Message(s) moved successfully.';
$messages['messagecopied'] = 'Message(s) copied successfully.';
$messages['messagemarked'] = 'Message(s) marked successfully.';
$messages['autocompletechars'] = 'Enter at least $min characters for autocompletion.';
$messages['autocompletemore'] = 'More matching entries found. Please type more characters.';
$messages['namecannotbeempty'] = 'Name cannot be empty.';
$messages['nametoolong'] = 'Name is too long.';
$messages['folderupdated'] = 'Folder updated successfully.';
$messages['foldercreated'] = 'Folder created successfully.';
$messages['invalidimageformat'] = 'Not a valid image format.';
$messages['mispellingsfound'] = 'Spelling errors detected in the message.';
$messages['parentnotwritable'] = 'Unable to create/move folder into selected parent folder. No access rights.';
$messages['messagetoobig'] = 'The message part is too big to process it.';
$messages['attachmentvalidationerror'] = 'WARNING! This attachment is suspicious because its type doesn\'t match the type declared in the message. If you do not trust the sender, you shouldn\'t open it in the browser because it may contain malicious contents.<br/><br/><em>Expected: $expected; found: $detected</em>';
$messages['noscriptwarning'] = 'Warning: This webmail service requires Javascript! In order to use it please enable Javascript in your browser\'s settings.';
?>
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index 1a687f508..7b6a4829d 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -1,1956 +1,1993 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/steps/mail/func.inc |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2012, The Roundcube Dev Team |
| |
| 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: |
| Provide webmail functionality and GUI objects |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
// setup some global vars used by mail steps
$SENT_MBOX = $RCMAIL->config->get('sent_mbox');
$DRAFTS_MBOX = $RCMAIL->config->get('drafts_mbox');
$SEARCH_MODS_DEFAULT = array(
'*' => array('subject'=>1, 'from'=>1),
$SENT_MBOX => array('subject'=>1, 'to'=>1),
$DRAFTS_MBOX => array('subject'=>1, 'to'=>1)
);
// always instantiate storage object (but not connect to server yet)
$RCMAIL->storage_init();
// set imap properties and session vars
if (strlen(trim($mbox = get_input_value('_mbox', RCUBE_INPUT_GPC, true))))
$RCMAIL->storage->set_folder(($_SESSION['mbox'] = $mbox));
else if ($RCMAIL->storage)
$_SESSION['mbox'] = $RCMAIL->storage->get_folder();
if (!empty($_GET['_page']))
$RCMAIL->storage->set_page(($_SESSION['page'] = intval($_GET['_page'])));
// set default sort col/order to session
if (!isset($_SESSION['sort_col']))
$_SESSION['sort_col'] = !empty($CONFIG['message_sort_col']) ? $CONFIG['message_sort_col'] : '';
if (!isset($_SESSION['sort_order']))
$_SESSION['sort_order'] = strtoupper($CONFIG['message_sort_order']) == 'ASC' ? 'ASC' : 'DESC';
// set threads mode
$a_threading = $RCMAIL->config->get('message_threading', array());
if (isset($_GET['_threads'])) {
if ($_GET['_threads'])
$a_threading[$_SESSION['mbox']] = true;
else
unset($a_threading[$_SESSION['mbox']]);
$RCMAIL->user->save_prefs(array('message_threading' => $a_threading));
}
$RCMAIL->storage->set_threading($a_threading[$_SESSION['mbox']]);
// set message set for search result
if (!empty($_REQUEST['_search']) && isset($_SESSION['search'])
&& $_SESSION['search_request'] == $_REQUEST['_search']
) {
$RCMAIL->storage->set_search_set($_SESSION['search']);
$OUTPUT->set_env('search_request', $_REQUEST['_search']);
$OUTPUT->set_env('search_text', $_SESSION['last_text_search']);
}
// set main env variables, labels and page title
if (empty($RCMAIL->action) || $RCMAIL->action == 'list') {
// connect to storage server and trigger error on failure
$RCMAIL->storage_connect();
$mbox_name = $RCMAIL->storage->get_folder();
if (empty($RCMAIL->action)) {
// initialize searching result if search_filter is used
if ($_SESSION['search_filter'] && $_SESSION['search_filter'] != 'ALL') {
$search_request = md5($mbox_name.$_SESSION['search_filter']);
$RCMAIL->storage->search($mbox_name, $_SESSION['search_filter'], RCMAIL_CHARSET, rcmail_sort_column());
$_SESSION['search'] = $RCMAIL->storage->get_search_set();
$_SESSION['search_request'] = $search_request;
$OUTPUT->set_env('search_request', $search_request);
}
$search_mods = $RCMAIL->config->get('search_mods', $SEARCH_MODS_DEFAULT);
$OUTPUT->set_env('search_mods', $search_mods);
}
$threading = (bool) $RCMAIL->storage->get_threading();
$delimiter = $RCMAIL->storage->get_hierarchy_delimiter();
// set current mailbox and some other vars in client environment
$OUTPUT->set_env('mailbox', $mbox_name);
$OUTPUT->set_env('pagesize', $RCMAIL->storage->get_pagesize());
$OUTPUT->set_env('delimiter', $delimiter);
$OUTPUT->set_env('threading', $threading);
$OUTPUT->set_env('threads', $threading || $RCMAIL->storage->get_capability('THREAD'));
$OUTPUT->set_env('preview_pane_mark_read', $RCMAIL->config->get('preview_pane_mark_read', 0));
if ($RCMAIL->storage->get_capability('QUOTA')) {
$OUTPUT->set_env('quota', true);
}
foreach (array('delete_junk','flag_for_deletion','read_when_deleted','skip_deleted','display_next','message_extwin','compose_extwin','forward_attachment') as $prop) {
if ($CONFIG[$prop])
$OUTPUT->set_env($prop, true);
}
if ($CONFIG['trash_mbox'])
$OUTPUT->set_env('trash_mailbox', $CONFIG['trash_mbox']);
if ($CONFIG['drafts_mbox'])
$OUTPUT->set_env('drafts_mailbox', $CONFIG['drafts_mbox']);
if ($CONFIG['junk_mbox'])
$OUTPUT->set_env('junk_mailbox', $CONFIG['junk_mbox']);
if (!empty($_SESSION['browser_caps']))
$OUTPUT->set_env('browser_capabilities', $_SESSION['browser_caps']);
if (!$OUTPUT->ajax_call)
$OUTPUT->add_label('checkingmail', 'deletemessage', 'movemessagetotrash',
'movingmessage', 'copyingmessage', 'deletingmessage', 'markingmessage',
- 'copy', 'move', 'quota', 'replyall', 'replylist');
+ 'copy', 'move', 'quota', 'replyall', 'replylist', 'importwait');
$pagetitle = $RCMAIL->localize_foldername($RCMAIL->storage->mod_folder($mbox_name), true);
$pagetitle = str_replace($delimiter, " \xC2\xBB ", $pagetitle);
$OUTPUT->set_pagetitle($pagetitle);
}
/**
* Returns 'to' if current folder is configured Sent or Drafts
* or their subfolders, otherwise returns 'from'.
*
* @return string Column name
*/
function rcmail_message_list_smart_column_name()
{
global $RCMAIL;
$delim = $RCMAIL->storage->get_hierarchy_delimiter();
$mbox = $RCMAIL->storage->get_folder();
$sent_mbox = $RCMAIL->config->get('sent_mbox');
$drafts_mbox = $RCMAIL->config->get('drafts_mbox');
if (strpos($mbox.$delim, $sent_mbox.$delim) === 0 || strpos($mbox.$delim, $drafts_mbox.$delim) === 0) {
return 'to';
}
return 'from';
}
/**
* Returns configured messages list sorting column name
* The name is context-sensitive, which means if sorting is set to 'fromto'
* it will return 'from' or 'to' according to current folder type.
*
* @return string Column name
*/
function rcmail_sort_column()
{
global $RCMAIL;
if (isset($_SESSION['sort_col'])) {
$column = $_SESSION['sort_col'];
}
else {
$column = $RCMAIL->config->get('message_sort_col');
}
// get name of smart From/To column in folder context
if ($column == 'fromto') {
$column = rcmail_message_list_smart_column_name();
}
return $column;
}
/**
* Returns configured message list sorting order
*
* @return string Sorting order (ASC|DESC)
*/
function rcmail_sort_order()
{
global $RCMAIL;
if (isset($_SESSION['sort_order'])) {
return $_SESSION['sort_order'];
}
return $RCMAIL->config->get('message_sort_order');
}
/**
* return the message list as HTML table
*/
function rcmail_message_list($attrib)
{
global $CONFIG, $OUTPUT;
// add some labels to client
$OUTPUT->add_label('from', 'to');
// add id to message list table if not specified
if (!strlen($attrib['id']))
$attrib['id'] = 'rcubemessagelist';
// define list of cols to be displayed based on parameter or config
if (empty($attrib['columns'])) {
$a_show_cols = is_array($CONFIG['list_cols']) ? $CONFIG['list_cols'] : array('subject');
$OUTPUT->set_env('col_movable', !in_array('list_cols', (array)$CONFIG['dont_override']));
}
else {
$a_show_cols = preg_split('/[\s,;]+/', strip_quotes($attrib['columns']));
$attrib['columns'] = $a_show_cols;
}
// save some variables for use in ajax list
$_SESSION['list_attrib'] = $attrib;
// make sure 'threads' and 'subject' columns are present
if (!in_array('subject', $a_show_cols))
array_unshift($a_show_cols, 'subject');
if (!in_array('threads', $a_show_cols))
array_unshift($a_show_cols, 'threads');
$_SESSION['skin_path'] = $CONFIG['skin_path'];
// set client env
$OUTPUT->add_gui_object('messagelist', $attrib['id']);
$OUTPUT->set_env('autoexpand_threads', intval($CONFIG['autoexpand_threads']));
$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->include_script('list.js');
$table = new html_table($attrib);
if (!$attrib['noheader']) {
foreach (rcmail_message_list_head($attrib, $a_show_cols) as $cell)
$table->add_header(array('class' => $cell['className'], 'id' => $cell['id']), $cell['html']);
}
return $table->show();
}
/**
* return javascript commands to add rows to the message list
*/
function rcmail_js_message_list($a_headers, $insert_top=FALSE, $a_show_cols=null)
{
global $CONFIG, $RCMAIL, $OUTPUT;
if (empty($a_show_cols)) {
if (!empty($_SESSION['list_attrib']['columns']))
$a_show_cols = $_SESSION['list_attrib']['columns'];
else
$a_show_cols = is_array($CONFIG['list_cols']) ? $CONFIG['list_cols'] : array('subject');
}
else {
if (!is_array($a_show_cols))
$a_show_cols = preg_split('/[\s,;]+/', strip_quotes($a_show_cols));
$head_replace = true;
}
$mbox = $RCMAIL->storage->get_folder();
// make sure 'threads' and 'subject' columns are present
if (!in_array('subject', $a_show_cols))
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);
// Plugins may set header's list_cols/list_flags and other rcube_message_header variables
// and list columns
$plugin = $RCMAIL->plugins->exec_hook('messages_list',
array('messages' => $a_headers, 'cols' => $a_show_cols));
$a_show_cols = $plugin['cols'];
$a_headers = $plugin['messages'];
$thead = $head_replace ? rcmail_message_list_head($_SESSION['list_attrib'], $a_show_cols) : NULL;
// get name of smart From/To column in folder context
if (array_search('fromto', $a_show_cols) !== false) {
$smart_col = rcmail_message_list_smart_column_name();
}
$OUTPUT->command('set_message_coltypes', $a_show_cols, $thead, $smart_col);
if (empty($a_headers))
return;
// remove 'threads', 'attachment', 'flag', 'status' columns, we don't need them here
foreach (array('threads', 'attachment', 'flag', 'status', 'priority') as $col) {
if (($key = array_search($col, $a_show_cols)) !== FALSE)
unset($a_show_cols[$key]);
}
// loop through message headers
foreach ($a_headers as $header) {
if (empty($header))
continue;
$a_msg_cols = array();
$a_msg_flags = array();
// format each col; similar as in rcmail_message_list()
foreach ($a_show_cols as $col) {
$col_name = $col == 'fromto' ? $smart_col : $col;
if (in_array($col_name, array('from', 'to', 'cc', 'replyto')))
$cont = rcmail_address_string($header->$col_name, 3, false, null, $header->charset);
else if ($col == 'subject') {
$cont = trim(rcube_mime::decode_header($header->$col, $header->charset));
if (!$cont) $cont = rcube_label('nosubject');
$cont = Q($cont);
}
else if ($col == 'size')
$cont = show_bytes($header->$col);
else if ($col == 'date')
$cont = format_date($header->date);
else
$cont = Q($header->$col);
$a_msg_cols[$col] = $cont;
}
$a_msg_flags = array_change_key_case(array_map('intval', (array) $header->flags));
if ($header->depth)
$a_msg_flags['depth'] = $header->depth;
else if ($header->has_children)
$roots[] = $header->uid;
if ($header->parent_uid)
$a_msg_flags['parent_uid'] = $header->parent_uid;
if ($header->has_children)
$a_msg_flags['has_children'] = $header->has_children;
if ($header->unread_children)
$a_msg_flags['unread_children'] = $header->unread_children;
if ($header->others['list-post'])
$a_msg_flags['ml'] = 1;
if ($header->priority)
$a_msg_flags['prio'] = (int) $header->priority;
$a_msg_flags['ctype'] = Q($header->ctype);
$a_msg_flags['mbox'] = $mbox;
// merge with plugin result (Deprecated, use $header->flags)
if (!empty($header->list_flags) && is_array($header->list_flags))
$a_msg_flags = array_merge($a_msg_flags, $header->list_flags);
if (!empty($header->list_cols) && is_array($header->list_cols))
$a_msg_cols = array_merge($a_msg_cols, $header->list_cols);
$OUTPUT->command('add_message_row',
$header->uid,
$a_msg_cols,
$a_msg_flags,
$insert_top);
}
if ($RCMAIL->storage->get_threading()) {
$OUTPUT->command('init_threads', (array) $roots, $mbox);
}
}
/*
* Creates <THEAD> for message list table
*/
function rcmail_message_list_head($attrib, $a_show_cols)
{
global $RCMAIL;
$skin_path = $_SESSION['skin_path'];
// check to see if we have some settings for sorting
$sort_col = $_SESSION['sort_col'];
$sort_order = $_SESSION['sort_order'];
$dont_override = (array)$RCMAIL->config->get('dont_override');
$disabled_sort = in_array('message_sort_col', $dont_override);
$disabled_order = in_array('message_sort_order', $dont_override);
$RCMAIL->output->set_env('disabled_sort_col', $disabled_sort);
$RCMAIL->output->set_env('disabled_sort_order', $disabled_order);
// define sortable columns
if ($disabled_sort)
$a_sort_cols = $sort_col && !$disabled_order ? array($sort_col) : array();
else
$a_sort_cols = array('subject', 'date', 'from', 'to', 'fromto', 'size', 'cc');
if (!empty($attrib['optionsmenuicon'])) {
$onclick = 'return ' . JS_OBJECT_NAME . ".command('menu-open', 'messagelistmenu')";
if ($attrib['optionsmenuicon'] === true || $attrib['optionsmenuicon'] == 'true')
$list_menu = html::div(array('onclick' => $onclick, 'class' => 'listmenu',
'id' => 'listmenulink', 'title' => rcube_label('listoptions')));
else
$list_menu = html::a(array('href' => '#', 'onclick' => $onclick),
html::img(array('src' => $skin_path . $attrib['optionsmenuicon'],
'id' => 'listmenulink', 'title' => rcube_label('listoptions')))
);
}
else
$list_menu = '';
$cells = array();
// get name of smart From/To column in folder context
if (array_search('fromto', $a_show_cols) !== false) {
$smart_col = rcmail_message_list_smart_column_name();
}
foreach ($a_show_cols as $col) {
// get column name
switch ($col) {
case 'flag':
$col_name = '<span class="flagged">&nbsp;</span>';
break;
case 'attachment':
case 'priority':
case 'status':
$col_name = '<span class="' . $col .'">&nbsp;</span>';
break;
case 'threads':
$col_name = $list_menu;
break;
case 'fromto':
$col_name = Q(rcube_label($smart_col));
break;
default:
$col_name = Q(rcube_label($col));
}
// make sort links
if (in_array($col, $a_sort_cols))
$col_name = html::a(array('href'=>"./#sort", 'onclick' => 'return '.JS_OBJECT_NAME.".command('sort','".$col."',this)", 'title' => rcube_label('sortby')), $col_name);
else if ($col_name[0] != '<')
$col_name = '<span class="' . $col .'">' . $col_name . '</span>';
$sort_class = $col == $sort_col && !$disabled_order ? " sorted$sort_order" : '';
$class_name = $col.$sort_class;
// put it all together
$cells[] = array('className' => $class_name, 'id' => "rcm$col", 'html' => $col_name);
}
return $cells;
}
/**
* return an HTML iframe for loading mail content
*/
function rcmail_messagecontent_frame($attrib)
{
global $OUTPUT, $RCMAIL;
if (empty($attrib['id']))
$attrib['id'] = 'rcmailcontentwindow';
$attrib['name'] = $attrib['id'];
if ($RCMAIL->config->get('preview_pane'))
$OUTPUT->set_env('contentframe', $attrib['id']);
$OUTPUT->set_env('blankpage', $attrib['src'] ? $OUTPUT->abs_url($attrib['src']) : 'program/resources/blank.gif');
return $OUTPUT->frame($attrib, true);
}
function rcmail_messagecount_display($attrib)
{
global $RCMAIL;
if (!$attrib['id'])
$attrib['id'] = 'rcmcountdisplay';
$RCMAIL->output->add_gui_object('countdisplay', $attrib['id']);
$content = $RCMAIL->action != 'show' ? rcmail_get_messagecount_text() : rcube_label('loading');
return html::span($attrib, $content);
}
function rcmail_get_messagecount_text($count=NULL, $page=NULL)
{
global $RCMAIL;
if ($page === NULL) {
$page = $RCMAIL->storage->get_page();
}
$page_size = $RCMAIL->storage->get_pagesize();
$start_msg = ($page-1) * $page_size + 1;
if ($count!==NULL)
$max = $count;
else if ($RCMAIL->action)
$max = $RCMAIL->storage->count(NULL, $RCMAIL->storage->get_threading() ? 'THREADS' : 'ALL');
if ($max==0)
$out = rcube_label('mailboxempty');
else
$out = rcube_label(array('name' => $RCMAIL->storage->get_threading() ? 'threadsfromto' : 'messagesfromto',
'vars' => array('from' => $start_msg,
'to' => min($max, $start_msg + $page_size - 1),
'count' => $max)));
return Q($out);
}
function rcmail_mailbox_name_display($attrib)
{
global $RCMAIL;
if (!$attrib['id'])
$attrib['id'] = 'rcmmailboxname';
$RCMAIL->output->add_gui_object('mailboxname', $attrib['id']);
return html::span($attrib, rcmail_get_mailbox_name_text());
}
function rcmail_get_mailbox_name_text()
{
global $RCMAIL;
return rcmail_localize_foldername($RCMAIL->storage->get_folder());
}
function rcmail_send_unread_count($mbox_name, $force=false, $count=null, $mark='')
{
global $RCMAIL;
$old_unseen = rcmail_get_unseen_count($mbox_name);
if ($count === null)
$unseen = $RCMAIL->storage->count($mbox_name, 'UNSEEN', $force);
else
$unseen = $count;
if ($unseen != $old_unseen || ($mbox_name == 'INBOX'))
$RCMAIL->output->command('set_unread_count', $mbox_name, $unseen,
($mbox_name == 'INBOX'), $unseen && $mark ? $mark : '');
rcmail_set_unseen_count($mbox_name, $unseen);
return $unseen;
}
function rcmail_set_unseen_count($mbox_name, $count)
{
// @TODO: this data is doubled (session and cache tables) if caching is enabled
// Make sure we have an array here (#1487066)
if (!is_array($_SESSION['unseen_count']))
$_SESSION['unseen_count'] = array();
$_SESSION['unseen_count'][$mbox_name] = $count;
}
function rcmail_get_unseen_count($mbox_name)
{
if (is_array($_SESSION['unseen_count']) && array_key_exists($mbox_name, $_SESSION['unseen_count']))
return $_SESSION['unseen_count'][$mbox_name];
else
return null;
}
/**
* Sets message is_safe flag according to 'show_images' option value
*
* @param object rcube_message Message
*/
function rcmail_check_safe(&$message)
{
global $RCMAIL;
if (!$message->is_safe
&& ($show_images = $RCMAIL->config->get('show_images'))
&& $message->has_html_part()
) {
switch ($show_images) {
case 1: // known senders only
// get default addressbook, like in addcontact.inc
$CONTACTS = $RCMAIL->get_address_book(-1, true);
if ($CONTACTS) {
$result = $CONTACTS->search('email', $message->sender['mailto'], 1, false);
if ($result->count) {
$message->set_safe(true);
}
}
break;
case 2: // always
$message->set_safe(true);
break;
}
}
}
/**
* Cleans up the given message HTML Body (for displaying)
*
* @param string HTML
* @param array Display parameters
* @param array CID map replaces (inline images)
* @return string Clean HTML
*/
function rcmail_wash_html($html, $p, $cid_replaces)
{
global $REMOTE_OBJECTS;
$p += array('safe' => false, 'inline_html' => true);
// charset was converted to UTF-8 in rcube_storage::get_message_part(),
// change/add charset specification in HTML accordingly,
// washtml cannot work without that
$meta = '<meta http-equiv="Content-Type" content="text/html; charset='.RCMAIL_CHARSET.'" />';
// remove old meta tag and add the new one, making sure
// that it is placed in the head (#1488093)
$html = preg_replace('/<meta[^>]+charset=[a-z0-9-_]+[^>]*>/Ui', '', $html);
$html = preg_replace('/(<head[^>]*>)/Ui', '\\1'.$meta, $html, -1, $rcount);
if (!$rcount) {
$html = '<head>' . $meta . '</head>' . $html;
}
// clean HTML with washhtml by Frederic Motte
$wash_opts = array(
'show_washed' => false,
'allow_remote' => $p['safe'],
'blocked_src' => "./program/resources/blocked.gif",
'charset' => RCMAIL_CHARSET,
'cid_map' => $cid_replaces,
'html_elements' => array('body'),
);
if (!$p['inline_html']) {
$wash_opts['html_elements'] = array('html','head','title','body');
}
if ($p['safe']) {
$wash_opts['html_elements'][] = 'link';
$wash_opts['html_attribs'] = array('rel','type');
}
// overwrite washer options with options from plugins
if (isset($p['html_elements']))
$wash_opts['html_elements'] = $p['html_elements'];
if (isset($p['html_attribs']))
$wash_opts['html_attribs'] = $p['html_attribs'];
// initialize HTML washer
$washer = new rcube_washtml($wash_opts);
if (!$p['skip_washer_form_callback'])
$washer->add_callback('form', 'rcmail_washtml_callback');
// allow CSS styles, will be sanitized by rcmail_washtml_callback()
if (!$p['skip_washer_style_callback'])
$washer->add_callback('style', 'rcmail_washtml_callback');
// Remove non-UTF8 characters (#1487813)
$html = rc_utf8_clean($html);
$html = $washer->wash($html);
$REMOTE_OBJECTS = $washer->extlinks;
return $html;
}
/**
* Convert the given message part to proper HTML
* which can be displayed the message view
*
* @param object rcube_message_part Message part
* @param array Display parameters array
* @return string Formatted HTML string
*/
function rcmail_print_body($part, $p = array())
{
global $RCMAIL;
// trigger plugin hook
$data = $RCMAIL->plugins->exec_hook('message_part_before',
array('type' => $part->ctype_secondary, 'body' => $part->body, 'id' => $part->mime_id)
+ $p + array('safe' => false, 'plain' => false, 'inline_html' => true));
// convert html to text/plain
if ($data['plain'] && ($data['type'] == 'html' || $data['type'] == 'enriched')) {
if ($data['type'] == 'enriched') {
$data['body'] = rcube_enriched::to_html($data['body']);
}
$txt = new rcube_html2text($data['body'], false, true);
$body = $txt->get_text();
$part->ctype_secondary = 'plain';
}
// text/html
else if ($data['type'] == 'html') {
$body = rcmail_wash_html($data['body'], $data, $part->replaces);
$part->ctype_secondary = $data['type'];
}
// text/enriched
else if ($data['type'] == 'enriched') {
$body = rcube_enriched::to_html($data['body']);
$body = rcmail_wash_html($body, $data, $part->replaces);
$part->ctype_secondary = 'html';
}
else {
// assert plaintext
$body = $part->body;
$part->ctype_secondary = $data['type'] = 'plain';
}
// free some memory (hopefully)
unset($data['body']);
// plaintext postprocessing
if ($part->ctype_secondary == 'plain') {
if ($part->ctype_secondary == 'plain' && $part->ctype_parameters['format'] == 'flowed') {
$body = rcube_mime::unfold_flowed($body);
}
$body = rcmail_plain_body($body);
}
// allow post-processing of the message body
$data = $RCMAIL->plugins->exec_hook('message_part_after',
array('type' => $part->ctype_secondary, 'body' => $body, 'id' => $part->mime_id) + $data);
return $data['type'] == 'html' ? $data['body'] : html::tag('pre', array(), $data['body']);
}
/**
* Handle links and citation marks in plain text message
*
* @param string Plain text string
*
* @return string Formatted HTML string
*/
function rcmail_plain_body($body)
{
global $RCMAIL;
// make links and email-addresses clickable
$attribs = array('link_attribs' => array('rel' => 'noreferrer', 'target' => '_blank'));
$replacer = new rcmail_string_replacer($attribs);
// search for patterns like links and e-mail addresses and replace with tokens
$body = $replacer->replace($body);
// split body into single lines
$body = preg_split('/\r?\n/', $body);
$quote_level = 0;
$last = -1;
// find/mark quoted lines...
for ($n=0, $cnt=count($body); $n < $cnt; $n++) {
if ($body[$n][0] == '>' && preg_match('/^(>+ {0,1})+/', $body[$n], $regs)) {
$q = substr_count($regs[0], '>');
$body[$n] = substr($body[$n], strlen($regs[0]));
if ($q > $quote_level) {
$body[$n] = $replacer->get_replacement($replacer->add(
str_repeat('<blockquote>', $q - $quote_level))) . $body[$n];
$last = $n;
}
else if ($q < $quote_level) {
$body[$n] = $replacer->get_replacement($replacer->add(
str_repeat('</blockquote>', $quote_level - $q))) . $body[$n];
$last = $n;
}
}
else {
$q = 0;
if ($quote_level > 0)
$body[$n] = $replacer->get_replacement($replacer->add(
str_repeat('</blockquote>', $quote_level))) . $body[$n];
}
$quote_level = $q;
}
$body = join("\n", $body);
// quote plain text (don't use Q() here, to display entities "as is")
$table = get_html_translation_table(HTML_SPECIALCHARS);
unset($table['?']);
$body = strtr($body, $table);
// colorize signature (up to <sig_max_lines> lines)
$len = strlen($body);
$sig_max_lines = $RCMAIL->config->get('sig_max_lines', 15);
while (($sp = strrpos($body, "-- \n", $sp ? -$len+$sp-1 : 0)) !== false) {
if ($sp == 0 || $body[$sp-1] == "\n") {
// do not touch blocks with more that X lines
if (substr_count($body, "\n", $sp) < $sig_max_lines)
$body = substr($body, 0, max(0, $sp))
.'<span class="sig">'.substr($body, $sp).'</span>';
break;
}
}
// insert url/mailto links and citation tags
$body = $replacer->resolve($body);
return $body;
}
/**
* Callback function for washtml cleaning class
*/
function rcmail_washtml_callback($tagname, $attrib, $content, $washtml)
{
switch ($tagname) {
case 'form':
$out = html::div('form', $content);
break;
case 'style':
// decode all escaped entities and reduce to ascii strings
$stripped = preg_replace('/[^a-zA-Z\(:;]/', '', rcmail_xss_entity_decode($content));
// now check for evil strings like expression, behavior or url()
if (!preg_match('/expression|behavior|javascript:|import[^a]/i', $stripped)) {
if (!$washtml->get_config('allow_remote') && stripos($stripped, 'url('))
$washtml->extlinks = true;
else
$out = html::tag('style', array('type' => 'text/css'), $content);
break;
}
default:
$out = '';
}
return $out;
}
/**
* return table with message headers
*/
function rcmail_message_headers($attrib, $headers=null)
{
global $MESSAGE, $PRINT_MODE, $RCMAIL;
static $sa_attrib;
// keep header table attrib
if (is_array($attrib) && !$sa_attrib && !$attrib['valueof'])
$sa_attrib = $attrib;
else if (!is_array($attrib) && is_array($sa_attrib))
$attrib = $sa_attrib;
if (!isset($MESSAGE))
return FALSE;
// get associative array of headers object
if (!$headers) {
$headers_obj = $MESSAGE->headers;
$headers = get_object_vars($MESSAGE->headers);
}
else if (is_object($headers)) {
$headers_obj = $headers;
$headers = get_object_vars($headers_obj);
}
else {
$headers_obj = rcube_message_header::from_array($headers);
}
// show these headers
$standard_headers = array('subject', 'from', 'sender', 'to', 'cc', 'bcc', 'replyto',
'mail-reply-to', 'mail-followup-to', 'date', 'priority');
$exclude_headers = $attrib['exclude'] ? explode(',', $attrib['exclude']) : array();
$output_headers = array();
foreach ($standard_headers as $hkey) {
$ishtml = false;
if ($headers[$hkey])
$value = $headers[$hkey];
else if ($headers['others'][$hkey])
$value = $headers['others'][$hkey];
else
continue;
if (in_array($hkey, $exclude_headers))
continue;
$header_title = rcube_label(preg_replace('/(^mail-|-)/', '', $hkey));
if ($hkey == 'date') {
if ($PRINT_MODE)
$header_value = format_date($value, $RCMAIL->config->get('date_long', 'x'));
else
$header_value = format_date($value);
}
else if ($hkey == 'priority') {
if ($value) {
$header_value = html::span('prio' . $value, rcmail_localized_priority($value));
}
else
continue;
}
else if ($hkey == 'replyto') {
if ($headers['replyto'] != $headers['from']) {
$header_value = rcmail_address_string($value, $attrib['max'], true, $attrib['addicon'], $headers['charset'], $header_title);
$ishtml = true;
}
else
continue;
}
else if ($hkey == 'mail-reply-to') {
if ($headers['mail-replyto'] != $headers['reply-to']
&& $headers['reply-to'] != $headers['from']
) {
$header_value = rcmail_address_string($value, $attrib['max'], true, $attrib['addicon'], $headers['charset'], $header_title);
$ishtml = true;
}
else
continue;
}
else if ($hkey == 'sender') {
if ($headers['sender'] != $headers['from']) {
$header_value = rcmail_address_string($value, $attrib['max'], true, $attrib['addicon'], $headers['charset'], $header_title);
$ishtml = true;
}
else
continue;
}
else if ($hkey == 'mail-followup-to') {
$header_value = rcmail_address_string($value, $attrib['max'], true, $attrib['addicon'], $headers['charset'], $header_title);
$ishtml = true;
}
else if (in_array($hkey, array('from', 'to', 'cc', 'bcc'))) {
$header_value = rcmail_address_string($value, $attrib['max'], true, $attrib['addicon'], $headers['charset'], $header_title);
$ishtml = true;
}
else if ($hkey == 'subject' && empty($value))
$header_value = rcube_label('nosubject');
else
$header_value = trim(rcube_mime::decode_header($value, $headers['charset']));
$output_headers[$hkey] = array(
'title' => $header_title,
'value' => $header_value,
'raw' => $value,
'html' => $ishtml,
);
}
$plugin = $RCMAIL->plugins->exec_hook('message_headers_output',
array('output' => $output_headers, 'headers' => $headers_obj, 'exclude' => $exclude_headers));
// single header value is requested
if (!empty($attrib['valueof']))
return Q($plugin['output'][$attrib['valueof']]['value'], ($attrib['valueof'] == 'subject' ? 'strict' : 'show'));
// compose html table
$table = new html_table(array('cols' => 2));
foreach ($plugin['output'] as $hkey => $row) {
$table->add(array('class' => 'header-title'), Q($row['title']));
$table->add(array('class' => 'header '.$hkey), $row['html'] ? $row['value'] : Q($row['value'], ($hkey == 'subject' ? 'strict' : 'show')));
}
return $table->show($attrib);
}
/**
* Convert Priority header value into a localized string
*/
function rcmail_localized_priority($value)
{
$labels_map = array(
'1' => 'highest',
'2' => 'high',
'3' => 'normal',
'4' => 'low',
'5' => 'lowest',
);
if ($value && $labels_map[$value])
return rcube_label($labels_map[$value]);
return '';
}
/**
* return block to show full message headers
*/
function rcmail_message_full_headers($attrib, $headers=NULL)
{
global $OUTPUT;
$html = html::div(array('id' => "all-headers", 'class' => "all", 'style' => 'display:none'), html::div(array('id' => 'headers-source'), ''));
$html .= html::div(array('class' => "more-headers show-headers", 'onclick' => "return ".JS_OBJECT_NAME.".command('show-headers','',this)", 'title' => rcube_label('togglefullheaders')), '');
$OUTPUT->add_gui_object('all_headers_row', 'all-headers');
$OUTPUT->add_gui_object('all_headers_box', 'headers-source');
return html::div($attrib, $html);
}
/**
* Handler for the 'messagebody' GUI object
*
* @param array Named parameters
* @return string HTML content showing the message body
*/
function rcmail_message_body($attrib)
{
global $CONFIG, $OUTPUT, $MESSAGE, $RCMAIL, $REMOTE_OBJECTS;
if (!is_array($MESSAGE->parts) && empty($MESSAGE->body))
return '';
if (!$attrib['id'])
$attrib['id'] = 'rcmailMsgBody';
$safe_mode = $MESSAGE->is_safe || intval($_GET['_safe']);
$out = '';
$header_attrib = array();
foreach ($attrib as $attr => $value)
if (preg_match('/^headertable([a-z]+)$/i', $attr, $regs))
$header_attrib[$regs[1]] = $value;
if (!empty($MESSAGE->parts)) {
foreach ($MESSAGE->parts as $part) {
if ($part->type == 'headers') {
$out .= html::div('message-partheaders', rcmail_message_headers(sizeof($header_attrib) ? $header_attrib : null, $part->headers));
}
else if ($part->type == 'content') {
// unsupported (e.g. encrypted)
if ($part->realtype) {
if ($part->realtype == 'multipart/encrypted' || $part->realtype == 'application/pkcs7-mime') {
$out .= html::span('part-notice', rcube_label('encryptedmessage'));
}
continue;
}
else if (!$part->size) {
continue;
}
// Check if we have enough memory to handle the message in it
// #1487424: we need up to 10x more memory than the body
else if (!rcmail_mem_check($part->size * 10)) {
$out .= html::span('part-notice', rcube_label('messagetoobig'). ' '
. html::a('?_task=mail&_action=get&_download=1&_uid='.$MESSAGE->uid.'&_part='.$part->mime_id
.'&_mbox='. urlencode($RCMAIL->storage->get_folder()), rcube_label('download')));
continue;
}
if (empty($part->ctype_parameters) || empty($part->ctype_parameters['charset']))
$part->ctype_parameters['charset'] = $MESSAGE->headers->charset;
// fetch part if not available
if (!isset($part->body))
$part->body = $MESSAGE->get_part_content($part->mime_id);
// extract headers from message/rfc822 parts
if ($part->mimetype == 'message/rfc822') {
$msgpart = rcube_mime::parse_message($part->body);
if (!empty($msgpart->headers)) {
$part = $msgpart;
$out .= html::div('message-partheaders', rcmail_message_headers(sizeof($header_attrib) ? $header_attrib : null, $part->headers));
}
}
// message is cached but not exists (#1485443), or other error
if ($part->body === false) {
rcmail_message_error($MESSAGE->uid);
}
$plugin = $RCMAIL->plugins->exec_hook('message_body_prefix', array(
'part' => $part, 'prefix' => ''));
$body = rcmail_print_body($part, array('safe' => $safe_mode, 'plain' => !$CONFIG['prefer_html']));
if ($part->ctype_secondary == 'html') {
$body = rcmail_html4inline($body, $attrib['id'], 'rcmBody', $attrs, $safe_mode);
$div_attr = array('class' => 'message-htmlpart');
$style = array();
if (!empty($attrs)) {
foreach ($attrs as $a_idx => $a_val)
$style[] = $a_idx . ': ' . $a_val;
if (!empty($style))
$div_attr['style'] = implode('; ', $style);
}
$out .= html::div($div_attr, $plugin['prefix'] . $body);
}
else
$out .= html::div('message-part', $plugin['prefix'] . $body);
}
}
}
else {
// Check if we have enough memory to handle the message in it
// #1487424: we need up to 10x more memory than the body
if (!rcmail_mem_check(strlen($MESSAGE->body) * 10)) {
$out .= html::span('part-notice', rcube_label('messagetoobig'). ' '
. html::a('?_task=mail&_action=get&_download=1&_uid='.$MESSAGE->uid.'&_part=0'
.'&_mbox='. urlencode($RCMAIL->storage->get_folder()), rcube_label('download')));
}
else {
$plugin = $RCMAIL->plugins->exec_hook('message_body_prefix', array(
'part' => $MESSAGE, 'prefix' => ''));
$out .= html::div('message-part', $plugin['prefix'] . html::tag('pre', array(),
rcmail_plain_body(Q($MESSAGE->body, 'strict', false))));
}
}
// list images after mail body
if ($RCMAIL->config->get('inline_images', true) && !empty($MESSAGE->attachments)) {
$thumbnail_size = $RCMAIL->config->get('image_thumbnail_size', 240);
$client_mimetypes = (array)$RCMAIL->config->get('client_mimetypes');
foreach ($MESSAGE->attachments as $attach_prop) {
// skip inline images
if ($attach_prop->content_id && $attach_prop->disposition == 'inline') {
continue;
}
// Content-Type: image/*...
if ($mimetype = rcmail_part_image_type($attach_prop)) {
// display thumbnails
if ($thumbnail_size) {
$show_link = array(
'href' => $MESSAGE->get_part_url($attach_prop->mime_id, false),
'onclick' => sprintf(
'return %s.command(\'load-attachment\',\'%s\',this)',
JS_OBJECT_NAME,
$attach_prop->mime_id)
);
$out .= html::p('image-attachment',
html::a($show_link + array('class' => 'image-link', 'style' => sprintf('width:%dpx', $thumbnail_size)),
html::img(array(
'class' => 'image-thumbnail',
'src' => $MESSAGE->get_part_url($attach_prop->mime_id, 'image') . '&_thumb=1',
'title' => $attach_prop->filename,
'alt' => $attach_prop->filename,
'style' => sprintf('max-width:%dpx; max-height:%dpx', $thumbnail_size, $thumbnail_size),
))
) .
html::span('image-filename', Q($attach_prop->filename)) .
html::span('image-filesize', Q($RCMAIL->message_part_size($attach_prop))) .
html::span('attachment-links',
(in_array($mimetype, $client_mimetypes) ? html::a($show_link, rcube_label('showattachment')) . '&nbsp;' : '') .
html::a($show_link['href'] . '&_download=1', rcube_label('download'))
) .
html::br(array('style' => 'clear:both'))
);
}
else {
$out .= html::tag('fieldset', 'image-attachment',
html::tag('legend', 'image-filename', Q($attach_prop->filename)) .
html::p(array('align' => "center"),
html::img(array(
'src' => $MESSAGE->get_part_url($attach_prop->mime_id, 'image'),
'title' => $attach_prop->filename,
'alt' => $attach_prop->filename,
)))
);
}
}
}
}
// tell client that there are blocked remote objects
if ($REMOTE_OBJECTS && !$safe_mode)
$OUTPUT->set_env('blockedobjects', true);
return html::div($attrib, $out);
}
function rcmail_part_image_type($part)
{
$rcmail = rcmail::get_instance();
// Skip TIFF images if browser doesn't support this format...
$tiff_support = !empty($_SESSION['browser_caps']) && !empty($_SESSION['browser_caps']['tif']);
// until we can convert them to JPEG
$tiff_support = $tiff_support || $rcmail->config->get('im_convert_path');
// Content-type regexp
$mime_regex = $tiff_support ? '/^image\//i' : '/^image\/(?!tif)/i';
// Content-Type: image/*...
if (preg_match($mime_regex, $part->mimetype)) {
return rcmail_fix_mimetype($part->mimetype);
}
// Many clients use application/octet-stream, we'll detect mimetype
// by checking filename extension
// Supported image filename extensions to image type map
$types = array(
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',
'bmp' => 'image/bmp',
);
if ($tiff_support) {
$types['tif'] = 'image/tiff';
$types['tiff'] = 'image/tiff';
}
if ($part->filename
&& preg_match('/^application\/octet-stream$/i', $part->mimetype)
&& preg_match('/\.([^.]+)$/i', $part->filename, $m)
&& ($extension = strtolower($m[1]))
&& isset($types[$extension])
) {
return $types[$extension];
}
}
/**
* modify a HTML message that it can be displayed inside a HTML page
*/
function rcmail_html4inline($body, $container_id, $body_id='', &$attributes=null, $allow_remote=false)
{
$last_style_pos = 0;
$cont_id = $container_id.($body_id ? ' div.'.$body_id : '');
// find STYLE tags
while (($pos = stripos($body, '<style', $last_style_pos)) && ($pos2 = stripos($body, '</style>', $pos)))
{
$pos = strpos($body, '>', $pos) + 1;
$len = $pos2 - $pos;
// replace all css definitions with #container [def]
$styles = substr($body, $pos, $len);
$styles = rcmail_mod_css_styles($styles, $cont_id, $allow_remote);
$body = substr_replace($body, $styles, $pos, $len);
$last_style_pos = $pos2 + strlen($styles) - $len;
}
// modify HTML links to open a new window if clicked
$GLOBALS['rcmail_html_container_id'] = $container_id;
$body = preg_replace_callback('/<(a|link|area)\s+([^>]+)>/Ui', 'rcmail_alter_html_link', $body);
unset($GLOBALS['rcmail_html_container_id']);
$body = preg_replace(array(
// add comments arround html and other tags
'/(<!DOCTYPE[^>]*>)/i',
'/(<\?xml[^>]*>)/i',
'/(<\/?html[^>]*>)/i',
'/(<\/?head[^>]*>)/i',
'/(<title[^>]*>.*<\/title>)/Ui',
'/(<\/?meta[^>]*>)/i',
// quote <? of php and xml files that are specified as text/html
'/<\?/',
'/\?>/',
// replace <body> with <div>
'/<body([^>]*)>/i',
'/<\/body>/i',
),
array(
'<!--\\1-->',
'<!--\\1-->',
'<!--\\1-->',
'<!--\\1-->',
'<!--\\1-->',
'<!--\\1-->',
'&lt;?',
'?&gt;',
'<div class="'.$body_id.'"\\1>',
'</div>',
),
$body);
$attributes = array();
// Handle body attributes that doesn't play nicely with div elements
$regexp = '/<div class="' . preg_quote($body_id, '/') . '"([^>]*)/';
if (preg_match($regexp, $body, $m)) {
$attrs = $m[0];
// Get bgcolor, we'll set it as background-color of the message container
if ($m[1] && preg_match('/bgcolor=["\']*([a-z0-9#]+)["\']*/', $attrs, $mb)) {
$attributes['background-color'] = $mb[1];
$attrs = preg_replace('/bgcolor=["\']*([a-z0-9#]+)["\']*/', '', $attrs);
}
// Get background, we'll set it as background-image of the message container
if ($m[1] && preg_match('/background=["\']*([^"\'>\s]+)["\']*/', $attrs, $mb)) {
$attributes['background-image'] = 'url('.$mb[1].')';
$attrs = preg_replace('/background=["\']*([^"\'>\s]+)["\']*/', '', $attrs);
}
if (!empty($attributes)) {
$body = preg_replace($regexp, rtrim($attrs), $body, 1);
}
// handle body styles related to background image
if ($attributes['background-image']) {
// get body style
if (preg_match('/#'.preg_quote($cont_id, '/').'\s+\{([^}]+)}/i', $body, $m)) {
// get background related style
if (preg_match_all('/(background-position|background-repeat)\s*:\s*([^;]+);/i', $m[1], $ma, PREG_SET_ORDER)) {
foreach ($ma as $style)
$attributes[$style[1]] = $style[2];
}
}
}
}
// make sure there's 'rcmBody' div, we need it for proper css modification
// its name is hardcoded in rcmail_message_body() also
else {
$body = '<div class="' . $body_id . '">' . $body . '</div>';
}
return $body;
}
/**
* parse link (a, link, area) attributes and set correct target
*/
function rcmail_alter_html_link($matches)
{
global $RCMAIL;
// Support unicode/punycode in top-level domain part
$EMAIL_PATTERN = '([a-z0-9][a-z0-9\-\.\+\_]*@[^&@"\'.][^@&"\']*\\.([^\\x00-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-z0-9]{2,}))';
$tag = strtolower($matches[1]);
$attrib = parse_attrib_string($matches[2]);
$end = '>';
// Remove non-printable characters in URL (#1487805)
if ($attrib['href'])
$attrib['href'] = preg_replace('/[\x00-\x1F]/', '', $attrib['href']);
if ($tag == 'link' && preg_match('/^https?:\/\//i', $attrib['href'])) {
$tempurl = 'tmp-' . md5($attrib['href']) . '.css';
$_SESSION['modcssurls'][$tempurl] = $attrib['href'];
$attrib['href'] = $RCMAIL->url(array('task' => 'utils', 'action' => 'modcss', 'u' => $tempurl, 'c' => $GLOBALS['rcmail_html_container_id']));
$end = ' />';
}
else if (preg_match('/^mailto:'.$EMAIL_PATTERN.'(\?[^"\'>]+)?/i', $attrib['href'], $mailto)) {
$attrib['href'] = $mailto[0];
$attrib['onclick'] = sprintf(
"return %s.command('compose','%s',this)",
JS_OBJECT_NAME,
JQ($mailto[1].$mailto[3]));
}
else if (empty($attrib['href']) && !$attrib['name']) {
$attrib['href'] = './#NOP';
$attrib['onclick'] = 'return false';
}
else if (!empty($attrib['href']) && $attrib['href'][0] != '#') {
$attrib['target'] = '_blank';
}
// Better security by adding rel="noreferrer" (#1484686)
if (($tag == 'a' || $tag == 'area') && $attrib['href'] && $attrib['href'][0] != '#') {
$attrib['rel'] = 'noreferrer';
}
// allowed attributes for a|link|area tags
$allow = array('href','name','target','onclick','id','class','style','title',
'rel','type','media','alt','coords','nohref','hreflang','shape');
return "<$tag" . html::attrib_string($attrib, $allow) . $end;
}
/**
* decode address string and re-format it as HTML links
*/
function rcmail_address_string($input, $max=null, $linked=false, $addicon=null, $default_charset=null, $title=null)
{
global $RCMAIL, $PRINT_MODE, $CONFIG;
$a_parts = rcube_mime::decode_address_list($input, null, true, $default_charset);
if (!sizeof($a_parts))
return $input;
$c = count($a_parts);
$j = 0;
$out = '';
$allvalues = array();
$show_email = $RCMAIL->config->get('message_show_email');
if ($addicon && !isset($_SESSION['writeable_abook'])) {
$_SESSION['writeable_abook'] = $RCMAIL->get_address_sources(true) ? true : false;
}
foreach ($a_parts as $part) {
$j++;
$name = $part['name'];
$mailto = $part['mailto'];
$string = $part['string'];
$valid = check_email($mailto, false);
// phishing email prevention (#1488981), e.g. "valid@email.addr <phishing@email.addr>"
if (!$show_email && $valid && $name && $name != $mailto && strpos($name, '@')) {
$name = '';
}
// IDNA ASCII to Unicode
if ($name == $mailto)
$name = rcube_idn_to_utf8($name);
if ($string == $mailto)
$string = rcube_idn_to_utf8($string);
$mailto = rcube_idn_to_utf8($mailto);
if ($PRINT_MODE) {
$out .= ($out ? ', ' : '') . sprintf('%s &lt;%s&gt;', Q($name), $mailto);
// for printing we display all addresses
continue;
}
else if ($valid) {
if ($linked) {
$attrs = array(
'href' => 'mailto:' . $mailto,
'onclick' => sprintf("return %s.command('compose','%s',this)", JS_OBJECT_NAME, JQ($mailto)),
'class' => "rcmContactAddress",
);
if ($show_email && $name && $mailto) {
$content = Q($name ? sprintf('%s <%s>', $name, $mailto) : $mailto);
}
else {
$content = Q($name ? $name : $mailto);
$attrs['title'] = $mailto;
}
$address = html::a($attrs, $content);
}
else {
$address = html::span(array('title' => $mailto, 'class' => "rcmContactAddress"),
Q($name ? $name : $mailto));
}
if ($addicon && $_SESSION['writeable_abook']) {
$address .= html::a(array(
'href' => "#add",
'onclick' => sprintf("return %s.command('add-contact','%s',this)", JS_OBJECT_NAME, JQ($string)),
'title' => rcube_label('addtoaddressbook'),
'class' => 'rcmaddcontact',
),
html::img(array(
'src' => $CONFIG['skin_path'] . $addicon,
'alt' => "Add contact",
)));
}
}
else {
$address = '';
if ($name)
$address .= Q($name);
if ($mailto)
$address = trim($address . ' ' . Q($name ? sprintf('<%s>', $mailto) : $mailto));
}
$address = html::span('adr', $address);
$allvalues[] = $address;
if (!$moreadrs)
$out .= ($out ? ', ' : '') . $address;
if ($max && $j == $max && $c > $j) {
if ($linked) {
$moreadrs = $c - $j;
}
else {
$out .= '...';
break;
}
}
}
if ($moreadrs) {
$out .= ' ' . html::a(array(
'href' => '#more',
'class' => 'morelink',
'onclick' => sprintf("return %s.show_popup_dialog('%s','%s')",
JS_OBJECT_NAME,
JQ(join(', ', $allvalues)),
JQ($title))
),
Q(rcube_label(array('name' => 'andnmore', 'vars' => array('nr' => $moreadrs)))));
}
return $out;
}
/**
* Wrap text to a given number of characters per line
* but respect the mail quotation of replies messages (>).
* Finally add another quotation level by prepending the lines
* with >
*
* @param string Text to wrap
* @param int The line width
* @return string The wrapped text
*/
function rcmail_wrap_and_quote($text, $length = 72)
{
// Rebuild the message body with a maximum of $max chars, while keeping quoted message.
$max = max(75, $length + 8);
$lines = preg_split('/\r?\n/', trim($text));
$out = '';
foreach ($lines as $line) {
// don't wrap already quoted lines
if ($line[0] == '>')
$line = '>' . rtrim($line);
else if (mb_strlen($line) > $max) {
$newline = '';
foreach (explode("\n", rc_wordwrap($line, $length - 2)) as $l) {
if (strlen($l))
$newline .= '> ' . $l . "\n";
else
$newline .= ">\n";
}
$line = rtrim($newline);
}
else
$line = '> ' . $line;
// Append the line
$out .= $line . "\n";
}
return rtrim($out, "\n");
}
function rcmail_draftinfo_encode($p)
{
$parts = array();
foreach ($p as $key => $val)
$parts[] = $key . '=' . ($key == 'folder' ? base64_encode($val) : $val);
return join('; ', $parts);
}
function rcmail_draftinfo_decode($str)
{
$info = array();
foreach (preg_split('/;\s+/', $str) as $part) {
list($key, $val) = explode('=', $part, 2);
if ($key == 'folder')
$val = base64_decode($val);
$info[$key] = $val;
}
return $info;
}
function rcmail_message_part_controls($attrib)
{
global $MESSAGE, $RCMAIL;
$part = asciiwords(get_input_value('_part', RCUBE_INPUT_GPC));
if (!is_object($MESSAGE) || !is_array($MESSAGE->parts) || !($_GET['_uid'] && $_GET['_part']) || !$MESSAGE->mime_parts[$part])
return '';
$part = $MESSAGE->mime_parts[$part];
$table = new html_table(array('cols' => 3));
$filename = rcmail_attachment_name($part);
if (!empty($filename)) {
$table->add('title', Q(rcube_label('filename')));
$table->add('header', Q($filename));
$table->add('download-link', html::a(array('href' => './?'.str_replace('_frame=', '_download=', $_SERVER['QUERY_STRING'])), Q(rcube_label('download'))));
}
$table->add('title', Q(rcube_label('filesize')));
$table->add('header', Q($RCMAIL->message_part_size($part)));
return $table->show($attrib);
}
function rcmail_message_part_frame($attrib)
{
global $MESSAGE;
$part = $MESSAGE->mime_parts[asciiwords(get_input_value('_part', RCUBE_INPUT_GPC))];
$ctype_primary = strtolower($part->ctype_primary);
$attrib['src'] = './?' . str_replace('_frame=', ($ctype_primary=='text' ? '_embed=' : '_preload='), $_SERVER['QUERY_STRING']);
return html::iframe($attrib);
}
/**
* clear message composing settings
*/
function rcmail_compose_cleanup($id)
{
if (!isset($_SESSION['compose_data_'.$id]))
return;
$rcmail = rcmail::get_instance();
$rcmail->plugins->exec_hook('attachments_cleanup', array('group' => $id));
$rcmail->session->remove('compose_data_'.$id);
}
/**
* Send the MDN response
*
* @param mixed $message Original message object (rcube_message) or UID
* @param array $smtp_error SMTP error array (reference)
*
* @return boolean Send status
*/
function rcmail_send_mdn($message, &$smtp_error)
{
global $RCMAIL;
if (!is_object($message) || !is_a($message, 'rcube_message'))
$message = new rcube_message($message);
if ($message->headers->mdn_to && empty($message->headers->flags['MDNSENT']) &&
($RCMAIL->storage->check_permflag('MDNSENT') || $RCMAIL->storage->check_permflag('*')))
{
$identity = rcmail_identity_select($message);
$sender = format_email_recipient($identity['email'], $identity['name']);
$recipient = array_shift(rcube_mime::decode_address_list(
$message->headers->mdn_to, 1, true, $message->headers->charset));
$mailto = $recipient['mailto'];
$compose = new Mail_mime("\r\n");
$compose->setParam('text_encoding', 'quoted-printable');
$compose->setParam('html_encoding', 'quoted-printable');
$compose->setParam('head_encoding', 'quoted-printable');
$compose->setParam('head_charset', RCMAIL_CHARSET);
$compose->setParam('html_charset', RCMAIL_CHARSET);
$compose->setParam('text_charset', RCMAIL_CHARSET);
// compose headers array
$headers = array(
'Date' => rcmail_user_date(),
'From' => $sender,
'To' => $message->headers->mdn_to,
'Subject' => rcube_label('receiptread') . ': ' . $message->subject,
'Message-ID' => rcmail_gen_message_id(),
'X-Sender' => $identity['email'],
'References' => trim($message->headers->references . ' ' . $message->headers->messageID),
);
if ($agent = $RCMAIL->config->get('useragent'))
$headers['User-Agent'] = $agent;
if ($RCMAIL->config->get('mdn_use_from'))
$options['mdn_use_from'] = true;
$body = rcube_label("yourmessage") . "\r\n\r\n" .
"\t" . rcube_label("to") . ': ' . rcube_mime::decode_mime_string($message->headers->to, $message->headers->charset) . "\r\n" .
"\t" . rcube_label("subject") . ': ' . $message->subject . "\r\n" .
"\t" . rcube_label("sent") . ': ' . format_date($message->headers->date, $RCMAIL->config->get('date_long')) . "\r\n" .
"\r\n" . rcube_label("receiptnote") . "\r\n";
$ua = $RCMAIL->config->get('useragent', "Roundcube Webmail (Version ".RCMAIL_VERSION.")");
$report = "Reporting-UA: $ua\r\n";
if ($message->headers->to)
$report .= "Original-Recipient: {$message->headers->to}\r\n";
$report .= "Final-Recipient: rfc822; {$identity['email']}\r\n" .
"Original-Message-ID: {$message->headers->messageID}\r\n" .
"Disposition: manual-action/MDN-sent-manually; displayed\r\n";
$compose->headers($headers);
$compose->setContentType('multipart/report', array('report-type'=> 'disposition-notification'));
$compose->setTXTBody(rc_wordwrap($body, 75, "\r\n"));
$compose->addAttachment($report, 'message/disposition-notification', 'MDNPart2.txt', false, '7bit', 'inline');
$sent = rcmail_deliver_message($compose, $identity['email'], $mailto, $smtp_error, $body_file, $options);
if ($sent) {
$RCMAIL->storage->set_flag($message->uid, 'MDNSENT');
return true;
}
}
return false;
}
/**
* Detect recipient identity from specified message
*/
function rcmail_identity_select($MESSAGE, $identities = null, $compose_mode = 'reply')
{
$a_recipients = array();
$a_names = array();
if ($identities === null) {
$identities = rcmail::get_instance()->user->list_identities(null, true);
}
// extract all recipients of the reply-message
if (is_object($MESSAGE->headers) && in_array($compose_mode, array('reply', 'forward'))) {
$a_to = rcube_mime::decode_address_list($MESSAGE->headers->to, null, true, $MESSAGE->headers->charset);
foreach ($a_to as $addr) {
if (!empty($addr['mailto'])) {
$a_recipients[] = format_email($addr['mailto']);
$a_names[] = $addr['name'];
}
}
if (!empty($MESSAGE->headers->cc)) {
$a_cc = rcube_mime::decode_address_list($MESSAGE->headers->cc, null, true, $MESSAGE->headers->charset);
foreach ($a_cc as $addr) {
if (!empty($addr['mailto'])) {
$a_recipients[] = format_email($addr['mailto']);
$a_names[] = $addr['name'];
}
}
}
}
$from_idx = null;
$found_idx = null;
$default_identity = 0; // default identity is always first on the list
// Select identity
foreach ($identities as $idx => $ident) {
// use From header
if (in_array($compose_mode, array('draft', 'edit'))) {
if ($MESSAGE->headers->from == $ident['ident']) {
$from_idx = $idx;
break;
}
}
// reply to yourself
else if ($compose_mode == 'reply' && $MESSAGE->headers->from == $ident['ident']) {
$from_idx = $idx;
break;
}
// use replied message recipients
else if (($found = array_search($ident['email_ascii'], $a_recipients)) !== false) {
if ($found_idx === null) {
$found_idx = $idx;
}
// match identity name
if ($a_names[$found] && $ident['name'] && $a_names[$found] == $ident['name']) {
$from_idx = $idx;
break;
}
}
}
// If matching by name+address doesn't found any matches, get first found address (identity)
if ($from_idx === null) {
$from_idx = $found_idx;
}
// Try Return-Path
if ($from_idx === null && ($return_path = $MESSAGE->headers->others['return-path'])) {
foreach ($identities as $idx => $ident) {
$ident = str_replace('@', '=', $ident['email_ascii']) . '@';
foreach ((array)$return_path as $path) {
if (strpos($path, $ident) !== false) {
$from_idx = $idx;
break 2;
}
}
}
}
// Fallback using Delivered-To
if ($from_idx === null && ($delivered_to = $MESSAGE->headers->others['delivered-to'])) {
foreach ($identities as $idx => $ident) {
if (in_array($ident['email_ascii'], (array)$delivered_to)) {
$from_idx = $idx;
break;
}
}
}
// Fallback using Envelope-To
if ($from_idx === null && ($envelope_to = $MESSAGE->headers->others['envelope-to'])) {
foreach ($identities as $idx => $ident) {
if (in_array($ident['email_ascii'], (array)$envelope_to)) {
$from_idx = $idx;
break;
}
}
}
return $identities[$from_idx !== null ? $from_idx : $default_identity];
}
// Fixes some content-type names
function rcmail_fix_mimetype($name)
{
// Some versions of Outlook create garbage Content-Type:
// application/pdf.A520491B_3BF7_494D_8855_7FAC2C6C0608
if (preg_match('/^application\/pdf.+/', $name))
$name = 'application/pdf';
// treat image/pjpeg (image/pjpg, image/jpg) as image/jpeg (#1489097)
else if (preg_match('/^image\/p?jpe?g$/', $name))
$name = 'image/jpeg';
return $name;
}
// return attachment filename, handle empty filename case
function rcmail_attachment_name($attachment, $display = false)
{
$filename = $attachment->filename;
if ($filename === null || $filename === '') {
if ($attachment->mimetype == 'text/html') {
$filename = rcube_label('htmlmessage');
}
else {
$ext = (array) rcube_mime::get_mime_extensions($attachment->mimetype);
$ext = array_shift($ext);
$filename = rcube_label('messagepart') . ' ' . $attachment->mime_id;
if ($ext) {
$filename .= '.' . $ext;
}
}
}
$filename = preg_replace('[\r\n]', '', $filename);
// Display smart names for some known mimetypes
if ($display) {
if (preg_match('/application\/(pgp|pkcs7)-signature/i', $attachment->mimetype)) {
$filename = rcube_label('digitalsig');
}
}
return $filename;
}
function rcmail_search_filter($attrib)
{
global $OUTPUT, $CONFIG;
if (!strlen($attrib['id']))
$attrib['id'] = 'rcmlistfilter';
$attrib['onchange'] = JS_OBJECT_NAME.'.filter_mailbox(this.value)';
// Content-Type values of messages with attachments
// the same as in app.js:add_message_row()
$ctypes = array('application/', 'multipart/m', 'multipart/signed', 'multipart/report');
// Build search string of "with attachment" filter
$attachment = str_repeat(' OR', count($ctypes)-1);
foreach ($ctypes as $type) {
$attachment .= ' HEADER Content-Type ' . rcube_imap_generic::escape($type);
}
$select_filter = new html_select($attrib);
$select_filter->add(rcube_label('all'), 'ALL');
$select_filter->add(rcube_label('unread'), 'UNSEEN');
$select_filter->add(rcube_label('flagged'), 'FLAGGED');
$select_filter->add(rcube_label('unanswered'), 'UNANSWERED');
if (!$CONFIG['skip_deleted']) {
$select_filter->add(rcube_label('deleted'), 'DELETED');
$select_filter->add(rcube_label('undeleted'), 'UNDELETED');
}
$select_filter->add(rcube_label('withattachment'), $attachment);
$select_filter->add(rcube_label('priority').': '.rcube_label('highest'), 'HEADER X-PRIORITY 1');
$select_filter->add(rcube_label('priority').': '.rcube_label('high'), 'HEADER X-PRIORITY 2');
$select_filter->add(rcube_label('priority').': '.rcube_label('normal'), 'NOT HEADER X-PRIORITY 1 NOT HEADER X-PRIORITY 2 NOT HEADER X-PRIORITY 4 NOT HEADER X-PRIORITY 5');
$select_filter->add(rcube_label('priority').': '.rcube_label('low'), 'HEADER X-PRIORITY 4');
$select_filter->add(rcube_label('priority').': '.rcube_label('lowest'), 'HEADER X-PRIORITY 5');
$out = $select_filter->show($_SESSION['search_filter']);
$OUTPUT->add_gui_object('search_filter', $attrib['id']);
return $out;
}
function rcmail_message_error($uid=null)
{
global $RCMAIL;
// Set env variables for messageerror.html template
if ($RCMAIL->action == 'show') {
$mbox_name = $RCMAIL->storage->get_folder();
$RCMAIL->output->set_env('mailbox', $mbox_name);
$RCMAIL->output->set_env('uid', null);
}
// display error message
$RCMAIL->output->show_message('messageopenerror', 'error');
// ... display message error page
$RCMAIL->output->send('messageerror');
}
+function rcmail_message_import_form($attrib = array())
+{
+ global $OUTPUT;
+
+ // set defaults
+ $attrib += array('id' => 'rcmImportform', 'buttons' => 'yes');
+
+ // Get filesize, enable upload progress bar
+ $max_filesize = rcube_upload_init();
+
+ $button = new html_inputfield(array('type' => 'button'));
+ $fileinput = new html_inputfield(array(
+ 'type' => 'file',
+ 'name' => '_file[]',
+ 'size' => $attrib['attachmentfieldsize'],
+ 'multiple' => 'multiple',
+ 'accept' => ".eml, .mbox, message/rfc822, text/*",
+ ));
+
+ $out = html::div($attrib,
+ $OUTPUT->form_tag(array('id' => $attrib['id'].'Frm', 'method' => 'post', 'enctype' => 'multipart/form-data'),
+ html::tag('input', array('type' => 'hidden', 'name' => '_unlock', 'value' => '')) .
+ html::div(null, $fileinput->show()) .
+ html::div('hint', rcube_label(array('name' => 'maxuploadsize', 'vars' => array('size' => $max_filesize)))) .
+ (get_boolean($attrib['buttons']) ? html::div('buttons',
+ $button->show(rcube_label('close'), array('class' => 'button', 'onclick' => "$('#$attrib[id]').hide()")) . ' ' .
+ $button->show(rcube_label('upload'), array('class' => 'button mainaction', 'onclick' => JS_OBJECT_NAME . ".command('import-messages', this.form)"))
+ ) : '')
+ )
+ );
+
+ $OUTPUT->add_gui_object('importform', $attrib['id'].'Frm');
+ return $out;
+}
+
+
// register UI objects
$OUTPUT->add_handlers(array(
'mailboxlist' => 'rcmail_mailbox_list',
'messages' => 'rcmail_message_list',
'messagecountdisplay' => 'rcmail_messagecount_display',
'quotadisplay' => 'rcmail_quota_display',
'mailboxname' => 'rcmail_mailbox_name_display',
'messageheaders' => 'rcmail_message_headers',
'messagefullheaders' => 'rcmail_message_full_headers',
'messagebody' => 'rcmail_message_body',
'messagecontentframe' => 'rcmail_messagecontent_frame',
'messagepartframe' => 'rcmail_message_part_frame',
'messagepartcontrols' => 'rcmail_message_part_controls',
+ 'messageimportform' => 'rcmail_message_import_form',
'searchfilter' => 'rcmail_search_filter',
'searchform' => array($OUTPUT, 'search_form'),
));
// register action aliases
$RCMAIL->register_action_map(array(
'refresh' => 'check_recent.inc',
'preview' => 'show.inc',
'print' => 'show.inc',
'moveto' => 'move_del.inc',
'delete' => 'move_del.inc',
'send' => 'sendmail.inc',
'expunge' => 'folders.inc',
'purge' => 'folders.inc',
'remove-attachment' => 'attachments.inc',
'display-attachment' => 'attachments.inc',
'upload' => 'attachments.inc',
'group-expand' => 'autocomplete.inc',
));
diff --git a/program/steps/mail/import.inc b/program/steps/mail/import.inc
new file mode 100644
index 000000000..f7e7a3eb8
--- /dev/null
+++ b/program/steps/mail/import.inc
@@ -0,0 +1,105 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/mail/import.inc |
+ | |
+ | This file is part of the Roundcube Webmail client |
+ | 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. |
+ | See the README file for a full license statement. |
+ | |
+ | PURPOSE: |
+ | Save the uploaded file(s) as messages to the current IMAP folder |
+ | |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com> |
+ +-----------------------------------------------------------------------+
+*/
+
+// clear all stored output properties (like scripts and env vars)
+$OUTPUT->reset();
+
+if (is_array($_FILES['_file'])) {
+ $imported = 0;
+
+ foreach ((array)$_FILES['_file']['tmp_name'] as $i => $filepath) {
+ // Process uploaded file if there is no error
+ $err = $_FILES['_file']['error'][$i];
+
+ if (!$err) {
+ // check file content type first
+ list($mtype_primary,) = explode('/', rc_mime_content_type($filepath, $_FILES['_file']['name'][$i], $_FILES['_file']['type'][$i]));
+ if (!in_array($mtype_primary, array('text','message'))) {
+ $OUTPUT->show_message('importmessageerror', 'error');
+ continue;
+ }
+
+ // read the first few lines to detect header-like structure
+ $fp = fopen($filepath, 'r');
+ do { $line = fgets($fp); }
+ while ($line !== false && trim($line) == '');
+
+ if (!preg_match('/^From\s+-/', $line) && !preg_match('/^[a-z-_]+:\s+.+/i', $line)) {
+ $OUTPUT->show_message('importmessageerror', 'error');
+ continue;
+ }
+
+ $message = $lastline = '';
+ fseek($fp, 0);
+ while (($line = fgets($fp)) !== false) {
+ // importing mbox file, split by From - lines
+ if (preg_match('/^From\s+-/', $line) && $lastline == '') {
+ if (!empty($message)) {
+ if ($RCMAIL->storage->save_message(null, rtrim($message))) {
+ $imported++;
+ }
+ else {
+ rcube::raise_error("Failed to import message to " . $RCMAIL->storage->get_folder(), false, true);
+ }
+ $message = '';
+ }
+ continue;
+ }
+
+ $message .= $line;
+ $lastline = rtrim($line);
+ }
+
+ if (!empty($message) && $RCMAIL->storage->save_message(null, rtrim($message))) {
+ $imported++;
+ }
+ }
+
+ if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) {
+ $msg = rcube_label(array('name' => 'filesizeerror', 'vars' => array('size' => show_bytes(parse_bytes(ini_get('upload_max_filesize'))))));
+ }
+ else if ($err) {
+ $OUTPUT->show_message('fileuploaderror', 'error');
+ }
+ } // end foreach
+
+ if ($imported) {
+ $OUTPUT->show_message(rcube_label(array('name' => 'importmessagesuccess', 'nr' => $imported, 'vars' => array('nr' => $imported))), 'confirmation');
+ $OUTPUT->command('command', 'list');
+ }
+ else {
+ $OUTPUT->show_message('importmessageerror', 'error');
+ }
+}
+else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ // if filesize exceeds post_max_size then $_FILES array is empty,
+ // show filesizeerror instead of fileuploaderror
+ if ($maxsize = ini_get('post_max_size'))
+ $msg = rcube_label(array('name' => 'filesizeerror', 'vars' => array('size' => show_bytes(parse_bytes($maxsize)))));
+ else
+ $msg = rcube_label('fileuploaderror');
+
+ $OUTPUT->command('display_message', $msg, 'error');
+}
+
+// send html page with JS calls as response
+$OUTPUT->send('iframe');
+
diff --git a/skins/larry/templates/mail.html b/skins/larry/templates/mail.html
index 85cd5203b..575cb792a 100644
--- a/skins/larry/templates/mail.html
+++ b/skins/larry/templates/mail.html
@@ -1,232 +1,241 @@
<roundcube:object name="doctype" value="html5" />
<html>
<head>
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
<style type="text/css">
<roundcube:if condition="config:preview_pane == true" />
#mailview-top { height: <roundcube:exp expression="!empty(cookie:mailviewsplitter) ? cookie:mailviewsplitter-48 : 276" />px; }
#mailview-bottom { top: <roundcube:exp expression="!empty(cookie:mailviewsplitter) ? cookie:mailviewsplitter+6 : 330" />px; height: auto; }
#mailpreviewframe { display: block; }
<roundcube:endif />
</style>
</head>
<body>
<div class="minwidth">
<roundcube:include file="/includes/header.html" />
<div id="mainscreen">
<!-- toolbar -->
<div id="messagetoolbar" class="toolbar">
<roundcube:button command="checkmail" type="link" class="button checkmail disabled" classAct="button checkmail" classSel="button checkmail pressed" label="refresh" title="checkmail" />
<roundcube:include file="/includes/mailtoolbar.html" />
</div>
<div id="mailview-left">
<!-- folders list -->
<div id="folderlist-header"></div>
<div id="mailboxcontainer" class="uibox listbox">
<div id="folderlist-content" class="scroller withfooter">
<roundcube:object name="mailboxlist" id="mailboxlist" class="treelist listing" folder_filter="mail" unreadwrap="%s" />
</div>
<div id="folderlist-footer" class="boxfooter">
<roundcube:button name="mailboxmenulink" id="mailboxmenulink" type="link" title="folderactions" class="listbutton groupactions" onclick="UI.show_popup('mailboxmenu');return false" innerClass="inner" content="&#9881;" />
<roundcube:if condition="env:quota" />
<roundcube:object name="quotaDisplay" id="quotadisplay" class="countdisplay" display="text" />
<roundcube:endif />
</div>
</div>
</div>
<div id="mailview-right">
<div id="messagesearchtools">
<!-- search filter -->
<div id="searchfilter">
<roundcube:object name="searchfilter" class="searchfilter decorated" />
</div>
<!-- search box -->
<div id="quicksearchbar" class="searchbox">
<roundcube:object name="searchform" id="quicksearchbox" />
<roundcube:button name="searchmenulink" id="searchmenulink" class="iconbutton searchoptions" onclick="UI.show_popup('searchmenu');return false" title="searchmod" content=" " />
<roundcube:button command="reset-search" id="searchreset" class="iconbutton reset" title="resetsearch" content=" " />
</div>
</div>
<roundcube:if condition="config:preview_pane == true" />
<div id="mailview-top" class="uibox">
<roundcube:else />
<div id="mailview-top" class="uibox fullheight">
<roundcube:endif />
<!-- messagelist -->
<div id="messagelistcontainer" class="boxlistcontent">
<roundcube:object name="messages"
id="messagelist"
class="records-table sortheader"
optionsmenuIcon="true" />
</div>
<!-- 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 />
</div>
<div id="listselectors">
<a href="#select" id="listselectmenulink" class="menuselector" onclick="UI.show_popup('listselectmenu');return false"><span class="handle"><roundcube:label name="select" /></span></a>
<roundcube:if condition="env:threads" />
&nbsp; <a href="#threads" id="threadselectmenulink" class="menuselector" onclick="UI.show_popup('threadselectmenu');return false"><span class="handle"><roundcube:label name="threads" /></span></a>
<roundcube:endif />
</div>
<div id="countcontrols" class="pagenav dark">
<roundcube:object name="messageCountDisplay" class="countdisplay" />
<span class="pagenavbuttons">
<roundcube:button command="firstpage" type="link" class="button firstpage disabled" classAct="button firstpage" classSel="button firstpage pressed" innerClass="inner" title="firstpage" content="|&amp;lt;" />
<roundcube:button command="previouspage" type="link" class="button prevpage disabled" classAct="button prevpage" classSel="button prevpage pressed" innerClass="inner" title="previouspage" content="&amp;lt;" />
<roundcube:button command="nextpage" type="link" class="button nextpage disabled" classAct="button nextpage" classSel="button nextpage pressed" innerClass="inner" title="nextpage" content="&amp;gt;" />
<roundcube:button command="lastpage" type="link" class="button lastpage disabled" classAct="button lastpage" classSel="button lastpage pressed" innerClass="inner" title="lastpage" content="&amp;gt;|" />
</span>
</div>
<roundcube:container name="listcontrols" id="listcontrols" />
<a href="#preview" id="mailpreviewtoggle" title="<roundcube:label name='previewpane' />"></a>
</div>
</div><!-- end mailview-top -->
<div id="mailview-bottom" class="uibox">
<div id="mailpreviewframe">
<roundcube:object name="messagecontentframe" id="messagecontframe" style="width:100%; height:100%" frameborder="0" src="/watermark.html" />
</div>
<roundcube:object name="message" id="message" class="statusbar" />
</div><!-- end mailview-bottom -->
</div><!-- end mailview-right -->
</div><!-- end mainscreen -->
<div><!-- end minwidth -->
<div id="searchmenu" class="popupmenu">
<ul class="toolbarmenu">
<li><label><input type="checkbox" name="s_mods[]" value="subject" id="s_mod_subject" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="subject" /></span></label></li>
<li><label><input type="checkbox" name="s_mods[]" value="from" id="s_mod_from" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="from" /></span></label></li>
<li><label><input type="checkbox" name="s_mods[]" value="to" id="s_mod_to" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="to" /></span></label></li>
<li><label><input type="checkbox" name="s_mods[]" value="cc" id="s_mod_cc" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="cc" /></span></label></li>
<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>
</ul>
</div>
<div id="dragmessagemenu" class="popupmenu">
<ul class="toolbarmenu">
<li><roundcube:button command="moveto" onclick="return rcmail.drag_menu_action('moveto')" label="move" classAct="active" /></li>
<li><roundcube:button command="copy" onclick="return rcmail.drag_menu_action('copy')" label="copy" classAct="active" /></li>
</ul>
</div>
<div id="mailboxmenu" class="popupmenu">
<ul class="toolbarmenu" id="mailboxoptionsmenu">
<li><roundcube:button command="expunge" type="link" label="compact" classAct="active" /></li>
- <li class="separator_below"><roundcube:button command="purge" type="link" label="empty" classAct="active" /></li>
+ <li><roundcube:button command="purge" type="link" label="empty" classAct="active" /></li>
+ <li><roundcube:button name="messageimport" type="link" class="active" label="importmessages" onclick="UI.show_uploadform()" /></li>
<li><roundcube:button command="folders" task="settings" type="link" label="managefolders" classAct="active" /></li>
<roundcube:container name="mailboxoptions" id="mailboxoptionsmenu" />
</ul>
</div>
<div id="listselectmenu" class="popupmenu dropdown">
<ul class="toolbarmenu iconized">
<li><roundcube:button command="select-all" type="link" label="all" class="icon" classAct="icon active" innerclass="icon mail" /></li>
<li><roundcube:button command="select-all" type="link" prop="page" label="currpage" class="icon" classAct="icon active" innerclass="icon list" /></li>
<li><roundcube:button command="select-all" type="link" prop="unread" label="unread" class="icon" classAct="icon active" innerclass="icon unread" /></li>
<li><roundcube:button command="select-all" type="link" prop="flagged" label="flagged" class="icon" classAct="icon active" innerclass="icon flagged" /></li>
<li><roundcube:button command="select-all" type="link" prop="invert" label="invert" class="icon" classAct="icon active" innerclass="icon invert" /></li>
<li><roundcube:button command="select-none" type="link" label="none" class="icon" classAct="icon active" innerclass="icon cross" /></li>
</ul>
</div>
<div id="threadselectmenu" class="popupmenu dropdown">
<ul class="toolbarmenu">
<li><roundcube:button command="expand-all" type="link" label="expand-all" class="icon" classAct="icon active" innerclass="icon conversation" /></li>
<li><roundcube:button command="expand-unread" type="link" label="expand-unread" class="icon" classAct="icon active" innerclass="icon conversation" /></li>
<li><roundcube:button command="collapse-all" type="link" label="collapse-all" class="icon" classAct="icon active" innerclass="icon conversation" /></li>
</ul>
</div>
<div id="listoptions" class="propform popupdialog">
<roundcube:if condition="!in_array('list_cols', (array)config:dont_override)" />
<fieldset class="floating">
<legend><roundcube:label name="listcolumns" /></legend>
<ul class="proplist">
<li><label class="disabled"><input type="checkbox" name="list_col[]" value="threads" checked="checked" disabled="disabled" /> <span><roundcube:label name="threads" /></span></label></li>
<li><label class="disabled"><input type="checkbox" name="list_col[]" value="subject" checked="checked" disabled="disabled" /> <span><roundcube:label name="subject" /></span></label></li>
<li><label><input type="checkbox" name="list_col[]" value="fromto" /> <span><roundcube:label name="fromto" /></span></label></li>
<li><label><input type="checkbox" name="list_col[]" value="from" /> <span><roundcube:label name="from" /></span></label></li>
<li><label><input type="checkbox" name="list_col[]" value="to" /> <span><roundcube:label name="to" /></span></label></li>
<li><label><input type="checkbox" name="list_col[]" value="replyto" /> <span><roundcube:label name="replyto" /></span></label></li>
<li><label><input type="checkbox" name="list_col[]" value="cc" /> <span><roundcube:label name="cc" /></span></label></li>
<li><label><input type="checkbox" name="list_col[]" value="date" /> <span><roundcube:label name="date" /></span></label></li>
<li><label><input type="checkbox" name="list_col[]" value="size" /> <span><roundcube:label name="size" /></span></label></li>
<li><label><input type="checkbox" name="list_col[]" value="status" /> <span><roundcube:label name="readstatus" /></span></label></li>
<li><label><input type="checkbox" name="list_col[]" value="attachment" /> <span><roundcube:label name="attachment" /></span></label></li>
<li><label><input type="checkbox" name="list_col[]" value="flag" /> <span><roundcube:label name="flag" /></span></label></li>
<li><label><input type="checkbox" name="list_col[]" value="priority" /> <span><roundcube:label name="priority" /></span></label></li>
</ul>
</fieldset>
<roundcube:endif />
<roundcube:if condition="!in_array('message_sort_col', (array)config:dont_override)" />
<fieldset class="floating">
<legend><roundcube:label name="listsorting" /></legend>
<ul class="proplist">
<li><label><input type="radio" name="sort_col" value="" /> <span><roundcube:label name="nonesort" /></span></label></li>
<li><label><input type="radio" name="sort_col" value="arrival" /> <span><roundcube:label name="arrival" /></span></label></li>
<li><label><input type="radio" name="sort_col" value="date" /> <span><roundcube:label name="sentdate" /></span></label></li>
<li><label><input type="radio" name="sort_col" value="subject" /> <span><roundcube:label name="subject" /></span></label></li>
<li><label><input type="radio" name="sort_col" value="fromto" /> <span><roundcube:label name="fromto" /></span></label></li>
<li><label><input type="radio" name="sort_col" value="from" /> <span><roundcube:label name="from" /></span></label></li>
<li><label><input type="radio" name="sort_col" value="to" /> <span><roundcube:label name="to" /></span></label></li>
<li><label><input type="radio" name="sort_col" value="cc" /> <span><roundcube:label name="cc" /></span></label></li>
<li><label><input type="radio" name="sort_col" value="size" /> <span><roundcube:label name="size" /></span></label></li>
</ul>
</fieldset>
<roundcube:endif />
<roundcube:if condition="!in_array('message_sort_order', (array)config:dont_override)" />
<fieldset class="floating">
<legend><roundcube:label name="listorder" /></legend>
<ul class="proplist">
<li><label><input type="radio" name="sort_ord" value="ASC" /> <span><roundcube:label name="asc" /></span></label></li>
<li><label><input type="radio" name="sort_ord" value="DESC" /> <span><roundcube:label name="desc" /></span></label></li>
</ul>
</fieldset>
<roundcube:endif />
<br style="clear:both" />
<div class="formbuttons">
<roundcube:button command="menu-save" id="listmenusave" type="input" class="button mainaction" label="save" />
<roundcube:button command="menu-open" id="listmenucancel" type="input" class="button" label="cancel" />
</div>
</div>
+<div id="upload-dialog" class="propform popupdialog" title="<roundcube:label name='importmessages' />">
+ <roundcube:object name="messageimportform" id="uploadform" attachmentFieldSize="40" buttons="no" />
+ <div class="formbuttons">
+ <roundcube:button command="import-messages" type="input" class="button mainaction" label="upload" />
+ <roundcube:button name="close" type="input" class="button" label="cancel" onclick="UI.show_uploadform()" />
+ </div>
+</div>
+
<roundcube:include file="/includes/footer.html" />
</body>
</html>
diff --git a/skins/larry/ui.js b/skins/larry/ui.js
index f7428f4da..ec4d03d00 100644
--- a/skins/larry/ui.js
+++ b/skins/larry/ui.js
@@ -1,1273 +1,1274 @@
/**
* Roundcube functions for default skin interface
*
* Copyright (c) 2013, The Roundcube Dev Team
*
* The contents are subject to the Creative Commons Attribution-ShareAlike
* License. It is allowed to copy, distribute, transmit and to adapt the work
* by keeping credits to the original autors in the README file.
* See http://creativecommons.org/licenses/by-sa/3.0/ for details.
*/
function rcube_mail_ui()
{
var env = {};
var popups = {};
var popupconfig = {
forwardmenu: { editable:1 },
searchmenu: { editable:1, callback:searchmenu },
attachmentmenu: { },
listoptions: { editable:1 },
dragmessagemenu: { sticky:1 },
groupmenu: { above:1 },
mailboxmenu: { above:1 },
spellmenu: { callback: spellmenu },
// toggle: #1486823, #1486930
'attachment-form': { editable:1, above:1, toggle:!bw.ie&&!bw.linux },
'upload-form': { editable:1, toggle:!bw.ie&&!bw.linux }
};
var me = this;
var mailviewsplit;
var compose_headers = {};
// export public methods
this.set = setenv;
this.init = init;
this.init_tabs = init_tabs;
this.show_about = show_about;
this.show_popup = show_popup;
this.add_popup = add_popup;
this.set_searchmod = set_searchmod;
this.show_uploadform = show_uploadform;
this.show_header_row = show_header_row;
this.hide_header_row = hide_header_row;
this.update_quota = update_quota;
// set minimal mode on small screens (don't wait for document.ready)
if (window.$ && document.body) {
var minmode = rcmail.get_cookie('minimalmode');
if (parseInt(minmode) || (minmode === null && $(window).height() < 850)) {
$(document.body).addClass('minimal');
}
}
/**
*
*/
function setenv(key, val)
{
env[key] = val;
}
/**
* Initialize UI
* Called on document.ready
*/
function init()
{
rcmail.addEventListener('message', message_displayed);
/*** prepare minmode functions ***/
$('#taskbar a').each(function(i,elem){
$(elem).append('<span class="tooltip">' + $('.button-inner', this).html() + '</span>')
});
$('#taskbar .minmodetoggle').click(function(e){
var ismin = $(document.body).toggleClass('minimal').hasClass('minimal');
rcmail.set_cookie('minimalmode', ismin?1:0);
$(window).resize();
});
/*** mail task ***/
if (rcmail.env.task == 'mail') {
rcmail.addEventListener('menu-open', menu_open);
rcmail.addEventListener('menu-save', menu_save);
rcmail.addEventListener('responseafterlist', function(e){ switch_view_mode(rcmail.env.threading ? 'thread' : 'list') });
var dragmenu = $('#dragmessagemenu');
if (dragmenu.length) {
rcmail.gui_object('message_dragmenu', 'dragmessagemenu');
popups.dragmessagemenu = dragmenu;
}
if (rcmail.env.action == 'show' || rcmail.env.action == 'preview') {
rcmail.addEventListener('enable-command', enable_command);
rcmail.addEventListener('aftershow-headers', function() { layout_messageview(); });
rcmail.addEventListener('afterhide-headers', function() { layout_messageview(); });
$('#previewheaderstoggle').click(function(e){ toggle_preview_headers(); return false });
// add menu link for each attachment
$('#attachment-list > li').each(function() {
$(this).append($('<a class="drop">').click(function() { attachmentmenu(this); }));
});
}
else if (rcmail.env.action == 'compose') {
rcmail.addEventListener('aftertoggle-editor', function(){ window.setTimeout(function(){ layout_composeview() }, 200); });
rcmail.addEventListener('aftersend-attachment', show_uploadform);
rcmail.addEventListener('add-recipient', function(p){ show_header_row(p.field, true); });
layout_composeview();
// Show input elements with non-empty value
var f, v, field, fields = ['cc', 'bcc', 'replyto', 'followupto'];
for (f=0; f < fields.length; f++) {
v = fields[f]; field = $('#_'+v);
if (field.length) {
field.on('change', {v: v}, function(e) { if (this.value) show_header_row(e.data.v, true); });
if (field.val() != '')
show_header_row(v, true);
}
}
$('#composeoptionstoggle').click(function(){
$('#composeoptionstoggle').toggleClass('remove');
$('#composeoptions').toggle();
layout_composeview();
return false;
}).css('cursor', 'pointer');
// toggle compose options if opened in new window and they were visible before
var opener_rc = rcmail.opener();
if (opener_rc && opener_rc.env.action == 'compose' && $('#composeoptionstoggle', opener.document).hasClass('remove'))
$('#composeoptionstoggle').click();
new rcube_splitter({ id:'composesplitterv', p1:'#composeview-left', p2:'#composeview-right',
orientation:'v', relative:true, start:248, min:170, size:12, render:layout_composeview }).init();
}
else if (rcmail.env.action == 'list' || !rcmail.env.action) {
var previewframe = $('#mailpreviewframe').is(':visible');
$('#mailpreviewtoggle').addClass(previewframe ? 'enabled' : 'closed').click(function(e){ toggle_preview_pane(e); return false });
$('#maillistmode').addClass(rcmail.env.threading ? '' : 'selected').click(function(e){ switch_view_mode('list'); return false });
$('#mailthreadmode').addClass(rcmail.env.threading ? 'selected' : '').click(function(e){ switch_view_mode('thread'); return false });
mailviewsplit = new rcube_splitter({ id:'mailviewsplitter', p1:'#mailview-top', p2:'#mailview-bottom',
orientation:'h', relative:true, start:310, min:150, size:12, offset:4 });
if (previewframe)
mailviewsplit.init();
new rcube_scroller('#folderlist-content', '#folderlist-header', '#folderlist-footer');
rcmail.addEventListener('setquota', update_quota);
rcmail.addEventListener('enable-command', enable_command);
+ rcmail.addEventListener('afterimport-messages', show_uploadform);
}
if ($('#mailview-left').length) {
new rcube_splitter({ id:'mailviewsplitterv', p1:'#mailview-left', p2:'#mailview-right',
orientation:'v', relative:true, start:226, min:150, size:12, callback:render_mailboxlist, render:resize_leftcol }).init();
}
}
/*** settings task ***/
else if (rcmail.env.task == 'settings') {
rcmail.addEventListener('init', function(){
var tab = '#settingstabpreferences';
if (rcmail.env.action)
tab = '#settingstab' + (rcmail.env.action.indexOf('identity')>0 ? 'identities' : rcmail.env.action.replace(/\./g, ''));
$(tab).addClass('selected')
.children().first().removeAttr('onclick').click(function() { return false; });
});
if (rcmail.env.action == 'folders') {
new rcube_splitter({ id:'folderviewsplitter', p1:'#folderslist', p2:'#folder-details',
orientation:'v', relative:true, start:266, min:180, size:12 }).init();
new rcube_scroller('#folderslist-content', '#folderslist-header', '#folderslist-footer');
rcmail.addEventListener('setquota', update_quota);
}
else if (rcmail.env.action == 'identities') {
new rcube_splitter({ id:'identviewsplitter', p1:'#identitieslist', p2:'#identity-details',
orientation:'v', relative:true, start:266, min:180, size:12 }).init();
}
else if (rcmail.env.action == 'preferences' || !rcmail.env.action) {
new rcube_splitter({ id:'prefviewsplitter', p1:'#sectionslist', p2:'#preferences-box',
orientation:'v', relative:true, start:266, min:180, size:12 }).init();
}
}
/*** addressbook task ***/
else if (rcmail.env.task == 'addressbook') {
rcmail.addEventListener('afterupload-photo', show_uploadform);
if (rcmail.env.action == '') {
new rcube_splitter({ id:'addressviewsplitterd', p1:'#addressview-left', p2:'#addressview-right',
orientation:'v', relative:true, start:226, min:150, size:12, render:resize_leftcol }).init();
new rcube_splitter({ id:'addressviewsplitter', p1:'#addresslist', p2:'#contacts-box',
orientation:'v', relative:true, start:286, min:270, size:12 }).init();
new rcube_scroller('#directorylist-content', '#directorylist-header', '#directorylist-footer');
}
}
// set min-width to show all toolbar buttons
var screen = $('.minwidth');
if (screen.length) {
screen.css('min-width', $('.toolbar').width() + $('#quicksearchbar').parent().width() + 20);
}
// turn a group of fieldsets into tabs
$('.tabbed').each(function(idx, elem){ init_tabs(elem); })
// decorate select elements
$('select.decorated').each(function(){
if (bw.opera) {
$(this).removeClass('decorated');
return;
}
var select = $(this),
height = Math.max(select.height(), 26) - 2,
width = select.width() - 22,
title = $('option', this).first().text();
if ($('option:selected', this).val() != '')
title = $('option:selected', this).text();
var overlay = $('<a class="menuselector"><span class="handle">' + title + '</span></a>')
.css('position', 'absolute')
.offset(select.position())
.insertAfter(select);
overlay.children().width(width).height(height).css('line-height', (height - 1) + 'px');
select.change(function() {
var val = $('option:selected', this).text();
$(this).next().children().html(val);
});
var parent = select.parent();
if (parent.css('position') != 'absolute')
parent.css('position', 'relative');
// re-set original select width to fix click action and options width in some browsers
if (!bw.mz)
select.width(overlay.width());
});
$(document.body)
.bind('mouseup', body_mouseup)
.bind('keyup', function(e){
if (e.keyCode == 27) {
for (var id in popups) {
if (popups[id].is(':visible'))
show_popup(id, false);
}
}
});
$('iframe').load(function(e){
// this = iframe
try {
var doc = this.contentDocument ? this.contentDocument : this.contentWindow ? this.contentWindow.document : null;
$(doc).mouseup(body_mouseup);
}
catch (e) {
// catch possible "Permission denied" error in IE
};
})
.contents().mouseup(body_mouseup);
// don't use $(window).resize() due to some unwanted side-effects
window.onresize = resize;
resize();
}
/**
* Handler for mouse-up events on the document body.
* This will close all open popup menus
*/
function body_mouseup(e)
{
var config, obj, target = e.target;
if (target.className == 'inner')
target = e.target.parentNode;
for (var id in popups) {
obj = popups[id];
config = popupconfig[id];
if (obj.is(':visible')
&& target.id != id+'link'
&& !config.toggle
&& (!config.editable || !target_overlaps(target, obj.get(0)))
&& (!config.sticky || !rcube_mouse_is_over(e, obj.get(0)))
) {
var myid = id+'';
window.setTimeout(function(){ show_popupmenu(myid, false) }, 10);
}
}
}
/**
* Update UI on window resize
*/
function resize(e)
{
// resize in intervals to prevent lags and double onresize calls in Chrome (#1489005)
var interval = e ? 10 : 0;
if (rcmail.resize_timeout)
window.clearTimeout(rcmail.resize_timeout);
rcmail.resize_timeout = window.setTimeout(function() {
if (rcmail.env.task == 'mail') {
if (rcmail.env.action == 'show' || rcmail.env.action == 'preview')
layout_messageview();
else if (rcmail.env.action == 'compose')
layout_composeview();
}
// make iframe footer buttons float if scrolling is active
$('body.iframe .footerleft').each(function(){
var footer = $(this),
body = $(document.body),
floating = footer.hasClass('floating'),
overflow = body.outerHeight(true) > $(window).height();
if (overflow != floating) {
var action = overflow ? 'addClass' : 'removeClass';
footer[action]('floating');
body[action]('floatingbuttons');
}
});
}, interval);
}
/**
* Triggered when a new user message is displayed
*/
function message_displayed(p)
{
// show a popup dialog on errors
if (p.type == 'error' && rcmail.env.task != 'login') {
if (me.message_timer) {
window.clearTimeout(me.message_timer);
}
if (!me.messagedialog) {
me.messagedialog = $('<div>').addClass('popupdialog').hide();
}
var msg = p.message,
pos = $(p.object).offset();
pos.top -= (rcmail.env.task == 'login' ? 20 : 160);
if (me.messagedialog.is(':visible'))
msg = me.messagedialog.html() + '<p>' + p.message + '</p>';
me.messagedialog.html(msg)
.dialog({
resizable: false,
closeOnEscape: true,
dialogClass: 'popupmessage ' + p.type,
title: env.errortitle,
close: function() {
me.messagedialog.dialog('destroy').hide();
},
position: ['center', pos.top],
hide: { effect:'drop', direction:'down' },
width: 420,
minHeight: 90
}).show();
me.message_timer = window.setTimeout(function(){ me.messagedialog.dialog('close'); }, Math.max(2000, p.timeout / 2));
}
}
/**
* Adjust UI objects of the mail view screen
*/
function layout_messageview()
{
$('#messagecontent').css('top', ($('#messageheader').outerHeight() + 1) + 'px');
$('#message-objects div a').addClass('button');
if (!$('#attachment-list li').length) {
$('div.rightcol').hide();
$('div.leftcol').css('margin-right', '0');
}
}
function render_mailboxlist(splitter)
{
// TODO: implement smart shortening of long folder names
}
function resize_leftcol(splitter)
{
// STUB
}
function layout_composeview()
{
var body = $('#composebody'),
form = $('#compose-content'),
bottom = $('#composeview-bottom'),
w, h, bh, ovflw, btns = 0,
minheight = 300,
bh = (form.height() - bottom.position().top);
ovflw = minheight - bh;
btns = ovflw > -100 ? 0 : 40;
bottom.css('height', Math.max(minheight, bh) + 'px');
form.css('overflow', ovflw > 0 ? 'auto' : 'hidden');
w = body.parent().width() - 5;
h = body.parent().height() - 16;
body.width(w).height(h);
$('#composebody_tbl').width((w+8)+'px').height('').css('margin-top', '1px');
$('#composebody_ifr').width((w+8)+'px').height((h-40)+'px');
$('#googie_edit_layer').height(h+'px');
// $('#composebodycontainer')[(btns ? 'addClass' : 'removeClass')]('buttons');
// $('#composeformbuttons')[(btns ? 'show' : 'hide')]();
var abooks = $('#directorylist');
$('#compose-contacts .scroller').css('top', abooks.position().top + abooks.outerHeight());
}
function update_quota(p)
{
var step = 24, step_count = 20,
y = p.total ? Math.ceil(p.percent / 100 * step_count) * step : 0;
// never show full-circle if quota is close to 100% but below.
if (p.total && y == step * step_count && p.percent < 100)
y -= step;
$('#quotadisplay').css('background-position', '0 -'+y+'px');
}
function enable_command(p)
{
if (p.command == 'reply-list') {
var label = rcmail.gettext(p.status ? 'replylist' : 'replyall');
if (rcmail.env.action == 'preview')
$('a.button.replyall').attr('title', label);
else
$('a.button.reply-all').text(label).attr('title', label);
}
}
/**
* Register a popup menu
*/
function add_popup(popup, config)
{
var obj = popups[popup] = $('#'+popup);
obj.appendTo(document.body); // move it to top for proper absolute positioning
if (obj.length)
popupconfig[popup] = $.extend(popupconfig[popup] || {}, config || {});
}
/**
* Trigger for popup menus
*/
function show_popup(popup, show, config)
{
// auto-register menu object
if (config || !popupconfig[popup])
add_popup(popup, config);
var visible = show_popupmenu(popup, show),
config = popupconfig[popup];
if (typeof config.callback == 'function')
config.callback(visible);
}
/**
* Show/hide a specific popup menu
*/
function show_popupmenu(popup, show)
{
var obj = popups[popup],
config = popupconfig[popup],
ref = $(config.link ? config.link : '#'+popup+'link'),
above = config.above;
if (!obj) {
obj = popups[popup] = $('#'+popup);
obj.appendTo(document.body); // move them to top for proper absolute positioning
}
if (!obj || !obj.length)
return false;
if (typeof show == 'undefined')
show = obj.is(':visible') ? false : true;
else if (config.toggle && show && obj.is(':visible'))
show = false;
if (show && ref.length) {
var parent = ref.parent(),
win = $(window),
pos;
if (parent.hasClass('dropbutton'))
ref = parent;
pos = ref.offset();
ref.offsetHeight = ref.outerHeight();
if (!above && pos.top + ref.offsetHeight + obj.height() > win.height())
above = true;
if (pos.left + obj.width() > win.width())
pos.left = win.width() - obj.width() - 12;
obj.css({ left:pos.left, top:(pos.top + (above ? -obj.height() : ref.offsetHeight)) });
}
obj[show?'show':'hide']();
// hide drop-down elements on buggy browsers
if (bw.ie6 && config.overlap) {
$('select').css('visibility', show?'hidden':'inherit');
$('select', obj).css('visibility', 'inherit');
}
return show;
}
/**
*
*/
function target_overlaps(target, elem)
{
while (target.parentNode) {
if (target.parentNode == elem)
return true;
target = target.parentNode;
}
return false;
}
/**
* Show/hide the preview pane
*/
function toggle_preview_pane(e)
{
var button = $(e.target),
frame = $('#mailpreviewframe'),
visible = !frame.is(':visible'),
splitter = mailviewsplit.pos || parseInt(rcmail.get_cookie('mailviewsplitter') || 320),
topstyles, bottomstyles, uid;
frame.toggle();
button.removeClass().addClass(visible ? 'enabled' : 'closed');
if (visible) {
$('#mailview-top').removeClass('fullheight').css({ bottom:'auto' });
$('#mailview-bottom').css({ height:'auto' });
rcmail.env.contentframe = 'messagecontframe';
if (uid = rcmail.message_list.get_single_selection())
rcmail.show_message(uid, false, true);
// let the splitter set the correct size and position
if (mailviewsplit.handle) {
mailviewsplit.handle.show();
mailviewsplit.resize();
}
else
mailviewsplit.init();
}
else {
rcmail.env.contentframe = null;
rcmail.show_contentframe(false);
$('#mailview-top').addClass('fullheight').css({ height:'auto', bottom:'28px' });
$('#mailview-bottom').css({ top:'auto', height:'26px' });
if (mailviewsplit.handle)
mailviewsplit.handle.hide();
}
if (visible && uid && rcmail.message_list)
rcmail.message_list.scrollto(uid);
rcmail.command('save-pref', { name:'preview_pane', value:(visible?1:0) });
}
/**
* Switch between short and full headers display in message preview
*/
function toggle_preview_headers()
{
$('#preview-shortheaders').toggle();
var full = $('#preview-allheaders').toggle(),
button = $('a#previewheaderstoggle');
// add toggle button to full headers table
if (full.is(':visible'))
button.attr('href', '#hide').removeClass('add').addClass('remove')
else
button.attr('href', '#details').removeClass('remove').addClass('add')
}
/**
*
*/
function switch_view_mode(mode)
{
if (rcmail.env.threading != (mode == 'thread'))
rcmail.set_list_options(null, undefined, undefined, mode == 'thread' ? 1 : 0);
$('#maillistmode, #mailthreadmode').removeClass('selected');
$('#mail'+mode+'mode').addClass('selected');
}
/**** popup callbacks ****/
function menu_open(p)
{
if (p && p.props && p.props.menu == 'attachmentmenu')
show_popupmenu('attachmentmenu');
else
show_listoptions();
}
function menu_save(prop)
{
save_listoptions();
}
function searchmenu(show)
{
if (show && rcmail.env.search_mods) {
var n, all,
obj = popups['searchmenu'],
list = $('input:checkbox[name="s_mods[]"]', obj),
mbox = rcmail.env.mailbox,
mods = rcmail.env.search_mods;
if (rcmail.env.task == 'mail') {
mods = mods[mbox] ? mods[mbox] : mods['*'];
all = 'text';
}
else {
all = '*';
}
if (mods[all])
list.map(function() {
this.checked = true;
this.disabled = this.value != all;
});
else {
list.prop('disabled', false).prop('checked', false);
for (n in mods)
$('#s_mod_' + n).prop('checked', true);
}
}
}
function attachmentmenu(elem)
{
var id = elem.parentNode.id.replace(/^attach/, '');
$('#attachmenuopen').unbind('click').attr('onclick', '').click(function(e) {
return rcmail.command('open-attachment', id, this);
});
$('#attachmenudownload').unbind('click').attr('onclick', '').click(function() {
rcmail.command('download-attachment', id, this);
});
popupconfig.attachmentmenu.link = elem;
rcmail.command('menu-open', {menu: 'attachmentmenu', id: id});
}
function spellmenu(show)
{
var link, li,
lang = rcmail.spellcheck_lang(),
menu = popups.spellmenu,
ul = $('ul', menu);
if (!ul.length) {
ul = $('<ul class="toolbarmenu selectable">');
for (i in rcmail.env.spell_langs) {
li = $('<li>');
link = $('<a href="#"></a>').text(rcmail.env.spell_langs[i])
.addClass('active').data('lang', i)
.click(function() {
rcmail.spellcheck_lang_set($(this).data('lang'));
});
link.appendTo(li);
li.appendTo(ul);
}
ul.appendTo(menu);
}
// select current language
$('li', ul).each(function() {
var el = $('a', this);
if (el.data('lang') == lang)
el.addClass('selected');
else if (el.hasClass('selected'))
el.removeClass('selected');
});
}
/**
*
*/
function show_listoptions()
{
var $dialog = $('#listoptions');
// close the dialog
if ($dialog.is(':visible')) {
$dialog.dialog('close');
return;
}
// set form values
$('input[name="sort_col"][value="'+rcmail.env.sort_col+'"]').prop('checked', true);
$('input[name="sort_ord"][value="DESC"]').prop('checked', rcmail.env.sort_order == 'DESC');
$('input[name="sort_ord"][value="ASC"]').prop('checked', rcmail.env.sort_order != 'DESC');
// set checkboxes
$('input[name="list_col[]"]').each(function() {
$(this).prop('checked', $.inArray(this.value, rcmail.env.coltypes) != -1);
});
$dialog.dialog({
modal: true,
resizable: false,
closeOnEscape: true,
title: null,
close: function() {
$dialog.dialog('destroy').hide();
},
minWidth: 500,
width: $dialog.width()+25
}).show();
}
/**
*
*/
function save_listoptions()
{
$('#listoptions').dialog('close');
var sort = $('input[name="sort_col"]:checked').val(),
ord = $('input[name="sort_ord"]:checked').val(),
cols = $('input[name="list_col[]"]:checked')
.map(function(){ return this.value; }).get();
rcmail.set_list_options(cols, sort, ord, rcmail.env.threading);
}
/**
*
*/
function set_searchmod(elem)
{
var all, m, task = rcmail.env.task,
mods = rcmail.env.search_mods,
mbox = rcmail.env.mailbox;
if (!mods)
mods = {};
if (task == 'mail') {
if (!mods[mbox])
mods[mbox] = rcube_clone_object(mods['*']);
m = mods[mbox];
all = 'text';
}
else { //addressbook
m = mods;
all = '*';
}
if (!elem.checked)
delete(m[elem.value]);
else
m[elem.value] = 1;
// mark all fields
if (elem.value != all)
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;
}
});
}
function show_uploadform()
{
var $dialog = $('#upload-dialog');
// close the dialog
if ($dialog.is(':visible')) {
$dialog.dialog('close');
return;
}
// add icons to clone file input field
if (rcmail.env.action == 'compose' && !$dialog.data('extended')) {
$('<a>')
.addClass('iconlink add')
.attr('href', '#add')
.html('Add')
.appendTo($('input[type="file"]', $dialog).parent())
.click(add_uploadfile);
$dialog.data('extended', true);
}
$dialog.dialog({
modal: true,
resizable: false,
closeOnEscape: true,
title: $dialog.attr('title'),
close: function() {
try { $('#upload-dialog form').get(0).reset(); }
catch(e){ } // ignore errors
$dialog.dialog('destroy').hide();
$('div.addline', $dialog).remove();
},
width: 480
}).show();
if (!document.all)
$('input[type=file]', $dialog).first().click();
}
function add_uploadfile(e)
{
var div = $(this).parent();
var clone = div.clone().addClass('addline').insertAfter(div);
clone.children('.iconlink').click(add_uploadfile);
clone.children('input').val('');
if (!document.all)
$('input[type=file]', clone).click();
}
/**
*
*/
function show_header_row(which, updated)
{
var row = $('#compose-' + which);
if (row.is(':visible'))
return; // nothing to be done here
if (compose_headers[which] && !updated)
$('#_' + which).val(compose_headers[which]);
row.show();
$('#' + which + '-link').hide();
layout_composeview();
return false;
}
/**
*
*/
function hide_header_row(which)
{
// copy and clear field value
var field = $('#_' + which);
compose_headers[which] = field.val();
field.val('');
$('#compose-' + which).hide();
$('#' + which + '-link').show();
layout_composeview();
return false;
}
/**
* Fieldsets-to-tabs converter
*/
function init_tabs(elem, current)
{
var content = $(elem),
id = content.get(0).id,
fs = content.children('fieldset');
if (!fs.length)
return;
if (!id) {
id = 'rcmtabcontainer';
content.attr('id', id);
}
// first hide not selected tabs
current = current || 0;
fs.each(function(idx) { if (idx != current) $(this).hide(); });
// create tabs container
var tabs = $('<div>').addClass('tabsbar').prependTo(content);
// convert fildsets into tabs
fs.each(function(idx) {
var tab, a, elm = $(this), legend = elm.children('legend');
// create a tab
a = $('<a>').text(legend.text()).attr('href', '#');
tab = $('<span>').attr({'id': 'tab'+idx, 'class': 'tablink'})
.click(function() { show_tab(id, idx); return false })
// remove legend
legend.remove();
// style fieldset
elm.addClass('tab');
// style selected tab
if (idx == current)
tab.addClass('selected');
// add the tab to container
tab.append(a).appendTo(tabs);
});
}
function show_tab(id, index)
{
var fs = $('#'+id).children('fieldset');
fs.each(function(idx) {
// Show/hide fieldset (tab content)
$(this)[index==idx ? 'show' : 'hide']();
// Select/unselect tab
$('#tab'+idx).toggleClass('selected', idx==index);
});
resize();
}
/**
* Show about page as jquery UI dialog
*/
function show_about(elem)
{
var frame = $('<iframe>').attr('id', 'aboutframe')
.attr('src', rcmail.url('settings/about'))
.attr('frameborder', '0')
.appendTo(document.body);
var h = Math.floor($(window).height() * 0.75);
var buttons = {};
var supportln = $('#supportlink');
if (supportln.length && (env.supporturl = supportln.attr('href')))
buttons[supportln.html()] = function(e){ env.supporturl.indexOf('mailto:') < 0 ? window.open(env.supporturl) : location.href = env.supporturl };
frame.dialog({
modal: true,
resizable: false,
closeOnEscape: true,
title: elem ? elem.title || elem.innerHTML : null,
close: function() {
frame.dialog('destroy').remove();
},
buttons: buttons,
width: 640,
height: h
}).width(640);
}
}
/**
* Roundcube Scroller class
*/
function rcube_scroller(list, top, bottom)
{
var ref = this;
this.list = $(list);
this.top = $(top);
this.bottom = $(bottom);
this.step_size = 6;
this.step_time = 20;
this.delay = 500;
this.top
.mouseenter(function() { if (rcmail.drag_active) ref.ts = window.setTimeout(function() { ref.scroll('down'); }, ref.delay); })
.mouseout(function() { if (ref.ts) window.clearTimeout(ref.ts); });
this.bottom
.mouseenter(function() { if (rcmail.drag_active) ref.ts = window.setTimeout(function() { ref.scroll('up'); }, ref.delay); })
.mouseout(function() { if (ref.ts) window.clearTimeout(ref.ts); });
this.scroll = function(dir)
{
var ref = this, size = this.step_size;
if (!rcmail.drag_active)
return;
if (dir == 'down')
size *= -1;
this.list.get(0).scrollTop += size;
this.ts = window.setTimeout(function() { ref.scroll(dir); }, this.step_time);
};
};
/**
* Roundcube UI splitter class
*
* @constructor
*/
function rcube_splitter(p)
{
this.p = p;
this.id = p.id;
this.horizontal = (p.orientation == 'horizontal' || p.orientation == 'h');
this.halfsize = (p.size !== undefined ? p.size : 10) / 2;
this.pos = p.start || 0;
this.min = p.min || 20;
this.offset = p.offset || 0;
this.relative = p.relative ? true : false;
this.drag_active = false;
this.render = p.render;
this.callback = p.callback;
var me = this;
rcube_splitter._instances[this.id] = me;
this.init = function()
{
this.p1 = $(this.p.p1);
this.p2 = $(this.p.p2);
// check if referenced elements exist, otherwise abort
if (!this.p1.length || !this.p2.length)
return;
// create and position the handle for this splitter
this.p1pos = this.relative ? this.p1.position() : this.p1.offset();
this.p2pos = this.relative ? this.p2.position() : this.p2.offset();
this.handle = $('<div>')
.attr('id', this.id)
.attr('unselectable', 'on')
.addClass('splitter ' + (this.horizontal ? 'splitter-h' : 'splitter-v'))
.appendTo(this.p1.parent())
.bind('mousedown', onDragStart);
if (this.horizontal) {
var top = this.p1pos.top + this.p1.outerHeight();
this.handle.css({ left:'0px', top:top+'px' });
}
else {
var left = this.p1pos.left + this.p1.outerWidth();
this.handle.css({ left:left+'px', top:'0px' });
}
// listen to window resize on IE
if (bw.ie)
$(window).resize(onResize);
// read saved position from cookie
var cookie = rcmail.get_cookie(this.id);
if (cookie && !isNaN(cookie)) {
this.pos = parseFloat(cookie);
this.resize();
}
else if (this.pos) {
this.resize();
this.set_cookie();
}
};
/**
* Set size and position of all DOM objects
* according to the saved splitter position
*/
this.resize = function()
{
if (this.horizontal) {
this.p1.css('height', Math.floor(this.pos - this.p1pos.top - this.halfsize) + 'px');
this.p2.css('top', Math.ceil(this.pos + this.halfsize + 2) + 'px');
this.handle.css('top', Math.round(this.pos - this.halfsize + this.offset)+'px');
if (bw.ie) {
var new_height = parseInt(this.p2.parent().outerHeight(), 10) - parseInt(this.p2.css('top'), 10) - (bw.ie8 ? 2 : 0);
this.p2.css('height', (new_height > 0 ? new_height : 0) + 'px');
}
}
else {
this.p1.css('width', Math.floor(this.pos - this.p1pos.left - this.halfsize) + 'px');
this.p2.css('left', Math.ceil(this.pos + this.halfsize) + 'px');
this.handle.css('left', Math.round(this.pos - this.halfsize + this.offset + 3)+'px');
if (bw.ie) {
var new_width = parseInt(this.p2.parent().outerWidth(), 10) - parseInt(this.p2.css('left'), 10) ;
this.p2.css('width', (new_width > 0 ? new_width : 0) + 'px');
}
}
this.p2.resize();
this.p1.resize();
// also resize iframe covers
if (this.drag_active) {
$('iframe').each(function(i, elem) {
var pos = $(this).offset();
$('#iframe-splitter-fix-'+i).css({ top: pos.top+'px', left: pos.left+'px', width:elem.offsetWidth+'px', height: elem.offsetHeight+'px' });
});
}
if (typeof this.render == 'function')
this.render(this);
};
/**
* Handler for mousedown events
*/
function onDragStart(e)
{
// disable text selection while dragging the splitter
if (bw.konq || bw.chrome || bw.safari)
document.body.style.webkitUserSelect = 'none';
me.p1pos = me.relative ? me.p1.position() : me.p1.offset();
me.p2pos = me.relative ? me.p2.position() : me.p2.offset();
me.drag_active = true;
// start listening to mousemove events
$(document).bind('mousemove.'+this.id, onDrag).bind('mouseup.'+this.id, onDragStop);
// enable dragging above iframes
$('iframe').each(function(i, elem) {
$('<div>')
.attr('id', 'iframe-splitter-fix-'+i)
.addClass('iframe-splitter-fix')
.css({ background: '#fff',
width: elem.offsetWidth+'px', height: elem.offsetHeight+'px',
position: 'absolute', opacity: '0.001', zIndex: 1000
})
.css($(this).offset())
.appendTo('body');
});
};
/**
* Handler for mousemove events
*/
function onDrag(e)
{
if (!me.drag_active)
return false;
var pos = rcube_event.get_mouse_pos(e);
if (me.relative) {
var parent = me.p1.parent().offset();
pos.x -= parent.left;
pos.y -= parent.top;
}
if (me.horizontal) {
if (((pos.y - me.halfsize) > me.p1pos.top) && ((pos.y + me.halfsize) < (me.p2pos.top + me.p2.outerHeight()))) {
me.pos = Math.max(me.min, pos.y - me.offset);
me.resize();
}
}
else {
if (((pos.x - me.halfsize) > me.p1pos.left) && ((pos.x + me.halfsize) < (me.p2pos.left + me.p2.outerWidth()))) {
me.pos = Math.max(me.min, pos.x - me.offset);
me.resize();
}
}
me.p1pos = me.relative ? me.p1.position() : me.p1.offset();
me.p2pos = me.relative ? me.p2.position() : me.p2.offset();
return false;
};
/**
* Handler for mouseup events
*/
function onDragStop(e)
{
// resume the ability to highlight text
if (bw.konq || bw.chrome || bw.safari)
document.body.style.webkitUserSelect = 'auto';
// cancel the listening for drag events
$(document).unbind('.'+me.id);
me.drag_active = false;
// remove temp divs
$('div.iframe-splitter-fix').remove();
me.set_cookie();
if (typeof me.callback == 'function')
me.callback(me);
return bw.safari ? true : rcube_event.cancel(e);
};
/**
* Handler for window resize events
*/
function onResize(e)
{
if (me.horizontal) {
var new_height = parseInt(me.p2.parent().outerHeight(), 10) - parseInt(me.p2[0].style.top, 10) - (bw.ie8 ? 2 : 0);
me.p2.css('height', (new_height > 0 ? new_height : 0) +'px');
}
else {
var new_width = parseInt(me.p2.parent().outerWidth(), 10) - parseInt(me.p2[0].style.left, 10);
me.p2.css('width', (new_width > 0 ? new_width : 0) + 'px');
}
};
/**
* Saves splitter position in cookie
*/
this.set_cookie = function()
{
var exp = new Date();
exp.setYear(exp.getFullYear() + 1);
rcmail.set_cookie(this.id, this.pos, exp);
};
} // end class rcube_splitter
// static getter for splitter instances
rcube_splitter._instances = {};
rcube_splitter.get_instance = function(id)
{
return rcube_splitter._instances[id];
};

File Metadata

Mime Type
text/x-diff
Expires
Sat, Mar 1, 5:21 AM (1 d, 13 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
165927
Default Alt Text
(396 KB)

Event Timeline