From ed1ceea87829093ddd62bf214e56213bf4d91c2f Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Fri, 18 Apr 2014 10:13:51 +0200 Subject: Add possibility to download messages in mbox format (#1486069) Removed "download folder" option, the same can be done by selecting all messages in a folder Code refactoring --- plugins/zipdownload/README | 3 +- plugins/zipdownload/composer.json | 9 +- plugins/zipdownload/config.inc.php.dist | 3 - plugins/zipdownload/localization/en_US.inc | 9 +- plugins/zipdownload/package.xml | 17 ++- plugins/zipdownload/zipdownload.js | 116 +++++++++++++----- plugins/zipdownload/zipdownload.php | 183 ++++++++++++++++++----------- 7 files changed, 227 insertions(+), 113 deletions(-) diff --git a/plugins/zipdownload/README b/plugins/zipdownload/README index f253d63ee..e343398c3 100644 --- a/plugins/zipdownload/README +++ b/plugins/zipdownload/README @@ -2,8 +2,7 @@ Roundcube Webmail ZipDownload ============================= This plugin adds an option to download all attachments to a message in one zip file, when a message has multiple attachments. The plugin also allows the -download of a selection of messages in 1 zip file and the download of entire -folders. +download of a selection of messages in 1 zip file. Requirements ============ diff --git a/plugins/zipdownload/composer.json b/plugins/zipdownload/composer.json index 7f6e30935..cdfbf9923 100644 --- a/plugins/zipdownload/composer.json +++ b/plugins/zipdownload/composer.json @@ -1,14 +1,19 @@ { "name": "roundcube/zipdownload", "type": "roundcube-plugin", - "description": "Adds an option to download all attachments to a message in one zip file, when a message has multiple attachments. Also allows the download of a selection of messages in one zip file and the download of entire folders.", + "description": "Adds an option to download all attachments to a message in one zip file, when a message has multiple attachments. Also allows the download of a selection of messages in one zip file. Supports mbox and maildir format.", "license": "GNU GPLv3+", - "version": "2.1", + "version": "3.0", "authors": [ { "name": "Thomas Bruederli", "email": "roundcube@gmail.com", "role": "Lead" + }, + { + "name": "Aleksander Machniak", + "email": "alec@alec.pl", + "role": "Lead" } ], "repositories": [ diff --git a/plugins/zipdownload/config.inc.php.dist b/plugins/zipdownload/config.inc.php.dist index 0b2d14b60..171b4aea5 100644 --- a/plugins/zipdownload/config.inc.php.dist +++ b/plugins/zipdownload/config.inc.php.dist @@ -9,9 +9,6 @@ // -1 to prevent downloading of attachments as zip $config['zipdownload_attachments'] = 1; -// Zip entire folders -$config['zipdownload_folder'] = false; - // Zip selection of messages $config['zipdownload_selection'] = false; diff --git a/plugins/zipdownload/localization/en_US.inc b/plugins/zipdownload/localization/en_US.inc index aee8a5e15..91145205e 100644 --- a/plugins/zipdownload/localization/en_US.inc +++ b/plugins/zipdownload/localization/en_US.inc @@ -5,7 +5,7 @@ | plugins/zipdownload/localization/.inc | | | | Localization file of the Roundcube Webmail Zipdownload plugin | - | Copyright (C) 2012-2013, The Roundcube Dev Team | + | Copyright (C) 2012-2014, The Roundcube Dev Team | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -18,6 +18,7 @@ $labels = array(); $labels['downloadall'] = 'Download all attachments'; -$labels['downloadfolder'] = 'Download folder'; - -?> \ No newline at end of file +$labels['download'] = 'Download...'; +$labels['downloadmbox'] = 'Mbox format (.zip)'; +$labels['downloadmaildir'] = 'Maildir format (.zip)'; +$labels['downloademl'] = 'Source (.eml)'; diff --git a/plugins/zipdownload/package.xml b/plugins/zipdownload/package.xml index 3e5722045..7485c5870 100644 --- a/plugins/zipdownload/package.xml +++ b/plugins/zipdownload/package.xml @@ -6,7 +6,8 @@ zipdownload pear.roundcube.net Download multiple attachments or messages in one zip file - Adds an option to download all attachments to a message in one zip file, when a message has multiple attachments. Also allows the download of a selection of messages in one zip file and the download of entire folders. + Adds an option to download all attachments to a message in one zip file, when a message has multiple attachments. + Also allows the download of a selection of messages in one zip file. Supports mbox and maildir formats. Philip Weir JohnDoh @@ -19,11 +20,17 @@ roundcube@gmail.com yes - 2014-04-07 - + + Aleksander Machniak + alec + alec@alec.pl + yes + + 2014-04-18 + - 2.1 - 2.0 + 3.0 + 3.0 stable diff --git a/plugins/zipdownload/zipdownload.js b/plugins/zipdownload/zipdownload.js index 080dcd9e3..0e0249dd3 100644 --- a/plugins/zipdownload/zipdownload.js +++ b/plugins/zipdownload/zipdownload.js @@ -2,32 +2,94 @@ * ZipDownload plugin script */ -function rcmail_zipmessages() { - if (rcmail.message_list && rcmail.message_list.get_selection().length > 1) { - rcmail.goto_url('plugin.zipdownload.zip_messages', '_mbox=' + urlencode(rcmail.env.mailbox) + '&_uid=' + rcmail.message_list.get_selection().join(',')); - } +window.rcmail && rcmail.addEventListener('init', function(evt) { + // register additional actions + rcmail.register_command('download-eml', function() { rcmail_zipdownload('eml'); }); + rcmail.register_command('download-mbox', function() { rcmail_zipdownload('mbox'); }); + rcmail.register_command('download-maildir', function() { rcmail_zipdownload('maildir'); }); + + // commands status + rcmail.message_list.addEventListener('select', function(list) { + var selected = list.get_selection().length; + + rcmail.enable_command('download', selected > 0); + rcmail.enable_command('download-eml', selected == 1); + rcmail.enable_command('download-mbox', 'download-maildir', selected > 1); + }); + + // hook before default download action + rcmail.addEventListener('beforedownload', rcmail_zipdownload_menu); + + // find and modify default download link/button + $.each(rcmail.buttons['download'] || [], function() { + var link = $('#' + this.id), + span = $('span', link); + + if (!span.length) { + span = $(''); + link.html('').append(span); + } + + span.addClass('folder-selector-link').text(rcmail.gettext('zipdownload.download')); + + rcmail.env.download_link = link; + }); + + // hide menu on click out of menu element + var fn = function(e) { + var menu = $('#zipdownload-menu'); + if (e.target != menu.get(0)) + menu.hide(); + }; + $(document.body).on('mouseup', fn); + $('iframe').contents().on('mouseup', fn) + .load(function(e) { try { $(this).contents().on('mouseup', fn); } catch(e) {}; }); +}); + + +function rcmail_zipdownload(mode) +{ + // default .eml download of single message + if (mode == 'eml') { + var uid = rcmail.get_single_uid(); + rcmail.goto_url('viewsource', {_uid: uid, _mbox: rcmail.get_message_mailbox(uid), _save: 1}); + return; + } + + // multi-message download, use hidden form to POST selection + if (rcmail.message_list && rcmail.message_list.get_selection().length > 1) { + var inputs = [], form = $('#zipdownload-form'), + post = rcmail.selection_post_data(); + + post._mode = mode; + post._token = rcmail.env.request_token; + + $.each(post, function(k, v) { + inputs.push($('').attr({type: 'hidden', name: k, value: v})); + }); + + if (!form.length) + form = $('
').attr({ + style: 'display: none', + method: 'POST', + action: '?_task=mail&_action=plugin.zipdownload.messages' + }) + .appendTo('body'); + + form.html('').append(inputs).submit(); + } } -$(document).ready(function() { - if (window.rcmail) { - rcmail.addEventListener('init', function(evt) { - // register command (directly enable in message view mode) - rcmail.register_command('plugin.zipdownload.zip_folder', function() { - rcmail.goto_url('plugin.zipdownload.zip_folder', '_mbox=' + urlencode(rcmail.env.mailbox)); - }, rcmail.env.messagecount > 0); - - if (rcmail.message_list && rcmail.env.zipdownload_selection) { - rcmail.message_list.addEventListener('select', function(list) { - rcmail.enable_command('download', list.get_selection().length > 0); - }); - - // check in contextmenu plugin exists and if so allow multiple message download - if (rcmail.contextmenu_disable_multi) - rcmail.contextmenu_disable_multi.splice($.inArray('#download', rcmail.contextmenu_disable_multi), 1); - } - }); - - rcmail.addEventListener('listupdate', function(props) { rcmail.enable_command('plugin.zipdownload.zip_folder', rcmail.env.messagecount > 0); } ); - rcmail.addEventListener('beforedownload', function(props) { rcmail_zipmessages(); } ); - } -}); \ No newline at end of file +// display download options menu +function rcmail_zipdownload_menu() +{ + // fix menu style and display menu + var z_index = rcmail.env.download_link.parents('.popupmenu').css('z-index'), + menu = $('#zipdownload-menu').css({'max-height': 'none', 'z-index': z_index + 1}).show(); + + // position menu on the screen + rcmail.element_position(menu, rcmail.env.download_link); + + // abort default download action + return false; +} diff --git a/plugins/zipdownload/zipdownload.php b/plugins/zipdownload/zipdownload.php index 029eecfb8..90a314437 100644 --- a/plugins/zipdownload/zipdownload.php +++ b/plugins/zipdownload/zipdownload.php @@ -4,11 +4,13 @@ * ZipDownload * * Plugin to allow the download of all message attachments in one zip file + * and downloading of many messages in one go. * - * @version 2.1 + * @version 3.0 * @requires php_zip extension (including ZipArchive class) * @author Philip Weir * @author Thomas Bruderli + * @author Aleksander Machniak */ class zipdownload extends rcube_plugin { @@ -40,18 +42,11 @@ class zipdownload extends rcube_plugin $this->add_hook('template_object_messageattachments', array($this, 'attachment_ziplink')); } - $this->register_action('plugin.zipdownload.zip_attachments', array($this, 'download_attachments')); - $this->register_action('plugin.zipdownload.zip_messages', array($this, 'download_selection')); - $this->register_action('plugin.zipdownload.zip_folder', array($this, 'download_folder')); + $this->register_action('plugin.zipdownload.attachments', array($this, 'download_attachments')); + $this->register_action('plugin.zipdownload.messages', array($this, 'download_messages')); - if (($selection = $rcmail->config->get('zipdownload_selection')) || $rcmail->config->get('zipdownload_folder')) { - $this->include_script('zipdownload.js'); - $this->api->output->set_env('zipdownload_selection', $selection); - - if ($rcmail->config->get('zipdownload_folder', false) && ($rcmail->action == '' || $rcmail->action == 'show')) { - $zipdownload = $this->api->output->button(array('command' => 'plugin.zipdownload.zip_folder', 'type' => 'link', 'classact' => 'active', 'content' => $this->gettext('downloadfolder'))); - $this->api->add_content(html::tag('li', array('class' => 'separator_above'), $zipdownload), 'mailboxoptions'); - } + if (!$rcmail->action && $rcmail->config->get('zipdownload_selection')) { + $this->download_menu(); } } @@ -65,7 +60,7 @@ class zipdownload extends rcube_plugin // only show the link if there is more than the configured number of attachments if (substr_count($p['content'], ' $rcmail->config->get('zipdownload_attachments', 1)) { $href = $rcmail->url(array( - '_action' => 'plugin.zipdownload.zip_attachments', + '_action' => 'plugin.zipdownload.attachments', '_mbox' => $rcmail->output->env['mailbox'], '_uid' => $rcmail->output->env['uid'], )); @@ -91,6 +86,30 @@ class zipdownload extends rcube_plugin return $p; } + /** + * Adds download options menu to the page + */ + public function download_menu() + { + $this->include_script('zipdownload.js'); + $this->add_label('download'); + + $rcmail = rcmail::get_instance(); + $menu = array(); + $ul_attr = $rcmail->config->get('skin') == 'classic' ? null : array('class' => 'toolbarmenu'); + + foreach (array('eml', 'mbox', 'maildir') as $type) { + $menu[] = html::tag('li', null, $rcmail->output->button(array( + 'command' => "download-$type", + 'label' => "zipdownload.download$type", + 'classact' => 'active', + ))); + } + + $rcmail->output->add_footer(html::div(array('id' => 'zipdownload-menu', 'class' => 'popupmenu'), + html::tag('ul', $ul_attr, implode('', $menu)))); + } + /** * Handler for attachment download action */ @@ -153,51 +172,18 @@ class zipdownload extends rcube_plugin /** * Handler for message download action */ - public function download_selection() + public function download_messages() { - if (isset($_REQUEST['_uid'])) { - $messageset = rcmail::get_uids(); + $rcmail = rcmail::get_instance(); - if (sizeof($messageset) > 0) { + if ($rcmail->config->get('zipdownload_selection') && !empty($_POST['_uid'])) { + $messageset = rcmail::get_uids(); + if (sizeof($messageset)) { $this->_download_messages($messageset); } } } - /** - * Handler for folder download action - */ - public function download_folder() - { - @set_time_limit(0); - - $imap = rcmail::get_instance()->get_storage(); - $mbox_name = $imap->get_folder(); - - // initialize searching result if search_filter is used - if ($_SESSION['search_filter'] && $_SESSION['search_filter'] != 'ALL') { - $imap->search($mbox_name, $_SESSION['search_filter'], RCUBE_CHARSET); - } - - // fetch message headers for all pages - $uids = array(); - if ($count = $imap->count($mbox_name, $imap->get_threading() ? 'THREADS' : 'ALL', FALSE)) { - for ($i = 0; ($i * $imap->get_pagesize()) <= $count; $i++) { - $a_headers = $imap->list_messages($mbox_name, ($i + 1)); - - foreach ($a_headers as $header) { - if (empty($header)) - continue; - - array_push($uids, $header->uid); - } - } - } - - if (sizeof($uids) > 0) - $this->_download_messages(array($mbox_name => $uids)); - } - /** * Helper method to packs all the given messages into a zip archive * @@ -207,40 +193,80 @@ class zipdownload extends rcube_plugin { $rcmail = rcmail::get_instance(); $imap = $rcmail->get_storage(); + $mode = rcube_utils::get_input_value('_mode', rcube_utils::INPUT_POST); $temp_dir = $rcmail->config->get('temp_dir'); $tmpfname = tempnam($temp_dir, 'zipdownload'); $tempfiles = array($tmpfname); $folders = count($messageset) > 1; + // @TODO: file size limit + // open zip file $zip = new ZipArchive(); $zip->open($tmpfname, ZIPARCHIVE::OVERWRITE); - foreach ($messageset as $mbox => $uids){ + if ($mode == 'mbox') { + $tmpfp = fopen($tmpfname . '.mbox', 'w'); + } + + foreach ($messageset as $mbox => $uids) { $imap->set_folder($mbox); $path = $folders ? str_replace($imap->get_hierarchy_delimiter(), '/', $mbox) . '/' : ''; - foreach ($uids as $uid){ + foreach ($uids as $uid) { $headers = $imap->get_message_headers($uid); - $subject = rcube_mime::decode_mime_string((string)$headers->subject); - $subject = $this->_convert_filename($subject); - $subject = substr($subject, 0, 16); - - $disp_name = ($subject ? $subject : 'message_rfc822') . ".eml"; - $disp_name = $path . $uid . "_" . $disp_name; - $tmpfn = tempnam($temp_dir, 'zipmessage'); - $tmpfp = fopen($tmpfn, 'w'); - $imap->get_raw_body($uid, $tmpfp); - $tempfiles[] = $tmpfn; - fclose($tmpfp); - $zip->addFile($tmpfn, $disp_name); + if ($mode == 'mbox') { + $from = rcube_mime::decode_address_list($headers->from, null, true, $headers->charset, true); + $from = array_shift($from); + + // Mbox format header + // @FIXME: \r\n or \n + // @FIXME: date format + $header = sprintf("From %s %s\r\n", + // replace spaces with hyphens + $from ? preg_replace('/\s/', '-', $from) : 'MAILER-DAEMON', + // internaldate + $headers->internaldate + ); + + fwrite($tmpfp, $header); + + // Use stream filter to quote "From " in the message body + stream_filter_register('mbox_filter', 'zipdownload_mbox_filter'); + $filter = stream_filter_append($tmpfp, 'mbox_filter'); + $imap->get_raw_body($uid, $tmpfp); + stream_filter_remove($filter); + fwrite($tmpfp, "\r\n"); + } + else { // maildir + $subject = rcube_mime::decode_mime_string((string)$headers->subject); + $subject = $this->_convert_filename($subject); + $subject = substr($subject, 0, 16); + + $disp_name = ($subject ? $subject : 'message_rfc822') . ".eml"; + $disp_name = $path . $uid . "_" . $disp_name; + + $tmpfn = tempnam($temp_dir, 'zipmessage'); + $tmpfp = fopen($tmpfn, 'w'); + $imap->get_raw_body($uid, $tmpfp); + $tempfiles[] = $tmpfn; + fclose($tmpfp); + $zip->addFile($tmpfn, $disp_name); + } } } + $filename = $folders ? 'messages' : $imap->get_folder(); + + if ($mode == 'mbox') { + $tempfiles[] = $tmpfname . '.mbox'; + fclose($tmpfp); + $zip->addFile($tmpfname . '.mbox', $filename . '.mbox'); + } + $zip->close(); - $filename = $folders ? 'messages' : $imap->get_folder(); $this->_deliver_zipfile($tmpfname, $filename . '.zip'); // delete temporary files from disk @@ -261,9 +287,7 @@ class zipdownload extends rcube_plugin $rcmail->output->nocacheing_headers(); - if ($browser->ie && $browser->ver < 7) - $filename = rawurlencode(abbreviate_string($filename, 55)); - else if ($browser->ie) + if ($browser->ie) $filename = rawurlencode($filename); else $filename = addcslashes($filename, '"'); @@ -288,6 +312,25 @@ class zipdownload extends rcube_plugin { $str = rcube_charset::convert($str, RCUBE_CHARSET, $this->charset); - return strtr($str, array(':'=>'', '/'=>'-')); + return strtr($str, array(':' => '', '/' => '-')); + } +} + +class zipdownload_mbox_filter extends php_user_filter +{ + function filter($in, $out, &$consumed, $closing) + { + while ($bucket = stream_bucket_make_writeable($in)) { + // messages are read line by line + if (preg_match('/^>*From /', $bucket->data)) { + $bucket->data = '>' . $bucket->data; + $bucket->datalen += 1; + } + + $consumed += $bucket->datalen; + stream_bucket_append($out, $bucket); + } + + return PSFS_PASS_ON; } } -- cgit v1.2.3