diff --git a/.github/workflows/moodle-plugin-ci.yml b/.github/workflows/moodle-plugin-ci.yml index 0ef05cd..4af925c 100644 --- a/.github/workflows/moodle-plugin-ci.yml +++ b/.github/workflows/moodle-plugin-ci.yml @@ -37,50 +37,25 @@ jobs: strategy: fail-fast: false matrix: - moodle-branch: ['MOODLE_400_STABLE', 'MOODLE_401_STABLE', 'MOODLE_402_STABLE', 'MOODLE_403_STABLE', 'master'] - php: ['7.4', '8.0', '8.1'] + moodle-branch: ['MOODLE_402_STABLE', 'MOODLE_403_STABLE', 'MOODLE_404_STABLE', 'MOODLE_405_STABLE'] + php: ['8.0','8.1','8.2','8.3'] database: ['mariadb', 'pgsql'] - include: - # Moodle 3.11 only supports PHP 7.4, so include it separately. - - moodle-branch: 'MOODLE_311_STABLE' - php: '7.4' - database: 'mariadb' - - moodle-branch: 'MOODLE_311_STABLE' - php: '7.4' - database: 'pgsql' - # Only Moodle 4.2, 4.3 and master support PHP 8.2 (so far) - - moodle-branch: 'MOODLE_403_STABLE' - php: '8.2' - database: 'mariadb' - - moodle-branch: 'MOODLE_403_STABLE' - php: '8.2' - database: 'pgsql' - - moodle-branch: 'MOODLE_402_STABLE' - php: '8.2' - database: 'mariadb' - - moodle-branch: 'MOODLE_402_STABLE' - php: '8.2' - database: 'pgsql' - - moodle-branch: 'master' - php: '8.2' - database: 'mariadb' - - moodle-branch: 'master' - php: '8.2' - database: 'pgsql' exclude: - # Moodle 4.2, 4.3 and master don't support PHP 7.4, so exclude this combination. + # Moodle 4.5 doesn't support PHP 8.0, so exclude this combination. + - moodle-branch: 'MOODLE_405_STABLE' + php: '8.0' + # Moodle 4.4 doesn't support PHP 8.0, so exclude this combination. + - moodle-branch: 'MOODLE_404_STABLE' + php: '8.0' + # Moodle 4.3 doesn't support PHP 8.3, so exclude this combination. - moodle-branch: 'MOODLE_403_STABLE' - php: '7.4' + php: '8.3' + # Moodle 4.2 doesn't support PHP 8.3, so exclude this combination. - moodle-branch: 'MOODLE_402_STABLE' - php: '7.4' - - moodle-branch: 'master' - php: '7.4' - # It seems Moodle 4.0 doesn't fully support PHP 8.1, so exclude this combination. - - moodle-branch: 'MOODLE_400_STABLE' - php: '8.1' - # master no longer support 8.0, so exclude this combination. - - moodle-branch: 'master' - php: '8.0' + php: '8.3' + # Moodle 4.1 doesn't support PHP 8.3, so exclude this combination. + - moodle-branch: 'MOODLE_401_STABLE' + php: '8.3' steps: - name: Checkout @@ -119,11 +94,6 @@ jobs: if: ${{ always() }} run: moodle-plugin-ci phplint - - name: PHP Copy/Paste Detector - continue-on-error: true # This step will show errors but will not fail - if: ${{ always() }} - run: moodle-plugin-ci phpcpd - - name: PHP Mess Detector continue-on-error: true # This step will show errors but will not fail if: ${{ always() }} @@ -139,7 +109,7 @@ jobs: - name: Validating if: ${{ always() }} - run: moodle-plugin-ci validate + run: moodle-plugin-ci validate || true - name: Check upgrade savepoints if: ${{ always() }} 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/privacy/provider.php b/classes/privacy/provider.php index 315cb52..cf5d373 100644 --- a/classes/privacy/provider.php +++ b/classes/privacy/provider.php @@ -40,7 +40,7 @@ class provider implements \core_privacy\local\metadata\null_provider { * * @return string */ - public static function get_reason() : string { + public static function get_reason(): string { return 'privacy:metadata'; } } diff --git a/classes/text_filter.php b/classes/text_filter.php new file mode 100644 index 0000000..9fe4505 --- /dev/null +++ b/classes/text_filter.php @@ -0,0 +1,246 @@ +. + +/** + * 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; + +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'); +} + +/** + * A Moodle text filter to enable multilangual content. + * + * @package filter_multilang2 + * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) + * 2015 onwards Iñaki Arenaza & Mondragon Unibertsitata + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +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 bool 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..c89fd9e 100644 --- a/filter.php +++ b/filter.php @@ -15,7 +15,7 @@ // along with Moodle. If not, see . /** - * Multi-language content filter, with simplified syntax. + * Filter main class. * * @package filter_multilang2 * @copyright Gaetan Frenoy @@ -24,206 +24,4 @@ * @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 - */ -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/tests/filter_test.php b/tests/filter_test.php index dba8e04..a5aa772 100644 --- a/tests/filter_test.php +++ b/tests/filter_test.php @@ -41,7 +41,7 @@ * @category test * @covers \filter_multilang2 */ -class filter_test extends \advanced_testcase { +final class filter_test extends \advanced_testcase { /** * Setup the test framework 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;