diff --git a/lib/moodlelib.php b/lib/moodlelib.php index 44b80c872bb..1d5b39ae826 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -6685,28 +6685,18 @@ class core_string_manager implements string_manager { } /** - * 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); } /** @@ -7222,6 +7212,46 @@ class core_string_manager implements string_manager { return -1; } } + + /// 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)); + } + } } diff --git a/lib/tests/fixtures/langtest/aa/langconfig.php b/lib/tests/fixtures/langtest/aa/langconfig.php new file mode 100644 index 00000000000..15ea4c8ab1d --- /dev/null +++ b/lib/tests/fixtures/langtest/aa/langconfig.php @@ -0,0 +1,4 @@ +. + +/** + * Unit tests for localization support in lib/moodlelib.php + * + * @package core + * @category test + * @copyright 2013 David Mudrak + * @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 + * @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 + * @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 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, $usecache = false, array $translist = array(), $menucache = null) { + global $CFG; + + if (is_null($localroot)) { + $localroot = $otherroot; + } + + if (is_null($menucache)) { + $menucache = $CFG->cachedir.'/languages'; + } + + return new testable_core_string_manager($otherroot, $localroot, $usecache, $translist, $menucache); + } +}