diff options
Diffstat (limited to 'program')
31 files changed, 563 insertions, 263 deletions
diff --git a/program/.htaccess b/program/.htaccess deleted file mode 100644 index be9e7e25a..000000000 --- a/program/.htaccess +++ /dev/null @@ -1,4 +0,0 @@ -<IfModule mod_rewrite.c> -RewriteEngine On -RewriteRule !^(js|resources) - [F] -</IfModule> diff --git a/program/include/rcmail.php b/program/include/rcmail.php index 4b3f13760..8abe87303 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -413,6 +413,9 @@ class rcmail extends rcube $this->output->set_env('comm_path', $this->comm_path); $this->output->set_charset(RCUBE_CHARSET); + if ($this->user && $this->user->ID) + $this->output->set_env('user_id', $this->user->get_hash()); + // add some basic labels to client $this->output->add_label('loading', 'servererror', 'requesttimedout', 'refreshing'); diff --git a/program/include/rcmail_output_html.php b/program/include/rcmail_output_html.php index 8a960673d..6db826e2e 100644 --- a/program/include/rcmail_output_html.php +++ b/program/include/rcmail_output_html.php @@ -310,12 +310,14 @@ class rcmail_output_html extends rcmail_output */ public function reset($all = false) { + $framed = $this->framed; $env = $all ? null : array_intersect_key($this->env, array('extwin'=>1, 'framed'=>1)); parent::reset(); // let some env variables survive $this->env = $this->js_env = $env; + $this->framed = $framed || $this->env['framed']; $this->js_labels = array(); $this->js_commands = array(); $this->script_files = array(); @@ -323,6 +325,11 @@ class rcmail_output_html extends rcmail_output $this->header = ''; $this->footer = ''; $this->body = ''; + + // load defaults + if (!$all) { + $this->__construct(); + } } /** @@ -1203,8 +1210,6 @@ class rcmail_output_html extends rcmail_output */ public function include_script($file, $position='head') { - static $sa_files = array(); - if (!preg_match('|^https?://|i', $file) && $file[0] != '/') { $file = $this->scripts_path . $file; if ($fs = @filemtime($file)) { @@ -1212,17 +1217,13 @@ class rcmail_output_html extends rcmail_output } } - if (in_array($file, $sa_files)) { - return; - } - - $sa_files[] = $file; - if (!is_array($this->script_files[$position])) { $this->script_files[$position] = array(); } - $this->script_files[$position][] = $file; + if (!in_array($file, $this->script_files[$position])) { + $this->script_files[$position][] = $file; + } } /** diff --git a/program/js/app.js b/program/js/app.js index 1f75e219c..636db82d4 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -34,7 +34,7 @@ function rcube_webmail() // webmail client settings this.dblclick_time = 500; - this.message_time = 4000; + this.message_time = 5000; this.identifier_expr = new RegExp('[^0-9a-z\-_]', 'gi'); // environment defaults @@ -187,6 +187,8 @@ function rcube_webmail() if (this.env.permaurl) this.enable_command('permaurl', 'extwin', true); + this.local_storage_prefix = 'roundcube.' + (this.env.user_id || 'anonymous') + '.'; + switch (this.task) { case 'mail': @@ -218,12 +220,8 @@ function rcube_webmail() // load messages this.command('list'); - } - if (this.gui_objects.qsearchbox) { - if (this.env.search_text != null) - this.gui_objects.qsearchbox.value = this.env.search_text; - $(this.gui_objects.qsearchbox).focusin(function() { rcmail.message_list && rcmail.message_list.blur(); }); + $(this.gui_objects.qsearchbox).val(this.env.search_text).focusin(function() { rcmail.message_list.blur(); }); } this.set_button_titles(); @@ -285,10 +283,10 @@ function rcube_webmail() 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); }) - } + // 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 p.doc_mouse_up(e); }; @@ -351,7 +349,7 @@ function rcube_webmail() 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', 'advanced-search', true); + 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, @@ -369,8 +367,8 @@ function rcube_webmail() this.gui_objects.contactslist.parentNode.onmousedown = function(e){ return p.click_on_list(e); }; document.onmouseup = function(e){ return p.doc_mouse_up(e); }; - if (this.gui_objects.qsearchbox) - $(this.gui_objects.qsearchbox).focusin(function() { rcmail.contact_list.blur(); }); + + $(this.gui_objects.qsearchbox).focusin(function() { rcmail.contact_list.blur(); }); this.update_group_commands(); this.command('list'); @@ -394,9 +392,6 @@ function rcube_webmail() this.init_contact_form(); } - if (this.gui_objects.qsearchbox) - this.enable_command('search', 'reset-search', true); - break; case 'settings': @@ -408,9 +403,6 @@ function rcube_webmail() else if (this.env.action == 'edit-identity' || this.env.action == 'add-identity') { this.enable_command('save', 'edit', 'toggle-editor', true); this.enable_command('delete', this.env.identities_level < 2); - - if (this.env.action == 'add-identity') - $("input[type='text']").first().select(); } else if (this.env.action == 'folders') { this.enable_command('subscribe', 'unsubscribe', 'create-folder', 'rename-folder', true); @@ -419,7 +411,6 @@ function rcube_webmail() this.enable_command('save', 'folder-size', true); parent.rcmail.env.exists = this.env.messagecount; parent.rcmail.enable_command('purge', this.env.messagecount); - $("input[type='text']").first().select(); } else if (this.env.action == 'responses') { this.enable_command('add', true); @@ -445,7 +436,7 @@ function rcube_webmail() } 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){ + this.responses_list.addEventListener('select', function(list) { var win, id = list.get_single_selection(); p.enable_command('delete', !!id && $.inArray(id, p.env.readonly_responses) < 0); if (id && (win = p.get_frame_window(p.env.contentframe))) { @@ -489,6 +480,11 @@ function rcube_webmail() 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; @@ -584,9 +580,12 @@ function rcube_webmail() } // check input before leaving compose step - if (this.task == 'mail' && this.env.action == 'compose' && $.inArray(command, this.env.compose_commands)<0) { + 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); } // process external commands @@ -621,10 +620,10 @@ function rcube_webmail() break; // commands to switch task + case 'logout': case 'mail': case 'addressbook': case 'settings': - case 'logout': this.switch_task(command); break; @@ -644,6 +643,7 @@ function rcube_webmail() var form = this.gui_objects.messageform, win = this.open_window(''); + 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; @@ -1298,8 +1298,10 @@ function rcube_webmail() return; var url = this.get_task_url(task); - if (task=='mail') + if (task == 'mail') url += '&_mbox=INBOX'; + else if (task == 'logout') + this.clear_compose_data(); this.redirect(url); }; @@ -1706,7 +1708,7 @@ function rcube_webmail() url += (url.match(/\?/) ? '&' : '?') + '_extwin=1'; if (this.env.standard_windows) - extwin = window.open(url, wname); + var extwin = window.open(url, wname); else { var win = this.is_framed() ? parent.window : window, page = $(win), @@ -1727,7 +1729,7 @@ function rcube_webmail() } // focus window, delayed to bring to front - window.setTimeout(function() { extwin.focus(); }, 10); + window.setTimeout(function() { extwin && extwin.focus(); }, 10); return extwin; }; @@ -3123,6 +3125,60 @@ function rcube_webmail() } } + // 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; + } + // 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() == '') @@ -3560,6 +3616,8 @@ function rcube_webmail() this.env.draft_id = id; $("input[name='_draft_saveid']").val(id); + + this.remove_compose_data(this.env.compose_id); }; this.auto_save_start = function() @@ -3567,6 +3625,20 @@ function rcube_webmail() if (this.env.draft_autosave) this.save_timer = setTimeout(function(){ 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; }; @@ -3595,6 +3667,115 @@ function rcube_webmail() 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; + } + + $('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 index = this.local_storage_get_item('compose.index', []); + + for (var 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) @@ -4027,7 +4208,7 @@ function rcube_webmail() case 38: // arrow up case 40: // arrow down if (!this.ksearch_visible()) - break; + return; var dir = key==38 ? 1 : 0; @@ -4062,8 +4243,7 @@ function rcube_webmail() case 37: // left case 39: // right - if (mod != SHIFT_KEY) - return; + return; } // start timer @@ -4126,8 +4306,10 @@ function rcube_webmail() if (this.ksearch_input.setSelectionRange) this.ksearch_input.setSelectionRange(cpos, cpos); - if (trigger) + if (trigger) { this.triggerEvent('autocomplete_insert', { field:this.ksearch_input, insert:insert }); + this.compose_type_activity++; + } }; this.replace_group_recipients = function(id, recipients) @@ -4136,6 +4318,7 @@ function rcube_webmail() 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++; } }; @@ -4820,8 +5003,6 @@ function rcube_webmail() $('input.datepicker').datepicker(); } - $("input[type='text']:visible").first().focus(); - // Submit search form on Enter if (this.env.action == 'search') $(this.gui_objects.editform).append($('<input type="submit">').hide()) @@ -5438,7 +5619,10 @@ function rcube_webmail() this.init_subscription_list = function() { - var p = this; + var p = this, 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){ p.subscription_select(o); }); @@ -5449,6 +5633,7 @@ function rcube_webmail() row.obj.onmouseout = function() { p.unfocus_subscription(row.id); }; }; this.subscription_list.init(); + $('#mailboxroot') .mouseover(function(){ p.focus_subscription(this.id); }) .mouseout(function(){ p.unfocus_subscription(this.id); }) @@ -5456,9 +5641,7 @@ function rcube_webmail() this.focus_subscription = function(id) { - var row, folder, - delim = RegExp.escape(this.env.delimiter), - reg = RegExp('['+delim+']?[^'+delim+']+$'); + var row, folder; if (this.drag_active && this.env.mailbox && (row = document.getElementById(id))) if (this.env.subscriptionrows[id] && @@ -5466,8 +5649,8 @@ function rcube_webmail() ) { if (this.check_droptarget(folder) && !this.env.subscriptionrows[this.get_folder_row_id(this.env.mailbox)][2] && - (folder != this.env.mailbox.replace(reg, '')) && - (!folder.startsWith(this.env.mailbox + this.env.delimiter)) + folder != this.env.mailbox.replace(this.last_sub_rx, '') && + !folder.startsWith(this.env.mailbox + this.env.delimiter) ) { this.env.dstfolder = folder; $(row).addClass('droptarget'); @@ -5480,7 +5663,8 @@ function rcube_webmail() var row = $('#'+id); this.env.dstfolder = null; - if (this.env.subscriptionrows[id] && row[0]) + + if (this.env.subscriptionrows[id] && row.length) row.removeClass('droptarget'); else $(this.subscription_list.frame).removeClass('droptarget'); @@ -5506,21 +5690,20 @@ function rcube_webmail() this.subscription_move_folder = function(list) { - var delim = RegExp.escape(this.env.delimiter), - reg = RegExp('['+delim+']?[^'+delim+']+$'); - - if (this.env.mailbox && this.env.dstfolder !== null && (this.env.dstfolder != this.env.mailbox) && - (this.env.dstfolder != this.env.mailbox.replace(reg, '')) + 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, '') ) { - reg = new RegExp('[^'+delim+']*['+delim+']', 'g'); - var basename = this.env.mailbox.replace(reg, ''), - newname = this.env.dstfolder === '' ? basename : this.env.dstfolder+this.env.delimiter+basename; + 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)); }; @@ -5549,7 +5732,7 @@ function rcube_webmail() if (!this.gui_objects.subscriptionlist) return false; - var row, n, i, tmp, tmp_name, folders, rowid, list = [], slist = [], + 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()); @@ -5564,8 +5747,7 @@ function rcube_webmail() row = $(refrow).clone(true); // set ID, reset css class - row.attr('id', id); - row.attr('class', class_name); + row.attr({id: id, 'class': class_name}); // set folder name row.find('td:first').html(display_name); @@ -5577,10 +5759,21 @@ function rcube_webmail() // add to folder/row-ID map this.env.subscriptionrows[id] = [name, display_name, 0]; - // sort folders, to find a place where to insert the row - folders = []; - $.each(this.env.subscriptionrows, function(k,v){ folders.push(v) }); - folders.sort(function(a,b){ return a[0] < b[0] ? -1 : (a[0] > b[0] ? 1 : 0) }); + // 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) { + 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 @@ -5642,7 +5835,7 @@ function rcube_webmail() tbody = this.gui_objects.subscriptionlist.tBodies[0], folders = this.env.subscriptionrows, id = this.get_folder_row_id(oldfolder), - regex = new RegExp('^'+RegExp.escape(oldfolder)), + prefix_len = oldfolder.length, subscribed = $('input[name="_subscribed[]"]', $('#'+id)).prop('checked'), // find subfolders of renamed folder list = this.get_subfolders(oldfolder); @@ -5667,7 +5860,7 @@ function rcube_webmail() row.after(tmprow); row = tmprow; // update folder index - name = name.replace(regex, newfolder); + 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 @@ -5912,59 +6105,36 @@ function rcube_webmail() // mouse over button this.button_over = function(command, id) { - var n, button, obj, a_buttons = this.buttons[command], - len = a_buttons ? a_buttons.length : 0; - - for (n=0; n<len; n++) { - button = a_buttons[n]; - if (button.id == id && button.status == 'act') { - obj = document.getElementById(button.id); - if (obj && button.over) { - if (button.type == 'image') - obj.src = button.over; - else - obj.className = button.over; - } - } - } + this.button_event(command, id, 'over'); }; // mouse down on button this.button_sel = function(command, id) { - var n, button, obj, a_buttons = this.buttons[command], - len = a_buttons ? a_buttons.length : 0; - - for (n=0; n<len; n++) { - button = a_buttons[n]; - if (button.id == id && button.status == 'act') { - obj = document.getElementById(button.id); - if (obj && button.sel) { - if (button.type == 'image') - obj.src = button.sel; - else - obj.className = button.sel; - } - this.buttons_sel[id] = command; - } - } + 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') { - obj = document.getElementById(button.id); - if (obj && button.act) { - if (button.type == 'image') - obj.src = button.act; - else - obj.className = button.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; } } } @@ -6039,7 +6209,7 @@ function rcube_webmail() this.triggerEvent('message', { message:msg, type:type, timeout:timeout, object:obj }); if (timeout > 0) - setTimeout(function() { ref.hide_message(id, type == 'loading'); }, timeout); + setTimeout(function() { ref.hide_message(id, type != 'loading'); }, timeout); return id; }; @@ -6152,8 +6322,8 @@ function rcube_webmail() // 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)); + 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 @@ -6163,14 +6333,10 @@ function rcube_webmail() this.treelist.select(name); } else if (this.gui_objects.folderlist) { - var current_li, target_li; - - if ((current_li = $('li.selected', this.gui_objects.folderlist))) { - current_li.removeClass('selected').addClass('unfocused'); - } - if ((target_li = this.get_folder_li(name, prefix, encode))) { - $(target_li).removeClass('unfocused').addClass('selected'); - } + $('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 }); @@ -6199,8 +6365,6 @@ function rcube_webmail() name = this.html_identifier(name, encode); return document.getElementById(prefix+name); } - - return null; }; // for reordering column array (Konqueror workaround) @@ -6744,6 +6908,20 @@ function rcube_webmail() 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) { @@ -7265,7 +7443,28 @@ function rcube_webmail() this.set_cookie = function(name, value, expires) { setCookie(name, value, expires, this.env.cookie_path, this.env.cookie_domain, this.env.cookie_secure); - } + }; + + // wrapper for localStorage.getItem(key) + this.local_storage_get_item = function(key, deflt, encrypted) + { + // TODO: add encryption + var item = localStorage.getItem(this.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.local_storage_prefix + key, JSON.stringify(data)); + }; + + // wrapper for localStorage.removeItem(key) + this.local_storage_remove_item = function(key) + { + return localStorage.removeItem(this.local_storage_prefix + key); + }; } // end object rcube_webmail diff --git a/program/js/common.js b/program/js/common.js index 02934f734..14091463f 100644 --- a/program/js/common.js +++ b/program/js/common.js @@ -37,47 +37,44 @@ function roundcube_browser() this.vendver = n.vendorSub ? parseFloat(n.vendorSub) : 0; this.product = n.product ? n.product : ''; this.platform = String(n.platform).toLowerCase(); - this.lang = (n.language) ? n.language.substring(0,2) : - (n.browserLanguage) ? n.browserLanguage.substring(0,2) : - (n.systemLanguage) ? n.systemLanguage.substring(0,2) : 'en'; + this.lang = n.language ? n.language.substring(0,2) : + n.browserLanguage ? n.browserLanguage.substring(0,2) : + n.systemLanguage ? n.systemLanguage.substring(0,2) : 'en'; - this.win = (this.platform.indexOf('win') >= 0); - this.mac = (this.platform.indexOf('mac') >= 0); - this.linux = (this.platform.indexOf('linux') >= 0); - this.unix = (this.platform.indexOf('unix') >= 0); + this.win = this.platform.indexOf('win') >= 0; + this.mac = this.platform.indexOf('mac') >= 0; + this.linux = this.platform.indexOf('linux') >= 0; + this.unix = this.platform.indexOf('unix') >= 0; this.dom = document.getElementById ? true : false; - this.dom2 = (document.addEventListener && document.removeEventListener); - - this.ie = (document.all && !window.opera); - this.ie4 = (this.ie && !this.dom); - this.ie5 = (this.dom && this.appver.indexOf('MSIE 5')>0); - this.ie8 = (this.dom && this.appver.indexOf('MSIE 8')>0); - this.ie9 = (this.dom && this.appver.indexOf('MSIE 9')>0); - this.ie7 = (this.dom && this.appver.indexOf('MSIE 7')>0); - this.ie6 = (this.dom && !this.ie8 && !this.ie7 && this.appver.indexOf('MSIE 6')>0); - - this.ns = ((this.ver < 5 && this.name == 'Netscape') || (this.ver >= 5 && this.vendor.indexOf('Netscape') >= 0)); - this.chrome = (this.agent_lc.indexOf('chrome') > 0); - this.safari = (!this.chrome && (this.agent_lc.indexOf('safari') > 0 || this.agent_lc.indexOf('applewebkit') > 0)); - this.konq = (this.agent_lc.indexOf('konqueror') > 0); - this.mz = (this.dom && !this.ie && !this.ns && !this.chrome && !this.safari && !this.konq && this.agent.indexOf('Mozilla') >= 0); - this.iphone = (this.safari && (this.agent_lc.indexOf('iphone') > 0 || this.agent_lc.indexOf('ipod') > 0)); - this.ipad = (this.safari && this.agent_lc.indexOf('ipad') > 0); - this.opera = window.opera ? true : false; - - if (this.opera && window.RegExp) - this.vendver = (/opera(\s|\/)([0-9\.]+)/.test(this.agent_lc)) ? parseFloat(RegExp.$2) : -1; - else if (this.chrome && window.RegExp) - this.vendver = (/chrome\/([0-9\.]+)/.test(this.agent_lc)) ? parseFloat(RegExp.$1) : 0; - else if (!this.vendver && this.safari) - this.vendver = (/(safari|applewebkit)\/([0-9]+)/.test(this.agent_lc)) ? parseInt(RegExp.$2) : 0; - else if ((!this.vendver && this.mz) || this.agent.indexOf('Camino')>0) - this.vendver = (/rv:([0-9\.]+)/.test(this.agent)) ? parseFloat(RegExp.$1) : 0; - else if (this.ie && window.RegExp) - this.vendver = (/msie\s+([0-9\.]+)/.test(this.agent_lc)) ? parseFloat(RegExp.$1) : 0; - else if (this.konq && window.RegExp) - this.vendver = (/khtml\/([0-9\.]+)/.test(this.agent_lc)) ? parseFloat(RegExp.$1) : 0; + this.dom2 = document.addEventListener && document.removeEventListener; + + this.ie = (document.all && !window.opera) || (this.win && this.agent_lc.indexOf('trident/') > 0); + + if (this.ie) { + this.ie6 = this.appver.indexOf('MSIE 6') > 0; + this.ie7 = this.appver.indexOf('MSIE 7') > 0; + this.ie8 = this.appver.indexOf('MSIE 8') > 0; + this.ie9 = this.appver.indexOf('MSIE 9') > 0; + } + else { + this.chrome = this.agent_lc.indexOf('chrome') > 0; + this.safari = !this.chrome && (this.agent_lc.indexOf('safari') > 0 || this.agent_lc.indexOf('applewebkit') > 0); + this.konq = this.agent_lc.indexOf('konqueror') > 0; + this.mz = this.dom && !this.chrome && !this.safari && !this.konq && this.agent.indexOf('Mozilla') >= 0; + this.iphone = this.safari && (this.agent_lc.indexOf('iphone') > 0 || this.agent_lc.indexOf('ipod') > 0); + this.ipad = this.safari && this.agent_lc.indexOf('ipad') > 0; + this.opera = window.opera ? true : false; + } + + if (!this.vendver) { + // common version strings + this.vendver = /(khtml|chrome|safari|applewebkit|opera|msie)(\s|\/)([0-9\.]+)/.test(this.agent_lc) ? parseFloat(RegExp.$3) : 0; + + // any other (Mozilla, Camino, IE>=11) + if (!this.vendver) + this.vendver = /rv:([0-9\.]+)/.test(this.agent) ? parseFloat(RegExp.$1) : 0; + } // get real language out of safari's user agent if (this.safari && (/;\s+([a-z]{2})-[a-z]{2}\)/.test(this.agent_lc))) @@ -86,11 +83,6 @@ function roundcube_browser() this.tablet = /ipad|android|xoom|sch-i800|playbook|tablet|kindle/i.test(this.agent_lc); this.mobile = /iphone|ipod|blackberry|iemobile|opera mini|opera mobi|mobile/i.test(this.agent_lc); this.touch = this.mobile || this.tablet; - this.dhtml = ((this.ie4 && this.win) || this.ie5 || this.ie6 || this.ns4 || this.mz); - this.vml = (this.win && this.ie && this.dom && !this.opera); - this.pngalpha = (this.mz || (this.opera && this.vendver >= 6) || (this.ie && this.mac && this.vendver >= 5) || - (this.ie && this.win && this.vendver >= 5.5) || this.safari); - this.opacity = (this.mz || (this.ie && this.vendver >= 5.5 && !this.opera) || (this.safari && this.vendver >= 100)); this.cookies = n.cookieEnabled; // test for XMLHTTP support diff --git a/program/js/editor.js b/program/js/editor.js index 020971d6e..df3d41240 100644 --- a/program/js/editor.js +++ b/program/js/editor.js @@ -61,6 +61,9 @@ function rcmail_editor_init(config) if (!active) rcmail.spellcheck_state(); }); + ed.onKeyPress.add(function(ed, e) { + rcmail.compose_type_activity++; + }); } } diff --git a/program/js/list.js b/program/js/list.js index e1d57745c..4ae6c0c66 100644 --- a/program/js/list.js +++ b/program/js/list.js @@ -795,8 +795,10 @@ select_row: function(id, mod_key, with_mouse) break; case CONTROL_KEY: - if (with_mouse) + if (with_mouse) { + this.shift_start = id; this.highlight_row(id, true); + } break; case CONTROL_SHIFT_KEY: @@ -893,7 +895,7 @@ select_children: function(uid) for (i=0; i<len; i++) if (!this.in_selection(children[i])) - this.select_row(children[i], CONTROL_KEY); + this.select_row(children[i], CONTROL_KEY, true); }, @@ -1133,13 +1135,15 @@ key_press: function(e) // Stop propagation so that the browser doesn't scroll rcube_event.cancel(e); return this.use_arrow_key(keyCode, mod_key); + case 32: rcube_event.cancel(e); return this.select_row(this.last_selected, mod_key, true); + case 37: // Left arrow key case 39: // Right arrow key case 107: // Plus sign on a numeric keypad - case 109: // Minus sign on a numeric keypad + case 109: // Minus sign on a numeric keypad // Stop propagation rcube_event.cancel(e); var ret = this.use_plusminus_key(keyCode, mod_key); @@ -1148,20 +1152,26 @@ key_press: function(e) this.triggerEvent('keypress'); this.modkey = 0; return ret; + case 36: // Home this.select_first(mod_key); return rcube_event.cancel(e); + case 35: // End this.select_last(mod_key); return rcube_event.cancel(e); + case 27: if (this.drag_active) return this.drag_mouse_up(e); + if (this.col_drag_active) { this.selected_column = null; return this.column_drag_mouse_up(e); } + return rcube_event.cancel(e); + default: this.key_pressed = keyCode; this.modkey = mod_key; diff --git a/program/lib/Roundcube/rcube.php b/program/lib/Roundcube/rcube.php index 399f84fd8..e0fa22c3c 100644 --- a/program/lib/Roundcube/rcube.php +++ b/program/lib/Roundcube/rcube.php @@ -1537,6 +1537,10 @@ class rcube !empty($response) ? join('; ', $response) : '')); } } + else { + // allow plugins to catch sending errors with the same parameters as in 'message_before_send' + $this->plugins->exec_hook('message_send_error', $plugin + array('error' => $error)); + } if (is_resource($msg_body)) { fclose($msg_body); diff --git a/program/lib/Roundcube/rcube_browser.php b/program/lib/Roundcube/rcube_browser.php index 34128291b..e53e31200 100644 --- a/program/lib/Roundcube/rcube_browser.php +++ b/program/lib/Roundcube/rcube_browser.php @@ -28,32 +28,24 @@ class rcube_browser { $HTTP_USER_AGENT = strtolower($_SERVER['HTTP_USER_AGENT']); - $this->ver = 0; - $this->win = strpos($HTTP_USER_AGENT, 'win') != false; - $this->mac = strpos($HTTP_USER_AGENT, 'mac') != false; + $this->ver = 0; + $this->win = strpos($HTTP_USER_AGENT, 'win') != false; + $this->mac = strpos($HTTP_USER_AGENT, 'mac') != false; $this->linux = strpos($HTTP_USER_AGENT, 'linux') != false; $this->unix = strpos($HTTP_USER_AGENT, 'unix') != false; - $this->opera = strpos($HTTP_USER_AGENT, 'opera') !== false; - $this->ns4 = strpos($HTTP_USER_AGENT, 'mozilla/4') !== false && strpos($HTTP_USER_AGENT, 'msie') === false; - $this->ns = ($this->ns4 || strpos($HTTP_USER_AGENT, 'netscape') !== false); - $this->ie = !$this->opera && strpos($HTTP_USER_AGENT, 'compatible; msie') !== false; - $this->khtml = strpos($HTTP_USER_AGENT, 'khtml') !== false; - $this->mz = !$this->ie && !$this->khtml && strpos($HTTP_USER_AGENT, 'mozilla/5') !== false; + $this->opera = strpos($HTTP_USER_AGENT, 'opera') !== false; + $this->ns = strpos($HTTP_USER_AGENT, 'netscape') !== false; $this->chrome = strpos($HTTP_USER_AGENT, 'chrome') !== false; - $this->safari = !$this->chrome && ($this->khtml || strpos($HTTP_USER_AGENT, 'safari') !== false); + $this->ie = !$this->opera && (strpos($HTTP_USER_AGENT, 'compatible; msie') !== false || strpos($HTTP_USER_AGENT, 'trident/') !== false); + $this->safari = !$this->chrome && (strpos($HTTP_USER_AGENT, 'safari') !== false || strpos($HTTP_USER_AGENT, 'applewebkit') !== false); + $this->mz = !$this->ie && !$this->safari && !$this->chrome && !$this->ns && strpos($HTTP_USER_AGENT, 'mozilla') !== false; - if ($this->ns || $this->chrome) { - $test = preg_match('/(mozilla|chrome)\/([0-9.]+)/', $HTTP_USER_AGENT, $regs); - $this->ver = $test ? (float)$regs[2] : 0; + if (preg_match('/(chrome|msie|opera|version|khtml)(\s*|\/)([0-9.]+)/', $HTTP_USER_AGENT, $regs)) { + $this->ver = (float) $regs[3]; } - else if ($this->mz) { - $test = preg_match('/rv:([0-9.]+)/', $HTTP_USER_AGENT, $regs); - $this->ver = $test ? (float)$regs[1] : 0; - } - else if ($this->ie || $this->opera) { - $test = preg_match('/(msie|opera) ([0-9.]+)/', $HTTP_USER_AGENT, $regs); - $this->ver = $test ? (float)$regs[2] : 0; + else if (preg_match('/rv:([0-9.]+)/', $HTTP_USER_AGENT, $regs)) { + $this->ver = (float) $regs[1]; } if (preg_match('/ ([a-z]{2})-([a-z]{2})/', $HTTP_USER_AGENT, $regs)) @@ -61,10 +53,10 @@ class rcube_browser else $this->lang = 'en'; - $this->dom = ($this->mz || $this->safari || ($this->ie && $this->ver>=5) || ($this->opera && $this->ver>=7)); + $this->dom = $this->mz || $this->safari || ($this->ie && $this->ver>=5) || ($this->opera && $this->ver>=7); $this->pngalpha = $this->mz || $this->safari || ($this->ie && $this->ver>=5.5) || ($this->ie && $this->ver>=5 && $this->mac) || ($this->opera && $this->ver>=7) ? true : false; - $this->imgdata = !$this->ie; + $this->imgdata = !$this->ie; } } diff --git a/program/lib/Roundcube/rcube_contacts.php b/program/lib/Roundcube/rcube_contacts.php index 5c9e5ab39..2e03352bf 100644 --- a/program/lib/Roundcube/rcube_contacts.php +++ b/program/lib/Roundcube/rcube_contacts.php @@ -350,7 +350,7 @@ class rcube_contacts extends rcube_addressbook if (in_array($col, $this->table_cols)) { switch ($mode) { case 1: // strict - $where[] = '(' . $this->db->quoteIdentifier($col) . ' = ' . $this->db->quote($val) + $where[] = '(' . $this->db->quote_identifier($col) . ' = ' . $this->db->quote($val) . ' OR ' . $this->db->ilike($col, $val . $AS . '%') . ' OR ' . $this->db->ilike($col, '%' . $AS . $val . $AS . '%') . ' OR ' . $this->db->ilike($col, '%' . $AS . $val) . ')'; @@ -390,7 +390,7 @@ class rcube_contacts extends rcube_addressbook } foreach (array_intersect($required, $this->table_cols) as $col) { - $and_where[] = $this->db->quoteIdentifier($col).' <> '.$this->db->quote(''); + $and_where[] = $this->db->quote_identifier($col).' <> '.$this->db->quote(''); } if (!empty($where)) { @@ -630,7 +630,7 @@ class rcube_contacts extends rcube_addressbook $a_insert_cols = $a_insert_values = array(); foreach ($save_data as $col => $value) { - $a_insert_cols[] = $this->db->quoteIdentifier($col); + $a_insert_cols[] = $this->db->quote_identifier($col); $a_insert_values[] = $this->db->quote($value); } @@ -665,7 +665,7 @@ class rcube_contacts extends rcube_addressbook $save_cols = $this->convert_save_data($save_cols, $record); foreach ($save_cols as $col => $value) { - $write_sql[] = sprintf("%s=%s", $this->db->quoteIdentifier($col), $this->db->quote($value)); + $write_sql[] = sprintf("%s=%s", $this->db->quote_identifier($col), $this->db->quote($value)); } if (!empty($write_sql)) { diff --git a/program/lib/Roundcube/rcube_csv2vcard.php b/program/lib/Roundcube/rcube_csv2vcard.php index 00e6d4e20..aa385dce4 100644 --- a/program/lib/Roundcube/rcube_csv2vcard.php +++ b/program/lib/Roundcube/rcube_csv2vcard.php @@ -47,7 +47,7 @@ class rcube_csv2vcard //'business_street_2' => '', //'business_street_3' => '', 'car_phone' => 'phone:car', - 'categories' => 'categories', + 'categories' => 'groups', //'children' => '', 'company' => 'organization', //'company_main_phone' => '', @@ -146,6 +146,9 @@ class rcube_csv2vcard 'work_title' => 'jobtitle', 'work_zip' => 'zipcode:work', 'group' => 'groups', + + // GMail + 'groups' => 'groups', ); /** @@ -427,6 +430,11 @@ class rcube_csv2vcard $contact['birthday'] = $contact['birthday-y'] .'-' .$contact['birthday-m'] . '-' . $contact['birthday-d']; } + // categories/groups separator in vCard is ',' not ';' + if (!empty($contact['groups'])) { + $contact['groups'] = str_replace(';', ',', $contact['groups']); + } + // Empty dates, e.g. "0/0/00", "0000-00-00 00:00:00" foreach (array('birthday', 'anniversary') as $key) { if (!empty($contact[$key])) { diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php index aaba28172..2828f26ee 100644 --- a/program/lib/Roundcube/rcube_db.php +++ b/program/lib/Roundcube/rcube_db.php @@ -392,7 +392,7 @@ class rcube_db */ protected function _query($query, $offset, $numrows, $params) { - $query = trim($query); + $query = ltrim($query); $this->db_connect($this->dsn_select($query), true); @@ -405,27 +405,28 @@ class rcube_db $query = $this->set_limit($query, $numrows, $offset); } - $params = (array) $params; - // Because in Roundcube we mostly use queries that are // executed only once, we will not use prepared queries $pos = 0; $idx = 0; - while ($pos = strpos($query, '?', $pos)) { - if ($query[$pos+1] == '?') { // skip escaped ? - $pos += 2; - } - else { - $val = $this->quote($params[$idx++]); - unset($params[$idx-1]); - $query = substr_replace($query, $val, $pos, 1); - $pos += strlen($val); + if (count($params)) { + while ($pos = strpos($query, '?', $pos)) { + if ($query[$pos+1] == '?') { // skip escaped '?' + $pos += 2; + } + else { + $val = $this->quote($params[$idx++]); + unset($params[$idx-1]); + $query = substr_replace($query, $val, $pos, 1); + $pos += strlen($val); + } } } - // replace escaped ? back to normal - $query = rtrim(strtr($query, array('??' => '?')), ';'); + // replace escaped '?' back to normal, see self::quote() + $query = str_replace('??', '?', $query); + $query = rtrim($query, " \t\n\r\0\x0B;"); $this->debug($query); diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index 9faf1bbc6..fdda1d4b2 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -3848,9 +3848,12 @@ class rcube_imap extends rcube_storage /** * Sort folders first by default folders and then in alphabethical order * - * @param array $a_folders Folders list + * @param array $a_folders Folders list + * @param bool $skip_default Skip default folders handling + * + * @return array Sorted list */ - protected function sort_folder_list($a_folders) + public function sort_folder_list($a_folders, $skip_default = false) { $a_out = $a_defaults = $folders = array(); @@ -3862,7 +3865,7 @@ class rcube_imap extends rcube_storage continue; } - if (($p = array_search($folder, $this->default_folders)) !== false && !$a_defaults[$p]) { + if (!$skip_default && ($p = array_search($folder, $this->default_folders)) !== false && !$a_defaults[$p]) { $a_defaults[$p] = $folder; } else { diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php index 9b662a286..f24ec3ed8 100644 --- a/program/lib/Roundcube/rcube_message.php +++ b/program/lib/Roundcube/rcube_message.php @@ -211,16 +211,19 @@ class rcube_message } $level = explode('.', $part->mime_id); + $depth = count($level); // Check if the part belongs to higher-level's multipart part - // this can be alternative/related/signed/encrypted, but not mixed + // this can be alternative/related/signed/encrypted or mixed while (array_pop($level) !== null) { - if (!count($level)) { + $parent_depth = count($level); + if (!$parent_depth) { return true; } $parent = $this->mime_parts[join('.', $level)]; - if (!preg_match('/^multipart\/(alternative|related|signed|encrypted)$/', $parent->mimetype)) { + if (!preg_match('/^multipart\/(alternative|related|signed|encrypted|mixed)$/', $parent->mimetype) + || ($parent->mimetype == 'multipart/mixed' && $parent_depth < $depth - 1)) { continue 2; } } @@ -529,8 +532,9 @@ class rcube_message $part_mimetype = $mail_part->real_mimetype; list($primary_type, $secondary_type) = explode('/', $part_mimetype); } - else - $part_mimetype = $mail_part->mimetype; + else { + $part_mimetype = $part_orig_mimetype = $mail_part->mimetype; + } // multipart/alternative if ($primary_type == 'multipart') { diff --git a/program/lib/Roundcube/rcube_plugin.php b/program/lib/Roundcube/rcube_plugin.php index 3153a8410..aa6d8376e 100644 --- a/program/lib/Roundcube/rcube_plugin.php +++ b/program/lib/Roundcube/rcube_plugin.php @@ -109,7 +109,7 @@ abstract class rcube_plugin */ public function require_plugin($plugin_name) { - return $this->api->load_plugin($plugin_name); + return $this->api->load_plugin($plugin_name, true); } /** diff --git a/program/lib/Roundcube/rcube_plugin_api.php b/program/lib/Roundcube/rcube_plugin_api.php index 2258f1486..ad012552d 100644 --- a/program/lib/Roundcube/rcube_plugin_api.php +++ b/program/lib/Roundcube/rcube_plugin_api.php @@ -168,10 +168,11 @@ class rcube_plugin_api * Load the specified plugin * * @param string Plugin name + * @param boolean Force loading of the plugin even if it doesn't match the filter * * @return boolean True on success, false if not loaded or failure */ - public function load_plugin($plugin_name) + public function load_plugin($plugin_name, $force = false) { static $plugins_dir; @@ -197,7 +198,7 @@ class rcube_plugin_api // check inheritance... if (is_subclass_of($plugin, 'rcube_plugin')) { // ... task, request type and framed mode - if ((!$plugin->task || preg_match('/^('.$plugin->task.')$/i', $this->task)) + if (($force || !$plugin->task || preg_match('/^('.$plugin->task.')$/i', $this->task)) && (!$plugin->noajax || (is_object($this->output) && $this->output->type == 'html')) && (!$plugin->noframe || empty($_REQUEST['_framed'])) ) { diff --git a/program/lib/Roundcube/rcube_session.php b/program/lib/Roundcube/rcube_session.php index 67072df41..caca262c6 100644 --- a/program/lib/Roundcube/rcube_session.php +++ b/program/lib/Roundcube/rcube_session.php @@ -34,6 +34,7 @@ class rcube_session private $changed; private $time_diff = 0; private $reloaded = false; + private $appends = array(); private $unsets = array(); private $gc_handlers = array(); private $cookiename = 'roundcube_sessauth'; @@ -441,8 +442,19 @@ class rcube_session $node = &$this->get_node(explode('.', $path), $_SESSION); - if ($key !== null) $node[$key] = $value; - else $node[] = $value; + if ($key !== null) { + $node[$key] = $value; + $path .= '.' . $key; + } + else { + $node[] = $value; + } + + $this->appends[] = $path; + + // when overwriting a previously unset variable + if ($this->unsets[$path]) + unset($this->unsets[$path]); } @@ -491,13 +503,40 @@ class rcube_session */ public function reload() { + // collect updated data from previous appends + $merge_data = array(); + foreach ((array)$this->appends as $var) { + $path = explode('.', $var); + $value = $this->get_node($path, $_SESSION); + $k = array_pop($path); + $node = &$this->get_node($path, $merge_data); + $node[$k] = $value; + } + if ($this->key && $this->memcache) $data = $this->mc_read($this->key); else if ($this->key) $data = $this->db_read($this->key); - if ($data) + if ($data) { session_decode($data); + + // apply appends and unsets to reloaded data + $_SESSION = array_merge_recursive($_SESSION, $merge_data); + + foreach ((array)$this->unsets as $var) { + if (isset($_SESSION[$var])) { + unset($_SESSION[$var]); + } + else { + $path = explode('.', $var); + $k = array_pop($path); + $node = &$this->get_node($path, $_SESSION); + unset($node[$k]); + } + } + } + } /** diff --git a/program/lib/Roundcube/rcube_spellchecker.php b/program/lib/Roundcube/rcube_spellchecker.php index 3182ff378..5b77bda02 100644 --- a/program/lib/Roundcube/rcube_spellchecker.php +++ b/program/lib/Roundcube/rcube_spellchecker.php @@ -352,7 +352,7 @@ class rcube_spellchecker "UPDATE ".$this->rc->db->table_name('dictionary') ." SET data = ?" ." WHERE user_id " . ($plugin['userid'] ? "= ".$this->rc->db->quote($plugin['userid']) : "IS NULL") - ." AND " . $this->rc->db->quoteIdentifier('language') . " = ?", + ." AND " . $this->rc->db->quote_identifier('language') . " = ?", implode(' ', $plugin['dictionary']), $plugin['language']); } // don't store empty dict @@ -360,14 +360,14 @@ class rcube_spellchecker $this->rc->db->query( "DELETE FROM " . $this->rc->db->table_name('dictionary') ." WHERE user_id " . ($plugin['userid'] ? "= ".$this->rc->db->quote($plugin['userid']) : "IS NULL") - ." AND " . $this->rc->db->quoteIdentifier('language') . " = ?", + ." AND " . $this->rc->db->quote_identifier('language') . " = ?", $plugin['language']); } } else if (!empty($this->dict)) { $this->rc->db->query( "INSERT INTO " .$this->rc->db->table_name('dictionary') - ." (user_id, " . $this->rc->db->quoteIdentifier('language') . ", data) VALUES (?, ?, ?)", + ." (user_id, " . $this->rc->db->quote_identifier('language') . ", data) VALUES (?, ?, ?)", $plugin['userid'], $plugin['language'], implode(' ', $plugin['dictionary'])); } } @@ -394,7 +394,7 @@ class rcube_spellchecker $sql_result = $this->rc->db->query( "SELECT data FROM ".$this->rc->db->table_name('dictionary') ." WHERE user_id ". ($plugin['userid'] ? "= ".$this->rc->db->quote($plugin['userid']) : "IS NULL") - ." AND " . $this->rc->db->quoteIdentifier('language') . " = ?", + ." AND " . $this->rc->db->quote_identifier('language') . " = ?", $plugin['language']); if ($sql_arr = $this->rc->db->fetch_assoc($sql_result)) { diff --git a/program/lib/Roundcube/rcube_user.php b/program/lib/Roundcube/rcube_user.php index 57f63361d..1d5a90577 100644 --- a/program/lib/Roundcube/rcube_user.php +++ b/program/lib/Roundcube/rcube_user.php @@ -221,6 +221,14 @@ class rcube_user return false; } + /** + * Generate a unique hash to identify this user which + */ + function get_hash() + { + $key = substr($this->rc->config->get('des_key'), 1, 4); + return md5($this->data['user_id'] . $key . $this->data['username'] . '@' . $this->data['mail_host']); + } /** * Get default identity of this user @@ -257,7 +265,7 @@ class rcube_user "SELECT * FROM ".$this->db->table_name('identities'). " WHERE del <> 1 AND user_id = ?". ($sql_add ? " ".$sql_add : ""). - " ORDER BY ".$this->db->quoteIdentifier('standard')." DESC, name ASC, identity_id ASC", + " ORDER BY ".$this->db->quote_identifier('standard')." DESC, name ASC, identity_id ASC", $this->ID); while ($sql_arr = $this->db->fetch_assoc($sql_result)) { @@ -292,7 +300,7 @@ class rcube_user $query_cols = $query_params = array(); foreach ((array)$data as $col => $value) { - $query_cols[] = $this->db->quoteIdentifier($col) . ' = ?'; + $query_cols[] = $this->db->quote_identifier($col) . ' = ?'; $query_params[] = $value; } $query_params[] = $iid; @@ -328,7 +336,7 @@ class rcube_user $insert_cols = $insert_values = array(); foreach ((array)$data as $col => $value) { - $insert_cols[] = $this->db->quoteIdentifier($col); + $insert_cols[] = $this->db->quote_identifier($col); $insert_values[] = $value; } $insert_cols[] = 'user_id'; @@ -393,7 +401,7 @@ class rcube_user if ($this->ID && $iid) { $this->db->query( "UPDATE ".$this->db->table_name('identities'). - " SET ".$this->db->quoteIdentifier('standard')." = '0'". + " SET ".$this->db->quote_identifier('standard')." = '0'". " WHERE user_id = ?". " AND identity_id <> ?". " AND del <> 1", @@ -633,11 +641,11 @@ class rcube_user $result = array(); $sql_result = $this->db->query( - "SELECT search_id AS id, ".$this->db->quoteIdentifier('name') + "SELECT search_id AS id, ".$this->db->quote_identifier('name') ." FROM ".$this->db->table_name('searches') ." WHERE user_id = ?" - ." AND ".$this->db->quoteIdentifier('type')." = ?" - ." ORDER BY ".$this->db->quoteIdentifier('name'), + ." AND ".$this->db->quote_identifier('type')." = ?" + ." ORDER BY ".$this->db->quote_identifier('name'), (int) $this->ID, (int) $type); while ($sql_arr = $this->db->fetch_assoc($sql_result)) { @@ -665,9 +673,9 @@ class rcube_user } $sql_result = $this->db->query( - "SELECT ".$this->db->quoteIdentifier('name') - .", ".$this->db->quoteIdentifier('data') - .", ".$this->db->quoteIdentifier('type') + "SELECT ".$this->db->quote_identifier('name') + .", ".$this->db->quote_identifier('data') + .", ".$this->db->quote_identifier('type') ." FROM ".$this->db->table_name('searches') ." WHERE user_id = ?" ." AND search_id = ?", @@ -722,11 +730,11 @@ class rcube_user $insert_cols[] = 'user_id'; $insert_values[] = (int) $this->ID; - $insert_cols[] = $this->db->quoteIdentifier('type'); + $insert_cols[] = $this->db->quote_identifier('type'); $insert_values[] = (int) $data['type']; - $insert_cols[] = $this->db->quoteIdentifier('name'); + $insert_cols[] = $this->db->quote_identifier('name'); $insert_values[] = $data['name']; - $insert_cols[] = $this->db->quoteIdentifier('data'); + $insert_cols[] = $this->db->quote_identifier('data'); $insert_values[] = serialize($data['data']); $sql = "INSERT INTO ".$this->db->table_name('searches') diff --git a/program/lib/Roundcube/rcube_vcard.php b/program/lib/Roundcube/rcube_vcard.php index 5f74ccbd4..a54ee7e11 100644 --- a/program/lib/Roundcube/rcube_vcard.php +++ b/program/lib/Roundcube/rcube_vcard.php @@ -378,7 +378,7 @@ class rcube_vcard default: if ($field == 'phone' && $this->phonetypemap[$type_uc]) { $type = $this->phonetypemap[$type_uc]; - } + } if (($tag = self::$fieldmap[$field]) && (is_array($value) || strlen($value))) { $index = count($this->raw[$tag]); diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc index 8f221a3a9..92ec82617 100644 --- a/program/localization/en_US/labels.inc +++ b/program/localization/en_US/labels.inc @@ -232,6 +232,9 @@ $labels['checkspelling'] = 'Check spelling'; $labels['resumeediting'] = 'Resume editing'; $labels['revertto'] = 'Revert to'; +$labels['restore'] = 'Restore'; +$labels['restoremessage'] = 'Restore message?'; + $labels['responses'] = 'Responses'; $labels['insertresponse'] = 'Insert a response'; $labels['manageresponses'] = 'Manage responses'; diff --git a/program/localization/en_US/messages.inc b/program/localization/en_US/messages.inc index 033c820d1..a36d9ab62 100644 --- a/program/localization/en_US/messages.inc +++ b/program/localization/en_US/messages.inc @@ -84,6 +84,7 @@ $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.'; diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc index 646d2bcd1..f75b219ff 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -110,9 +110,10 @@ $OUTPUT->add_label('nosubject', 'nosenderwarning', 'norecipientwarning', 'nosubj 'nobodywarning', 'notsentwarning', 'notuploadedwarning', 'savingmessage', 'sendingmessage', 'messagesaved', 'converting', 'editorwarning', 'searching', 'uploading', 'uploadingmany', 'fileuploaderror', 'sendmessage', 'savenewresponse', 'responsename', 'responsetext', 'save', - 'savingresponse'); + 'savingresponse', 'restoresavedcomposedata', 'restoremessage', 'delete', 'restore', 'ignore'); $OUTPUT->set_env('compose_id', $COMPOSE['id']); +$OUTPUT->set_env('session_id', session_id()); $OUTPUT->set_pagetitle(rcube_label('compose')); // add config parameters to client script @@ -239,6 +240,9 @@ if (!empty($msg_uid) && empty($COMPOSE['as_attachment'])) $COMPOSE['reply_msgid'] = '<' . $in_reply_to . '>'; $COMPOSE['references'] = $MESSAGE->headers->references; + + // use message-ID as draft_id, same as in sendmail.inc + $OUTPUT->set_env('draft_id', trim($MESSAGE->headers->get('message-id'), '<>')); } } else { @@ -442,6 +446,11 @@ function rcmail_process_compose_params(&$COMPOSE) } } + // clean HTML message body which can be submitted by URL + if ($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 @@ -642,7 +651,7 @@ function rcmail_prepare_message_body() } else if ($COMPOSE['param']['body']) { $body = $COMPOSE['param']['body']; - $isHtml = false; + $isHtml = (bool) $COMPOSE['param']['html']; } // forward as attachment else if ($compose_mode == RCUBE_COMPOSE_FORWARD && $COMPOSE['as_attachment']) { @@ -827,6 +836,9 @@ function rcmail_compose_body($attrib) $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, RCMAIL_CHARSET); diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index 78a977b82..8164592dd 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -1342,9 +1342,9 @@ function rcmail_html4inline($body, $container_id, $body_id='', &$attributes=null if (preg_match($regexp, $body, $m)) { $attrs = $m[0]; // Get bgcolor, we'll set it as background-color of the message container - if ($m[1] && preg_match('/bgcolor=["\']*([a-z0-9#]+)["\']*/', $attrs, $mb)) { + if ($m[1] && preg_match('/bgcolor=["\']*([a-z0-9#]+)["\']*/i', $attrs, $mb)) { $attributes['background-color'] = $mb[1]; - $attrs = preg_replace('/bgcolor=["\']*([a-z0-9#]+)["\']*/', '', $attrs); + $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)) { diff --git a/program/steps/mail/get.inc b/program/steps/mail/get.inc index e0c4e2911..ae48307e3 100644 --- a/program/steps/mail/get.inc +++ b/program/steps/mail/get.inc @@ -84,17 +84,18 @@ else if ($_GET['_thumb']) { // render thumbnail image if not done yet if (!is_file($cache_file)) { - $fp = fopen(($orig_name = $cache_basename . '.orig.' . $ext), 'w'); - $MESSAGE->get_part_content($part->mime_id, $fp); - fclose($fp); - - $image = new rcube_image($orig_name); - if ($imgtype = $image->resize($thumbnail_size, $cache_file, true)) { - $mimetype = 'image/' . $imgtype; - unlink($orig_name); - } - else { - rename($orig_name, $cache_file); + if ($fp = fopen(($orig_name = $cache_basename . '.orig.' . $ext), 'w')) { + $MESSAGE->get_part_content($part->mime_id, $fp); + fclose($fp); + + $image = new rcube_image($orig_name); + if ($imgtype = $image->resize($thumbnail_size, $cache_file, true)) { + $mimetype = 'image/' . $imgtype; + unlink($orig_name); + } + else { + rename($orig_name, $cache_file); + } } } diff --git a/program/steps/mail/sendmail.inc b/program/steps/mail/sendmail.inc index 52b02ecff..ea5eaaed1 100644 --- a/program/steps/mail/sendmail.inc +++ b/program/steps/mail/sendmail.inc @@ -855,6 +855,7 @@ else { $folders[] = $COMPOSE['mailbox']; rcmail_compose_cleanup($COMPOSE_ID); + $OUTPUT->command('remove_compose_data', $COMPOSE_ID); if ($store_folder && !$saved) $OUTPUT->command('sent_successfully', 'error', rcube_label('errorsavingsent'), $folders); diff --git a/program/steps/settings/edit_folder.inc b/program/steps/settings/edit_folder.inc index f19e2177b..7f2a10ebc 100644 --- a/program/steps/settings/edit_folder.inc +++ b/program/steps/settings/edit_folder.inc @@ -28,11 +28,11 @@ function rcmail_folder_form($attrib) $storage = $RCMAIL->get_storage(); // edited folder name (empty in create-folder mode) - $mbox = trim(get_input_value('_mbox', RCUBE_INPUT_GPC, true)); + $mbox = get_input_value('_mbox', RCUBE_INPUT_GPC, true); $mbox_imap = rcube_charset_convert($mbox, RCMAIL_CHARSET, 'UTF7-IMAP'); // predefined path for new folder - $parent = trim(get_input_value('_path', RCUBE_INPUT_GPC, true)); + $parent = get_input_value('_path', RCUBE_INPUT_GPC, true); $parent_imap = rcube_charset_convert($parent, RCMAIL_CHARSET, 'UTF7-IMAP'); $threading_supported = $storage->get_capability('THREAD'); diff --git a/program/steps/settings/folders.inc b/program/steps/settings/folders.inc index 64af18d62..44482e938 100644 --- a/program/steps/settings/folders.inc +++ b/program/steps/settings/folders.inc @@ -109,7 +109,7 @@ else if ($RCMAIL->action == 'delete-folder') else if ($RCMAIL->action == 'rename-folder') { $name_utf8 = trim(get_input_value('_folder_newname', RCUBE_INPUT_POST, true)); - $oldname_utf8 = trim(get_input_value('_folder_oldname', RCUBE_INPUT_POST, true)); + $oldname_utf8 = get_input_value('_folder_oldname', RCUBE_INPUT_POST, true); if (strlen($name_utf8) && strlen($oldname_utf8)) { $name = rcube_charset_convert($name_utf8, RCMAIL_CHARSET, 'UTF7-IMAP'); @@ -167,7 +167,7 @@ else if ($RCMAIL->action == 'purge') // get mailbox size else if ($RCMAIL->action == 'folder-size') { - $name = trim(get_input_value('_mbox', RCUBE_INPUT_POST, true)); + $name = get_input_value('_mbox', RCUBE_INPUT_POST, true); $size = $STORAGE->folder_size($name); diff --git a/program/steps/settings/func.inc b/program/steps/settings/func.inc index c922aca08..81744d904 100644 --- a/program/steps/settings/func.inc +++ b/program/steps/settings/func.inc @@ -1180,12 +1180,29 @@ function rcmail_user_prefs($current = null) $data = $RCMAIL->plugins->exec_hook('preferences_list', array('section' => $sect['id'], 'blocks' => $blocks, 'current' => $current)); + $advanced_prefs = $config['advanced_prefs']; + // create output - foreach ($data['blocks'] as $block) { + foreach ($data['blocks'] as $key => $block) { if (!empty($block['content']) || !empty($block['options'])) { $found = true; - break; } + // move some options to the 'advanced' block as configured by admin + if ($key != 'advanced') { + foreach ($advanced_prefs as $opt) { + if ($block['options'][$opt]) { + $data['blocks']['advanced']['options'][$opt] = $block['options'][$opt]; + unset($data['blocks'][$key]['options'][$opt]); + } + } + } + } + + // move 'advanced' block to the end of the list + if (!empty($data['blocks']['advanced'])) { + $adv = $data['blocks']['advanced']; + unset($data['blocks']['advanced']); + $data['blocks']['advanced'] = $adv; } if (!$found) diff --git a/program/steps/settings/save_folder.inc b/program/steps/settings/save_folder.inc index 877b0fbbe..efb096d57 100644 --- a/program/steps/settings/save_folder.inc +++ b/program/steps/settings/save_folder.inc @@ -5,7 +5,7 @@ | program/steps/settings/save_folder.inc | | | | This file is part of the Roundcube Webmail client | - | Copyright (C) 2005-2012, The Roundcube Dev Team | + | Copyright (C) 2005-2013, The Roundcube Dev Team | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -26,8 +26,8 @@ $STORAGE = $RCMAIL->get_storage(); $name = trim(get_input_value('_name', RCUBE_INPUT_POST, true)); -$old = trim(get_input_value('_mbox', RCUBE_INPUT_POST, true)); -$path = trim(get_input_value('_parent', RCUBE_INPUT_POST, true)); +$old = get_input_value('_mbox', RCUBE_INPUT_POST, true); +$path = get_input_value('_parent', RCUBE_INPUT_POST, true); $name_imap = rcube_charset_convert($name, RCMAIL_CHARSET, 'UTF7-IMAP'); $old_imap = rcube_charset_convert($old, RCMAIL_CHARSET, 'UTF7-IMAP'); diff --git a/program/steps/utils/error.inc b/program/steps/utils/error.inc index 9fb71c528..a891d42f8 100644 --- a/program/steps/utils/error.inc +++ b/program/steps/utils/error.inc @@ -110,6 +110,7 @@ EOF; if ($rcmail->output && $rcmail->output->template_exists('error')) { $rcmail->output->reset(); + $rcmail->output->set_env('server_error', $ERROR_CODE); $rcmail->output->send('error'); } |