diff --git a/filter/emoticon/filter.php b/filter/emoticon/filter.php index d579bf65b9f..5f5b3ff5dc0 100644 --- a/filter/emoticon/filter.php +++ b/filter/emoticon/filter.php @@ -32,6 +32,22 @@ defined('MOODLE_INTERNAL') || die(); class filter_emoticon extends moodle_text_filter { + /** + * Internal cache used for replacing. Multidimensional array; + * - dimension 1: language, + * - dimension 2: theme. + * @var array + */ + protected static $emoticontexts = array(); + + /** + * Internal cache used for replacing. Multidimensional array; + * - dimension 1: language, + * - dimension 2: theme. + * @var array + */ + protected static $emoticonimgs = array(); + /** * Apply the filter to the text * @@ -49,7 +65,7 @@ class filter_emoticon extends moodle_text_filter { return $text; } if (in_array($options['originalformat'], explode(',', get_config('filter_emoticon', 'formats')))) { - $this->replace_emoticons($text); + return $this->replace_emoticons($text); } return $text; } @@ -62,51 +78,73 @@ class filter_emoticon extends moodle_text_filter { * Replace emoticons found in the text with their images * * @param string $text to modify - * @return void + * @return string the modified result */ - protected function replace_emoticons(&$text) { + protected function replace_emoticons($text) { global $CFG, $OUTPUT, $PAGE; - static $emoticontexts = array(); // internal cache used for replacing - static $emoticonimgs = array(); // internal cache used for replacing $lang = current_language(); $theme = $PAGE->theme->name; - if (!isset($emoticontexts[$lang][$theme]) or !isset($emoticonimgs[$lang][$theme])) { + if (!isset(self::$emoticontexts[$lang][$theme]) or !isset(self::$emoticonimgs[$lang][$theme])) { // prepare internal caches $manager = get_emoticon_manager(); $emoticons = $manager->get_emoticons(); - $emoticontexts[$lang][$theme] = array(); - $emoticonimgs[$lang][$theme] = array(); + self::$emoticontexts[$lang][$theme] = array(); + self::$emoticonimgs[$lang][$theme] = array(); foreach ($emoticons as $emoticon) { - $emoticontexts[$lang][$theme][] = $emoticon->text; - $emoticonimgs[$lang][$theme][] = $OUTPUT->render($manager->prepare_renderable_emoticon($emoticon)); + self::$emoticontexts[$lang][$theme][] = $emoticon->text; + self::$emoticonimgs[$lang][$theme][] = $OUTPUT->render($manager->prepare_renderable_emoticon($emoticon)); } unset($emoticons); } - if (empty($emoticontexts[$lang][$theme])) { // No emoticons defined, nothing to process here - return; + if (empty(self::$emoticontexts[$lang][$theme])) { // No emoticons defined, nothing to process here. + return $text; } - // detect all the ", + 'format' => FORMAT_HTML, + 'expected' => "", + ], + 'Basic nolink should not be processed' => [ + 'input' => '(n)', + 'format' => FORMAT_HTML, + 'expected' => '(n)', + ], + 'Nested nolink should not be processed' => [ + 'input' => '(n)(n)', + 'format' => FORMAT_HTML, + 'expected' => '(n)(n)', + ], + 'Nested nolink should not be processed but following emoticon' => [ + 'input' => '(n)(n)(n)', + 'format' => FORMAT_HTML, + 'expected' => '(n)(n)' . $this->get_converted_content_for_emoticon('(n)'), + ], + ]; + } + + /** + * Translate the text for a single emoticon into the rendered value. + * + * @param string $text The text to translate. + * @return string + */ + public function get_converted_content_for_emoticon($text) { + global $OUTPUT; + $manager = get_emoticon_manager(); + $emoticons = $manager->get_emoticons(); + foreach ($emoticons as $emoticon) { + if ($emoticon->text == $text) { + return $OUTPUT->render($manager->prepare_renderable_emoticon($emoticon)); + } + } + + return $text; + } + + /** + * Tests the filter doesn't break anything if activated but invalid format passed. + * + */ + public function test_filter_invalidformat() { + global $PAGE; + $this->resetAfterTest(); + + $filter = new testable_filter_emoticon(); + $input = '(grr)'; $expected = '(grr)'; - $options = array('originalformat' => FORMAT_MOODLE); // Only FORMAT_HTML is filtered, see {@link testable_filter_emoticon}. - $this->assertEquals($expected, $filter->filter('(grr)', $options)); - $options = array('originalformat' => FORMAT_MARKDOWN); // Only FORMAT_HTML is filtered, see {@link testable_filter_emoticon}. - $this->assertEquals($expected, $filter->filter('(grr)', $options)); + $this->assertEquals($expected, $filter->filter($input, [ + 'originalformat' => 'ILLEGALFORMAT', + ])); + } - $options = array('originalformat' => FORMAT_PLAIN); // Only FORMAT_HTML is filtered, see {@link testable_filter_emoticon}. - $this->assertEquals($expected, $filter->filter('(grr)', $options)); + /** + * Tests the filter doesn't break anything if activated but no emoticons available. + * + */ + public function test_filter_emptyemoticons() { + global $CFG; + $this->resetAfterTest(); + // Empty the emoticons array. + $CFG->emoticons = null; - // And texts matching target formats are filtered. - $expected = 'angry'; - $options = array('originalformat' => FORMAT_HTML); // Only FORMAT_HTML is filtered, see {@link testable_filter_emoticon}. - $this->assertEquals($expected, $filter->filter('(grr)', $options)); + $filter = new filter_emoticon(context_system::instance(), array('originalformat' => FORMAT_HTML)); + + $input = '(grr)'; + $expected = '(grr)'; + + $this->assertEquals($expected, $filter->filter($input, [ + 'originalformat' => FORMAT_HTML, + ])); } } @@ -65,6 +160,9 @@ class filter_emoticon_testcase extends advanced_testcase { */ class testable_filter_emoticon extends filter_emoticon { public function __construct() { + // Reset static emoticon caches. + parent::$emoticontexts = array(); + parent::$emoticonimgs = array(); // Use this context for filtering. $this->context = context_system::instance(); // Define FORMAT_HTML as only one filtering in DB.