From a63f14ec4045e82f47b237663bcf09939a0eadc5 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Sat, 29 Aug 2015 07:52:57 +0200 Subject: [PATCH] Emoticons-related code refactoring - Emoticons: All emoticons-related functionality is handled by the plugin now - Emoticons: Added option to switch on/off emoticons in compose editor (#1485732) - Emoticons: Added option to switch on/off emoticons in plain text messages - Plugin API: Added disabled_plugins an disabled_buttons options in html_editor hook - Plugin API: Added html2text hook --- CHANGELOG | 5 + plugins/emoticons/composer.json | 4 +- plugins/emoticons/config.inc.php.dist | 7 + plugins/emoticons/emoticons.php | 208 +++++++++++++++----- plugins/emoticons/emoticons_engine.php | 152 ++++++++++++++ plugins/emoticons/localization/en_US.inc | 23 +++ plugins/emoticons/tests/Emoticons.php | 41 ---- plugins/emoticons/tests/EmoticonsEngine.php | 48 +++++ program/include/bc.php | 5 - program/include/rcmail.php | 91 +++++---- program/js/editor.js | 11 +- program/steps/mail/compose.inc | 8 +- program/steps/mail/func.inc | 3 +- program/steps/mail/sendmail.inc | 68 +------ program/steps/utils/html2text.inc | 14 +- tests/phpunit.xml | 1 + 16 files changed, 469 insertions(+), 220 deletions(-) create mode 100644 plugins/emoticons/config.inc.php.dist create mode 100644 plugins/emoticons/emoticons_engine.php create mode 100644 plugins/emoticons/localization/en_US.inc create mode 100644 plugins/emoticons/tests/EmoticonsEngine.php diff --git a/CHANGELOG b/CHANGELOG index 5ddd45dfb..6e72efbd1 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 @@ CHANGELOG Roundcube Webmail - 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 d1679cfe9..72f160217 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 000000000..1af6f6763 --- /dev/null +++ b/plugins/emoticons/config.inc.php.dist @@ -0,0 +1,7 @@ +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 () + */ + function message_part_after($args) { - // This is a lookbehind assertion which will exclude html entities - // E.g. situation when ";)" in "")" shouldn't be replaced by the icon - // It's so long because of assertion format restrictions - $entity = '(? $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-)' ), - '/(? $this->img_tag('smiley-surprised.gif', ':O' ), - '/(? $this->img_tag('smiley-surprised.gif', ':-O' ), - '/(? $this->img_tag('smiley-tongue-out.gif', ':P' ), - '/(? $this->img_tag('smiley-tongue-out.gif', ':-P' ), - '/(? $this->img_tag('smiley-yell.gif', ':@' ), - '/(? $this->img_tag('smiley-yell.gif', ':-@' ), - '/O:\)/i' => $this->img_tag('smiley-innocent.gif', 'O:)' ), - '/O:-\)/i' => $this->img_tag('smiley-innocent.gif', 'O:-)' ), - '/(? $this->img_tag('smiley-smile.gif', ':)' ), - '/(? $this->img_tag('smiley-smile.gif', ':-)' ), - '/(? $this->img_tag('smiley-embarassed.gif', ':$' ), - '/(? $this->img_tag('smiley-embarassed.gif', ':-$' ), - '/(? $this->img_tag('smiley-kiss.gif', ':*' ), - '/(? $this->img_tag('smiley-kiss.gif', ':-*' ), - '/(? $this->img_tag('smiley-undecided.gif', ':S' ), - '/(? $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 000000000..4d534cdc1 --- /dev/null +++ b/plugins/emoticons/emoticons_engine.php @@ -0,0 +1,152 @@ + '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) { + // Cry + $file = preg_quote(self::IMG_PATH . $file . '.gif', '/'); + $search[] = '/]+\/>/i'; + $replace[] = $idx; + } + + return preg_replace($search, $replace, $html); + } + + /** + * Replace common plain text emoticons with empticon 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 "")" shouldn't be replaced by the icon + // It's so long because of assertion format restrictions + $entity = '(? 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-)' ), + '/(? self::img_tag('smiley-surprised.gif', ':O' ), + '/(? self::img_tag('smiley-surprised.gif', ':-O' ), + '/(? self::img_tag('smiley-tongue-out.gif', ':P' ), + '/(? self::img_tag('smiley-tongue-out.gif', ':-P' ), + '/(? self::img_tag('smiley-yell.gif', ':@' ), + '/(? self::img_tag('smiley-yell.gif', ':-@' ), + '/O:\)/i' => self::img_tag('smiley-innocent.gif', 'O:)' ), + '/O:-\)/i' => self::img_tag('smiley-innocent.gif', 'O:-)' ), + '/(? self::img_tag('smiley-smile.gif', ':)' ), + '/(? self::img_tag('smiley-smile.gif', ':-)' ), + '/(? self::img_tag('smiley-embarassed.gif', ':$' ), + '/(? self::img_tag('smiley-embarassed.gif', ':-$' ), + '/(? self::img_tag('smiley-kiss.gif', ':*' ), + '/(? self::img_tag('smiley-kiss.gif', ':-*' ), + '/(? self::img_tag('smiley-undecided.gif', ':S' ), + '/(? 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 '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: + // + // with this: + // + + $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 000000000..04b10964d --- /dev/null +++ b/plugins/emoticons/localization/en_US.inc @@ -0,0 +1,23 @@ +.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 14c7fd0e6..bc21ceb1d 100644 --- a/plugins/emoticons/tests/Emoticons.php +++ b/plugins/emoticons/tests/Emoticons.php @@ -19,45 +19,4 @@ class Emoticons_Plugin extends PHPUnit_Framework_TestCase $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 000000000..f343450e6 --- /dev/null +++ b/plugins/emoticons/tests/EmoticonsEngine.php @@ -0,0 +1,48 @@ + 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 47cca0d78..259ebc32d 100644 --- a/program/include/bc.php +++ b/program/include/bc.php @@ -220,11 +220,6 @@ function rcube_html_editor($mode='') 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 10a9d64f8..db5fce617 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -1858,7 +1858,20 @@ class rcmail extends rcube */ 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 @@ class rcmail extends rcube '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'); @@ -1896,43 +1911,6 @@ class rcmail extends rcube $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) { - // Cry - $search[] = '/]+\/>/i'; - $replace[] = $idx; - } - - return preg_replace($search, $replace, $html); - } - /** * File upload progress handler. */ @@ -2319,6 +2297,39 @@ class rcmail extends rcube 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 6446dc56a..2415b7a3b 100644 --- a/program/js/editor.js +++ b/program/js/editor.js @@ -89,7 +89,7 @@ function rcube_text_editor(config, id) 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 @@ function rcube_text_editor(config, id) }); } + // 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 402b32cf3..85e5e27d2 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -499,8 +499,7 @@ function rcmail_compose_header_from($attrib) $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 @@ function rcmail_compose_part_body($part, $isHtml = false) 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 37298f54b..6ba8ce136 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -884,8 +884,7 @@ function rcmail_print_body($body, $part, $p = array()) $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 c791c3d41..7b74e88c3 100644 --- a/program/steps/mail/sendmail.inc +++ b/program/steps/mail/sendmail.inc @@ -398,12 +398,8 @@ if ($isHtml) { $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,12 +408,9 @@ if ($isHtml) { $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); } @@ -765,61 +758,6 @@ function rcmail_get_identity($id) return false; } -/** - * go from this: - * Cool - * - * to this: - * - * 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); -} - /** * Extract image attachments from HTML content (data URIs) */ diff --git a/program/steps/utils/html2text.inc b/program/steps/utils/html2text.inc index 7fa834063..251eaf7c4 100644 --- a/program/steps/utils/html2text.inc +++ b/program/steps/utils/html2text.inc @@ -26,15 +26,11 @@ if (get_magic_quotes_gpc() || get_magic_quotes_runtime()) { $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 07d8d4181..bb2e9bc2c 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -60,6 +60,7 @@ ./../plugins/database_attachments/tests/DatabaseAttachments.php ./../plugins/debug_logger/tests/DebugLogger.php ./../plugins/emoticons/tests/Emoticons.php + ./../plugins/emoticons/tests/EmoticonsEngine.php ./../plugins/enigma/tests/Enigma.php ./../plugins/example_addressbook/tests/ExampleAddressbook.php ./../plugins/filesystem_attachments/tests/FilesystemAttachments.php