Aleksander Machniak
2015-10-08 d5609160657829b0077fbb0db3b76b6096033652
Added possibility to drag-n-drop attachments from mail preview to compose window
7 files modified
334 ■■■■■ changed files
CHANGELOG 1 ●●●● patch | view | raw | blame | history
program/js/app.js 48 ●●●● patch | view | raw | blame | history
program/localization/en_US/messages.inc 2 ●●●●● patch | view | raw | blame | history
program/steps/mail/attachments.inc 108 ●●●●● patch | view | raw | blame | history
program/steps/mail/compose.inc 99 ●●●●● patch | view | raw | blame | history
program/steps/mail/func.inc 74 ●●●●● patch | view | raw | blame | history
program/steps/mail/show.inc 2 ●●●●● patch | view | raw | blame | history
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
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()
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.';
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);
}
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)
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;
}
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;