From 3cc1afa1c2f30bfebb30146795e50172947b4b5f Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Sun, 29 Jun 2014 16:35:18 +0200 Subject: Support images in HTML signatures (#1488676) This enables image button and file browser in html editor for signatures --- CHANGELOG | 1 + config/defaults.inc.php | 4 ++ program/include/rcmail.php | 79 +++++++++++++++++++++++++++++++- program/js/app.js | 15 +++--- program/js/editor.js | 16 +++++-- program/steps/mail/attachments.inc | 63 +------------------------ program/steps/settings/edit_identity.inc | 10 ++++ program/steps/settings/func.inc | 1 + program/steps/settings/save_identity.inc | 34 +++++++++++++- 9 files changed, 149 insertions(+), 74 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e4a02ad4f..b73ee3f19 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- Support images in HTML signatures (#1488676) - Display full quota information in popup (#1485769, #1486604) - Mail compose: Selecting contact inserts recipient to previously focused input - to/cc/bcc accordingly (#1489684) - Add option to set default message list mode - default_list_mode (#1487312) diff --git a/config/defaults.inc.php b/config/defaults.inc.php index 71a2e4277..e7cb1e3aa 100644 --- a/config/defaults.inc.php +++ b/config/defaults.inc.php @@ -481,6 +481,10 @@ $config['mdn_use_from'] = false; // 4 - one identity with possibility to edit only signature $config['identities_level'] = 0; +// Maximum size of uploaded image in kilobytes +// Images (in html signatures) are stored in database as data URIs +$config['identity_image_size'] = 64; + // Mimetypes supported by the browser. // attachments of these types will open in a preview window // either a comma-separated list or an array: 'text/plain,text/html,text/xml,image/jpeg,image/gif,image/png,application/pdf' diff --git a/program/include/rcmail.php b/program/include/rcmail.php index 0151020c7..54d0d88e6 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -1944,8 +1944,10 @@ class rcmail extends rcube /** * Initializes file uploading interface. + * + * @param $int Optional maximum file size in bytes */ - public function upload_init() + public function upload_init($max_size = null) { // Enable upload progress bar if ($seconds = $this->config->get('upload_progress')) { @@ -1973,6 +1975,10 @@ class rcmail extends rcube $max_filesize = $max_postsize; } + if ($max_size && $max_size < $max_filesize) { + $max_filesize = $max_size; + } + $this->output->set_env('max_filesize', $max_filesize); $max_filesize = $this->show_bytes($max_filesize); $this->output->set_env('filesizeerror', $this->gettext(array( @@ -1981,6 +1987,77 @@ class rcmail extends rcube return $max_filesize; } + /** + * Outputs uploaded file content (with image thumbnails support + * + * @param array $file Upload file data + */ + public function display_uploaded_file($file) + { + if (empty($file)) { + return; + } + + $file = $this->plugins->exec_hook('attachment_display', $file); + + if ($file['status']) { + if (empty($file['size'])) { + $file['size'] = $file['data'] ? strlen($file['data']) : @filesize($file['path']); + } + + // generate image thumbnail for file browser in HTML editor + if (!empty($_GET['_thumbnail'])) { + $temp_dir = $this->config->get('temp_dir'); + $thumbnail_size = 80; + list(,$ext) = explode('/', $file['mimetype']); + $mimetype = $file['mimetype']; + $file_ident = $file['id'] . ':' . $file['mimetype'] . ':' . $file['size']; + $cache_basename = $temp_dir . '/' . md5($file_ident . ':' . $this->user->ID . ':' . $thumbnail_size); + $cache_file = $cache_basename . '.' . $ext; + + // render thumbnail image if not done yet + if (!is_file($cache_file)) { + if (!$file['path']) { + $orig_name = $filename = $cache_basename . '.orig.' . $ext; + file_put_contents($orig_name, $file['data']); + } + else { + $filename = $file['path']; + } + + $image = new rcube_image($filename); + if ($imgtype = $image->resize($thumbnail_size, $cache_file, true)) { + $mimetype = 'image/' . $imgtype; + + if ($orig_name) { + unlink($orig_name); + } + } + } + + if (is_file($cache_file)) { + // cache for 1h + $this->output->future_expire_header(3600); + header('Content-Type: ' . $mimetype); + header('Content-Length: ' . filesize($cache_file)); + + readfile($cache_file); + exit; + } + } + + header('Content-Type: ' . $file['mimetype']); + header('Content-Length: ' . $file['size']); + + if ($file['data']) { + echo $file['data']; + } + else if ($file['path']) { + readfile($file['path']); + } + } + } + /** * Initializes client-side autocompletion. */ diff --git a/program/js/app.js b/program/js/app.js index 8f2065635..3b5ff0422 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -4083,6 +4083,14 @@ function rcube_webmail() if (upload_id) this.triggerEvent('fileuploaded', {name: name, attachment: att, id: upload_id}); + if (!this.env.attachments) + this.env.attachments = {}; + + if (upload_id && this.env.attachments[upload_id]) + delete this.env.attachments[upload_id]; + + this.env.attachments[name] = att; + if (!this.gui_objects.attachmentlist) return false; @@ -4112,11 +4120,6 @@ function rcube_webmail() var tabindex = $(this.gui_objects.attachmentlist).attr('data-tabindex') || '0'; li.find('a').attr('tabindex', tabindex); - if (upload_id && this.env.attachments[upload_id]) - delete this.env.attachments[upload_id]; - - this.env.attachments[name] = att; - return true; }; @@ -7563,7 +7566,7 @@ function rcube_webmail() $(form).attr({ target: frame_name, - action: this.url(action, { _id:this.env.compose_id||'', _uploadid:ts }), + action: this.url(action, {_id: this.env.compose_id || '', _uploadid: ts, _from: this.env.action}), method: 'POST'}) .attr(form.encoding ? 'encoding' : 'enctype', 'multipart/form-data') .submit(); diff --git a/program/js/editor.js b/program/js/editor.js index dfd3e27ea..0dd8fef9a 100644 --- a/program/js/editor.js +++ b/program/js/editor.js @@ -65,10 +65,12 @@ function rcube_text_editor(config, id) // minimal editor if (config.mode == 'identity') { $.extend(conf, { - plugins: 'autolink charmap code colorpicker hr link paste tabfocus textcolor', + plugins: 'autolink charmap code colorpicker hr image link paste tabfocus textcolor', toolbar: 'bold italic underline alignleft aligncenter alignright alignjustify' - + ' | outdent indent charmap hr link unlink code forecolor' - + ' | fontselect fontsizeselect' + + ' | outdent indent charmap hr link unlink image code forecolor' + + ' | fontselect fontsizeselect', + file_browser_callback: function(name, url, type, win) { ref.file_browser_callback(name, url, type); }, + file_browser_callback_types: 'image' }); } // full-featured editor @@ -610,6 +612,8 @@ function rcube_text_editor(config, id) } }); } + + // @todo: upload progress indicator }; // close file browser window @@ -652,7 +656,9 @@ function rcube_text_editor(config, id) } if (rx.test(file.mimetype)) { - var href = rcmail.env.comm_path+'&_id='+rcmail.env.compose_id+'&_action=display-attachment&_file='+file_id, + var path = rcmail.env.comm_path + '&_from=' + rcmail.env.action, + action = rcmail.env.compose_id ? '&_id=' + rcmail.env.compose_id + '&_action=display-attachment' : '&_action=upload-display', + href = path + action + '&_file=' + file_id, img = $('').attr({title: file.name, src: img_src ? img_src : href + '&_thumbnail=1'}); return $('
  • ').attr({tabindex: 0}) @@ -686,7 +692,7 @@ function rcube_text_editor(config, id) this.hack_file_input = function(elem, clone_form) { var link = $(elem), - file = $(''), + file = $('').attr('name', '_files[]'), form = $('
    ').attr({method: 'post', enctype: 'multipart/form-data'}), offset = link.offset(); diff --git a/program/steps/mail/attachments.inc b/program/steps/mail/attachments.inc index fd122c5c1..5eaa655e3 100644 --- a/program/steps/mail/attachments.inc +++ b/program/steps/mail/attachments.inc @@ -38,7 +38,7 @@ if (!$COMPOSE) { // remove an attachment -if ($RCMAIL->action=='remove-attachment') { +if ($RCMAIL->action == 'remove-attachment') { $id = 'undefined'; if (preg_match('/^rcmfile(\w+)$/', $_POST['_file'], $regs)) { @@ -67,66 +67,7 @@ if ($RCMAIL->action == 'display-attachment') { $id = $regs[1]; } - if ($attachment = $COMPOSE['attachments'][$id]) { - $attachment = $RCMAIL->plugins->exec_hook('attachment_display', $attachment); - } - - if ($attachment['status']) { - if (empty($attachment['size'])) { - $attachment['size'] = $attachment['data'] ? strlen($attachment['data']) : @filesize($attachment['path']); - } - - // generate image thumbnail for file browser in HTML editor - if (!empty($_GET['_thumbnail'])) { - $temp_dir = $RCMAIL->config->get('temp_dir'); - $thumbnail_size = 80; - list(,$ext) = explode('/', $attachment['mimetype']); - $mimetype = $attachment['mimetype']; - $file_ident = $attachment['id'] . ':' . $attachment['mimetype'] . ':' . $attachment['size']; - $cache_basename = $temp_dir . '/' . md5($file_ident . ':' . $RCMAIL->user->ID . ':' . $thumbnail_size); - $cache_file = $cache_basename . '.' . $ext; - - // render thumbnail image if not done yet - if (!is_file($cache_file)) { - if (!$attachment['path']) { - $orig_name = $filename = $cache_basename . '.orig.' . $ext; - file_put_contents($orig_name, $attachment['data']); - } - else { - $filename = $attachment['path']; - } - - $image = new rcube_image($filename); - if ($imgtype = $image->resize($thumbnail_size, $cache_file, true)) { - $mimetype = 'image/' . $imgtype; - - if ($orig_name) { - unlink($orig_name); - } - } - } - - if (is_file($cache_file)) { - // cache for 1h - $RCMAIL->output->future_expire_header(3600); - header('Content-Type: ' . $mimetype); - header('Content-Length: ' . filesize($cache_file)); - - readfile($cache_file); - exit; - } - } - - header('Content-Type: ' . $attachment['mimetype']); - header('Content-Length: ' . $attachment['size']); - - if ($attachment['data']) { - echo $attachment['data']; - } - else if ($attachment['path']) { - readfile($attachment['path']); - } - } + $RCMAIL->display_uploaded_file($COMPOSE['attachments'][$id]); exit; } diff --git a/program/steps/settings/edit_identity.inc b/program/steps/settings/edit_identity.inc index 20f822027..34fe9798f 100644 --- a/program/steps/settings/edit_identity.inc +++ b/program/steps/settings/edit_identity.inc @@ -176,5 +176,15 @@ function rcube_identity_form($attrib) $out .= $form_end; + // add image upload form + $max_filesize = $RCMAIL->upload_init($RCMAIL->config->get('identity_image_size', 64) * 1024); + $upload_form_id = 'identityImageUpload'; + + $out .= '' + . html::div('hint', $RCMAIL->gettext(array('name' => 'maxuploadsize', 'vars' => array('size' => $max_filesize)))) + . '
    '; + + $RCMAIL->output->add_gui_object('uploadform', $upload_form_id); + return $out; } diff --git a/program/steps/settings/func.inc b/program/steps/settings/func.inc index 89103ee44..7ccbfa4a5 100644 --- a/program/steps/settings/func.inc +++ b/program/steps/settings/func.inc @@ -44,6 +44,7 @@ $RCMAIL->register_action_map(array( 'add-response' => 'edit_response.inc', 'save-response' => 'edit_response.inc', 'delete-response' => 'responses.inc', + 'upload-display' => 'upload.inc', )); diff --git a/program/steps/settings/save_identity.inc b/program/steps/settings/save_identity.inc index 77245b988..de0c84c91 100644 --- a/program/steps/settings/save_identity.inc +++ b/program/steps/settings/save_identity.inc @@ -79,8 +79,11 @@ foreach ($email_checks as $email) { } } -// XSS protection in HTML signature (#1489251) if (!empty($save_data['signature']) && !empty($save_data['html_signature'])) { + // replace uploaded images with data URIs + $save_data['signature'] = rcmail_attach_images($save_data['signature']); + + // XSS protection in HTML signature (#1489251) $save_data['signature'] = rcmail_wash_html($save_data['signature']); // clear POST data of signature, we want to use safe content @@ -190,6 +193,35 @@ else { } +/** + * Attach uploaded images into signature as data URIs + */ +function rcmail_attach_images($html) +{ + global $RCMAIL; + + $offset = 0; + $regexp = '/\s(poster|src)\s*=\s*[\'"]*\S+upload-display\S+file=rcmfile([0-9]+)[\s\'"]*/'; + + while (preg_match($regexp, $html, $matches, 0, $offset)) { + $file_id = $matches[2]; + $data_uri = ' '; + + if ($file_id && ($file = $_SESSION['identity']['files'][$file_id])) { + $file = $RCMAIL->plugins->exec_hook('attachment_get', $file); + + $data_uri .= 'src="data:' . $file['mimetype'] . ';base64,'; + $data_uri .= base64_encode($file['data'] ? $file['data'] : file_get_contents($file['path'])); + $data_uri .= '" '; + } + + $html = str_replace($matches[0], $data_uri, $html); + $offset += strlen($data_uri) - strlen($matches[0]) + 1; + } + + return $html; +} + /** * Sanity checks/cleanups on HTML body of signature */ -- cgit v1.2.3