diff --git a/lib/Config/Menu.php b/lib/Config/Menu.php index 1d84494b35..60849a541f 100644 --- a/lib/Config/Menu.php +++ b/lib/Config/Menu.php @@ -59,6 +59,11 @@ class Menu { if(self::isOnMailPoetAdminPage()) { do_action('mailpoet_conflict_resolver_styles'); do_action('mailpoet_conflict_resolver_scripts'); + + if($_REQUEST['page'] === 'mailpoet-newsletter-editor') { + // Disable WP emojis to not interfere with the newsletter editor emoji handling + $this->disableWPEmojis(); + } } // Main page @@ -314,6 +319,11 @@ class Menu { ); } + function disableWPEmojis() { + remove_action('admin_print_scripts', 'print_emoji_detection_script'); + remove_action('admin_print_styles', 'print_emoji_styles'); + } + function welcome() { if((bool)(defined('DOING_AJAX') && DOING_AJAX)) return; diff --git a/lib/Models/Newsletter.php b/lib/Models/Newsletter.php index ec7914d21b..077762d82e 100644 --- a/lib/Models/Newsletter.php +++ b/lib/Models/Newsletter.php @@ -4,6 +4,7 @@ use Carbon\Carbon; use MailPoet\Newsletter\Renderer\Renderer; use MailPoet\Util\Helpers; use MailPoet\Util\Security; +use MailPoet\WP\Emoji; if(!defined('ABSPATH')) exit; @@ -79,11 +80,15 @@ class Newsletter extends Model { $this->set_expr('deleted_at', 'NULL'); } - $this->set('body', - is_array($this->body) - ? json_encode($this->body) - : $this->body - ); + if(isset($this->body)) { + if(is_array($this->body)) { + $this->body = json_encode($this->body); + } + $this->set( + 'body', + Emoji::encodeForUTF8Column(self::$_table, 'body', $this->body) + ); + } $this->set('hash', ($this->hash) diff --git a/lib/Models/SendingQueue.php b/lib/Models/SendingQueue.php index bc4b2fdafc..39073cce1d 100644 --- a/lib/Models/SendingQueue.php +++ b/lib/Models/SendingQueue.php @@ -1,6 +1,8 @@ set('subscribers', serialize($this->subscribers)); } if(!is_serialized($this->newsletter_rendered_body) && !is_null($this->newsletter_rendered_body)) { - $this->set('newsletter_rendered_body', serialize($this->newsletter_rendered_body)); + $this->set( + 'newsletter_rendered_body', + serialize($this->encodeEmojisInBody($this->newsletter_rendered_body)) + ); } // set the default priority to medium if(!$this->priority) { @@ -81,12 +86,34 @@ class SendingQueue extends Model { function getNewsletterRenderedBody($type = false) { $rendered_newsletter = (!is_serialized($this->newsletter_rendered_body)) ? $this->newsletter_rendered_body : - unserialize($this->newsletter_rendered_body); + $this->decodeEmojisInBody(unserialize($this->newsletter_rendered_body)); return ($type && !empty($rendered_newsletter[$type])) ? $rendered_newsletter[$type] : $rendered_newsletter; } + function encodeEmojisInBody($newsletter_rendered_body) { + if(is_array($newsletter_rendered_body)) { + foreach($newsletter_rendered_body as $key => $value) { + $newsletter_rendered_body[$key] = Emoji::encodeForUTF8Column( + self::$_table, + 'newsletter_rendered_body', + $value + ); + } + } + return $newsletter_rendered_body; + } + + function decodeEmojisInBody($newsletter_rendered_body) { + if(is_array($newsletter_rendered_body)) { + foreach($newsletter_rendered_body as $key => $value) { + $newsletter_rendered_body[$key] = Emoji::decodeEntities($value); + } + } + return $newsletter_rendered_body; + } + function isSubscriberProcessed($subscriber_id) { $subscribers = $this->getSubscribers(); return in_array($subscriber_id, $subscribers['processed']); diff --git a/lib/WP/Emoji.php b/lib/WP/Emoji.php new file mode 100644 index 0000000000..c45e32d37f --- /dev/null +++ b/lib/WP/Emoji.php @@ -0,0 +1,32 @@ +get_col_charset($table, $field); + if($charset === 'utf8') { + $value = wp_encode_emoji($value); + } + return $value; + } + + static function decodeEntities($content) { + // Based on wp_staticize_emoji() + + // Loosely match the Emoji Unicode range. + $regex = '/(&#x[2-3][0-9a-f]{3};|[1-6][0-9a-f]{2};)/'; + + $matches = array(); + if(preg_match_all($regex, $content, $matches)) { + if(!empty($matches[1])) { + foreach($matches[1] as $emoji) { + $entity = html_entity_decode($emoji, ENT_COMPAT, 'UTF-8'); + $content = str_replace($emoji, $entity, $content); + } + } + } + + return $content; + } +} diff --git a/tests/unit/Models/SendingQueueTest.php b/tests/unit/Models/SendingQueueTest.php new file mode 100644 index 0000000000..223b31e09c --- /dev/null +++ b/tests/unit/Models/SendingQueueTest.php @@ -0,0 +1,42 @@ +queue = SendingQueue::create(); + $this->queue->save(); + + $this->rendered_body = array( + 'html' => 'some html', + 'text' => 'some text' + ); + } + + function testItCanEncodeEmojisInBody() { + $mock = Mock::double('MailPoet\WP\Emoji', [ + 'encodeForUTF8Column' => function($params) { + return $params; + } + ]); + $this->queue->encodeEmojisInBody($this->rendered_body); + $mock->verifyInvokedMultipleTimes('encodeForUTF8Column', 2); + } + + function testItCanDecodeEmojisInBody() { + $mock = Mock::double('MailPoet\WP\Emoji', [ + 'decodeEntities' => function($params) { + return $params; + } + ]); + $this->queue->decodeEmojisInBody($this->rendered_body); + $mock->verifyInvokedMultipleTimes('decodeEntities', 2); + } + + function _after() { + Mock::clean(); + \ORM::raw_execute('TRUNCATE ' . SendingQueue::$_table); + } +} diff --git a/tests/unit/WP/EmojiTest.php b/tests/unit/WP/EmojiTest.php new file mode 100644 index 0000000000..089549f6f5 --- /dev/null +++ b/tests/unit/WP/EmojiTest.php @@ -0,0 +1,51 @@ +data_encoded = "Emojis: 😃😵💪, not emojis: .Ž"; + $this->data_decoded = "Emojis: 😃😵💪, not emojis: .Ž"; + + $this->column = 'dummycol'; + } + + function testItCanEncodeForUTF8Column() { + $table = Env::$db_prefix . 'dummytable_utf8'; + $this->createTable($table, 'utf8'); + + $result = Emoji::encodeForUTF8Column($table, $this->column, $this->data_decoded); + expect($result)->equals($this->data_encoded); + + $this->dropTable($table); + } + + function testItDoesNotEncodeForUTF8MB4Column() { + $table = Env::$db_prefix . 'dummytable_utf8mb4'; + $this->createTable($table, 'utf8mb4'); + + $result = Emoji::encodeForUTF8Column($table, $this->column, $this->data_decoded); + expect($result)->equals($this->data_decoded); + + $this->dropTable($table); + } + + function testItCanDecodeEntities() { + $result = Emoji::decodeEntities($this->data_encoded); + expect($result)->equals($this->data_decoded); + } + + private function createTable($table, $charset) { + \ORM::raw_execute( + 'CREATE TABLE IF NOT EXISTS ' . $table + . ' (' . $this->column . ' TEXT) ' + . 'DEFAULT CHARSET=' . $charset . ';' + ); + } + + private function dropTable($table) { + \ORM::raw_execute('DROP TABLE IF EXISTS ' . $table); + } +}