From 3cc1afa1c2f30bfebb30146795e50172947b4b5f Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Sun, 29 Jun 2014 10:35:18 -0400
Subject: [PATCH] Support images in HTML signatures (#1488676) This enables image button and file browser in html editor for signatures

---
 CHANGELOG                                |    1 
 program/steps/mail/attachments.inc       |   63 ---------------
 program/include/rcmail.php               |   79 +++++++++++++++++++
 program/js/editor.js                     |   16 ++-
 program/steps/settings/func.inc          |    1 
 program/steps/settings/save_identity.inc |   34 ++++++++
 program/js/app.js                        |   15 ++-
 config/defaults.inc.php                  |    4 +
 program/steps/settings/edit_identity.inc |   10 ++
 9 files changed, 149 insertions(+), 74 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index e4a02ad..b73ee3f 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 71a2e42..e7cb1e3 100644
--- a/config/defaults.inc.php
+++ b/config/defaults.inc.php
@@ -481,6 +481,10 @@
 // 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 0151020..54d0d88 100644
--- a/program/include/rcmail.php
+++ b/program/include/rcmail.php
@@ -1944,8 +1944,10 @@
 
     /**
      * 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 @@
             $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(
@@ -1982,6 +1988,77 @@
     }
 
     /**
+     * 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.
      */
     public function autocomplete_init()
diff --git a/program/js/app.js b/program/js/app.js
index 8f20656..3b5ff04 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -4083,6 +4083,14 @@
     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;
 
@@ -4111,11 +4119,6 @@
     // set tabindex attribute
     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 @@
 
     $(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 dfd3e27..0dd8fef 100644
--- a/program/js/editor.js
+++ b/program/js/editor.js
@@ -65,10 +65,12 @@
   // 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 @@
         }
       });
     }
+
+    // @todo: upload progress indicator
   };
 
   // close file browser window
@@ -652,7 +656,9 @@
     }
 
     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 = $('<img>').attr({title: file.name, src: img_src ? img_src : href + '&_thumbnail=1'});
 
       return $('<li>').attr({tabindex: 0})
@@ -686,7 +692,7 @@
   this.hack_file_input = function(elem, clone_form)
   {
     var link = $(elem),
-      file = $('<input>'),
+      file = $('<input>').attr('name', '_files[]'),
       form = $('<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 fd122c5..5eaa655 100644
--- a/program/steps/mail/attachments.inc
+++ b/program/steps/mail/attachments.inc
@@ -38,7 +38,7 @@
 
 
 // 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 @@
         $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 20f8220..34fe979 100644
--- a/program/steps/settings/edit_identity.inc
+++ b/program/steps/settings/edit_identity.inc
@@ -176,5 +176,15 @@
 
     $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 .= '<form id="' . $upload_form_id . '" style="display: none">'
+        . html::div('hint', $RCMAIL->gettext(array('name' => 'maxuploadsize', 'vars' => array('size' => $max_filesize))))
+        . '</form>';
+
+    $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 89103ee..7ccbfa4 100644
--- a/program/steps/settings/func.inc
+++ b/program/steps/settings/func.inc
@@ -44,6 +44,7 @@
     '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 77245b9..de0c84c 100644
--- a/program/steps/settings/save_identity.inc
+++ b/program/steps/settings/save_identity.inc
@@ -79,8 +79,11 @@
     }
 }
 
-// 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
@@ -191,6 +194,35 @@
 
 
 /**
+ * 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
  */
 function rcmail_wash_html($html)

--
Gitblit v1.9.1