diff --git a/admin/tool/langimport/classes/locale.php b/admin/tool/langimport/classes/locale.php index 69c43b69cf0..c489e6fbf6c 100644 --- a/admin/tool/langimport/classes/locale.php +++ b/admin/tool/langimport/classes/locale.php @@ -79,6 +79,19 @@ class locale { * @return string|false Returns the new current locale, or FALSE on error. */ protected function set_locale(int $category = LC_ALL, string $locale = '0') { - return setlocale($category, $locale); + if (strlen($locale) <= 255 || PHP_OS_FAMILY === 'BSD' || PHP_OS_FAMILY === 'Darwin') { + // We can set the whole locale all together. + return setlocale($category, $locale); + } + + // Too long locale with linux or windows, let's split it into known and supported categories. + $split = explode(';', $locale); + foreach ($split as $element) { + [$category, $value] = explode('=', $element); + if (defined($category)) { // Only if the category exists, there are OS differences. + setlocale(constant($category), $value); + } + } + return setlocale(LC_ALL, 0); // Finally, return the complete configured locale. } } diff --git a/admin/tool/langimport/tests/locale_test.php b/admin/tool/langimport/tests/locale_test.php index 2f36910967c..f2f121525ad 100644 --- a/admin/tool/langimport/tests/locale_test.php +++ b/admin/tool/langimport/tests/locale_test.php @@ -14,14 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * Tests for \tool_langimport\locale class. - * - * @package tool_langimport - * @copyright 2018 Université Rennes 2 {@link https://www.univ-rennes2.fr} - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - namespace tool_langimport; /** @@ -29,6 +21,7 @@ namespace tool_langimport; * * @package tool_langimport * @category test + * @coversDefaultClass \tool_langimport\locale * @copyright 2018 Université Rennes 2 {@link https://www.univ-rennes2.fr} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -36,6 +29,7 @@ class locale_test extends \advanced_testcase { /** * Test that \tool_langimport\locale::check_locale_availability() works as expected. * + * @covers ::check_locale_availability * @return void */ public function test_check_locale_availability() { @@ -43,7 +37,7 @@ class locale_test extends \advanced_testcase { // - first setlocale() call which backup current locale // - second setlocale() call which try to set new 'es' locale // - third setlocale() call which restore locale. - $mock = $this->getMockBuilder(\tool_langimport\locale::class) + $mock = $this->getMockBuilder(locale::class) ->onlyMethods(['set_locale']) ->getMock(); $mock->method('set_locale')->will($this->onConsecutiveCalls('en', 'es', 'en')); @@ -56,7 +50,7 @@ class locale_test extends \advanced_testcase { // - first setlocale() call which backup current locale // - second setlocale() call which fail to set new locale // - third setlocale() call which restore locale. - $mock = $this->getMockBuilder(\tool_langimport\locale::class) + $mock = $this->getMockBuilder(locale::class) ->onlyMethods(['set_locale']) ->getMock(); $mock->method('set_locale')->will($this->onConsecutiveCalls('en', false, 'en')); @@ -66,8 +60,104 @@ class locale_test extends \advanced_testcase { $this->assertFalse($result); // Test an invalid parameter. - $locale = new \tool_langimport\locale(); + $locale = new locale(); $this->expectException(\coding_exception::class); $locale->check_locale_availability(''); } + + /** + * Test \tool_langimport\locale::set_locale() own logic. + * + * We have to explicitly test set_locale() own logic and results, + * that effectively sets the current locale, so we need to restore + * the original locale after every test (ugly, from a purist unit test + * point of view, but needed). + * + * @dataProvider set_locale_provider + * @covers ::set_locale + * + * @param string $set locale string to be set. + * @param string $ret expected results returned after setting the locale. + */ + public function test_set_locale(string $set, string $ret) { + // Make set_locale() public. + $loc = new locale(); + $rc = new \ReflectionClass(locale::class); + $rm = $rc->getMethod('set_locale'); + $rm->setAccessible(true); + + // Capture current locale for later restore (funnily, using the set_locale() method itself. + $originallocale = $rm->invokeArgs($loc, [LC_ALL, 0]); + + // Assert we get the locale defined as expected. + $this->assertEquals($ret, $rm->invokeArgs($loc, [LC_ALL, $set])); + + // We have finished, restore the original locale, so this doesn't affect other tests at distance. + // (again, funnily, using the very same set_locale() method). + $rm->invokeArgs($loc, [LC_ALL, $originallocale]); + + } + + /** + * Data provider for test_set_locale(). + * + * Provides a locale to be set (as 'set') and a expected return value (as 'ret'). Note that + * some of the locales are OS dependent, so only the ones matching the OS will be provided. + * + * We make extensive use of the en_AU.UTF-8/English_Australia.1252 locale that is mandatory to + * be installed in any system running PHPUnit tests. + */ + public function set_locale_provider(): array { + // Let's list the allowed categories by OS. + $bsdallowed = ['LC_COLLATE', 'LC_CTYPE', 'LC_MESSAGES', 'LC_MONETARY', 'LC_NUMERIC', 'LC_TIME']; + $winallowed = ['LC_COLLATE', 'LC_CTYPE', 'LC_MONETARY', 'LC_NUMERIC', 'LC_TIME']; + $linuxallowed = [ + 'LC_COLLATE', 'LC_CTYPE', 'LC_MESSAGES', 'LC_MONETARY', 'LC_NUMERIC', 'LC_TIME', + 'LC_PAPER', 'LC_NAME', 'LC_ADDRESS', 'LC_TELEPHONE', 'LC_MEASUREMENT', 'LC_IDENTIFICATION' + ]; + + // The base locale name is also OS dependent. + $baselocale = get_string('locale', 'langconfig'); + if (PHP_OS_FAMILY === 'Windows') { + $baselocale = get_string('localewin', 'langconfig'); + } + + // Here we'll go accumulating cases to be provided. + $cases = []; + + // First, the simplest case, just pass a locale name, without categories. + $cases['rawlocale'] = [ + 'set' => $baselocale, + 'ret' => $baselocale, + ]; + + // Now, let's fill ALL LC categories, we should get back the locale name if all them are set with same value. + // Note that this case is the one that, under Linux only, covers the changes performed to the set_locale() method. + // Pick the correct categories depending on the OS. + $oscategories = $bsdallowed; // Default to BSD/Dawrwin ones because they are the standard 6 supported by PHP. + if (PHP_OS_FAMILY === 'Windows') { + $oscategories = $winallowed; + } else if (PHP_OS_FAMILY === 'Linux') { + $oscategories = $linuxallowed; + } + + $localestr = ''; + foreach ($oscategories as $category) { + // Format is different by OS too, so let build the string conditionally. + if (PHP_OS_FAMILY === 'BSD' || PHP_OS_FAMILY === 'Darwin') { + // BSD uses slashes (/) separated list of the 6 values in exact order. + $localestr .= '/' . $baselocale; + } else { + // Linux/Windows use semicolon (;) separated list of category=value pairs. + $localestr .= ';' . $category . '=' . $baselocale; + } + } + $cases['allcategories'] = [ + 'set' => trim($localestr, ';/'), + 'ret' => $baselocale, + ]; + + // Return all the built cases. + return $cases; + } }