From d5609160657829b0077fbb0db3b76b6096033652 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak <alec@alec.pl> Date: Thu, 08 Oct 2015 10:55:43 -0400 Subject: [PATCH] Added possibility to drag-n-drop attachments from mail preview to compose window --- CHANGELOG | 1 program/steps/mail/attachments.inc | 108 ++++++++++++----- program/steps/mail/compose.inc | 99 --------------- program/localization/en_US/messages.inc | 2 program/steps/mail/func.inc | 74 ++++++++++++ program/steps/mail/show.inc | 2 program/js/app.js | 48 +++++++- 7 files changed, 199 insertions(+), 135 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 444447d..040abb9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- Added possibility to drag-n-drop attachments from mail preview to compose window - Implemented mail messages searching with predefined date interval - PGP encryption support via Mailvelope integration - PGP encryption support via Enigma plugin diff --git a/program/js/app.js b/program/js/app.js index 46e0857..b8713a7 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -6,8 +6,8 @@ * @licstart The following is the entire license notice for the * JavaScript code in this file. * - * Copyright (C) 2005-2014, The Roundcube Dev Team - * Copyright (C) 2011-2014, Kolab Systems AG + * Copyright (C) 2005-2015, The Roundcube Dev Team + * Copyright (C) 2011-2015, Kolab Systems AG * * The JavaScript code in this page is free software: you can * redistribute it and/or modify it under the terms of the GNU @@ -274,6 +274,23 @@ this.enable_command('compose', 'add-contact', false); parent.rcmail.show_contentframe(true); } + + // initialize drag-n-drop on attachments, so they can e.g. + // be dropped into mail compose attachments in another window + if (this.gui_objects.attachments) + $('li > a', this.gui_objects.attachments).not('.drop').on('dragstart', function(e) { + var n, href = this.href, dt = e.originalEvent.dataTransfer; + if (dt) { + // inject username to the uri + href = href.replace(/^https?:\/\//, function(m) { return m + urlencode(ref.env.username) + '@'}); + // cleanup the node to get filename without the size test + n = $(this).clone(); + n.children().remove(); + + dt.setData('roundcube-uri', href); + dt.setData('roundcube-name', $.trim(n.text())); + } + }); } else if (this.env.action == 'compose') { this.env.address_group_stack = []; @@ -8410,15 +8427,32 @@ this.file_drag_hover(e, false); // prepare multipart form data composition - var files = e.target.files || e.dataTransfer.files, + var uri, files = e.target.files || e.dataTransfer.files, formdata = window.FormData ? new FormData() : null, fieldname = (this.env.filedrop.fieldname || '_file') + (this.env.filedrop.single ? '' : '[]'), boundary = '------multipartformboundary' + (new Date).getTime(), dashdash = '--', crlf = '\r\n', - multipart = dashdash + boundary + crlf; + multipart = dashdash + boundary + crlf, + args = {_id: this.env.compose_id || this.env.cid || '', _remote: 1, _from: this.env.action}; - if (!files || !files.length) + if (!files || !files.length) { + // Roundcube attachment, pass its uri to the backend and attach + if (uri = e.dataTransfer.getData('roundcube-uri')) { + var ts = new Date().getTime(), + // jQuery way to escape filename (#1490530) + content = $('<span>').text(e.dataTransfer.getData('roundcube-name') || this.gettext('attaching')).html(); + + args._uri = uri; + args._uploadid = ts; + + // add to attachments list + if (!this.add2attachment_list(ts, {name: '', html: content, classname: 'uploading', complete: false})) + this.file_upload_id = this.set_busy(true, 'attaching'); + + this.http_post(this.env.filedrop.action || 'upload', args); + } return; + } // inline function to submit the files to the server var submit_data = function() { @@ -8434,10 +8468,12 @@ // complete multipart content and post request multipart += dashdash + boundary + dashdash + crlf; + args._uploadid = ts; + $.ajax({ type: 'POST', dataType: 'json', - url: ref.url(ref.env.filedrop.action || 'upload', {_id: ref.env.compose_id||ref.env.cid||'', _uploadid: ts, _remote: 1, _from: ref.env.action}), + url: ref.url(ref.env.filedrop.action || 'upload', args), contentType: formdata ? false : 'multipart/form-data; boundary=' + boundary, processData: false, timeout: 0, // disable default timeout set in ajaxSetup() diff --git a/program/localization/en_US/messages.inc b/program/localization/en_US/messages.inc index cc8d5ea..313d6ae 100644 --- a/program/localization/en_US/messages.inc +++ b/program/localization/en_US/messages.inc @@ -41,6 +41,7 @@ $messages['refreshing'] = 'Refreshing...'; $messages['loading'] = 'Loading...'; $messages['uploading'] = 'Uploading file...'; +$messages['attaching'] = 'Attaching file...'; $messages['uploadingmany'] = 'Uploading files...'; $messages['loadingdata'] = 'Loading data...'; $messages['checkingmail'] = 'Checking for new messages...'; @@ -114,6 +115,7 @@ $messages['deletedsuccessfully'] = 'Successfully deleted.'; $messages['converting'] = 'Removing formatting...'; $messages['messageopenerror'] = 'Could not load message from server.'; +$messages['filelinkerror'] = 'Attaching the file failed.'; $messages['fileuploaderror'] = 'File upload failed.'; $messages['filesizeerror'] = 'The uploaded file exceeds the maximum size of $size.'; $messages['copysuccess'] = 'Successfully copied $nr contacts.'; diff --git a/program/steps/mail/attachments.inc b/program/steps/mail/attachments.inc index fe5684c..c39b25c 100644 --- a/program/steps/mail/attachments.inc +++ b/program/steps/mail/attachments.inc @@ -68,7 +68,6 @@ } $RCMAIL->display_uploaded_file($COMPOSE['attachments'][$id]); - exit; } @@ -77,8 +76,41 @@ // clear all stored output properties (like scripts and env vars) $OUTPUT->reset(); -$uploadid = rcube_utils::get_input_value('_uploadid', rcube_utils::INPUT_GET); +$uploadid = rcube_utils::get_input_value('_uploadid', rcube_utils::INPUT_GPC); +$uri = rcube_utils::get_input_value('_uri', rcube_utils::INPUT_POST); +// handle dropping a reference to an attachment part of some message +if ($uri) { + $url = parse_url($uri); + parse_str($url['query'], $params); + + if (strlen($params['_mbox']) && $params['_uid'] && $params['_part']) { + // @TODO: at some point we might support drag-n-drop between + // two different accounts on the same server, for now make sure + // this is the same server and the same user + list($host, $port) = explode(':', $_SERVER['HTTP_HOST']); + if ($host == $url['host'] && $port == $url['port'] + && $RCMAIL->get_user_name() == rawurldecode($url['user']) + ) { + $message = new rcube_message($params['_uid'], $params['_mbox']); + } + } + + if ($message && !empty($message->headers) + && ($attachment = rcmail_save_attachment($message, $params['_part'], $COMPOSE_ID)) + ) { + rcmail_attachment_success($attachment, $uploadid); + } + else { + $OUTPUT->command('display_message', $RCMAIL->gettext('filelinkerror'), 'error'); + $OUTPUT->command('remove_from_attachment_list', $uploadid); + } + + $OUTPUT->send(); + return; +} + +// handle file(s) upload if (is_array($_FILES['_attachments']['tmp_name'])) { $multiple = count($_FILES['_attachments']['tmp_name']) > 1; @@ -97,41 +129,11 @@ } if (!$err && $attachment['status'] && !$attachment['abort']) { - $id = $attachment['id']; - // store new attachment in session unset($attachment['status'], $attachment['abort']); - $RCMAIL->session->append($SESSION_KEY.'.attachments', $id, $attachment); + $RCMAIL->session->append($SESSION_KEY . '.attachments', $attachment['id'], $attachment); - if (($icon = $COMPOSE['deleteicon']) && is_file($icon)) { - $button = html::img(array( - 'src' => $icon, - 'alt' => $RCMAIL->gettext('delete') - )); - } - else if ($COMPOSE['textbuttons']) { - $button = rcube::Q($RCMAIL->gettext('delete')); - } - else { - $button = ''; - } - - $content = html::a(array( - 'href' => "#delete", - 'onclick' => sprintf("return %s.command('remove-attachment','rcmfile%s', this)", rcmail_output::JS_OBJECT_NAME, $id), - 'title' => $RCMAIL->gettext('delete'), - 'class' => 'delete', - 'aria-label' => $RCMAIL->gettext('delete') . ' ' . $attachment['name'], - ), $button); - - $content .= rcube::Q($attachment['name']); - - $OUTPUT->command('add2attachment_list', "rcmfile$id", array( - 'html' => $content, - 'name' => $attachment['name'], - 'mimetype' => $attachment['mimetype'], - 'classname' => rcube_utils::file2class($attachment['mimetype'], $attachment['name']), - 'complete' => true), $uploadid); + rcmail_attachment_success($attachment, $uploadid); } else { // upload failed if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) { @@ -172,3 +174,41 @@ // send html page with JS calls as response $OUTPUT->command('auto_save_start', false); $OUTPUT->send('iframe'); + + +function rcmail_attachment_success($attachment, $uploadid) +{ + global $RCMAIL, $COMPOSE; + + $id = $attachment['id']; + + if (($icon = $COMPOSE['deleteicon']) && is_file($icon)) { + $button = html::img(array( + 'src' => $icon, + 'alt' => $RCMAIL->gettext('delete') + )); + } + else if ($COMPOSE['textbuttons']) { + $button = rcube::Q($RCMAIL->gettext('delete')); + } + else { + $button = ''; + } + + $content = html::a(array( + 'href' => "#delete", + 'onclick' => sprintf("return %s.command('remove-attachment','rcmfile%s', this)", rcmail_output::JS_OBJECT_NAME, $id), + 'title' => $RCMAIL->gettext('delete'), + 'class' => 'delete', + 'aria-label' => $RCMAIL->gettext('delete') . ' ' . $attachment['name'], + ), $button); + + $content .= rcube::Q($attachment['name']); + + $RCMAIL->output->command('add2attachment_list', "rcmfile$id", array( + 'html' => $content, + 'name' => $attachment['name'], + 'mimetype' => $attachment['mimetype'], + 'classname' => rcube_utils::file2class($attachment['mimetype'], $attachment['name']), + 'complete' => true), $uploadid); +} diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc index 0b047d4..0e61e26 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -88,7 +88,7 @@ 'selectimportfile', 'messageissent', 'loadingdata', 'nopubkeyfor', 'nopubkeyforsender', 'encryptnoattachments','encryptedsendialog','searchpubkeyservers', 'importpubkeys', 'encryptpubkeysfound', 'search', 'close', 'import', 'keyid', 'keylength', 'keyexpired', - 'keyrevoked', 'keyimportsuccess', 'keyservererror'); + 'keyrevoked', 'keyimportsuccess', 'keyservererror', 'attaching'); $OUTPUT->set_pagetitle($RCMAIL->gettext('compose')); @@ -1294,10 +1294,8 @@ } if (($attachment = $loaded_attachments[rcmail_attachment_name($part) . $part->mimetype]) - || ($attachment = rcmail_save_attachment($message, $pid)) + || ($attachment = rcmail_save_attachment($message, $pid, $COMPOSE['id'])) ) { - $COMPOSE['attachments'][$attachment['id']] = $attachment; - if ($bodyIsHtml && ($part->content_id || $part->content_location)) { $url = sprintf('%s&_id=%s&_action=display-attachment&_file=rcmfile%s', $RCMAIL->comm_path, $COMPOSE['id'], $attachment['id']); @@ -1329,8 +1327,7 @@ foreach ((array)$message->mime_parts as $pid => $part) { if (($part->content_id || $part->content_location) && $part->filename) { - if ($attachment = rcmail_save_attachment($message, $pid)) { - $COMPOSE['attachments'][$attachment['id']] = $attachment; + if ($attachment = rcmail_save_attachment($message, $pid, $COMPOSE['id'])) { $url = sprintf('%s&_id=%s&_action=display-attachment&_file=rcmfile%s', $RCMAIL->comm_path, $COMPOSE['id'], $attachment['id']); @@ -1398,46 +1395,11 @@ $names[$name] = 1; $name .= '.eml'; - $data = $path = null; - if (!empty($loaded_attachments[$name . 'message/rfc822'])) { continue; } - // don't load too big attachments into memory - if (!rcube_utils::mem_check($message->size)) { - $temp_dir = unslashify($RCMAIL->config->get('temp_dir')); - $path = tempnam($temp_dir, 'rcmAttmnt'); - if ($fp = fopen($path, 'w')) { - $storage->get_raw_body($message->uid, $fp); - fclose($fp); - } - else { - return false; - } - } - else { - $data = $storage->get_raw_body($message->uid); - } - - $attachment = array( - 'group' => $COMPOSE['id'], - 'name' => $name, - 'mimetype' => 'message/rfc822', - 'data' => $data, - 'path' => $path, - 'size' => $path ? filesize($path) : strlen($data), - ); - - $attachment = $RCMAIL->plugins->exec_hook('attachment_save', $attachment); - - if ($attachment['status']) { - unset($attachment['data'], $attachment['status'], $attachment['content_id'], $attachment['abort']); - $COMPOSE['attachments'][$attachment['id']] = $attachment; - } - else if ($path) { - @unlink($path); - } + rcmail_save_attachment($message, null, $COMPOSE['id'], array('filename' => $name)); if ($message->headers->messageID) { $refs[] = $message->headers->messageID; @@ -1451,59 +1413,6 @@ if (!empty($refs)) { $COMPOSE['references'] = implode(' ', $refs); } -} - - -function rcmail_save_attachment(&$message, $pid) -{ - global $COMPOSE; - - $rcmail = rcmail::get_instance(); - $part = $message->mime_parts[$pid]; - $data = $path = null; - - // don't load too big attachments into memory - if (!rcube_utils::mem_check($part->size)) { - $temp_dir = unslashify($rcmail->config->get('temp_dir')); - $path = tempnam($temp_dir, 'rcmAttmnt'); - - if ($fp = fopen($path, 'w')) { - $message->get_part_body($pid, false, 0, $fp); - fclose($fp); - } - else { - return false; - } - } - else { - $data = $message->get_part_body($pid); - } - - $mimetype = $part->ctype_primary . '/' . $part->ctype_secondary; - $filename = rcmail_attachment_name($part); - - $attachment = array( - 'group' => $COMPOSE['id'], - 'name' => $filename, - 'mimetype' => $mimetype, - 'content_id' => $part->content_id, - 'data' => $data, - 'path' => $path, - 'size' => $path ? filesize($path) : strlen($data), - 'charset' => $part->charset, - ); - - $attachment = $rcmail->plugins->exec_hook('attachment_save', $attachment); - - if ($attachment['status']) { - unset($attachment['data'], $attachment['status'], $attachment['content_id'], $attachment['abort']); - return $attachment; - } - else if ($path) { - @unlink($path); - } - - return false; } function rcmail_save_image($path, $mimetype = '', $data = null) diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index d949cf6..80b618c 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -2199,3 +2199,77 @@ return $jsresult; } + +function rcmail_save_attachment($message, $pid, $compose_id, $params = array()) +{ + $rcmail = rcmail::get_instance(); + $storage = $rcmail->get_storage(); + + if ($pid) { + // attachment requested + $part = $message->mime_parts[$pid]; + $size = $part->size; + $mimetype = $part->ctype_primary . '/' . $part->ctype_secondary; + $filename = $params['filename'] ?: rcmail_attachment_name($part); + } + else { + // the whole message requested + $size = $message->size; + $mimetype = 'message/rfc822'; + $filename = $params['filename'] ?: 'message_rfc822.eml'; + } + + // don't load too big attachments into memory + if (!rcube_utils::mem_check($size)) { + $temp_dir = unslashify($rcmail->config->get('temp_dir')); + $path = tempnam($temp_dir, 'rcmAttmnt'); + + if ($fp = fopen($path, 'w')) { + if ($pid) { + // part body + $message->get_part_body($pid, false, 0, $fp); + } + else { + // complete message + $storage->get_raw_body($message->uid, $fp); + } + + fclose($fp); + } + else { + return false; + } + } + else if ($pid) { + // part body + $data = $message->get_part_body($pid); + } + else { + // complete message + $data = $storage->get_raw_body($message->uid); + } + + $attachment = array( + 'group' => $compose_id, + 'name' => $filename, + 'mimetype' => $mimetype, + 'content_id' => $part ? $part->content_id : null, + 'data' => $data, + 'path' => $path, + 'size' => $path ? filesize($path) : strlen($data), + 'charset' => $part ? $part->charset : null, + ); + + $attachment = $rcmail->plugins->exec_hook('attachment_save', $attachment); + + if ($attachment['status']) { + unset($attachment['data'], $attachment['status'], $attachment['content_id'], $attachment['abort']); + $rcmail->session->append('compose_data_' . $compose_id . '.attachments', $attachment['id'], $attachment); + return $attachment; + } + else if ($path) { + @unlink($path); + } + + return false; +} diff --git a/program/steps/mail/show.inc b/program/steps/mail/show.inc index e5bfcf0..82bcb41 100644 --- a/program/steps/mail/show.inc +++ b/program/steps/mail/show.inc @@ -68,6 +68,7 @@ $OUTPUT->set_env('safemode', $MESSAGE->is_safe); $OUTPUT->set_env('sender', $MESSAGE->sender['string']); $OUTPUT->set_env('mailbox', $mbox_name); + $OUTPUT->set_env('username', $RCMAIL->get_user_name()); $OUTPUT->set_env('permaurl', $RCMAIL->url(array('_action' => 'show', '_uid' => $MESSAGE->uid, '_mbox' => $mbox_name))); if ($MESSAGE->headers->get('list-post', false)) { @@ -240,6 +241,7 @@ $out = html::tag('ul', $attrib, $ol, html::$common_attrib); $RCMAIL->output->set_env('attachments', $attachments); + $RCMAIL->output->add_gui_object('attachments', $attrib['id']); } return $out; -- Gitblit v1.9.1