diff --git a/eZ/Bundle/EzPublishCoreBundle/Resources/config/locale.yml b/eZ/Bundle/EzPublishCoreBundle/Resources/config/locale.yml index 4d1a4f22095..346fbf17dd2 100644 --- a/eZ/Bundle/EzPublishCoreBundle/Resources/config/locale.yml +++ b/eZ/Bundle/EzPublishCoreBundle/Resources/config/locale.yml @@ -54,7 +54,7 @@ parameters: de: ['ger-DE'] dk: ['dan-DK'] en: ['eng-GB', 'eng-US'] - en-us: ['eng-US'] + en_us: ['eng-US'] es: ['esl-ES'] fi: ['fin-FI'] fr: ['fre-FR'] @@ -103,3 +103,4 @@ services: autowire: true arguments: $languageCodesMap: '%ezpublish.locale.browser_map%' + $localeFallback: '%locale_fallback%' diff --git a/eZ/Publish/Core/MVC/Symfony/Locale/Tests/UserLanguagePreferenceProviderTest.php b/eZ/Publish/Core/MVC/Symfony/Locale/Tests/UserLanguagePreferenceProviderTest.php index e8e48a4d0e9..fe2c0998428 100644 --- a/eZ/Publish/Core/MVC/Symfony/Locale/Tests/UserLanguagePreferenceProviderTest.php +++ b/eZ/Publish/Core/MVC/Symfony/Locale/Tests/UserLanguagePreferenceProviderTest.php @@ -14,9 +14,16 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Yaml\Yaml; +use eZ\Publish\API\Repository\UserPreferenceService; +use eZ\Publish\API\Repository\Values\UserPreference\UserPreference; +use eZ\Publish\Core\Base\Exceptions\NotFoundException; class UserLanguagePreferenceProviderTest extends TestCase { + private const LOCALE_FALLBACK = 'en'; + private const LANGUAGE_PREFERENCE_NAME = 'language'; + private const LANGUAGE_PREFERENCE_VALUE = 'no'; + /** * @var \eZ\Publish\Core\MVC\Symfony\Locale\UserLanguagePreferenceProviderInterface */ @@ -27,13 +34,31 @@ class UserLanguagePreferenceProviderTest extends TestCase */ private $requestStackMock; + /** + * @var \eZ\Publish\API\Repository\UserPreferenceService + */ + private $userPreferenceServiceMock; + public function setUp() { $this->requestStackMock = $this->createMock(RequestStack::class); + $userLanguagePreference = new UserPreference([ + 'name' => self::LANGUAGE_PREFERENCE_NAME, + 'value' => self::LANGUAGE_PREFERENCE_VALUE, + ]); + + $this->userPreferenceServiceMock = $this->createMock(UserPreferenceService::class); + $this->userPreferenceServiceMock + ->method('getUserPreference') + ->with(self::LANGUAGE_PREFERENCE_NAME) + ->willReturn($userLanguagePreference); + $this->userLanguagePreferenceProvider = new UserLanguagePreferenceProvider( $this->requestStackMock, - $this->getLanguageCodesMap() + $this->userPreferenceServiceMock, + $this->getLanguageCodesMap(), + self::LOCALE_FALLBACK ); } @@ -43,7 +68,46 @@ public function setUp() * @param array $userLanguages * @param array $expectedEzLanguageCodes */ - public function testGetPreferredLanguages(array $userLanguages, array $expectedEzLanguageCodes) + public function testGetPreferredLanguagesWithoutUserLanguage(array $userLanguages, array $expectedEzLanguageCodes): void + { + $request = new Request(); + $request->headers = new HeaderBag( + [ + 'Accept-Language' => implode(', ', $userLanguages), + ] + ); + $this + ->requestStackMock + ->expects($this->once()) + ->method('getCurrentRequest') + ->willReturn($request); + + $userPreferenceServiceMock = $this->createMock(UserPreferenceService::class); + $userPreferenceServiceMock + ->method('getUserPreference') + ->with(self::LANGUAGE_PREFERENCE_NAME) + ->will($this->throwException(new NotFoundException('User Preference', self::LANGUAGE_PREFERENCE_NAME))); + + $userLanguagePreferenceProvider = new UserLanguagePreferenceProvider( + $this->requestStackMock, + $userPreferenceServiceMock, + $this->getLanguageCodesMap(), + self::LOCALE_FALLBACK + ); + + self::assertEquals( + $expectedEzLanguageCodes, + $userLanguagePreferenceProvider->getPreferredLanguages() + ); + } + + /** + * @dataProvider providerForTestGetPreferredLanguagesWithUserPreferredLanguage + * + * @param array $userLanguages + * @param array $expectedEzLanguageCodes + */ + public function testGetPreferredLanguagesWithUserPreferredLanguage(array $userLanguages, array $expectedEzLanguageCodes): void { $request = new Request(); $request->headers = new HeaderBag( @@ -57,9 +121,16 @@ public function testGetPreferredLanguages(array $userLanguages, array $expectedE ->method('getCurrentRequest') ->willReturn($request); + $userLanguagePreferenceProvider = new UserLanguagePreferenceProvider( + $this->requestStackMock, + $this->userPreferenceServiceMock, + $this->getLanguageCodesMap(), + self::LOCALE_FALLBACK + ); + self::assertEquals( $expectedEzLanguageCodes, - $this->userLanguagePreferenceProvider->getPreferredLanguages() + $userLanguagePreferenceProvider->getPreferredLanguages() ); } @@ -71,10 +142,27 @@ public function testGetPreferredLanguages(array $userLanguages, array $expectedE public function providerForTestGetPreferredLanguages(): array { return [ + [[], ['eng-GB', 'eng-US']], [['pl'], ['pol-PL']], - [['pol-PL'], ['pol-PL']], [['fr'], ['fre-FR']], [['en'], ['eng-GB', 'eng-US']], + [['en_us'], ['eng-US']], + ]; + } + + /** + * @see testGetPreferredLanguages + * + * @return array + */ + public function providerForTestGetPreferredLanguagesWithUserPreferredLanguage(): array + { + return [ + [[], ['nor-NO', 'eng-GB', 'eng-US']], + [['pl'], ['nor-NO', 'pol-PL']], + [['fr'], ['nor-NO', 'fre-FR']], + [['en'], ['nor-NO', 'eng-GB', 'eng-US']], + [['en_us'], ['nor-NO', 'eng-US']], ]; } diff --git a/eZ/Publish/Core/MVC/Symfony/Locale/UserLanguagePreferenceProvider.php b/eZ/Publish/Core/MVC/Symfony/Locale/UserLanguagePreferenceProvider.php index dfaa717fab1..a3195d4d19f 100644 --- a/eZ/Publish/Core/MVC/Symfony/Locale/UserLanguagePreferenceProvider.php +++ b/eZ/Publish/Core/MVC/Symfony/Locale/UserLanguagePreferenceProvider.php @@ -10,6 +10,8 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; +use eZ\Publish\API\Repository\UserPreferenceService; +use eZ\Publish\API\Repository\Exceptions\NotFoundException; class UserLanguagePreferenceProvider implements UserLanguagePreferenceProviderInterface { @@ -18,53 +20,72 @@ class UserLanguagePreferenceProvider implements UserLanguagePreferenceProviderIn */ private $requestStack; + /** + * @var \eZ\Publish\API\Repository\UserPreferenceService + */ + private $userPreferenceService; + /** * @var array */ private $languageCodesMap; + /** + * @var string + */ + private $localeFallback; + /** * @param \Symfony\Component\HttpFoundation\RequestStack $requestStack + * @param \eZ\Publish\API\Repository\UserPreferenceService $userPreferenceService * @param array $languageCodesMap + * @param string $localeFallback */ - public function __construct(RequestStack $requestStack, array $languageCodesMap) - { + public function __construct( + RequestStack $requestStack, + UserPreferenceService $userPreferenceService, + array $languageCodesMap, + string $localeFallback + ) { $this->requestStack = $requestStack; + $this->userPreferenceService = $userPreferenceService; $this->languageCodesMap = $languageCodesMap; + $this->localeFallback = $localeFallback; } public function getPreferredLocales(Request $request = null): array { + $languages = [$this->localeFallback]; + $request = $request ?? $this->requestStack->getCurrentRequest(); - $preferredLocales = $request->headers->get('Accept-Language') ?? ''; - $preferredLocales = array_reduce( - explode(',', $preferredLocales), - function (array $result, string $languageWithQuality) { - [$language, $quality] = array_merge(explode(';q=', $languageWithQuality), [1]); - $result[$language] = (float) $quality; + if (null !== $request) { + $browserLanguages = $request->getLanguages(); + if ([] !== $browserLanguages) { + $languages = $browserLanguages; + } + } - return $result; - }, - [] - ); - arsort($preferredLocales); + try { + $preferredLanguage = $this->userPreferenceService->getUserPreference('language')->value; + array_unshift($languages, $preferredLanguage); + } catch (NotFoundException $e) { + } - return array_keys($preferredLocales); + return array_unique($languages); } public function getPreferredLanguages(): array { - $languageCodes = []; + $languageCodes = [[]]; foreach ($this->getPreferredLocales() as $locale) { $locale = strtolower($locale); - if (isset($this->languageCodesMap[$locale])) { - $languageCodes = array_merge($languageCodes, $this->languageCodesMap[$locale]); - } elseif (preg_match('/^([a-z]{3})-([a-z]{2})$/', $locale, $matches)) { - // if the given locale is already in the eZ format - $languageCodes[] = strtolower($matches[1]) . '-' . strtoupper($matches[2]); + if (!isset($this->languageCodesMap[$locale])) { + continue; } + + $languageCodes[] = $this->languageCodesMap[$locale]; } - return array_unique($languageCodes); + return array_unique(array_merge(...$languageCodes)); } }