Page MenuHomePhorge

No OneTemporary

Size
594 KB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
This document is not UTF8. It was detected as ISO-8859-1 (Latin 1) and converted to UTF8 for display.
diff --git a/plugins/hide_blockquote/hide_blockquote.js b/plugins/hide_blockquote/hide_blockquote.js
index 2d28076a1..964cc07a3 100644
--- a/plugins/hide_blockquote/hide_blockquote.js
+++ b/plugins/hide_blockquote/hide_blockquote.js
@@ -1,63 +1,63 @@
/**
* Hide Blockquotes plugin script
*
* @licstart The following is the entire license notice for the
* JavaScript code in this file.
*
* Copyright (c) 2012-2014, The Roundcube Dev Team
*
* The JavaScript code in this page is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* @licend The above is the entire license notice
* for the JavaScript code in this file.
*/
if (window.rcmail)
rcmail.addEventListener('init', function() { hide_blockquote(); });
function hide_blockquote()
{
var limit = rcmail.env.blockquote_limit;
if (limit <= 0)
return;
- $('pre > blockquote', $('#messagebody')).each(function() {
+ $('div.message-part div.pre > blockquote', $('#messagebody')).each(function() {
var div, link, q = $(this),
text = $.trim(q.text()),
res = text.split(/\n/);
if (res.length <= limit) {
// there can be also a block with very long wrapped line
// assume line height = 15px
if (q.height() <= limit * 15)
return;
}
div = $('<blockquote class="blockquote-header">')
.css({'white-space': 'nowrap', overflow: 'hidden', position: 'relative'})
.text(res[0]);
link = $('<span class="blockquote-link"></span>')
.css({position: 'absolute', 'z-Index': 2})
.text(rcmail.gettext('hide_blockquote.show'))
.data('parent', div)
.click(function() {
var t = $(this), parent = t.data('parent'), visible = parent.is(':visible');
t.text(rcmail.gettext(visible ? 'hide' : 'show', 'hide_blockquote'))
.detach().appendTo(visible ? q : parent);
parent[visible ? 'hide' : 'show']();
q[visible ? 'show' : 'hide']();
});
link.appendTo(div);
// Modify blockquote
q.hide().css({position: 'relative'}).before(div);
});
}
diff --git a/program/js/app.js b/program/js/app.js
index 08f80e432..97b8192b2 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -1,7861 +1,7877 @@
/**
* Roundcube Webmail Client Script
*
* This file is part of the Roundcube Webmail client
*
* @licstart The following is the entire license notice for the
* JavaScript code in this file.
*
* Copyright (C) 2005-2014, The Roundcube Dev Team
* Copyright (C) 2011-2014, Kolab Systems AG
*
* The JavaScript code in this page is free software: you can
* redistribute it and/or modify it under the terms of the GNU
* General Public License (GNU GPL) as published by the Free Software
* Foundation, either version 3 of the License, or (at your option)
* any later version. The code is distributed WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
*
* As additional permission under GNU GPL version 3 section 7, you
* may distribute non-source (e.g., minimized or compacted) forms of
* that code without the copy of the GNU GPL normally required by
* section 4, provided you include this license notice and a URL
* through which recipients can access the Corresponding Source.
*
* @licend The above is the entire license notice
* for the JavaScript code in this file.
*
* @author Thomas Bruederli <roundcube@gmail.com>
* @author Aleksander 'A.L.E.C' Machniak <alec@alec.pl>
* @author 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 = {};
this.http_request_jobs = {};
// webmail client settings
this.dblclick_time = 500;
this.message_time = 5000;
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() { ref.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;
this.task = this.env.task;
// check browser
if (this.env.server_error != 409 && (!bw.dom || !bw.xmlhttp_test() || (bw.mz && bw.vendver < 1.9) || (bw.ie && bw.vendver < 7))) {
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', '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
.addEventListener('initrow', function(o) { ref.init_message_row(o); })
.addEventListener('dblclick', function(o) { ref.msglist_dbl_click(o); })
.addEventListener('click', function(o) { ref.msglist_click(o); })
.addEventListener('keypress', function(o) { ref.msglist_keypress(o); })
.addEventListener('select', function(o) { ref.msglist_select(o); })
.addEventListener('dragstart', function(o) { ref.drag_start(o); })
.addEventListener('dragmove', function(e) { ref.drag_move(e); })
.addEventListener('dragend', function(e) { ref.drag_end(e); })
.addEventListener('expandcollapse', function(o) { ref.msglist_expand(o); })
.addEventListener('column_replace', function(o) { ref.msglist_set_coltypes(o); })
.addEventListener('listupdate', function(o) { ref.triggerEvent('listupdate', o); })
.init();
// TODO: this should go into the list-widget code
$(this.message_list.thead).on('click', 'a.sortcol', function(e){
return ref.command('sort', $(this).attr('rel'), this);
});
document.onmouseup = function(e){ return ref.doc_mouse_up(e); };
this.gui_objects.messagelist.parentNode.onmousedown = function(e){ return ref.click_on_list(e); };
this.enable_command('toggle_status', 'toggle_flag', 'sort', true);
this.enable_command('set-listmode', this.env.threads && !this.is_multifolder_listing());
// load messages
this.command('list');
$(this.gui_objects.qsearchbox).val(this.env.search_text).focusin(function() { ref.message_list.blur(); });
}
this.set_button_titles();
this.env.message_commands = ['show', 'reply', 'reply-all', 'reply-list',
'move', '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.address_group_stack = [];
this.env.compose_commands = ['send-attachment', 'remove-attachment', 'send', 'cancel',
'toggle-editor', 'list-adresses', 'pushgroup', 'search', 'reset-search', 'extwin',
'insert-response', 'save-response'];
if (this.env.drafts_mailbox)
this.env.compose_commands.push('savedraft')
this.enable_command(this.env.compose_commands, 'identities', 'responses', 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);
}
// init canned response functions
if (this.gui_objects.responseslist) {
$('a.insertresponse', this.gui_objects.responseslist)
.attr('unselectable', 'on')
.mousedown(function(e){ return rcube_event.cancel(e); })
.mouseup(function(e){
ref.command('insert-response', $(this).attr('rel'));
$(document.body).trigger('mouseup'); // hides the menu
return rcube_event.cancel(e);
});
// avoid textarea loosing focus when hitting the save-response button/link
for (var i=0; this.buttons['save-response'] && i < this.buttons['save-response'].length; i++) {
$('#'+this.buttons['save-response'][i].id).mousedown(function(e){ return rcube_event.cancel(e); })
}
}
document.onmouseup = function(e){ return ref.doc_mouse_up(e); };
// init message compose form
this.init_messageform();
}
else if (this.env.action == 'get')
this.enable_command('download', 'print', true);
// show printing dialog
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('initrow', function(o) { ref.triggerEvent('insertrow', { cid:o.uid, row:o }); })
.addEventListener('select', function(o) { ref.compose_recipient_select(o); })
.addEventListener('dblclick', function(o) { ref.compose_add_recipient('to'); })
.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':
this.env.address_group_stack = [];
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', 'pushgroup', 'popgroup', 'listsearch', 'search', 'reset-search', '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
.addEventListener('initrow', function(o) { ref.triggerEvent('insertrow', { cid:o.uid, row:o }); })
.addEventListener('keypress', function(o) { ref.contactlist_keypress(o); })
.addEventListener('select', function(o) { ref.contactlist_select(o); })
.addEventListener('dragstart', function(o) { ref.drag_start(o); })
.addEventListener('dragmove', function(e) { ref.drag_move(e); })
.addEventListener('dragend', function(e) { ref.drag_end(e); })
.init();
if (this.env.cid)
this.contact_list.highlight_row(this.env.cid);
this.gui_objects.contactslist.parentNode.onmousedown = function(e){ return ref.click_on_list(e); };
document.onmouseup = function(e){ return ref.doc_mouse_up(e); };
$(this.gui_objects.qsearchbox).focusin(function() { ref.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();
}
break;
case 'settings':
this.enable_command('preferences', 'identities', 'responses', '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);
}
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.exists = this.env.messagecount;
parent.rcmail.enable_command('purge', this.env.messagecount);
}
else if (this.env.action == 'responses') {
this.enable_command('add', true);
}
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) { ref.identity_select(o); })
.init()
.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) { ref.section_select(o); })
.init()
.focus();
}
else if (this.gui_objects.subscriptionlist) {
this.init_subscription_list();
}
else if (this.gui_objects.responseslist) {
this.responses_list = new rcube_list_widget(this.gui_objects.responseslist, {multiselect:false, draggable:false, keyboard:false});
this.responses_list
.addEventListener('select', function(list) {
var win, id = list.get_single_selection();
ref.enable_command('delete', !!id && $.inArray(id, ref.env.readonly_responses) < 0);
if (id && (win = ref.get_frame_window(ref.env.contentframe))) {
ref.set_busy(true);
ref.location_href({ _action:'edit-response', _key:id, _framed:1 }, win);
}
})
.init()
.focus();
}
break;
case 'login':
var input_user = $('#rcmloginuser');
input_user.bind('keyup', function(e){ return ref.login_user_keyup(e); });
if (input_user.val() == '')
input_user.focus();
else
$('#rcmloginpwd').focus();
// detect client timezone
if (window.jstz) {
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);
ref.clear_messages();
ref.display_message('', 'loading');
});
this.enable_command('login', true);
break;
}
// select first input field in an edit form
if (this.gui_objects.editform)
$("input,select,textarea", this.gui_objects.editform)
.not(':hidden').not(':disabled').first().select();
// 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;
this.env.lastrefresh = new Date();
// show message
if (this.pending_message)
this.display_message(this.pending_message[0], this.pending_message[1], this.pending_message[2]);
// init treelist widget
if (this.gui_objects.folderlist && 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) })
.addEventListener('expand', function(node) { ref.folder_collapsed(node) })
.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, aborted = false;
if (obj && obj.blur)
obj.blur();
// do nothing if interface is locked by other command (with exception for searching reset)
if (this.busy && !(command == 'reset-search' && this.last_command == 'search'))
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 && !this.env.server_error) {
if (this.cmp_hash != this.compose_field_hash() && !confirm(this.get_label('notsentwarning')))
return false;
// remove copy from local storage if compose screen is left intentionally
this.remove_compose_data(this.env.compose_id);
}
this.last_command = command;
// 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 'logout':
case 'mail':
case 'addressbook':
case 'settings':
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('');
if (win) {
this.save_compose_form_local();
$("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.display_message(this.get_label('windowopenerror'), 'error');
}
}
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.get_message_mailbox(uid), _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 'set-listmode':
this.set_list_options(null, undefined, undefined, props == 'threads' ? 1 : 0);
break;
case 'sort':
var sort_order = this.env.sort_order,
sort_col = !this.env.disabled_sort_col ? props : this.env.sort_col;
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.env.action == 'responses') {
var frame;
if ((frame = this.get_frame_window(this.env.contentframe))) {
this.set_busy(true);
this.location_href({ _action:'add-response', _framed:1 }, frame);
}
}
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' && (uid = this.get_single_uid())) {
url = { _mbox: this.get_message_mailbox(uid) };
url[this.env.mailbox == this.env.drafts_mailbox && props != 'new' ? '_draft_uid' : '_uid'] = uid;
this.open_compose_step(url);
}
break;
case '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();
// settings: canned response
else if (this.task == 'settings' && this.env.action == 'responses')
this.delete_response();
// settings: user identities
else if (this.task == 'settings')
this.delete_identity();
break;
// mail task commands
case 'move':
case 'moveto': // deprecated
if (this.task == 'mail')
this.move_messages(props, obj);
else if (this.task == 'addressbook')
this.move_contacts(props);
break;
case 'copy':
if (this.task == 'mail')
this.copy_messages(props, obj);
else if (this.task == 'addressbook')
this.copy_contacts(props);
break;
case 'mark':
if (props)
this.mark_message(props);
break;
case 'toggle_status':
case 'toggle_flag':
flag = command == 'toggle_flag' ? 'flagged' : 'read';
if (uid = props) {
// toggle flagged/unflagged
if (flag == 'flagged') {
if (this.message_list.rows[uid].flagged)
flag = 'unflagged';
}
// toggle read/unread
else 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 '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'))
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);
if (!(flag = this.upload_file(props || this.gui_objects.uploadform, 'upload'))) {
if (flag !== false)
alert(this.get_label('selectimportfile'));
aborted = true;
}
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.get_message_mailbox(uid)};
if (command == 'reply-all')
// do reply-list, when list is detected and popup menu wasn't used
url._all = (!props && this.env.reply_all_mode == 1 && this.commands['reply-list'] ? 'list' : 'all');
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, _search: this.env.search_request };
if (command == 'forward-attachment' || (!props && this.env.forward_attachment) || uids.length > 1)
url._attachment = 1;
this.open_compose_step(url);
}
break;
case 'print':
if (this.env.action == 'get') {
this.gui_objects.messagepartframe.contentWindow.print();
}
else if (uid = this.get_single_uid()) {
ref.printwin = this.open_window(this.env.comm_path+'&_action=print&_uid='+uid+'&_mbox='+urlencode(this.get_message_mailbox(uid))+(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 (this.env.action == 'get') {
location.href = location.href.replace(/_frame=/, '_download=');
}
else if (uid = this.get_single_uid()) {
this.goto_url('viewsource', { _uid: uid, _mbox: this.get_message_mailbox(uid), _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 'pushgroup':
// add group ID to stack
this.env.address_group_stack.push(props.id);
if (obj && event)
rcube_event.cancel(event);
case 'listgroup':
this.reset_qsearch();
this.list_contacts(props.source, props.id);
break;
case 'popgroup':
if (this.env.address_group_stack.length > 1) {
this.env.address_group_stack.pop();
this.reset_qsearch();
this.list_contacts(props.source, this.env.address_group_stack[this.env.address_group_stack.length-1]);
}
break;
case 'import-messages':
var form = props || this.gui_objects.importform,
importlock = this.set_busy(true, 'importwait');
$('input[name="_unlock"]', form).val(importlock);
if (!(flag = this.upload_file(form, 'import'))) {
this.set_busy(false, null, importlock);
if (flag !== false)
alert(this.get_label('selectimportfile'));
aborted = true;
}
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'));
aborted = true;
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 'responses':
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 (!aborted && this.triggerEvent('after'+command, props) === false)
ret = false;
this.triggerEvent('actionafter', { props:props, action:command, aborted:aborted });
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]);
}
}
};
this.command_enabled = function(cmd)
{
return this.commands[cmd];
}
// 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';
else if (task == 'logout')
this.clear_compose_data();
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() { ref.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.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.dragmenu;
if (menu) {
$(menu).hide();
}
this.command(action, this.env.drag_target);
this.env.drag_target = null;
};
this.drag_start = function(list)
{
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)
{
var list, model;
if (this.treelist)
this.treelist.drag_end();
// execute drag & drop action when mouse was released
if (list = this.message_list)
model = this.env.mailboxes;
else if (list = this.contact_list)
model = this.env.contactfolders;
if (this.drag_active && model && this.env.last_folder_target) {
var target = model[this.env.last_folder_target];
list.draglayer.hide();
if (this.contact_list) {
if (!this.contacts_drag_menu(e, target))
this.command('move', target);
}
else if (!this.drag_menu(e, target))
this.command('move', target);
}
this.drag_active = false;
this.env.last_folder_target = null;
};
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.startsWith(name + this.env.delimiter) && !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 list, id;
// ignore event if jquery UI dialog is open
if ($(rcube_event.get_target(e)).closest('.ui-dialog, .ui-widget-overlay').length)
return;
list = this.message_list || this.contact_list;
if (list && !rcube_mouse_is_over(e, list.list.parentNode))
list.blur();
// 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', 'move', '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.messages[uid].mbox || this.env.mailbox) == this.env.drafts_mailbox)
this.open_compose_step({ _draft_uid: uid, _mbox: this.env.mailbox });
else if (uid)
this.show_message(uid, false, false);
};
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.listcols = [];
for (i=0; i<cols.length; i++)
if (cols[i].id && cols[i].id.startsWith('rcm')) {
name = cols[i].id.slice(3);
this.env.listcols.push(name);
}
if ((found = $.inArray('flag', this.env.listcols)) >= 0)
this.env.flagged_col = found;
if ((found = $.inArray('subject', this.env.listcols)) >= 0)
this.env.subject_col = found;
this.command('save-pref', { name: 'list_cols', value: this.env.listcols, session: 'list_attrib/columns' });
};
this.check_droptarget = function(id)
{
switch (this.task) {
case 'mail':
return (this.env.mailboxes[id]
&& !this.env.mailboxes[id].virtual
&& (this.env.mailboxes[id].id != this.env.mailbox || this.is_multifolder_listing())) ? 1 : 0;
case 'settings':
return id != this.env.mailbox ? 1 : 0;
case 'addressbook':
var target;
if (id != this.env.source && (target = this.env.contactfolders[id])) {
// droptarget is a group
if (target.type == 'group') {
if (target.id != this.env.group && !this.env.contactfolders[target.source].readonly) {
var is_other = this.env.selection_sources.length > 1 || $.inArray(target.source, this.env.selection_sources) == -1;
return !is_other || this.commands.move ? 1 : 2;
}
}
// droptarget is a (writable) addressbook and it's not the source
else if (!target.readonly && (this.env.selection_sources.length > 1 || $.inArray(id, this.env.selection_sources) == -1)) {
return this.commands.move ? 1 : 2;
}
}
}
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)
var 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>');
}
// allow plugins to grab the window reference (#1489413)
this.triggerEvent('openwindow', { url:url, handle:extwin });
// focus window, delayed to bring to front
window.setTimeout(function() { extwin && extwin.focus(); }, 10);
return extwin;
};
/*********************************************************/
/********* (message) list functionality *********/
/*********************************************************/
this.init_message_row = function(row)
{
var i, fn = {}, uid = row.uid,
status_icon = (this.env.status_col != null ? 'status' : 'msg') + 'icn' + row.id;
if (uid && this.env.messages[uid])
$.extend(row, this.env.messages[uid]);
// set eventhandler to status icon
if (row.icon = document.getElementById(status_icon)) {
fn.icon = function(e) { ref.command('toggle_status', uid); };
}
// save message icon position too
if (this.env.status_col != null)
row.msgicon = document.getElementById('msgicn'+row.id);
else
row.msgicon = row.icon;
// set eventhandler to flag icon
if (this.env.flagged_col != null && (row.flagicon = document.getElementById('flagicn'+row.id))) {
fn.flagicon = function(e) { ref.command('toggle_flag', uid); };
}
// set event handler to thread expand/collapse icon
if (!row.depth && row.has_children && (row.expando = document.getElementById('rcmexpando'+row.id))) {
fn.expando = function(e) { ref.expand_message_row(e, uid); };
}
// attach events
$.each(fn, function(i, f) {
row[i].onclick = function(e) { f(e); return rcube_event.cancel(e); };
if (bw.touch) {
row[i].addEventListener('touchend', function(e) {
if (e.changedTouches.length == 1) {
f(e);
return rcube_event.cancel(e);
}
}, false);
}
});
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,
mbox: flags.mbox,
// 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],
msg_id = this.html_identifier(uid,true),
row_class = 'message'
+ (!flags.seen ? ' unread' : '')
+ (flags.deleted ? ' deleted' : '')
+ (flags.flagged ? ' flagged' : '')
+ (message.selected ? ' selected' : ''),
row = { cols:[], style:{}, id:'rcmrow'+msg_id, uid: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' + msg_id + '" class="branch" style="width:' + (message.depth * 15) + 'px;">&nbsp;&nbsp;</span>';
if ((rows[message.parent_uid] && rows[message.parent_uid].expanded === false)
|| ((this.env.autoexpand_threads == 0 || this.env.autoexpand_threads == 2) &&
(!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' + row.id + '" class="' + (message.expanded ? 'expanded' : 'collapsed') + '">&nbsp;&nbsp;</div>';
row_class += ' thread' + (message.expanded? ' expanded' : '');
}
if (flags.unread_children && flags.seen && !message.expanded)
row_class += ' unroot';
}
tree += '<span id="msgicn'+row.id+'" class="'+css_class+'">&nbsp;</span>';
row.className = row_class;
// build subject link
if (cols.subject) {
var action = flags.mbox == this.env.drafts_mailbox ? 'compose' : 'show';
var uid_param = flags.mbox == this.env.drafts_mailbox ? '_draft_uid' : '_uid';
cols.subject = '<a href="./?_task=mail&_action='+action+'&_mbox='+urlencode(flags.mbox)+'&'+uid_param+'='+urlencode(uid)+'"'+
' onclick="return rcube_event.cancel(event)" onmouseover="rcube_webmail.long_subject_title(this,'+(message.depth+1)+')"><span>'+cols.subject+'</span></a>';
}
// add each submitted col
for (n in this.env.listcols) {
c = this.env.listcols[n];
col = {className: String(c).toLowerCase(), events:{}};
if (this.env.coltypes[c] && this.env.coltypes[c].hidden) {
col.className += ' hidden';
}
if (c == 'flag') {
css_class = (flags.flagged ? 'flagged' : 'unflagged');
html = '<span id="flagicn'+row.id+'" class="'+css_class+'">&nbsp;</span>';
}
else if (c == 'attachment') {
if (flags.attachmentClass)
html = '<span class="'+flags.attachmentClass+'">&nbsp;</span>';
else 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'+row.id+'" class="'+css_class+'">&nbsp;</span>';
}
else if (c == 'threads')
html = expando;
else if (c == 'subject') {
if (bw.ie)
col.events.mouseover = function() { rcube_webmail.long_subject_title_ex(this); };
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 if (c == 'folder') {
html = '<span onmouseover="rcube_webmail.long_subject_title(this)">' + cols[c] + '<span>';
}
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.listcols;
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.get_message_mailbox(id));
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);
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.href.indexOf(this.env.blankpage) < 0) {
if (win.stop)
win.stop();
else // IE
win.document.execCommand('Stop');
win.location.href = this.env.blankpage;
}
}
else if (!bw.safari && !bw.konq)
$(frame)[show ? 'show' : 'hide']();
}
if (!show && this.env.frame_lock)
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_post('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.env.search_filter = filter;
this.http_request('search', this.search_params(false, filter), lock);
};
// reload the current message listing
this.refresh_list = function()
{
this.list_mailbox(this.env.mailbox, this.env.current_page || 1, null, { _clear:1 }, true);
if (this.message_list)
this.message_list.clear_selection();
};
// list messages of a specific mailbox
this.list_mailbox = function(mbox, page, sort, url, update_only)
{
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;
}
if (!update_only) {
// unselect selected messages and clear the list and message data
this.clear_message_list();
if (mbox != this.env.mailbox || (mbox == this.env.mailbox && !page && !sort))
url._refresh = 1;
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, url)
{
var lock = this.set_busy(true, 'loading');
if (typeof url != 'object')
url = {};
url._mbox = mbox;
if (page)
url._page = page;
this.http_request('list', url, 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'+this.html_identifier(uid, true)).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.id).width(r.depth * 15).html('');
if (!r.depth) { // a new root
count++; // increase roots count
r.parent_uid = 0;
if (r.has_children) {
// replace 'leaf' with 'collapsed'
$('#'+r.id+' .leaf:first')
.attr('id', 'rcmexpando' + r.id)
.attr('class', (r.obj.style.display != 'none' ? 'expanded' : 'collapsed'))
.bind('mousedown', {uid: r.uid},
function(e) { return ref.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') {
if (row.unread != status)
this.update_thread_root(uid, status ? 'unread' : 'read');
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, obj)
{
if (mbox && typeof mbox === 'object')
mbox = mbox.id;
else if (!mbox)
return this.folder_selector(obj, function(folder) { ref.command('copy', folder); });
// 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, obj)
{
if (mbox && typeof mbox === 'object')
mbox = mbox.id;
else if (!mbox)
return this.folder_selector(obj, function(folder) { ref.command('move', folder); });
// exit if current or no mailbox specified
if (!mbox || (mbox == this.env.mailbox && !this.is_multifolder_listing()))
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('move', post_data, lock);
};
// delete selected messages from the current mailbox
this.delete_messages = function(event)
{
var list = this.message_list, trash = this.env.trash_mailbox;
// 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 specific move/delete request with UIDs of all selected messages
// @private
this._with_selected_messages = function(action, post_data, lock)
{
var count = 0, msg,
remove = (action == 'delete' || !this.is_multifolder_listing());
// 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);
}
}
if (remove)
this.message_list.remove_row(id, (this.env.display_next && n == selection.length-1));
}
// make sure there are no selected rows
if (!this.env.display_next && remove)
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 (count < 0)
post_data._count = (count*-1);
// remove threads from the end of the list
else if (count > 0 && remove)
this.delete_excessive_thread_rows();
if (!remove)
post_data._refresh = 1;
if (!lock) {
msg = action == 'move' ? 'movingmessage' : 'deletingmessage';
lock = this.display_message(this.get_label(msg), 'loading');
}
// 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;
if (this.env.display_next && this.env.next_uid)
data._next_uid = this.env.next_uid;
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);
};
// 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 (!this.message_list || (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 : {};
if (typeof uids == 'string')
uids = 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.length <= 1 ? uids.join(',') : uids);
};
// 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.startsWith(this.env.trash_mailbox + this.env.delimiter)
|| this.env.mailbox.startsWith(this.env.junk_mailbox + 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),
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(){
if (opener.history.length > 1)
opener.history.back();
else
opener_rc.redirect(opener_rc.get_task_url('mail'));
}, 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.change_identity(input_from[0]);
}
}
// check for locally stored compose data
if (window.localStorage) {
var index = this.local_storage_get_item('compose.index', []);
for (var key, i = 0; i < index.length; i++) {
key = index[i], formdata = this.local_storage_get_item('compose.' + key, null, true);
if (!formdata) {
continue;
}
// restore saved copy of current compose_id
if (formdata.changed && key == this.env.compose_id) {
this.restore_compose_form(key, html_mode);
break;
}
// skip records from 'other' drafts
if (this.env.draft_id && formdata.draft_id && formdata.draft_id != this.env.draft_id) {
continue;
}
// skip records on reply
if (this.env.reply_msgid && formdata.reply_msgid != this.env.reply_msgid) {
continue;
}
// show dialog asking to restore the message
if (formdata.changed && formdata.session != this.env.session_id) {
this.show_popup_dialog(
this.get_label('restoresavedcomposedata')
.replace('$date', new Date(formdata.changed).toLocaleString())
.replace('$subject', formdata._subject)
.replace(/\n/g, '<br/>'),
this.get_label('restoremessage'),
[{
text: this.get_label('restore'),
click: function(){
ref.restore_compose_form(key, html_mode);
ref.remove_compose_data(key); // remove old copy
ref.save_compose_form_local(); // save under current compose_id
$(this).dialog('close');
}
},
{
text: this.get_label('delete'),
click: function(){
ref.remove_compose_data(key);
$(this).dialog('close');
}
},
{
text: this.get_label('ignore'),
click: function(){
$(this).dialog('close');
}
}]
);
break;
}
}
}
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.keydown(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)
{
var id, n, recipients = 0;
for (n=0; n < list.selection.length; n++) {
id = list.selection[n];
if (this.env.contactdata[id])
recipients++;
}
this.enable_command('add-recipient', recipients);
};
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);
+ var input = $('#' + props.id);
- if (this.env.default_font)
- setTimeout(function() {
- $(tinyMCE.get(props.id).getBody()).css('font-family', ref.env.default_font);
- }, 500);
- }
- else if (this.html2plain(tinyMCE.get(props.id).getContent(), props.id))
- tinyMCE.execCommand('mceRemoveControl', false, props.id);
+ if (props.mode == 'html')
+ this.plain2html(input.val(), function(data) {
+ input.val(data);
+ tinyMCE.execCommand('mceAddControl', false, props.id);
+
+ if (ref.env.default_font)
+ setTimeout(function() {
+ $(tinyMCE.get(props.id).getBody()).css('font-family', ref.env.default_font);
+ }, 500);
+ });
+ else
+ this.html2plain(tinyMCE.get(props.id).getContent(), function(data) {
+ tinyMCE.execCommand('mceRemoveControl', false, props.id);
+ input.val(data);
+ });
return true;
};
this.insert_response = function(key)
{
var insert = this.env.textresponses[key] ? this.env.textresponses[key].text : null;
if (!insert)
return false;
// insert into tinyMCE editor
if ($("input[name='_is_html']").val() == '1') {
var editor = tinyMCE.get(this.env.composebody);
editor.getWin().focus(); // correct focus in IE & Chrome
editor.selection.setContent(this.quote_html(insert).replace(/\r?\n/g, '<br/>'), { format:'text' });
}
// replace selection in compose textarea
else {
var textarea = rcube_find_object(this.env.composebody),
selection = $(textarea).is(':focus') ? this.get_input_selection(textarea) : { start:0, end:0 },
inp_value = textarea.value;
pre = inp_value.substring(0, selection.start),
end = inp_value.substring(selection.end, inp_value.length);
// insert response text
textarea.value = pre + insert + end;
// set caret after inserted text
this.set_caret_pos(textarea, selection.start + insert.length);
textarea.focus();
}
};
/**
* Open the dialog to save a new canned response
*/
this.save_response = function()
{
var sigstart, text = '', strip = false;
// get selected text from tinyMCE editor
if ($("input[name='_is_html']").val() == '1') {
var editor = tinyMCE.get(this.env.composebody);
editor.getWin().focus(); // correct focus in IE & Chrome
text = editor.selection.getContent({ format:'text' });
if (!text) {
text = editor.getContent({ format:'text' });
strip = true;
}
}
// get selected text from compose textarea
else {
var textarea = rcube_find_object(this.env.composebody), sigstart;
if (textarea && $(textarea).is(':focus')) {
text = this.get_input_selection(textarea).text;
}
if (!text && textarea) {
text = textarea.value;
strip = true;
}
}
// strip off signature
if (strip) {
sigstart = text.indexOf('-- \n');
if (sigstart > 0) {
text = text.substring(0, sigstart);
}
}
// show dialog to enter a name and to modify the text to be saved
var buttons = {},
html = '<form class="propform">' +
'<div class="prop block"><label>' + this.get_label('responsename') + '</label>' +
'<input type="text" name="name" id="ffresponsename" size="40" /></div>' +
'<div class="prop block"><label>' + this.get_label('responsetext') + '</label>' +
'<textarea name="text" id="ffresponsetext" cols="40" rows="8"></textarea></div>' +
'</form>';
buttons[this.gettext('save')] = function(e) {
var name = $('#ffresponsename').val(),
text = $('#ffresponsetext').val();
if (!text) {
$('#ffresponsetext').select();
return false;
}
if (!name)
name = text.substring(0,40);
var lock = ref.display_message(ref.get_label('savingresponse'), 'loading');
ref.http_post('settings/responses', { _insert:1, _name:name, _text:text }, lock);
$(this).dialog('close');
};
buttons[this.gettext('cancel')] = function() {
$(this).dialog('close');
};
this.show_popup_dialog(html, this.gettext('savenewresponse'), buttons);
$('#ffresponsetext').val(text);
$('#ffresponsename').select();
};
this.add_response_item = function(response)
{
var key = response.key;
this.env.textresponses[key] = response;
// append to responses list
if (this.gui_objects.responseslist) {
var li = $('<li>').appendTo(this.gui_objects.responseslist);
$('<a>').addClass('insertresponse active')
.attr('href', '#')
.attr('rel', key)
.html(this.quote_html(response.name))
.appendTo(li)
.mousedown(function(e){
return rcube_event.cancel(e);
})
.mouseup(function(e){
ref.command('insert-response', key);
$(document.body).trigger('mouseup'); // hides the menu
return rcube_event.cancel(e);
});
}
};
this.edit_responses = function()
{
// TODO: implement inline editing of responses
};
this.delete_response = function(key)
{
if (!key && this.responses_list) {
var selection = this.responses_list.get_selection();
key = selection[0];
}
// submit delete request
if (key && confirm(this.get_label('deleteresponseconfirm'))) {
this.http_post('settings/delete-response', { _key: key }, false);
return true;
}
return false;
};
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 (this.buttons.spellcheck)
$('#'+this.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 (id && id != this.env.draft_id) {
if (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);
// reset history of hidden iframe used for saving draft (#1489643)
// but don't do this on timer-triggered draft-autosaving (#1489789)
if (window.frames['savetarget'] && window.frames['savetarget'].history && !this.draft_autosave_submit) {
window.frames['savetarget'].history.back();
}
this.draft_autosave_submit = false;
}
// always remove local copy upon saving as draft
this.remove_compose_data(this.env.compose_id);
};
this.auto_save_start = function()
{
if (this.env.draft_autosave) {
this.draft_autosave_submit = false;
this.save_timer = setTimeout(function(){
ref.draft_autosave_submit = true; // set auto-saved flag (#1489789)
ref.command("savedraft");
}, this.env.draft_autosave * 1000);
}
// save compose form content to local storage every 5 seconds
if (!this.local_save_timer && window.localStorage) {
// track typing activity and only save on changes
this.compose_type_activity = this.compose_type_activity_last = 0;
$(document).bind('keypress', function(e){ ref.compose_type_activity++; });
this.local_save_timer = setInterval(function(){
if (ref.compose_type_activity > ref.compose_type_activity_last) {
ref.save_compose_form_local();
ref.compose_type_activity_last = ref.compose_type_activity;
}
}, 5000);
}
// 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;
};
// store the contents of the compose form to localstorage
this.save_compose_form_local = function()
{
var formdata = { session:this.env.session_id, changed:new Date().getTime() },
ed, empty = true;
// get fresh content from editor
if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody))) {
tinyMCE.triggerSave();
}
if (this.env.draft_id) {
formdata.draft_id = this.env.draft_id;
}
if (this.env.reply_msgid) {
formdata.reply_msgid = this.env.reply_msgid;
}
$('input, select, textarea', this.gui_objects.messageform).each(function(i, elem) {
switch (elem.tagName.toLowerCase()) {
case 'input':
if (elem.type == 'button' || elem.type == 'submit' || (elem.type == 'hidden' && elem.name != '_is_html')) {
break;
}
formdata[elem.name] = elem.type != 'checkbox' || elem.checked ? $(elem).val() : '';
if (formdata[elem.name] != '' && elem.type != 'hidden')
empty = false;
break;
case 'select':
formdata[elem.name] = $('option:checked', elem).val();
break;
default:
formdata[elem.name] = $(elem).val();
if (formdata[elem.name] != '')
empty = false;
}
});
if (window.localStorage && !empty) {
var index = this.local_storage_get_item('compose.index', []),
key = this.env.compose_id;
if ($.inArray(key, index) < 0) {
index.push(key);
}
this.local_storage_set_item('compose.' + key, formdata, true);
this.local_storage_set_item('compose.index', index);
}
};
// write stored compose data back to form
this.restore_compose_form = function(key, html_mode)
{
var ed, formdata = this.local_storage_get_item('compose.' + key, true);
if (formdata && typeof formdata == 'object') {
$.each(formdata, function(k, value) {
if (k[0] == '_') {
var elem = $("*[name='"+k+"']");
if (elem[0] && elem[0].type == 'checkbox') {
elem.prop('checked', value != '');
}
else {
elem.val(value);
}
}
});
// initialize HTML editor
if (formdata._is_html == '1') {
if (!html_mode) {
tinyMCE.execCommand('mceAddControl', false, this.env.composebody);
this.triggerEvent('aftertoggle-editor', { mode:'html' });
}
}
else if (html_mode) {
tinyMCE.execCommand('mceRemoveControl', false, this.env.composebody);
this.triggerEvent('aftertoggle-editor', { mode:'plain' });
}
}
};
// remove stored compose data from localStorage
this.remove_compose_data = function(key)
{
if (window.localStorage) {
var index = this.local_storage_get_item('compose.index', []);
if ($.inArray(key, index) >= 0) {
this.local_storage_remove_item('compose.' + key);
this.local_storage_set_item('compose.index', $.grep(index, function(val,i) { return val != key; }));
}
}
};
// clear all stored compose data of this user
this.clear_compose_data = function()
{
if (window.localStorage) {
var i, index = this.local_storage_get_item('compose.index', []);
for (i=0; i < index.length; i++) {
this.local_storage_remove_item('compose.' + index[i]);
}
this.local_storage_remove_item('compose.index');
}
}
this.change_identity = function(obj, show_sig)
{
if (!obj || !obj.options)
return false;
if (!show_sig)
show_sig = this.env.show_sig;
// first function execution
if (!this.env.identities_initialized) {
this.env.identities_initialized = true;
if (this.env.show_sig_later)
this.env.show_sig = true;
if (this.env.opened_extwin)
return;
}
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_separator,
rx_delim = RegExp.escape(delim),
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(rx_delim + '\\s*' + rx_delim, 'g');
input_val = input_val.replace(rx, delim);
rx = new RegExp('^[\\s' + rx_delim + ']+');
input_val = input_val.replace(rx, '');
// add new address(es)
if (new_val && input_val.indexOf(new_val) == -1 && input_val.indexOf(new_val.replace(/"/g, '')) == -1) {
if (input_val) {
rx = new RegExp('[' + rx_delim + '\\s]+$')
input_val = input_val.replace(rx, '') + 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;
this.triggerEvent('change_identity');
return true;
};
// upload (attachment) file
this.upload_file = function(form, action)
{
if (!form)
return;
// 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 false;
}
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[1].innerHTML;
} catch (err) {}
if (!content.match(/add2attachment/) && (!bw.opera || (ref.env.uploadframe && ref.env.uploadframe == e.data.ts))) {
if (!content.match(/display_message/))
ref.display_message(ref.get_label('fileuploaderror'), 'error');
ref.remove_from_attachment_list(e.data.ts);
}
// Opera hack: handle double onload
if (bw.opera)
ref.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>');
li.attr('id', name)
.addClass(att.classname)
.html(att.html)
.on('mouseover', function() { rcube_webmail.long_subject_title_ex(this); });
// 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)
{
if (this.env.attachments) {
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() { ref.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};
this.enable_command('set-listmode', this.env.threads && (this.env.search_scope || 'base') == 'base');
return true;
}
return false;
};
this.continue_search = function(request_id)
{
var lock = ref.set_busy(true, 'stillsearching');
setTimeout(function(){
var url = ref.search_params();
url._continue = request_id;
ref.env.qsearch = { lock: lock, request: ref.http_request('search', url, lock) };
}, 100);
};
// build URL params for search
this.search_params = function(search, filter)
{
var n, url = {}, mods_arr = [],
mods = this.env.search_mods,
scope = this.env.search_scope || 'base',
mbox = scope == 'all' ? '*' : this.env.mailbox;
if (!filter && this.gui_objects.search_filter)
filter = this.gui_objects.search_filter.value;
if (!search && 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['*'];
if (mods) {
for (n in mods)
mods_arr.push(n);
url._headers = mods_arr.join(',');
}
}
if (scope)
url._scope = scope;
if (mbox && scope != 'all')
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.enable_command('set-listmode', this.env.threads);
};
this.set_searchscope = function(scope)
{
var old = this.env.search_scope;
this.env.search_scope = scope;
// re-send search query with new scope
if (scope != old && this.env.search_request) {
if (!this.qsearch(this.gui_objects.qsearchbox.value) && this.env.search_filter && this.env.search_filter != 'ALL')
this.filter_mailbox(this.env.search_filter);
if (scope != 'all')
this.select_folder(this.env.mailbox, '', true);
}
};
this.set_searchmods = function(mods)
{
var mbox = this.env.mailbox,
scope = this.env.search_scope || 'base';
if (scope == 'all')
mbox = '*';
if (!this.env.search_mods)
this.env.search_mods = {};
if (mbox)
this.env.search_mods[mbox] = mods;
};
this.is_multifolder_listing = function()
{
return typeof this.env.multifolder_listing != 'undefined' ? this.env.multifolder_listing :
(this.env.search_request && (this.env.search_scope || 'base') != 'base');
}
this.sent_successfully = function(type, msg, folders)
{
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 or replied message comes from
if (folders && rc.env.task == 'mail' && rc.env.action == '' && $.inArray(rc.env.mailbox, folders) >= 0) {
// @TODO: try with 'checkmail' here when #1485186 is fixed. See also #1489249.
rc.command('list');
}
}
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())
return;
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
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].type == 'group') {
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] === 'object' && this.env.contacts[id].name) {
insert = this.env.contacts[id].name + this.env.recipients_delimiter;
trigger = true;
}
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
this.set_caret_pos(this.ksearch_input, p + insert.length);
if (trigger) {
this.triggerEvent('autocomplete_insert', { field:this.ksearch_input, insert:insert, data:this.env.contacts[id] });
this.compose_type_activity++;
}
};
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;
this.compose_type_activity++;
}
};
// 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,
data = 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.startsWith(old_value) && (!data || data.num <= 0) && this.env.contacts && !this.env.contacts.length)
return;
var sources = props && props.sources ? props.sources : [''];
var reqid = this.multi_thread_http_request({
items: sources,
threads: props && props.threads ? props.threads : 1,
action: props && props.action ? props.action : 'mail/autocomplete',
postdata: { _search:q, _source:'%s' },
lock: this.display_message(this.get_label('searching'), 'loading')
});
this.ksearch_data = { id:reqid, sources:sources.slice(), num:sources.length };
};
this.ksearch_query_results = function(results, search, reqid)
{
// trigger multi-thread http response callback
this.multi_thread_http_response(results, reqid);
// search stopped in meantime?
if (!this.ksearch_value)
return;
// ignore this outdated search response
if (this.ksearch_input && search != this.ksearch_value)
return;
// display search results
var i, len, ul, li, text, type, init,
value = this.ksearch_value,
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];
type = typeof results[i] === 'object' ? results[i].type : '';
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;
if (type) li.className = type;
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);
if (this.ksearch_data.id == reqid)
this.ksearch_data.num--;
};
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()
{
if (this.ksearch_data)
this.multi_thread_request_abort(this.ksearch_data.id);
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;
};
/*********************************************************/
/********* 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, contact, 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) {
list.draggable = false;
// 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) {
this.env.selection_sources.push(this.env.source);
}
for (n in list.selection) {
contact = list.data[list.selection[n]];
if (!source) {
sid = String(list.selection[n]).replace(/^[^-]+-/, '');
if (sid && this.env.address_sources[sid]) {
writable = writable || (!this.env.address_sources[sid].readonly && !contact.readonly);
this.env.selection_sources.push(sid);
}
}
else {
writable = writable || (!source.readonly && !contact.readonly);
}
if (contact._type != 'group')
list.draggable = true;
}
this.env.selection_sources = $.unique(this.env.selection_sources);
}
// 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 && writable);
this.enable_command('compose', this.env.group || list.selection.length > 0);
this.enable_command('export-selected', 'copy', list.selection.length > 0);
this.enable_command('edit', id && writable);
this.enable_command('delete', 'move', list.selection.length > 0 && 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.env.source = src;
this.env.group = group;
// truncate groups listing stack
var index = $.inArray(this.env.group, this.env.address_group_stack);
if (index < 0)
this.env.address_group_stack = [];
else
this.env.address_group_stack = this.env.address_group_stack.slice(0,index);
// make sure the current group is on top of the stack
if (this.env.group) {
this.env.address_group_stack.push(this.env.group);
// mark the first group on the stack as selected in the directory list
folder = 'G'+src+this.env.address_group_stack[0];
}
else if (this.gui_objects.addresslist_title) {
$(this.gui_objects.addresslist_title).html(this.get_label('contacts'));
}
this.select_folder(folder, '', true);
// 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.data = {};
this.contact_list.clear(true);
this.show_contentframe(false);
this.enable_command('delete', 'move', 'copy', false);
this.enable_command('compose', this.env.group ? true : false);
};
this.set_group_prop = function(prop)
{
if (this.gui_objects.addresslist_title) {
var boxtitle = $(this.gui_objects.addresslist_title).html(''); // clear contents
// add link to pop back to parent group
if (this.env.address_group_stack.length > 1) {
$('<a href="#list">...</a>')
.addClass('poplink')
.appendTo(boxtitle)
.click(function(e){ return ref.command('popgroup','',this); });
boxtitle.append('&nbsp;&raquo;&nbsp;');
}
boxtitle.append($('<span>').text(prop.name));
}
this.triggerEvent('groupupdate', prop);
};
// load contact record
this.load_contact = function(cid, action, framed)
{
var win, url = {}, target = window,
rec = this.contact_list ? this.contact_list.data[cid] : null;
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('compose', rec && rec.email);
this.enable_command('export-selected', rec && rec._type != 'group');
}
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);
};
this.contacts_drag_menu = function(e, to)
{
var dest = to.type == 'group' ? to.source : to.id,
source = this.env.source;
if (!this.env.address_sources[dest] || this.env.address_sources[dest].readonly)
return true;
// 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];
if (to.type == 'group' && dest == source) {
var cid = this.contact_list.get_selection().join(',');
this.group_member_change('add', cid, dest, to.id);
return true;
}
// move action is not possible, "redirect" to copy if menu wasn't requested
else if (!this.commands.move && rcube_event.get_modifier(e) != SHIFT_KEY) {
this.copy_contacts(to);
return true;
}
return this.drag_menu(e, to);
};
// copy contact(s) to the specified target (group or directory)
this.copy_contacts = function(to)
{
var n, dest = to.type == 'group' ? to.source : to.id,
source = this.env.source,
group = this.env.group ? this.env.group : '',
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)
return;
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);
}
};
// move contact(s) to the specified target (group or directory)
this.move_contacts = function(to)
{
var dest = to.type == 'group' ? to.source : to.id,
source = this.env.source,
group = this.env.group ? this.env.group : '';
if (!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];
if (to.type == 'group') {
if (dest == source)
return;
this._with_selected_contacts('move', {_to: dest, _togid: to.id});
}
// target is an addressbook
else if (to.id != source)
this._with_selected_contacts('move', {_to: to.id});
};
// delete contact(s)
this.delete_contacts = function()
{
var undelete = this.env.source && this.env.address_sources[this.env.source].undelete;
if (!undelete && !confirm(this.get_label('deletecontactconfirm')))
return;
return this._with_selected_contacts('delete');
};
this._with_selected_contacts = function(action, post_data)
{
var selection = this.contact_list ? this.contact_list.get_selection() : [];
// exit if no mailbox specified or if selection is empty
if (!selection.length && !this.env.cid)
return;
var n, a_cids = [],
label = action == 'delete' ? 'contactdeleting' : 'movingcontact',
lock = this.display_message(this.get_label(label), '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);
}
if (!post_data)
post_data = {};
post_data._source = this.env.source;
post_data._from = this.env.action;
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(action, post_data, lock)
return true;
};
// update a contact record in the list
this.update_contact_row = function(cid, cols_arr, newcid, source, data)
{
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);
list.data[cid] = data;
};
// add row to contacts list
this.add_contact_row = function(cid, cols, classes, data)
{
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);
}
// store data in list member
list.data[cid] = data;
list.insert_row(row);
this.enable_command('export', list.rowcount > 0);
};
this.init_contact_form = function()
{
var 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();
}
// 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 ref.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)) {
this.triggerEvent('group_delete', { source:prop.source, id:prop.id });
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 ref.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 ref.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 ref.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) {
ref.set_busy(false, null, ref.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 || 0) + '&_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 ref.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 (id || action == 'add-identity') {
this.location_href(url, target, true);
}
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 ]);
}
};
this.update_response_row = function(response, oldkey)
{
var list = this.responses_list;
if (list && oldkey) {
list.update_row(oldkey, [ response.name ], response.key, true);
}
else if (list) {
list.insert_row({ id:'rcmrow'+response.key, cols:[ { className:'name', innerHTML:response.name } ] });
list.select(response.key);
}
};
this.remove_response = function(key)
{
var frame;
if (this.env.textresponses) {
delete this.env.textresponses[key];
}
if (this.responses_list) {
this.responses_list.remove_row(key);
if (this.env.contentframe && (frame = this.get_frame_window(this.env.contentframe))) {
frame.location.href = this.env.blankpage;
}
}
};
/*********************************************************/
/********* folder manager methods *********/
/*********************************************************/
this.init_subscription_list = function()
{
var delim = RegExp.escape(this.env.delimiter);
this.last_sub_rx = RegExp('['+delim+']?[^'+delim+']+$');
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){ ref.subscription_select(o); })
.addEventListener('dragstart', function(o){ ref.drag_active = true; })
.addEventListener('dragend', function(o){ ref.subscription_move_folder(o); })
.addEventListener('initrow', function (row) {
row.obj.onmouseover = function() { ref.focus_subscription(row.id); };
row.obj.onmouseout = function() { ref.unfocus_subscription(row.id); };
})
.init();
$('#mailboxroot')
.mouseover(function(){ ref.focus_subscription(this.id); })
.mouseout(function(){ ref.unfocus_subscription(this.id); })
};
this.focus_subscription = function(id)
{
var row, folder;
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(this.last_sub_rx, '') &&
!folder.startsWith(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.length)
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)
{
if (this.env.mailbox && this.env.dstfolder !== null &&
this.env.dstfolder != this.env.mailbox &&
this.env.dstfolder != this.env.mailbox.replace(this.last_sub_rx, '')
) {
var path = this.env.mailbox.split(this.env.delimiter),
basename = path.pop(),
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, rowid, folders = [], 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, '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, false];
// sort folders (to find a place where to insert the row)
// replace delimiter with \0 character to fix sorting
// issue where 'Abc Abc' would be placed before 'Abc/def'
var replace_from = RegExp(RegExp.escape(this.env.delimiter), 'g'),
replace_to = String.fromCharCode(0);
$.each(this.env.subscriptionrows, function(k,v) {
if (v.length < 4) {
var n = v[0];
n = n.replace(replace_from, replace_to);
v.push(n);
}
folders.push(v);
});
folders.sort(function(a, b) {
var len = a.length - 1; n1 = a[len], n2 = b[len];
return n1 < n2 ? -1 : 1;
});
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].startsWith(tmp))
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.startsWith(slist[n] + this.env.delimiter))
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) {
if (this.is_framed)
return parent.rcmail.replace_folder_row(oldfolder, newfolder, display_name, is_protected, class_name);
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),
prefix_len = oldfolder.length,
subscribed = $('input[name="_subscribed[]"]', $('#'+id)).prop('checked'),
// find subfolders of renamed folder
list = this.get_subfolders(oldfolder);
// no renaming, only update class_name
if (oldfolder == newfolder) {
$('#'+id).attr('class', class_name || '');
this.subscription_list.focus();
return;
}
// 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 = newfolder + name.slice(prefix_len);
$('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 = [],
prefix = folder + 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 (name && name.startsWith(prefix)) {
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 ref.button_sel(this._command, this._id); };
elm.onmouseup = function(e) { return ref.button_out(this._command, this._id); };
if (preload)
new Image().src = prop.sel;
}
if (prop.over) {
elm.onmouseover = function(e) { return ref.button_over(this._command, this._id); };
elm.onmouseout = function(e) { return ref.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 == 'pas';
}
else if (button.type == 'uibutton') {
$(obj).button('option', 'disabled', state == 'pas');
}
}
};
// 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)
{
this.button_event(command, id, 'over');
};
// mouse down on button
this.button_sel = function(command, id)
{
this.button_event(command, id, 'sel');
};
// mouse out of button
this.button_out = function(command, id)
{
this.button_event(command, id, 'act');
};
// event of button
this.button_event = function(command, id, event)
{
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') {
if (button[event] && (obj = document.getElementById(button.id))) {
obj[button.type == 'image' ? 'src' : 'className'] = button[event];
}
if (event == 'sel') {
this.buttons_sel[id] = command;
}
}
}
};
// 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 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, options)
{
// forward call to parent window
if (this.is_framed()) {
return parent.rcmail.show_popup_dialog(html, title, buttons, options);
}
var popup = $('<div class="popup">')
.html(html)
.dialog($.extend({
title: title,
buttons: buttons,
modal: true,
resizable: true,
width: 500,
close: function(event, ui) { $(this).remove() }
}, options || {}));
// 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 + 36)
});
return popup;
};
// 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) {
$('li.selected', this.gui_objects.folderlist)
.removeClass('selected').addClass('unfocused');
$(this.get_folder_li(name, prefix, encode))
.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);
this.triggerEvent('markfolder', {folder: name, mark: class_name, status: true});
};
// 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);
this.triggerEvent('markfolder', {folder: name, mark: class_name, status: false});
};
// 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);
}
};
// for reordering column array (Konqueror workaround)
// and for setting some message list global variables
this.set_message_coltypes = function(listcols, repl, smart_col)
{
var list = this.message_list,
thead = list ? list.thead : null,
repl, cell, col, n, len, tr;
this.env.listcols = listcols;
// replace old column headers
if (thead) {
if (repl) {
thead.innerHTML = '';
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);
}
thead.appendChild(tr);
}
for (n=0, len=this.env.listcols.length; n<len; n++) {
col = this.env.listcols[n];
if ((cell = thead.rows[0].cells[n]) && (col == 'from' || col == 'to' || col == 'fromto')) {
$(cell).attr('rel', col).find('span,a').text(this.get_label(col == 'fromto' ? smart_col : col));
}
}
}
this.env.subject_col = null;
this.env.flagged_col = null;
this.env.status_col = null;
if (this.env.coltypes.folder)
this.env.coltypes.folder.hidden = !(this.env.search_request || this.env.search_id) || this.env.search_scope == 'base';
if ((n = $.inArray('subject', this.env.listcols)) >= 0) {
this.env.subject_col = n;
if (list)
list.subject_col = n;
}
if ((n = $.inArray('flag', this.env.listcols)) >= 0)
this.env.flagged_col = n;
if ((n = $.inArray('status', this.env.listcols)) >= 0)
this.env.status_col = n;
if (list) {
list.hide_column('folder', (this.env.coltypes.folder && this.env.coltypes.folder.hidden) || $.inArray('folder', this.env.listcols) < 0);
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 trash folder state
this.set_trash_count = function(count)
{
this[(count ? 'un' : '') + 'mark_folder'](this.env.trash_mailbox, 'empty', '', true);
};
// 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.startsWith(mbox + this.env.delimiter))
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() { ref.command('hide-headers', '', elem); };
// fetch headers only once
if (!this.gui_objects.all_headers_box.innerHTML) {
this.http_post('headers', {_uid: this.env.uid, _mbox: this.env.mailbox},
this.display_message(this.get_label('loading'), 'loading')
);
}
};
// 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() { ref.command('show-headers', '', elem); };
};
// create folder selector popup, position and display it
this.folder_selector = function(obj, callback)
{
var container = this.folder_selector_element;
if (!container) {
var rows = [],
delim = this.env.delimiter,
ul = $('<ul class="toolbarmenu iconized">'),
li = document.createElement('li'),
link = document.createElement('a'),
span = document.createElement('span');
container = $('<div id="folder-selector" class="popupmenu"></div>');
link.href = '#';
link.className = 'icon';
// loop over sorted folders list
$.each(this.env.mailboxes_list, function() {
var tmp, n = 0, s = 0,
folder = ref.env.mailboxes[this],
id = folder.id,
a = link.cloneNode(false), row = li.cloneNode(false);
if (folder.virtual)
a.className += ' virtual';
else {
a.className += ' active';
a.onclick = function() { container.hide().data('callback')(folder.id); };
}
if (folder['class'])
a.className += ' ' + folder['class'];
// calculate/set indentation level
while ((s = id.indexOf(delim, s)) >= 0) {
n++; s++;
}
a.style.paddingLeft = n ? (n * 16) + 'px' : 0;
// add folder name element
tmp = span.cloneNode(false);
$(tmp).text(folder.name);
a.appendChild(tmp);
row.appendChild(a);
rows.push(row);
});
ul.append(rows).appendTo(container);
// temporarily show element to calculate its size
container.css({left: '-1000px', top: '-1000px'})
.appendTo($('body')).show();
// set max-height if the list is long
if (rows.length > 10)
container.css('max-height', $('li', container)[0].offsetHeight * 10 + 9)
// hide selector on click out of selector element
var fn = function(e) { if (e.target != container.get(0)) container.hide(); };
$(document.body).on('mouseup', fn);
$('iframe').contents().on('mouseup', fn)
.load(function(e) { try { $(this).contents().on('mouseup', fn); } catch(e) {}; });
this.folder_selector_element = container;
}
// position menu on the screen
this.element_position(container, obj);
container.show().data('callback', callback);
};
// position a menu element on the screen in relation to other object
this.element_position = function(element, obj)
{
var obj = $(obj), win = $(window),
width = obj.outerWidth(),
height = obj.outerHeight(),
menu_pos = obj.data('menu-pos'),
win_height = win.height(),
elem_height = $(element).height(),
elem_width = $(element).width(),
pos = obj.offset(),
top = pos.top,
left = pos.left + width;
if (menu_pos == 'bottom') {
top += height;
left -= width;
}
else
left -= 5;
if (top + elem_height > win_height) {
top -= elem_height - height;
if (top < 0)
top = Math.max(0, (win_height - elem_height) / 2);
}
if (left + elem_width > win.width())
left -= elem_width + width;
element.css({left: left + 'px', top: top + 'px'});
};
/********************************************************/
/********* html to text conversion functions *********/
/********************************************************/
- this.html2plain = function(htmlText, id)
+ this.html2plain = function(html, func)
+ {
+ return this.format_converter(html, 'html', func);
+ };
+
+ this.plain2html = function(plain, func)
+ {
+ return this.format_converter(plain, 'plain', func);
+ };
+
+ this.format_converter = function(text, format, func)
{
// warn the user (if converted content is not empty)
- if (!htmlText || !(htmlText.replace(/<[^>]+>|&nbsp;|\s/g, '')).length) {
+ if (!text
+ || (format == 'html' && !(text.replace(/<[^>]+>|&nbsp;|\xC2\xA0|\s/g, '')).length)
+ || (format != 'html' && !(text.replace(/\xC2\xA0|\s/g, '')).length)
+ ) {
// without setTimeout() here, textarea is filled with initial (onload) content
- setTimeout(function() { $('#'+id).val(''); }, 50);
+ setTimeout(function() { if (func) func(''); }, 50);
return true;
}
- if (!confirm(this.get_label('editorwarning')))
+ var confirmed = this.env.editor_warned || confirm(this.get_label('editorwarning'));
+
+ this.env.editor_warned = true;
+
+ if (!confirmed)
return false;
- var url = '?_task=utils&_action=html2text',
+ var url = '?_task=utils&_action=' + (format == 'html' ? 'html2text' : 'text2html'),
lock = this.set_busy(true, 'converting');
this.log('HTTP POST: ' + url);
- $.ajax({ type: 'POST', url: url, data: htmlText, contentType: 'application/octet-stream',
+ $.ajax({ type: 'POST', url: url, data: text, contentType: 'application/octet-stream',
error: function(o, status, err) { ref.http_error(o, status, err, lock); },
- success: function(data) { ref.set_busy(false, null, lock); $('#'+id).val(data); ref.log(data); }
+ success: function(data) {
+ ref.set_busy(false, null, lock);
+ if (func) func(data);
+ }
});
return true;
};
- 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 if (this.env.action)
query._action = this.env.action;
var base = this.env.comm_path, k, param = {};
// overwrite task name
if (action && 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 + (base.indexOf('?') > -1 ? '&' : '?') + $.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), lock);
};
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
url = this.url(action, 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 'move':
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':
// update message flags
$.each(this.env.recent_flags || {}, function(uid, flags) {
ref.set_message(uid, 'deleted', flags.deleted);
ref.set_message(uid, 'replied', flags.answered);
ref.set_message(uid, 'unread', !flags.seen);
ref.set_message(uid, 'forwarded', flags.forwarded);
ref.set_message(uid, 'flagged', flags.flagged);
});
delete this.env.recent_flags;
case 'getunread':
case 'search':
this.env.qsearch = null;
case 'list':
if (this.task == 'mail') {
var is_multifolder = this.is_multifolder_listing();
this.enable_command('show', 'select-all', 'select-none', this.env.messagecount > 0);
this.enable_command('expunge', this.env.exists && !is_multifolder);
this.enable_command('purge', this.purge_mailbox_test() && !is_multifolder);
this.enable_command('import-messages', !is_multifolder);
this.enable_command('expand-all', 'expand-unread', 'collapse-all', this.env.threading && this.env.messagecount && !is_multifolder);
this.enable_command('set-listmode', this.env.threads && !is_multifolder);
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('connerror'), '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);
};
// handler for session errors detected on the server
this.session_error = function(redirect_url)
{
this.env.server_error = 401;
// save message in local storage and do not redirect
if (this.env.action == 'compose') {
this.save_compose_form_local();
}
else if (redirect_url) {
window.setTimeout(function(){ ref.redirect(redirect_url, true); }, 2000);
}
};
// 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);
};
/**
Send multi-threaded parallel HTTP requests to the server for a list if items.
The string '%' in either a GET query or POST parameters will be replaced with the respective item value.
This is the argument object expected: {
items: ['foo','bar','gna'], // list of items to send requests for
action: 'task/some-action', // Roudncube action to call
query: { q:'%s' }, // GET query parameters
postdata: { source:'%s' }, // POST data (sends a POST request if present)
threads: 3, // max. number of concurrent requests
onresponse: function(data){ }, // Callback function called for every response received from server
whendone: function(alldata){ } // Callback function called when all requests have been sent
}
*/
this.multi_thread_http_request = function(prop)
{
var i, item, reqid = new Date().getTime(),
threads = prop.threads || 1;
prop.reqid = reqid;
prop.running = 0;
prop.requests = [];
prop.result = [];
prop._items = $.extend([], prop.items); // copy items
if (!prop.lock)
prop.lock = this.display_message(this.get_label('loading'), 'loading');
// add the request arguments to the jobs pool
this.http_request_jobs[reqid] = prop;
// start n threads
for (i=0; i < threads; i++) {
item = prop._items.shift();
if (item === undefined)
break;
prop.running++;
prop.requests.push(this.multi_thread_send_request(prop, item));
}
return reqid;
};
// helper method to send an HTTP request with the given iterator value
this.multi_thread_send_request = function(prop, item)
{
var k, postdata, query;
// replace %s in post data
if (prop.postdata) {
postdata = {};
for (k in prop.postdata) {
postdata[k] = String(prop.postdata[k]).replace('%s', item);
}
postdata._reqid = prop.reqid;
}
// replace %s in query
else if (typeof prop.query == 'string') {
query = prop.query.replace('%s', item);
query += '&_reqid=' + prop.reqid;
}
else if (typeof prop.query == 'object' && prop.query) {
query = {};
for (k in prop.query) {
query[k] = String(prop.query[k]).replace('%s', item);
}
query._reqid = prop.reqid;
}
// send HTTP GET or POST request
return postdata ? this.http_post(prop.action, postdata) : this.http_request(prop.action, query);
};
// callback function for multi-threaded http responses
this.multi_thread_http_response = function(data, reqid)
{
var prop = this.http_request_jobs[reqid];
if (!prop || prop.running <= 0 || prop.cancelled)
return;
prop.running--;
// trigger response callback
if (prop.onresponse && typeof prop.onresponse == 'function') {
prop.onresponse(data);
}
prop.result = $.extend(prop.result, data);
// send next request if prop.items is not yet empty
var item = prop._items.shift();
if (item !== undefined) {
prop.running++;
prop.requests.push(this.multi_thread_send_request(prop, item));
}
// trigger whendone callback and mark this request as done
else if (prop.running == 0) {
if (prop.whendone && typeof prop.whendone == 'function') {
prop.whendone(prop.result);
}
this.set_busy(false, '', prop.lock);
// remove from this.http_request_jobs pool
delete this.http_request_jobs[reqid];
}
};
// abort a running multi-thread request with the given identifier
this.multi_thread_request_abort = function(reqid)
{
var prop = this.http_request_jobs[reqid];
if (prop) {
for (var i=0; prop.running > 0 && i < prop.requests.length; i++) {
if (prop.requests[i].abort)
prop.requests[i].abort();
}
prop.running = 0;
prop.cancelled = true;
this.set_busy(false, '', prop.lock);
}
};
// post the given form to a hidden iframe
this.async_upload_form = function(form, action, onload)
{
var frame, 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) {
document.body.insertAdjacentHTML('BeforeEnd', '<iframe name="'+frame_name+'"'
+ ' src="program/resources/blank.gif" style="width:0;height:0;visibility:hidden;"></iframe>');
frame = $('iframe[name="'+frame_name+'"]');
}
// for standards-compliant browsers
else {
frame = $('<iframe>').attr('name', frame_name)
.css({border: 'none', width: 0, height: 0, visibility: 'hidden'})
.appendTo(document.body);
}
// handle upload errors, parsing iframe content in onload
frame.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},
xhr: function() { var xhr = jQuery.ajaxSettings.xhr(); if (!formdata && xhr.sendAsBinary) xhr.send = xhr.sendAsBinary; return xhr; },
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 += reader.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();
params._last = Math.floor(this.env.lastrefresh.getTime() / 1000);
this.env.lastrefresh = new Date();
// plugins should bind to 'requestrefresh' event to add own params
this.http_post('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.quotadisplay)
params._quota = 1;
if (this.env.search_request)
params._search = this.env.search_request;
if (this.gui_objects.messagelist) {
params._list = 1;
// message uids for flag updates check
params._uids = $.map(this.message_list.rows, function(row, uid) { return uid; }).join(',');
}
return params;
};
/********************************************************/
/********* helper methods *********/
/********************************************************/
/**
* Quote html entities
*/
this.quote_html = function(str)
{
return String(str).replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
};
// 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);
};
// get the IMP mailbox of the message with the given UID
this.get_message_mailbox = function(uid)
{
var msg = this.env.messages ? this.env.messages[uid] : {};
return msg.mbox || this.env.mailbox;
};
// gets cursor position
this.get_caret_pos = function(obj)
{
if (obj.selectionEnd !== undefined)
return obj.selectionEnd;
return obj.value.length;
};
// moves cursor to specified position
this.set_caret_pos = function(obj, pos)
{
try {
if (obj.setSelectionRange)
obj.setSelectionRange(pos, pos);
}
catch(e) {}; // catch Firefox exception if obj is hidden
};
// get selected text from an input field
this.get_input_selection = function(obj)
{
var start = 0, end = 0, normalizedValue = '';
if (typeof obj.selectionStart == "number" && typeof obj.selectionEnd == "number") {
normalizedValue = obj.value;
start = obj.selectionStart;
end = obj.selectionEnd;
}
return {start: start, end: end, text: normalizedValue.substr(start, end-start)};
};
// 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);
else if (lock || $.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.display_message(String(e), 'error');
};
};
this.check_protocol_handler = function(name, elem)
{
var nav = window.navigator;
if (!nav || (typeof nav.registerProtocolHandler != 'function')) {
$(elem).addClass('disabled').click(function(){ return false; });
}
else {
var status = null;
if (typeof nav.isProtocolHandlerRegistered == 'function') {
status = nav.isProtocolHandlerRegistered('mailto', this.mailto_handler_uri());
if (status)
$(elem).parent().find('.mailtoprotohandler-status').html(status);
}
else {
$(elem).click(function() { ref.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() { ref.env.browser_capabilities.tif = 1; };
img.onerror = function() { ref.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);
};
this.get_local_storage_prefix = function()
{
if (!this.local_storage_prefix)
this.local_storage_prefix = 'roundcube.' + (this.env.user_id || 'anonymous') + '.';
return this.local_storage_prefix;
};
// wrapper for localStorage.getItem(key)
this.local_storage_get_item = function(key, deflt, encrypted)
{
// TODO: add encryption
var item = localStorage.getItem(this.get_local_storage_prefix() + key);
return item !== null ? JSON.parse(item) : (deflt || null);
};
// wrapper for localStorage.setItem(key, data)
this.local_storage_set_item = function(key, data, encrypted)
{
// TODO: add encryption
return localStorage.setItem(this.get_local_storage_prefix() + key, JSON.stringify(data));
};
// wrapper for localStorage.removeItem(key)
this.local_storage_remove_item = function(key)
{
return localStorage.removeItem(this.get_local_storage_prefix() + key);
};
} // 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 || 0) * 15 > $elem.parent().width())
elem.title = $elem.text();
}
};
rcube_webmail.long_subject_title_ex = function(elem)
{
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 + $('span.branch', $elem).width() * 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/js/editor.js b/program/js/editor.js
index af877d7b5..d23d2b26f 100644
--- a/program/js/editor.js
+++ b/program/js/editor.js
@@ -1,187 +1,187 @@
/**
* Roundcube editor js library
*
* This file is part of the Roundcube Webmail client
*
* @licstart The following is the entire license notice for the
* JavaScript code in this file.
*
* Copyright (c) 2006-2014, The Roundcube Dev Team
*
* The JavaScript code in this page is free software: you can
* redistribute it and/or modify it under the terms of the GNU
* General Public License (GNU GPL) as published by the Free Software
* Foundation, either version 3 of the License, or (at your option)
* any later version. The code is distributed WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
*
* As additional permission under GNU GPL version 3 section 7, you
* may distribute non-source (e.g., minimized or compacted) forms of
* that code without the copy of the GNU GPL normally required by
* section 4, provided you include this license notice and a URL
* through which recipients can access the Corresponding Source.
*
* @licend The above is the entire license notice
* for the JavaScript code in this file.
*
* @author Eric Stadtherr <estadtherr@gmail.com>
*/
// Initialize HTML editor
function rcmail_editor_init(config)
{
var ret, conf = {
mode: 'textareas',
editor_selector: 'mce_editor',
apply_source_formatting: true,
theme: 'advanced',
language: config.lang,
- content_css: config.skin_path + '/editor_content.css',
+ content_css: config.skin_path + '/editor_content.css?v2',
theme_advanced_toolbar_location: 'top',
theme_advanced_toolbar_align: 'left',
theme_advanced_buttons3: '',
theme_advanced_statusbar_location: 'none',
extended_valid_elements: 'font[face|size|color|style],span[id|class|align|style]',
relative_urls: false,
remove_script_host: false,
gecko_spellcheck: true,
convert_urls: false, // #1486944
external_image_list: window.rcmail_editor_images,
rc_client: rcmail
};
if (config.mode == 'identity')
$.extend(conf, {
plugins: 'paste,tabfocus',
theme_advanced_buttons1: 'bold,italic,underline,strikethrough,justifyleft,justifycenter,justifyright,justifyfull,separator,outdent,indent,charmap,hr,link,unlink,code,forecolor',
theme_advanced_buttons2: 'fontselect,fontsizeselect'
});
else { // mail compose
$.extend(conf, {
plugins: 'paste,emotions,media,nonbreaking,table,searchreplace,visualchars,directionality,inlinepopups,tabfocus' + (config.spellcheck ? ',spellchecker' : ''),
theme_advanced_buttons1: 'bold,italic,underline,|,justifyleft,justifycenter,justifyright,justifyfull,|,bullist,numlist,outdent,indent,ltr,rtl,blockquote,|,forecolor,backcolor,fontselect,fontsizeselect',
theme_advanced_buttons2: 'link,unlink,table,|,emotions,charmap,image,media,|,code,search,undo,redo',
spellchecker_languages: (rcmail.env.spellcheck_langs ? rcmail.env.spellcheck_langs : 'Dansk=da,Deutsch=de,+English=en,Espanol=es,Francais=fr,Italiano=it,Nederlands=nl,Polski=pl,Portugues=pt,Suomi=fi,Svenska=sv'),
spellchecker_rpc_url: '?_task=utils&_action=spell_html&_remote=1',
spellchecker_enable_learn_rpc: config.spelldict,
accessibility_focus: false,
oninit: 'rcmail_editor_callback'
});
// add handler for spellcheck button state update
conf.setup = function(ed) {
ed.onSetProgressState.add(function(ed, active) {
if (!active)
rcmail.spellcheck_state();
});
ed.onKeyPress.add(function(ed, e) {
rcmail.compose_type_activity++;
});
}
}
// support external configuration settings e.g. from skin
if (window.rcmail_editor_settings)
$.extend(conf, window.rcmail_editor_settings);
tinyMCE.init(conf);
}
// react to real individual tinyMCE editor init
function rcmail_editor_callback()
{
var css = {},
elem = rcube_find_object('_from'),
fe = rcmail.env.compose_focus_elem;
if (rcmail.env.default_font)
css['font-family'] = rcmail.env.default_font;
if (rcmail.env.default_font_size)
css['font-size'] = rcmail.env.default_font_size;
if (css['font-family'] || css['font-size'])
$(tinyMCE.get(rcmail.env.composebody).getBody()).css(css);
if (elem && elem.type == 'select-one') {
rcmail.change_identity(elem);
// Focus previously focused element
if (fe && fe.id != rcmail.env.composebody) {
// use setTimeout() for IE9 (#1488541)
window.setTimeout(function() {
window.focus(); // for WebKit (#1486674)
fe.focus();
}, 10);
}
}
// set tabIndex and set focus to element that was focused before
rcmail_editor_tabindex(fe && fe.id == rcmail.env.composebody);
// Trigger resize (needed for proper editor resizing in some browsers using default skin)
$(window).resize();
}
// set tabIndex on tinyMCE editor
function rcmail_editor_tabindex(focus)
{
if (rcmail.env.task == 'mail') {
var editor = tinyMCE.get(rcmail.env.composebody);
if (editor) {
var textarea = editor.getElement();
var node = editor.getContentAreaContainer().childNodes[0];
if (textarea && node)
node.tabIndex = textarea.tabIndex;
if (focus)
editor.getBody().focus();
}
}
}
// switch html/plain mode
function rcmail_toggle_editor(select, textAreaId, flagElement)
{
var flag, ishtml;
if (select.tagName != 'SELECT')
ishtml = select.checked;
else
ishtml = select.value == 'html';
var res = rcmail.command('toggle-editor', {id:textAreaId, mode:ishtml?'html':'plain'});
if (ishtml) {
// #1486593
setTimeout("rcmail_editor_tabindex(true);", 500);
if (flagElement && (flag = rcube_find_object(flagElement)))
flag.value = '1';
}
else if (res) {
if (flagElement && (flag = rcube_find_object(flagElement)))
flag.value = '0';
if (rcmail.env.composebody)
rcube_find_object(rcmail.env.composebody).focus();
}
else { // !res
if (select.tagName == 'SELECT')
select.value = 'html';
else if (select.tagName == 'INPUT')
select.checked = true;
}
}
// editor callbeck for images listing
function rcmail_editor_images()
{
var i, files = rcmail.env.attachments, list = [];
for (i in files) {
att = files[i];
if (att.complete && att.mimetype.startsWith('image/')) {
list.push([att.name, rcmail.env.comm_path+'&_id='+rcmail.env.compose_id+'&_action=display-attachment&_file='+i]);
}
}
return list;
};
diff --git a/program/lib/Roundcube/rcube_html2text.php b/program/lib/Roundcube/rcube_html2text.php
index 8628371d7..499c4b05c 100644
--- a/program/lib/Roundcube/rcube_html2text.php
+++ b/program/lib/Roundcube/rcube_html2text.php
@@ -1,711 +1,711 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2008-2012, The Roundcube Dev Team |
| Copyright (c) 2005-2007, Jon Abernathy <jon@chuggnutt.com> |
| |
| 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: |
| Converts HTML to formatted plain text (based on html2text class) |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
| Author: Jon Abernathy <jon@chuggnutt.com> |
+-----------------------------------------------------------------------+
*/
/**
* Takes HTML and converts it to formatted, plain text.
*
* Thanks to Alexander Krug (http://www.krugar.de/) to pointing out and
* correcting an error in the regexp search array. Fixed 7/30/03.
*
* Updated set_html() function's file reading mechanism, 9/25/03.
*
* Thanks to Joss Sanglier (http://www.dancingbear.co.uk/) for adding
* several more HTML entity codes to the $search and $replace arrays.
* Updated 11/7/03.
*
* Thanks to Darius Kasperavicius (http://www.dar.dar.lt/) for
* suggesting the addition of $allowed_tags and its supporting function
* (which I slightly modified). Updated 3/12/04.
*
* Thanks to Justin Dearing for pointing out that a replacement for the
* <TH> tag was missing, and suggesting an appropriate fix.
* Updated 8/25/04.
*
* Thanks to Mathieu Collas (http://www.myefarm.com/) for finding a
* display/formatting bug in the _build_link_list() function: email
* readers would show the left bracket and number ("[1") as part of the
* rendered email address.
* Updated 12/16/04.
*
* Thanks to Wojciech Bajon (http://histeria.pl/) for submitting code
* to handle relative links, which I hadn't considered. I modified his
* code a bit to handle normal HTTP links and MAILTO links. Also for
* suggesting three additional HTML entity codes to search for.
* Updated 03/02/05.
*
* Thanks to Jacob Chandler for pointing out another link condition
* for the _build_link_list() function: "https".
* Updated 04/06/05.
*
* Thanks to Marc Bertrand (http://www.dresdensky.com/) for
* suggesting a revision to the word wrapping functionality; if you
* specify a $width of 0 or less, word wrapping will be ignored.
* Updated 11/02/06.
*
* *** Big housecleaning updates below:
*
* Thanks to Colin Brown (http://www.sparkdriver.co.uk/) for
* suggesting the fix to handle </li> and blank lines (whitespace).
* Christian Basedau (http://www.movetheweb.de/) also suggested the
* blank lines fix.
*
* Special thanks to Marcus Bointon (http://www.synchromedia.co.uk/),
* Christian Basedau, Norbert Laposa (http://ln5.co.uk/),
* Bas van de Weijer, and Marijn van Butselaar
* for pointing out my glaring error in the <th> handling. Marcus also
* supplied a host of fixes.
*
* Thanks to Jeffrey Silverman (http://www.newtnotes.com/) for pointing
* out that extra spaces should be compressed--a problem addressed with
* Marcus Bointon's fixes but that I had not yet incorporated.
*
* Thanks to Daniel Schledermann (http://www.typoconsult.dk/) for
* suggesting a valuable fix with <a> tag handling.
*
* Thanks to Wojciech Bajon (again!) for suggesting fixes and additions,
* including the <a> tag handling that Daniel Schledermann pointed
* out but that I had not yet incorporated. I haven't (yet)
* incorporated all of Wojciech's changes, though I may at some
* future time.
*
* *** End of the housecleaning updates. Updated 08/08/07.
*/
/**
* Converts HTML to formatted plain text
*
* @package Framework
* @subpackage Utils
*/
class rcube_html2text
{
/**
* Contains the HTML content to convert.
*
* @var string $html
*/
protected $html;
/**
* Contains the converted, formatted text.
*
* @var string $text
*/
protected $text;
/**
* Maximum width of the formatted text, in columns.
*
* Set this value to 0 (or less) to ignore word wrapping
* and not constrain text to a fixed-width column.
*
* @var integer $width
*/
protected $width = 70;
/**
* Target character encoding for output text
*
* @var string $charset
*/
protected $charset = 'UTF-8';
/**
* List of preg* regular expression patterns to search for,
* used in conjunction with $replace.
*
* @var array $search
* @see $replace
*/
protected $search = array(
"/\r/", // Non-legal carriage return
"/[\n\t]+/", // Newlines and tabs
'/<head[^>]*>.*?<\/head>/i', // <head>
'/<script[^>]*>.*?<\/script>/i', // <script>s -- which strip_tags supposedly has problems with
'/<style[^>]*>.*?<\/style>/i', // <style>s -- which strip_tags supposedly has problems with
'/<p[^>]*>/i', // <P>
'/<br[^>]*>/i', // <br>
'/<i[^>]*>(.*?)<\/i>/i', // <i>
'/<em[^>]*>(.*?)<\/em>/i', // <em>
'/(<ul[^>]*>|<\/ul>)/i', // <ul> and </ul>
'/(<ol[^>]*>|<\/ol>)/i', // <ol> and </ol>
'/<li[^>]*>(.*?)<\/li>/i', // <li> and </li>
'/<li[^>]*>/i', // <li>
'/<hr[^>]*>/i', // <hr>
'/<div[^>]*>/i', // <div>
'/(<table[^>]*>|<\/table>)/i', // <table> and </table>
'/(<tr[^>]*>|<\/tr>)/i', // <tr> and </tr>
'/<td[^>]*>(.*?)<\/td>/i', // <td> and </td>
);
/**
* List of pattern replacements corresponding to patterns searched.
*
* @var array $replace
* @see $search
*/
protected $replace = array(
'', // Non-legal carriage return
' ', // Newlines and tabs
'', // <head>
'', // <script>s -- which strip_tags supposedly has problems with
'', // <style>s -- which strip_tags supposedly has problems with
"\n\n", // <P>
"\n", // <br>
'_\\1_', // <i>
'_\\1_', // <em>
"\n\n", // <ul> and </ul>
"\n\n", // <ol> and </ol>
"\t* \\1\n", // <li> and </li>
"\n\t* ", // <li>
"\n-------------------------\n", // <hr>
"<div>\n", // <div>
"\n\n", // <table> and </table>
"\n", // <tr> and </tr>
"\t\t\\1\n", // <td> and </td>
);
/**
* List of preg* regular expression patterns to search for,
* used in conjunction with $ent_replace.
*
* @var array $ent_search
* @see $ent_replace
*/
protected $ent_search = array(
'/&(nbsp|#160);/i', // Non-breaking space
'/&(quot|rdquo|ldquo|#8220|#8221|#147|#148);/i',
// Double quotes
'/&(apos|rsquo|lsquo|#8216|#8217);/i', // Single quotes
'/&gt;/i', // Greater-than
'/&lt;/i', // Less-than
'/&(copy|#169);/i', // Copyright
'/&(trade|#8482|#153);/i', // Trademark
'/&(reg|#174);/i', // Registered
'/&(mdash|#151|#8212);/i', // mdash
'/&(ndash|minus|#8211|#8722);/i', // ndash
'/&(bull|#149|#8226);/i', // Bullet
'/&(pound|#163);/i', // Pound sign
'/&(euro|#8364);/i', // Euro sign
'/&(amp|#38);/i', // Ampersand: see _converter()
'/[ ]{2,}/', // Runs of spaces, post-handling
);
/**
* List of pattern replacements corresponding to patterns searched.
*
* @var array $ent_replace
* @see $ent_search
*/
protected $ent_replace = array(
' ', // Non-breaking space
'"', // Double quotes
"'", // Single quotes
'>',
'<',
'(c)',
'(tm)',
'(R)',
'--',
'-',
'*',
'£',
'EUR', // Euro sign. € ?
'|+|amp|+|', // Ampersand: see _converter()
' ', // Runs of spaces, post-handling
);
/**
* List of preg* regular expression patterns to search for
* and replace using callback function.
*
* @var array $callback_search
*/
protected $callback_search = array(
'/<(a) [^>]*href=("|\')([^"\']+)\2[^>]*>(.*?)<\/a>/i', // <a href="">
'/<(h)[123456]( [^>]*)?>(.*?)<\/h[123456]>/i', // h1 - h6
'/<(b)( [^>]*)?>(.*?)<\/b>/i', // <b>
'/<(strong)( [^>]*)?>(.*?)<\/strong>/i', // <strong>
'/<(th)( [^>]*)?>(.*?)<\/th>/i', // <th> and </th>
);
/**
* List of preg* regular expression patterns to search for in PRE body,
* used in conjunction with $pre_replace.
*
* @var array $pre_search
* @see $pre_replace
*/
protected $pre_search = array(
"/\n/",
"/\t/",
'/ /',
'/<pre[^>]*>/',
'/<\/pre>/'
);
/**
* List of pattern replacements corresponding to patterns searched for PRE body.
*
* @var array $pre_replace
* @see $pre_search
*/
protected $pre_replace = array(
'<br>',
'&nbsp;&nbsp;&nbsp;&nbsp;',
'&nbsp;',
'',
''
);
/**
* Contains a list of HTML tags to allow in the resulting text.
*
* @var string $allowed_tags
* @see set_allowed_tags()
*/
protected $allowed_tags = '';
/**
* Contains the base URL that relative links should resolve to.
*
* @var string $url
*/
protected $url;
/**
* Indicates whether content in the $html variable has been converted yet.
*
* @var boolean $_converted
* @see $html, $text
*/
protected $_converted = false;
/**
* Contains URL addresses from links to be rendered in plain text.
*
* @var array $_link_list
* @see _build_link_list()
*/
protected $_link_list = array();
/**
* Boolean flag, true if a table of link URLs should be listed after the text.
*
* @var boolean $_do_links
* @see __construct()
*/
protected $_do_links = true;
/**
* Constructor.
*
* If the HTML source string (or file) is supplied, the class
* will instantiate with that source propagated, all that has
* to be done it to call get_text().
*
* @param string $source HTML content
* @param boolean $from_file Indicates $source is a file to pull content from
* @param boolean $do_links Indicate whether a table of link URLs is desired
* @param integer $width Maximum width of the formatted text, 0 for no limit
*/
function __construct($source = '', $from_file = false, $do_links = true, $width = 75, $charset = 'UTF-8')
{
if (!empty($source)) {
$this->set_html($source, $from_file);
}
$this->set_base_url();
$this->_do_links = $do_links;
$this->width = $width;
$this->charset = $charset;
}
/**
* Loads source HTML into memory, either from $source string or a file.
*
* @param string $source HTML content
* @param boolean $from_file Indicates $source is a file to pull content from
*/
function set_html($source, $from_file = false)
{
if ($from_file && file_exists($source)) {
$this->html = file_get_contents($source);
}
else {
$this->html = $source;
}
$this->_converted = false;
}
/**
* Returns the text, converted from HTML.
*
* @return string Plain text
*/
function get_text()
{
if (!$this->_converted) {
$this->_convert();
}
return $this->text;
}
/**
* Prints the text, converted from HTML.
*/
function print_text()
{
print $this->get_text();
}
/**
* Sets the allowed HTML tags to pass through to the resulting text.
*
* Tags should be in the form "<p>", with no corresponding closing tag.
*/
function set_allowed_tags($allowed_tags = '')
{
if (!empty($allowed_tags)) {
$this->allowed_tags = $allowed_tags;
}
}
/**
* Sets a base URL to handle relative links.
*/
function set_base_url($url = '')
{
if (empty($url)) {
if (!empty($_SERVER['HTTP_HOST'])) {
$this->url = 'http://' . $_SERVER['HTTP_HOST'];
}
else {
$this->url = '';
}
}
else {
// Strip any trailing slashes for consistency (relative
// URLs may already start with a slash like "/file.html")
if (substr($url, -1) == '/') {
$url = substr($url, 0, -1);
}
$this->url = $url;
}
}
/**
* Workhorse function that does actual conversion (calls _converter() method).
*/
protected function _convert()
{
// Variables used for building the link list
$this->_link_list = array();
- $text = trim(stripslashes($this->html));
+ $text = $this->html;
// Convert HTML to TXT
$this->_converter($text);
// Add link list
if (!empty($this->_link_list)) {
$text .= "\n\nLinks:\n------\n";
foreach ($this->_link_list as $idx => $url) {
$text .= '[' . ($idx+1) . '] ' . $url . "\n";
}
}
$this->text = $text;
$this->_converted = true;
}
/**
* Workhorse function that does actual conversion.
*
* First performs custom tag replacement specified by $search and
* $replace arrays. Then strips any remaining HTML tags, reduces whitespace
* and newlines to a readable format, and word wraps the text to
* $width characters.
*
* @param string Reference to HTML content string
*/
protected function _converter(&$text)
{
// Convert <BLOCKQUOTE> (before PRE!)
$this->_convert_blockquotes($text);
// Convert <PRE>
$this->_convert_pre($text);
// Run our defined tags search-and-replace
$text = preg_replace($this->search, $this->replace, $text);
// Run our defined tags search-and-replace with callback
$text = preg_replace_callback($this->callback_search, array($this, 'tags_preg_callback'), $text);
// Strip any other HTML tags
$text = strip_tags($text, $this->allowed_tags);
// Run our defined entities/characters search-and-replace
$text = preg_replace($this->ent_search, $this->ent_replace, $text);
// Replace known html entities
$text = html_entity_decode($text, ENT_QUOTES, $this->charset);
// Replace unicode nbsp to regular spaces
$text = preg_replace('/\xC2\xA0/', ' ', $text);
// Remove unknown/unhandled entities (this cannot be done in search-and-replace block)
$text = preg_replace('/&([a-zA-Z0-9]{2,6}|#[0-9]{2,4});/', '', $text);
// Convert "|+|amp|+|" into "&", need to be done after handling of unknown entities
// This properly handles situation of "&amp;quot;" in input string
$text = str_replace('|+|amp|+|', '&', $text);
// Bring down number of empty lines to 2 max
$text = preg_replace("/\n\s+\n/", "\n\n", $text);
$text = preg_replace("/[\n]{3,}/", "\n\n", $text);
// remove leading empty lines (can be produced by eg. P tag on the beginning)
$text = ltrim($text, "\n");
// Wrap the text to a readable format
// for PHP versions >= 4.0.2. Default width is 75
// If width is 0 or less, don't wrap the text.
if ( $this->width > 0 ) {
$text = wordwrap($text, $this->width);
}
}
/**
* Helper function called by preg_replace() on link replacement.
*
* Maintains an internal list of links to be displayed at the end of the
* text, with numeric indices to the original point in the text they
* appeared. Also makes an effort at identifying and handling absolute
* and relative links.
*
* @param string $link URL of the link
* @param string $display Part of the text to associate number with
*/
protected function _build_link_list( $link, $display )
{
if (!$this->_do_links || empty($link)) {
return $display;
}
// Ignored link types
if (preg_match('!^(javascript:|mailto:|#)!i', $link)) {
return $display;
}
if (preg_match('!^([a-z][a-z0-9.+-]+:)!i', $link)) {
$url = $link;
}
else {
$url = $this->url;
if (substr($link, 0, 1) != '/') {
$url .= '/';
}
$url .= "$link";
}
if (($index = array_search($url, $this->_link_list)) === false) {
$index = count($this->_link_list);
$this->_link_list[] = $url;
}
return $display . ' [' . ($index+1) . ']';
}
/**
* Helper function for PRE body conversion.
*
* @param string HTML content
*/
protected function _convert_pre(&$text)
{
// get the content of PRE element
while (preg_match('/<pre[^>]*>(.*)<\/pre>/ismU', $text, $matches)) {
$this->pre_content = $matches[1];
// Run our defined tags search-and-replace with callback
$this->pre_content = preg_replace_callback($this->callback_search,
array($this, 'tags_preg_callback'), $this->pre_content);
// convert the content
$this->pre_content = sprintf('<div><br>%s<br></div>',
preg_replace($this->pre_search, $this->pre_replace, $this->pre_content));
// replace the content (use callback because content can contain $0 variable)
$text = preg_replace_callback('/<pre[^>]*>.*<\/pre>/ismU',
array($this, 'pre_preg_callback'), $text, 1);
// free memory
$this->pre_content = '';
}
}
/**
* Helper function for BLOCKQUOTE body conversion.
*
* @param string HTML content
*/
protected function _convert_blockquotes(&$text)
{
$level = 0;
$offset = 0;
while (($start = strpos($text, '<blockquote', $offset)) !== false) {
$offset = $start + 12;
do {
$end = strpos($text, '</blockquote>', $offset);
$next = strpos($text, '<blockquote', $offset);
// nested <blockquote>, skip
if ($next !== false && $next < $end) {
$offset = $next + 12;
$level++;
}
// nested </blockquote> tag
if ($end !== false && $level > 0) {
$offset = $end + 12;
$level--;
}
// found matching end tag
else if ($end !== false && $level == 0) {
$taglen = strpos($text, '>', $start) - $start;
$startpos = $start + $taglen + 1;
// get blockquote content
$body = trim(substr($text, $startpos, $end - $startpos));
// adjust text wrapping width
$p_width = $this->width;
if ($this->width > 0) $this->width -= 2;
// replace content with inner blockquotes
$this->_converter($body);
// resore text width
$this->width = $p_width;
// Add citation markers and create <pre> block
$body = preg_replace_callback('/((?:^|\n)>*)([^\n]*)/', array($this, 'blockquote_citation_callback'), trim($body));
$body = '<pre>' . htmlspecialchars($body) . '</pre>';
$text = substr_replace($text, $body . "\n", $start, $end + 13 - $start);
$offset = 0;
break;
}
// abort on invalid tag structure (e.g. no closing tag found)
else {
break;
}
}
while ($end || $next);
}
}
/**
* Callback function to correctly add citation markers for blockquote contents
*/
public function blockquote_citation_callback($m)
{
$line = ltrim($m[2]);
$space = $line[0] == '>' ? '' : ' ';
return $m[1] . '>' . $space . $line;
}
/**
* Callback function for preg_replace_callback use.
*
* @param array PREG matches
* @return string
*/
public function tags_preg_callback($matches)
{
switch (strtolower($matches[1])) {
case 'b':
case 'strong':
return $this->_toupper($matches[3]);
case 'th':
return $this->_toupper("\t\t". $matches[3] ."\n");
case 'h':
return $this->_toupper("\n\n". $matches[3] ."\n\n");
case 'a':
// Remove spaces in URL (#1487805)
$url = str_replace(' ', '', $matches[3]);
return $this->_build_link_list($url, $matches[4]);
}
}
/**
* Callback function for preg_replace_callback use in PRE content handler.
*
* @param array PREG matches
* @return string
*/
public function pre_preg_callback($matches)
{
return $this->pre_content;
}
/**
* Strtoupper function with HTML tags and entities handling.
*
* @param string $str Text to convert
* @return string Converted text
*/
private function _toupper($str)
{
// string can containg HTML tags
$chunks = preg_split('/(<[^>]*>)/', $str, null, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
// convert toupper only the text between HTML tags
foreach ($chunks as $idx => $chunk) {
if ($chunk[0] != '<') {
$chunks[$idx] = $this->_strtoupper($chunk);
}
}
return implode($chunks);
}
/**
* Strtoupper multibyte wrapper function with HTML entities handling.
*
* @param string $str Text to convert
* @return string Converted text
*/
private function _strtoupper($str)
{
$str = html_entity_decode($str, ENT_COMPAT, $this->charset);
$str = mb_strtoupper($str);
$str = htmlspecialchars($str, ENT_COMPAT, $this->charset);
return $str;
}
}
diff --git a/program/lib/Roundcube/rcube_mime.php b/program/lib/Roundcube/rcube_mime.php
index 091b2fae8..370d5a8d5 100644
--- a/program/lib/Roundcube/rcube_mime.php
+++ b/program/lib/Roundcube/rcube_mime.php
@@ -1,892 +1,908 @@
<?php
/*
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2012, The Roundcube Dev Team |
| Copyright (C) 2011-2012, Kolab Systems AG |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| MIME message parsing utilities |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
/**
* Class for parsing MIME messages
*
* @package Framework
* @subpackage Storage
* @author Thomas Bruederli <roundcube@gmail.com>
* @author Aleksander Machniak <alec@alec.pl>
*/
class rcube_mime
{
private static $default_charset;
/**
* Object constructor.
*/
function __construct($default_charset = null)
{
self::$default_charset = $default_charset;
}
/**
* Returns message/object character set name
*
* @return string Characted set name
*/
public static function get_charset()
{
if (self::$default_charset) {
return self::$default_charset;
}
if ($charset = rcube::get_instance()->config->get('default_charset')) {
return $charset;
}
return RCUBE_CHARSET;
}
/**
* Parse the given raw message source and return a structure
* of rcube_message_part objects.
*
* It makes use of the PEAR:Mail_mimeDecode library
*
* @param string The message source
* @return object rcube_message_part The message structure
*/
public static function parse_message($raw_body)
{
$mime = new Mail_mimeDecode($raw_body);
$struct = $mime->decode(array('include_bodies' => true, 'decode_bodies' => true));
return self::structure_part($struct);
}
/**
* Recursive method to convert a Mail_mimeDecode part into a rcube_message_part object
*
* @param object A message part struct
* @param int Part count
* @param string Parent MIME ID
*
* @return object rcube_message_part
*/
private static function structure_part($part, $count=0, $parent='')
{
$struct = new rcube_message_part;
$struct->mime_id = $part->mime_id ? $part->mime_id : (empty($parent) ? (string)$count : "$parent.$count");
$struct->headers = $part->headers;
$struct->ctype_primary = $part->ctype_primary;
$struct->ctype_secondary = $part->ctype_secondary;
$struct->mimetype = $part->ctype_primary . '/' . $part->ctype_secondary;
$struct->ctype_parameters = $part->ctype_parameters;
if ($part->headers['content-transfer-encoding'])
$struct->encoding = $part->headers['content-transfer-encoding'];
if ($part->ctype_parameters['charset'])
$struct->charset = $part->ctype_parameters['charset'];
$part_charset = $struct->charset ? $struct->charset : self::get_charset();
// determine filename
if (($filename = $part->d_parameters['filename']) || ($filename = $part->ctype_parameters['name'])) {
$struct->filename = rcube_mime::decode_mime_string($filename, $part_charset);
}
// copy part body and convert it to UTF-8 if necessary
$struct->body = $part->ctype_primary == 'text' || !$part->ctype_parameters['charset'] ? rcube_charset::convert($part->body, $part_charset) : $part->body;
$struct->size = strlen($part->body);
$struct->disposition = $part->disposition;
foreach ((array)$part->parts as $child_part) {
$struct->parts[] = self::structure_part($child_part, ++$count, $struct->mime_id);
}
return $struct;
}
/**
* Split an address list into a structured array list
*
* @param string $input Input string
* @param int $max List only this number of addresses
* @param boolean $decode Decode address strings
* @param string $fallback Fallback charset if none specified
* @param boolean $addronly Return flat array with e-mail addresses only
*
* @return array Indexed list of addresses
*/
static function decode_address_list($input, $max = null, $decode = true, $fallback = null, $addronly = false)
{
$a = self::parse_address_list($input, $decode, $fallback);
$out = array();
$j = 0;
// Special chars as defined by RFC 822 need to in quoted string (or escaped).
$special_chars = '[\(\)\<\>\\\.\[\]@,;:"]';
if (!is_array($a))
return $out;
foreach ($a as $val) {
$j++;
$address = trim($val['address']);
if ($addronly) {
$out[$j] = $address;
}
else {
$name = trim($val['name']);
if ($name && $address && $name != $address)
$string = sprintf('%s <%s>', preg_match("/$special_chars/", $name) ? '"'.addcslashes($name, '"').'"' : $name, $address);
else if ($address)
$string = $address;
else if ($name)
$string = $name;
$out[$j] = array('name' => $name, 'mailto' => $address, 'string' => $string);
}
if ($max && $j==$max)
break;
}
return $out;
}
/**
* Decode a message header value
*
* @param string $input Header value
* @param string $fallback Fallback charset if none specified
*
* @return string Decoded string
*/
public static function decode_header($input, $fallback = null)
{
$str = self::decode_mime_string((string)$input, $fallback);
return $str;
}
/**
* Decode a mime-encoded string to internal charset
*
* @param string $input Header value
* @param string $fallback Fallback charset if none specified
*
* @return string Decoded string
*/
public static function decode_mime_string($input, $fallback = null)
{
$default_charset = !empty($fallback) ? $fallback : self::get_charset();
// rfc: all line breaks or other characters not found
// in the Base64 Alphabet must be ignored by decoding software
// delete all blanks between MIME-lines, differently we can
// receive unnecessary blanks and broken utf-8 symbols
$input = preg_replace("/\?=\s+=\?/", '?==?', $input);
// encoded-word regexp
$re = '/=\?([^?]+)\?([BbQq])\?([^\n]*?)\?=/';
// Find all RFC2047's encoded words
if (preg_match_all($re, $input, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {
// Initialize variables
$tmp = array();
$out = '';
$start = 0;
foreach ($matches as $idx => $m) {
$pos = $m[0][1];
$charset = $m[1][0];
$encoding = $m[2][0];
$text = $m[3][0];
$length = strlen($m[0][0]);
// Append everything that is before the text to be decoded
if ($start != $pos) {
$substr = substr($input, $start, $pos-$start);
$out .= rcube_charset::convert($substr, $default_charset);
$start = $pos;
}
$start += $length;
// Per RFC2047, each string part "MUST represent an integral number
// of characters . A multi-octet character may not be split across
// adjacent encoded-words." However, some mailers break this, so we
// try to handle characters spanned across parts anyway by iterating
// through and aggregating sequential encoded parts with the same
// character set and encoding, then perform the decoding on the
// aggregation as a whole.
$tmp[] = $text;
if ($next_match = $matches[$idx+1]) {
if ($next_match[0][1] == $start
&& $next_match[1][0] == $charset
&& $next_match[2][0] == $encoding
) {
continue;
}
}
$count = count($tmp);
$text = '';
// Decode and join encoded-word's chunks
if ($encoding == 'B' || $encoding == 'b') {
// base64 must be decoded a segment at a time
for ($i=0; $i<$count; $i++)
$text .= base64_decode($tmp[$i]);
}
else { //if ($encoding == 'Q' || $encoding == 'q') {
// quoted printable can be combined and processed at once
for ($i=0; $i<$count; $i++)
$text .= $tmp[$i];
$text = str_replace('_', ' ', $text);
$text = quoted_printable_decode($text);
}
$out .= rcube_charset::convert($text, $charset);
$tmp = array();
}
// add the last part of the input string
if ($start != strlen($input)) {
$out .= rcube_charset::convert(substr($input, $start), $default_charset);
}
// return the results
return $out;
}
// no encoding information, use fallback
return rcube_charset::convert($input, $default_charset);
}
/**
* Decode a mime part
*
* @param string $input Input string
* @param string $encoding Part encoding
* @return string Decoded string
*/
public static function decode($input, $encoding = '7bit')
{
switch (strtolower($encoding)) {
case 'quoted-printable':
return quoted_printable_decode($input);
case 'base64':
return base64_decode($input);
case 'x-uuencode':
case 'x-uue':
case 'uue':
case 'uuencode':
return convert_uudecode($input);
case '7bit':
default:
return $input;
}
}
/**
* Split RFC822 header string into an associative array
* @access private
*/
public static function parse_headers($headers)
{
$a_headers = array();
$headers = preg_replace('/\r?\n(\t| )+/', ' ', $headers);
$lines = explode("\n", $headers);
$c = count($lines);
for ($i=0; $i<$c; $i++) {
if ($p = strpos($lines[$i], ': ')) {
$field = strtolower(substr($lines[$i], 0, $p));
$value = trim(substr($lines[$i], $p+1));
if (!empty($value))
$a_headers[$field] = $value;
}
}
return $a_headers;
}
/**
* @access private
*/
private static function parse_address_list($str, $decode = true, $fallback = null)
{
// remove any newlines and carriage returns before
$str = preg_replace('/\r?\n(\s|\t)?/', ' ', $str);
// extract list items, remove comments
$str = self::explode_header_string(',;', $str, true);
$result = array();
// simplified regexp, supporting quoted local part
$email_rx = '(\S+|("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+"))@\S+';
foreach ($str as $key => $val) {
$name = '';
$address = '';
$val = trim($val);
if (preg_match('/(.*)<('.$email_rx.')>$/', $val, $m)) {
$address = $m[2];
$name = trim($m[1]);
}
else if (preg_match('/^('.$email_rx.')$/', $val, $m)) {
$address = $m[1];
$name = '';
}
// special case (#1489092)
else if (preg_match('/(\s*<MAILER-DAEMON>)$/', $val, $m)) {
$address = 'MAILER-DAEMON';
$name = substr($val, 0, -strlen($m[1]));
}
else if (preg_match('/('.$email_rx.')/', $val, $m)) {
$name = $m[1];
}
else {
$name = $val;
}
// dequote and/or decode name
if ($name) {
if ($name[0] == '"' && $name[strlen($name)-1] == '"') {
$name = substr($name, 1, -1);
$name = stripslashes($name);
}
if ($decode) {
$name = self::decode_header($name, $fallback);
// some clients encode addressee name with quotes around it
if ($name[0] == '"' && $name[strlen($name)-1] == '"') {
$name = substr($name, 1, -1);
}
}
}
if (!$address && $name) {
$address = $name;
$name = '';
}
if ($address) {
$result[$key] = array('name' => $name, 'address' => $address);
}
}
return $result;
}
/**
* Explodes header (e.g. address-list) string into array of strings
* using specified separator characters with proper handling
* of quoted-strings and comments (RFC2822)
*
* @param string $separator String containing separator characters
* @param string $str Header string
* @param bool $remove_comments Enable to remove comments
*
* @return array Header items
*/
public static function explode_header_string($separator, $str, $remove_comments = false)
{
$length = strlen($str);
$result = array();
$quoted = false;
$comment = 0;
$out = '';
for ($i=0; $i<$length; $i++) {
// we're inside a quoted string
if ($quoted) {
if ($str[$i] == '"') {
$quoted = false;
}
else if ($str[$i] == "\\") {
if ($comment <= 0) {
$out .= "\\";
}
$i++;
}
}
// we are inside a comment string
else if ($comment > 0) {
if ($str[$i] == ')') {
$comment--;
}
else if ($str[$i] == '(') {
$comment++;
}
else if ($str[$i] == "\\") {
$i++;
}
continue;
}
// separator, add to result array
else if (strpos($separator, $str[$i]) !== false) {
if ($out) {
$result[] = $out;
}
$out = '';
continue;
}
// start of quoted string
else if ($str[$i] == '"') {
$quoted = true;
}
// start of comment
else if ($remove_comments && $str[$i] == '(') {
$comment++;
}
if ($comment <= 0) {
$out .= $str[$i];
}
}
if ($out && $comment <= 0) {
$result[] = $out;
}
return $result;
}
/**
* Interpret a format=flowed message body according to RFC 2646
*
- * @param string $text Raw body formatted as flowed text
+ * @param string $text Raw body formatted as flowed text
+ * @param string $mark Mark each flowed line with specified character
*
* @return string Interpreted text with unwrapped lines and stuffed space removed
*/
- public static function unfold_flowed($text)
+ public static function unfold_flowed($text, $mark = null)
{
$text = preg_split('/\r?\n/', $text);
$last = -1;
$q_level = 0;
+ $marks = array();
foreach ($text as $idx => $line) {
if (preg_match('/^(>+)/', $line, $m)) {
// remove quote chars
$q = strlen($m[1]);
$line = preg_replace('/^>+/', '', $line);
// remove (optional) space-staffing
$line = preg_replace('/^ /', '', $line);
// The same paragraph (We join current line with the previous one) when:
// - the same level of quoting
// - previous line was flowed
// - previous line contains more than only one single space (and quote char(s))
if ($q == $q_level
&& isset($text[$last]) && $text[$last][strlen($text[$last])-1] == ' '
&& !preg_match('/^>+ {0,1}$/', $text[$last])
) {
$text[$last] .= $line;
unset($text[$idx]);
+
+ if ($mark) {
+ $marks[$last] = true;
+ }
}
else {
$last = $idx;
}
}
else {
$q = 0;
if ($line == '-- ') {
$last = $idx;
}
else {
// remove space-stuffing
- $line = preg_replace('/^\s/', '', $line);
+ $line = preg_replace('/^ /', '', $line);
if (isset($text[$last]) && $line
&& $text[$last] != '-- '
&& $text[$last][strlen($text[$last])-1] == ' '
) {
$text[$last] .= $line;
unset($text[$idx]);
+
+ if ($mark) {
+ $marks[$last] = true;
+ }
}
else {
$text[$idx] = $line;
$last = $idx;
}
}
}
$q_level = $q;
}
+ if (!empty($marks)) {
+ foreach (array_keys($marks) as $mk) {
+ $text[$mk] = $mark . $text[$mk];
+ }
+ }
+
return implode("\r\n", $text);
}
/**
* Wrap the given text to comply with RFC 2646
*
* @param string $text Text to wrap
* @param int $length Length
* @param string $charset Character encoding of $text
*
* @return string Wrapped text
*/
public static function format_flowed($text, $length = 72, $charset=null)
{
$text = preg_split('/\r?\n/', $text);
foreach ($text as $idx => $line) {
if ($line != '-- ') {
if (preg_match('/^(>+)/', $line, $m)) {
// remove quote chars
$level = strlen($m[1]);
$line = preg_replace('/^>+/', '', $line);
// remove (optional) space-staffing and spaces before the line end
$line = preg_replace('/(^ | +$)/', '', $line);
$prefix = str_repeat('>', $level) . ' ';
$line = $prefix . self::wordwrap($line, $length - $level - 2, " \r\n$prefix", false, $charset);
}
else if ($line) {
$line = self::wordwrap(rtrim($line), $length - 2, " \r\n", false, $charset);
// space-stuffing
$line = preg_replace('/(^|\r\n)(From| |>)/', '\\1 \\2', $line);
}
$text[$idx] = $line;
}
}
return implode("\r\n", $text);
}
/**
* Improved wordwrap function with multibyte support.
* The code is based on Zend_Text_MultiByte::wordWrap().
*
* @param string $string Text to wrap
* @param int $width Line width
* @param string $break Line separator
* @param bool $cut Enable to cut word
* @param string $charset Charset of $string
* @param bool $wrap_quoted When enabled quoted lines will not be wrapped
*
* @return string Text
*/
public static function wordwrap($string, $width=75, $break="\n", $cut=false, $charset=null, $wrap_quoted=true)
{
// Note: Never try to use iconv instead of mbstring functions here
// Iconv's substr/strlen are 100x slower (#1489113)
if ($charset && $charset != RCUBE_CHARSET && function_exists('mb_internal_encoding')) {
mb_internal_encoding($charset);
}
// Convert \r\n to \n, this is our line-separator
$string = str_replace("\r\n", "\n", $string);
$separator = "\n"; // must be 1 character length
$result = array();
while (($stringLength = mb_strlen($string)) > 0) {
$breakPos = mb_strpos($string, $separator, 0);
// quoted line (do not wrap)
if ($wrap_quoted && $string[0] == '>') {
if ($breakPos === $stringLength - 1 || $breakPos === false) {
$subString = $string;
$cutLength = null;
}
else {
$subString = mb_substr($string, 0, $breakPos);
$cutLength = $breakPos + 1;
}
}
// next line found and current line is shorter than the limit
else if ($breakPos !== false && $breakPos < $width) {
if ($breakPos === $stringLength - 1) {
$subString = $string;
$cutLength = null;
}
else {
$subString = mb_substr($string, 0, $breakPos);
$cutLength = $breakPos + 1;
}
}
else {
$subString = mb_substr($string, 0, $width);
// last line
if ($breakPos === false && $subString === $string) {
$cutLength = null;
}
else {
$nextChar = mb_substr($string, $width, 1);
if ($nextChar === ' ' || $nextChar === $separator) {
$afterNextChar = mb_substr($string, $width + 1, 1);
// Note: mb_substr() does never return False
if ($afterNextChar === false || $afterNextChar === '') {
$subString .= $nextChar;
}
$cutLength = mb_strlen($subString) + 1;
}
else {
$spacePos = mb_strrpos($subString, ' ', 0);
if ($spacePos !== false) {
$subString = mb_substr($subString, 0, $spacePos);
$cutLength = $spacePos + 1;
}
else if ($cut === false) {
$spacePos = mb_strpos($string, ' ', 0);
if ($spacePos !== false && ($breakPos === false || $spacePos < $breakPos)) {
$subString = mb_substr($string, 0, $spacePos);
$cutLength = $spacePos + 1;
}
else if ($breakPos === false) {
$subString = $string;
$cutLength = null;
}
else {
$subString = mb_substr($string, 0, $breakPos);
$cutLength = $breakPos + 1;
}
}
else {
$cutLength = $width;
}
}
}
}
$result[] = $subString;
if ($cutLength !== null) {
$string = mb_substr($string, $cutLength, ($stringLength - $cutLength));
}
else {
break;
}
}
if ($charset && $charset != RCUBE_CHARSET && function_exists('mb_internal_encoding')) {
mb_internal_encoding(RCUBE_CHARSET);
}
return implode($break, $result);
}
/**
* A method to guess the mime_type of an attachment.
*
* @param string $path Path to the file or file contents
* @param string $name File name (with suffix)
* @param string $failover Mime type supplied for failover
* @param boolean $is_stream Set to True if $path contains file contents
* @param boolean $skip_suffix Set to True if the config/mimetypes.php mappig should be ignored
*
* @return string
* @author Till Klampaeckel <till@php.net>
* @see http://de2.php.net/manual/en/ref.fileinfo.php
* @see http://de2.php.net/mime_content_type
*/
public static function file_content_type($path, $name, $failover = 'application/octet-stream', $is_stream = false, $skip_suffix = false)
{
static $mime_ext = array();
$mime_type = null;
$config = rcube::get_instance()->config;
$mime_magic = $config->get('mime_magic');
if (!$skip_suffix && empty($mime_ext)) {
foreach ($config->resolve_paths('mimetypes.php') as $fpath) {
$mime_ext = array_merge($mime_ext, (array) @include($fpath));
}
}
// use file name suffix with hard-coded mime-type map
if (!$skip_suffix && is_array($mime_ext) && $name) {
if ($suffix = substr($name, strrpos($name, '.')+1)) {
$mime_type = $mime_ext[strtolower($suffix)];
}
}
// try fileinfo extension if available
if (!$mime_type && function_exists('finfo_open')) {
// null as a 2nd argument should be the same as no argument
// this however is not true on all systems/versions
if ($mime_magic) {
$finfo = finfo_open(FILEINFO_MIME, $mime_magic);
}
else {
$finfo = finfo_open(FILEINFO_MIME);
}
if ($finfo) {
if ($is_stream)
$mime_type = finfo_buffer($finfo, $path);
else
$mime_type = finfo_file($finfo, $path);
finfo_close($finfo);
}
}
// try PHP's mime_content_type
if (!$mime_type && !$is_stream && function_exists('mime_content_type')) {
$mime_type = @mime_content_type($path);
}
// fall back to user-submitted string
if (!$mime_type) {
$mime_type = $failover;
}
else {
// Sometimes (PHP-5.3?) content-type contains charset definition,
// Remove it (#1487122) also "charset=binary" is useless
$mime_type = array_shift(preg_split('/[; ]/', $mime_type));
}
return $mime_type;
}
/**
* Get mimetype => file extension mapping
*
* @param string Mime-Type to get extensions for
* @return array List of extensions matching the given mimetype or a hash array with ext -> mimetype mappings if $mimetype is not given
*/
public static function get_mime_extensions($mimetype = null)
{
static $mime_types, $mime_extensions;
// return cached data
if (is_array($mime_types)) {
return $mimetype ? $mime_types[$mimetype] : $mime_extensions;
}
// load mapping file
$file_paths = array();
if ($mime_types = rcube::get_instance()->config->get('mime_types')) {
$file_paths[] = $mime_types;
}
// try common locations
if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
$file_paths[] = 'C:/xampp/apache/conf/mime.types.';
}
else {
$file_paths[] = '/etc/mime.types';
$file_paths[] = '/etc/httpd/mime.types';
$file_paths[] = '/etc/httpd2/mime.types';
$file_paths[] = '/etc/apache/mime.types';
$file_paths[] = '/etc/apache2/mime.types';
$file_paths[] = '/usr/local/etc/httpd/conf/mime.types';
$file_paths[] = '/usr/local/etc/apache/conf/mime.types';
}
foreach ($file_paths as $fp) {
if (@is_readable($fp)) {
$lines = file($fp, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
break;
}
}
$mime_types = $mime_extensions = array();
$regex = "/([\w\+\-\.\/]+)\s+([\w\s]+)/i";
foreach((array)$lines as $line) {
// skip comments or mime types w/o any extensions
if ($line[0] == '#' || !preg_match($regex, $line, $matches))
continue;
$mime = $matches[1];
foreach (explode(' ', $matches[2]) as $ext) {
$ext = trim($ext);
$mime_types[$mime][] = $ext;
$mime_extensions[$ext] = $mime;
}
}
// fallback to some well-known types most important for daily emails
if (empty($mime_types)) {
foreach (rcube::get_instance()->config->resolve_paths('mimetypes.php') as $fpath) {
$mime_extensions = array_merge($mime_extensions, (array) @include($fpath));
}
foreach ($mime_extensions as $ext => $mime) {
$mime_types[$mime][] = $ext;
}
}
// Add some known aliases that aren't included by some mime.types (#1488891)
// the order is important here so standard extensions have higher prio
$aliases = array(
'image/gif' => array('gif'),
'image/png' => array('png'),
'image/x-png' => array('png'),
'image/jpeg' => array('jpg', 'jpeg', 'jpe'),
'image/jpg' => array('jpg', 'jpeg', 'jpe'),
'image/pjpeg' => array('jpg', 'jpeg', 'jpe'),
'image/tiff' => array('tif'),
'message/rfc822' => array('eml'),
'text/x-mail' => array('eml'),
);
foreach ($aliases as $mime => $exts) {
$mime_types[$mime] = array_unique(array_merge((array) $mime_types[$mime], $exts));
foreach ($exts as $ext) {
if (!isset($mime_extensions[$ext])) {
$mime_extensions[$ext] = $mime;
}
}
}
return $mimetype ? $mime_types[$mimetype] : $mime_extensions;
}
/**
* Detect image type of the given binary data by checking magic numbers.
*
* @param string $data Binary file content
*
* @return string Detected mime-type or jpeg as fallback
*/
public static function image_content_type($data)
{
$type = 'jpeg';
if (preg_match('/^\x89\x50\x4E\x47/', $data)) $type = 'png';
else if (preg_match('/^\x47\x49\x46\x38/', $data)) $type = 'gif';
else if (preg_match('/^\x00\x00\x01\x00/', $data)) $type = 'ico';
// else if (preg_match('/^\xFF\xD8\xFF\xE0/', $data)) $type = 'jpeg';
return 'image/' . $type;
}
}
diff --git a/program/lib/Roundcube/rcube_text2html.php b/program/lib/Roundcube/rcube_text2html.php
new file mode 100644
index 000000000..46c2b7e9a
--- /dev/null
+++ b/program/lib/Roundcube/rcube_text2html.php
@@ -0,0 +1,307 @@
+<?php
+
+/**
+ +-----------------------------------------------------------------------+
+ | This file is part of the Roundcube Webmail client |
+ | Copyright (C) 2008-2014, 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: |
+ | Converts plain text to HTML |
+ +-----------------------------------------------------------------------+
+ | Author: Aleksander Machniak <alec@alec.pl> |
+ +-----------------------------------------------------------------------+
+ */
+
+/**
+ * Converts plain text to HTML
+ *
+ * @package Framework
+ * @subpackage Utils
+ */
+class rcube_text2html
+{
+ /**
+ * Contains the HTML content after conversion.
+ *
+ * @var string $html
+ */
+ protected $html;
+
+ /**
+ * Contains the plain text.
+ *
+ * @var string $text
+ */
+ protected $text;
+
+ /**
+ * Configuration
+ *
+ * @var array $config
+ */
+ protected $config = array(
+ // non-breaking space
+ 'space' => "\xC2\xA0",
+ // enables format=flowed parser
+ 'flowed' => false,
+ // enables wrapping for non-flowed text
+ 'wrap' => true,
+ // line-break tag
+ 'break' => "<br>\n",
+ // prefix and suffix (wrapper element)
+ 'begin' => '<div class="pre">',
+ 'end' => '</div>',
+ // enables links replacement
+ 'links' => true,
+ );
+
+
+ /**
+ * Constructor.
+ *
+ * If the plain text source string (or file) is supplied, the class
+ * will instantiate with that source propagated, all that has
+ * to be done it to call get_html().
+ *
+ * @param string $source Plain text
+ * @param boolean $from_file Indicates $source is a file to pull content from
+ * @param array $config Class configuration
+ */
+ function __construct($source = '', $from_file = false, $config = array())
+ {
+ if (!empty($source)) {
+ $this->set_text($source, $from_file);
+ }
+
+ if (!empty($config) && is_array($config)) {
+ $this->config = array_merge($this->config, $config);
+ }
+ }
+
+ /**
+ * Loads source text into memory, either from $source string or a file.
+ *
+ * @param string $source Plain text
+ * @param boolean $from_file Indicates $source is a file to pull content from
+ */
+ function set_text($source, $from_file = false)
+ {
+ if ($from_file && file_exists($source)) {
+ $this->text = file_get_contents($source);
+ }
+ else {
+ $this->text = $source;
+ }
+
+ $this->_converted = false;
+ }
+
+ /**
+ * Returns the HTML content.
+ *
+ * @return string HTML content
+ */
+ function get_html()
+ {
+ if (!$this->_converted) {
+ $this->_convert();
+ }
+
+ return $this->html;
+ }
+
+ /**
+ * Prints the HTML.
+ */
+ function print_html()
+ {
+ print $this->get_html();
+ }
+
+ /**
+ * Workhorse function that does actual conversion (calls _converter() method).
+ */
+ protected function _convert()
+ {
+ // Convert TXT to HTML
+ $this->html = $this->_converter($this->text);
+ $this->_converted = true;
+ }
+
+ /**
+ * Workhorse function that does actual conversion.
+ *
+ * @param string Plain text
+ */
+ protected function _converter($text)
+ {
+ // make links and email-addresses clickable
+ $attribs = array('link_attribs' => array('rel' => 'noreferrer', 'target' => '_blank'));
+ $replacer = new rcmail_string_replacer($attribs);
+
+ if ($this->config['flowed']) {
+ $flowed_char = 0x01;
+ $text = rcube_mime::unfold_flowed($text, chr($flowed_char));
+ }
+
+ // search for patterns like links and e-mail addresses and replace with tokens
+ if ($this->config['links']) {
+ $text = $replacer->replace($text);
+ }
+
+ // split body into single lines
+ $text = preg_split('/\r?\n/', $text);
+ $quote_level = 0;
+ $last = null;
+
+ // wrap quoted lines with <blockquote>
+ for ($n = 0, $cnt = count($text); $n < $cnt; $n++) {
+ $flowed = false;
+ if ($this->config['flowed'] && ord($text[$n][0]) == $flowed_char) {
+ $flowed = true;
+ $text[$n] = substr($text[$n], 1);
+ }
+
+ if ($text[$n][0] == '>' && preg_match('/^(>+ {0,1})+/', $text[$n], $regs)) {
+ $q = substr_count($regs[0], '>');
+ $text[$n] = substr($text[$n], strlen($regs[0]));
+ $text[$n] = $this->_convert_line($text[$n], $flowed || $this->config['wrap']);
+ $_length = strlen(str_replace(' ', '', $text[$n]));
+
+ if ($q > $quote_level) {
+ if ($last !== null) {
+ $text[$last] .= (!$length ? "\n" : '')
+ . $replacer->get_replacement($replacer->add(
+ str_repeat('<blockquote>', $q - $quote_level)))
+ . $text[$n];
+
+ unset($text[$n]);
+ }
+ else {
+ $text[$n] = $replacer->get_replacement($replacer->add(
+ str_repeat('<blockquote>', $q - $quote_level))) . $text[$n];
+
+ $last = $n;
+ }
+ }
+ else if ($q < $quote_level) {
+ $text[$last] .= (!$length ? "\n" : '')
+ . $replacer->get_replacement($replacer->add(
+ str_repeat('</blockquote>', $quote_level - $q)))
+ . $text[$n];
+
+ unset($text[$n]);
+ }
+ else {
+ $last = $n;
+ }
+ }
+ else {
+ $text[$n] = $this->_convert_line($text[$n], $flowed || $this->config['wrap']);
+ $q = 0;
+ $_length = strlen(str_replace(' ', '', $text[$n]));
+
+ if ($quote_level > 0) {
+ $text[$last] .= (!$length ? "\n" : '')
+ . $replacer->get_replacement($replacer->add(
+ str_repeat('</blockquote>', $quote_level)))
+ . $text[$n];
+
+ unset($text[$n]);
+ }
+ else {
+ $last = $n;
+ }
+ }
+
+ $quote_level = $q;
+ $length = $_length;
+ }
+
+ if ($quote_level > 0) {
+ $text[$last] .= $replacer->get_replacement($replacer->add(
+ str_repeat('</blockquote>', $quote_level)));
+ }
+
+ $text = join("\n", $text);
+
+ // colorize signature (up to <sig_max_lines> lines)
+ $len = strlen($text);
+ $sig_sep = "--" . $this->config['space'] . "\n";
+ $sig_max_lines = rcube::get_instance()->config->get('sig_max_lines', 15);
+
+ while (($sp = strrpos($text, $sig_sep, $sp ? -$len+$sp-1 : 0)) !== false) {
+ if ($sp == 0 || $text[$sp-1] == "\n") {
+ // do not touch blocks with more that X lines
+ if (substr_count($text, "\n", $sp) < $sig_max_lines) {
+ $text = substr($text, 0, max(0, $sp))
+ .'<span class="sig">'.substr($text, $sp).'</span>';
+ }
+
+ break;
+ }
+ }
+
+ // insert url/mailto links and citation tags
+ $text = $replacer->resolve($text);
+
+ // replace line breaks
+ $text = str_replace("\n", $this->config['break'], $text);
+
+ return $this->config['begin'] . $text . $this->config['end'];
+ }
+
+ /**
+ * Converts spaces in line of text
+ */
+ protected function _convert_line($text, $is_flowed)
+ {
+ static $table;
+
+ if (empty($table)) {
+ $table = get_html_translation_table(HTML_SPECIALCHARS);
+ unset($table['?']);
+ }
+
+ // skip signature separator
+ if ($text == '-- ') {
+ return '--' . $this->config['space'];
+ }
+
+ // replace HTML special characters
+ $text = strtr($text, $table);
+
+ $nbsp = $this->config['space'];
+
+ // replace some whitespace characters
+ $text = str_replace(array("\r", "\t"), array('', ' '), $text);
+
+ // replace spaces with non-breaking spaces
+ if ($is_flowed) {
+ $pos = 0;
+ $diff = 0;
+ $len = strlen($nbsp);
+ $copy = $text;
+
+ while (($pos = strpos($text, ' ', $pos)) !== false) {
+ if ($pos == 0 || $text[$pos-1] == ' ') {
+ $copy = substr_replace($copy, $nbsp, $pos + $diff, 1);
+ $diff += $len - 1;
+ }
+ $pos++;
+ }
+
+ $text = $copy;
+ }
+ else {
+ // make the whole line non-breakable
+ $text = str_replace(array(' ', '-', '/'), array($nbsp, '-&#8288;', '/&#8288;'), $text);
+ }
+
+ return $text;
+ }
+}
diff --git a/program/localization/en_US/messages.inc b/program/localization/en_US/messages.inc
index 45c91e3d0..0d0a6916c 100644
--- a/program/localization/en_US/messages.inc
+++ b/program/localization/en_US/messages.inc
@@ -1,182 +1,182 @@
<?php
/*
+-----------------------------------------------------------------------+
| localization/<lang>/messages.inc |
| |
| Localization file of the Roundcube Webmail client |
| Copyright (C) 2005-2014, The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| 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['connerror'] = 'Connection Error (Failed to reach the server)!';
$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['nomessages'] = 'No messages';
$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['savingresponse'] = 'Saving response text...';
$messages['deleteresponseconfirm'] = 'Do you really want to delete this response text?';
$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 occurred while saving sent message.';
$messages['errorsaving'] = 'An error occurred 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['restoresavedcomposedata'] = 'A previously composed but unsent message was found.\n\nSubject: $subject\nSaved: $date\n\nDo you want to restore this 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['stillsearching'] = 'Still searching...';
$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 contacts.';
$messages['movesuccess'] = 'Successfully moved $nr contacts.';
$messages['copyerror'] = 'Could not copy any contacts.';
$messages['moveerror'] = 'Could not move any contacts.';
$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['movingcontact'] = 'Moving 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['editorwarning'] = 'Switching editor type may cause 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 occurred. 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/compose.inc b/program/steps/mail/compose.inc
index 2b717d673..0ceb85db2 100644
--- a/program/steps/mail/compose.inc
+++ b/program/steps/mail/compose.inc
@@ -1,1882 +1,1876 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/steps/mail/compose.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: |
| Compose a new mail message with all headers and attachments |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
// define constants for message compose mode
define('RCUBE_COMPOSE_REPLY', 'reply');
define('RCUBE_COMPOSE_FORWARD', 'forward');
define('RCUBE_COMPOSE_DRAFT', 'draft');
define('RCUBE_COMPOSE_EDIT', 'edit');
$MESSAGE_FORM = null;
$COMPOSE_ID = rcube_utils::get_input_value('_id', rcube_utils::INPUT_GET);
$COMPOSE = null;
if ($COMPOSE_ID && $_SESSION['compose_data_'.$COMPOSE_ID])
$COMPOSE =& $_SESSION['compose_data_'.$COMPOSE_ID];
// give replicated session storage some time to synchronize
$retries = 0;
while ($COMPOSE_ID && !is_array($COMPOSE) && $RCMAIL->db->is_replicated() && $retries++ < 5) {
usleep(500000);
$RCMAIL->session->reload();
if ($_SESSION['compose_data_'.$COMPOSE_ID]) {
$COMPOSE =& $_SESSION['compose_data_'.$COMPOSE_ID];
}
}
// Nothing below is called during message composition, only at "new/forward/reply/draft" initialization or
// if a compose-ID is given (i.e. when the compose step is opened in a new window/tab).
if (!is_array($COMPOSE)) {
// Infinite redirect prevention in case of broken session (#1487028)
if ($COMPOSE_ID) {
rcube::raise_error(array('code' => 500, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Invalid compose ID"), true, true);
}
$COMPOSE_ID = uniqid(mt_rand());
$_SESSION['compose_data_'.$COMPOSE_ID] = array(
'id' => $COMPOSE_ID,
'param' => rcube_utils::request2param(rcube_utils::INPUT_GET, 'task|action', true),
'mailbox' => $RCMAIL->storage->get_folder(),
);
$COMPOSE =& $_SESSION['compose_data_'.$COMPOSE_ID];
rcmail_process_compose_params($COMPOSE);
// check if folder for saving sent messages exists and is subscribed (#1486802)
if ($sent_folder = $COMPOSE['param']['sent_mbox']) {
rcmail_check_sent_folder($sent_folder, true);
}
// redirect to a unique URL with all parameters stored in session
$OUTPUT->redirect(array(
'_action' => 'compose',
'_id' => $COMPOSE['id'],
'_search' => $_REQUEST['_search'],
));
}
// add some labels to client
$OUTPUT->add_label('nosubject', 'nosenderwarning', 'norecipientwarning', 'nosubjectwarning', 'cancel',
'nobodywarning', 'notsentwarning', 'notuploadedwarning', 'savingmessage', 'sendingmessage',
'messagesaved', 'converting', 'editorwarning', 'searching', 'uploading', 'uploadingmany',
'fileuploaderror', 'sendmessage', 'savenewresponse', 'responsename', 'responsetext', 'save',
'savingresponse', 'restoresavedcomposedata', 'restoremessage', 'delete', 'restore', 'ignore',
'selectimportfile');
$OUTPUT->set_pagetitle($RCMAIL->gettext('compose'));
$OUTPUT->set_env('compose_id', $COMPOSE['id']);
$OUTPUT->set_env('session_id', session_id());
$OUTPUT->set_env('mailbox', $RCMAIL->storage->get_folder());
$OUTPUT->set_env('top_posting', intval($RCMAIL->config->get('reply_mode')) > 0);
$OUTPUT->set_env('recipients_separator', trim($RCMAIL->config->get('recipients_separator', ',')));
$drafts_mbox = $RCMAIL->config->get('drafts_mbox');
$config_show_sig = $RCMAIL->config->get('show_sig', 1);
// add config parameters to client script
if (strlen($drafts_mbox)) {
$OUTPUT->set_env('drafts_mailbox', $drafts_mbox);
$OUTPUT->set_env('draft_autosave', $RCMAIL->config->get('draft_autosave'));
}
// default font for HTML editor
$font = rcmail::font_defs($RCMAIL->config->get('default_font'));
if ($font && !is_array($font)) {
$OUTPUT->set_env('default_font', $font);
}
// default font size for HTML editor
if ($font_size = $RCMAIL->config->get('default_font_size')) {
$OUTPUT->set_env('default_font_size', $font_size);
}
// get reference message and set compose mode
if ($msg_uid = $COMPOSE['param']['draft_uid']) {
$compose_mode = RCUBE_COMPOSE_DRAFT;
$OUTPUT->set_env('draft_id', $msg_uid);
$RCMAIL->storage->set_folder($drafts_mbox);
}
else if ($msg_uid = $COMPOSE['param']['reply_uid']) {
$compose_mode = RCUBE_COMPOSE_REPLY;
}
else if ($msg_uid = $COMPOSE['param']['forward_uid']) {
$compose_mode = RCUBE_COMPOSE_FORWARD;
$COMPOSE['forward_uid'] = $msg_uid;
$COMPOSE['as_attachment'] = !empty($COMPOSE['param']['attachment']);
}
else if ($msg_uid = $COMPOSE['param']['uid']) {
$compose_mode = RCUBE_COMPOSE_EDIT;
}
$COMPOSE['mode'] = $compose_mode;
$OUTPUT->set_env('compose_mode', $compose_mode);
if ($compose_mode == RCUBE_COMPOSE_EDIT || $compose_mode == RCUBE_COMPOSE_DRAFT) {
// don't add signature in draft/edit mode, we'll also not remove the old-one
// but only on page display, later we should be able to change identity/sig (#1489229)
if ($config_show_sig == 1 || $config_show_sig == 2) {
$OUTPUT->set_env('show_sig_later', true);
}
}
else if ($config_show_sig == 1)
$OUTPUT->set_env('show_sig', true);
else if ($config_show_sig == 2 && empty($compose_mode))
$OUTPUT->set_env('show_sig', true);
else if ($config_show_sig == 3 && ($compose_mode == RCUBE_COMPOSE_REPLY || $compose_mode == RCUBE_COMPOSE_FORWARD))
$OUTPUT->set_env('show_sig', true);
// set line length for body wrapping
$LINE_LENGTH = $RCMAIL->config->get('line_length', 72);
if (!empty($msg_uid) && empty($COMPOSE['as_attachment'])) {
$mbox_name = $RCMAIL->storage->get_folder();
// set format before rcube_message construction
// use the same format as for the message view
if (isset($_SESSION['msg_formats'][$mbox_name.':'.$msg_uid])) {
$RCMAIL->config->set('prefer_html', $_SESSION['msg_formats'][$mbox_name.':'.$msg_uid]);
}
else {
$prefer_html = $RCMAIL->config->get('prefer_html') || $RCMAIL->config->get('htmleditor')
|| $compose_mode == RCUBE_COMPOSE_DRAFT || $compose_mode == RCUBE_COMPOSE_EDIT;
$RCMAIL->config->set('prefer_html', $prefer_html);
}
$MESSAGE = new rcube_message($msg_uid);
// make sure message is marked as read
if ($MESSAGE->headers && empty($MESSAGE->headers->flags['SEEN'])) {
$RCMAIL->storage->set_flag($msg_uid, 'SEEN');
}
if (!empty($MESSAGE->headers->charset)) {
$RCMAIL->storage->set_charset($MESSAGE->headers->charset);
}
if (!$MESSAGE->headers) {
// error
}
else if ($compose_mode == RCUBE_COMPOSE_FORWARD || $compose_mode == RCUBE_COMPOSE_REPLY) {
if ($compose_mode == RCUBE_COMPOSE_REPLY) {
$COMPOSE['reply_uid'] = $msg_uid;
if (!empty($COMPOSE['param']['all'])) {
$MESSAGE->reply_all = $COMPOSE['param']['all'];
}
}
else {
$COMPOSE['forward_uid'] = $msg_uid;
}
$COMPOSE['reply_msgid'] = $MESSAGE->headers->messageID;
$COMPOSE['references'] = trim($MESSAGE->headers->references . " " . $MESSAGE->headers->messageID);
// Save the sent message in the same folder of the message being replied to
if ($RCMAIL->config->get('reply_same_folder') && ($sent_folder = $COMPOSE['mailbox'])
&& rcmail_check_sent_folder($sent_folder, false)
) {
$COMPOSE['param']['sent_mbox'] = $sent_folder;
}
}
else if ($compose_mode == RCUBE_COMPOSE_DRAFT || $compose_mode == RCUBE_COMPOSE_EDIT) {
if ($compose_mode == RCUBE_COMPOSE_DRAFT) {
if ($draft_info = $MESSAGE->headers->get('x-draft-info')) {
// get reply_uid/forward_uid to flag the original message when sending
$info = rcmail_draftinfo_decode($draft_info);
if ($info['type'] == 'reply')
$COMPOSE['reply_uid'] = $info['uid'];
else if ($info['type'] == 'forward')
$COMPOSE['forward_uid'] = $info['uid'];
$COMPOSE['mailbox'] = $info['folder'];
// Save the sent message in the same folder of the message being replied to
if ($RCMAIL->config->get('reply_same_folder') && ($sent_folder = $info['folder'])
&& rcmail_check_sent_folder($sent_folder, false)
) {
$COMPOSE['param']['sent_mbox'] = $sent_folder;
}
}
$COMPOSE['param']['message-id'] = $MESSAGE->headers->get('message-id');
// use message UID as draft_id
$OUTPUT->set_env('draft_id', $msg_uid);
}
if ($in_reply_to = $MESSAGE->headers->get('in-reply-to')) {
$COMPOSE['reply_msgid'] = '<' . $in_reply_to . '>';
}
$COMPOSE['references'] = $MESSAGE->headers->references;
}
}
else {
$MESSAGE = new stdClass();
// apply mailto: URL parameters
if (!empty($COMPOSE['param']['in-reply-to'])) {
$COMPOSE['reply_msgid'] = '<' . $COMPOSE['param']['in-reply-to'] . '>';
}
if (!empty($COMPOSE['param']['references'])) {
$COMPOSE['references'] = $COMPOSE['param']['references'];
}
}
if (!empty($COMPOSE['reply_msgid'])) {
$OUTPUT->set_env('reply_msgid', $COMPOSE['reply_msgid']);
}
$MESSAGE->compose = array();
// get user's identities
$MESSAGE->identities = $RCMAIL->user->list_identities(null, true);
// Set From field value
if (!empty($_POST['_from'])) {
$MESSAGE->compose['from'] = rcube_utils::get_input_value('_from', rcube_utils::INPUT_POST);
}
else if (!empty($COMPOSE['param']['from'])) {
$MESSAGE->compose['from'] = $COMPOSE['param']['from'];
}
else if (count($MESSAGE->identities)) {
$ident = rcmail_identity_select($MESSAGE, $MESSAGE->identities, $compose_mode);
$MESSAGE->compose['from_email'] = $ident['email'];
$MESSAGE->compose['from'] = $ident['identity_id'];
}
// Set other headers
$a_recipients = array();
$parts = array('to', 'cc', 'bcc', 'replyto', 'followupto');
$separator = trim($RCMAIL->config->get('recipients_separator', ',')) . ' ';
$from_email = @mb_strtolower($MESSAGE->compose['from_email']);
foreach ($parts as $header) {
$fvalue = '';
$decode_header = true;
// we have a set of recipients stored is session
if ($header == 'to' && ($mailto_id = $COMPOSE['param']['mailto'])
&& $_SESSION['mailto'][$mailto_id]
) {
$fvalue = urldecode($_SESSION['mailto'][$mailto_id]);
$decode_header = false;
// make session to not grow up too much
unset($_SESSION['mailto'][$mailto_id]);
$COMPOSE['param']['to'] = $fvalue;
}
else if (!empty($_POST['_'.$header])) {
$fvalue = rcube_utils::get_input_value('_'.$header, rcube_utils::INPUT_POST, TRUE);
}
else if (!empty($COMPOSE['param'][$header])) {
$fvalue = $COMPOSE['param'][$header];
}
else if ($compose_mode == RCUBE_COMPOSE_REPLY) {
// get recipent address(es) out of the message headers
if ($header == 'to') {
$mailfollowup = $MESSAGE->headers->others['mail-followup-to'];
$mailreplyto = $MESSAGE->headers->others['mail-reply-to'];
// Reply to mailing list...
if ($MESSAGE->reply_all == 'list' && $mailfollowup)
$fvalue = $mailfollowup;
else if ($MESSAGE->reply_all == 'list'
&& preg_match('/<mailto:([^>]+)>/i', $MESSAGE->headers->others['list-post'], $m))
$fvalue = $m[1];
// Reply to...
else if ($MESSAGE->reply_all && $mailfollowup)
$fvalue = $mailfollowup;
else if ($mailreplyto)
$fvalue = $mailreplyto;
else if (!empty($MESSAGE->headers->replyto))
$fvalue = $MESSAGE->headers->replyto;
else if (!empty($MESSAGE->headers->from))
$fvalue = $MESSAGE->headers->from;
// Reply to message sent by yourself (#1487074, #1489230)
if (!empty($ident) && in_array($ident['ident'], array($fvalue, $MESSAGE->headers->from))) {
$fvalue = $MESSAGE->headers->to;
}
}
// add recipient of original message if reply to all
else if ($header == 'cc' && !empty($MESSAGE->reply_all) && $MESSAGE->reply_all != 'list') {
if ($v = $MESSAGE->headers->to)
$fvalue .= $v;
if ($v = $MESSAGE->headers->cc)
$fvalue .= (!empty($fvalue) ? $separator : '') . $v;
// Use Sender header (#1489011)
if (($v = $MESSAGE->headers->get('Sender', false)) && strpos($v, '-bounces@') === false)
$fvalue .= (!empty($fvalue) ? $separator : '') . $v;
// When To: and Reply-To: are the same we add From: address to the list (#1489037)
if ($v = $MESSAGE->headers->from) {
$from = rcube_mime::decode_address_list($v, null, false, $MESSAGE->headers->charset, true);
$to = rcube_mime::decode_address_list($MESSAGE->headers->to, null, false, $MESSAGE->headers->charset, true);
$replyto = rcube_mime::decode_address_list($MESSAGE->headers->replyto, null, false, $MESSAGE->headers->charset, true);
if (count($replyto) && !count(array_diff($to, $replyto)) && count(array_diff($from, $to))) {
$fvalue .= (!empty($fvalue) ? $separator : '') . $v;
}
}
}
}
else if (in_array($compose_mode, array(RCUBE_COMPOSE_DRAFT, RCUBE_COMPOSE_EDIT))) {
// get drafted headers
if ($header=='to' && !empty($MESSAGE->headers->to))
$fvalue = $MESSAGE->get_header('to', true);
else if ($header=='cc' && !empty($MESSAGE->headers->cc))
$fvalue = $MESSAGE->get_header('cc', true);
else if ($header=='bcc' && !empty($MESSAGE->headers->bcc))
$fvalue = $MESSAGE->get_header('bcc', true);
else if ($header=='replyto' && !empty($MESSAGE->headers->others['mail-reply-to']))
$fvalue = $MESSAGE->get_header('mail-reply-to');
else if ($header=='replyto' && !empty($MESSAGE->headers->replyto))
$fvalue = $MESSAGE->get_header('reply-to');
else if ($header=='followupto' && !empty($MESSAGE->headers->others['mail-followup-to']))
$fvalue = $MESSAGE->get_header('mail-followup-to');
}
// split recipients and put them back together in a unique way
if (!empty($fvalue) && in_array($header, array('to', 'cc', 'bcc'))) {
$to_addresses = rcube_mime::decode_address_list($fvalue, null, $decode_header, $MESSAGE->headers->charset);
$fvalue = array();
foreach ($to_addresses as $addr_part) {
if (empty($addr_part['mailto'])) {
continue;
}
// According to RFC5321 local part of email address is case-sensitive
// however, here it is better to compare addresses in case-insensitive manner
$mailto = format_email(rcube_utils::idn_to_utf8($addr_part['mailto']));
$mailto_lc = mb_strtolower($addr_part['mailto']);
if (($header == 'to' || $compose_mode != RCUBE_COMPOSE_REPLY || $mailto_lc != $from_email)
&& !in_array($mailto_lc, $a_recipients)
) {
if ($addr_part['name'] && $mailto != $addr_part['name']) {
$mailto = format_email_recipient($mailto, $addr_part['name']);
}
$fvalue[] = $mailto;
$a_recipients[] = $mailto_lc;
}
}
$fvalue = implode($separator, $fvalue);
}
$MESSAGE->compose[$header] = $fvalue;
}
unset($a_recipients);
// process $MESSAGE body/attachments, set $MESSAGE_BODY/$HTML_MODE vars and some session data
$MESSAGE_BODY = rcmail_prepare_message_body();
// register UI objects
$OUTPUT->add_handlers(array(
'composeheaders' => 'rcmail_compose_headers',
'composesubject' => 'rcmail_compose_subject',
'composebody' => 'rcmail_compose_body',
'composeattachmentlist' => 'rcmail_compose_attachment_list',
'composeattachmentform' => 'rcmail_compose_attachment_form',
'composeattachment' => 'rcmail_compose_attachment_field',
'filedroparea' => 'compose_file_drop_area',
'priorityselector' => 'rcmail_priority_selector',
'editorselector' => 'rcmail_editor_selector',
'receiptcheckbox' => 'rcmail_receipt_checkbox',
'dsncheckbox' => 'rcmail_dsn_checkbox',
'storetarget' => 'rcmail_store_target_selection',
'addressbooks' => 'rcmail_addressbook_list',
'addresslist' => 'rcmail_contacts_list',
'responseslist' => 'rcmail_compose_responses_list',
));
$OUTPUT->send('compose');
/****** compose mode functions ********/
// process compose request parameters
function rcmail_process_compose_params(&$COMPOSE)
{
if ($COMPOSE['param']['to']) {
$mailto = explode('?', $COMPOSE['param']['to'], 2);
// #1486037: remove "mailto:" prefix
$COMPOSE['param']['to'] = preg_replace('/^mailto:/i', '', $mailto[0]);
// Supported case-insensitive tokens in mailto URL
$url_tokens = array('to', 'cc', 'bcc', 'reply-to', 'in-reply-to', 'references', 'subject', 'body');
if (!empty($mailto[1])) {
parse_str($mailto[1], $query);
foreach ($query as $f => $val) {
if (($key = array_search(strtolower($f), $url_tokens)) !== false) {
$f = $url_tokens[$key];
}
// merge mailto: addresses with addresses from 'to' parameter
if ($f == 'to' && !empty($COMPOSE['param']['to'])) {
$to_addresses = rcube_mime::decode_address_list($COMPOSE['param']['to'], null, true, null, true);
$add_addresses = rcube_mime::decode_address_list($val, null, true);
foreach ($add_addresses as $addr) {
if (!in_array($addr['mailto'], $to_addresses)) {
$to_addresses[] = $addr['mailto'];
$COMPOSE['param']['to'] = (!empty($to_addresses) ? ', ' : '') . $addr['string'];
}
}
}
else {
$COMPOSE['param'][$f] = $val;
}
}
}
}
// resolve _forward_uid=* to an absolute list of messages from a search result
if ($COMPOSE['param']['forward_uid'] == '*' && is_object($_SESSION['search'][1])) {
$COMPOSE['param']['forward_uid'] = $_SESSION['search'][1]->get();
}
// clean HTML message body which can be submitted by URL
if (!empty($COMPOSE['param']['body'])) {
$COMPOSE['param']['body'] = rcmail_wash_html($COMPOSE['param']['body'], array('safe' => false, 'inline_html' => true), array());
}
$RCMAIL = rcmail::get_instance();
// select folder where to save the sent message
$COMPOSE['param']['sent_mbox'] = $RCMAIL->config->get('sent_mbox');
// pipe compose parameters thru plugins
$plugin = $RCMAIL->plugins->exec_hook('message_compose', $COMPOSE);
$COMPOSE['param'] = array_merge($COMPOSE['param'], $plugin['param']);
// add attachments listed by message_compose hook
if (is_array($plugin['attachments'])) {
foreach ($plugin['attachments'] as $attach) {
// we have structured data
if (is_array($attach)) {
$attachment = $attach + array('group' => $COMPOSE_ID);
}
// only a file path is given
else {
$filename = basename($attach);
$attachment = array(
'group' => $COMPOSE_ID,
'name' => $filename,
'mimetype' => rcube_mime::file_content_type($attach, $filename),
'path' => $attach,
);
}
// save attachment if valid
if (($attachment['data'] && $attachment['name']) || ($attachment['path'] && file_exists($attachment['path']))) {
$attachment = rcmail::get_instance()->plugins->exec_hook('attachment_save', $attachment);
}
if ($attachment['status'] && !$attachment['abort']) {
unset($attachment['data'], $attachment['status'], $attachment['abort']);
$COMPOSE['attachments'][$attachment['id']] = $attachment;
}
}
}
}
function rcmail_compose_headers($attrib)
{
global $RCMAIL, $MESSAGE;
list($form_start,) = get_form_tags($attrib);
$out = '';
$part = strtolower($attrib['part']);
switch ($part) {
case 'from':
return $form_start . rcmail_compose_header_from($attrib);
case 'to':
case 'cc':
case 'bcc':
$fname = '_' . $part;
$header = $param = $part;
$allow_attrib = array('id', 'class', 'style', 'cols', 'rows', 'tabindex');
$field_type = 'html_textarea';
break;
case 'replyto':
case 'reply-to':
$fname = '_replyto';
$param = 'replyto';
$header = 'reply-to';
case 'followupto':
case 'followup-to':
if (!$fname) {
$fname = '_followupto';
$param = 'followupto';
$header = 'mail-followup-to';
}
$allow_attrib = array('id', 'class', 'style', 'size', 'tabindex');
$field_type = 'html_inputfield';
break;
}
if ($fname && $field_type) {
// pass the following attributes to the form class
$field_attrib = array('name' => $fname, 'spellcheck' => 'false');
foreach ($attrib as $attr => $value) {
if (in_array($attr, $allow_attrib)) {
$field_attrib[$attr] = $value;
}
}
// create teaxtarea object
$input = new $field_type($field_attrib);
$out = $input->show($MESSAGE->compose[$param]);
}
if ($form_start) {
$out = $form_start . $out;
}
// configure autocompletion
$RCMAIL->autocomplete_init();
return $out;
}
function rcmail_compose_header_from($attrib)
{
global $MESSAGE, $OUTPUT, $RCMAIL, $COMPOSE, $compose_mode;
// pass the following attributes to the form class
$field_attrib = array('name' => '_from');
foreach ($attrib as $attr => $value) {
if (in_array($attr, array('id', 'class', 'style', 'size', 'tabindex'))) {
$field_attrib[$attr] = $value;
}
}
if (count($MESSAGE->identities)) {
$a_signatures = array();
$identities = array();
$separator = intval($RCMAIL->config->get('reply_mode')) > 0
&& ($compose_mode == RCUBE_COMPOSE_REPLY || $compose_mode == RCUBE_COMPOSE_FORWARD) ? '---' : '-- ';
$field_attrib['onchange'] = rcmail_output::JS_OBJECT_NAME.".change_identity(this)";
$select_from = new html_select($field_attrib);
// create SELECT element
foreach ($MESSAGE->identities as $sql_arr) {
$identity_id = $sql_arr['identity_id'];
$select_from->add(format_email_recipient($sql_arr['email'], $sql_arr['name']), $identity_id);
// add signature to array
if (!empty($sql_arr['signature']) && empty($COMPOSE['param']['nosig'])) {
$text = $html = $sql_arr['signature'];
if ($sql_arr['html_signature']) {
$h2t = new rcube_html2text($sql_arr['signature'], false, false);
$text = trim($h2t->get_text());
}
else {
$html = htmlentities($html, ENT_NOQUOTES, RCUBE_CHARSET);
}
if (!preg_match('/^--[ -]\r?\n/m', $text)) {
$text = $separator . "\n" . $text;
$html = $separator . "<br>" . $html;
}
if (!$sql_arr['html_signature']) {
- $html = "<pre>" . $html . "</pre>";
+ $t2h = new rcube_text2html($sql_arr['signature'], false);
+ $html = $t2h->get_html();
}
$a_signatures[$identity_id]['text'] = $text;
$a_signatures[$identity_id]['html'] = $html;
}
// add bcc and reply-to
if (!empty($sql_arr['reply-to'])) {
$identities[$identity_id]['replyto'] = $sql_arr['reply-to'];
}
if (!empty($sql_arr['bcc'])) {
$identities[$identity_id]['bcc'] = $sql_arr['bcc'];
}
}
$out = $select_from->show($MESSAGE->compose['from']);
// add signatures to client
$OUTPUT->set_env('signatures', $a_signatures);
$OUTPUT->set_env('identities', $identities);
}
// no identities, display text input field
else {
$field_attrib['class'] = 'from_address';
$input_from = new html_inputfield($field_attrib);
$out = $input_from->show($MESSAGE->compose['from']);
}
return $out;
}
function rcmail_compose_editor_mode()
{
global $RCMAIL, $compose_mode;
static $useHtml;
if ($useHtml !== null) {
return $useHtml;
}
$html_editor = intval($RCMAIL->config->get('htmleditor'));
if (isset($_POST['_is_html'])) {
$useHtml = !empty($_POST['_is_html']);
}
else if ($compose_mode == RCUBE_COMPOSE_DRAFT || $compose_mode == RCUBE_COMPOSE_EDIT) {
$useHtml = rcmail_message_is_html();
}
else if ($compose_mode == RCUBE_COMPOSE_REPLY) {
$useHtml = ($html_editor == 1 || ($html_editor >= 2 && rcmail_message_is_html()));
}
else if ($compose_mode == RCUBE_COMPOSE_FORWARD) {
$useHtml = ($html_editor == 1 || ($html_editor == 3 && rcmail_message_is_html()));
}
else {
$useHtml = ($html_editor == 1);
}
return $useHtml;
}
function rcmail_message_is_html()
{
global $RCMAIL, $MESSAGE;
return $RCMAIL->config->get('prefer_html') && ($MESSAGE instanceof rcube_message) && $MESSAGE->has_html_part(true);
}
function rcmail_prepare_message_body()
{
global $RCMAIL, $MESSAGE, $COMPOSE, $compose_mode, $HTML_MODE;
// use posted message body
if (!empty($_POST['_message'])) {
$body = rcube_utils::get_input_value('_message', rcube_utils::INPUT_POST, true);
$isHtml = (bool) rcube_utils::get_input_value('_is_html', rcube_utils::INPUT_POST);
}
else if ($COMPOSE['param']['body']) {
$body = $COMPOSE['param']['body'];
$isHtml = (bool) $COMPOSE['param']['html'];
}
// forward as attachment
else if ($compose_mode == RCUBE_COMPOSE_FORWARD && $COMPOSE['as_attachment']) {
$isHtml = rcmail_compose_editor_mode();
$body = '';
rcmail_write_forward_attachments();
}
// reply/edit/draft/forward
else if ($compose_mode && ($compose_mode != RCUBE_COMPOSE_REPLY || intval($RCMAIL->config->get('reply_mode')) != -1)) {
$isHtml = rcmail_compose_editor_mode();
$messages = array();
if (!empty($MESSAGE->parts)) {
// collect IDs of message/rfc822 parts
if ($compose_mode == RCUBE_COMPOSE_EDIT || $compose_mode == RCUBE_COMPOSE_DRAFT) {
foreach ($MESSAGE->attachments as $part) {
if ($part->mimetype == 'message/rfc822') {
$messages[] = $part->mime_id;
}
}
}
foreach ($MESSAGE->parts as $part) {
// skip no-content and attachment parts (#1488557)
if ($part->type != 'content' || !$part->size || $MESSAGE->is_attachment($part)) {
continue;
}
// skip all content parts inside the message/rfc822 part in DRAFT/EDIT mode
foreach ($messages as $mimeid) {
if (strpos($part->mime_id, $mimeid . '.') === 0) {
continue 2;
}
}
if ($part_body = rcmail_compose_part_body($part, $isHtml)) {
$body .= ($body ? ($isHtml ? '<br/>' : "\n") : '') . $part_body;
}
}
}
else {
$body = rcmail_compose_part_body($MESSAGE, $isHtml);
}
// compose reply-body
if ($compose_mode == RCUBE_COMPOSE_REPLY)
$body = rcmail_create_reply_body($body, $isHtml);
// forward message body inline
else if ($compose_mode == RCUBE_COMPOSE_FORWARD)
$body = rcmail_create_forward_body($body, $isHtml);
// load draft message body
else if ($compose_mode == RCUBE_COMPOSE_DRAFT || $compose_mode == RCUBE_COMPOSE_EDIT)
$body = rcmail_create_draft_body($body, $isHtml);
}
else { // new message
$isHtml = rcmail_compose_editor_mode();
}
$plugin = $RCMAIL->plugins->exec_hook('message_compose_body',
array('body' => $body, 'html' => $isHtml, 'mode' => $compose_mode));
$body = $plugin['body'];
unset($plugin);
// add blocked.gif attachment (#1486516)
if ($isHtml && preg_match('#<img src="\./program/resources/blocked\.gif"#', $body)) {
if ($attachment = rcmail_save_image('program/resources/blocked.gif', 'image/gif')) {
$COMPOSE['attachments'][$attachment['id']] = $attachment;
$url = sprintf('%s&_id=%s&_action=display-attachment&_file=rcmfile%s',
$RCMAIL->comm_path, $COMPOSE['id'], $attachment['id']);
$body = preg_replace('#\./program/resources/blocked\.gif#', $url, $body);
}
}
$HTML_MODE = $isHtml;
return $body;
}
function rcmail_compose_part_body($part, $isHtml = false)
{
global $RCMAIL, $MESSAGE, $LINE_LENGTH, $compose_mode;
// Check if we have enough memory to handle the message in it
// #1487424: we need up to 10x more memory than the body
if (!rcube_utils::mem_check($part->size * 10)) {
return '';
}
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);
}
// message is cached but not exists (#1485443), or other error
if ($part->body === false) {
return '';
}
$body = $part->body;
if ($isHtml) {
if ($part->ctype_secondary == 'html') {
}
else if ($part->ctype_secondary == 'enriched') {
$body = rcube_enriched::to_html($body);
}
else {
// try to remove the signature
if ($compose_mode != RCUBE_COMPOSE_DRAFT && $compose_mode != RCUBE_COMPOSE_EDIT) {
if ($RCMAIL->config->get('strip_existing_sig', true)) {
$body = rcmail_remove_signature($body);
}
}
- if ($part->ctype_parameters['format'] == 'flowed') {
- $body = rcube_mime::unfold_flowed($body);
- }
-
// add HTML formatting
- $body = rcmail_plain_body($body);
- if ($body) {
- $body = '<pre>' . $body . '</pre>';
- }
+ $body = rcmail_plain_body($body, $part->ctype_parameters['format'] == 'flowed');
}
}
else {
if ($part->ctype_secondary == 'enriched') {
$body = rcube_enriched::to_html($body);
$part->ctype_secondary = 'html';
}
if ($part->ctype_secondary == 'html') {
// use html part if it has been used for message (pre)viewing
// decrease line length for quoting
$len = $compose_mode == RCUBE_COMPOSE_REPLY ? $LINE_LENGTH-2 : $LINE_LENGTH;
$txt = new rcube_html2text($body, false, true, $len);
$body = $txt->get_text();
}
else {
if ($part->ctype_secondary == 'plain' && $part->ctype_parameters['format'] == 'flowed') {
$body = rcube_mime::unfold_flowed($body);
}
// try to remove the signature
if ($compose_mode != RCUBE_COMPOSE_DRAFT && $compose_mode != RCUBE_COMPOSE_EDIT) {
if ($RCMAIL->config->get('strip_existing_sig', true)) {
$body = rcmail_remove_signature($body);
}
}
}
}
return $body;
}
function rcmail_compose_body($attrib)
{
global $RCMAIL, $OUTPUT, $HTML_MODE, $MESSAGE_BODY;
list($form_start, $form_end) = get_form_tags($attrib);
unset($attrib['form']);
if (empty($attrib['id']))
$attrib['id'] = 'rcmComposeBody';
$attrib['name'] = '_message';
$isHtml = $HTML_MODE;
$out = $form_start ? "$form_start\n" : '';
$saveid = new html_hiddenfield(array('name' => '_draft_saveid', 'value' => $RCMAIL->output->get_env('draft_id')));
$out .= $saveid->show();
$drafttoggle = new html_hiddenfield(array('name' => '_draft', 'value' => 'yes'));
$out .= $drafttoggle->show();
$msgtype = new html_hiddenfield(array('name' => '_is_html', 'value' => ($isHtml ? "1" : "0")));
$out .= $msgtype->show();
$framed = new html_hiddenfield(array('name' => '_framed', 'value' => '1'));
$out .= $framed->show();
// If desired, set this textarea to be editable by TinyMCE
if ($isHtml) {
$MESSAGE_BODY = htmlentities($MESSAGE_BODY, ENT_NOQUOTES, RCUBE_CHARSET);
$attrib['class'] = 'mce_editor';
$attrib['is_escaped'] = true;
$textarea = new html_textarea($attrib);
$out .= $textarea->show($MESSAGE_BODY);
}
else {
$textarea = new html_textarea($attrib);
$out .= $textarea->show('');
// quote plain text, inject into textarea
$table = get_html_translation_table(HTML_SPECIALCHARS);
$MESSAGE_BODY = strtr($MESSAGE_BODY, $table);
$out = substr($out, 0, -11) . $MESSAGE_BODY . '</textarea>';
}
$out .= $form_end ? "\n$form_end" : '';
$OUTPUT->set_env('composebody', $attrib['id']);
// include HTML editor
$RCMAIL->html_editor();
// Set language list
if ($RCMAIL->config->get('enable_spellcheck')) {
$engine = new rcube_spellchecker();
$dictionary = (bool) $RCMAIL->config->get('spellcheck_dictionary');
$spellcheck_langs = $engine->languages();
$lang = $_SESSION['language'];
// if not found in the list, try with two-letter code
if (!$spellcheck_langs[$lang]) {
$lang = strtolower(substr($lang, 0, 2));
}
if (!$spellcheck_langs[$lang]) {
$lang = 'en';
}
$OUTPUT->set_env('spell_langs', $spellcheck_langs);
$OUTPUT->set_env('spell_lang', $lang);
$editor_lang_set = array();
foreach ($spellcheck_langs as $key => $name) {
$editor_lang_set[] = ($key == $lang ? '+' : '') . rcube::JQ($name).'='.rcube::JQ($key);
}
// include GoogieSpell
$OUTPUT->include_script('googiespell.js');
$OUTPUT->add_script(sprintf(
"var googie = new GoogieSpell('%s/images/googiespell/','%s&lang=', %s);\n".
"googie.lang_chck_spell = \"%s\";\n".
"googie.lang_rsm_edt = \"%s\";\n".
"googie.lang_close = \"%s\";\n".
"googie.lang_revert = \"%s\";\n".
"googie.lang_no_error_found = \"%s\";\n".
"googie.lang_learn_word = \"%s\";\n".
"googie.setLanguages(%s);\n".
"googie.setCurrentLanguage('%s');\n".
"googie.setDecoration(false);\n".
"googie.decorateTextarea('%s');\n".
"%s.set_env('spellcheck', googie);",
$RCMAIL->output->get_skin_path(),
$RCMAIL->url(array('_task' => 'utils', '_action' => 'spell', '_remote' => 1)),
!empty($dictionary) ? 'true' : 'false',
rcube::JQ(rcube::Q($RCMAIL->gettext('checkspelling'))),
rcube::JQ(rcube::Q($RCMAIL->gettext('resumeediting'))),
rcube::JQ(rcube::Q($RCMAIL->gettext('close'))),
rcube::JQ(rcube::Q($RCMAIL->gettext('revertto'))),
rcube::JQ(rcube::Q($RCMAIL->gettext('nospellerrors'))),
rcube::JQ(rcube::Q($RCMAIL->gettext('addtodict'))),
rcube_output::json_serialize($spellcheck_langs),
$lang,
$attrib['id'],
rcmail_output::JS_OBJECT_NAME), 'foot');
$OUTPUT->add_label('checking');
$OUTPUT->set_env('spellcheck_langs', join(',', $editor_lang_set));
}
$out .= "\n".'<iframe name="savetarget" src="program/resources/blank.gif" style="width:0;height:0;border:none;visibility:hidden;"></iframe>';
return $out;
}
function rcmail_create_reply_body($body, $bodyIsHtml)
{
global $RCMAIL, $MESSAGE, $LINE_LENGTH;
// build reply prefix
$from = array_pop(rcube_mime::decode_address_list($MESSAGE->get_header('from'), 1, false, $MESSAGE->headers->charset));
$prefix = $RCMAIL->gettext(array(
'name' => 'mailreplyintro',
'vars' => array(
'date' => $RCMAIL->format_date($MESSAGE->headers->date, $RCMAIL->config->get('date_long')),
'sender' => $from['name'] ? $from['name'] : rcube_utils::idn_to_utf8($from['mailto']),
)
));
if (!$bodyIsHtml) {
$body = preg_replace('/\r?\n/', "\n", $body);
$body = trim($body, "\n");
// soft-wrap and quote message text
$body = rcmail_wrap_and_quote($body, $LINE_LENGTH);
$prefix .= "\n";
$suffix = '';
if (intval($RCMAIL->config->get('reply_mode')) > 0) { // top-posting
$prefix = "\n\n\n" . $prefix;
}
}
else {
// save inline images to files
$cid_map = rcmail_write_inline_attachments($MESSAGE);
// set is_safe flag (we need this for html body washing)
rcmail_check_safe($MESSAGE);
// clean up html tags
$body = rcmail_wash_html($body, array('safe' => $MESSAGE->is_safe), $cid_map);
// build reply (quote content)
$prefix = '<p>' . rcube::Q($prefix) . "</p>\n";
$prefix .= '<blockquote>';
if (intval($RCMAIL->config->get('reply_mode')) > 0) { // top-posting
$prefix = '<br>' . $prefix;
$suffix = '</blockquote>';
}
else {
$suffix = '</blockquote><p></p>';
}
}
return $prefix . $body . $suffix;
}
function rcmail_create_forward_body($body, $bodyIsHtml)
{
global $RCMAIL, $MESSAGE, $COMPOSE;
// add attachments
if (!isset($COMPOSE['forward_attachments']) && is_array($MESSAGE->mime_parts)) {
$cid_map = rcmail_write_compose_attachments($MESSAGE, $bodyIsHtml);
}
$date = $RCMAIL->format_date($MESSAGE->headers->date, $RCMAIL->config->get('date_long'));
if (!$bodyIsHtml) {
$prefix = "\n\n\n-------- " . $RCMAIL->gettext('originalmessage') . " --------\n";
$prefix .= $RCMAIL->gettext('subject') . ': ' . $MESSAGE->subject . "\n";
$prefix .= $RCMAIL->gettext('date') . ': ' . $date . "\n";
$prefix .= $RCMAIL->gettext('from') . ': ' . $MESSAGE->get_header('from') . "\n";
$prefix .= $RCMAIL->gettext('to') . ': ' . $MESSAGE->get_header('to') . "\n";
if ($cc = $MESSAGE->headers->get('cc')) {
$prefix .= $RCMAIL->gettext('cc') . ': ' . $cc . "\n";
}
if (($replyto = $MESSAGE->headers->get('reply-to')) && $replyto != $MESSAGE->get_header('from')) {
$prefix .= $RCMAIL->gettext('replyto') . ': ' . $replyto . "\n";
}
$prefix .= "\n";
$body = trim($body, "\r\n");
}
else {
// set is_safe flag (we need this for html body washing)
rcmail_check_safe($MESSAGE);
// clean up html tags
$body = rcmail_wash_html($body, array('safe' => $MESSAGE->is_safe), $cid_map);
$prefix = sprintf(
"<br /><p>-------- " . $RCMAIL->gettext('originalmessage') . " --------</p>" .
"<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\"><tbody>" .
"<tr><th align=\"right\" nowrap=\"nowrap\" valign=\"baseline\">%s: </th><td>%s</td></tr>" .
"<tr><th align=\"right\" nowrap=\"nowrap\" valign=\"baseline\">%s: </th><td>%s</td></tr>" .
"<tr><th align=\"right\" nowrap=\"nowrap\" valign=\"baseline\">%s: </th><td>%s</td></tr>" .
"<tr><th align=\"right\" nowrap=\"nowrap\" valign=\"baseline\">%s: </th><td>%s</td></tr>",
$RCMAIL->gettext('subject'), rcube::Q($MESSAGE->subject),
$RCMAIL->gettext('date'), rcube::Q($date),
$RCMAIL->gettext('from'), rcube::Q($MESSAGE->get_header('from'), 'replace'),
$RCMAIL->gettext('to'), rcube::Q($MESSAGE->get_header('to'), 'replace'));
if ($cc = $MESSAGE->headers->get('cc'))
$prefix .= sprintf("<tr><th align=\"right\" nowrap=\"nowrap\" valign=\"baseline\">%s: </th><td>%s</td></tr>",
$RCMAIL->gettext('cc'), rcube::Q($cc, 'replace'));
if (($replyto = $MESSAGE->headers->get('reply-to')) && $replyto != $MESSAGE->get_header('from'))
$prefix .= sprintf("<tr><th align=\"right\" nowrap=\"nowrap\" valign=\"baseline\">%s: </th><td>%s</td></tr>",
$RCMAIL->gettext('replyto'), rcube::Q($replyto, 'replace'));
$prefix .= "</tbody></table><br>";
}
return $prefix . $body;
}
function rcmail_create_draft_body($body, $bodyIsHtml)
{
global $MESSAGE, $COMPOSE;
// add attachments
// sizeof($MESSAGE->mime_parts can be 1 - e.g. attachment, but no text!
if (empty($COMPOSE['forward_attachments'])
&& is_array($MESSAGE->mime_parts)
&& count($MESSAGE->mime_parts) > 0
) {
$cid_map = rcmail_write_compose_attachments($MESSAGE, $bodyIsHtml);
}
// clean up HTML tags - XSS prevention (#1489251)
if ($bodyIsHtml) {
$body = rcmail_wash_html($body, array('safe' => 1), $cid_map);
// remove comments (produced by washtml)
$body = preg_replace('/<!--[^>]+-->/', '', $body);
// replace cid with href in inline images links
if (!empty($cid_map)) {
$body = str_replace(array_keys($cid_map), array_values($cid_map), $body);
}
}
return $body;
}
function rcmail_remove_signature($body)
{
global $RCMAIL;
$body = str_replace("\r\n", "\n", $body);
$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-1));
}
break;
}
}
return $body;
}
function rcmail_write_compose_attachments(&$message, $bodyIsHtml)
{
global $RCMAIL, $COMPOSE, $compose_mode;
$loaded_attachments = array();
foreach ((array)$COMPOSE['attachments'] as $attachment) {
$loaded_attachments[$attachment['name'] . $attachment['mimetype']] = $attachment;
}
$cid_map = array();
$messages = array();
foreach ((array)$message->mime_parts as $pid => $part) {
if ($part->disposition == 'attachment' || ($part->disposition == 'inline' && $bodyIsHtml) || $part->filename) {
// skip parts that aren't valid attachments
if ($part->ctype_primary == 'multipart' || $part->mimetype == 'application/ms-tnef') {
continue;
}
// skip message attachments in reply mode
if ($part->ctype_primary == 'message' && $compose_mode == RCUBE_COMPOSE_REPLY) {
continue;
}
// skip inline images when forwarding in text mode
if ($part->content_id && $part->disposition == 'inline' && !$bodyIsHtml && $compose_mode == RCUBE_COMPOSE_FORWARD) {
continue;
}
// skip message/rfc822 attachments on forwards (#1489214)
// Thunderbird when forwarding in inline mode displays such attachments
// and skips any attachments from inside of such part, this however
// skipped e.g. images used in HTML body or other attachments. So,
// better to skip .eml attachments but not their content (included files).
if ($part->mimetype == 'message/rfc822') {
if ($compose_mode == RCUBE_COMPOSE_FORWARD) {
continue;
}
$messages[] = $part->mime_id;
}
else if ($compose_mode != RCUBE_COMPOSE_FORWARD) {
// skip attachments included in message/rfc822 attachment (#1486487)
foreach ($messages as $mimeid) {
if (strpos($part->mime_id, $mimeid . '.') === 0) {
continue 2;
}
}
}
if (($attachment = $loaded_attachments[rcmail_attachment_name($part) . $part->mimetype])
|| ($attachment = rcmail_save_attachment($message, $pid))
) {
$COMPOSE['attachments'][$attachment['id']] = $attachment;
if ($bodyIsHtml && ($part->content_id || $part->content_location)) {
$url = sprintf('%s&_id=%s&_action=display-attachment&_file=rcmfile%s',
$RCMAIL->comm_path, $COMPOSE['id'], $attachment['id']);
if ($part->content_id)
$cid_map['cid:'.$part->content_id] = $url;
else
$cid_map[$part->content_location] = $url;
}
}
}
}
$COMPOSE['forward_attachments'] = true;
return $cid_map;
}
function rcmail_write_inline_attachments(&$message)
{
global $RCMAIL, $COMPOSE;
$cid_map = array();
foreach ((array)$message->mime_parts as $pid => $part) {
if (($part->content_id || $part->content_location) && $part->filename) {
if ($attachment = rcmail_save_attachment($message, $pid)) {
$COMPOSE['attachments'][$attachment['id']] = $attachment;
$url = sprintf('%s&_id=%s&_action=display-attachment&_file=rcmfile%s',
$RCMAIL->comm_path, $COMPOSE['id'], $attachment['id']);
if ($part->content_id)
$cid_map['cid:'.$part->content_id] = $url;
else
$cid_map[$part->content_location] = $url;
}
}
}
return $cid_map;
}
// Creates attachment(s) from the forwarded message(s)
function rcmail_write_forward_attachments()
{
global $RCMAIL, $COMPOSE, $MESSAGE;
$storage = $RCMAIL->get_storage();
$names = array();
$refs = array();
$loaded_attachments = array();
foreach ((array)$COMPOSE['attachments'] as $attachment) {
$loaded_attachments[$attachment['name'] . $attachment['mimetype']] = $attachment;
}
if ($COMPOSE['forward_uid'] == '*') {
$index = $storage->index(null, rcmail_sort_column(), rcmail_sort_order());
$COMPOSE['forward_uid'] = $index->get();
}
else if (!is_array($COMPOSE['forward_uid']) && strpos($COMPOSE['forward_uid'], ':')) {
$COMPOSE['forward_uid'] = rcube_imap_generic::uncompressMessageSet($COMPOSE['forward_uid']);
}
else if (is_string($COMPOSE['forward_uid'])) {
$COMPOSE['forward_uid'] = explode(',', $COMPOSE['forward_uid']);
}
foreach ((array)$COMPOSE['forward_uid'] as $uid) {
$message = new rcube_message($uid);
if (empty($message->headers)) {
continue;
}
if (!empty($message->headers->charset)) {
$storage->set_charset($message->headers->charset);
}
if (empty($MESSAGE->subject)) {
$MESSAGE->subject = $message->subject;
}
// generate (unique) attachment name
$name = strlen($message->subject) ? mb_substr($message->subject, 0, 64) : 'message_rfc822';
if (!empty($names[$name])) {
$names[$name]++;
$name .= '_' . $names[$name];
}
$names[$name] = 1;
$name .= '.eml';
$data = $path = null;
if (!empty($loaded_attachments[$name . 'message/rfc822'])) {
continue;
}
// don't load too big attachments into memory
if (!rcube_utils::mem_check($message->size)) {
$temp_dir = unslashify($RCMAIL->config->get('temp_dir'));
$path = tempnam($temp_dir, 'rcmAttmnt');
if ($fp = fopen($path, 'w')) {
$storage->get_raw_body($message->uid, $fp);
fclose($fp);
}
else {
return false;
}
}
else {
$data = $storage->get_raw_body($message->uid);
$curr_mem += $message->size;
}
$attachment = array(
'group' => $COMPOSE['id'],
'name' => $name,
'mimetype' => 'message/rfc822',
'data' => $data,
'path' => $path,
'size' => $path ? filesize($path) : strlen($data),
);
$attachment = $RCMAIL->plugins->exec_hook('attachment_save', $attachment);
if ($attachment['status']) {
unset($attachment['data'], $attachment['status'], $attachment['content_id'], $attachment['abort']);
$COMPOSE['attachments'][$attachment['id']] = $attachment;
}
else if ($path) {
@unlink($path);
}
if ($message->headers->messageID) {
$refs[] = $message->headers->messageID;
}
}
// set In-Reply-To and References headers
if (count($refs) == 1) {
$COMPOSE['reply_msgid'] = $refs[0];
}
if (!empty($refs)) {
$COMPOSE['references'] = implode(' ', $refs);
}
}
function rcmail_save_attachment(&$message, $pid)
{
global $COMPOSE;
$rcmail = rcmail::get_instance();
$part = $message->mime_parts[$pid];
$data = $path = null;
// don't load too big attachments into memory
if (!rcube_utils::mem_check($part->size)) {
$temp_dir = unslashify($rcmail->config->get('temp_dir'));
$path = tempnam($temp_dir, 'rcmAttmnt');
if ($fp = fopen($path, 'w')) {
$message->get_part_content($pid, $fp, true, 0, false);
fclose($fp);
}
else {
return false;
}
}
else {
$data = $message->get_part_content($pid, null, true, 0, false);
}
$mimetype = $part->ctype_primary . '/' . $part->ctype_secondary;
$filename = rcmail_attachment_name($part);
$attachment = array(
'group' => $COMPOSE['id'],
'name' => $filename,
'mimetype' => $mimetype,
'content_id' => $part->content_id,
'data' => $data,
'path' => $path,
'size' => $path ? filesize($path) : strlen($data),
);
$attachment = $rcmail->plugins->exec_hook('attachment_save', $attachment);
if ($attachment['status']) {
unset($attachment['data'], $attachment['status'], $attachment['content_id'], $attachment['abort']);
return $attachment;
}
else if ($path) {
@unlink($path);
}
return false;
}
function rcmail_save_image($path, $mimetype='')
{
global $COMPOSE;
// handle attachments in memory
$data = file_get_contents($path);
$name = rcmail_basename($path);
$attachment = array(
'group' => $COMPOSE['id'],
'name' => $name,
'mimetype' => $mimetype ? $mimetype : rcube_mime::file_content_type($path, $name),
'data' => $data,
'size' => strlen($data),
);
$attachment = rcmail::get_instance()->plugins->exec_hook('attachment_save', $attachment);
if ($attachment['status']) {
unset($attachment['data'], $attachment['status'], $attachment['content_id'], $attachment['abort']);
return $attachment;
}
return false;
}
function rcmail_basename($filename)
{
// basename() is not unicode safe and locale dependent
if (stristr(PHP_OS, 'win') || stristr(PHP_OS, 'netware')) {
return preg_replace('/^.*[\\\\\\/]/', '', $filename);
}
else {
return preg_replace('/^.*[\/]/', '', $filename);
}
}
function rcmail_compose_subject($attrib)
{
global $MESSAGE, $COMPOSE, $compose_mode;
list($form_start, $form_end) = get_form_tags($attrib);
unset($attrib['form']);
$attrib['name'] = '_subject';
$attrib['spellcheck'] = 'true';
$textfield = new html_inputfield($attrib);
$subject = '';
// use subject from post
if (isset($_POST['_subject'])) {
$subject = rcube_utils::get_input_value('_subject', rcube_utils::INPUT_POST, TRUE);
}
// create a reply-subject
else if ($compose_mode == RCUBE_COMPOSE_REPLY) {
if (preg_match('/^re:/i', $MESSAGE->subject))
$subject = $MESSAGE->subject;
else
$subject = 'Re: '.$MESSAGE->subject;
}
// create a forward-subject
else if ($compose_mode == RCUBE_COMPOSE_FORWARD) {
if (preg_match('/^fwd:/i', $MESSAGE->subject))
$subject = $MESSAGE->subject;
else
$subject = 'Fwd: '.$MESSAGE->subject;
}
// creeate a draft-subject
else if ($compose_mode == RCUBE_COMPOSE_DRAFT || $compose_mode == RCUBE_COMPOSE_EDIT) {
$subject = $MESSAGE->subject;
}
else if (!empty($COMPOSE['param']['subject'])) {
$subject = $COMPOSE['param']['subject'];
}
$out = $form_start ? "$form_start\n" : '';
$out .= $textfield->show($subject);
$out .= $form_end ? "\n$form_end" : '';
return $out;
}
function rcmail_compose_attachment_list($attrib)
{
global $RCMAIL, $OUTPUT, $COMPOSE;
// add ID if not given
if (!$attrib['id'])
$attrib['id'] = 'rcmAttachmentList';
$out = "\n";
$jslist = array();
$button = '';
$skin_path = $RCMAIL->config->get('skin_path');
if (is_array($COMPOSE['attachments'])) {
if ($attrib['deleteicon']) {
$button = html::img(array(
'src' => $skin_path . $attrib['deleteicon'],
'alt' => $RCMAIL->gettext('delete')
));
}
else if (rcube_utils::get_boolean($attrib['textbuttons'])) {
$button = rcube::Q($RCMAIL->gettext('delete'));
}
foreach ($COMPOSE['attachments'] as $id => $a_prop) {
if (empty($a_prop)) {
continue;
}
$out .= html::tag('li', array(
'id' => 'rcmfile'.$id,
'class' => rcube_utils::file2class($a_prop['mimetype'], $a_prop['name']),
'onmouseover' => "rcube_webmail.long_subject_title_ex(this, 0)",
),
html::a(array(
'href' => "#delete",
'title' => $RCMAIL->gettext('delete'),
'onclick' => sprintf("return %s.command('remove-attachment','rcmfile%s', this)", rcmail_output::JS_OBJECT_NAME, $id),
'class' => 'delete'
),
$button
) . rcube::Q($a_prop['name'])
);
$jslist['rcmfile'.$id] = array(
'name' => $a_prop['name'],
'complete' => true,
'mimetype' => $a_prop['mimetype']
);
}
}
if ($attrib['deleteicon'])
$COMPOSE['deleteicon'] = $skin_path . $attrib['deleteicon'];
else if (rcube_utils::get_boolean($attrib['textbuttons']))
$COMPOSE['textbuttons'] = true;
if ($attrib['cancelicon'])
$OUTPUT->set_env('cancelicon', $skin_path . $attrib['cancelicon']);
if ($attrib['loadingicon'])
$OUTPUT->set_env('loadingicon', $skin_path . $attrib['loadingicon']);
$OUTPUT->set_env('attachments', $jslist);
$OUTPUT->add_gui_object('attachmentlist', $attrib['id']);
return html::tag('ul', $attrib, $out, html::$common_attrib);
}
function rcmail_compose_attachment_form($attrib)
{
global $OUTPUT, $RCMAIL;
// set defaults
$attrib += array('id' => 'rcmUploadbox', 'buttons' => 'yes');
// Get filesize, enable upload progress bar
$max_filesize = $RCMAIL->upload_init();
$button = new html_inputfield(array('type' => 'button'));
$content = html::div(null, rcmail_compose_attachment_field())
. html::div('hint', $RCMAIL->gettext(array('name' => 'maxuploadsize', 'vars' => array('size' => $max_filesize))));
if (rcube_utils::get_boolean($attrib['buttons'])) {
$content .= html::div('buttons',
$button->show($RCMAIL->gettext('close'), array('class' => 'button', 'onclick' => "$('#$attrib[id]').hide()")) . ' ' .
$button->show($RCMAIL->gettext('upload'), array('class' => 'button mainaction', 'onclick' => rcmail_output::JS_OBJECT_NAME . ".command('send-attachment', this.form)"))
);
}
$out = html::div($attrib, $OUTPUT->form_tag(array(
'id' => $attrib['id'] . 'Frm',
'name' => 'uploadform',
'method' => 'post',
'enctype' => 'multipart/form-data'
), $content
));
$OUTPUT->add_gui_object('uploadform', $attrib['id'] . 'Frm');
return $out;
}
function rcmail_compose_attachment_field($attrib = array())
{
$attrib['type'] = 'file';
$attrib['name'] = '_attachments[]';
$attrib['multiple'] = 'multiple';
$field = new html_inputfield($attrib);
return $field->show();
}
function rcmail_priority_selector($attrib)
{
global $RCMAIL, $MESSAGE;
list($form_start, $form_end) = get_form_tags($attrib);
unset($attrib['form']);
$attrib['name'] = '_priority';
$prio_list = array(
$RCMAIL->gettext('lowest') => 5,
$RCMAIL->gettext('low') => 4,
$RCMAIL->gettext('normal') => 0,
$RCMAIL->gettext('high') => 2,
$RCMAIL->gettext('highest') => 1,
);
$selector = new html_select($attrib);
$selector->add(array_keys($prio_list), array_values($prio_list));
if (isset($_POST['_priority']))
$sel = $_POST['_priority'];
else if (isset($MESSAGE->headers->priority) && intval($MESSAGE->headers->priority) != 3)
$sel = $MESSAGE->headers->priority;
else
$sel = 0;
$out = $form_start ? "$form_start\n" : '';
$out .= $selector->show((int) $sel);
$out .= $form_end ? "\n$form_end" : '';
return $out;
}
function rcmail_receipt_checkbox($attrib)
{
global $RCMAIL, $MESSAGE, $compose_mode;
list($form_start, $form_end) = get_form_tags($attrib);
unset($attrib['form']);
if (!isset($attrib['id']))
$attrib['id'] = 'receipt';
$attrib['name'] = '_receipt';
$attrib['value'] = '1';
$checkbox = new html_checkbox($attrib);
if (isset($_POST['_receipt']))
$mdn_default = $_POST['_receipt'];
else if (in_array($compose_mode, array(RCUBE_COMPOSE_DRAFT, RCUBE_COMPOSE_EDIT)))
$mdn_default = (bool) $MESSAGE->headers->mdn_to;
else
$mdn_default = $RCMAIL->config->get('mdn_default');
$out = $form_start ? "$form_start\n" : '';
$out .= $checkbox->show($mdn_default);
$out .= $form_end ? "\n$form_end" : '';
return $out;
}
function rcmail_dsn_checkbox($attrib)
{
global $RCMAIL;
list($form_start, $form_end) = get_form_tags($attrib);
unset($attrib['form']);
if (!isset($attrib['id']))
$attrib['id'] = 'dsn';
$attrib['name'] = '_dsn';
$attrib['value'] = '1';
$checkbox = new html_checkbox($attrib);
if (isset($_POST['_dsn']))
$dsn_value = (int) $_POST['_dsn'];
else
$dsn_value = $RCMAIL->config->get('dsn_default');
$out = $form_start ? "$form_start\n" : '';
$out .= $checkbox->show($dsn_value);
$out .= $form_end ? "\n$form_end" : '';
return $out;
}
function rcmail_editor_selector($attrib)
{
global $RCMAIL;
// determine whether HTML or plain text should be checked
$useHtml = rcmail_compose_editor_mode();
if (empty($attrib['editorid']))
$attrib['editorid'] = 'rcmComposeBody';
if (empty($attrib['name']))
$attrib['name'] = 'editorSelect';
$attrib['onchange'] = "return rcmail_toggle_editor(this, '".$attrib['editorid']."', '_is_html')";
$select = new html_select($attrib);
$select->add(rcube::Q($RCMAIL->gettext('htmltoggle')), 'html');
$select->add(rcube::Q($RCMAIL->gettext('plaintoggle')), 'plain');
return $select->show($useHtml ? 'html' : 'plain');
}
function rcmail_store_target_selection($attrib)
{
global $COMPOSE, $RCMAIL;
$attrib['name'] = '_store_target';
$select = $RCMAIL->folder_selector(array_merge($attrib, array(
'noselection' => '- ' . $RCMAIL->gettext('dontsave') . ' -',
'folder_filter' => 'mail',
'folder_rights' => 'w',
)));
return $select->show(isset($_POST['_store_target']) ? $_POST['_store_target'] : $COMPOSE['param']['sent_mbox'], $attrib);
}
function rcmail_check_sent_folder($folder, $create=false)
{
global $RCMAIL;
// we'll not save the message, so it doesn't matter
if ($RCMAIL->config->get('no_save_sent_messages')) {
return true;
}
if ($RCMAIL->storage->folder_exists($folder, true)) {
return true;
}
// folder may exist but isn't subscribed (#1485241)
if ($create) {
if (!$RCMAIL->storage->folder_exists($folder))
return $RCMAIL->storage->create_folder($folder, true);
else
return $RCMAIL->storage->subscribe($folder);
}
return false;
}
function get_form_tags($attrib)
{
global $RCMAIL, $MESSAGE_FORM, $COMPOSE;
$form_start = '';
if (!$MESSAGE_FORM) {
$hiddenfields = new html_hiddenfield(array('name' => '_task', 'value' => $RCMAIL->task));
$hiddenfields->add(array('name' => '_action', 'value' => 'send'));
$hiddenfields->add(array('name' => '_id', 'value' => $COMPOSE['id']));
$hiddenfields->add(array('name' => '_attachments'));
$form_start = empty($attrib['form']) ? $RCMAIL->output->form_tag(array('name' => "form", 'method' => "post")) : '';
$form_start .= $hiddenfields->show();
}
$form_end = ($MESSAGE_FORM && !strlen($attrib['form'])) ? '</form>' : '';
$form_name = !empty($attrib['form']) ? $attrib['form'] : 'form';
if (!$MESSAGE_FORM)
$RCMAIL->output->add_gui_object('messageform', $form_name);
$MESSAGE_FORM = $form_name;
return array($form_start, $form_end);
}
function rcmail_addressbook_list($attrib = array())
{
global $RCMAIL, $OUTPUT;
$attrib += array('id' => 'rcmdirectorylist');
$out = '';
$line_templ = html::tag('li', array(
'id' => 'rcmli%s', 'class' => '%s'),
html::a(array('href' => '#list',
'rel' => '%s',
'onclick' => "return ".rcmail_output::JS_OBJECT_NAME.".command('list-adresses','%s',this)"), '%s'));
foreach ($RCMAIL->get_address_sources(false, true) as $j => $source) {
$id = strval(strlen($source['id']) ? $source['id'] : $j);
$js_id = rcube::JQ($id);
// set class name(s)
$class_name = 'addressbook';
if ($source['class_name'])
$class_name .= ' ' . $source['class_name'];
$out .= sprintf($line_templ,
rcube_utils::html_identifier($id,true),
$class_name,
$source['id'],
$js_id, (!empty($source['name']) ? $source['name'] : $id));
}
$OUTPUT->add_gui_object('addressbookslist', $attrib['id']);
return html::tag('ul', $attrib, $out, html::$common_attrib);
}
// return the contacts list as HTML table
function rcmail_contacts_list($attrib = array())
{
global $RCMAIL, $OUTPUT;
$attrib += array('id' => 'rcmAddressList');
// set client env
$OUTPUT->add_gui_object('contactslist', $attrib['id']);
$OUTPUT->set_env('pagecount', 0);
$OUTPUT->set_env('current_page', 0);
$OUTPUT->include_script('list.js');
return $RCMAIL->table_output($attrib, array(), array('name'), 'ID');
}
/**
* Register a certain container as active area to drop files onto
*/
function compose_file_drop_area($attrib)
{
global $OUTPUT;
if ($attrib['id']) {
$OUTPUT->add_gui_object('filedrop', $attrib['id']);
$OUTPUT->set_env('filedrop', array('action' => 'upload', 'fieldname' => '_attachments'));
}
}
/**
*
*/
function rcmail_compose_responses_list($attrib)
{
global $RCMAIL, $OUTPUT;
$attrib += array('id' => 'rcmresponseslist', 'tagname' => 'ul', 'cols' => 1);
$jsenv = array();
$list = new html_table($attrib);
foreach ($RCMAIL->get_compose_responses(true) as $response) {
$key = $response['key'];
$item = html::a(array(
'href '=> '#'.urlencode($response['name']),
'class' => rtrim('insertresponse ' . $attrib['itemclass']),
'unselectable' => 'on',
'rel' => $key,
), rcube::Q($response['name']));
$jsenv[$key] = $response;
$list->add(array(), $item);
}
// set client env
$OUTPUT->set_env('textresponses', $jsenv);
$OUTPUT->add_gui_object('responseslist', $attrib['id']);
return $list->show();
}
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index 7270cf95a..a1d1a4163 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -1,2177 +1,2111 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/steps/mail/func.inc |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2014, The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Provide webmail functionality and GUI objects |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
// always instantiate storage object (but not connect to server yet)
$RCMAIL->storage_init();
// set imap properties and session vars
if (!strlen($mbox = rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_GPC, true))) {
$mbox = strlen($_SESSION['mbox']) ? $_SESSION['mbox'] : 'INBOX';
}
if (!($page = intval($_GET['_page']))) {
$page = $_SESSION['page'] ? $_SESSION['page'] : 1;
}
$RCMAIL->storage->set_folder($_SESSION['mbox'] = $mbox);
$RCMAIL->storage->set_page($_SESSION['page'] = $page);
$a_threading = $RCMAIL->config->get('message_threading', array());
$message_sort_col = $RCMAIL->config->get('message_sort_col');
$message_sort_order = $RCMAIL->config->get('message_sort_order');
// set default sort col/order to session
if (!isset($_SESSION['sort_col'])) {
$_SESSION['sort_col'] = $message_sort_col ? $message_sort_col : '';
}
if (!isset($_SESSION['sort_order'])) {
$_SESSION['sort_order'] = strtoupper($message_sort_order) == 'ASC' ? 'ASC' : 'DESC';
}
// set threads mode
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']);
}
// remove mbox part from _uid
if (($_uid = rcube_utils::get_input_value('_uid', RCUBE_INPUT_GPC)) && !is_array($_uid) && preg_match('/^\d+-.+/', $_uid)) {
list($_uid, $mbox) = explode('-', $_uid, 2);
if (isset($_GET['_uid'])) $_GET['_uid'] = $_uid;
if (isset($_POST['_uid'])) $_POST['_uid'] = $_uid;
$_REQUEST['_uid'] = $_uid;
unset($_uid);
// override mbox
if (!empty($mbox)) {
$_GET['_mbox'] = $mbox;
$_POST['_mbox'] = $mbox;
$RCMAIL->storage->set_folder(($_SESSION['mbox'] = $mbox));
}
}
if (!empty($_SESSION['browser_caps']) && !$OUTPUT->ajax_call) {
$OUTPUT->set_env('browser_capabilities', $_SESSION['browser_caps']);
}
// 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') {
$RCMAIL->storage->search($mbox_name, $_SESSION['search_filter'], RCUBE_CHARSET, rcmail_sort_column());
$search_request = md5($mbox_name.$_SESSION['search_filter']);
$_SESSION['search'] = $RCMAIL->storage->get_search_set();
$_SESSION['search_request'] = $search_request;
$OUTPUT->set_env('search_request', $search_request);
}
$OUTPUT->set_env('search_mods', rcmail_search_mods());
if (!empty($_SESSION['search_scope']))
$OUTPUT->set_env('search_scope', $_SESSION['search_scope']);
}
$threading = (bool) $RCMAIL->storage->get_threading();
$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('reply_all_mode', (int) $RCMAIL->config->get('reply_all_mode'));
$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);
}
// set special folders
foreach (array('drafts', 'trash', 'junk') as $mbox) {
if ($folder = $RCMAIL->config->get($mbox . '_mbox')) {
$OUTPUT->set_env($mbox . '_mailbox', $folder);
}
}
// set configuration
$RCMAIL->set_env_config(array('delete_junk', 'flag_for_deletion', 'read_when_deleted',
'skip_deleted', 'display_next', 'message_extwin', 'compose_extwin', 'forward_attachment'));
if (!$OUTPUT->ajax_call) {
$OUTPUT->add_label('checkingmail', 'deletemessage', 'movemessagetotrash',
'movingmessage', 'copyingmessage', 'deletingmessage', 'markingmessage',
'copy', 'move', 'quota', 'replyall', 'replylist', 'stillsearching');
}
$pagetitle = $RCMAIL->localize_foldername($mbox_name, true);
$pagetitle = str_replace($delimiter, " \xC2\xBB ", $pagetitle);
$OUTPUT->set_pagetitle($pagetitle);
}
// register UI objects
$OUTPUT->add_handlers(array(
'mailboxlist' => array($RCMAIL, 'folder_list'),
'quotadisplay' => array($RCMAIL, 'quota_display'),
'messages' => 'rcmail_message_list',
'messagecountdisplay' => 'rcmail_messagecount_display',
'mailboxname' => 'rcmail_mailbox_name_display',
'messageheaders' => 'rcmail_message_headers',
'messagefullheaders' => 'rcmail_message_full_headers',
'messagebody' => 'rcmail_message_body',
'messagecontentframe' => 'rcmail_messagecontent_frame',
'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',
'move' => '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',
));
/**
* Returns default search mods
*/
function rcmail_search_mods()
{
global $RCMAIL;
$mods = $RCMAIL->config->get('search_mods');
if (empty($mods)) {
$mods = array('*' => array('subject' => 1, 'from' => 1));
foreach (array('sent', 'drafts') as $mbox) {
if ($mbox = $RCMAIL->config->get($mbox . '_mbox')) {
$mods[$mbox] = array('subject' => 1, 'to' => 1);
}
}
}
return $mods;
}
/**
* 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)
&& strtoupper($mbox) != 'INBOX'
) {
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 $RCMAIL, $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'])) {
$list_cols = $RCMAIL->config->get('list_cols');
$a_show_cols = !empty($list_cols) && is_array($list_cols) ? $list_cols : array('subject');
$OUTPUT->set_env('col_movable', !in_array('list_cols', (array)$RCMAIL->config->get('dont_override')));
}
else {
$a_show_cols = preg_split('/[\s,;]+/', str_replace(array("'", '"'), '', $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'] = $RCMAIL->config->get('skin_path');
// set client env
$OUTPUT->add_gui_object('messagelist', $attrib['id']);
$OUTPUT->set_env('autoexpand_threads', intval($RCMAIL->config->get('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('listcols', $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 $RCMAIL, $OUTPUT;
if (empty($a_show_cols)) {
if (!empty($_SESSION['list_attrib']['columns']))
$a_show_cols = $_SESSION['list_attrib']['columns'];
else {
$list_cols = $RCMAIL->config->get('list_cols');
$a_show_cols = !empty($list_cols) && is_array($list_cols) ? $list_cols : array('subject');
}
}
else {
if (!is_array($a_show_cols)) {
$a_show_cols = preg_split('/[\s,;]+/', str_replace(array("'", '"'), '', $a_show_cols));
}
$head_replace = true;
}
// add 'folder' column to list on multi-folder searches
$search_set = $RCMAIL->storage->get_search_set();
$multifolder = $search_set && $search_set[1]->multi;
if ($multifolder && !in_array('folder', $a_show_cols)) {
$a_show_cols[] = 'folder';
$head_replace = true;
}
$mbox = $RCMAIL->storage->get_folder();
// make sure 'threads' and 'subject' columns are present
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');
// 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 ($multifolder && $_SESSION['search_scope'] == 'all') {
$OUTPUT->command('select_folder', '');
}
$OUTPUT->set_env('multifolder_listing', $multifolder);
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;
// make message UIDs unique by appending the folder name
if ($multifolder) {
$header->uid .= '-'.$header->folder;
$header->flags['skip_mbox_check'] = true;
if ($header->parent_uid)
$header->parent_uid .= '-'.$header->folder;
}
$a_msg_cols = array();
$a_msg_flags = array();
// 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 = $RCMAIL->gettext('nosubject');
$cont = rcube::Q($cont);
}
else if ($col == 'size')
$cont = show_bytes($header->$col);
else if ($col == 'date')
$cont = $RCMAIL->format_date($header->date);
else if ($col == 'folder')
$cont = rcube::Q(rcube_charset::convert($header->folder, 'UTF7-IMAP'));
else
$cont = rcube::Q($header->$col);
$a_msg_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'] = rcube::Q($header->ctype);
$a_msg_flags['mbox'] = $header->folder;
// 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 ' . rcmail_output::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' => $RCMAIL->gettext('listoptions')));
else
$list_menu = html::a(array('href' => '#', 'onclick' => $onclick),
html::img(array('src' => $skin_path . $attrib['optionsmenuicon'],
'id' => 'listmenulink', 'title' => $RCMAIL->gettext('listoptions'))));
}
else {
$list_menu = '';
}
$cells = $coltypes = 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) {
$label = '';
$sortable = false;
// get column name
switch ($col) {
case 'flag':
$col_name = html::span('flagged', '&nbsp;');
break;
case 'attachment':
case 'priority':
case 'status':
$col_name = html::span($col, '&nbsp;');
break;
case 'threads':
$col_name = $list_menu;
break;
case 'fromto':
$label = $RCMAIL->gettext($smart_col);
$col_name = rcube::Q($label);
break;
default:
$label = $RCMAIL->gettext($col);
$col_name = rcube::Q($label);
}
// make sort links
if (in_array($col, $a_sort_cols)) {
$sortable = true;
$col_name = html::a(array(
'href' => "./#sort",
'class' => 'sortcol',
'rel' => $col,
'title' => $RCMAIL->gettext('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);
$coltypes[$col] = array('className' => $class_name, 'id' => "rcm$col", 'label' => $label, 'sortable' => $sortable);
}
$RCMAIL->output->set_env('coltypes', $coltypes);
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() : $RCMAIL->gettext('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 = $RCMAIL->storage->get_search_set() ? $RCMAIL->gettext('nomessages') : $RCMAIL->gettext('mailboxempty');
else
$out = $RCMAIL->gettext(array('name' => $RCMAIL->storage->get_threading() ? 'threadsfromto' : 'messagesfromto',
'vars' => array('from' => $start_msg,
'to' => min($max, $start_msg + $page_size - 1),
'count' => $max)));
return rcube::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];
}
}
/**
* 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);
}
}
$RCMAIL->plugins->exec_hook('message_check_safe', array('message' => $message));
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='.RCUBE_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' => RCUBE_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 = rcube_charset::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);
+ $body = rcmail_plain_body($body, $part->ctype_parameters['format'] == 'flowed');
}
// 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']);
+ return $data['body'];
}
/**
* Handle links and citation marks in plain text message
*
* @param string Plain text string
+ * @param boolean Set to True if the source text is in format=flowed
*
* @return string Formatted HTML string
*/
-function rcmail_plain_body($body)
+function rcmail_plain_body($body, $flowed = false)
{
- 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 rcube::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);
+ $options = array('flowed' => $flowed, 'wrap' => !$flowed);
+ $text2html = new rcube_text2html($body, false, $options);
+ $body = $text2html->get_html();
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\(:;]/', '', rcube_utils::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 if (!$attrib['valueof'])
continue;
if (in_array($hkey, $exclude_headers))
continue;
$header_title = $RCMAIL->gettext(preg_replace('/(^mail-|-)/', '', $hkey));
if ($hkey == 'date') {
if ($PRINT_MODE)
$header_value = $RCMAIL->format_date($value, $RCMAIL->config->get('date_long', 'x'));
else
$header_value = $RCMAIL->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 = $RCMAIL->gettext('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 rcube::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) {
$val = $row['html'] ? $row['value'] : rcube::Q($row['value'], ($hkey == 'subject' ? 'strict' : 'show'));
$table->add(array('class' => 'header-title'), rcube::Q($row['title']));
$table->add(array('class' => 'header '.$hkey), $val);
}
return $table->show($attrib);
}
/**
* Convert Priority header value into a localized string
*/
function rcmail_localized_priority($value)
{
global $RCMAIL;
$labels_map = array(
'1' => 'highest',
'2' => 'high',
'3' => 'normal',
'4' => 'low',
'5' => 'lowest',
);
if ($value && $labels_map[$value]) {
return $RCMAIL->gettext($labels_map[$value]);
}
return '';
}
/**
* return block to show full message headers
*/
function rcmail_message_full_headers($attrib, $headers=NULL)
{
global $OUTPUT, $RCMAIL;
$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 ".rcmail_output::JS_OBJECT_NAME.".command('show-headers','',this)",
'title' => $RCMAIL->gettext('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 $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', $RCMAIL->gettext('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 (!rcube_utils::mem_check($part->size * 10)) {
$out .= html::span('part-notice', $RCMAIL->gettext('messagetoobig'). ' '
. html::a('?_task=mail&_action=get&_download=1&_uid='.$MESSAGE->uid.'&_part='.$part->mime_id
.'&_mbox='. urlencode($RCMAIL->storage->get_folder()), $RCMAIL->gettext('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' => !$RCMAIL->config->get('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 (!rcube_utils::mem_check(strlen($MESSAGE->body) * 10)) {
$out .= html::span('part-notice', $RCMAIL->gettext('messagetoobig'). ' '
. html::a('?_task=mail&_action=get&_download=1&_uid='.$MESSAGE->uid.'&_part=0'
.'&_mbox='. urlencode($RCMAIL->storage->get_folder()), $RCMAIL->gettext('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(rcube::Q($MESSAGE->body, 'strict', false))));
+ $out .= html::div('message-part',
+ $plugin['prefix'] . rcmail_plain_body($MESSAGE->body));
}
}
// 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)',
rcmail_output::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', rcube::Q($attach_prop->filename)) .
html::span('image-filesize', rcube::Q($RCMAIL->message_part_size($attach_prop))) .
html::span('attachment-links',
(in_array($mimetype, $client_mimetypes) ? html::a($show_link, $RCMAIL->gettext('showattachment')) . '&nbsp;' : '') .
html::a($show_link['href'] . '&_download=1', $RCMAIL->gettext('download'))
) .
html::br(array('style' => 'clear:both'))
);
}
else {
$out .= html::tag('fieldset', 'image-attachment',
html::tag('legend', 'image-filename', rcube::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)
{
// 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 || rcube_image::is_convertable('image/tiff');
// 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 = rcube_utils::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#]+)["\']*/i', $attrs, $mb)) {
$attributes['background-color'] = $mb[1];
$attrs = preg_replace('/bgcolor=["\']*[a-z0-9#]+["\']*/i', '', $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
$regexp = '/(background-position|background-repeat)\s*:\s*([^;]+);/i';
if (preg_match_all($regexp, $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;
$tag = strtolower($matches[1]);
$attrib = html::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:(.+)/i', $attrib['href'], $mailto)) {
list($mailto, $url) = explode('?', html_entity_decode($mailto[1], ENT_QUOTES, 'UTF-8'), 2);
$url = urldecode($url);
$mailto = urldecode($mailto);
$addresses = rcube_mime::decode_address_list($mailto, null, true);
$mailto = array();
// do sanity checks on recipients
foreach ($addresses as $idx => $addr) {
if (rcube_utils::check_email($addr['mailto'], false)) {
$addresses[$idx] = $addr['mailto'];
$mailto[] = $addr['string'];
}
else {
unset($addresses[$idx]);
}
}
if (!empty($addresses)) {
$attrib['href'] = 'mailto:' . implode(',', $addresses);
$attrib['onclick'] = sprintf(
"return %s.command('compose','%s',this)",
rcmail_output::JS_OBJECT_NAME,
rcube::JQ(implode(',', $mailto) . ($url ? "?$url" : '')));
}
else {
$attrib['href'] = '#NOP';
$attrib['onclick'] = '';
}
}
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;
$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 = rcube_utils::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_utils::idn_to_utf8($name);
if ($string == $mailto)
$string = rcube_utils::idn_to_utf8($string);
$mailto = rcube_utils::idn_to_utf8($mailto);
if ($PRINT_MODE) {
$address = sprintf('%s &lt;%s&gt;', rcube::Q($name), rcube::Q($mailto));
}
else if ($valid) {
if ($linked) {
$attrs = array(
'href' => 'mailto:' . $mailto,
'class' => 'rcmContactAddress',
'onclick' => sprintf("return %s.command('compose','%s',this)",
rcmail_output::JS_OBJECT_NAME, rcube::JQ(format_email_recipient($mailto, $name))),
);
if ($show_email && $name && $mailto) {
$content = rcube::Q($name ? sprintf('%s <%s>', $name, $mailto) : $mailto);
}
else {
$content = rcube::Q($name ? $name : $mailto);
$attrs['title'] = $mailto;
}
$address = html::a($attrs, $content);
}
else {
$address = html::span(array('title' => $mailto, 'class' => "rcmContactAddress"),
rcube::Q($name ? $name : $mailto));
}
if ($addicon && $_SESSION['writeable_abook']) {
$address .= html::a(array(
'href' => "#add",
'title' => $RCMAIL->gettext('addtoaddressbook'),
'class' => 'rcmaddcontact',
'onclick' => sprintf("return %s.command('add-contact','%s',this)",
rcmail_output::JS_OBJECT_NAME, rcube::JQ($string)),
),
html::img(array(
'src' => $RCMAIL->config->get('skin_path') . $addicon,
'alt' => "Add contact",
)));
}
}
else {
$address = '';
if ($name)
$address .= rcube::Q($name);
if ($mailto)
$address = trim($address . ' ' . rcube::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) {
if ($PRINT_MODE) {
$out .= ' ' . html::a(array(
'href' => '#more',
'class' => 'morelink',
'onclick' => '$(this).hide().next().show()',
),
rcube::Q($RCMAIL->gettext(array('name' => 'andnmore', 'vars' => array('nr' => $moreadrs)))))
. html::span(array('style' => 'display:none'), join(', ', $allvalues));
}
else {
$out .= ' ' . html::a(array(
'href' => '#more',
'class' => 'morelink',
'onclick' => sprintf("return %s.show_popup_dialog('%s','%s')",
rcmail_output::JS_OBJECT_NAME,
rcube::JQ(join(', ', $allvalues)),
rcube::JQ($title))
),
rcube::Q($RCMAIL->gettext(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", rcube_mime::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) {
$encode = $key == 'folder' || strpos($val, ';') !== false;
$parts[] = $key . '=' . ($encode ? 'B::' . 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 (strpos($val, 'B::') === 0) {
$val = base64_decode(substr($val, 3));
}
else if ($key == 'folder') {
$val = base64_decode($val);
}
$info[$key] = $val;
}
return $info;
}
/**
* 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', RCUBE_CHARSET);
$compose->setParam('html_charset', RCUBE_CHARSET);
$compose->setParam('text_charset', RCUBE_CHARSET);
// compose headers array
$headers = array(
'Date' => $RCMAIL->user_date(),
'From' => $sender,
'To' => $message->headers->mdn_to,
'Subject' => $RCMAIL->gettext('receiptread') . ': ' . $message->subject,
'Message-ID' => $RCMAIL->gen_message_id(),
'X-Sender' => $identity['email'],
'References' => trim($message->headers->references . ' ' . $message->headers->messageID),
);
$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";
if ($message->headers->to) {
$report .= "Original-Recipient: {$message->headers->to}\r\n";
}
if ($agent = $RCMAIL->config->get('useragent')) {
$headers['User-Agent'] = $agent;
$report .= "Reporting-UA: $agent\r\n";
}
$body = $RCMAIL->gettext("yourmessage") . "\r\n\r\n" .
"\t" . $RCMAIL->gettext("to") . ': ' . rcube_mime::decode_mime_string($message->headers->to, $message->headers->charset) . "\r\n" .
"\t" . $RCMAIL->gettext("subject") . ': ' . $message->subject . "\r\n" .
"\t" . $RCMAIL->gettext("sent") . ': ' . $RCMAIL->format_date($message->headers->date, $RCMAIL->config->get('date_long')) . "\r\n" .
"\r\n" . $RCMAIL->gettext("receiptnote");
$compose->headers($headers);
$compose->setContentType('multipart/report', array('report-type'=> 'disposition-notification'));
$compose->setTXTBody(rcube_mime::wordwrap($body, 75, "\r\n"));
$compose->addAttachment($report, 'message/disposition-notification', 'MDNPart2.txt', false, '7bit', 'inline');
if ($RCMAIL->config->get('mdn_use_from')) {
$options['mdn_use_from'] = true;
}
$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[] = strtolower($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[] = strtolower($addr['mailto']);
$a_names[] = $addr['name'];
}
}
}
}
// decode From: address
$from = rcube_mime::decode_address_list($MESSAGE->headers->from, null, true, $MESSAGE->headers->charset);
$from = array_shift($from);
$from['mailto'] = strtolower($from['mailto']);
$from_idx = null;
$found_idx = array('to' => null, 'from' => null);
$check_from = in_array($compose_mode, array('draft', 'edit', 'reply'));
// Select identity
foreach ($identities as $idx => $ident) {
// use From: header when in edit/draft or reply-to-self
if ($check_from && $from['mailto'] == strtolower($ident['email_ascii'])) {
// remember first matching identity address
if ($found_idx['from'] === null) {
$found_idx['from'] = $idx;
}
// match identity name
if ($from['name'] && $ident['name'] && $from['name'] == $ident['name']) {
$from_idx = $idx;
break;
}
}
// use replied/forwarded message recipients
else if (($found = array_search(strtolower($ident['email_ascii']), $a_recipients)) !== false) {
// remember first matching identity address
if ($found_idx['to'] === null) {
$found_idx['to'] = $idx;
}
// match identity name
if ($a_names[$found] && $ident['name'] && $a_names[$found] == $ident['name']) {
$from_idx = $idx;
break;
}
}
}
// If matching by name+address didn't find any matches,
// get first found identity (address) if any
if ($from_idx === null) {
$from_idx = $found_idx['from'] !== null ? $found_idx['from'] : $found_idx['to'];
}
// Try Return-Path
if ($from_idx === null && ($return_path = $MESSAGE->headers->others['return-path'])) {
$return_path = array_map('strtolower', (array) $return_path);
foreach ($identities as $idx => $ident) {
// Return-Path header contains an email address, but on some mailing list
// it can be e.g. <pear-dev-return-55250-local=domain.tld@lists.php.net>
// where local@domain.tld is the address we're looking for (#1489241)
$ident1 = strtolower($ident['email_ascii']);
$ident2 = str_replace('@', '=', $ident1);
$ident1 = '<' . $ident1 . '>';
$ident2 = '-' . $ident2 . '@';
foreach ($return_path as $path) {
if ($path == $ident1 || stripos($path, $ident2)) {
$from_idx = $idx;
break 2;
}
}
}
}
// See identity_select plugin for example usage of this hook
$plugin = rcmail::get_instance()->plugins->exec_hook('identity_select',
array('message' => $MESSAGE, 'identities' => $identities, 'selected' => $from_idx));
$selected = $plugin['selected'];
// default identity is always first on the list
return $identities[$selected !== null ? $selected : 0];
}
// 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)
{
global $RCMAIL;
$filename = $attachment->filename;
if ($filename === null || $filename === '') {
if ($attachment->mimetype == 'text/html') {
$filename = $RCMAIL->gettext('htmlmessage');
}
else {
$ext = (array) rcube_mime::get_mime_extensions($attachment->mimetype);
$ext = array_shift($ext);
$filename = $RCMAIL->gettext('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 = $RCMAIL->gettext('digitalsig');
}
}
return $filename;
}
function rcmail_search_filter($attrib)
{
global $RCMAIL;
if (!strlen($attrib['id']))
$attrib['id'] = 'rcmlistfilter';
$attrib['onchange'] = rcmail_output::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 = trim(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($RCMAIL->gettext('all'), 'ALL');
$select_filter->add($RCMAIL->gettext('unread'), 'UNSEEN');
$select_filter->add($RCMAIL->gettext('flagged'), 'FLAGGED');
$select_filter->add($RCMAIL->gettext('unanswered'), 'UNANSWERED');
if (!$RCMAIL->config->get('skip_deleted')) {
$select_filter->add($RCMAIL->gettext('deleted'), 'DELETED');
$select_filter->add($RCMAIL->gettext('undeleted'), 'UNDELETED');
}
$select_filter->add($RCMAIL->gettext('withattachment'), $attachment);
$select_filter->add($RCMAIL->gettext('priority').': '.$RCMAIL->gettext('highest'), 'HEADER X-PRIORITY 1');
$select_filter->add($RCMAIL->gettext('priority').': '.$RCMAIL->gettext('high'), 'HEADER X-PRIORITY 2');
$select_filter->add($RCMAIL->gettext('priority').': '.$RCMAIL->gettext('normal'), 'NOT HEADER X-PRIORITY 1 NOT HEADER X-PRIORITY 2 NOT HEADER X-PRIORITY 4 NOT HEADER X-PRIORITY 5');
$select_filter->add($RCMAIL->gettext('priority').': '.$RCMAIL->gettext('low'), 'HEADER X-PRIORITY 4');
$select_filter->add($RCMAIL->gettext('priority').': '.$RCMAIL->gettext('lowest'), 'HEADER X-PRIORITY 5');
$out = $select_filter->show($_SESSION['search_filter']);
$RCMAIL->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 $RCMAIL;
// set defaults
$attrib += array('id' => 'rcmImportform', 'buttons' => 'yes');
// Get filesize, enable upload progress bar
$max_filesize = $RCMAIL->upload_init();
$button = new html_inputfield(array('type' => 'button'));
$fileinput = new html_inputfield(array(
'type' => 'file',
'name' => '_file[]',
'multiple' => 'multiple',
'accept' => ".eml, .mbox, message/rfc822, text/*",
));
$content = html::tag('input', array('type' => 'hidden', 'name' => '_unlock', 'value' => ''))
. html::div(null, $fileinput->show())
. html::div('hint', $RCMAIL->gettext(array('name' => 'maxuploadsize', 'vars' => array('size' => $max_filesize))));
if (rcube_utils::get_boolean($attrib['buttons'])) {
$content .= html::div('buttons',
$button->show($RCMAIL->gettext('close'), array('class' => 'button', 'onclick' => "$('#$attrib[id]').hide()"))
. ' ' .
$button->show($RCMAIL->gettext('upload'), array(
'class' => 'button mainaction',
'onclick' => rcmail_output::JS_OBJECT_NAME . ".command('import-messages', this.form)"
)));
}
$out = $RCMAIL->output->form_tag(array(
'id' => $attrib['id'].'Frm',
'method' => 'post',
'enctype' => 'multipart/form-data'
),
$content);
$RCMAIL->output->add_gui_object('importform', $attrib['id'].'Frm');
$RCMAIL->output->add_label('selectimportfile','importwait');
return html::div($attrib, $out);
}
/**
* Add groups from the given address source to the address book widget
*/
function rcmail_compose_contact_groups($abook, $source_id, $search = null, $search_mode = 0)
{
global $RCMAIL, $OUTPUT;
$jsresult = array();
foreach ($abook->list_groups($search, $search_mode) as $group) {
$abook->reset();
$abook->set_group($group['ID']);
$group_prop = $abook->get_group($group['ID']);
// group (distribution list) with email address(es)
if ($group_prop['email']) {
foreach ((array)$group_prop['email'] as $email) {
$row_id = 'G'.$group['ID'];
$jsresult[$row_id] = format_email_recipient($email, $group['name']);
$OUTPUT->command('add_contact_row', $row_id, array(
'contactgroup' => html::span(array('title' => $email), rcube::Q($group['name']))), 'group');
}
}
// make virtual groups clickable to list their members
else if ($group_prop['virtual']) {
$row_id = 'G'.$group['ID'];
$OUTPUT->command('add_contact_row', $row_id, array(
'contactgroup' => html::a(array(
'href' => '#list',
'rel' => $group['ID'],
'title' => $RCMAIL->gettext('listgroup'),
'onclick' => sprintf("return %s.command('pushgroup',{'source':'%s','id':'%s'},this,event)",
rcmail_output::JS_OBJECT_NAME, $source_id, $group['ID']),
), rcube::Q($group['name']) . '&nbsp;' . html::span('action', '&raquo;'))),
'group',
array('ID' => $group['ID'], 'name' => $group['name'], 'virtual' => true));
}
// show group with count
else if (($result = $abook->count()) && $result->count) {
$row_id = 'E'.$group['ID'];
$jsresult[$row_id] = $group['name'];
$OUTPUT->command('add_contact_row', $row_id, array(
'contactgroup' => rcube::Q($group['name'] . ' (' . intval($result->count) . ')')), 'group');
}
}
$abook->reset();
$abook->set_group(0);
return $jsresult;
}
diff --git a/program/steps/mail/sendmail.inc b/program/steps/mail/sendmail.inc
index 04ba94f5e..baecbd118 100644
--- a/program/steps/mail/sendmail.inc
+++ b/program/steps/mail/sendmail.inc
@@ -1,922 +1,933 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/steps/mail/sendmail.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: |
| Compose a new mail message with all headers and attachments |
| and send it using the PEAR::Net_SMTP class or with PHP mail() |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
// remove all scripts and act as called in frame
$OUTPUT->reset();
$OUTPUT->framed = TRUE;
$savedraft = !empty($_POST['_draft']) ? true : false;
$sendmail_delay = (int) $RCMAIL->config->get('sendmail_delay');
$drafts_mbox = $RCMAIL->config->get('drafts_mbox');
$COMPOSE_ID = rcube_utils::get_input_value('_id', rcube_utils::INPUT_GPC);
$COMPOSE =& $_SESSION['compose_data_'.$COMPOSE_ID];
/****** checks ********/
if (!isset($COMPOSE['id'])) {
rcube::raise_error(array('code' => 500, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Invalid compose ID"), true, false);
$OUTPUT->show_message('internalerror', 'error');
$OUTPUT->send('iframe');
}
if (!$savedraft) {
if (empty($_POST['_to']) && empty($_POST['_cc']) && empty($_POST['_bcc'])
&& empty($_POST['_subject']) && $_POST['_message']
) {
$OUTPUT->show_message('sendingfailed', 'error');
$OUTPUT->send('iframe');
}
if ($sendmail_delay) {
$wait_sec = time() - $sendmail_delay - intval($RCMAIL->config->get('last_message_time'));
if ($wait_sec < 0) {
$OUTPUT->show_message('senttooquickly', 'error', array('sec' => $wait_sec * -1));
$OUTPUT->send('iframe');
}
}
}
/****** compose message ********/
if (empty($COMPOSE['param']['message-id'])) {
$COMPOSE['param']['message-id'] = $RCMAIL->gen_message_id();
}
$message_id = $COMPOSE['param']['message-id'];
// set default charset
$message_charset = isset($_POST['_charset']) ? $_POST['_charset'] : $OUTPUT->get_charset();
$EMAIL_FORMAT_ERROR = NULL;
$RECIPIENT_COUNT = 0;
$mailto = rcmail_email_input_format(rcube_utils::get_input_value('_to', rcube_utils::INPUT_POST, TRUE, $message_charset), true);
$mailcc = rcmail_email_input_format(rcube_utils::get_input_value('_cc', rcube_utils::INPUT_POST, TRUE, $message_charset), true);
$mailbcc = rcmail_email_input_format(rcube_utils::get_input_value('_bcc', rcube_utils::INPUT_POST, TRUE, $message_charset), true);
if ($EMAIL_FORMAT_ERROR) {
$OUTPUT->show_message('emailformaterror', 'error', array('email' => $EMAIL_FORMAT_ERROR));
$OUTPUT->send('iframe');
}
if (empty($mailto) && !empty($mailcc)) {
$mailto = $mailcc;
$mailcc = null;
}
else if (empty($mailto)) {
$mailto = 'undisclosed-recipients:;';
}
// Get sender name and address...
$from = rcube_utils::get_input_value('_from', rcube_utils::INPUT_POST, true, $message_charset);
// ... from identity...
if (is_numeric($from)) {
if (is_array($identity_arr = rcmail_get_identity($from))) {
if ($identity_arr['mailto'])
$from = $identity_arr['mailto'];
if ($identity_arr['string'])
$from_string = $identity_arr['string'];
}
else {
$from = null;
}
}
// ... if there is no identity record, this might be a custom from
else if ($from_string = rcmail_email_input_format($from)) {
if (preg_match('/(\S+@\S+)/', $from_string, $m))
$from = trim($m[1], '<>');
else
$from = null;
}
if (!$from_string && $from) {
$from_string = $from;
}
// compose headers array
$headers = array();
// if configured, the Received headers goes to top, for good measure
if ($RCMAIL->config->get('http_received_header')) {
$nldlm = "\r\n\t";
$encrypt = $RCMAIL->config->get('http_received_header_encrypt');
// FROM/VIA
$http_header = 'from ';
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$hosts = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'], 2);
$hostname = gethostbyaddr($hosts[0]);
if ($encrypt) {
$http_header .= rcmail_encrypt_header($hostname);
if ($host != $hostname)
$http_header .= ' ('. rcmail_encrypt_header($host) . ')';
}
else {
$http_header .= (($host != $hostname) ? $hostname : '[' . $host . ']');
if ($host != $hostname)
$http_header .= ' (['. $host .'])';
}
$http_header .= $nldlm . ' via ';
}
$host = $_SERVER['REMOTE_ADDR'];
$hostname = gethostbyaddr($host);
if ($encrypt) {
$http_header .= rcmail_encrypt_header($hostname);
if ($host != $hostname)
$http_header .= ' ('. rcmail_encrypt_header($host) . ')';
}
else {
$http_header .= (($host != $hostname) ? $hostname : '[' . $host . ']');
if ($host != $hostname)
$http_header .= ' (['. $host .'])';
}
// BY
$http_header .= $nldlm . 'by ' . $_SERVER['HTTP_HOST'];
// WITH
$http_header .= $nldlm . 'with HTTP (' . $_SERVER['SERVER_PROTOCOL'] .
' '.$_SERVER['REQUEST_METHOD'] . '); ' . date('r');
$http_header = wordwrap($http_header, 69, $nldlm);
$headers['Received'] = $http_header;
}
$headers['Date'] = $RCMAIL->user_date();
$headers['From'] = rcube_charset::convert($from_string, RCUBE_CHARSET, $message_charset);
$headers['To'] = $mailto;
// additional recipients
if (!empty($mailcc)) {
$headers['Cc'] = $mailcc;
}
if (!empty($mailbcc)) {
$headers['Bcc'] = $mailbcc;
}
if (($max_recipients = (int) $RCMAIL->config->get('max_recipients')) > 0) {
if ($RECIPIENT_COUNT > $max_recipients) {
$OUTPUT->show_message('toomanyrecipients', 'error', array('max' => $max_recipients));
$OUTPUT->send('iframe');
}
}
// add subject
$headers['Subject'] = trim(rcube_utils::get_input_value('_subject', rcube_utils::INPUT_POST, TRUE, $message_charset));
if (!empty($identity_arr['organization'])) {
$headers['Organization'] = $identity_arr['organization'];
}
if ($hdr = rcube_utils::get_input_value('_replyto', rcube_utils::INPUT_POST, TRUE, $message_charset)) {
$headers['Reply-To'] = rcmail_email_input_format($hdr);
}
if (!empty($headers['Reply-To'])) {
$headers['Mail-Reply-To'] = $headers['Reply-To'];
}
if ($hdr = rcube_utils::get_input_value('_followupto', rcube_utils::INPUT_POST, TRUE, $message_charset)) {
$headers['Mail-Followup-To'] = rcmail_email_input_format($hdr);
}
// remember reply/forward UIDs in special headers
if (!empty($COMPOSE['reply_uid']) && $savedraft) {
$headers['X-Draft-Info'] = array('type' => 'reply', 'uid' => $COMPOSE['reply_uid']);
}
else if (!empty($COMPOSE['forward_uid']) && $savedraft) {
$headers['X-Draft-Info'] = array('type' => 'forward', 'uid' => rcube_imap_generic::compressMessageSet($COMPOSE['forward_uid']));
}
if (!empty($COMPOSE['reply_msgid'])) {
$headers['In-Reply-To'] = $COMPOSE['reply_msgid'];
}
if (!empty($COMPOSE['references'])) {
$headers['References'] = $COMPOSE['references'];
}
if (!empty($_POST['_priority'])) {
$priority = intval($_POST['_priority']);
$a_priorities = array(1 => 'highest', 2 => 'high', 4 => 'low', 5 => 'lowest');
if ($str_priority = $a_priorities[$priority]) {
$headers['X-Priority'] = sprintf("%d (%s)", $priority, ucfirst($str_priority));
}
}
if (!empty($_POST['_receipt'])) {
$headers['Return-Receipt-To'] = $from_string;
$headers['Disposition-Notification-To'] = $from_string;
}
// additional headers
$headers['Message-ID'] = $message_id;
$headers['X-Sender'] = $from;
if (is_array($headers['X-Draft-Info'])) {
$headers['X-Draft-Info'] = rcmail_draftinfo_encode($headers['X-Draft-Info'] + array('folder' => $COMPOSE['mailbox']));
}
if ($hdr = $RCMAIL->config->get('useragent')) {
$headers['User-Agent'] = $hdr;
}
// exec hook for header checking and manipulation
// Depracated: use message_before_send hook instead
$data = $RCMAIL->plugins->exec_hook('message_outgoing_headers', array('headers' => $headers));
// sending aborted by plugin
if ($data['abort'] && !$savedraft) {
$OUTPUT->show_message($data['message'] ? $data['message'] : 'sendingfailed');
$OUTPUT->send('iframe');
}
else {
$headers = $data['headers'];
}
$isHtml = (bool) rcube_utils::get_input_value('_is_html', rcube_utils::INPUT_POST);
// fetch message body
$message_body = rcube_utils::get_input_value('_message', rcube_utils::INPUT_POST, TRUE, $message_charset);
if ($isHtml) {
$bstyle = array();
if ($font_size = $RCMAIL->config->get('default_font_size')) {
$bstyle[] = 'font-size: ' . $font_size;
}
if ($font_family = $RCMAIL->config->get('default_font')) {
$bstyle[] = 'font-family: ' . rcmail::font_defs($font_family);
}
// append doctype and html/body wrappers
$message_body = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN">'
. "\r\n<html><body" . (!empty($bstyle) ? " style='" . implode($bstyle, '; ') . "'" : '') . ">\r\n"
. $message_body;
}
if (!$savedraft) {
if ($isHtml) {
- // remove signature's div ID
- $message_body = preg_replace('/\s*id="_rc_sig"/', '', $message_body);
-
- // add inline css for blockquotes
- $bstyle = 'padding-left:5px; border-left:#1010ff 2px solid; margin-left:5px';
- $message_body = preg_replace('/<blockquote>/',
- '<blockquote type="cite" style="'.$bstyle.'">', $message_body);
+ $b_style = 'padding: 0 0.4em; border-left: #1010ff 2px solid; margin: 0';
+ $pre_style = 'margin: 0; padding: 0; font-family: monospace';
+
+ $message_body = preg_replace(
+ array(
+ // remove signature's div ID
+ '/\s*id="_rc_sig"/',
+ // add inline css for blockquotes and container
+ '/<blockquote>/',
+ '/<div class="pre">/'
+ ),
+ array(
+ '',
+ '<blockquote type="cite" style="'.$b_style.'">',
+ '<div class="pre" style="'.$pre_style.'">'
+ ),
+ $message_body);
}
// Check spelling before send
if ($RCMAIL->config->get('spellcheck_before_send') && $RCMAIL->config->get('enable_spellcheck')
&& empty($COMPOSE['spell_checked']) && !empty($message_body)
) {
$message_body = str_replace("\r\n", "\n", $message_body);
$spellchecker = new rcube_spellchecker(rcube_utils::get_input_value('_lang', rcube_utils::INPUT_GPC));
$spell_result = $spellchecker->check($message_body, $isHtml);
$COMPOSE['spell_checked'] = true;
if (!$spell_result) {
$result = $isHtml ? $spellchecker->get_words() : $spellchecker->get_xml();
$OUTPUT->show_message('mispellingsfound', 'error');
$OUTPUT->command('spellcheck_resume', $isHtml, $result);
$OUTPUT->send('iframe');
}
}
// generic footer for all messages
if ($footer = rcmail_generic_message_footer($isHtml)) {
$footer = rcube_charset::convert($footer, RCUBE_CHARSET, $message_charset);
$message_body .= "\r\n" . $footer;
}
}
if ($isHtml) {
$message_body .= "\r\n</body></html>\r\n";
}
// sort attachments to make sure the order is the same as in the UI (#1488423)
if ($files = rcube_utils::get_input_value('_attachments', rcube_utils::INPUT_POST)) {
$files = explode(',', $files);
$files = array_flip($files);
foreach ($files as $idx => $val) {
$files[$idx] = $COMPOSE['attachments'][$idx];
unset($COMPOSE['attachments'][$idx]);
}
$COMPOSE['attachments'] = array_merge(array_filter($files), $COMPOSE['attachments']);
}
// set line length for body wrapping
$LINE_LENGTH = $RCMAIL->config->get('line_length', 72);
// Since we can handle big messages with disk usage, we need more time to work
@set_time_limit(0);
// create PEAR::Mail_mime instance
$MAIL_MIME = new Mail_mime("\r\n");
// Check if we have enough memory to handle the message in it
// It's faster than using files, so we'll do this if we only can
if (is_array($COMPOSE['attachments']) && $RCMAIL->config->get('smtp_server')
&& ($mem_limit = parse_bytes(ini_get('memory_limit')))
) {
$memory = 0;
foreach ($COMPOSE['attachments'] as $id => $attachment) {
$memory += $attachment['size'];
}
// Yeah, Net_SMTP needs up to 12x more memory, 1.33 is for base64
if (!rcube_utils::mem_check($memory * 1.33 * 12)) {
$MAIL_MIME->setParam('delay_file_io', true);
}
}
// For HTML-formatted messages, construct the MIME message with both
// the HTML part and the plain-text part
if ($isHtml) {
$plugin = $RCMAIL->plugins->exec_hook('message_outgoing_body',
array('body' => $message_body, 'type' => 'html', 'message' => $MAIL_MIME));
$MAIL_MIME->setHTMLBody($plugin['body']);
// replace emoticons
$plugin['body'] = $RCMAIL->replace_emoticons($plugin['body']);
// add a plain text version of the e-mail as an alternative part.
$h2t = new rcube_html2text($plugin['body'], false, true, 0, $message_charset);
$plainTextPart = rcube_mime::wordwrap($h2t->get_text(), $LINE_LENGTH, "\r\n", false, $message_charset);
$plainTextPart = wordwrap($plainTextPart, 998, "\r\n", true);
// make sure all line endings are CRLF (#1486712)
$plainTextPart = preg_replace('/\r?\n/', "\r\n", $plainTextPart);
$plugin = $RCMAIL->plugins->exec_hook('message_outgoing_body',
array('body' => $plainTextPart, 'type' => 'alternative', 'message' => $MAIL_MIME));
$MAIL_MIME->setTXTBody($plugin['body']);
// look for "emoticon" images from TinyMCE and change their src paths to
// be file paths on the server instead of URL paths.
rcmail_fix_emoticon_paths($MAIL_MIME);
// Extract image Data URIs into message attachments (#1488502)
rcmail_extract_inline_images($MAIL_MIME, $from);
}
else {
$plugin = $RCMAIL->plugins->exec_hook('message_outgoing_body',
array('body' => $message_body, 'type' => 'plain', 'message' => $MAIL_MIME));
$message_body = $plugin['body'];
// compose format=flowed content if enabled
if ($flowed = ($savedraft || $RCMAIL->config->get('send_format_flowed', true)))
$message_body = rcube_mime::format_flowed($message_body, min($LINE_LENGTH+2, 79), $message_charset);
else
$message_body = rcube_mime::wordwrap($message_body, $LINE_LENGTH, "\r\n", false, $message_charset);
$message_body = wordwrap($message_body, 998, "\r\n", true);
$MAIL_MIME->setTXTBody($message_body, false, true);
}
// add stored attachments, if any
if (is_array($COMPOSE['attachments'])) {
foreach ($COMPOSE['attachments'] as $id => $attachment) {
// This hook retrieves the attachment contents from the file storage backend
$attachment = $RCMAIL->plugins->exec_hook('attachment_get', $attachment);
if ($isHtml) {
$dispurl = '/\ssrc\s*=\s*[\'"]*\S+display-attachment\S+file=rcmfile'
. preg_quote($attachment['id']) . '[\s\'"]*/';
$message_body = $MAIL_MIME->getHTMLBody();
$is_inline = preg_match($dispurl, $message_body);
}
else {
$is_inline = false;
}
// inline image
if ($is_inline) {
// Mail_Mime does not support many inline attachments with the same name (#1489406)
// we'll generate cid: urls here to workaround this
$cid = preg_replace('/[^0-9a-zA-Z]/', '', uniqid(time(), true));
if (preg_match('#(@[0-9a-zA-Z\-\.]+)#', $from, $matches)) {
$cid .= $matches[1];
}
else {
$cid .= '@localhost';
}
$message_body = preg_replace($dispurl, ' src="cid:' . $cid . '" ', $message_body);
$MAIL_MIME->setHTMLBody($message_body);
if ($attachment['data'])
$MAIL_MIME->addHTMLImage($attachment['data'], $attachment['mimetype'], $attachment['name'], false, $cid);
else
$MAIL_MIME->addHTMLImage($attachment['path'], $attachment['mimetype'], $attachment['name'], true, $cid);
}
else {
$ctype = str_replace('image/pjpeg', 'image/jpeg', $attachment['mimetype']); // #1484914
$file = $attachment['data'] ? $attachment['data'] : $attachment['path'];
$folding = (int) $RCMAIL->config->get('mime_param_folding');
$MAIL_MIME->addAttachment($file,
$ctype,
$attachment['name'],
$attachment['data'] ? false : true,
$ctype == 'message/rfc822' ? '8bit' : 'base64',
'attachment',
'', '', '',
$folding ? 'quoted-printable' : NULL,
$folding == 2 ? 'quoted-printable' : NULL,
'', RCUBE_CHARSET
);
}
}
}
// choose transfer encoding for plain/text body
if (preg_match('/[^\x00-\x7F]/', $MAIL_MIME->getTXTBody())) {
$text_charset = $message_charset;
$transfer_encoding = $RCMAIL->config->get('force_7bit') ? 'quoted-printable' : '8bit';
}
else {
$text_charset = '';
$transfer_encoding = '7bit';
}
if ($flowed) {
if (!$text_charset) {
$text_charset = 'US-ASCII';
}
$text_charset .= ";\r\n format=flowed";
}
// encoding settings for mail composing
$MAIL_MIME->setParam('text_encoding', $transfer_encoding);
$MAIL_MIME->setParam('html_encoding', 'quoted-printable');
$MAIL_MIME->setParam('head_encoding', 'quoted-printable');
$MAIL_MIME->setParam('head_charset', $message_charset);
$MAIL_MIME->setParam('html_charset', $message_charset);
$MAIL_MIME->setParam('text_charset', $text_charset);
// encoding subject header with mb_encode provides better results with asian characters
if (function_exists('mb_encode_mimeheader')) {
mb_internal_encoding($message_charset);
$headers['Subject'] = mb_encode_mimeheader($headers['Subject'],
$message_charset, 'Q', "\r\n", 8);
mb_internal_encoding(RCUBE_CHARSET);
}
// pass headers to message object
$MAIL_MIME->headers($headers);
// Begin SMTP Delivery Block
if (!$savedraft) {
// check 'From' address (identity may be incomplete)
if (empty($from)) {
$OUTPUT->show_message('nofromaddress', 'error');
$OUTPUT->send('iframe');
}
// Handle Delivery Status Notification request
if (!empty($_POST['_dsn'])) {
$smtp_opts['dsn'] = true;
}
$sent = $RCMAIL->deliver_message($MAIL_MIME, $from, $mailto,
$smtp_error, $mailbody_file, $smtp_opts);
// return to compose page if sending failed
if (!$sent) {
// remove temp file
if ($mailbody_file) {
unlink($mailbody_file);
}
if ($smtp_error)
$OUTPUT->show_message($smtp_error['label'], 'error', $smtp_error['vars']);
else
$OUTPUT->show_message('sendingfailed', 'error');
$OUTPUT->send('iframe');
}
// save message sent time
if ($sendmail_delay) {
$RCMAIL->user->save_prefs(array('last_message_time' => time()));
}
// set replied/forwarded flag
if ($COMPOSE['reply_uid']) {
foreach (rcmail::get_uids($COMPOSE['reply_uid'], $COMPOSE['mailbox']) as $mbox => $uids) {
$RCMAIL->storage->set_flag($uids, 'ANSWERED', $mbox);
}
}
else if ($COMPOSE['forward_uid']) {
foreach (rcmail::get_uids($COMPOSE['forward_uid'], $COMPOSE['mailbox']) as $mbox => $uids) {
$RCMAIL->storage->set_flag($uids, 'FORWARDED', $mbox);
}
}
}
// Determine which folder to save message
if ($savedraft) {
$store_target = $drafts_mbox;
}
else if (!$RCMAIL->config->get('no_save_sent_messages')) {
$store_target = rcube_utils::get_input_value('_store_target', rcube_utils::INPUT_POST);
if (!strlen($store_target)) {
$sore_target = $RCMAIL->config->get('sent_mbox');
}
}
if ($store_target) {
// check if folder is subscribed
if ($RCMAIL->storage->folder_exists($store_target, true)) {
$store_folder = true;
}
// folder may be existing but not subscribed (#1485241)
else if (!$RCMAIL->storage->folder_exists($store_target)) {
$store_folder = $RCMAIL->storage->create_folder($store_target, true);
}
else if ($RCMAIL->storage->subscribe($store_target)) {
$store_folder = true;
}
// append message to sent box
if ($store_folder) {
// message body in file
if ($mailbody_file || $MAIL_MIME->getParam('delay_file_io')) {
$headers = $MAIL_MIME->txtHeaders();
// file already created
if ($mailbody_file) {
$msg = $mailbody_file;
}
else {
$temp_dir = $RCMAIL->config->get('temp_dir');
$mailbody_file = tempnam($temp_dir, 'rcmMsg');
if (!PEAR::isError($msg = $MAIL_MIME->saveMessageBody($mailbody_file))) {
$msg = $mailbody_file;
}
}
}
else {
$msg = $MAIL_MIME->getMessage();
$headers = '';
}
if (PEAR::isError($msg)) {
rcube::raise_error(array('code' => 650, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Could not create message: ".$msg->getMessage()),
true, false);
}
else {
$saved = $RCMAIL->storage->save_message($store_target, $msg, $headers,
$mailbody_file ? true : false, array('SEEN'));
}
if ($mailbody_file) {
unlink($mailbody_file);
$mailbody_file = null;
}
}
// raise error if saving failed
if (!$saved) {
rcube::raise_error(array('code' => 800, 'type' => 'imap',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Could not save message in $store_target"), true, false);
if ($savedraft) {
$OUTPUT->show_message('errorsaving', 'error');
// start the auto-save timer again
$OUTPUT->command('auto_save_start');
$OUTPUT->send('iframe');
}
}
// delete previous saved draft
if ($saved && ($old_id = rcube_utils::get_input_value('_draft_saveid', rcube_utils::INPUT_POST))) {
$deleted = $RCMAIL->storage->delete_message($old_id, $drafts_mbox);
// raise error if deletion of old draft failed
if (!$deleted) {
rcube::raise_error(array('code' => 800, 'type' => 'imap',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Could not delete message from $drafts_mbox"), true, false);
}
}
}
// remove temp file
else if ($mailbody_file) {
unlink($mailbody_file);
}
if ($savedraft) {
// remember new draft-uid ($saved could be an UID or true/false here)
if ($saved && is_bool($saved)) {
$index = $RCMAIL->storage->search_once($drafts_mbox, 'HEADER Message-ID ' . $message_id);
$saved = @max($index->get());
}
if ($saved) {
$plugin = $RCMAIL->plugins->exec_hook('message_draftsaved',
array('msgid' => $message_id, 'uid' => $saved, 'folder' => $store_target));
// display success
$OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'messagesaved', 'confirmation');
// update "_draft_saveid" and the "cmp_hash" to prevent "Unsaved changes" warning
$COMPOSE['param']['draft_uid'] = $plugin['uid'];
$OUTPUT->command('set_draft_id', $plugin['uid']);
$OUTPUT->command('compose_field_hash', true);
}
// start the auto-save timer again
$OUTPUT->command('auto_save_start');
}
else {
$folders = array();
if ($COMPOSE['mode'] == 'reply' || $COMPOSE['mode'] == 'forward') {
$folders[] = $COMPOSE['mailbox'];
}
rcmail_compose_cleanup($COMPOSE_ID);
$OUTPUT->command('remove_compose_data', $COMPOSE_ID);
if ($store_folder && !$saved) {
$OUTPUT->command('sent_successfully', 'error', $RCMAIL->gettext('errorsavingsent'), $folders);
}
else if ($store_folder) {
$folders[] = $store_target;
}
$OUTPUT->command('sent_successfully', 'confirmation', $RCMAIL->gettext('messagesent'), $folders);
}
$OUTPUT->send('iframe');
/****** message sending functions ********/
// encrypt parts of the header
function rcmail_encrypt_header($what)
{
global $RCMAIL;
if (!$RCMAIL->config->get('http_received_header_encrypt')) {
return $what;
}
return $RCMAIL->encrypt($what);
}
// get identity record
function rcmail_get_identity($id)
{
global $RCMAIL, $message_charset;
if ($sql_arr = $RCMAIL->user->get_identity($id)) {
$out = $sql_arr;
if ($message_charset != RCUBE_CHARSET) {
foreach ($out as $k => $v) {
$out[$k] = rcube_charset::convert($v, RCUBE_CHARSET, $message_charset);
}
}
$out['mailto'] = $sql_arr['email'];
$out['string'] = format_email_recipient($sql_arr['email'], $sql_arr['name']);
return $out;
}
return false;
}
/**
* go from this:
* <img src="http[s]://.../tiny_mce/plugins/emotions/images/smiley-cool.gif" border="0" alt="Cool" title="Cool" />
*
* to this:
*
* <img src="/path/on/server/.../tiny_mce/plugins/emotions/images/smiley-cool.gif" border="0" alt="Cool" title="Cool" />
*/
function rcmail_fix_emoticon_paths($mime_message)
{
global $RCMAIL;
$body = $mime_message->getHTMLBody();
// remove any null-byte characters before parsing
$body = preg_replace('/\x00/', '', $body);
$searchstr = 'program/js/tiny_mce/plugins/emotions/img/';
$offset = 0;
// keep track of added images, so they're only added once
$included_images = array();
if (preg_match_all('# src=[\'"]([^\'"]+)#', $body, $matches, PREG_OFFSET_CAPTURE)) {
foreach ($matches[1] as $m) {
// find emoticon image tags
if (preg_match('#'.$searchstr.'(.*)$#', $m[0], $imatches)) {
$image_name = $imatches[1];
// sanitize image name so resulting attachment doesn't leave images dir
$image_name = preg_replace('/[^a-zA-Z0-9_\.\-]/i', '', $image_name);
$img_file = INSTALL_PATH . '/' . $searchstr . $image_name;
if (! in_array($image_name, $included_images)) {
// add the image to the MIME message
if (!$mime_message->addHTMLImage($img_file, 'image/gif', '', true, $image_name)) {
$RCMAIL->output->show_message("emoticonerror", 'error');
}
array_push($included_images, $image_name);
}
$body = substr_replace($body, $img_file, $m[1] + $offset, strlen($m[0]));
$offset += strlen($img_file) - strlen($m[0]);
}
}
}
$mime_message->setHTMLBody($body);
}
/**
* Extract image attachments from HTML content (data URIs)
*/
function rcmail_extract_inline_images($mime_message, $from)
{
$body = $mime_message->getHTMLBody();
$offset = 0;
$list = array();
$domain = 'localhost';
$regexp = '#img[^>]+src=[\'"](data:([^;]*);base64,([a-z0-9+/=\r\n]+))([\'"])#i';
if (preg_match_all($regexp, $body, $matches, PREG_OFFSET_CAPTURE)) {
// get domain for the Content-ID, must be the same as in Mail_Mime::get()
if (preg_match('#@([0-9a-zA-Z\-\.]+)#', $from, $m)) {
$domain = $m[1];
}
foreach ($matches[1] as $idx => $m) {
$data = preg_replace('/\r\n/', '', $matches[3][$idx][0]);
$data = base64_decode($data);
if (empty($data)) {
continue;
}
$hash = md5($data) . '@' . $domain;
$mime_type = $matches[2][$idx][0];
$name = $list[$hash];
if (empty($mime_type)) {
$mime_type = rcube_mime::image_content_type($data);
}
// add the image to the MIME message
if (!$name) {
$ext = preg_replace('#^[^/]+/#', '', $mime_type);
$name = substr($hash, 0, 8) . '.' . $ext;
$list[$hash] = $name;
$mime_message->addHTMLImage($data, $mime_type, $name, false, $hash);
}
$body = substr_replace($body, $name, $m[1] + $offset, strlen($m[0]));
$offset += strlen($name) - strlen($m[0]);
}
}
$mime_message->setHTMLBody($body);
}
/**
* Parse and cleanup email address input (and count addresses)
*
* @param string Address input
* @param boolean Do count recipients (saved in global $RECIPIENT_COUNT)
* @param boolean Validate addresses (errors saved in global $EMAIL_FORMAT_ERROR)
* @return string Canonical recipients string separated by comma
*/
function rcmail_email_input_format($mailto, $count=false, $check=true)
{
global $RCMAIL, $EMAIL_FORMAT_ERROR, $RECIPIENT_COUNT;
// simplified email regexp, supporting quoted local part
$email_regexp = '(\S+|("[^"]+"))@\S+';
$delim = trim($RCMAIL->config->get('recipients_separator', ','));
$regexp = array("/[,;$delim]\s*[\r\n]+/", '/[\r\n]+/', "/[,;$delim]\s*\$/m", '/;/', '/(\S{1})(<'.$email_regexp.'>)/U');
$replace = array($delim.' ', ', ', '', $delim, '\\1 \\2');
// replace new lines and strip ending ', ', make address input more valid
$mailto = trim(preg_replace($regexp, $replace, $mailto));
$items = rcube_utils::explode_quoted_string($delim, $mailto);
$result = array();
foreach ($items as $item) {
$item = trim($item);
// address in brackets without name (do nothing)
if (preg_match('/^<'.$email_regexp.'>$/', $item)) {
$item = rcube_utils::idn_to_ascii(trim($item, '<>'));
$result[] = $item;
}
// address without brackets and without name (add brackets)
else if (preg_match('/^'.$email_regexp.'$/', $item)) {
$item = rcube_utils::idn_to_ascii($item);
$result[] = $item;
}
// address with name (handle name)
else if (preg_match('/<*'.$email_regexp.'>*$/', $item, $matches)) {
$address = $matches[0];
$name = trim(str_replace($address, '', $item));
if ($name[0] == '"' && $name[count($name)-1] == '"') {
$name = substr($name, 1, -1);
}
$name = stripcslashes($name);
$address = rcube_utils::idn_to_ascii(trim($address, '<>'));
$result[] = format_email_recipient($address, $name);
$item = $address;
}
else if (trim($item)) {
continue;
}
// check address format
$item = trim($item, '<>');
if ($item && $check && !rcube_utils::check_email($item)) {
$EMAIL_FORMAT_ERROR = $item;
return;
}
}
if ($count) {
$RECIPIENT_COUNT += count($result);
}
return implode(', ', $result);
}
function rcmail_generic_message_footer($isHtml)
{
global $RCMAIL;
if ($isHtml && ($file = $RCMAIL->config->get('generic_message_footer_html'))) {
$html_footer = true;
}
else {
$file = $RCMAIL->config->get('generic_message_footer');
$html_footer = false;
}
if ($file && realpath($file)) {
// sanity check
if (!preg_match('/\.(php|ini|conf)$/', $file) && strpos($file, '/etc/') === false) {
$footer = file_get_contents($file);
if ($isHtml && !$html_footer) {
- $footer = '<pre>' . $footer . '</pre>';
+ $t2h = new rcube_text2html($footer, false);
+ $footer = $t2h->get_html();
}
return $footer;
}
}
return false;
}
diff --git a/program/steps/utils/html2text.inc b/program/steps/utils/html2text.inc
index 58df34bd0..f6e2bec4d 100644
--- a/program/steps/utils/html2text.inc
+++ b/program/steps/utils/html2text.inc
@@ -1,31 +1,36 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/steps/utils/html2text.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: |
| Convert HTML message to plain text |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
$html = stream_get_contents(fopen('php://input', 'r'));
+// strip slashes if magic_quotes enabled
+if (get_magic_quotes_gpc() || get_magic_quotes_runtime()) {
+ $html = stripslashes($html);
+}
+
// Replace emoticon images with its text representation
$html = $RCMAIL->replace_emoticons($html);
$converter = new rcube_html2text($html, false, true, 0);
header('Content-Type: text/plain; charset=UTF-8');
print rtrim($converter->get_text());
exit;
diff --git a/program/steps/utils/html2text.inc b/program/steps/utils/text2html.inc
similarity index 65%
copy from program/steps/utils/html2text.inc
copy to program/steps/utils/text2html.inc
index 58df34bd0..56d15fa19 100644
--- a/program/steps/utils/html2text.inc
+++ b/program/steps/utils/text2html.inc
@@ -1,31 +1,33 @@
<?php
/*
+-----------------------------------------------------------------------+
- | program/steps/utils/html2text.inc |
+ | program/steps/utils/text2html.inc |
| |
| This file is part of the Roundcube Webmail client |
- | Copyright (C) 2005-2012, The Roundcube Dev Team |
+ | Copyright (C) 2005-2014, The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
- | Convert HTML message to plain text |
+ | Convert plain text to HTML |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
-$html = stream_get_contents(fopen('php://input', 'r'));
+$text = stream_get_contents(fopen('php://input', 'r'));
-// Replace emoticon images with its text representation
-$html = $RCMAIL->replace_emoticons($html);
+// strip slashes if magic_quotes enabled
+if (get_magic_quotes_gpc() || get_magic_quotes_runtime()) {
+ $html = stripslashes($html);
+}
-$converter = new rcube_html2text($html, false, true, 0);
+$converter = new rcube_text2html($text, false, array('wrap' => true));
-header('Content-Type: text/plain; charset=UTF-8');
-print rtrim($converter->get_text());
+header('Content-Type: text/html; charset=' . RCUBE_CHARSET);
+print $converter->get_html();
exit;
diff --git a/skins/classic/editor_content.css b/skins/classic/editor_content.css
index aabed07b5..67480ab77 100644
--- a/skins/classic/editor_content.css
+++ b/skins/classic/editor_content.css
@@ -1,31 +1,26 @@
/* This file contains the CSS data for the editable area(iframe) of TinyMCE */
body, td, pre {
font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
font-size: 12px;
}
body {
background-color: #FFFFFF;
margin-left: 4px;
margin-right: 4px;
margin-top: 2px;
}
-pre
-{
+div.pre {
margin: 0;
padding: 0;
- white-space: -moz-pre-wrap !important;
- white-space: pre-wrap !important;
- white-space: pre;
- word-wrap: break-word; /* IE (and Safari) */
+ font-family: monospace;
}
blockquote
{
- padding-left: 5px;
border-left: #1010ff 2px solid;
- margin-left: 5px;
- width: 100%;
+ margin: 0;
+ padding: 0 0.4em;
}
diff --git a/skins/classic/mail.css b/skins/classic/mail.css
index fc066e023..6409b6b9b 100644
--- a/skins/classic/mail.css
+++ b/skins/classic/mail.css
@@ -1,1884 +1,1881 @@
/***** Roundcube|Mail mail task styles *****/
#messagetoolbar
{
position: absolute;
top: 47px;
left: 205px;
right: 10px;
height: 35px;
min-width: 650px;
white-space: nowrap;
/* border: 1px solid #cccccc; */
}
.extwin #messagetoolbar
{
top: 5px;
left: 20px;
}
#messagetoolbar a,
#messagetoolbar select
{
display: block;
float: left;
padding-right: 10px;
}
#messagetoolbar a.button,
#messagetoolbar a.buttonPas {
display: block;
float: left;
width: 32px;
height: 32px;
padding: 0;
margin: 0 5px;
overflow: hidden;
background: url(images/mail_toolbar.png) 0 0 no-repeat transparent;
opacity: 0.99; /* this is needed to make buttons appear correctly in Chrome */
}
#messagetoolbar a.buttonPas {
opacity: 0.35;
}
#messagetoolbar a.button.selected {
background-color: #ddd;
margin-left: 4px;
margin-right: 4px;
margin-top: -1px;
border: 1px solid #ccc;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
}
#messagetoolbar a.checkmailSel {
background-position: 0 -32px;
}
#messagetoolbar a.back {
background-position: -32px 0;
}
#messagetoolbar a.backSel {
background-position: -32px -32px;
}
#messagetoolbar a.compose {
background-position: -64px 0;
}
#messagetoolbar a.composeSel {
background-position: -64px -32px;
}
#messagetoolbar a.reply {
background-position: -96px 0;
}
#messagetoolbar a.replySel {
background-position: -96px -32px;
}
#messagetoolbar a.replyAll {
background-position: -128px 0;
}
#messagetoolbar a.replyAllSel {
background-position: -128px -32px;
}
#messagetoolbar a.forward {
background-position: -160px 0;
}
#messagetoolbar a.forwardSel {
background-position: -160px -32px;
}
#messagetoolbar a.delete {
background-position: -192px 0;
}
#messagetoolbar a.deleteSel {
background-position: -192px -32px;
}
#messagetoolbar a.print {
background-position: -256px 0;
}
#messagetoolbar a.printSel {
background-position: -256px -32px;
}
#messagetoolbar a.markmessage {
background-position: -288px 0;
}
#messagetoolbar a.messagemenu {
background-position: -320px 0;
width: 34px;
}
#messagetoolbar a.spellcheck {
background-position: -418px 0;
}
#messagetoolbar a.spellcheckSel {
background-position: -418px -32px;
}
#messagetoolbar a.attach {
background-position: -386px 0;
}
#messagetoolbar a.attachSel {
background-position: -386px -32px;
}
#messagetoolbar a.insertsig {
background-position: -482px 0;
}
#messagetoolbar a.insertsigSel {
background-position: -482px -32px;
}
#messagetoolbar a.savedraft {
background-position: -354px 0;
}
#messagetoolbar a.savedraftSel {
background-position: -354px -32px;
}
#messagetoolbar a.send {
background-position: -450px 0;
}
#messagetoolbar a.sendSel {
background-position: -450px -32px;
}
#messagetoolbar a.move {
background-position: -580px 0;
}
#messagetoolbar a.moveSel {
background-position: -580px -32px;
}
#messagetoolbar a.download {
background-position: -480px 0;
}
#messagetoolbar a.downloadSel {
background-position: -480px -32px;
}
#messagetoolbar a.responses {
background-position: -548px 0;
}
#messagemenu li a.active:hover,
#attachmentmenu li a.active:hover,
#markmessagemenu li a.active:hover
{
color: #fff;
background-color: #c00;
}
#messagemenu li a,
#attachmentmenu li a
{
background: url(images/messageactions.png) no-repeat 7px 0;
background-position: 7px 20px;
}
#messagemenu li a.printlink
{
background-position: 7px 1px;
}
#messagemenu li a.downloadlink,
#attachmentmenu li a.downloadlink
{
background-position: 7px -17px;
}
#messagemenu li a.sourcelink
{
background-position: 7px -35px;
}
#messagemenu li a.openlink,
#attachmentmenu li a.openlink
{
background-position: 7px -52px;
}
#messagemenu li a.editlink
{
background-position: 6px -70px;
}
#messagemenu li a.movelink
{
background-position: 6px -161px;
}
#messagemenu li a.copylink
{
background-position: 6px -143px;
}
#markmessagemenu li a,
#compose-attachments li a
{
background: url(images/messageicons.png) no-repeat;
}
#markmessagemenu li a.readlink
{
background-position: 7px -51px;
}
#markmessagemenu li a.unreadlink
{
background-position: 7px -119px;
}
#markmessagemenu li a.flaggedlink
{
background-position: 7px -153px;
}
#markmessagemenu li a.unflaggedlink
{
background-position: 7px -136px;
}
#searchfilter
{
white-space: nowrap;
position: absolute;
right: 198px;
vertical-align: middle;
}
#searchfilter label
{
font-size: 11px;
}
#mailleftcontainer
{
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 160px;
}
#mailrightcontainer
{
position: absolute;
top: 0;
left: 170px;
bottom: 0;
right: 0;
min-width: 600px;
}
#mailrightcontent
{
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
#messagepartcontainer
{
position: absolute;
top: 0;
left: 170px;
right: 0;
bottom: 0;
}
#messagepartheader
{
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 160px;
border: 1px solid #999999;
background-color: #F9F9F9;
overflow: hidden;
}
#messagepartheader table
{
width: 100%;
table-layout: fixed;
}
#messagepartheader table td
{
text-overflow: ellipsis;
}
#messagepartheader table td.title
{
width: 60px;
}
#mailcontframe
{
position: absolute;
width: 100%;
top: 0;
bottom: 0;
border: 1px solid #999999;
background-color: #F9F9F9;
overflow: hidden;
}
#mailpreviewframe
{
position: absolute;
width: 100%;
top: 205px;
bottom: 0px;
border: 1px solid #999999;
background-color: #F9F9F9;
}
#messagecontframe
{
position: relative;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
width: 100%;
height: 100%;
min-height: 100%; /* Chrome 14 bug */
}
#messagepartframe
{
width: 100%;
height: 100%;
min-height: 100%; /* Chrome 14 bug */
border: 1px solid #999999;
background-color: #fff;
}
/** mailbox list styles */
#mailboxlist-container
{
position: absolute;
top: 0;
left: 0;
width: 100%;
bottom: 0;
border: 1px solid #999;
background-color: #F9F9F9;
}
#mailboxlist
{
position:relative;
height: auto;
margin: 0px;
padding: 0px;
list-style-image: none;
list-style-type: none;
overflow: hidden;
white-space: nowrap;
background-color: #FFF;
}
#mailboxlist li
{
display: block;
position: relative;
font-size: 11px;
border-bottom: 1px solid #EBEBEB;
}
#mailboxlist li ul li:last-child
{
border-bottom: 0 none;
}
#mailboxlist li.inbox a
{
background-position: 5px -18px;
}
#mailboxlist li.drafts a
{
background-position: 5px -37px;
}
#mailboxlist li.sent a
{
background-position: 5px -54px;
}
#mailboxlist li.junk a
{
background-position: 5px -73px;
}
#mailboxlist li.trash a
{
background-position: 5px -180px;
}
#mailboxlist li.trash.empty a
{
background-position: 5px -90px;
}
#mailboxlist li a
{
cursor: default;
display: block;
position: relative;
padding-left: 25px;
padding-top: 2px;
padding-bottom: 2px;
text-decoration: none;
height: 15px;
background: url(images/icons/folders.png) 5px 0 no-repeat;
}
#mailboxlist li.unread
{
font-weight: bold;
}
#mailboxlist li.virtual > a
{
color: #666;
}
#mailboxlist li.recent > a
{
color: #0066FF;
}
#mailboxlist li.selected,
#mailboxlist li.droptarget li.selected
{
background-color: #929292;
}
#mailboxlist li.selected > a,
#mailboxlist li.droptarget li.selected a
{
color: #FFF;
font-weight: bold;
}
#mailboxlist li.droptarget
{
background-color: #FFFFA6;
}
/* styles for nested folders */
#mailboxlist ul {
list-style: none;
padding: 0;
margin: 0;
border-top: 1px solid #EBEBEB;
background-color: #FFF;
font-weight: normal;
}
#mailboxlist li.mailbox ul li a {
padding-left: 40px; /* 24 + 1 x 16 */
background-position: 20px 0; /* 4 + 1 x 16 */
}
#mailboxlist li.mailbox ul li div.treetoggle {
left: 23px !important;
}
#mailboxlist li.mailbox ul ul li.mailbox a {
padding-left: 56px; /* 2x */
background-position: 36px 0;
}
#mailboxlist li.mailbox ul ul li div.treetoggle {
left: 39px !important;
}
#mailboxlist li.mailbox ul ul ul li.mailbox a {
padding-left: 72px; /* 3x */
background-position: 52px 0;
}
#mailboxlist li.mailbox ul ul ul li div.treetoggle {
left: 55px !important;
}
#mailboxlist li.mailbox ul ul ul ul li.mailbox a {
padding-left: 88px; /* 4x */
background-position: 68px 0;
}
#mailboxlist li.mailbox ul ul ul ul li div.treetoggle {
left: 71px !important;
}
/* indent folders on levels > 4 */
#mailboxlist li.mailbox ul ul ul ul ul li {
padding-left: 16px;
}
#mailboxlist li.mailbox ul ul ul ul ul li div.treetoggle {
left: 87px !important;
}
#mailboxlist li.mailbox ul li.drafts a
{
background-position: 21px -37px;
}
#mailboxlist li.mailbox ul li.sent a
{
background-position: 21px -54px;
}
#mailboxlist li.mailbox ul li.junk a
{
background-position: 21px -73px;
}
#mailboxlist li.mailbox ul li.trash a
{
background-position: 21px -180px;
}
#mailboxlist li.mailbox ul li.trash.empty a
{
background-position: 21px -90px;
}
#listcontrols
{
position: relative;
white-space: nowrap;
line-height: 22px;
padding: 0 4px;
width: auto;
min-width: 300px;
}
#listcontrols a,
#listcontrols span
{
display: block;
float: left;
font-size: 11px;
}
#listcontrols span input
{
vertical-align: middle;
}
#listcontrols a.button,
#listcontrols a.buttonPas
{
display: block;
float: left;
width: 15px;
height: 15px;
padding: 0;
margin-top: 4px;
margin-right: 2px;
overflow: hidden;
background: url(images/mail_footer.png) 0 0 no-repeat transparent;
opacity: 0.99; /* this is needed to make buttons appear correctly in Chrome */
}
#listcontrols a.buttonPas
{
opacity: 0.35;
}
#listcontrols a.all {
background-position: -30px 0;
}
#listcontrols a.allsel {
background-position: -30px -15px;
}
#listcontrols a.page {
background-position: -135px 0;
}
#listcontrols a.pagesel {
background-position: -135px -15px;
}
#listcontrols a.unread {
background-position: -45px 0;
}
#listcontrols a.unreadsel {
background-position: -45px -15px;
}
#listcontrols a.invert {
background-position: -60px 0;
}
#listcontrols a.invertsel {
background-position: -60px -15px;
}
#listcontrols a.none {
background-position: -75px 0;
}
#listcontrols a.nonesel {
background-position: -75px -15px;
}
#listcontrols a.expand-all {
background-position: -90px 0;
}
#listcontrols a.expand-allsel {
background-position: -90px -15px;
}
#listcontrols a.collapse-all {
background-position: -105px 0;
}
#listcontrols a.collapse-allsel {
background-position: -105px -15px;
}
#listcontrols a.expand-unread {
background-position: -120px 0;
}
#listcontrols a.expand-unreadsel {
background-position: -120px -15px;
}
#countcontrols
{
position: absolute;
top: 4px;
right: 4px;
white-space: nowrap;
font-size: 11px;
}
#countcontrols a.button,
#countcontrols a.buttonPas
{
float: right;
}
/** message list styles */
body.messagelist
{
margin: 0px;
background-color: #F9F9F9;
}
table.messagelist
{
width: 100%;
display: table;
table-layout: fixed;
border-collapse: collapse;
border-spacing: 0;
z-index: 1;
}
table.messagelist.fixedcopy
{
z-index: 2;
}
.messagelist thead tr td
{
height: 20px;
padding: 0 4px 0 2px;
vertical-align: middle;
border-bottom: 1px solid #999999;
color: #333333;
background: url(images/listheader.gif) top left repeat-x #CCC;
font-size: 11px;
font-weight: bold;
}
.messagelist thead tr td.sortedASC,
.messagelist thead tr td.sortedDESC
{
background-position: 0 -26px;
}
.messagelist thead tr td.sortedASC a
{
background: url(images/icons/sort.gif) right 0 no-repeat;
}
.messagelist thead tr td.sortedDESC a
{
background: url(images/icons/sort.gif) right -14px no-repeat;
}
.messagelist thead tr td a
{
display: block;
width: auto !important;
width: 100%;
color: #333333;
text-decoration: none;
}
.messagelist thead tr td.size.sortedASC a,
.messagelist thead tr td.size.sortedDESC a
{
padding-right: 18px;
}
.messagelist thead tr td.subject
{
padding-left: 18px;
width: 99%;
}
.messagelist tbody tr td
{
height: 20px;
padding: 0;
font-size: 11px;
overflow: hidden;
vertical-align: middle;
white-space: nowrap;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
border-bottom: 1px solid #EBEBEB;
cursor: default;
}
.messagelist tbody tr td a
{
color: #000;
text-decoration: none;
white-space: nowrap;
cursor: inherit;
}
.messagelist td img
{
vertical-align: middle;
display: inline-block;
}
.messagelist tbody tr td.flag,
.messagelist tbody tr td.status,
.messagelist tbody tr td.subject span.status
{
cursor: pointer;
}
.messagelist tr td.flag span,
.messagelist tr td.status span,
.messagelist tr td.attachment span,
.messagelist tr td.priority span
{
display: block;
width: 15px;
}
.messagelist tr td div.collapsed,
.messagelist tr td div.expanded,
.messagelist tr td.threads div.listmenu,
.messagelist tr td.attachment span.attachment,
.messagelist tr td.attachment span.report,
.messagelist tr td.priority span.priority,
.messagelist tr td.priority span.prio1,
.messagelist tr td.priority span.prio2,
.messagelist tr td.priority span.prio3,
.messagelist tr td.priority span.prio4,
.messagelist tr td.priority span.prio5,
.messagelist tr td.flag span.flagged,
.messagelist tr td.flag span.unflagged,
.messagelist tr td.flag span.unflagged:hover,
.messagelist tr td.status span.status,
.messagelist tr td.status span.msgicon,
.messagelist tr td.status span.deleted,
.messagelist tr td.status span.unread,
.messagelist tr td.status span.unreadchildren,
.messagelist tr td.subject span.msgicon,
.messagelist tr td.subject span.deleted,
.messagelist tr td.subject span.unread,
.messagelist tr td.subject span.replied,
.messagelist tr td.subject span.forwarded,
.messagelist tr td.subject span.unreadchildren
{
display: inline-block;
vertical-align: middle;
height: 17px;
width: 15px;
background: url(images/messageicons.png) center no-repeat;
}
.messagelist tr td.attachment span.attachment
{
background-position: 0 -170px;
}
.messagelist tr td.attachment span.report
{
background-position: 0 -255px;
}
.messagelist tr td.priority span.priority
{
background-position: 0 -309px;
}
.messagelist tr td.priority span.prio5
{
background-position: 0 -358px;
}
.messagelist tr td.priority span.prio4
{
background-position: 0 -340px;
}
.messagelist tr td.priority span.prio3
{
background-position: 0 -324px;
}
.messagelist tr td.priority span.prio2
{
background-position: 0 -309px;
}
.messagelist tr td.priority span.prio1
{
background-position: 0 -290px;
}
.messagelist tr td.flag span.flagged
{
background-position: 0 -153px;
}
.messagelist tr td.flag span.unflagged:hover
{
background-position: 0 -136px;
}
.messagelist tr td.subject span.msgicon,
.messagelist tr td.subject span.unreadchildren
{
background-position: 0 -51px;
margin: 0 2px;
}
.messagelist tr td.subject span.replied
{
background-position: 0 -85px;
}
.messagelist tr td.subject span.forwarded
{
background-position: 0 -68px;
}
.messagelist tr td.subject span.replied.forwarded
{
background-position: 0 -102px;
}
.messagelist tr td.status span.msgicon,
.messagelist tr td.flag span.unflagged,
.messagelist tr td.status span.unreadchildren
{
background-position: 0 17px; /* no icon */
}
.messagelist tr td.status span.msgicon:hover
{
background-position: 0 -272px;
}
.messagelist tr td.status span.deleted,
.messagelist tr td.subject span.deleted
{
background-position: 0 -187px;
}
.messagelist tr td.status span.status,
.messagelist tr td.status span.unread,
.messagelist tr td.subject span.unread
{
background-position: 0 -119px;
}
.messagelist tr td div.collapsed
{
background-position: 0 -221px;
cursor: pointer;
}
.messagelist tr td div.expanded
{
background-position: 0 -204px;
cursor: pointer;
}
.messagelist tr td.threads div.listmenu
{
background-position: 0 -238px;
cursor: pointer;
}
.messagelist tbody tr td.subject
{
width: 99%;
}
.messagelist tbody tr td.subject a
{
cursor: default;
vertical-align: middle; /* #1487091 */
}
/* thread parent message with unread children */
.messagelist tbody tr.unroot td.subject a
{
text-decoration: underline;
}
.messagelist tr td.attachment,
.messagelist tr td.threads,
.messagelist tr td.status,
.messagelist tr td.flag,
.messagelist tr td.priority
{
width: 17px;
padding: 0 0 0 2px;
}
.messagelist tr td.size
{
width: 60px;
text-align: right;
padding: 0 2px;
}
.messagelist tr td.fromto,
.messagelist tr td.from,
.messagelist tr td.to,
.messagelist tr td.cc,
.messagelist tr td.replyto
{
width: 180px;
padding: 0 2px;
}
.messagelist tr td.date
{
width: 135px;
padding: 0 2px;
}
.messagelist tr td.folder
{
width: 135px;
}
.messagelist tr td.hidden
{
display: none;
}
.messagelist tr.message
{
background-color: #FFF;
}
.messagelist tr.unread
{
font-weight: bold;
background-color: #FFFFFF;
}
.messagelist tr.flagged td,
.messagelist tr.flagged td a
{
color: #CC0000;
}
/* This padding-left minus the focused padding left should be half of the focused border-left */
.messagelist thead tr td:first-child,
.messagelist tbody tr td:first-child {
border-left: 0;
padding-left: 6px;
}
/* because of border-collapse, we make the left border twice what we want it to be - half will be hidden to the left */
.messagelist tbody tr.focused > td:first-child {
border-left: 4px solid #d4d4d4;
padding-left: 4px;
}
.messagelist tbody tr.selected.focused > td:first-child {
border-left: 2px solid #ccc;
padding-left: 5px;
}
.messagelist tr.selected td
{
color: #FFFFFF;
background-color: #CC3333;
}
.messagelist tr.unfocused td
{
color: #FFFFFF;
background-color: #929292;
}
.messagelist tr.selected td a
{
color: #FFFFFF;
}
.messagelist tr.unfocused td a
{
color: #FFFFFF;
}
.messagelist tr.deleted td,
.messagelist tr.deleted td a
{
color: #CCCCCC;
}
#listmenu
{
padding: 6px;
max-height: none;
}
#listmenu legend
{
color: #999999;
}
#listmenu fieldset
{
border: 1px solid #999999;
margin: 0 5px;
float: left;
}
#listmenu div
{
padding: 8px 0 3px 0;
text-align: center;
clear: both;
}
/***** tree indicators *****/
td span.branch div
{
float: left;
height: 16px;
}
td span.branch div.tree
{
height: 17px;
width: 15px;
background: url(images/tree.gif) 0px 0px no-repeat;
}
td span.branch div.l1
{
background-position: 0px 0px; /* L */
}
td span.branch div.l2
{
background-position: -30px 0px; /* | */
}
td span.branch div.l3
{
background-position: -15px 0px; /* |- */
}
/** message view styles */
#messageframe
{
position: absolute;
top: 0;
left: 180px;
right: 0;
bottom: 0;
border: 1px solid #999;
background-color: #FFF;
overflow: auto;
z-index: 1;
}
.extwin #messageframe
{
left: 0;
}
div.messageheaderbox
{
margin: -14px 8px 0px 8px;
border: 1px solid #ccc;
}
table.headers-table
{
width: 100%;
background-color: #EBEBEB;
}
#messagebody #full-headers,
#messagebody table.headers-table
{
width: auto;
margin: 6px 8px;
background-color: #F4F4F4;
}
#messagebody table.headers-table
{
margin: 16px 6px 6px 6px;
}
div.message-partheaders + div.message-part
{
border-top: 0;
padding-top: 4px;
}
table.headers-table tr td
{
font-size: 11px;
border-bottom:1px solid #FFFFFF;
}
table.headers-table tr td.header-title
{
width: 1%;
color: #666666;
font-weight: bold;
text-align: right;
white-space: nowrap;
padding: 0 4px 0 8px;
}
table.headers-table tr td.header
{
width: 99%;
}
table.headers-table tr td.subject
{
font-weight: bold;
}
table.headers-table tr td.header span
{
white-space: nowrap;
}
#attachment-list
{
margin: 0;
padding: 0 4px 0 8px;
min-height: 16px;
list-style-image: none;
list-style-type: none;
background: url(images/icons/attachment.png) 4px 2px no-repeat #DFDFDF;
}
#messageframe #attachment-list
{
border-bottom: 1px solid #ccc;
}
.messageheaderbox #attachment-list
{
border-top: 1px solid #ccc;
}
#attachment-list:after
{
content: ".";
display: block;
height: 0;
font-size: 0;
clear: both;
visibility: hidden;
}
#attachment-list li
{
float: left;
height: 18px;
font-size: 11px;
padding: 2px 0px 0px 15px;
white-space: nowrap;
}
#attachment-list li a
{
text-decoration: none;
}
#attachment-list li a:hover
{
text-decoration: underline;
}
#attachment-list li a.drop {
background: url(images/icons/down_small.gif) no-repeat center 6px;
width: 12px;
height: 7px;
cursor: pointer;
padding: 5px 0 0;
margin-left: 3px;
display: inline-block;
}
#messagebody
{
position:relative;
padding-bottom: 10px;
background-color: #FFFFFF;
}
div.message-part,
div.message-htmlpart
{
padding: 10px 8px;
border-top: 1px solid #ccc;
/* overflow: hidden; */
}
#messagebody div:first-child
{
border-top: 0;
}
div.message-part a,
div.message-htmlpart a
{
color: #0000CC;
}
-div.message-part pre,
-div.message-htmlpart pre,
div.message-part div.pre
{
margin: 0px;
padding: 0px;
font-family: monospace;
font-size: 12px;
- white-space: -moz-pre-wrap !important;
- white-space: pre-wrap !important;
- white-space: pre;
}
div.message-part span.sig
{
color: #666666;
}
div.message-part blockquote
{
color: blue;
border-left: 2px solid blue;
border-right: 2px solid blue;
background-color: #F6F6F6;
- margin: 2px 0px;
- padding: 1px 8px 1px 10px;
+ margin: 2px 0;
+ padding: 0 0.4em;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
div.message-part blockquote blockquote
{
color: green;
border-left: 2px solid green;
border-right: 2px solid green;
}
div.message-part blockquote blockquote blockquote
{
color: #990000;
border-left: 2px solid #bb0000;
border-right: 2px solid #bb0000;
}
body.iframe div.message-htmlpart
{
margin: 8px;
}
div.message-htmlpart div.rcmBody
{
margin: 8px;
}
#messagebody span.part-notice
{
display: block;
}
#message-objects div,
#messagebody span.part-notice
{
margin: 8px;
min-height: 20px;
padding: 10px 10px 6px 46px;
}
#message-objects div a,
#messagebody span.part-notice a
{
color: #666666;
padding-left: 10px;
}
#message-objects div a:hover,
#messagebody span.part-notice a:hover
{
color: #333333;
}
#messagebody fieldset.image-attachment {
border: 0;
border-top: 1px solid #ccc;
margin: 1em 1em 0 1em;
}
#messagebody fieldset.image-attachment p > img
{
max-width: 80%;
}
#messagebody legend.image-filename
{
color: #999;
font-size: 0.9em;
}
#messagebody p.image-attachment
{
margin: 0 1em;
padding: 1em;
border-top: 1px solid #ccc;
}
#messagebody p.image-attachment a.image-link
{
float: left;
margin-right: 2em;
min-width: 160px;
min-height: 60px;
text-align: center;
}
#messagebody p.image-attachment .image-filename
{
display: block;
font-weight: bold;
line-height: 1.6em;
}
#messagebody p.image-attachment .image-filesize
{
font-size: 11px;
padding-right: 1em;
}
#messagebody p.image-attachment .attachment-links a
{
margin-right: 0.6em;
color: #cc0000;
font-size: 11px;
text-decoration: none;
}
#messagebody p.image-attachment .attachment-links a:hover
{
text-decoration: underline;
}
#messagelinks
{
position: absolute;
top: 8px;
right: 10px;
height: 16px;
text-align: right;
}
#messageframe #messagelinks
{
top: 2px;
right: 2px;
}
#compose-headers #openextwinlink
{
position: absolute;
height: 15px;
top: 4px;
right: 2px;
}
#full-headers
{
color: #666666;
text-align: center;
padding: 2px 6px;
border-bottom: 1px solid #ccc;
background-color: #EBEBEB;
}
.messageheaderbox #full-headers
{
border-bottom: 0;
}
div.more-headers
{
cursor: pointer;
height: 8px;
border-bottom: 0;
}
div.show-headers
{
background: url(images/icons/down_small.gif) no-repeat center;
}
div.hide-headers
{
background: url(images/icons/up_small.gif) no-repeat center;
}
#headers-source
{
margin: 2px 0;
padding: 0.5em;
height: 145px;
background: white;
overflow: auto;
font-size: 11px;
border: 1px solid #CCC;
display: none;
text-align: left;
color: #333;
}
/** message compose styles */
#compose-container
{
position: absolute;
top: 0;
left: 205px;
right: 0;
bottom: 0;
margin: 0;
}
#compose-div
{
position: absolute;
top: 85px;
right: 0;
left: 0;
bottom: 0;
margin: 0;
}
#compose-body-div
{
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 175px;
border: 1px solid #999;
}
#compose-div .boxlistcontent
{
bottom: 23px;
}
#compose-body
{
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: 0;
font-size: 9pt;
font-family: monospace;
resize: none;
border: none;
outline: none;
}
#compose-body_tbl,
#compose-body_tbl td
{
border: none;
}
#compose-body_tbl tr.mceFirst td.mceToolbar
{
border-bottom: 1px solid #ccc;
}
#compose-headers
{
width: 100%;
}
#compose-headers td.editfield
{
padding-right: 8px;
width: 95%;
}
#compose-headers td.top
{
vertical-align: top;
}
#compose-headers td.title,
#compose-subject td.title
{
width: 80px !important;
font-size: 11px;
font-weight: bold;
padding-right: 10px;
white-space: nowrap;
color: #666;
}
#compose-headers td textarea,
#compose-headers td input
{
resize: none;
width: 100%;
border: 1px solid #999;
}
#compose-headers td textarea
{
height: 32px;
}
input.from_address
{
width: 80% !important;
}
#compose-cc,
#compose-bcc,
#compose-replyto,
#compose-followupto
{
display: none;
}
#compose-editorfooter
{
position: absolute;
right: 5px;
bottom: 0;
text-align: right;
line-height: 20px;
}
#compose-editorfooter label
{
font-size: 11px;
font-weight: bold;
color: #666;
}
#compose-buttons
{
position: absolute;
left: 5px;
bottom: 1px;
width: auto;
}
#compose-contacts
{
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 195px;
border: 1px solid #999;
background-color: #F9F9F9;
}
#compose-attachments
{
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 1px solid #999;
background-color: #F9F9F9;
}
#compose-attachments.droptarget.hover
{
background-color: #F0F0EE;
box-shadow: 0 0 5px 0 #999;
-moz-box-shadow: 0 0 5px 0 #999;
-o-box-shadow: 0 0 5px 0 #999;
}
#compose-attachments ul
{
margin: 0px;
padding: 0px;
background-color: #FFF;
list-style-image: none;
list-style-type: none;
}
#compose-attachments ul li
{
height: 18px;
line-height: 16px;
font-size: 11px;
padding: 2px 2px 1px 2px;
border-bottom: 1px solid #EBEBEB;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
}
#compose-attachments li a
{
text-indent: -5000px;
width: 17px;
height: 16px;
padding-bottom: 2px;
display: inline-block;
text-decoration: none;
vertical-align: middle;
}
#compose-attachments li img
{
vertical-align: middle;
}
#compose-attachments li a.delete,
#compose-attachments li a.cancelupload
{
background-position: 0px -392px;
}
#compose-attachments li span
{
line-height: 18px;
vertical-align: middle;
}
#upload-form,
#attachment-form
{
padding: 6px;
}
#upload-form div,
#attachment-form div
{
padding: 2px;
}
#upload-form div.buttons,
#attachment-form div.buttons
{
margin-top: 4px;
}
#quota
{
position: absolute;
top: 3px;
right: 8px;
width: 100px;
}
#quotaimg
{
position: absolute;
top: 3px;
right: 6px;
z-index: 101;
}
/* addressbook in compose - copy from addressbook.css */
#directorylist
{
list-style: none;
margin: 0;
padding: 0;
background-color: #FFFFFF;
}
#directorylist li
{
display: block;
font-size: 11px;
background: url(images/icons/folders.png) 5px -108px no-repeat;
border-bottom: 1px solid #EBEBEB;
white-space: nowrap;
}
#directorylist li a
{
cursor: default;
display: block;
padding-left: 25px;
padding-top: 2px;
padding-bottom: 2px;
text-decoration: none;
white-space: nowrap;
height: 15px;
}
#directorylist li.selected
{
background-color: #929292;
border-bottom: 1px solid #898989;
}
#directorylist li.selected a
{
color: #FFF;
font-weight: bold;
}
#contacts-table
{
width: 100%;
table-layout: fixed;
}
#contacts-table tbody td
{
cursor: default;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
}
#contacts-table td span.email
{
display: inline;
color: #ccc;
font-style: italic;
margin-left: 0.5em;
}
#abookcountbar
{
margin-top: 4px;
margin-left: 4px;
position: absolute;
margin-right: 5px;
right: 0;
}
#abookactions
{
position: absolute;
text-underline: none;
}
#abookactions a
{
font-weight: bold;
line-height: 22px;
height: 22px;
width: auto;
margin: 0;
padding-left: 5px;
padding-right: 5px;
text-shadow: 1px 1px white;
background: url("images/icons/groupactions.png") no-repeat right -70px;
}
#abookactions a.disabled
{
color: #999;
}
#compose-contacts #quicksearchbar
{
top: 2px;
left: 7px;
}
#compose-contacts #directorylist
{
width: 100%;
top: 23px;
position: absolute;
border-top: 1px solid #eee;
}
#compose-contacts #contacts-table
{
top: 45px;
position: absolute;
}
diff --git a/skins/larry/editor_content.css b/skins/larry/editor_content.css
index aabed07b5..67480ab77 100644
--- a/skins/larry/editor_content.css
+++ b/skins/larry/editor_content.css
@@ -1,31 +1,26 @@
/* This file contains the CSS data for the editable area(iframe) of TinyMCE */
body, td, pre {
font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
font-size: 12px;
}
body {
background-color: #FFFFFF;
margin-left: 4px;
margin-right: 4px;
margin-top: 2px;
}
-pre
-{
+div.pre {
margin: 0;
padding: 0;
- white-space: -moz-pre-wrap !important;
- white-space: pre-wrap !important;
- white-space: pre;
- word-wrap: break-word; /* IE (and Safari) */
+ font-family: monospace;
}
blockquote
{
- padding-left: 5px;
border-left: #1010ff 2px solid;
- margin-left: 5px;
- width: 100%;
+ margin: 0;
+ padding: 0 0.4em;
}
diff --git a/skins/larry/mail.css b/skins/larry/mail.css
index 05159e016..329acb205 100644
--- a/skins/larry/mail.css
+++ b/skins/larry/mail.css
@@ -1,1588 +1,1585 @@
/**
* Roundcube webmail styles for the Email section
*
* Copyright (c) 2012, The Roundcube Dev Team
* Screendesign by FLINT / Büro für Gestaltung, bueroflint.com
*
* 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.
*/
#mailview-left {
position: absolute;
top: 0;
left: 0;
width: 220px;
bottom: 0;
z-index: 2;
}
#mailview-right {
position: absolute;
top: 0;
left: 232px;
right: 0;
bottom: 0;
z-index: 3;
}
#mailview-right.fullwidth {
left: 0;
}
#mailview-top {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0px;
}
html.ie #mailview-top {
overflow: visible; /* fixes display issues of fixed list header in IE */
}
#mailview-bottom {
display: none;
position: absolute;
left: 0;
bottom: 0;
right: 0;
height: 0;
border-radius: 4px;
border-top: none;
}
#composeview-right #mailview-bottom {
border-radius: 0 0 4px 4px;
}
#mailboxcontainer,
#messagelistcontainer {
position: absolute;
top: 0;
left: 0;
width: 100%;
bottom: 0;
}
#messagelistcontainer {
top: 0;
bottom: 30px;
overflow: auto;
}
/* Real browsers accept this (not IE) */
html>/**/body #messagelist {
overflow: auto;
overflow-x: hidden;
}
#messagelistfooter {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 22px;
padding: 4px 6px;
border-top: 1px solid #ddd;
background: #ebebeb;
background: -moz-linear-gradient(top, #ebebeb 0%, #c6c6c6 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ebebeb), color-stop(100%,#c6c6c6));
background: -o-linear-gradient(top, #ebebeb 0%, #c6c6c6 100%);
background: -ms-linear-gradient(top, #ebebeb 0%, #c6c6c6 100%);
background: linear-gradient(top, #ebebeb 0%, #c6c6c6 100%);
border-radius: 0 0 4px 4px;
white-space: nowrap;
}
#messagelistfooter.rightalign {
text-align: right;
}
#messagelistfooter #countcontrols {
display: inline-block;
}
#messagelistfooter #listcontrols,
#messagelistfooter #listselectors {
display: inline-block;
margin-right: 2em;
vertical-align: middle;
}
#messagelistfooter #listselectors .menuselector {
margin-top: -2px;
}
a.iconbutton.listmode {
width: 26px;
height: 20px;
background-position: 0 -477px;
}
a.iconbutton.threadmode {
width: 26px;
height: 20px;
background-position: 0 -497px;
}
a.iconbutton.listmode.selected {
background-position: -26px -477px;
}
a.iconbutton.threadmode.selected {
background-position: -26px -497px;
}
#mailboxlist li.mailbox {
position: relative;
background-repeat: no-repeat;
background-position: 6px 2px;
}
#mailboxlist > li:first-child {
border-radius: 4px 4px 0 0;
border-top: 0;
}
#mailboxlist li.mailbox a {
padding-left: 36px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
background-image: url(images/listicons.png);
background-repeat: no-repeat;
background-position: 6px 3px;
}
#mailboxlist li.mailbox.unread > a {
padding-right: 36px;
}
#mailboxlist li.mailbox.selected > a {
background-position: 6px -21px;
}
#mailboxlist li.mailbox.inbox > a {
background-position: 6px -189px;
}
#mailboxlist li.mailbox.inbox.selected > a {
background-position: 6px -213px;
}
#mailboxlist li.mailbox.drafts > a {
background-position: 6px -238px;
}
#mailboxlist li.mailbox.drafts.selected > a {
background-position: 6px -262px;
}
#mailboxlist li.mailbox.sent > a {
background-position: 6px -286px;
}
#mailboxlist li.mailbox.sent.selected > a {
background-position: 6px -310px;
}
#mailboxlist li.mailbox.junk > a {
background-position: 6px -334px;
}
#mailboxlist li.mailbox.junk.selected > a {
background-position: 6px -358px;
}
#mailboxlist li.mailbox.trash > a {
background-position: 6px -382px;
}
#mailboxlist li.mailbox.trash.selected > a {
background-position: 6px -406px;
}
#mailboxlist li.mailbox.trash.empty > a {
background-position: 6px -1924px;
}
#mailboxlist li.mailbox.trash.empty.selected > a {
background-position: 6px -1948px;
}
#mailboxlist li.mailbox.archive > a {
background-position: 6px -1699px;
}
#mailboxlist li.mailbox.archive.selected > a {
background-position: 6px -1723px;
}
#mailboxlist li.mailbox ul li.drafts > a {
background-position: 23px -238px;
}
#mailboxlist li.mailbox ul li.drafts.selected > a {
background-position: 23px -262px;
}
#mailboxlist li.mailbox ul li.sent > a {
background-position: 23px -286px;
}
#mailboxlist li.mailbox ul li.sent.selected > a {
background-position: 23px -310px;
}
#mailboxlist li.mailbox ul li.junk > a {
background-position: 23px -334px;
}
#mailboxlist li.mailbox ul li.junk.selected > a {
background-position: 23px -358px;
}
#mailboxlist li.mailbox ul li.trash > a {
background-position: 23px -382px;
}
#mailboxlist li.mailbox ul li.trash.selected > a {
background-position: 23px -406px;
}
#mailboxlist li.mailbox ul li.trash.empty > a {
background-position: 23px -1924px;
}
#mailboxlist li.mailbox ul li.trash.empty.selected > a {
background-position: 23px -1948px;
}
#mailboxlist li.mailbox ul li.archive > a {
background-position: 23px -1699px;
}
#mailboxlist li.mailbox ul li.archive.selected > a {
background-position: 23px -1723px;
}
#mailboxlist li.unread {
font-weight: bold;
}
#mailboxlist li.virtual > a {
color: #aaa;
}
#mailboxlist li.recent > a {
color: #017cb4;
}
#mailboxlist li.mailbox div.treetoggle {
top: 13px;
left: 19px;
}
#mailboxlist li.mailbox ul li:last-child {
border-bottom: 0;
}
/* nested mailboxes */
#mailboxlist li.mailbox ul {
list-style: none;
margin: 0;
padding: 0;
border-top: 1px solid #bbd3da;
}
#mailboxlist li.mailbox ul li a {
padding-left: 52px; /* 36 + 1 x 16 */
background-position: 22px -95px; /* 6 + 1 x 16 */
}
#mailboxlist li.mailbox ul li.selected > a {
background-position: 22px -119px;
}
#mailboxlist li.mailbox ul li div.treetoggle {
left: 33px;
top: 14px;
}
#mailboxlist li.mailbox ul ul li.mailbox a {
padding-left: 68px; /* 2x */
background-position: 38px -95px;
}
#mailboxlist li.mailbox ul ul li.selected > a {
background-position: 38px -119px;
}
#mailboxlist li.mailbox ul ul li div.treetoggle {
left: 48px;
}
#mailboxlist li.mailbox ul ul ul li.mailbox a {
padding-left: 84px; /* 3x */
background-position: 54px -95px;
}
#mailboxlist li.mailbox ul ul ul li.selected > a {
background-position: 54px -119px;
}
#mailboxlist li.mailbox ul ul ul li div.treetoggle {
left: 64px;
}
#mailboxlist li.mailbox ul ul ul ul li.mailbox a {
padding-left: 100px; /* 4x */
background-position: 70px -95px;
}
#mailboxlist li.mailbox ul ul ul ul li.selected > a {
background-position: 70px -119px;
}
#mailboxlist li.mailbox ul ul ul ul li div.treetoggle {
left: 80px;
}
/* indent folders on levels > 4 */
#mailboxlist li.mailbox ul ul ul ul ul li {
padding-left: 16px;
}
#mailboxlist li.mailbox ul ul ul ul ul li div.treetoggle {
left: 96px;
}
#mailboxlist li.mailbox .unreadcount {
position: absolute;
top: 3px;
right: 6px;
min-width: 1.8em;
line-height: 15px;
padding: 2px 4px;
background: #82acb5;
background: -moz-linear-gradient(top, #82acb5 0%, #6a939f 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#82acb5), color-stop(100%,#6a939f));
background: -o-linear-gradient(top, #82acb5 0%, #6a939f 100%);
background: -ms-linear-gradient(top, #82acb5 0%, #6a939f 100%);
background: linear-gradient(top, #82acb5 0%, #6a939f 100%);
box-shadow: inset 0 1px 1px 0 #536d72;
-o-box-shadow: inset 0 1px 1px 0 #536d72;
-webkit-box-shadow: inset 0 1px 1px 0 #536d72;
-moz-box-shadow: inset 0 1px 1px 0 #536d72;
border-radius: 9px;
color: #fff;
text-align: center;
font-weight: bold;
text-shadow: none;
}
#mailboxlist li.mailbox.selected > a .unreadcount {
background: #005d76;
background: -moz-linear-gradient(top, #005d76 0%, #004558 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#005d76), color-stop(100%,#004558));
background: -o-linear-gradient(top, #005d76 0%, #004558 100%);
background: -ms-linear-gradient(top, #005d76 0%, #004558 100%);
background: linear-gradient(top, #005d76 0%, #004558 100%);
box-shadow: inset 0 1px 1px 0 #003645;
-o-box-shadow: inset 0 1px 1px 0 #003645;
-webkit-box-shadow: inset 0 1px 1px 0 #003645;
-moz-box-shadow: inset 0 1px 1px 0 #003645;
}
#mailboxlist li.mailbox.recent > a .unreadcount {
background: #017cb4;
background: -moz-linear-gradient(top, #017cb4 0%, #006ca4 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#017cb4), color-stop(100%,#006ca4));
background: -o-linear-gradient(top, #017cb4 0%, #006ca4 100%);
background: -ms-linear-gradient(top, #017cb4 0%, #006ca4 100%);
background: linear-gradient(top, #017cb4 0%, #006ca4 100%);
box-shadow: inset 0 1px 1px 0 #005080;
-o-box-shadow: inset 0 1px 1px 0 #005080;
-webkit-box-shadow: inset 0 1px 1px 0 #005080;
-moz-box-shadow: inset 0 1px 1px 0 #005080;
}
#searchfilter {
position: absolute;
right: 256px;
width: auto;
top: 2px;
}
#searchfilter select {
height: 26px;
}
#mailview-left select.mailboxlist {
position: relative;
top: 10px;
width: 100%;
}
#messagetoolbar {
position: absolute;
top: -6px;
left: 0;
height: 40px;
white-space: nowrap;
z-index: 10;
}
#messagetoolbar.fullwidth {
right: 0;
}
#messagesearchtools {
position: absolute;
right: 0;
top: 0;
}
#mailpreviewtoggle {
display: block;
position: absolute;
top: 6px;
right: 4px;
width: 20px;
height: 18px;
background: url(images/buttons.png) -3px -458px no-repeat;
}
#mailpreviewtoggle.enabled {
background-position: -28px -458px;
}
/*** message list ***/
table.messagelist {
z-index: 1;
}
table.messagelist.fixedcopy {
z-index: 2;
}
.messagelist thead td:first-child {
border-radius: 4px 0 0 0; /* for Chrome */
}
.messagelist tr td.attachment,
.messagelist tr td.threads,
.messagelist tr td.status,
.messagelist tr td.flag,
.messagelist tr td.priority {
width: 20px;
padding: 2px 3px;
}
.webkit .messagelist tr td.attachment,
.webkit .messagelist tr td.threads,
.webkit .messagelist tr td.status,
.webkit .messagelist tr td.flag,
.webkit .messagelist tr td.priority {
width: 26px;
}
.messagelist tr td.threads {
width: 26px;
}
.webkit .messagelist tr td.threads {
width: 30px;
}
.messagelist tr td.threads,
.messagelist tr td.threads + td {
border-left: 0;
}
.messagelist tr td.size {
width: 60px;
text-align: right;
}
.messagelist thead tr td.size {
text-align: left;
}
.messagelist tr td.fromto,
.messagelist tr td.from,
.messagelist tr td.to,
.messagelist tr td.cc,
.messagelist tr td.replyto {
width: 200px;
}
.messagelist tr td.date {
width: 155px;
}
.messagelist tr td.folder {
width: 135px;
}
.messagelist tr td.hidden {
display: none;
}
.messagelist tr.message {
/* background-color: #fff; */
}
.messagelist tr.thread.expanded td {
background-color: #ededed;
}
.messagelist tr.unread {
font-weight: bold;
/* background-color: #fff; */
}
.messagelist tr.flagged td,
.messagelist tr.flagged td a {
color: #f30;
}
.messagelist thead tr td.sortedASC a,
.messagelist thead tr td.sortedDESC a {
color: #004458;
text-decoration: underline;
background: url(images/listicons.png) right -912px no-repeat;
}
.messagelist thead tr td.sortedASC a {
background-position: right -944px;
}
.messagelist td img {
vertical-align: middle;
display: inline-block;
}
.messagelist tbody td a {
color: #333;
text-decoration: none;
white-space: nowrap;
cursor: default;
}
.messagelist tbody tr td.flag,
.messagelist tbody tr td.status,
.messagelist tbody tr td.subject span.status {
cursor: pointer;
}
.messagelist tr td.flag span,
.messagelist tr td.status span,
.messagelist tr td.attachment span,
.messagelist tr td.priority span {
display: block;
width: 20px;
}
.messagelist tr td div.collapsed,
.messagelist tr td div.expanded,
.messagelist tr td.threads div.listmenu,
.messagelist tr td.attachment span.attachment,
.messagelist tr td.attachment span.report,
.messagelist tr td.priority span.priority,
.messagelist tr td.priority span.prio1,
.messagelist tr td.priority span.prio2,
.messagelist tr td.priority span.prio3,
.messagelist tr td.priority span.prio4,
.messagelist tr td.priority span.prio5,
.messagelist tr td.flag span.flagged,
.messagelist tr td.flag span.unflagged,
.messagelist tr td.flag span.unflagged:hover,
.messagelist tr td.status span.status,
.messagelist tr td.status span.msgicon,
.messagelist tr td.status span.deleted,
.messagelist tr td.status span.unread,
.messagelist tr td.status span.unreadchildren,
.messagelist tr td.subject span.msgicon,
.messagelist tr td.subject span.deleted,
.messagelist tr td.subject span.unread,
.messagelist tr td.subject span.replied,
.messagelist tr td.subject span.forwarded,
.messagelist tr td.subject span.unreadchildren {
display: inline-block;
vertical-align: middle;
height: 18px;
width: 20px;
padding: 0;
background: url(images/listicons.png) -100px 0 no-repeat;
}
.messagelist tbody tr td.attachment span.attachment {
background-position: 0 -996px;
}
.messagelist thead tr td.attachment span.attachment {
background-position: -24px -997px;
}
.messagelist tbody tr td.attachment span.report {
background-position: -24px -1116px;
}
.messagelist thead tr td.priority span.priority {
background-position: -24px -1845px;
}
.messagelist tr td.priority span.prio5 {
background-position: 0 -1905px;
}
.messagelist tr td.priority span.prio4 {
background-position: 0 -1885px;
}
.messagelist tr td.priority span.prio2 {
background-position: 0 -1865px;
}
.messagelist tr td.priority span.prio1 {
background-position: 0 -1845px;
}
.messagelist tbody tr td.flag span.flagged {
background-position: 0 -1036px;
}
.messagelist thead tr td.flag span.flagged {
background-position: -22px -1036px;
}
.messagelist tr td.status span.msgicon:hover {
background-position: -23px -1056px;
}
.messagelist tr td.flag span.unflagged:hover {
background-position: -23px -1076px;
}
.messagelist tr td.subject span.msgicon,
.messagelist tr td.subject span.unreadchildren {
background-position: 0 -1056px;
margin: 0 1px 0 0;
width: 24px;
}
.messagelist tr td.subject span.replied {
background-position: 0 -1076px;
}
.messagelist tr td.subject span.forwarded {
background-position: 0 -1096px;
}
.messagelist tr td.subject span.replied.forwarded {
background-position: 0 -1116px;
}
.messagelist tr td.status span.msgicon,
.messagelist tr td.flag span.unflagged,
.messagelist tr td.status span.unreadchildren {
background-position: 0 1056px; /* no icon */
}
/*
.messagelist tr td.status span.msgicon:hover {
background-position: 0 -272px;
}
*/
.messagelist tr td.status span.deleted,
.messagelist tr td.status span.deleted:hover,
.messagelist tr td.subject span.deleted {
background-position: -22px -1096px;
}
.messagelist tr td.status span.status,
.messagelist tr td.status span.unread,
.messagelist tr td.subject span.unread,
.messagelist tr td.status span.unread:hover {
background-position: 0 -1016px;
}
.messagelist thead tr td.status span.status {
background-position: -23px -1017px;
}
.messagelist tr td div.collapsed {
background-position: 0 -1137px;
cursor: pointer;
}
.messagelist tr td div.expanded {
background-position: 0 -1157px;
cursor: pointer;
}
.messagelist tr td.threads div.listmenu {
background-position: 0 -976px;
cursor: pointer;
width: 26px;
}
.messagelist thead tr td.subject,
.messagelist tbody tr td.subject {
width: 99%;
white-space: nowrap;
}
.messagelist tbody tr td.subject a {
cursor: default;
vertical-align: middle; /* #1487091 */
}
/* thread parent message with unread children */
.messagelist tbody tr.unroot td.subject a {
text-decoration: underline;
}
/**** tree indicators ****/
.messagelist tbody tr td span.branch div {
display: inline-block;
}
.messagelist tbody tr td span.branch div.tree {
width: 15px;
}
#listoptions ul.proplist {
min-width: 16em;
}
/**** message view ****/
#mailpreviewframe {
display: none;
position: absolute;
top: 0;
left: 0;
width: 100%;
bottom: 0px;
}
#messagecontframe {
border: 0;
border-radius: 4px 4px 0 0;
}
#messagecontent {
position: absolute;
top: 110px;
left: 0;
width: 100%;
bottom: 1px;
overflow: auto;
-webkit-overflow-scrolling: touch;
}
#messageheader,
#composeheaders {
position: relative;
padding: 3px 0;
background: #f9f9f9;
background: -moz-linear-gradient(top, #fff 0%, #f0f0f0 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#fff), color-stop(100%,#f0f0f0));
background: -o-linear-gradient(top, #fff 0%, #f0f0f0 100%);
background: -ms-linear-gradient(top, #fff 0%, #f0f0f0 100%);
background: linear-gradient(top, #fff 0%, #f0f0f0 100%);
border-bottom: 1px solid #dfdfdf;
}
#mailview-right #messageheader {
border-radius: 4px 4px 0 0;
padding-left: 78px;
/* avoid headers eating up all the vertical space */
max-height: 50%;
overflow: auto;
}
h2.subject {
font-size: 15px;
margin: 0 15em 0 0;
padding: 4px 8px 2px 8px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#mailview-right #messageheader h2.subject {
margin-left: -56px;
}
h3.subject {
font-size: 14px;
margin: 0 12em 0 0;
padding: 8px 8px 4px 8px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.headers-table td {
color: #666;
padding: 2px 8px;
}
.headers-table td.header,
.ui-dialog-content.popup span.adr {
font-weight: bold;
}
.headers-table td.header-title {
white-space: nowrap;
}
.headers-table td.header a,
.ui-dialog-content.popup span.adr a {
color: #666;
text-decoration: none;
}
.headers-table td.header a:hover,
.ui-dialog-content.popup span.adr a:hover {
text-decoration: underline;
}
.headers-table td.subject {
color: #333;
font-size: 110%;
font-weight: bold;
}
.headers-table td.header span,
.ui-dialog-content.popup span.adr {
white-space: nowrap;
}
.headers-table td.header a.morelink {
color: #0069a6;
white-space: nowrap;
font-weight: normal;
}
.rcmaddcontact {
position: relative;
top: 1px;
margin-left: 0.5em;
}
.rcmaddcontact imp {
width: 20px;
height: 13px;
}
#preview-allheaders {
display: none;
}
#preview-allheaders td.header-title,
#preview-shortheaders td.header-title {
padding-left: 0;
}
#preview-shortheaders td.header {
padding-right: 18px;
}
.moreheaderstoggle {
display: block;
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 18px;
padding: 0;
outline: none;
background: #f2f2f2;
background: -moz-linear-gradient(left, #fbfbfb 0, #e9e9e9 100%);
background: -webkit-gradient(linear, left top, right top, color-stop(0,#fbfbfb), color-stop(100%,#e9e9e9));
background: -o-linear-gradient(left, #fbfbfb 0, #e9e9e9 100%);
background: -ms-linear-gradient(left, #fbfbfb 0, #e9e9e9 100%);
background: linear-gradient(left, #fbfbfb 0, #e9e9e9 100%);
border-right: 1px solid #dfdfdf;
border-radius: 3px 0 0 0; /* for Opera */
}
.moreheaderstoggle .iconlink {
display: inline-block;
position: absolute;
top: 8px;
left: 0;
width: 18px;
height: 16px;
background: url(images/buttons.png) -27px -242px no-repeat;
}
.moreheaderstoggle.remove .iconlink {
top: auto;
bottom: 5px;
background-position: -5px -242px;
}
#full-headers {
position: relative;
}
div.more-headers {
position: absolute;
top: -12px;
right: 10px;
width: 12px;
height: 10px;
cursor: pointer;
background: url(images/buttons.png) center -1579px no-repeat;
}
div.hide-headers {
background-position: center -1590px;
}
#all-headers {
position: relative;
margin: 4px 10px;
padding: 0;
height: 180px;
border: 1px solid #ccc;
border-radius: 4px;
background: #fdfdfd;
-moz-box-shadow: inset 0 0 1px 1px rgba(0,0,0, 0.1);
-webkit-box-shadow: inset 0 0 1px 1px rgba(0,0,0, 0.1);
-o-box-shadow: inset 0 0 1px 1px rgba(0,0,0, 0.1);
box-shadow: inset 0 0 1px 1px rgba(0,0,0, 0.1);
}
#headers-source {
display: none;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
padding: 3px 6px;
overflow: auto;
text-align: left;
color: #333;
}
#messageheader.previewheader #all-headers {
margin-left: 0;
}
#messageheader.previewheader {
position: relative;
height: auto;
min-height: 52px;
padding: 0 0 3px 72px;
}
#messageheader.previewheader h3.subject {
padding: 8px 8px 2px 0;
}
#messageheader.previewheader #contactphoto {
display: block;
position: absolute;
top: 11px;
left: 30px;
width: 32px;
height: 32px;
overflow: hidden;
background: url(images/contactpic_32px.png) center center no-repeat #fff;
border-radius: 3px;
}
#messageheader.previewheader #contactphoto img {
width: 32px;
height: auto;
border-radius: 3px;
}
#messageheader .message-headers {
min-height: 60px;
}
#messageheader #contactphoto {
display: block;
position: absolute;
top: 34px;
left: 30px;
width: 48px;
height: 48px;
overflow: hidden;
border-radius: 4px;
border: 1px solid #e6e6e6;
background: url(images/contactpic_48px.png) center center no-repeat #fff;
}
#messageheader #contactphoto img {
width: 48px;
height: auto;
border-radius: 4px;
}
#messageheader #countcontrols,
#messageheader #formatcontrols {
position: absolute;
top: 8px;
right: 8px;
text-align: right;
white-space: nowrap;
}
#messageheader #formatcontrols {
top: 38px;
right: 8px;
}
#messageheader .pagenav .countdisplay {
min-width: 0;
padding-right: 0.5em;
white-space: nowrap;
}
#messagecontent .leftcol,
#messagepreview .leftcol {
margin-right: 252px;
overflow-x: auto;
}
#messagecontent .rightcol,
#messagepreview .rightcol {
float: right;
/*
position: absolute;
top: 10px;
right: 10px;
height: 90%;
*/
width: 230px;
margin: 8px;
min-height: 200px;
background: #f0f0f0;
padding: 8px;
border-radius: 4px;
}
#messagebody {
position: relative;
margin: 8px;
}
#message-objects div,
#messagebody span.part-notice {
margin: 8px;
}
#message-objects div.notice {
display: block;
color: #960;
border: 1px solid #ffdf0e;
background-color: #fef893;
background-position: 5px -83px;
padding: 6px 12px 6px 30px;
white-space: normal;
}
#message-objects div a.button,
#messagebody span.part-notice a.button {
margin-left: 10px;
margin-top: -1px;
}
div.message-part,
div.message-htmlpart,
div.message-partheaders {
padding: 10px 2px;
border-top: 1px solid #ccc;
}
#messagebody div:first-child {
padding-top: 0;
border-top: 0;
}
-div.message-part pre,
-div.message-htmlpart pre,
div.message-part div.pre {
margin: 0;
padding: 0;
font-family: monospace;
font-size: 12px;
- white-space: -moz-pre-wrap !important;
- white-space: pre-wrap !important;
- white-space: pre;
}
div.message-part span.sig {
color: #666;
}
div.message-part blockquote {
color: blue;
border-left: 2px solid blue;
border-right: 2px solid blue;
background-color: #F6F6F6;
- margin: 2px 0 2px 0;
- padding: 1px 8px 1px 10px;
+ margin: 2px 0;
+ padding: 0 0.4em;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
div.message-part blockquote blockquote {
color: green;
border-left: 2px solid green;
border-right: 2px solid green;
}
div.message-part blockquote blockquote blockquote {
color: #900;
border-left: 2px solid #b00;
border-right: 2px solid #b00;
}
div.message-partheaders {
margin-top: 8px;
padding: 8px 0;
}
div.message-partheaders .headers-table {
width: 100%;
}
div.message-partheaders .headers-table td.header-title {
width: auto;
padding-left: 0;
}
div.message-partheaders .headers-table td.header {
width: 88%;
}
#messagebody > hr {
color: #fff;
background: #fff;
border: 0;
border-bottom: 2px solid #f0f0f0;
}
#messagebody fieldset.image-attachment {
border: 0;
border-top: 1px solid #ccc;
margin-top: 1em;
}
#messagebody fieldset.image-attachment p > img {
max-width: 80%;
}
#messagebody legend.image-filename {
color: #999;
font-size: 0.9em;
margin: 0 1em;
}
#messagebody p.image-attachment {
position: relative;
padding: 1em;
border-top: 1px solid #ccc;
}
#messagebody p.image-attachment a.image-link {
float: left;
display: block;
margin-right: 2em;
min-width: 160px;
min-height: 60px;
text-align: center;
}
#messagebody p.image-attachment .image-filename {
display: block;
font-weight: bold;
line-height: 1.6em;
}
#messagebody p.image-attachment .image-filesize {
padding-right: 1em;
}
#messagebody p.image-attachment .attachment-links a {
margin-right: 0.6em;
}
#messagepartcontainer {
position: absolute;
top: 0;
left: 232px;
right: 0;
bottom: 0;
}
#messagepartframe {
border: 0;
width: 100%;
height: 100%;
}
#messagepartheader {
position: absolute;
top: 0;
left: 0;
width: 220px;
bottom: 0;
}
#messagepartheader table {
table-layout: fixed;
overflow: hidden;
}
#messagepartheader table td {
text-overflow: ellipsis;
overflow: hidden;
}
#messagepartheader table td.title {
width: 60px;
padding-right: 0;
}
/*** message composition ***/
#composeview-left {
position: absolute;
top: 0;
left: 0;
width: 250px;
bottom: 0;
}
#composeview-right {
position: absolute;
top: 0;
left: 262px;
right: 0;
bottom: 0;
}
#compose-contacts {
position: absolute;
top: 0;
left: 0;
width: 100%;
bottom: 0;
}
#compose-contacts #directorylist {
border-bottom: 4px solid #c7e3ef;
}
#compose-contacts .scroller {
top: 65px;
border-top: 1px solid #fff;
}
#contacts-table {
table-layout: fixed;
}
#contacts-table td {
width: 100%;
}
#contacts-table td span {
display: block;
}
#contacts-table td span.email {
display: inline;
color: #69939e;
font-style: italic;
margin-left: 0.5em;
}
#compose-contacts li a, #contacts-table td {
background: url(images/listicons.png) -100px 0 no-repeat;
overflow: hidden;
padding-left: 36px;
text-overflow: ellipsis;
}
#contacts-table td.contactgroup a {
color: #376572;
text-decoration: none;
}
#contacts-table td.contactgroup a span {
display: inline-block;
font-size: 16px;
font-weight: bold;
line-height: 11px;
margin-left: 0.3em;
}
#contacts-table tr:first-child td {
border-top: 0;
}
#compose-contacts li.addressbook a {
background-position: 6px -766px;
}
#compose-contacts li.addressbook.selected a {
background-position: 6px -791px;
}
#contacts-table td.contactgroup {
background-position: 6px -1555px;
}
#contacts-table tr.unfocused td.contactgroup,
#contacts-table tr.selected td.contactgroup {
background-position: 6px -1579px;
}
#contacts-table td.contact {
background-position: 6px -1603px;
}
#contacts-table tr.unfocused td.contact,
#contacts-table tr.selected td.contact {
background-position: 6px -1627px;
}
#compose-content {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0px;
overflow: hidden;
}
#composeheaders {
border-radius: 4px 4px 0 0;
padding-left: 19px;
}
#composebuttons {
position: absolute;
top: 6px;
right: 6px;
width: auto;
white-space: nowrap;
z-index: 100;
}
#composebuttons a.button.extwin {
padding: 2px 3px;
}
.compose-headers {
width: 99%;
margin-bottom: 2px;
}
.compose-headers td {
padding: 2px 4px;
}
.compose-headers td.title {
width: 11%;
white-space: nowrap;
padding-left: 6px;
}
.compose-headers td.title label {
float: left;
}
.compose-headers td.title a.iconbutton {
float: right;
position: relative;
top: -2px;
width: 15px;
}
.compose-headers td.editfield {
width: 90%;
padding-left: 4px;
}
.compose-headers td.editfield a.iconlink {
margin-left: 0.5em;
}
.compose-headers td.formlinks {
padding: 0 4px;
}
.compose-headers td.top {
vertical-align: top;
padding-top: 10px;
}
.compose-headers td textarea,
.compose-headers td input {
width: 100%;
resize: none;
font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
font-size: 11px;
}
#compose-cc, #compose-bcc, #compose-replyto, #compose-followupto {
display: none;
}
#composeoptions {
display: none;
padding: 2px 0 0 8px;
white-space: normal;
border-top: 1px solid #dfdfdf;
box-shadow: inset 0 1px 0 0 #fff;
-o-box-shadow: inset 0 1px 0 0 #fff;
-webkit-box-shadow: inset 0 1px 0 0 #fff;
-moz-box-shadow: inset 0 1px 0 0 #fff;
}
.composeoption {
color: #666;
padding-right: 22px;
white-space: nowrap;
}
#composeoptions .composeoption {
display: inline-block;
padding: 4px 22px 4px 0;
}
#composeoptions .composeoption:last-child {
padding-right: 4px;
}
.mozilla .composeoption input {
vertical-align: -3px;
}
#composeview-bottom {
position: relative;
width: 100%;
height: 200px;
}
#composebodycontainer {
position: absolute;
top: 0;
left: 0;
right: 260px;
bottom: 0;
border-radius: 0 0 0 4px;
}
#composebodycontainer.buttons {
bottom: 42px;
}
#composebody {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 99%;
border: 0;
border-radius: 0 0 0 4px;
padding: 8px 0 8px 8px;
resize: none;
font-family: monospace;
font-size: 9pt;
outline: none;
box-shadow: inset 0 0 2px 1px rgba(0,0,0, 0.2);
-moz-box-shadow: inset 0 0 2px 1px rgba(0,0,0, 0.2);
-webkit-box-shadow: inset 0 0 2px 1px rgba(0,0,0, 0.2);
-o-box-shadow: inset 0 0 2px 1px rgba(0,0,0, 0.2);
}
#composebody:active,
#composebody:focus {
box-shadow: inset 0 0 3px 2px rgba(71,135,177, 0.9);
-moz-box-shadow: inset 0 0 3px 2px rgba(71,135,177, 0.9);
-webkit-box-shadow: inset 0 0 3px 2px rgba(71,135,177, 0.9);
-o-box-shadow: inset 0 0 3px 2px rgba(71,135,177, 0.9);
}
#compose-attachments {
position: absolute;
right: 0;
top: 1px;
bottom: 0;
width: 240px;
background: #f0f0f0;
border-style: solid;
border-color: #f0f0f0 #f0f0f0 #f0f0f0 #ddd;
border-width: 1px;
padding: 8px;
overflow: auto;
}
#compose-attachments.droptarget {
background-image: url(images/filedrop.png);
background-position: center bottom;
background-repeat: no-repeat;
}
#compose-attachments.droptarget.hover,
#compose-attachments.droptarget.active {
border-color: #019bc6;
box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5);
-moz-box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5);
-webkit-box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5);
-o-box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5);
}
#compose-attachments.droptarget.hover {
background-color: #d9ecf4;
box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
-moz-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
-webkit-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
-o-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
}
#composeview-bottom .formbuttons.floating {
position: absolute;
width: auto;
right: 260px;
z-index: 200;
padding-bottom: 8px;
}
.defaultSkin table.mceLayout,
.defaultSkin table.mceLayout tr.mceLast td {
border: 0 !important;
}
.defaultSkin td.mceToolbar {
border: 0 !important;
}
.defaultSkin table.mceLayout tr.mceFirst td {
background: #f0f0f0;
}
#composebody_toolbargroup {
border-bottom: 1px solid #ddd;
}
#uploadform a.iconlink {
margin-left: 1em;
text-indent: -5000px;
}
#uploadform form div {
margin: 4px 0;
}
diff --git a/tests/Framework/Text2Html.php b/tests/Framework/Text2Html.php
new file mode 100644
index 000000000..8d091d5c9
--- /dev/null
+++ b/tests/Framework/Text2Html.php
@@ -0,0 +1,93 @@
+<?php
+
+/**
+ * Test class to test rcube_text2html class
+ *
+ * @package Tests
+ */
+class Framework_Text2Html extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Data for test_text2html()
+ */
+ function data_text2html()
+ {
+ $options = array(
+ 'begin' => '',
+ 'end' => '',
+ 'break' => '<br>',
+ 'links' => false,
+ 'flowed' => false,
+ 'wrap' => false,
+ 'space' => '_', // replace UTF-8 non-breaking space for simpler testing
+ );
+
+ $data[] = array(" aaaa", "_aaaa", $options);
+ $data[] = array("aaaa aaaa", "aaaa_aaaa", $options);
+ $data[] = array("aaaa aaaa", "aaaa__aaaa", $options);
+ $data[] = array("aaaa aaaa", "aaaa___aaaa", $options);
+ $data[] = array("aaaa\taaaa", "aaaa____aaaa", $options);
+ $data[] = array("aaaa\naaaa", "aaaa<br>aaaa", $options);
+ $data[] = array("aaaa\n aaaa", "aaaa<br>_aaaa", $options);
+ $data[] = array("aaaa\n aaaa", "aaaa<br>__aaaa", $options);
+ $data[] = array("aaaa\n aaaa", "aaaa<br>___aaaa", $options);
+ $data[] = array("\taaaa", "____aaaa", $options);
+ $data[] = array("\naaaa", "<br>aaaa", $options);
+ $data[] = array("\n aaaa", "<br>_aaaa", $options);
+ $data[] = array("\n aaaa", "<br>__aaaa", $options);
+ $data[] = array("\n aaaa", "<br>___aaaa", $options);
+ $data[] = array("aaaa\n\nbbbb", "aaaa<br><br>bbbb", $options);
+ $data[] = array(">aaaa \n>aaaa", "<blockquote>aaaa_<br>aaaa</blockquote>", $options);
+ $data[] = array(">aaaa\n>aaaa", "<blockquote>aaaa<br>aaaa</blockquote>", $options);
+ $data[] = array(">aaaa \n>bbbb\ncccc dddd", "<blockquote>aaaa_<br>bbbb</blockquote>cccc_dddd", $options);
+ $data[] = array("aaaa-bbbb/cccc", "aaaa-&#8288;bbbb/&#8288;cccc", $options);
+
+ $options['flowed'] = true;
+
+ $data[] = array(" aaaa", "aaaa", $options);
+ $data[] = array("aaaa aaaa", "aaaa_aaaa", $options);
+ $data[] = array("aaaa aaaa", "aaaa__aaaa", $options);
+ $data[] = array("aaaa aaaa", "aaaa___aaaa", $options);
+ $data[] = array("aaaa\taaaa", "aaaa____aaaa", $options);
+ $data[] = array("aaaa\naaaa", "aaaa<br>aaaa", $options);
+ $data[] = array("aaaa\n aaaa", "aaaa<br>aaaa", $options);
+ $data[] = array("aaaa\n aaaa", "aaaa<br>_aaaa", $options);
+ $data[] = array("aaaa\n aaaa", "aaaa<br>__aaaa", $options);
+ $data[] = array("\taaaa", "____aaaa", $options);
+ $data[] = array("\naaaa", "<br>aaaa", $options);
+ $data[] = array("\n aaaa", "<br>aaaa", $options);
+ $data[] = array("\n aaaa", "<br>_aaaa", $options);
+ $data[] = array("\n aaaa", "<br>__aaaa", $options);
+ $data[] = array("aaaa\n\nbbbb", "aaaa<br><br>bbbb", $options);
+ $data[] = array(">aaaa \n>aaaa", "<blockquote>aaaa aaaa</blockquote>", $options);
+ $data[] = array(">aaaa\n>aaaa", "<blockquote>aaaa<br>aaaa</blockquote>", $options);
+ $data[] = array(">aaaa \n>bbbb\ncccc dddd", "<blockquote>aaaa bbbb</blockquote>cccc_dddd", $options);
+
+ $options['flowed'] = false;
+ $options['wrap'] = true;
+
+ $data[] = array(">>aaaa bbbb\n>>\n>>>\n>cccc\n\ndddd eeee",
+ "<blockquote><blockquote>aaaa bbbb<br><br><blockquote><br></blockquote></blockquote>cccc</blockquote><br>dddd eeee", $options);
+ $data[] = array("\n>>aaaa\n\ndddd",
+ "<br><blockquote><blockquote>aaaa</blockquote></blockquote><br>dddd", $options);
+ $data[] = array("aaaa\n>bbbb\n>cccc\n\ndddd\n>>test",
+ "aaaa<blockquote>bbbb<br>cccc</blockquote><br>dddd<blockquote><blockquote>test</blockquote></blockquote>", $options);
+
+ return $data;
+ }
+
+ /**
+ * Test text to html conversion
+ *
+ * @dataProvider data_text2html
+ */
+ function test_text2html($input, $output, $options)
+ {
+ $t2h = new rcube_text2html($input, false, $options);
+
+ $html = $t2h->get_html();
+
+ $this->assertEquals($output, $html);
+ }
+}
diff --git a/tests/phpunit.xml b/tests/phpunit.xml
index cee3434c1..f3df53521 100644
--- a/tests/phpunit.xml
+++ b/tests/phpunit.xml
@@ -1,85 +1,86 @@
<phpunit backupGlobals="false"
bootstrap="bootstrap.php"
colors="true">
<testsuites>
<testsuite name="All Tests">
<file>Framework/BaseReplacer.php</file>
<file>Framework/Bootstrap.php</file>
<file>Framework/Browser.php</file>
<file>Framework/Cache.php</file>
<file>Framework/CacheShared.php</file>
<file>Framework/Charset.php</file>
<file>Framework/Contacts.php</file>
<file>Framework/ContentFilter.php</file>
<file>Framework/Csv2vcard.php</file>
<file>Framework/DB.php</file>
<file>Framework/DBMssql.php</file>
<file>Framework/DBMysql.php</file>
<file>Framework/DBPgsql.php</file>
<file>Framework/DBSqlite.php</file>
<file>Framework/DBSqlsrv.php</file>
<file>Framework/Enriched.php</file>
<file>Framework/Html.php</file>
<file>Framework/Html2text.php</file>
<file>Framework/Imap.php</file>
<file>Framework/ImapCache.php</file>
<file>Framework/ImapGeneric.php</file>
<file>Framework/Image.php</file>
<file>Framework/LdapGeneric.php</file>
<file>Framework/MessageHeader.php</file>
<file>Framework/MessagePart.php</file>
<file>Framework/Mime.php</file>
<file>Framework/Rcube.php</file>
<file>Framework/ResultIndex.php</file>
<file>Framework/ResultSet.php</file>
<file>Framework/ResultThread.php</file>
<file>Framework/Smtp.php</file>
<file>Framework/SpellcheckAtd.php</file>
<file>Framework/SpellcheckEnchant.php</file>
<file>Framework/SpellcheckGoogie.php</file>
<file>Framework/SpellcheckPspell.php</file>
<file>Framework/Spellchecker.php</file>
<file>Framework/StringReplacer.php</file>
+ <file>Framework/Text2Html.php</file>
<file>Framework/User.php</file>
<file>Framework/Utils.php</file>
<file>Framework/VCard.php</file>
<file>Framework/Washtml.php</file>
<file>MailFunc.php</file>
</testsuite>
<testsuite name="Plugins Tests">
<file>./../plugins/acl/tests/Acl.php</file>
<file>./../plugins/additional_message_headers/tests/AdditionalMessageHeaders.php</file>
<file>./../plugins/archive/tests/Archive.php</file>
<file>./../plugins/autologon/tests/Autologon.php</file>
<file>./../plugins/database_attachments/tests/DatabaseAttachments.php</file>
<file>./../plugins/debug_logger/tests/DebugLogger.php</file>
<file>./../plugins/emoticons/tests/Emoticons.php</file>
<file>./../plugins/enigma/tests/Enigma.php</file>
<file>./../plugins/example_addressbook/tests/ExampleAddressbook.php</file>
<file>./../plugins/filesystem_attachments/tests/FilesystemAttachments.php</file>
<file>./../plugins/help/tests/Help.php</file>
<file>./../plugins/hide_blockquote/tests/HideBlockquote.php</file>
<file>./../plugins/http_authentication/tests/HttpAuthentication.php</file>
<file>./../plugins/identity_select/tests/IdentitySelect.php</file>
<file>./../plugins/jqueryui/tests/Jqueryui.php</file>
<file>./../plugins/legacy_browser/tests/LegacyBrowser.php</file>
<file>./../plugins/managesieve/tests/Managesieve.php</file>
<file>./../plugins/managesieve/tests/Parser.php</file>
<file>./../plugins/managesieve/tests/Tokenizer.php</file>
<file>./../plugins/markasjunk/tests/Markasjunk.php</file>
<file>./../plugins/new_user_dialog/tests/NewUserDialog.php</file>
<file>./../plugins/new_user_identity/tests/NewUserIdentity.php</file>
<file>./../plugins/newmail_notifier/tests/NewmailNotifier.php</file>
<file>./../plugins/password/tests/Password.php</file>
<file>./../plugins/redundant_attachments/tests/RedundantAttachments.php</file>
<file>./../plugins/show_additional_headers/tests/ShowAdditionalHeaders.php</file>
<file>./../plugins/squirrelmail_usercopy/tests/Squirrelmail_usercopy.php</file>
<file>./../plugins/subscriptions_option/tests/SubscriptionsOption.php</file>
<file>./../plugins/userinfo/tests/Userinfo.php</file>
<file>./../plugins/vcard_attachments/tests/VcardAttachments.php</file>
<file>./../plugins/virtuser_file/tests/VirtuserFile.php</file>
<file>./../plugins/virtuser_query/tests/VirtuserQuery.php</file>
<file>./../plugins/zipdownload/tests/Zipdownload.php</file>
</testsuite>
</testsuites>
</phpunit>

File Metadata

Mime Type
text/x-diff
Expires
Mon, Aug 17, 1:31 PM (23 h, 1 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1261457
Default Alt Text
(594 KB)

Event Timeline