Merge pull request #19356 from MauricioFauth/language-refactor

Refactor LanguageManager class
This commit is contained in:
Maurício Meneghini Fauth 2024-11-05 17:52:22 -03:00 committed by GitHub
commit e328ec450b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 141 additions and 201 deletions

View File

@ -7854,26 +7854,6 @@ parameters:
count: 2
path: src/Http/ServerRequest.php
-
message: "#^Cannot access offset 'a_meta_dir' on mixed\\.$#"
count: 1
path: src/I18n/Language.php
-
message: "#^Cannot access offset 'a_meta_language' on mixed\\.$#"
count: 1
path: src/I18n/Language.php
-
message: "#^Cannot access offset 'w_page' on mixed\\.$#"
count: 1
path: src/I18n/Language.php
-
message: "#^Parameter \\#2 \\$path of function _bindtextdomain expects string, mixed given\\.$#"
count: 1
path: src/I18n/Language.php
-
message: """
#^Call to deprecated method getInstance\\(\\) of class PhpMyAdmin\\\\Config\\:
@ -7883,20 +7863,10 @@ parameters:
path: src/I18n/LanguageManager.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 5
path: src/I18n/LanguageManager.php
-
message: "#^Method PhpMyAdmin\\\\I18n\\\\LanguageManager\\:\\:availableLocales\\(\\) should return array\\<string\\> but returns array\\|false\\.$#"
message: "#^Method PhpMyAdmin\\\\I18n\\\\LanguageManager\\:\\:availableLocales\\(\\) should return array\\<string\\> but returns array\\.$#"
count: 1
path: src/I18n/LanguageManager.php
-
message: "#^Parameter \\#1 \\$code of method PhpMyAdmin\\\\I18n\\\\LanguageManager\\:\\:getLanguage\\(\\) expects string, mixed given\\.$#"
count: 4
path: src/I18n/LanguageManager.php
-
message: "#^Parameter \\#1 \\$directory of function opendir expects string, mixed given\\.$#"
count: 1
@ -7913,7 +7883,12 @@ parameters:
path: src/I18n/LanguageManager.php
-
message: "#^Property PhpMyAdmin\\\\I18n\\\\LanguageManager\\:\\:\\$availableLocales \\(array\\<string\\>\\) does not accept array\\|false\\.$#"
message: "#^Parameter \\#2 \\$path of function _bindtextdomain expects string, mixed given\\.$#"
count: 1
path: src/I18n/LanguageManager.php
-
message: "#^Property PhpMyAdmin\\\\I18n\\\\LanguageManager\\:\\:\\$availableLocales \\(array\\<string\\>\\) does not accept array\\.$#"
count: 1
path: src/I18n/LanguageManager.php
@ -16670,14 +16645,6 @@ parameters:
count: 1
path: tests/unit/Html/MySQLDocumentationTest.php
-
message: """
#^Call to deprecated method getInstance\\(\\) of class PhpMyAdmin\\\\Config\\:
Use dependency injection instead\\.$#
"""
count: 6
path: tests/unit/I18n/LanguageTest.php
-
message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertTrue\\(\\) with true will always evaluate to true\\.$#"
count: 1

View File

@ -6156,41 +6156,25 @@
<DeprecatedMethod>
<code><![CDATA[Config::getInstance()]]></code>
</DeprecatedMethod>
<MixedArgument>
<code><![CDATA[$this->config->get('Lang')]]></code>
<code><![CDATA[$this->config->getCookie('pma_lang')]]></code>
</MixedArgument>
<DocblockTypeContradiction>
<code><![CDATA[$locales]]></code>
</DocblockTypeContradiction>
<MixedArrayOffset>
<code><![CDATA[$langs[$this->config->get('DefaultLang')]]]></code>
</MixedArrayOffset>
<MixedArrayTypeCoercion>
<code><![CDATA[$langs[$this->config->get('DefaultLang')]]]></code>
</MixedArrayTypeCoercion>
<MixedOperand>
<code><![CDATA[$this->config->get('FilterLanguages')]]></code>
</MixedOperand>
<PossiblyInvalidArgument>
<code><![CDATA[$_GET['lang']]]></code>
<code><![CDATA[$_POST['lang']]]></code>
</PossiblyInvalidArgument>
<PossiblyInvalidCast>
<code><![CDATA[$_GET['lang']]]></code>
<code><![CDATA[$_POST['lang']]]></code>
</PossiblyInvalidCast>
<PossiblyNullArgument>
<code><![CDATA[$this->config->get('Lang')]]></code>
<code><![CDATA[$this->config->getCookie('pma_lang')]]></code>
</PossiblyNullArgument>
<MixedAssignment>
<code><![CDATA[$languageFromConfig]]></code>
<code><![CDATA[$languageFromCookie]]></code>
</MixedAssignment>
<PossiblyNullArrayOffset>
<code><![CDATA[$langs]]></code>
</PossiblyNullArrayOffset>
<RiskyTruthyFalsyComparison>
<code><![CDATA[empty($_GET['lang'])]]></code>
<code><![CDATA[empty($_POST['lang'])]]></code>
<code><![CDATA[empty($this->config->get('FilterLanguages'))]]></code>
<code><![CDATA[empty($this->config->get('Lang'))]]></code>
<code><![CDATA[empty($this->config->getCookie('pma_lang'))]]></code>
</RiskyTruthyFalsyComparison>
<RedundantConditionGivenDocblockType>
<code><![CDATA[$availableLocales !== false]]></code>
</RedundantConditionGivenDocblockType>
</file>
<file src="src/Image/ImageWrapper.php">
<PossiblyUnusedReturnValue>
@ -13409,17 +13393,9 @@
<code><![CDATA[providerForTestGetRoute]]></code>
</PossiblyUnusedMethod>
</file>
<file src="tests/unit/I18n/LanguageTest.php">
<DeprecatedMethod>
<code><![CDATA[Config::getInstance()]]></code>
<code><![CDATA[Config::getInstance()]]></code>
<code><![CDATA[Config::getInstance()]]></code>
<code><![CDATA[Config::getInstance()]]></code>
<code><![CDATA[Config::getInstance()]]></code>
<code><![CDATA[Config::getInstance()]]></code>
</DeprecatedMethod>
<file src="tests/unit/I18n/LanguageManagerTest.php">
<PossiblyUnusedMethod>
<code><![CDATA[listLocales]]></code>
<code><![CDATA[availableLocalesProvider]]></code>
<code><![CDATA[selectDataProvider]]></code>
</PossiblyUnusedMethod>
</file>

View File

@ -484,10 +484,11 @@ class Config
$this->setUserValue(null, 'lang', $GLOBALS['lang'], 'en');
}
} elseif (isset($configData['lang'])) {
$languageManager = LanguageManager::getInstance();
// read language from settings
$language = LanguageManager::getInstance()->getLanguage($configData['lang']);
$language = $languageManager->getLanguage($configData['lang']);
if ($language !== false) {
$language->activate();
$languageManager->activate($language);
$this->setCookie('pma_lang', $language->getCode());
}
}

View File

@ -14,8 +14,8 @@ final class LanguageLoading implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$language = LanguageManager::getInstance()->selectLanguage();
$language->activate();
$languageManager = LanguageManager::getInstance();
$languageManager->activate($languageManager->selectLanguage());
return $handler->handle($request);
}

View File

@ -4,15 +4,9 @@ declare(strict_types=1);
namespace PhpMyAdmin\I18n;
use function __;
use function _bindtextdomain;
use function _setlocale;
use function _textdomain;
use function addcslashes;
use function function_exists;
use function in_array;
use function preg_match;
use function setlocale;
use function str_contains;
use function str_replace;
use function strcmp;
@ -146,37 +140,4 @@ class Language
{
return in_array($this->code, ['ar', 'fa', 'he', 'ur'], true);
}
/**
* Activates given translation
*/
public function activate(): void
{
$GLOBALS['lang'] = $this->code;
// Set locale
_setlocale(0, $this->code);
_bindtextdomain('phpmyadmin', LOCALE_PATH);
_textdomain('phpmyadmin');
// Set PHP locale as well
if (function_exists('setlocale')) {
setlocale(0, $this->code);
}
LanguageManager::$textDirection = $this->isRTL() ? TextDirection::RightToLeft : TextDirection::LeftToRight;
/* TCPDF */
$GLOBALS['l'] = [];
/* TCPDF settings */
$GLOBALS['l']['a_meta_charset'] = 'UTF-8';
$GLOBALS['l']['a_meta_dir'] = LanguageManager::$textDirection->value;
$GLOBALS['l']['a_meta_language'] = $this->code;
/* TCPDF translations */
$GLOBALS['l']['w_page'] = __('Page number:');
/* Show possible warnings from language selection */
LanguageManager::getInstance()->showWarnings();
}
}

View File

@ -9,14 +9,20 @@ use PhpMyAdmin\Core;
use PhpMyAdmin\Exceptions\UnsupportedLanguageCode;
use function __;
use function _bindtextdomain;
use function _setlocale;
use function _textdomain;
use function closedir;
use function count;
use function explode;
use function file_exists;
use function function_exists;
use function is_dir;
use function is_string;
use function opendir;
use function preg_grep;
use function readdir;
use function setlocale;
use function strtolower;
use function uasort;
use function ucfirst;
@ -721,15 +727,12 @@ class LanguageManager
private bool $langFailedRequest = false;
private static LanguageManager|null $instance = null;
public static LanguageManager|null $instance = null;
public static TextDirection $textDirection = TextDirection::LeftToRight;
private readonly Config $config;
public function __construct()
public function __construct(private readonly Config $config)
{
$this->config = Config::getInstance();
}
/**
@ -738,7 +741,7 @@ class LanguageManager
public static function getInstance(): LanguageManager
{
if (self::$instance === null) {
self::$instance = new LanguageManager();
self::$instance = new LanguageManager(Config::getInstance());
}
return self::$instance;
@ -791,18 +794,19 @@ class LanguageManager
*/
public function availableLocales(): array
{
if ($this->availableLocales === []) {
if (empty($this->config->get('FilterLanguages'))) {
$this->availableLocales = $this->listLocaleDir();
} else {
$this->availableLocales = preg_grep(
'@' . $this->config->get('FilterLanguages') . '@',
$this->listLocaleDir(),
);
}
if ($this->availableLocales !== []) {
return $this->availableLocales;
}
return $this->availableLocales;
$filterLanguages = $this->config->get('FilterLanguages');
if (! is_string($filterLanguages) || $filterLanguages === '') {
return $this->availableLocales = $this->listLocaleDir();
}
$locales = $this->listLocaleDir();
$availableLocales = preg_grep('@' . $filterLanguages . '@', $locales);
return $this->availableLocales = $availableLocales !== false ? $availableLocales : $locales;
}
/**
@ -878,9 +882,9 @@ class LanguageManager
*/
public function selectLanguage(): Language
{
// check forced language
if (! empty($this->config->get('Lang'))) {
$lang = $this->getLanguage($this->config->get('Lang'));
$languageFromConfig = $this->config->get('Lang');
if (is_string($languageFromConfig) && $languageFromConfig !== '') {
$lang = $this->getLanguage($languageFromConfig);
if ($lang !== false) {
return $lang;
}
@ -888,10 +892,9 @@ class LanguageManager
$this->langFailedConfig = true;
}
// Don't use REQUEST in following code as it might be confused by cookies
// with same name. Check user requested language (POST)
if (! empty($_POST['lang'])) {
$lang = $this->getLanguage($_POST['lang']);
$languageFromRequest = $_POST['lang'] ?? null;
if (is_string($languageFromRequest) && $languageFromRequest !== '') {
$lang = $this->getLanguage($languageFromRequest);
if ($lang !== false) {
return $lang;
}
@ -899,9 +902,9 @@ class LanguageManager
$this->langFailedRequest = true;
}
// check user requested language (GET)
if (! empty($_GET['lang'])) {
$lang = $this->getLanguage($_GET['lang']);
$languageFromRequest = $_GET['lang'] ?? null;
if (is_string($languageFromRequest) && $languageFromRequest !== '') {
$lang = $this->getLanguage($languageFromRequest);
if ($lang !== false) {
return $lang;
}
@ -909,9 +912,9 @@ class LanguageManager
$this->langFailedRequest = true;
}
// check previous set language
if (! empty($this->config->getCookie('pma_lang'))) {
$lang = $this->getLanguage($this->config->getCookie('pma_lang'));
$languageFromCookie = $this->config->getCookie('pma_lang');
if (is_string($languageFromCookie) && $languageFromCookie !== '') {
$lang = $this->getLanguage($languageFromCookie);
if ($lang !== false) {
return $lang;
}
@ -950,6 +953,8 @@ class LanguageManager
/**
* Displays warnings about invalid languages. This needs to be postponed
* to show messages at time when language is initialized.
*
* @throws UnsupportedLanguageCode
*/
public function showWarnings(): void
{
@ -960,4 +965,36 @@ class LanguageManager
throw new UnsupportedLanguageCode(__('Ignoring unsupported language code.'));
}
/**
* Activates given translation
*
* @throws UnsupportedLanguageCode
*/
public function activate(Language $language): void
{
$GLOBALS['lang'] = $language->getCode();
// Set locale
_setlocale(0, $language->getCode());
_bindtextdomain('phpmyadmin', LOCALE_PATH);
_textdomain('phpmyadmin');
// Set PHP locale as well
if (function_exists('setlocale')) {
setlocale(0, $language->getCode());
}
self::$textDirection = $language->isRTL() ? TextDirection::RightToLeft : TextDirection::LeftToRight;
/* TCPDF settings and translations */
$GLOBALS['l'] = [
'a_meta_charset' => 'UTF-8',
'a_meta_dir' => self::$textDirection->value,
'a_meta_language' => $language->getCode(),
'w_page' => __('Page number:'),
];
/* Show possible warnings from language selection */
$this->showWarnings();
}
}

View File

@ -116,13 +116,14 @@ abstract class AbstractTestCase extends TestCase
protected function setLanguage(string $code = 'en'): void
{
$GLOBALS['lang'] = $code;
$languageManager = LanguageManager::getInstance();
/* Ensure default language is active */
$languageEn = LanguageManager::getInstance()->getLanguage($code);
$languageEn = $languageManager->getLanguage($code);
if ($languageEn === false) {
return;
}
$languageEn->activate();
$languageManager->activate($languageEn);
Translator::load();
}

View File

@ -13,6 +13,7 @@ use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Large;
use function _ngettext;
use function array_map;
use function count;
use function file_exists;
use function is_readable;
@ -21,23 +22,17 @@ use function strtolower;
#[CoversClass(Language::class)]
#[CoversClass(LanguageManager::class)]
#[Large]
class LanguageTest extends AbstractTestCase
final class LanguageManagerTest extends AbstractTestCase
{
private LanguageManager $manager;
/**
* Setup for Language tests.
*/
protected function setUp(): void
{
parent::setUp();
$loc = LOCALE_PATH . '/cs/LC_MESSAGES/phpmyadmin.mo';
if (! is_readable($loc)) {
self::markTestSkipped('Missing compiled locales.');
if (is_readable(LOCALE_PATH . '/cs/LC_MESSAGES/phpmyadmin.mo')) {
return;
}
$this->manager = new LanguageManager();
self::markTestSkipped('Missing compiled locales.');
}
protected function tearDown(): void
@ -45,12 +40,18 @@ class LanguageTest extends AbstractTestCase
parent::tearDown();
// Ensure we have English locale after tests
$lang = $this->manager->getLanguage('en');
if ($lang === false) {
return;
}
$languageManager = new LanguageManager(new Config());
$lang = $languageManager->getLanguage('en');
self::assertNotFalse($lang);
$languageManager->activate($lang);
}
$lang->activate();
public function testUniqueness(): void
{
LanguageManager::$instance = null;
$instanceOne = LanguageManager::getInstance();
$instanceTwo = LanguageManager::getInstance();
self::assertSame($instanceOne, $instanceTwo);
}
/**
@ -58,10 +59,10 @@ class LanguageTest extends AbstractTestCase
*/
public function testAvailable(): void
{
$config = Config::getInstance();
$config = new Config();
$config->set('FilterLanguages', 'cs|en$');
$langs = $this->manager->availableLocales();
$langs = (new LanguageManager($config))->availableLocales();
self::assertCount(2, $langs);
self::assertContains('cs', $langs);
@ -73,9 +74,10 @@ class LanguageTest extends AbstractTestCase
*/
public function testAllAvailable(): void
{
Config::getInstance()->set('FilterLanguages', '');
$config = new Config();
$config->set('FilterLanguages', '');
$langs = $this->manager->availableLocales();
$langs = (new LanguageManager($config))->availableLocales();
self::assertContains('cs', $langs);
self::assertContains('en', $langs);
@ -86,7 +88,7 @@ class LanguageTest extends AbstractTestCase
*/
public function testList(): void
{
$langs = $this->manager->listLocaleDir();
$langs = (new LanguageManager(new Config()))->listLocaleDir();
self::assertContains('cs', $langs);
self::assertContains('en', $langs);
}
@ -96,7 +98,7 @@ class LanguageTest extends AbstractTestCase
*/
public function testLanguages(): void
{
$langs = $this->manager->availableLanguages();
$langs = (new LanguageManager(new Config()))->availableLanguages();
self::assertGreaterThan(1, count($langs));
/* Ensure we have name for every language */
@ -115,12 +117,14 @@ class LanguageTest extends AbstractTestCase
*/
public function testMySQLLocale(): void
{
Config::getInstance()->set('FilterLanguages', '');
$czech = $this->manager->getLanguage('cs');
$config = new Config();
$config->set('FilterLanguages', '');
$languageManager = new LanguageManager($config);
$czech = $languageManager->getLanguage('cs');
self::assertNotFalse($czech);
self::assertSame('cs_CZ', $czech->getMySQLLocale());
$azerbaijani = $this->manager->getLanguage('az');
$azerbaijani = $languageManager->getLanguage('az');
self::assertNotFalse($azerbaijani);
self::assertSame('', $azerbaijani->getMySQLLocale());
}
@ -130,7 +134,7 @@ class LanguageTest extends AbstractTestCase
*/
public function testSortedLanguages(): void
{
$langs = $this->manager->sortedLanguages();
$langs = (new LanguageManager(new Config()))->sortedLanguages();
self::assertGreaterThan(1, count($langs));
}
@ -139,12 +143,14 @@ class LanguageTest extends AbstractTestCase
*/
public function testGet(): void
{
Config::getInstance()->set('FilterLanguages', '');
$lang = $this->manager->getLanguage('cs');
$config = new Config();
$config->set('FilterLanguages', '');
$languageManager = new LanguageManager($config);
$lang = $languageManager->getLanguage('cs');
self::assertNotFalse($lang);
self::assertSame('Czech', $lang->getEnglishName());
self::assertSame('Čeština', $lang->getNativeName());
$lang = $this->manager->getLanguage('nonexisting');
$lang = $languageManager->getLanguage('nonexisting');
self::assertFalse($lang);
}
@ -176,7 +182,7 @@ class LanguageTest extends AbstractTestCase
self::markTestSkipped('Locale file does not exists: ' . $expect);
}
$config = Config::getInstance();
$config = new Config();
$config->set('FilterLanguages', '');
$config->set('Lang', $lang);
$config->set('is_https', false);
@ -187,7 +193,7 @@ class LanguageTest extends AbstractTestCase
$_SERVER['HTTP_USER_AGENT'] = $agent;
$config->set('DefaultLang', $default);
$lang = $this->manager->selectLanguage();
$lang = (new LanguageManager($config))->selectLanguage();
self::assertSame($expect, $lang->getCode());
@ -233,42 +239,33 @@ class LanguageTest extends AbstractTestCase
];
}
/**
* Test for setting and parsing locales
*
* @param string $locale locale name
*/
#[DataProvider('listLocales')]
public function testGettext(string $locale): void
#[DataProvider('availableLocalesProvider')]
public function testSettingAndParsingLocales(string $localeCode): void
{
Config::getInstance()->set('FilterLanguages', '');
$config = new Config();
$config->set('FilterLanguages', '');
/* We should be able to set the language */
$lang = $this->manager->getLanguage($locale);
$languageManager = new LanguageManager($config);
$lang = $languageManager->getLanguage($localeCode);
self::assertNotFalse($lang);
$lang->activate();
$languageManager->activate($lang);
/* Grab some texts */
self::assertStringContainsString('%s', _ngettext('%s table', '%s tables', 10));
self::assertStringContainsString('%s', _ngettext('%s table', '%s tables', 1));
self::assertSame(
$locale,
$this->manager->getCurrentLanguage()->getCode(),
);
self::assertSame($localeCode, $languageManager->getCurrentLanguage()->getCode());
}
/**
* Data provider to generate list of available locales.
*
* @return mixed[] with arrays of available locales
* @return array<string, array{string}>
*/
public static function listLocales(): array
public static function availableLocalesProvider(): array
{
$ret = [];
foreach (LanguageManager::getInstance()->availableLanguages() as $language) {
$ret[] = [$language->getCode()];
}
$availableLanguages = (new LanguageManager(new Config()))->availableLanguages();
return $ret;
return array_map(static fn ($language) => [$language->getCode()], $availableLanguages);
}
}