diff --git a/CHANGELOG b/CHANGELOG index eb01fe426..75afbe6de 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ CHANGELOG Roundcube Webmail =========================== +- Archive: Don't reload messages list when it's not needed (#5225) +- Archive: Add option to automatically mark archived messages as \Seen (#5142) - Improve randomness of password salts and random hashes (#5266) - Password/cPanel: Add support for hash authentication and reseller accounts (#5252) - Support host-specific imap_conn_options/smtp_conn_options/managesieve_conn_options (#5136) diff --git a/plugins/archive/archive.js b/plugins/archive/archive.js index ec8a7ced2..23611beea 100644 --- a/plugins/archive/archive.js +++ b/plugins/archive/archive.js @@ -1,11 +1,11 @@ /** * Archive plugin script - * @version 2.4 + * @version 3.0 * * @licstart The following is the entire license notice for the * JavaScript code in this file. * - * Copyright (c) 2012-2014, The Roundcube Dev Team + * Copyright (c) 2012-2016, 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 @@ -18,29 +18,30 @@ function rcmail_archive(prop) { - if (!rcmail.env.uid && (!rcmail.message_list || !rcmail.message_list.get_selection().length)) + if (rcmail_is_archive()) return; - if (!rcmail_is_archive()) { - if (!rcmail.env.archive_type) { - // simply move to archive folder (if no partition type is set) - rcmail.command('move', rcmail.env.archive_folder); - } - else { - // let the server sort the messages to the according subfolders - rcmail.http_post('plugin.move2archive', rcmail.selection_post_data()); - } - } + var post_data = rcmail.selection_post_data(); + + // exit if selection is empty + if (!post_data._uid) + return; + + rcmail.show_contentframe(false); + + // Disable message command buttons until a message is selected + rcmail.enable_command(rcmail.env.message_commands, false); + rcmail.enable_command('plugin.archive', false); + + // let the server sort the messages to the according subfolders + rcmail.with_selected_messages('move', post_data, null, 'plugin.move2archive'); } function rcmail_is_archive() { // check if current folder is an archive folder or one of its children - if (rcmail.env.mailbox == rcmail.env.archive_folder - || rcmail.env.mailbox.startsWith(rcmail.env.archive_folder + rcmail.env.delimiter) - ) { - return true; - } + return rcmail.env.mailbox == rcmail.env.archive_folder + || rcmail.env.mailbox.startsWith(rcmail.env.archive_folder + rcmail.env.delimiter); } // callback for app-onload event @@ -59,11 +60,5 @@ if (window.rcmail) { var li; if (rcmail.env.archive_folder && (li = rcmail.get_folder_li(rcmail.env.archive_folder, '', true))) $(li).addClass('archive'); - - // callback for server response - rcmail.addEventListener('plugin.move2archive_response', function(result) { - if (result.update) - rcmail.command('list'); // refresh list - }); }); } diff --git a/plugins/archive/archive.php b/plugins/archive/archive.php index b50ef4247..7bfb985e4 100644 --- a/plugins/archive/archive.php +++ b/plugins/archive/archive.php @@ -6,7 +6,7 @@ * Plugin that adds a new button to the mailbox toolbar * to move messages to a (user selectable) archive folder. * - * @version 2.4 + * @version 3.0 * @license GNU GPLv3+ * @author Andre Rodier, Thomas Bruederli, Aleksander Machniak */ @@ -58,12 +58,8 @@ class archive extends rcube_plugin $this->register_action('plugin.move2archive', array($this, 'move_messages')); } else if ($rcmail->task == 'settings') { - $dont_override = $rcmail->config->get('dont_override', array()); - - if (!in_array('archive_mbox', $dont_override)) { - $this->add_hook('preferences_list', array($this, 'prefs_table')); - $this->add_hook('preferences_save', array($this, 'save_prefs')); - } + $this->add_hook('preferences_list', array($this, 'prefs_table')); + $this->add_hook('preferences_save', array($this, 'save_prefs')); } } @@ -116,114 +112,125 @@ class archive extends rcube_plugin */ function move_messages() { + $rcmail = rcmail::get_instance(); + + // only process ajax requests + if (!$rcmail->output->ajax_call) { + return; + } + $this->add_texts('localization'); - $rcmail = rcmail::get_instance(); $storage = $rcmail->get_storage(); $delimiter = $storage->get_hierarchy_delimiter(); - $archive_folder = $rcmail->config->get('archive_mbox'); + $read_on_move = (bool) $rcmail->config->get('read_on_archive'); $archive_type = $rcmail->config->get('archive_type', ''); + $archive_folder = $rcmail->config->get('archive_mbox'); + $archive_prefix = $archive_folder . $delimiter; $current_mbox = rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_POST); $search_request = rcube_utils::get_input_value('_search', rcube_utils::INPUT_GPC); $uids = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST); - $result = array('reload' => false, 'update' => false, 'errors' => array()); - $folders = array(); - - if ($uids == '*') { - $index = $storage->index(null, rcmail_sort_column(), rcmail_sort_order()); - $messageset = array($current_mbox => $index->get()); - } - else { - $messageset = rcmail::get_uids(); + // count messages before changing anything + if ($_POST['_from'] != 'show') { + $threading = (bool) $storage->get_threading(); + $old_count = $storage->count(null, $threading ? 'THREADS' : 'ALL'); + $old_pages = ceil($old_count / $storage->get_pagesize()); } - foreach ($messageset as $mbox => $uids) { - $storage->set_folder(($current_mbox = $mbox)); - - foreach ($uids as $uid) { - if (!$archive_folder || !($message = $rcmail->storage->get_message($uid))) { - continue; - } + $count = 0; + + // this way response handler for 'move' action will be executed + $rcmail->action = 'move'; + $this->result = array( + 'reload' => false, + 'error' => false, + 'sources' => array(), + 'destinations' => array(), + ); + + foreach (rcmail::get_uids(null, null, $multifolder) as $mbox => $uids) { + if (!$archive_folder || strpos($mbox, $archive_prefix) === 0) { + $count = count($uids); + continue; + } + else if (!$archive_type || $archive_type == 'folder') { + $folder = $archive_folder; - $subfolder = null; - switch ($archive_type) { - case 'year': - $subfolder = $rcmail->format_date($message->timestamp, 'Y'); - break; + if ($archive_type == 'folder') { + // compose full folder path + $folder .= $delimiter . $mbox; - case 'month': - $subfolder = $rcmail->format_date($message->timestamp, 'Y') . $delimiter . $rcmail->format_date($message->timestamp, 'm'); - break; + // create archive subfolder if it doesn't yet exist + $this->subfolder_worker($folder); + } - case 'folder': - $subfolder = $current_mbox; - break; + $count += $this->move_messages_worker($uids, $mbox, $folder, $read_on_move); + } + else { + if ($uids == '*') { + $index = $storage->index(null, rcmail_sort_column(), rcmail_sort_order()); + $uids = $index->get(); + } - case 'sender': - $from = $message->get('from'); - if (preg_match('/[\b<](.+@.+)[\b>]/i', $from, $m)) { - $subfolder = $m[1]; - } - else { - $subfolder = $this->gettext('unkownsender'); + $messages = $storage->fetch_headers($mbox, $uids); + $execute = array(); + + foreach ($messages as $message) { + $subfolder = null; + switch ($archive_type) { + case 'year': + $subfolder = $rcmail->format_date($message->timestamp, 'Y'); + break; + + case 'month': + $subfolder = $rcmail->format_date($message->timestamp, 'Y') + . $delimiter . $rcmail->format_date($message->timestamp, 'm'); + break; + + case 'sender': + $from = $message->get('from'); + preg_match('/[\b<](.+@.+)[\b>]/i', $from, $m); + $subfolder = $m[1] ?: $this->gettext('unkownsender'); + + // replace reserved characters in folder name + $repl = $delimiter == '-' ? '_' : '-'; + $replacements[$delimiter] = $repl; + $replacements['.'] = $repl; // some IMAP server do not allow . characters + $subfolder = strtr($subfolder, $replacements); + break; } - // replace reserved characters in folder name - $repl = $delimiter == '-' ? '_' : '-'; - $replacements[$delimiter] = $repl; - $replacements['.'] = $repl; // some IMAP server do not allow . characters - $subfolder = strtr($subfolder, $replacements); - break; + // compose full folder path + $folder = $archive_folder . ($subfolder ? $delimiter . $subfolder : ''); - default: - $subfolder = ''; - break; + $execute[$folder][] = $message->uid; } - // compose full folder path - $folder = $archive_folder . ($subfolder ? $delimiter . $subfolder : ''); - - // create archive subfolder if it doesn't yet exist - // we'll create all folders in the path - if (!in_array($folder, $folders)) { - if (empty($list)) { - $list = $storage->list_folders('', $archive_folder . '*', 'mail', null, true); - } - $path = explode($delimiter, $folder); - - for ($i=0; $icreate_folder($_folder, true)) { - $result['reload'] = true; - $list[] = $_folder; - } - } - } - - $folders[] = $folder; - } + foreach ($execute as $folder => $uids) { + // create archive subfolder if it doesn't yet exist + $this->subfolder_worker($folder); - // move message to target folder - if ($storage->move_message(array($uid), $folder)) { - $result['update'] = true; + $count += $this->move_messages_worker($uids, $mbox, $folder, $read_on_move); } - else { - $result['errors'][] = $uid; - } - } // end for + } } - // send response - if ($result['errors']) { + if ($this->result['error']) { + if ($_POST['_from'] != 'show') { + $rcmail->output->command('list_mailbox'); + } + $rcmail->output->show_message($this->gettext('archiveerror'), 'warning'); + $rcmail->output->send(); } - if ($result['reload']) { - $rcmail->output->show_message($this->gettext('archivedreload'), 'confirmation'); + + if (!empty($_POST['_refresh'])) { + // FIXME: send updated message rows instead of reloading the entire list + $rcmail->output->command('refresh_list'); } - else if ($result['update']) { - $rcmail->output->show_message($this->gettext('archived'), 'confirmation'); + else { + $addrows = true; } // refresh saved search set after moving some messages @@ -231,16 +238,134 @@ class archive extends rcube_plugin $_SESSION['search'] = $rcmail->storage->refresh_search(); } - if ($_POST['_from'] == 'show' && !empty($result['update'])) { + if ($_POST['_from'] == 'show') { if ($next = rcube_utils::get_input_value('_next_uid', rcube_utils::INPUT_GPC)) { $rcmail->output->command('show_message', $next); } else { $rcmail->output->command('command', 'list'); } + + $rcmail->output->send(); + } + + $mbox = $storage->get_folder(); + $msg_count = $storage->count(null, $threading ? 'THREADS' : 'ALL'); + $exists = $storage->count($mbox, 'EXISTS', true); + $page_size = $storage->get_pagesize(); + $page = $storage->get_page(); + $pages = ceil($msg_count / $page_size); + $nextpage_count = $old_count - $page_size * $page; + $remaining = $msg_count - $page_size * ($page - 1); + + // jump back one page (user removed the whole last page) + if ($page > 1 && $remaining == 0) { + $page -= 1; + $storage->set_page($page); + $_SESSION['page'] = $page; + $jump_back = true; + } + + // update message count display + $rcmail->output->set_env('messagecount', $msg_count); + $rcmail->output->set_env('current_page', $page); + $rcmail->output->set_env('pagecount', $pages); + $rcmail->output->set_env('exists', $exists); + + // update mailboxlist + $unseen_count = $msg_count ? $storage->count($mbox, 'UNSEEN') : 0; + $old_unseen = rcmail_get_unseen_count($mbox); + $quota_root = $multifolder ? $this->result['sources'][0] : 'INBOX'; + + if ($old_unseen != $unseen_count) { + $rcmail->output->command('set_unread_count', $mbox, $unseen_count, ($mbox == 'INBOX')); + rcmail_set_unseen_count($mbox, $unseen_count); + } + + $rcmail->output->command('set_quota', $rcmail->quota_content(null, $quota_root)); + $rcmail->output->command('set_rowcount', rcmail_get_messagecount_text($msg_count), $mbox); + + if ($threading) { + $count = rcube_utils::get_input_value('_count', rcube_utils::INPUT_POST); + } + + // add new rows from next page (if any) + if ($addrows && $count && $uids != '*' && ($jump_back || $nextpage_count > 0)) { + $a_headers = $storage->list_messages($mbox, null, + rcmail_sort_column(), rcmail_sort_order(), $jump_back ? null : $count); + + rcmail_js_message_list($a_headers, false); + } + + if ($this->result['reload']) { + $rcmail->output->show_message($this->gettext('archivedreload'), 'confirmation'); } else { - $rcmail->output->command('plugin.move2archive_response', $result); + $rcmail->output->show_message($this->gettext('archived'), 'confirmation'); + + if (!$read_on_move) { + foreach ($this->result['destinations'] as $folder) { + rcmail_send_unread_count($folder, true); + } + } + } + + // send response + $rcmail->output->send(); + } + + /** + * Move messages from one folder to another and mark as read if needed + */ + private function move_messages_worker($uids, $from_mbox, $to_mbox, $read_on_move) + { + $storage = rcmail::get_instance()->get_storage(); + + if ($read_on_move) { + // don't flush cache (4th argument) + $storage->set_flag($uids, 'SEEN', $from_mbox, true); + } + + // move message to target folder + if ($storage->move_message($uids, $to_mbox, $from_mbox)) { + if (!in_array($from_mbox, $this->result['sources'])) { + $this->result['sources'][] = $from_mbox; + } + if (!in_array($to_mbox, $this->result['destinations'])) { + $this->result['destinations'][] = $to_mbox; + } + + return count($uids); + } + + $this->result['error'] = true; + } + + /** + * Create archive subfolder if it doesn't yet exist + */ + private function subfolder_worker($folder) + { + $storage = rcmail::get_instance()->get_storage(); + $delimiter = $storage->get_hierarchy_delimiter(); + + if ($this->folders === null) { + $this->folders = $storage->list_folders('', $archive_folder . '*', 'mail', null, true); + } + + if (!in_array($folder, $this->folders)) { + $path = explode($delimiter, $folder); + + // we'll create all folders in the path + for ($i=0; $ifolders)) { + if ($storage->create_folder($_folder, true)) { + $this->result['reload'] = true; + $this->folders[] = $_folder; + } + } + } } } @@ -251,12 +376,14 @@ class archive extends rcube_plugin { global $CURR_SECTION; - if ($args['section'] == 'folders') { - $this->add_texts('localization'); + $this->add_texts('localization'); - $rcmail = rcmail::get_instance(); - $mbox = $rcmail->config->get('archive_mbox'); - $type = $rcmail->config->get('archive_type'); + $rcmail = rcmail::get_instance(); + $dont_override = $rcmail->config->get('dont_override', array()); + + if ($args['section'] == 'folders' && !in_array('archive_mbox', $dont_override)) { + $mbox = $rcmail->config->get('archive_mbox'); + $type = $rcmail->config->get('archive_type'); // load folders list when needed if ($CURR_SECTION) { @@ -295,6 +422,13 @@ class archive extends rcube_plugin ) ); } + else if ($args['section'] == 'server' && !in_array('read_on_archive', $dont_override)) { + $chbox = new html_checkbox(array('name' => '_read_on_archive', 'id' => 'ff_read_on_archive', 'value' => 1)); + $args['blocks']['main']['options']['read_on_archive'] = array( + 'title' => $this->gettext('readonarchive'), + 'content' => $chbox->show($rcmail->config->get('read_on_archive') ? 1 : 0) + ); + } return $args; } @@ -304,9 +438,15 @@ class archive extends rcube_plugin */ function save_prefs($args) { - if ($args['section'] == 'folders') { + $rcmail = rcmail::get_instance(); + $dont_override = $rcmail->config->get('dont_override', array()); + + if ($args['section'] == 'folders' && !in_array('archive_mbox', $dont_override)) { $args['prefs']['archive_type'] = rcube_utils::get_input_value('_archive_type', rcube_utils::INPUT_POST); } + else if ($args['section'] == 'server' && !in_array('read_on_archive', $dont_override)) { + $args['prefs']['read_on_archive'] = (bool) rcube_utils::get_input_value('_read_on_archive', rcube_utils::INPUT_POST); + } return $args; } diff --git a/plugins/archive/composer.json b/plugins/archive/composer.json index a79940b58..b223d2eaa 100644 --- a/plugins/archive/composer.json +++ b/plugins/archive/composer.json @@ -3,7 +3,7 @@ "type": "roundcube-plugin", "description": "This adds a button to move the selected messages to an archive folder. The folder (and the optional structure of subfolders) can be selected in the settings panel.", "license": "GPLv3+", - "version": "2.4", + "version": "3.0", "authors": [ { "name": "Thomas Bruederli", diff --git a/plugins/archive/localization/en_US.inc b/plugins/archive/localization/en_US.inc index d3714c118..51bdb7b13 100644 --- a/plugins/archive/localization/en_US.inc +++ b/plugins/archive/localization/en_US.inc @@ -5,7 +5,7 @@ | plugins/archive/localization/.inc | | | | Localization file of the Roundcube Webmail Archive plugin | - | Copyright (C) 2013, The Roundcube Dev Team | + | Copyright (C) 2016, The Roundcube Dev Team | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -30,5 +30,6 @@ $labels['archivetypemonth'] = 'Month (e.g. Archive/2012/06)'; $labels['archivetypefolder'] = 'Original folder'; $labels['archivetypesender'] = 'Sender email'; $labels['unkownsender'] = 'unknown'; +$labels['readonarchive'] = 'Mark the message as read on archive'; ?> diff --git a/program/js/app.js b/program/js/app.js index 238ac279a..d7aa2afe5 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -3035,7 +3035,7 @@ function rcube_webmail() // 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); + this.with_selected_messages('move', post_data, lock); }; // delete selected messages from the current mailbox @@ -3078,12 +3078,11 @@ function rcube_webmail() return; this.show_contentframe(false); - this._with_selected_messages('delete', post_data); + 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) + this.with_selected_messages = function(action, post_data, lock, http_action) { var count = 0, msg, remove = (action == 'delete' || !this.is_multifolder_listing()); @@ -3130,7 +3129,7 @@ function rcube_webmail() } // send request to server - this.http_post(action, post_data, lock); + this.http_post(http_action || action, post_data, lock); }; // build post data for message delete/move/copy/flag requests diff --git a/program/steps/mail/move_del.inc b/program/steps/mail/move_del.inc index 88aff2c2b..5b70b97d4 100644 --- a/program/steps/mail/move_del.inc +++ b/program/steps/mail/move_del.inc @@ -20,15 +20,19 @@ */ // only process ajax requests -if (!$OUTPUT->ajax_call) - return; +if (!$OUTPUT->ajax_call) { + return; +} // count messages before changing anything $threading = (bool) $RCMAIL->storage->get_threading(); -$old_count = $RCMAIL->storage->count(NULL, $threading ? 'THREADS' : 'ALL'); -$old_pages = ceil($old_count / $RCMAIL->storage->get_pagesize()); -$sources = array(); $trash = $RCMAIL->config->get('trash_mbox'); +$sources = array(); + +if ($_POST['_from'] != 'show') { + $old_count = $RCMAIL->storage->count(NULL, $threading ? 'THREADS' : 'ALL'); + $old_pages = ceil($old_count / $RCMAIL->storage->get_pagesize()); +} // move messages if ($RCMAIL->action == 'move' && !empty($_POST['_uid']) && strlen($_POST['_target_mbox'])) { @@ -60,7 +64,7 @@ if ($RCMAIL->action == 'move' && !empty($_POST['_uid']) && strlen($_POST['_targe } if (!empty($_POST['_refresh'])) { - // FIXME: send updated message rows instead of releading the entire list + // FIXME: send updated message rows instead of reloading the entire list $OUTPUT->command('refresh_list'); } else {