Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

MDL-39023 Make get_language_dependencies() aware of circular dependen…

…cy risk

The patch reimplements the core_string_manager::get_language_dependencies()
so that potentially mis-configured language packs with circular
dependencies or self dependency do not make the site unavailable.
Unit tests for the expected behaviour are added.
  • Loading branch information...
commit a82871d4f79f727b9edd203ca8348ec16981672c 1 parent 7d253e0
@mudrd8mz mudrd8mz authored
View
68 lib/moodlelib.php
@@ -6455,28 +6455,18 @@ public function __construct($otherroot, $localroot, $cacheroot, $usediskcache, $
}
/**
- * Returns dependencies of current language, en is not included.
+ * Returns list of all explicit parent languages for the given language.
*
- * @param string $lang
- * @return array all parents, the lang itself is last
+ * English (en) is considered as the top implicit parent of all language packs
+ * and is not included in the returned list. The language itself is appended to the
+ * end of the list. The method is aware of circular dependency risk.
+ *
+ * @see self::populate_parent_languages()
+ * @param string $lang the code of the language
+ * @return array all explicit parent languages with the lang itself appended
*/
public function get_language_dependencies($lang) {
- if ($lang === 'en') {
- return array();
- }
- if (!file_exists("$this->otherroot/$lang/langconfig.php")) {
- return array();
- }
- $string = array();
- include("$this->otherroot/$lang/langconfig.php");
-
- if (empty($string['parentlanguage'])) {
- return array($lang);
- } else {
- $parentlang = $string['parentlanguage'];
- unset($string);
- return array_merge($this->get_language_dependencies($parentlang), array($lang));
- }
+ return $this->populate_parent_languages($lang);
}
/**
@@ -6981,6 +6971,46 @@ public function reset_caches() {
fulldelete($this->menucache);
$this->get_list_of_translations(true);
}
+
+ /// End of external API ////////////////////////////////////////////////////
+
+ /**
+ * Helper method that recursively loads all parents of the given language.
+ *
+ * @see self::get_language_dependencies()
+ * @param string $lang language code
+ * @param array $stack list of parent languages already populated in previous recursive calls
+ * @return array list of all parents of the given language with the $lang itself added as the last element
+ */
+ protected function populate_parent_languages($lang, array $stack = array()) {
+
+ // English does not have a parent language.
+ if ($lang === 'en') {
+ return $stack;
+ }
+
+ // Prevent circular dependency (and thence the infinitive recursion loop).
+ if (in_array($lang, $stack)) {
+ return $stack;
+ }
+
+ // Load language configuration and look for the explicit parent language.
+ if (!file_exists("$this->otherroot/$lang/langconfig.php")) {
+ return $stack;
+ }
+ $string = array();
+ include("$this->otherroot/$lang/langconfig.php");
+
+ if (empty($string['parentlanguage']) or $string['parentlanguage'] === 'en') {
+ unset($string);
+ return array_merge(array($lang), $stack);
+
+ } else {
+ $parentlang = $string['parentlanguage'];
+ unset($string);
+ return $this->populate_parent_languages($parentlang, array_merge(array($lang), $stack));
+ }
+ }
}
View
4 lib/tests/fixtures/langtest/aa/langconfig.php
@@ -0,0 +1,4 @@
+<?php
+
+$string['thislanguage'] = 'AA native name';
+$string['thislanguageint'] = 'AA international name';
View
4 lib/tests/fixtures/langtest/bb/langconfig.php
@@ -0,0 +1,4 @@
+<?php
+
+$string['thislanguage'] = 'Orphaned language with non-existing parent';
+$string['parentlanguage'] = 'bbparent';
View
4 lib/tests/fixtures/langtest/bc/langconfig.php
@@ -0,0 +1,4 @@
+<?php
+
+$string['thislanguage'] = 'My parent is orphaned language';
+$string['parentlanguage'] = 'bb';
View
4 lib/tests/fixtures/langtest/cda/langconfig.php
@@ -0,0 +1,4 @@
+<?php
+
+$string['thislanguage'] = 'Circular dependency A';
+$string['parentlanguage'] = 'cdc';
View
4 lib/tests/fixtures/langtest/cdb/langconfig.php
@@ -0,0 +1,4 @@
+<?php
+
+$string['thislanguage'] = 'Circular dependency B';
+$string['parentlanguage'] = 'cda';
View
4 lib/tests/fixtures/langtest/cdc/langconfig.php
@@ -0,0 +1,4 @@
+<?php
+
+$string['thislanguage'] = 'Circular dependency C';
+$string['parentlanguage'] = 'cdb';
View
4 lib/tests/fixtures/langtest/de/langconfig.php
@@ -0,0 +1,4 @@
+<?php
+
+$string['thislanguage'] = 'Deutsch';
+$string['parentlanguage'] = 'en';
View
4 lib/tests/fixtures/langtest/de_du/langconfig.php
@@ -0,0 +1,4 @@
+<?php
+
+$string['thislanguage'] = 'Deutsch - Du';
+$string['parentlanguage'] = 'de';
View
4 lib/tests/fixtures/langtest/de_kids/langconfig.php
@@ -0,0 +1,4 @@
+<?php
+
+$string['thislanguage'] = 'Deutsch - Kids';
+$string['parentlanguage'] = 'de_du';
View
4 lib/tests/fixtures/langtest/sd/langconfig.php
@@ -0,0 +1,4 @@
+<?php
+
+$string['thislanguage'] = 'Self dependency';
+$string['parentlanguage'] = 'sd';
View
113 lib/tests/string_test.php
@@ -0,0 +1,113 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Unit tests for localization support in lib/moodlelib.php
+ *
+ * @package core
+ * @category test
+ * @copyright 2013 David Mudrak <david@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->libdir.'/moodlelib.php');
+
+/**
+ * Tests for the API of the string_manager
+ *
+ * @copyright 2013 David Mudrak <david@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class string_manager_test extends advanced_testcase {
+
+ public function test_string_manager_instance() {
+ global $CFG;
+ $this->resetAfterTest();
+
+ $otherroot = dirname(__FILE__).'/fixtures/langtest';
+ $stringman = testable_core_string_manager::instance($otherroot);
+ $this->assertTrue(in_array('string_manager', class_implements($stringman)));
+ }
+
+ public function test_get_language_dependencies() {
+ global $CFG;
+ $this->resetAfterTest();
+
+ $otherroot = dirname(__FILE__).'/fixtures/langtest';
+ $stringman = testable_core_string_manager::instance($otherroot);
+
+ // There is no parent language for 'en'.
+ $this->assertSame(array(), $stringman->get_language_dependencies('en'));
+ // Language with no parent language declared.
+ $this->assertSame(array('aa'), $stringman->get_language_dependencies('aa'));
+ // Language with parent language explicitly set to English (en < de).
+ $this->assertSame(array('de'), $stringman->get_language_dependencies('de'));
+ // Language dependency hierarchy (de < de_du < de_kids).
+ $this->assertSame(array('de', 'de_du', 'de_kids'), $stringman->get_language_dependencies('de_kids'));
+ // Language with the parent language misconfigured to itself (sd < sd).
+ $this->assertSame(array('sd'), $stringman->get_language_dependencies('sd'));
+ // Language with circular dependency (cda < cdb < cdc < cda).
+ $this->assertSame(array('cda', 'cdb', 'cdc'), $stringman->get_language_dependencies('cdc'));
+ // Orphaned language (N/A < bb).
+ $this->assertSame(array('bb'), $stringman->get_language_dependencies('bb'));
+ // Descendant of an orphaned language (N/A < bb < bc).
+ $this->assertSame(array('bb', 'bc'), $stringman->get_language_dependencies('bc'));
+ }
+}
+
+
+/**
+ * Helper class providing testable string_manager
+ *
+ * @copyright 2013 David Mudrak <david@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class testable_core_string_manager extends core_string_manager {
+
+ /**
+ * Factory method
+ *
+ * @param string $otherroot full path to the location of installed upstream language packs
+ * @param string $localroot full path to the location of locally customized language packs, defaults to $otherroot
+ * @param string $cacheroot full path to the location of on-disk cache
+ * @param bool $usecache use application permanent cache
+ * @param array $translist explicit list of visible translations
+ * @param string $menucache the location of a file that caches the list of available translations
+ * @return testable_core_string_manager
+ */
+ public static function instance($otherroot, $localroot = null, $cacheroot = null, $usecache = false,
+ array $translist = array(), $menucache = null) {
+ global $CFG;
+
+ if (is_null($localroot)) {
+ $localroot = $otherroot;
+ }
+
+ if (is_null($cacheroot)) {
+ $cacheroot = $CFG->cachedir.'/lang';
+ }
+
+ if (is_null($menucache)) {
+ $menucache = $CFG->cachedir.'/languages';
+ }
+
+ return new testable_core_string_manager($otherroot, $localroot, $cacheroot, $usecache, $translist, $menucache);
+ }
+}
Please sign in to comment.
Something went wrong with that request. Please try again.