diff --git a/CHANGELOG.md b/CHANGELOG.md index d95f7da..8957fc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.0.1] - 2024-10-08 +- Add support to Moodle 4.5 + ## [2.0.1] - 2024-01-30 ### Added diff --git a/classes/text_filter.php b/classes/text_filter.php new file mode 100644 index 0000000..ddecbe2 --- /dev/null +++ b/classes/text_filter.php @@ -0,0 +1,240 @@ +. + +/** + * Multi-language content filter, with simplified syntax. + * + * @package filter_multilang2 + * @copyright Gaetan Frenoy + * @copyright 2004 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} + * @copyright 2015 onwards Iñaki Arenaza & Mondragon Unibertsitatea + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * Given multilinguage text, return relevant text according to current language. + * + * The way the filter works is as follows: + * + * - look for multilang blocks in the text. + * - if there exists texts in the currently active language, print them. + * - else, if there exists texts in the current parent language, print them. + * - else, if there exists texts in the language 'other', print them. + * - else, don't print any text inside the lang block (this is a change + * from previous filter versions behaviour!!!!) + * + * Please note that English texts are not used as default anymore! + * Language 'other' can be used for fallbacks. + * + * This version is based on original multilang filter by Gaetan Frenoy, Eloy and skodak. + * + * Following new syntax is not compatible with old one: + * {mlang XX}one lang{mlang}Some common text for any language.{mlang YY}another language{mlang} + * + * 2019.11.19 A new enhanced syntax to be able to specify multiple languages + * for a single tag is now available. Just specify the list of the languages + * separated by commas: + * {mlang XX,YY,ZZ}Text displayed if current lang is XX, YY or ZZ, or one of their parent laguages.{mlang} + * + * @package filter_multilang2 + * @copyright 2015 onwards Iñaki Arenaza & Mondragon Unibertsitatea + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace filter_multilang2; + +defined('MOODLE_INTERNAL') || die; + +if (class_exists('\core_filters\text_filter')) { + class_alias('\core_filters\text_filter', 'base_text_filter'); +} else { + class_alias('\moodle_text_filter', 'base_text_filter'); +} + +class text_filter extends \base_text_filter { + + /** + * @var array Cache of parent language(s) of a given language + */ + protected static $parentcache; + + /** + * @var string The langauge we are currently using to filter multilang blocks. + * It can be either the user current language, or the language 'other' + */ + protected $lang; + + /** + * @var boolean Whether the filter has already found a block that + * corresponds to the user language, or it has to + * "fall back" to the "other" "language block (if it + * exists). + */ + protected $replacementdone; + + /** + * Filter text before changing format to HTML. + * + * @param string $text + * @param array $options + * @return string + */ + public function filter_stage_pre_format(string $text, array $options): string { + // Ideally we want to get rid of all other languages before any text formatting. + return $this->filter($text, $options); + } + + /** + * Filter HTML text before sanitising text. + * + * Text sanitisation might not be performed if $options['noclean'] true. + * + * @param string $text + * @param array $options + * @return string + */ + public function filter_stage_pre_clean(string $text, array $options): string { + return $text; + } + + /** + * Filter HTML text after sanitisation. + * + * @param string $text + * @param array $options + * @return string + */ + public function filter_stage_post_clean(string $text, array $options): string { + return $text; + } + + /** + * Filter simple text coming from format_string(). + * + * Note that unless $CFG->formatstringstriptags is disabled + * HTML tags are not expected in returned value. + * + * @param string $text + * @param array $options + * @return string + */ + public function filter_stage_string(string $text, array $options): string { + return $this->filter($text, $options); + } + + /** + * This function filters the received text based on the language + * tags embedded in the text, and the current user language or + * 'other', if present. + * + * @param string $text The text to filter. + * @param array $options The filter options. + * @return string The filtered text for this multilang block. + */ + public function filter($text, array $options = []) { + + if (!is_string($text) || empty($text)) { + // Non-string data can not be filtered anyway. + return $text; + } + + if (stripos($text, 'mlang') === false) { + // Performance shortcut - if there is no 'mlang' text, nothing can match. + return $text; + } + + if (!isset(self::$parentcache)) { + self::$parentcache['other'] = []; + } + + $this->replacementdone = false; + $currlang = current_language(); + if (!array_key_exists($currlang, self::$parentcache)) { + $parentlangs = get_string_manager()->get_language_dependencies($currlang); + self::$parentcache[$currlang] = $parentlangs; + } + + $search = '/{\s*mlang\s+( # Look for the leading {mlang + (?:[a-z0-9_-]+) # At least one language must be present + # (but dont capture it individually). + (?:\s*,\s*[a-z0-9_-]+\s*)* # More can follow, separated by commas + # (again dont capture them individually). + )\s*} # Capture the language list as a single capture. + (.*?) # Now capture the text to be filtered. + {\s*mlang\s*} # And look for the trailing {mlang}. + /isx'; + + $replacelang = $currlang; + $result = preg_replace_callback($search, + function ($matches) use ($replacelang) { + return $this->replace_callback($replacelang, $matches); + }, + $text); + if (is_null($result)) { + return $text; // Error during regex processing, keep original text. + } + if ($this->replacementdone) { + return $result; + } + + $replacelang = 'other'; + $result = preg_replace_callback($search, + function ($matches) use ($replacelang) { + return $this->replace_callback($replacelang, $matches); + }, + $text); + if (is_null($result)) { + return $text; + } + return $result; + } + + /** + * This function filters the current block of multilang tag. If + * any of the tag languages (or their parent languages) match the + * specified filtering language, it returns the text of the + * block. Otherwise it returns an empty string. + * + * @param string $replacelang A string that specifies the language used to + * filter the matches. + * @param array $langblock An array containing the matching captured pieces of the + * regular expression. They are the languages of the tag, + * and the text associated with those languages. + * @return string + */ + protected function replace_callback($replacelang, $langblock): string { + /* Normalize languages. We can use strtolower instead of + * core_text::strtolower() as language short names are ASCII + * only, and strtolower is much faster. We have to remove the + * white space between language names to be able to match them + * to official language names. + */ + $blocklangs = explode(',', str_replace(' ', '', str_replace('-', '_', strtolower($langblock[1])))); + $blocktext = $langblock[2]; + $parentlangs = self::$parentcache[$replacelang]; + foreach ($blocklangs as $blocklang) { + /* We don't check for empty values of $blocklang as they simply don't + * match any language and they don't produce any errors or warnings. + */ + if (($blocklang === $replacelang) || in_array($blocklang, $parentlangs)) { + $this->replacementdone = true; + return $blocktext; + } + } + + return ''; + } +} diff --git a/filter.php b/filter.php index 9d12217..acc255a 100644 --- a/filter.php +++ b/filter.php @@ -14,216 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * Multi-language content filter, with simplified syntax. - * - * @package filter_multilang2 - * @copyright Gaetan Frenoy - * @copyright 2004 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} - * @copyright 2015 onwards Iñaki Arenaza & Mondragon Unibertsitatea - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ +defined('MOODLE_INTERNAL') || die(); -/** - * Given multilinguage text, return relevant text according to current language. - * - * The way the filter works is as follows: - * - * - look for multilang blocks in the text. - * - if there exists texts in the currently active language, print them. - * - else, if there exists texts in the current parent language, print them. - * - else, if there exists texts in the language 'other', print them. - * - else, don't print any text inside the lang block (this is a change - * from previous filter versions behaviour!!!!) - * - * Please note that English texts are not used as default anymore! - * Language 'other' can be used for fallbacks. - * - * This version is based on original multilang filter by Gaetan Frenoy, Eloy and skodak. - * - * Following new syntax is not compatible with old one: - * {mlang XX}one lang{mlang}Some common text for any language.{mlang YY}another language{mlang} - * - * 2019.11.19 A new enhanced syntax to be able to specify multiple languages - * for a single tag is now available. Just specify the list of the languages - * separated by commas: - * {mlang XX,YY,ZZ}Text displayed if current lang is XX, YY or ZZ, or one of their parent laguages.{mlang} - * - * @package filter_multilang2 - * @copyright 2015 onwards Iñaki Arenaza & Mondragon Unibertsitatea - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class filter_multilang2 extends moodle_text_filter { - - /** - * @var array Cache of parent language(s) of a given language - */ - protected static $parentcache; - - /** - * @var string The langauge we are currently using to filter multilang blocks. - * It can be either the user current language, or the language 'other' - */ - protected $lang; - - /** - * @var boolean Whether the filter has already found a block that - * corresponds to the user language, or it has to - * "fall back" to the "other" "language block (if it - * exists). - */ - protected $replacementdone; - - /** - * Filter text before changing format to HTML. - * - * @param string $text - * @param array $options - * @return string - */ - public function filter_stage_pre_format(string $text, array $options): string { - // Ideally we want to get rid of all other languages before any text formatting. - return $this->filter($text, $options); - } - - /** - * Filter HTML text before sanitising text. - * - * Text sanitisation might not be performed if $options['noclean'] true. - * - * @param string $text - * @param array $options - * @return string - */ - public function filter_stage_pre_clean(string $text, array $options): string { - return $text; - } - - /** - * Filter HTML text after sanitisation. - * - * @param string $text - * @param array $options - * @return string - */ - public function filter_stage_post_clean(string $text, array $options): string { - return $text; - } - - /** - * Filter simple text coming from format_string(). - * - * Note that unless $CFG->formatstringstriptags is disabled - * HTML tags are not expected in returned value. - * - * @param string $text - * @param array $options - * @return string - */ - public function filter_stage_string(string $text, array $options): string { - return $this->filter($text, $options); - } - - /** - * This function filters the received text based on the language - * tags embedded in the text, and the current user language or - * 'other', if present. - * - * @param string $text The text to filter. - * @param array $options The filter options. - * @return string The filtered text for this multilang block. - */ - public function filter($text, array $options = []) { - - if (!is_string($text) || empty($text)) { - // Non-string data can not be filtered anyway. - return $text; - } - - if (stripos($text, 'mlang') === false) { - // Performance shortcut - if there is no 'mlang' text, nothing can match. - return $text; - } - - if (!isset(self::$parentcache)) { - self::$parentcache['other'] = []; - } - - $this->replacementdone = false; - $currlang = current_language(); - if (!array_key_exists($currlang, self::$parentcache)) { - $parentlangs = get_string_manager()->get_language_dependencies($currlang); - self::$parentcache[$currlang] = $parentlangs; - } - - $search = '/{\s*mlang\s+( # Look for the leading {mlang - (?:[a-z0-9_-]+) # At least one language must be present - # (but dont capture it individually). - (?:\s*,\s*[a-z0-9_-]+\s*)* # More can follow, separated by commas - # (again dont capture them individually). - )\s*} # Capture the language list as a single capture. - (.*?) # Now capture the text to be filtered. - {\s*mlang\s*} # And look for the trailing {mlang}. - /isx'; - - $replacelang = $currlang; - $result = preg_replace_callback($search, - function ($matches) use ($replacelang) { - return $this->replace_callback($replacelang, $matches); - }, - $text); - if (is_null($result)) { - return $text; // Error during regex processing, keep original text. - } - if ($this->replacementdone) { - return $result; - } - - $replacelang = 'other'; - $result = preg_replace_callback($search, - function ($matches) use ($replacelang) { - return $this->replace_callback($replacelang, $matches); - }, - $text); - if (is_null($result)) { - return $text; - } - return $result; - } - - /** - * This function filters the current block of multilang tag. If - * any of the tag languages (or their parent languages) match the - * specified filtering language, it returns the text of the - * block. Otherwise it returns an empty string. - * - * @param string $replacelang A string that specifies the language used to - * filter the matches. - * @param array $langblock An array containing the matching captured pieces of the - * regular expression. They are the languages of the tag, - * and the text associated with those languages. - * @return string - */ - protected function replace_callback($replacelang, $langblock): string { - /* Normalize languages. We can use strtolower instead of - * core_text::strtolower() as language short names are ASCII - * only, and strtolower is much faster. We have to remove the - * white space between language names to be able to match them - * to official language names. - */ - $blocklangs = explode(',', str_replace(' ', '', str_replace('-', '_', strtolower($langblock[1])))); - $blocktext = $langblock[2]; - $parentlangs = self::$parentcache[$replacelang]; - foreach ($blocklangs as $blocklang) { - /* We don't check for empty values of $blocklang as they simply don't - * match any language and they don't produce any errors or warnings. - */ - if (($blocklang === $replacelang) || in_array($blocklang, $parentlangs)) { - $this->replacementdone = true; - return $blocktext; - } - } - - return ''; - } -} +class_alias(\filter_multilang2\text_filter::class, \filter_multilang2::class); diff --git a/version.php b/version.php index 8e78193..1955c29 100644 --- a/version.php +++ b/version.php @@ -25,8 +25,8 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2024013101; // The current plugin version (Date: YYYYMMDDXX). +$plugin->version = 2024100801; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2021051700; // Requires this Moodle version. $plugin->component = 'filter_multilang2'; // Full name of the plugin (used for diagnostics). -$plugin->release = '2.0.1'; +$plugin->release = '2.0.2'; $plugin->maturity = MATURITY_STABLE;