diff --git a/.upgradenotes/MDL-81920-2024061811464967.yml b/.upgradenotes/MDL-81920-2024061811464967.yml new file mode 100644 index 00000000000..c3b7b437bd9 --- /dev/null +++ b/.upgradenotes/MDL-81920-2024061811464967.yml @@ -0,0 +1,12 @@ +issueNumber: MDL-81920 +notes: + core: + - message: > + The following classes have been moved into the `\core` namespace and now + support autoloading: + + + - `emoticon_manager` + + - `lang_string` + type: improved diff --git a/lib/classes/emoticon_manager.php b/lib/classes/emoticon_manager.php new file mode 100644 index 00000000000..44351c022bd --- /dev/null +++ b/lib/classes/emoticon_manager.php @@ -0,0 +1,191 @@ +. + +namespace core; + +use pix_emoticon; +use stdClass; + +/** + * Provides core support for plugins that have to deal with emoticons (like HTML editor or emoticon filter). + * + * Whenever this manager mentiones 'emoticon object', the following data structure is expected: + * stdClass with properties text, imagename, imagecomponent, altidentifier and altcomponent + * + * @see \admin_setting_emoticons + * + * @package core + * @copyright 2010 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class emoticon_manager { + + /** + * Returns the currently enabled emoticons + * + * @param bool $selectable If true, only return emoticons that should be selectable from a list + * @return stdClass[] array of emoticon objects + */ + public function get_emoticons( + bool $selectable = false, + ): array { + global $CFG; + + $notselectable = ['martin', 'egg']; + + if (empty($CFG->emoticons)) { + return []; + } + + $emoticons = $this->decode_stored_config($CFG->emoticons); + + if (!is_array($emoticons)) { + // Something is wrong with the format of stored setting. + debugging('Invalid format of emoticons setting, please resave the emoticons settings form', DEBUG_NORMAL); + return []; + } + + if ($selectable) { + foreach ($emoticons as $index => $emote) { + if (in_array($emote->altidentifier, $notselectable)) { + // Skip this one. + unset($emoticons[$index]); + } + } + } + + return $emoticons; + } + + /** + * Converts emoticon object into renderable pix_emoticon object + * + * @param stdClass $emoticon emoticon object + * @param array $attributes explicit HTML attributes to set + * @return pix_emoticon + */ + public function prepare_renderable_emoticon( + stdClass $emoticon, + array $attributes = [], + ): pix_emoticon { + $stringmanager = get_string_manager(); + if ($stringmanager->string_exists($emoticon->altidentifier, $emoticon->altcomponent)) { + $alt = get_string($emoticon->altidentifier, $emoticon->altcomponent); + } else { + $alt = s($emoticon->text); + } + return new pix_emoticon($emoticon->imagename, $alt, $emoticon->imagecomponent, $attributes); + } + + /** + * Encodes the array of emoticon objects into a string storable in config table + * + * @param stdClass[] $emoticons array of emoticon objects + * @return string + */ + public function encode_stored_config( + array $emoticons, + ): string { + return json_encode($emoticons); + } + + /** + * Decodes the string into an array of emoticon objects + * + * @param string $encoded + * @return ?array + */ + public function decode_stored_config( + string $encoded, + ): ?array { + $decoded = json_decode($encoded); + if (!is_array($decoded)) { + return null; + } + return $decoded; + } + + /** + * Returns default set of emoticons supported by Moodle + * + * @return stdClass[] array of emoticon objects + */ + public function default_emoticons(): array { + return [ + $this->prepare_emoticon_object(":-)", 's/smiley', 'smiley'), + $this->prepare_emoticon_object(":)", 's/smiley', 'smiley'), + $this->prepare_emoticon_object(":-D", 's/biggrin', 'biggrin'), + $this->prepare_emoticon_object(";-)", 's/wink', 'wink'), + $this->prepare_emoticon_object(":-/", 's/mixed', 'mixed'), + $this->prepare_emoticon_object("V-.", 's/thoughtful', 'thoughtful'), + $this->prepare_emoticon_object(":-P", 's/tongueout', 'tongueout'), + $this->prepare_emoticon_object(":-p", 's/tongueout', 'tongueout'), + $this->prepare_emoticon_object("B-)", 's/cool', 'cool'), + $this->prepare_emoticon_object("^-)", 's/approve', 'approve'), + $this->prepare_emoticon_object("8-)", 's/wideeyes', 'wideeyes'), + $this->prepare_emoticon_object(":o)", 's/clown', 'clown'), + $this->prepare_emoticon_object(":-(", 's/sad', 'sad'), + $this->prepare_emoticon_object(":(", 's/sad', 'sad'), + $this->prepare_emoticon_object("8-.", 's/shy', 'shy'), + $this->prepare_emoticon_object(":-I", 's/blush', 'blush'), + $this->prepare_emoticon_object(":-X", 's/kiss', 'kiss'), + $this->prepare_emoticon_object("8-o", 's/surprise', 'surprise'), + $this->prepare_emoticon_object("P-|", 's/blackeye', 'blackeye'), + $this->prepare_emoticon_object("8-[", 's/angry', 'angry'), + $this->prepare_emoticon_object("(grr)", 's/angry', 'angry'), + $this->prepare_emoticon_object("xx-P", 's/dead', 'dead'), + $this->prepare_emoticon_object("|-.", 's/sleepy', 'sleepy'), + $this->prepare_emoticon_object("}-]", 's/evil', 'evil'), + $this->prepare_emoticon_object("(h)", 's/heart', 'heart'), + $this->prepare_emoticon_object("(heart)", 's/heart', 'heart'), + $this->prepare_emoticon_object("(y)", 's/yes', 'yes', 'core'), + $this->prepare_emoticon_object("(n)", 's/no', 'no', 'core'), + $this->prepare_emoticon_object("(martin)", 's/martin', 'martin'), + $this->prepare_emoticon_object("( )", 's/egg', 'egg'), + ]; + } + + /** + * Helper method preparing an emoticon object + * + * @param string|string[] $text + * @param string $imagename to be used by {@see pix_emoticon} + * @param ?string $altidentifier alternative string identifier, null for no alt + * @param string $altcomponent where the alternative string is defined + * @param string $imagecomponent to be used by {@see pix_emoticon} + * @return stdClass + */ + protected function prepare_emoticon_object( + string|array $text, + string $imagename, + ?string $altidentifier = null, + string $altcomponent = 'core_pix', + string $imagecomponent = 'core', + ): stdClass { + return (object) [ + 'text' => $text, + 'imagename' => $imagename, + 'altidentifier' => $altidentifier, + 'altcomponent' => $altcomponent, + 'imagecomponent' => $imagecomponent, + ]; + } +} + +// Alias this class to the old name. +// This file will be autoloaded by the legacyclasses autoload system. +// In future all uses of this class will be corrected and the legacy references will be removed. +class_alias(emoticon_manager::class, \emoticon_manager::class); diff --git a/lib/db/legacyclasses.php b/lib/db/legacyclasses.php index e1568603b03..7d049292dcf 100644 --- a/lib/db/legacyclasses.php +++ b/lib/db/legacyclasses.php @@ -33,6 +33,7 @@ $legacyclasses = [ \bootstrap_renderer::class => 'output/bootstrap_renderer.php', // Core API. + \emoticon_manager::class => 'emoticon_manager.php', \lang_string::class => 'lang_string.php', // Exception API. diff --git a/lib/moodlelib.php b/lib/moodlelib.php index d3c5d020fe0..e8aac00c089 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -7128,172 +7128,20 @@ function get_list_of_themes() { } /** - * Factory function for emoticon_manager + * Factory function for {@see \core\emoticon_manager} * - * @return emoticon_manager singleton + * @return \core\emoticon_manager singleton */ -function get_emoticon_manager() { +function get_emoticon_manager(): \core\emoticon_manager { static $singleton = null; if (is_null($singleton)) { - $singleton = new emoticon_manager(); + $singleton = new \core\emoticon_manager(); } return $singleton; } -/** - * Provides core support for plugins that have to deal with emoticons (like HTML editor or emoticon filter). - * - * Whenever this manager mentiones 'emoticon object', the following data - * structure is expected: stdClass with properties text, imagename, imagecomponent, - * altidentifier and altcomponent - * - * @see admin_setting_emoticons - * - * @copyright 2010 David Mudrak - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class emoticon_manager { - - /** - * Returns the currently enabled emoticons - * - * @param boolean $selectable - If true, only return emoticons that should be selectable from a list. - * @return array of emoticon objects - */ - public function get_emoticons($selectable = false) { - global $CFG; - $notselectable = ['martin', 'egg']; - - if (empty($CFG->emoticons)) { - return array(); - } - - $emoticons = $this->decode_stored_config($CFG->emoticons); - - if (!is_array($emoticons)) { - // Something is wrong with the format of stored setting. - debugging('Invalid format of emoticons setting, please resave the emoticons settings form', DEBUG_NORMAL); - return array(); - } - if ($selectable) { - foreach ($emoticons as $index => $emote) { - if (in_array($emote->altidentifier, $notselectable)) { - // Skip this one. - unset($emoticons[$index]); - } - } - } - - return $emoticons; - } - - /** - * Converts emoticon object into renderable pix_emoticon object - * - * @param stdClass $emoticon emoticon object - * @param array $attributes explicit HTML attributes to set - * @return pix_emoticon - */ - public function prepare_renderable_emoticon(stdClass $emoticon, array $attributes = array()) { - $stringmanager = get_string_manager(); - if ($stringmanager->string_exists($emoticon->altidentifier, $emoticon->altcomponent)) { - $alt = get_string($emoticon->altidentifier, $emoticon->altcomponent); - } else { - $alt = s($emoticon->text); - } - return new pix_emoticon($emoticon->imagename, $alt, $emoticon->imagecomponent, $attributes); - } - - /** - * Encodes the array of emoticon objects into a string storable in config table - * - * @see self::decode_stored_config() - * @param array $emoticons array of emtocion objects - * @return string - */ - public function encode_stored_config(array $emoticons) { - return json_encode($emoticons); - } - - /** - * Decodes the string into an array of emoticon objects - * - * @see self::encode_stored_config() - * @param string $encoded - * @return array|null - */ - public function decode_stored_config($encoded) { - $decoded = json_decode($encoded); - if (!is_array($decoded)) { - return null; - } - return $decoded; - } - - /** - * Returns default set of emoticons supported by Moodle - * - * @return array of sdtClasses - */ - public function default_emoticons() { - return array( - $this->prepare_emoticon_object(":-)", 's/smiley', 'smiley'), - $this->prepare_emoticon_object(":)", 's/smiley', 'smiley'), - $this->prepare_emoticon_object(":-D", 's/biggrin', 'biggrin'), - $this->prepare_emoticon_object(";-)", 's/wink', 'wink'), - $this->prepare_emoticon_object(":-/", 's/mixed', 'mixed'), - $this->prepare_emoticon_object("V-.", 's/thoughtful', 'thoughtful'), - $this->prepare_emoticon_object(":-P", 's/tongueout', 'tongueout'), - $this->prepare_emoticon_object(":-p", 's/tongueout', 'tongueout'), - $this->prepare_emoticon_object("B-)", 's/cool', 'cool'), - $this->prepare_emoticon_object("^-)", 's/approve', 'approve'), - $this->prepare_emoticon_object("8-)", 's/wideeyes', 'wideeyes'), - $this->prepare_emoticon_object(":o)", 's/clown', 'clown'), - $this->prepare_emoticon_object(":-(", 's/sad', 'sad'), - $this->prepare_emoticon_object(":(", 's/sad', 'sad'), - $this->prepare_emoticon_object("8-.", 's/shy', 'shy'), - $this->prepare_emoticon_object(":-I", 's/blush', 'blush'), - $this->prepare_emoticon_object(":-X", 's/kiss', 'kiss'), - $this->prepare_emoticon_object("8-o", 's/surprise', 'surprise'), - $this->prepare_emoticon_object("P-|", 's/blackeye', 'blackeye'), - $this->prepare_emoticon_object("8-[", 's/angry', 'angry'), - $this->prepare_emoticon_object("(grr)", 's/angry', 'angry'), - $this->prepare_emoticon_object("xx-P", 's/dead', 'dead'), - $this->prepare_emoticon_object("|-.", 's/sleepy', 'sleepy'), - $this->prepare_emoticon_object("}-]", 's/evil', 'evil'), - $this->prepare_emoticon_object("(h)", 's/heart', 'heart'), - $this->prepare_emoticon_object("(heart)", 's/heart', 'heart'), - $this->prepare_emoticon_object("(y)", 's/yes', 'yes', 'core'), - $this->prepare_emoticon_object("(n)", 's/no', 'no', 'core'), - $this->prepare_emoticon_object("(martin)", 's/martin', 'martin'), - $this->prepare_emoticon_object("( )", 's/egg', 'egg'), - ); - } - - /** - * Helper method preparing the stdClass with the emoticon properties - * - * @param string|array $text or array of strings - * @param string $imagename to be used by {@link pix_emoticon} - * @param string $altidentifier alternative string identifier, null for no alt - * @param string $altcomponent where the alternative string is defined - * @param string $imagecomponent to be used by {@link pix_emoticon} - * @return stdClass - */ - protected function prepare_emoticon_object($text, $imagename, $altidentifier = null, - $altcomponent = 'core_pix', $imagecomponent = 'core') { - return (object)array( - 'text' => $text, - 'imagename' => $imagename, - 'imagecomponent' => $imagecomponent, - 'altidentifier' => $altidentifier, - 'altcomponent' => $altcomponent, - ); - } -} - // ENVIRONMENT CHECKING. /** diff --git a/lib/tests/emoticon_manager_test.php b/lib/tests/emoticon_manager_test.php new file mode 100644 index 00000000000..c734fbf5e0f --- /dev/null +++ b/lib/tests/emoticon_manager_test.php @@ -0,0 +1,123 @@ +. + +namespace core; + +use advanced_testcase; +use stdClass; +use ReflectionMethod; + +/** + * Unit tests for emoticon manager + * + * @package core + * @covers \core\emoticon_manager + * @copyright 2024 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +final class emoticon_manager_test extends advanced_testcase { + + /** + * Data provider for {@see test_get_emoticons} + * + * @return array[] + */ + public static function get_emoticons_provider(): array { + return [ + 'Only selectable' => [true, ['wink', 'biggrin'], ['egg', 'martin']], + 'Including non-selectable' => [false, ['wink', 'biggrin', 'egg', 'martin']], + ]; + } + + /** + * Test getting enabled emoticons + * + * @param bool $selectable + * @param string[] $expected + * @param string[] $excluded + * + * @dataProvider get_emoticons_provider + */ + public function test_get_emoticons( + bool $selectable, + array $expected, + array $excluded = [], + ): void { + $emoticons = (new emoticon_manager())->get_emoticons($selectable); + $identifiers = array_column($emoticons, 'altidentifier'); + + // Assert the subset of expected identifiers are all present. + $this->assertEquals([], array_diff($expected, $identifiers)); + + // Assert none of the excluded identifiers are present. + $this->assertEquals($excluded, array_diff($excluded, $identifiers)); + } + + /** + * Test converting emoticon object into renderable + */ + public function test_prepare_renderable_emoticon(): void { + $manager = new emoticon_manager(); + + // With language string identifier. + $emoticon = $this->prepare_emoticon_object(':)', 's/smiley', 'smiley'); + $pixemoticon = $manager->prepare_renderable_emoticon($emoticon); + + $this->assertEquals('s/smiley', $pixemoticon->pix); + $this->assertEquals('core', $pixemoticon->component); + $this->assertEquals([ + 'class' => 'emoticon', + 'alt' => 'smile', + 'title' => 'smile', + ], $pixemoticon->attributes); + + // With language string identifier from another component. + $emoticon = $this->prepare_emoticon_object(':)', 's/smiley', 'thanks', 'core'); + $pixemoticon = $manager->prepare_renderable_emoticon($emoticon); + + $this->assertEquals('s/smiley', $pixemoticon->pix); + $this->assertEquals('core', $pixemoticon->component); + $this->assertEquals([ + 'class' => 'emoticon', + 'alt' => 'Thanks', + 'title' => 'Thanks', + ], $pixemoticon->attributes); + + // Without language string identifier. + $emoticon = $this->prepare_emoticon_object(':-O', 's/shock'); + $pixemoticon = $manager->prepare_renderable_emoticon($emoticon); + + $this->assertEquals('s/shock', $pixemoticon->pix); + $this->assertEquals('core', $pixemoticon->component); + $this->assertEquals([ + 'class' => 'emoticon', + 'alt' => ':-O', + 'title' => ':-O', + ], $pixemoticon->attributes); + } + + /** + * Proxy method for creating emoticon object via {@see \core\emoticon_manager::prepare_emoticon_object} + * + * @param mixed ...$params + * @return stdClass + */ + private function prepare_emoticon_object(...$params): stdClass { + $manager = new emoticon_manager(); + $method = new ReflectionMethod($manager, 'prepare_emoticon_object'); + return $method->invoke($manager, ...$params); + } +}