From a63f14ec4045e82f47b237663bcf09939a0eadc5 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Sat, 29 Aug 2015 01:52:57 -0400
Subject: [PATCH] Emoticons-related code refactoring

---
 CHANGELOG                                   |    5 
 plugins/emoticons/localization/en_US.inc    |   23 +
 plugins/emoticons/tests/EmoticonsEngine.php |   48 ++++
 program/steps/mail/compose.inc              |    8 
 program/js/editor.js                        |   11 
 plugins/emoticons/tests/Emoticons.php       |   41 ---
 program/steps/mail/sendmail.inc             |   68 -----
 program/include/bc.php                      |    5 
 plugins/emoticons/emoticons_engine.php      |  152 ++++++++++++
 program/steps/utils/html2text.inc           |   14 
 program/include/rcmail.php                  |   91 ++++---
 program/steps/mail/func.inc                 |    3 
 tests/phpunit.xml                           |    1 
 plugins/emoticons/composer.json             |    4 
 plugins/emoticons/config.inc.php.dist       |    7 
 plugins/emoticons/emoticons.php             |  208 +++++++++++++----
 16 files changed, 469 insertions(+), 220 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 5ddd45d..6e72efb 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,9 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Emoticons: Added option to switch on/off emoticons in compose editor (#1485732)
+- Emoticons: Added option to switch on/off emoticons in plain text messages
+- Emoticons: All emoticons-related functionality is handled by the plugin now
 - Installer: Add button to save generated config file in system temp directory (#1488149)
 - Remove common subject prefixes Re:, Re[x]:, Re-x: on reply (#1490497)
 - Added GSSAPI/Kerberos authentication plugin - krb_authentication
@@ -13,6 +16,8 @@
 - Installer: Remove system() function use (#1490139)
 - Password plugin: Added 'kpasswd' driver by Peter Allgeyer
 - Add initdb.sh to create database from initial.sql script with prefix support (#1490188)
+- Plugin API: Added disabled_plugins an disabled_buttons options in html_editor hook
+- Plugin API: Added html2text hook
 - Plugin API: Added message_part_body hook
 - Plugin API: Added message_ready hook
 - Plugin API: Add special onload() method to execute plugin actions before startup (session and GUI initialization)
diff --git a/plugins/emoticons/composer.json b/plugins/emoticons/composer.json
index d1679cf..72f1602 100644
--- a/plugins/emoticons/composer.json
+++ b/plugins/emoticons/composer.json
@@ -1,9 +1,9 @@
 {
     "name": "roundcube/emoticons",
     "type": "roundcube-plugin",
-    "description": "Sample plugin to replace emoticons in plain text message body with real icons.",
+    "description": "Plugin that adds emoticons support.",
     "license": "GPLv3+",
-    "version": "1.4",
+    "version": "2.0",
     "authors": [
         {
             "name": "Thomas Bruederli",
diff --git a/plugins/emoticons/config.inc.php.dist b/plugins/emoticons/config.inc.php.dist
new file mode 100644
index 0000000..1af6f67
--- /dev/null
+++ b/plugins/emoticons/config.inc.php.dist
@@ -0,0 +1,7 @@
+<?php
+
+// Enable emoticons in plain text messages preview
+$config['emoticons_display'] = false;
+
+// Enable emoticons in compose editor (HTML)
+$config['emoticons_compose'] = true;
diff --git a/plugins/emoticons/emoticons.php b/plugins/emoticons/emoticons.php
index f3f987a..cb90b6f 100644
--- a/plugins/emoticons/emoticons.php
+++ b/plugins/emoticons/emoticons.php
@@ -1,9 +1,10 @@
 <?php
 
 /**
- * Display Emoticons
+ * Emoticons
  *
- * Sample plugin to replace emoticons in plain text message body with real icons
+ * Plugin to replace emoticons in plain text message body with real icons.
+ * Also it enables emoticons in HTML compose editor. Both features are optional.
  *
  * @version @package_version@
  * @license GNU GPLv3+
@@ -13,66 +14,173 @@
  */
 class emoticons extends rcube_plugin
 {
-    public $task = 'mail';
+    public $task = 'mail|settings|utils';
 
+
+    /**
+     * Plugin initilization.
+     */
     function init()
     {
-        $this->add_hook('message_part_after', array($this, 'replace'));
+        $rcube = rcube::get_instance();
+
+        $this->add_hook('message_part_after', array($this, 'message_part_after'));
+        $this->add_hook('message_outgoing_body', array($this, 'message_outgoing_body'));
+        $this->add_hook('html2text', array($this, 'html2text'));
+        $this->add_hook('html_editor', array($this, 'html_editor'));
+
+        if ($rcube->task == 'settings') {
+            $this->add_hook('preferences_list', array($this, 'preferences_list'));
+            $this->add_hook('preferences_save', array($this, 'preferences_save'));
+        }
     }
 
-    function replace($args)
+    /**
+     * 'message_part_after' hook handler to replace common plain text emoticons
+     * with emoticon images (<img>)
+     */
+    function message_part_after($args)
     {
-        // This is a lookbehind assertion which will exclude html entities
-        // E.g. situation when ";)" in "&quot;)" shouldn't be replaced by the icon
-        // It's so long because of assertion format restrictions
-        $entity = '(?<!&'
-            . '[a-zA-Z0-9]{2}' . '|' . '#[0-9]{2}' . '|'
-            . '[a-zA-Z0-9]{3}' . '|' . '#[0-9]{3}' . '|'
-            . '[a-zA-Z0-9]{4}' . '|' . '#[0-9]{4}' . '|'
-            . '[a-zA-Z0-9]{5}' . '|'
-            . '[a-zA-Z0-9]{6}' . '|'
-            . '[a-zA-Z0-9]{7}'
-            . ')';
-
-        // map of emoticon replacements
-        $map = array(
-            '/(?<!mailto):D/'   => $this->img_tag('smiley-laughing.gif',    ':D'    ),
-            '/:-D/'             => $this->img_tag('smiley-laughing.gif',    ':-D'   ),
-            '/:\(/'             => $this->img_tag('smiley-frown.gif',       ':('    ),
-            '/:-\(/'            => $this->img_tag('smiley-frown.gif',       ':-('   ),
-            '/'.$entity.';\)/'  => $this->img_tag('smiley-wink.gif',        ';)'    ),
-            '/'.$entity.';-\)/' => $this->img_tag('smiley-wink.gif',        ';-)'   ),
-            '/8\)/'             => $this->img_tag('smiley-cool.gif',        '8)'    ),
-            '/8-\)/'            => $this->img_tag('smiley-cool.gif',        '8-)'   ),
-            '/(?<!mailto):O/i'  => $this->img_tag('smiley-surprised.gif',   ':O'    ),
-            '/(?<!mailto):-O/i' => $this->img_tag('smiley-surprised.gif',   ':-O'   ),
-            '/(?<!mailto):P/i'  => $this->img_tag('smiley-tongue-out.gif',  ':P'    ),
-            '/(?<!mailto):-P/i' => $this->img_tag('smiley-tongue-out.gif',  ':-P'   ),
-            '/(?<!mailto):@/i'  => $this->img_tag('smiley-yell.gif',        ':@'    ),
-            '/(?<!mailto):-@/i' => $this->img_tag('smiley-yell.gif',        ':-@'   ),
-            '/O:\)/i'           => $this->img_tag('smiley-innocent.gif',    'O:)'   ),
-            '/O:-\)/i'          => $this->img_tag('smiley-innocent.gif',    'O:-)'  ),
-            '/(?<!O):\)/'       => $this->img_tag('smiley-smile.gif',       ':)'    ),
-            '/(?<!O):-\)/'      => $this->img_tag('smiley-smile.gif',       ':-)'   ),
-            '/(?<!mailto):\$/'  => $this->img_tag('smiley-embarassed.gif',  ':$'    ),
-            '/(?<!mailto):-\$/' => $this->img_tag('smiley-embarassed.gif',  ':-$'   ),
-            '/(?<!mailto):\*/i'  => $this->img_tag('smiley-kiss.gif',       ':*'    ),
-            '/(?<!mailto):-\*/i' => $this->img_tag('smiley-kiss.gif',       ':-*'   ),
-            '/(?<!mailto):S/i'  => $this->img_tag('smiley-undecided.gif',   ':S'    ),
-            '/(?<!mailto):-S/i' => $this->img_tag('smiley-undecided.gif',   ':-S'   ),
-        );
-
         if ($args['type'] == 'plain') {
-            $args['body'] = preg_replace(
-                array_keys($map), array_values($map), $args['body']);
+            $this->load_config();
+
+            $rcube = rcube::get_instance();
+            if (!$rcube->config->get('emoticons_display', false)) {
+                return $args;
+            }
+
+            require_once __DIR__ . '/emoticons_engine.php';
+
+            $args['body'] = emoticons_engine::text2icons($args['body']);
         }
 
         return $args;
     }
 
-    private function img_tag($ico, $title)
+    /**
+     * 'message_outgoing_body' hook handler to replace image emoticons from TinyMCE
+     * editor with image attachments.
+     */
+    function message_outgoing_body($args)
     {
-        $path = './program/js/tinymce/plugins/emoticons/img/';
-        return html::img(array('src' => $path.$ico, 'title' => $title));
+        if ($args['type'] == 'html') {
+            $this->load_config();
+
+            $rcube = rcube::get_instance();
+            if (!$rcube->config->get('emoticons_compose', true)) {
+                return $args;
+            }
+
+            require_once __DIR__ . '/emoticons_engine.php';
+
+            // look for "emoticon" images from TinyMCE and change their src paths to
+            // be file paths on the server instead of URL paths.
+            $images = emoticons_engine::replace($args['body']);
+
+            // add these images as attachments to the MIME message
+            foreach ($images as $img_name => $img_file) {
+                $args['message']->addHTMLImage($img_file, 'image/gif', '', true, $img_name);
+            }
+        }
+
+        return $args;
+    }
+
+    /**
+     * 'html2text' hook handler to replace image emoticons from TinyMCE
+     * editor with plain text emoticons.
+     *
+     * This is executed on html2text action, i.e. when switching from HTML to text
+     * in compose window (or similiar place). Also when generating alternative
+     * text/plain part.
+     */
+    function html2text($args)
+    {
+        $rcube = rcube::get_instance();
+
+        if ($rcube->action == 'html2text' || $rcube->action == 'send') {
+            $this->load_config();
+
+            if (!$rcube->config->get('emoticons_compose', true)) {
+                return $args;
+            }
+
+            require_once __DIR__ . '/emoticons_engine.php';
+
+            $args['body'] = emoticons_engine::icons2text($args['body']);
+        }
+
+        return $args;
+    }
+
+    /**
+     * 'html_editor' hook handler, where we enable emoticons in TinyMCE
+     */
+    function html_editor($args)
+    {
+        $rcube = rcube::get_instance();
+
+        $this->load_config();
+
+        if (!$rcube->config->get('emoticons_compose', true)) {
+            $args['disabled_plugins'][] = 'emoticons';
+            $args['disabled_buttons'][] = 'emoticons';
+        }
+
+        return $args;
+    }
+
+    /**
+     * 'preferences_list' hook handler
+     */
+    function preferences_list($args)
+    {
+        $rcube         = rcube::get_instance();
+        $dont_override = $rcube->config->get('dont_override', array());
+
+        if ($args['section'] == 'mailview' && !in_array('emoticons_display', $dont_override)) {
+            $this->load_config();
+            $this->add_texts('localization');
+
+            $field_id = 'emoticons_display';
+            $checkbox = new html_checkbox(array('name' => '_' . $field_id, 'id' => $field_id, 'value' => 1));
+
+            $args['blocks']['main']['options']['emoticons_display'] = array(
+                    'title'   => $this->gettext('emoticonsdisplay'),
+                    'content' => $checkbox->show(intval($rcube->config->get('emoticons_display', false)))
+            );
+        }
+        else if ($args['section'] == 'compose' && !in_array('emoticons_compose', $dont_override)) {
+            $this->load_config();
+            $this->add_texts('localization');
+
+            $field_id = 'emoticons_compose';
+            $checkbox = new html_checkbox(array('name' => '_' . $field_id, 'id' => $field_id, 'value' => 1));
+
+            $args['blocks']['main']['options']['emoticons_compose'] = array(
+                    'title'   => $this->gettext('emoticonscompose'),
+                    'content' => $checkbox->show(intval($rcube->config->get('emoticons_compose', true)))
+            );
+        }
+
+        return $args;
+    }
+
+    /**
+     * 'preferences_save' hook handler
+     */
+    function preferences_save($args)
+    {
+        $rcube         = rcube::get_instance();
+        $dont_override = $rcube->config->get('dont_override', array());
+
+        if ($args['section'] == 'mailview' && !in_array('emoticons_display', $dont_override)) {
+            $args['prefs']['emoticons_display'] = rcube_utils::get_input_value('_emoticons_display', rcube_utils::INPUT_POST) ? true : false;
+        }
+        else if ($args['section'] == 'compose' && !in_array('emoticons_compose', $dont_override)) {
+            $args['prefs']['emoticons_compose'] = rcube_utils::get_input_value('_emoticons_compose', rcube_utils::INPUT_POST) ? true : false;
+        }
+
+        return $args;
     }
 }
diff --git a/plugins/emoticons/emoticons_engine.php b/plugins/emoticons/emoticons_engine.php
new file mode 100644
index 0000000..4d534cd
--- /dev/null
+++ b/plugins/emoticons/emoticons_engine.php
@@ -0,0 +1,152 @@
+<?php
+
+/**
+ * @license GNU GPLv3+
+ * @author Thomas Bruederli
+ * @author Aleksander Machniak
+ */
+class emoticons_engine
+{
+    const IMG_PATH = 'program/js/tinymce/plugins/emoticons/img/';
+
+    /**
+     * Replaces TinyMCE's emoticon images with plain-text representation
+     *
+     * @param string $html HTML content
+     *
+     * @return string HTML content
+     */
+    public static function icons2text($html)
+    {
+        $emoticons = array(
+            '8-)' => 'smiley-cool',
+            ':-#' => 'smiley-foot-in-mouth',
+            ':-*' => 'smiley-kiss',
+            ':-X' => 'smiley-sealed',
+            ':-P' => 'smiley-tongue-out',
+            ':-@' => 'smiley-yell',
+            ":'(" => 'smiley-cry',
+            ':-(' => 'smiley-frown',
+            ':-D' => 'smiley-laughing',
+            ':-)' => 'smiley-smile',
+            ':-S' => 'smiley-undecided',
+            ':-$' => 'smiley-embarassed',
+            'O:-)' => 'smiley-innocent',
+            ':-|' => 'smiley-money-mouth',
+            ':-O' => 'smiley-surprised',
+            ';-)' => 'smiley-wink',
+        );
+
+        foreach ($emoticons as $idx => $file) {
+            // <img title="Cry" src="http://.../program/js/tinymce/plugins/emoticons/img/smiley-cry.gif" border="0" alt="Cry" />
+            $file      = preg_quote(self::IMG_PATH . $file . '.gif', '/');
+            $search[]  = '/<img (title="[a-z ]+" )?src="[^"]+' . $file . '"[^>]+\/>/i';
+            $replace[] = $idx;
+        }
+
+        return preg_replace($search, $replace, $html);
+    }
+
+    /**
+     * Replace common plain text emoticons with empticon <img> tags
+     *
+     * @param string $text Text
+     *
+     * @return string Converted text
+     */
+    public static function text2icons($text)
+    {
+        // This is a lookbehind assertion which will exclude html entities
+        // E.g. situation when ";)" in "&quot;)" shouldn't be replaced by the icon
+        // It's so long because of assertion format restrictions
+        $entity = '(?<!&'
+            . '[a-zA-Z0-9]{2}' . '|' . '#[0-9]{2}' . '|'
+            . '[a-zA-Z0-9]{3}' . '|' . '#[0-9]{3}' . '|'
+            . '[a-zA-Z0-9]{4}' . '|' . '#[0-9]{4}' . '|'
+            . '[a-zA-Z0-9]{5}' . '|'
+            . '[a-zA-Z0-9]{6}' . '|'
+            . '[a-zA-Z0-9]{7}'
+            . ')';
+
+        // map of emoticon replacements
+        $map = array(
+            '/(?<!mailto):D/'   => self::img_tag('smiley-laughing.gif',    ':D'    ),
+            '/:-D/'             => self::img_tag('smiley-laughing.gif',    ':-D'   ),
+            '/:\(/'             => self::img_tag('smiley-frown.gif',       ':('    ),
+            '/:-\(/'            => self::img_tag('smiley-frown.gif',       ':-('   ),
+            '/'.$entity.';\)/'  => self::img_tag('smiley-wink.gif',        ';)'    ),
+            '/'.$entity.';-\)/' => self::img_tag('smiley-wink.gif',        ';-)'   ),
+            '/8\)/'             => self::img_tag('smiley-cool.gif',        '8)'    ),
+            '/8-\)/'            => self::img_tag('smiley-cool.gif',        '8-)'   ),
+            '/(?<!mailto):O/i'  => self::img_tag('smiley-surprised.gif',   ':O'    ),
+            '/(?<!mailto):-O/i' => self::img_tag('smiley-surprised.gif',   ':-O'   ),
+            '/(?<!mailto):P/i'  => self::img_tag('smiley-tongue-out.gif',  ':P'    ),
+            '/(?<!mailto):-P/i' => self::img_tag('smiley-tongue-out.gif',  ':-P'   ),
+            '/(?<!mailto):@/i'  => self::img_tag('smiley-yell.gif',        ':@'    ),
+            '/(?<!mailto):-@/i' => self::img_tag('smiley-yell.gif',        ':-@'   ),
+            '/O:\)/i'           => self::img_tag('smiley-innocent.gif',    'O:)'   ),
+            '/O:-\)/i'          => self::img_tag('smiley-innocent.gif',    'O:-)'  ),
+            '/(?<!O):\)/'       => self::img_tag('smiley-smile.gif',       ':)'    ),
+            '/(?<!O):-\)/'      => self::img_tag('smiley-smile.gif',       ':-)'   ),
+            '/(?<!mailto):\$/'  => self::img_tag('smiley-embarassed.gif',  ':$'    ),
+            '/(?<!mailto):-\$/' => self::img_tag('smiley-embarassed.gif',  ':-$'   ),
+            '/(?<!mailto):\*/i'  => self::img_tag('smiley-kiss.gif',       ':*'    ),
+            '/(?<!mailto):-\*/i' => self::img_tag('smiley-kiss.gif',       ':-*'   ),
+            '/(?<!mailto):S/i'  => self::img_tag('smiley-undecided.gif',   ':S'    ),
+            '/(?<!mailto):-S/i' => self::img_tag('smiley-undecided.gif',   ':-S'   ),
+        );
+
+        return preg_replace(array_keys($map), array_values($map), $text);
+    }
+
+    protected static function img_tag($ico, $title)
+    {
+        return html::img(array('src' => './' . self::IMG_PATH . $ico, 'title' => $title));
+    }
+
+    /**
+     * Replace emoticon icons <img> 'src' attribute, so it can
+     * be replaced with real file by Mail_Mime.
+     *
+     * @param string &$html HTML content
+     *
+     * @return array List of image files
+     */
+    public static function replace(&$html)
+    {
+        // Replace this:
+        // <img src="http[s]://.../tinymce/plugins/emoticons/img/smiley-cool.gif" ... />
+        // with this:
+        // <img src="/path/on/server/.../tinymce/plugins/emoticons/img/smiley-cool.gif" ... />
+
+        $rcube      = rcube::get_instance();
+        $assets_dir = $rcube->config->get('assets_dir');
+        $path       = unslashify($assets_dir ?: INSTALL_PATH) . '/' . self::IMG_PATH;
+        $offset     = 0;
+        $images     = array();
+
+        // remove any null-byte characters before parsing
+        $html = preg_replace('/\x00/', '', $html);
+
+        if (preg_match_all('# src=[\'"]([^\'"]+)#', $html, $matches, PREG_OFFSET_CAPTURE)) {
+            foreach ($matches[1] as $m) {
+                // find emoticon image tags
+                if (preg_match('#'. self::IMG_PATH . '(.*)$#', $m[0], $imatches)) {
+                    $image_name = $imatches[1];
+
+                    // sanitize image name so resulting attachment doesn't leave images dir
+                    $image_name = preg_replace('/[^a-zA-Z0-9_\.\-]/i', '', $image_name);
+                    $image_file = $path . $image_name;
+
+                    // Add the same image only once
+                    $images[$image_name] = $image_file;
+
+                    $html    = substr_replace($html, $image_file, $m[1] + $offset, strlen($m[0]));
+                    $offset += strlen($image_file) - strlen($m[0]);
+                }
+            }
+        }
+
+        return $images;
+    }
+}
diff --git a/plugins/emoticons/localization/en_US.inc b/plugins/emoticons/localization/en_US.inc
new file mode 100644
index 0000000..04b1096
--- /dev/null
+++ b/plugins/emoticons/localization/en_US.inc
@@ -0,0 +1,23 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | plugins/emoticons/localization/<lang>.inc                             |
+ |                                                                       |
+ | Localization file of the Roundcube Webmail Emoticons plugin           |
+ | Copyright (C) 2012-2015, The Roundcube Dev Team                       |
+ |                                                                       |
+ | Licensed under the GNU General Public License version 3 or            |
+ | any later version with exceptions for skins & plugins.                |
+ | See the README file for a full license statement.                     |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+
+ For translation see https://www.transifex.com/projects/p/roundcube-webmail/resource/plugin-emoticons/
+*/
+
+$labels = array();
+$labels['emoticonsdisplay'] = 'Display emoticons in plain text messages';
+$labels['emoticonscompose'] = 'Enable emoticons';
+
+?>
diff --git a/plugins/emoticons/tests/Emoticons.php b/plugins/emoticons/tests/Emoticons.php
index 14c7fd0..bc21ceb 100644
--- a/plugins/emoticons/tests/Emoticons.php
+++ b/plugins/emoticons/tests/Emoticons.php
@@ -19,45 +19,4 @@
         $this->assertInstanceOf('emoticons', $plugin);
         $this->assertInstanceOf('rcube_plugin', $plugin);
     }
-
-    /**
-     * replace() method tests
-     */
-    function test_replace()
-    {
-        $rcube  = rcube::get_instance();
-        $plugin = new emoticons($rcube->api);
-
-        $map = array(
-            ':D'  => array('smiley-laughing.gif',    ':D'    ),
-            ':-D' => array('smiley-laughing.gif',    ':-D'   ),
-            ':('  => array('smiley-frown.gif',       ':('    ),
-            ':-(' => array('smiley-frown.gif',       ':-('   ),
-            '8)'  => array('smiley-cool.gif',        '8)'    ),
-            '8-)' => array('smiley-cool.gif',        '8-)'   ),
-            ':O'  => array('smiley-surprised.gif',   ':O'    ),
-            ':-O' => array('smiley-surprised.gif',   ':-O'   ),
-            ':P'  => array('smiley-tongue-out.gif',  ':P'    ),
-            ':-P' => array('smiley-tongue-out.gif',  ':-P'   ),
-            ':@'  => array('smiley-yell.gif',        ':@'    ),
-            ':-@' => array('smiley-yell.gif',        ':-@'   ),
-            'O:)' => array('smiley-innocent.gif',    'O:)'   ),
-            'O:-)' => array('smiley-innocent.gif',    'O:-)' ),
-            ':)'  => array('smiley-smile.gif',       ':)'    ),
-            ':-)' => array('smiley-smile.gif',       ':-)'   ),
-            ':$'  => array('smiley-embarassed.gif',  ':$'    ),
-            ':-$' => array('smiley-embarassed.gif',  ':-$'   ),
-            ':*'  => array('smiley-kiss.gif',       ':*'     ),
-            ':-*' => array('smiley-kiss.gif',       ':-*'    ),
-            ':S'  => array('smiley-undecided.gif',   ':S'    ),
-            ':-S' => array('smiley-undecided.gif',   ':-S'   ),
-        );
-
-        foreach ($map as $body => $expected) {
-            $args = array('type' => 'plain', 'body' => $body);
-            $args = $plugin->replace($args);
-            $this->assertRegExp('/' . preg_quote($expected[0], '/') . '/', $args['body']);
-            $this->assertRegExp('/title="' . preg_quote($expected[1], '/') . '"/', $args['body']);
-        }
-    }
 }
diff --git a/plugins/emoticons/tests/EmoticonsEngine.php b/plugins/emoticons/tests/EmoticonsEngine.php
new file mode 100644
index 0000000..f343450
--- /dev/null
+++ b/plugins/emoticons/tests/EmoticonsEngine.php
@@ -0,0 +1,48 @@
+<?php
+
+class EmoticonsEngine extends PHPUnit_Framework_TestCase
+{
+
+    function setUp()
+    {
+        include_once __DIR__ . '/../emoticons_engine.php';
+    }
+
+    /**
+     * text2icons() method tests
+     */
+    function test_text2icons()
+    {
+        $map = array(
+            ':D'  => array('smiley-laughing.gif',    ':D'    ),
+            ':-D' => array('smiley-laughing.gif',    ':-D'   ),
+            ':('  => array('smiley-frown.gif',       ':('    ),
+            ':-(' => array('smiley-frown.gif',       ':-('   ),
+            '8)'  => array('smiley-cool.gif',        '8)'    ),
+            '8-)' => array('smiley-cool.gif',        '8-)'   ),
+            ':O'  => array('smiley-surprised.gif',   ':O'    ),
+            ':-O' => array('smiley-surprised.gif',   ':-O'   ),
+            ':P'  => array('smiley-tongue-out.gif',  ':P'    ),
+            ':-P' => array('smiley-tongue-out.gif',  ':-P'   ),
+            ':@'  => array('smiley-yell.gif',        ':@'    ),
+            ':-@' => array('smiley-yell.gif',        ':-@'   ),
+            'O:)' => array('smiley-innocent.gif',    'O:)'   ),
+            'O:-)' => array('smiley-innocent.gif',    'O:-)' ),
+            ':)'  => array('smiley-smile.gif',       ':)'    ),
+            ':-)' => array('smiley-smile.gif',       ':-)'   ),
+            ':$'  => array('smiley-embarassed.gif',  ':$'    ),
+            ':-$' => array('smiley-embarassed.gif',  ':-$'   ),
+            ':*'  => array('smiley-kiss.gif',       ':*'     ),
+            ':-*' => array('smiley-kiss.gif',       ':-*'    ),
+            ':S'  => array('smiley-undecided.gif',   ':S'    ),
+            ':-S' => array('smiley-undecided.gif',   ':-S'   ),
+        );
+
+        foreach ($map as $body => $expected) {
+            $result = emoticons_engine::text2icons($body);
+
+            $this->assertRegExp('/' . preg_quote($expected[0], '/') . '/', $result);
+            $this->assertRegExp('/title="' . preg_quote($expected[1], '/') . '"/', $result);
+        }
+    }
+}
diff --git a/program/include/bc.php b/program/include/bc.php
index 47cca0d..259ebc3 100644
--- a/program/include/bc.php
+++ b/program/include/bc.php
@@ -220,11 +220,6 @@
     rcmail::get_instance()->html_editor($mode);
 }
 
-function rcmail_replace_emoticons($html)
-{
-    return rcmail::get_instance()->replace_emoticons($html);
-}
-
 function rcmail_deliver_message(&$message, $from, $mailto, &$smtp_error, &$body_file=null, $smtp_opts=null)
 {
     return rcmail::get_instance()->deliver_message($message, $from, $mailto, $smtp_error, $body_file, $smtp_opts);
diff --git a/program/include/rcmail.php b/program/include/rcmail.php
index 10a9d64..db5fce6 100644
--- a/program/include/rcmail.php
+++ b/program/include/rcmail.php
@@ -1858,7 +1858,20 @@
      */
     public function html_editor($mode = '')
     {
-        $hook = $this->plugins->exec_hook('html_editor', array('mode' => $mode));
+        $spellcheck       = intval($this->config->get('enable_spellcheck'));
+        $spelldict        = intval($this->config->get('spellcheck_dictionary'));
+        $disabled_plugins = array();
+        $disabled_buttons = array();
+
+        if (!$spellcheck) {
+            $disabled_plugins[] = 'spellchecker';
+        }
+
+        $hook = $this->plugins->exec_hook('html_editor', array(
+                'mode'             => $mode,
+                'disabled_plugins' => $disabled_plugins,
+                'disabled_buttons' => $disabled_buttons,
+        ));
 
         if ($hook['abort']) {
             return;
@@ -1885,8 +1898,10 @@
             'mode'       => $mode,
             'lang'       => $lang,
             'skin_path'  => $this->output->get_skin_path(),
-            'spellcheck' => intval($this->config->get('enable_spellcheck')),
-            'spelldict'  => intval($this->config->get('spellcheck_dictionary'))
+            'spellcheck' => $spellcheck, // deprecated
+            'spelldict'  => $spelldict,
+            'disabled_plugins' => $hook['disabled_plugins'],
+            'disabled_buttons' => $hook['disabled_buttons'],
         );
 
         $this->output->add_label('selectimage', 'addimage', 'selectmedia', 'addmedia');
@@ -1894,43 +1909,6 @@
         $this->output->include_css('program/js/tinymce/roundcube/browser.css');
         $this->output->include_script('tinymce/tinymce.min.js');
         $this->output->include_script('editor.js');
-    }
-
-    /**
-     * Replaces TinyMCE's emoticon images with plain-text representation
-     *
-     * @param string $html  HTML content
-     *
-     * @return string HTML content
-     */
-    public static function replace_emoticons($html)
-    {
-        $emoticons = array(
-            '8-)' => 'smiley-cool',
-            ':-#' => 'smiley-foot-in-mouth',
-            ':-*' => 'smiley-kiss',
-            ':-X' => 'smiley-sealed',
-            ':-P' => 'smiley-tongue-out',
-            ':-@' => 'smiley-yell',
-            ":'(" => 'smiley-cry',
-            ':-(' => 'smiley-frown',
-            ':-D' => 'smiley-laughing',
-            ':-)' => 'smiley-smile',
-            ':-S' => 'smiley-undecided',
-            ':-$' => 'smiley-embarassed',
-            'O:-)' => 'smiley-innocent',
-            ':-|' => 'smiley-money-mouth',
-            ':-O' => 'smiley-surprised',
-            ';-)' => 'smiley-wink',
-        );
-
-        foreach ($emoticons as $idx => $file) {
-            // <img title="Cry" src="http://.../program/js/tinymce/plugins/emoticons/img/smiley-cry.gif" border="0" alt="Cry" />
-            $search[]  = '/<img title="[a-z ]+" src="https?:\/\/[a-z0-9_.\/-]+\/tinymce\/plugins\/emoticons\/img\/'.$file.'.gif"[^>]+\/>/i';
-            $replace[] = $idx;
-        }
-
-        return preg_replace($search, $replace, $html);
     }
 
     /**
@@ -2319,6 +2297,39 @@
         return file_get_contents($name, false);
     }
 
+    /**
+     * Converts HTML content into plain text
+     *
+     * @param string $html    HTML content
+     * @param array  $options Conversion parameters (width, links, charset)
+     *
+     * @return string Plain text
+     */
+    public function html2text($html, $options)
+    {
+        $default_options = array(
+            'links'   => true,
+            'width'   => 75,
+            'body'    => $html,
+            'charset' => RCUBE_CHARSET,
+        );
+
+        $options = array_merge($default_options, $options);
+
+        // Plugins may want to modify HTML in another/additional way
+        $options = $this->plugins->exec_hook('html2text', $options);
+
+        // Convert to text
+        if (!$options['abort']) {
+            $converter = new rcube_html2text($options['body'],
+                false, $options['links'], $options['width'], $options['charset']);
+
+            $options['body'] = rtrim($converter->get_text());
+        }
+
+        return $options['body'];
+    }
+
 
     /************************************************************************
      *********          Deprecated methods (to be removed)          *********
diff --git a/program/js/editor.js b/program/js/editor.js
index 6446dc5..2415b7a 100644
--- a/program/js/editor.js
+++ b/program/js/editor.js
@@ -89,7 +89,7 @@
   else {
     $.extend(conf, {
       plugins: 'autolink charmap code colorpicker directionality emoticons link image media nonbreaking'
-        + ' paste table tabfocus textcolor searchreplace' + (config.spellcheck ? ' spellchecker' : ''),
+        + ' paste table tabfocus textcolor searchreplace spellchecker',
       toolbar: 'bold italic underline | alignleft aligncenter alignright alignjustify'
         + ' | bullist numlist outdent indent ltr rtl blockquote | forecolor backcolor | fontselect fontsizeselect'
         + ' | link unlink table | emoticons charmap image media | code searchreplace undo redo',
@@ -102,6 +102,15 @@
     });
   }
 
+  // disable TinyMCE plugins/buttons from Roundcube plugin
+  $.each(config.disabled_plugins || [], function() {
+    conf.plugins = conf.plugins.replace(this, '');
+  });
+  $.each(config.disabled_plugins || [], function() {
+    conf.toolbar = conf.toolbar.replace(this, '');
+  });
+  conf.toolbar = conf.toolbar.replace(/\|\s+\|/g, '|');
+
   // support external configuration settings e.g. from skin
   if (window.rcmail_editor_settings)
     $.extend(conf, window.rcmail_editor_settings);
diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc
index 402b32c..85e5e27 100644
--- a/program/steps/mail/compose.inc
+++ b/program/steps/mail/compose.inc
@@ -499,8 +499,7 @@
                 $text = $html = $sql_arr['signature'];
 
                 if ($sql_arr['html_signature']) {
-                    $h2t  = new rcube_html2text($html, false, true);
-                    $text = trim($h2t->get_text());
+                    $text = $RCMAIL->html2text($html);
                 }
                 else {
                     $t2h  = new rcube_text2html($text, false);
@@ -874,9 +873,8 @@
         if ($part->ctype_secondary == 'html') {
             // use html part if it has been used for message (pre)viewing
             // decrease line length for quoting
-            $len = $COMPOSE['mode'] == RCUBE_COMPOSE_REPLY ? $LINE_LENGTH-2 : $LINE_LENGTH;
-            $txt = new rcube_html2text($body, false, true, $len);
-            $body = $txt->get_text();
+            $len  = $COMPOSE['mode'] == RCUBE_COMPOSE_REPLY ? $LINE_LENGTH-2 : $LINE_LENGTH;
+            $body = $RCMAIL->html2text($body, array('width' => $len));
         }
         else {
             if ($part->ctype_secondary == 'plain' && $part->ctype_parameters['format'] == 'flowed') {
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index 37298f5..6ba8ce1 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -884,8 +884,7 @@
             $data['body'] = rcube_enriched::to_html($data['body']);
         }
 
-        $txt  = new rcube_html2text($data['body'], false, true);
-        $body = $txt->get_text();
+        $body = $RCMAIL->html2text($data['body']);
         $part->ctype_secondary = 'plain';
     }
     // text/html
diff --git a/program/steps/mail/sendmail.inc b/program/steps/mail/sendmail.inc
index c791c3d..7b74e88 100644
--- a/program/steps/mail/sendmail.inc
+++ b/program/steps/mail/sendmail.inc
@@ -398,12 +398,8 @@
 
     $MAIL_MIME->setHTMLBody($plugin['body']);
 
-    // replace emoticons
-    $plugin['body'] = $RCMAIL->replace_emoticons($plugin['body']);
-
-    // add a plain text version of the e-mail as an alternative part.
-    $h2t = new rcube_html2text($plugin['body'], false, true, 0, $message_charset);
-    $plainTextPart = rcube_mime::wordwrap($h2t->get_text(), $LINE_LENGTH, "\r\n", false, $message_charset);
+    $plainTextPart = $RCMAIL->html2text($plugin['body'], array('width' => 0, 'charset' => $message_charset));
+    $plainTextPart = rcube_mime::wordwrap($plainTextPart, $LINE_LENGTH, "\r\n", false, $message_charset);
     $plainTextPart = wordwrap($plainTextPart, 998, "\r\n", true);
 
     // make sure all line endings are CRLF (#1486712)
@@ -412,11 +408,8 @@
     $plugin = $RCMAIL->plugins->exec_hook('message_outgoing_body',
         array('body' => $plainTextPart, 'type' => 'alternative', 'message' => $MAIL_MIME));
 
+    // add a plain text version of the e-mail as an alternative part.
     $MAIL_MIME->setTXTBody($plugin['body']);
-
-    // look for "emoticon" images from TinyMCE and change their src paths to
-    // be file paths on the server instead of URL paths.
-    rcmail_fix_emoticon_paths($MAIL_MIME);
 
     // Extract image Data URIs into message attachments (#1488502)
     rcmail_extract_inline_images($MAIL_MIME, $from);
@@ -763,61 +756,6 @@
     }
 
     return false;
-}
-
-/**
- * go from this:
- * <img src="http[s]://.../tinymce/plugins/emoticons/img/smiley-cool.gif" border="0" alt="Cool" title="Cool" />
- *
- * to this:
- *
- * <img src="/path/on/server/.../tinymce/plugins/emoticons/img/smiley-cool.gif" border="0" alt="Cool" title="Cool" />
- */
-function rcmail_fix_emoticon_paths($mime_message)
-{
-    global $RCMAIL;
-
-    $body = $mime_message->getHTMLBody();
-
-    // remove any null-byte characters before parsing
-    $body = preg_replace('/\x00/', '', $body);
-
-    $searchstr  = 'program/js/tinymce/plugins/emoticons/img/';
-    $assets_dir = $RCMAIL->config->get('assets_dir');
-    $path       = ($assets_dir ?: INSTALL_PATH) . '/' . $searchstr;
-    $offset     = 0;
-
-    // keep track of added images, so they're only added once
-    $included_images = array();
-
-    if (preg_match_all('# src=[\'"]([^\'"]+)#', $body, $matches, PREG_OFFSET_CAPTURE)) {
-        foreach ($matches[1] as $m) {
-            // find emoticon image tags
-            if (preg_match('#'.$searchstr.'(.*)$#', $m[0], $imatches)) {
-                $image_name = $imatches[1];
-
-                // sanitize image name so resulting attachment doesn't leave images dir
-                $image_name = preg_replace('/[^a-zA-Z0-9_\.\-]/i', '', $image_name);
-                $img_file   = $path . $image_name;
-
-                if (!in_array($image_name, $included_images)) {
-                    // add the image to the MIME message
-                    $res = $mime_message->addHTMLImage($img_file, 'image/gif', '', true, $image_name);
-                    if (is_a($res, 'PEAR_Error')) {
-                        $RCMAIL->output->show_message("emoticonerror", 'error');
-                        continue;
-                    }
-
-                    array_push($included_images, $image_name);
-                }
-
-                $body    = substr_replace($body, $img_file, $m[1] + $offset, strlen($m[0]));
-                $offset += strlen($img_file) - strlen($m[0]);
-            }
-        }
-    }
-
-    $mime_message->setHTMLBody($body);
 }
 
 /**
diff --git a/program/steps/utils/html2text.inc b/program/steps/utils/html2text.inc
index 7fa8340..251eaf7 100644
--- a/program/steps/utils/html2text.inc
+++ b/program/steps/utils/html2text.inc
@@ -26,15 +26,11 @@
     $html = stripslashes($html);
 }
 
-// Replace emoticon images with its text representation
-$html = $RCMAIL->replace_emoticons($html);
+$params['links'] = (bool) rcube_utils::get_input_value('_do_links', rcube_utils::INPUT_GET);
+$params['width'] = (int) rcube_utils::get_input_value('_width', rcube_utils::INPUT_GET);
 
-$do_links = (bool) rcube_utils::get_input_value('_do_links', rcube_utils::INPUT_GET);
-$width    = (int) rcube_utils::get_input_value('_width', rcube_utils::INPUT_GET);
+$text = $RCMAIL->html2text($html, $params);
 
-// Convert to text
-$converter = new rcube_html2text($html, false, $do_links, $width);
-
-header('Content-Type: text/plain; charset=UTF-8');
-print rtrim($converter->get_text());
+header('Content-Type: text/plain; charset=' . RCUBE_CHARSET);
+print $text;
 exit;
diff --git a/tests/phpunit.xml b/tests/phpunit.xml
index 07d8d41..bb2e9bc 100644
--- a/tests/phpunit.xml
+++ b/tests/phpunit.xml
@@ -60,6 +60,7 @@
             <file>./../plugins/database_attachments/tests/DatabaseAttachments.php</file>
             <file>./../plugins/debug_logger/tests/DebugLogger.php</file>
             <file>./../plugins/emoticons/tests/Emoticons.php</file>
+            <file>./../plugins/emoticons/tests/EmoticonsEngine.php</file>
             <file>./../plugins/enigma/tests/Enigma.php</file>
             <file>./../plugins/example_addressbook/tests/ExampleAddressbook.php</file>
             <file>./../plugins/filesystem_attachments/tests/FilesystemAttachments.php</file>

--
Gitblit v1.9.1