diff --git a/src/bundle/Resources/config/services/services.yaml b/src/bundle/Resources/config/services/services.yaml index 048f2476ce..befb31e0c5 100644 --- a/src/bundle/Resources/config/services/services.yaml +++ b/src/bundle/Resources/config/services/services.yaml @@ -13,3 +13,14 @@ services: Ibexa\Contracts\AdminUi\ContentType\ContentTypeFieldsByExpressionServiceInterface: '@Ibexa\AdminUi\ContentType\ContentTypeFieldsByExpressionService' + + Ibexa\AdminUi\Request\Resolver\AdminUiContentLanguageCodeResolver: + tags: + - { name: ibexa.admin_ui.content_language_code_resolver, priority: 100 } + + Ibexa\AdminUi\Request\Resolver\ChainContentLanguageCodeResolver: + arguments: + $resolvers: !tagged_iterator ibexa.admin_ui.content_language_code_resolver + + Ibexa\Contracts\AdminUi\Request\Resolver\ContentLanguageCodeResolverInterface: + '@Ibexa\AdminUi\Request\Resolver\ChainContentLanguageCodeResolver' diff --git a/src/contracts/Request/ContentLanguageContext.php b/src/contracts/Request/ContentLanguageContext.php new file mode 100644 index 0000000000..6583d16ba8 --- /dev/null +++ b/src/contracts/Request/ContentLanguageContext.php @@ -0,0 +1,14 @@ +resolver = $resolver; + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::REQUEST => ['onKernelRequest', 12], + ]; + } + + public function onKernelRequest(RequestEvent $event): void + { + if (HttpKernelInterface::MAIN_REQUEST !== $event->getRequestType()) { + return; + } + + $request = $event->getRequest(); + $normalizedLanguageCode = $request->attributes->get(ContentLanguageContext::ATTRIBUTE_NAME); + if (is_string($normalizedLanguageCode) && $normalizedLanguageCode !== '') { + return; + } + + if (null !== $languageCode = $this->resolver->resolve($request)) { + $request->attributes->set(ContentLanguageContext::ATTRIBUTE_NAME, $languageCode); + } + } +} diff --git a/src/lib/Request/Resolver/AdminUiContentLanguageCodeResolver.php b/src/lib/Request/Resolver/AdminUiContentLanguageCodeResolver.php new file mode 100644 index 0000000000..76cc0433e4 --- /dev/null +++ b/src/lib/Request/Resolver/AdminUiContentLanguageCodeResolver.php @@ -0,0 +1,35 @@ +attributes->get('language'); + if ($language instanceof Language) { + return $language->getLanguageCode(); + } + + if (is_string($language) && $language !== '') { + return $language; + } + + $languageCode = $request->attributes->get('languageCode'); + if (is_string($languageCode) && $languageCode !== '') { + return $languageCode; + } + + return null; + } +} diff --git a/src/lib/Request/Resolver/ChainContentLanguageCodeResolver.php b/src/lib/Request/Resolver/ChainContentLanguageCodeResolver.php new file mode 100644 index 0000000000..22e4202b1b --- /dev/null +++ b/src/lib/Request/Resolver/ChainContentLanguageCodeResolver.php @@ -0,0 +1,39 @@ + */ + private iterable $resolvers; + + /** + * @param iterable<\Ibexa\Contracts\AdminUi\Request\Resolver\ContentLanguageCodeResolverInterface> $resolvers + */ + public function __construct(iterable $resolvers) + { + $this->resolvers = $resolvers; + } + + public function resolve(Request $request): ?string + { + foreach ($this->resolvers as $resolver) { + $languageCode = $resolver->resolve($request); + + if (is_string($languageCode) && $languageCode !== '') { + return $languageCode; + } + } + + return null; + } +} diff --git a/tests/lib/EventListener/NormalizedLanguageCodeListenerTest.php b/tests/lib/EventListener/NormalizedLanguageCodeListenerTest.php new file mode 100644 index 0000000000..d56797f688 --- /dev/null +++ b/tests/lib/EventListener/NormalizedLanguageCodeListenerTest.php @@ -0,0 +1,103 @@ +resolver = $this->createMock(ContentLanguageCodeResolverInterface::class); + $this->listener = new NormalizedLanguageCodeListener($this->resolver); + } + + public function testLanguageCodeIsSetFromResolver(): void + { + $request = new Request(); + $this->resolver + ->expects(self::once()) + ->method('resolve') + ->with($request) + ->willReturn('eng-GB'); + + $this->listener->onKernelRequest($this->createEvent($request, HttpKernelInterface::MAIN_REQUEST)); + + self::assertSame('eng-GB', $request->attributes->get(ContentLanguageContext::ATTRIBUTE_NAME)); + } + + public function testNormalizedLanguageCodeHasPriorityOverResolver(): void + { + $request = new Request([], [], [ContentLanguageContext::ATTRIBUTE_NAME => 'eng-GB']); + $this->resolver + ->expects(self::never()) + ->method('resolve'); + + $this->listener->onKernelRequest($this->createEvent($request, HttpKernelInterface::MAIN_REQUEST)); + + self::assertSame('eng-GB', $request->attributes->get(ContentLanguageContext::ATTRIBUTE_NAME)); + } + + public function testNullValueFromResolverIsIgnored(): void + { + $request = new Request(); + $this->resolver + ->expects(self::once()) + ->method('resolve') + ->with($request) + ->willReturn(null); + + $this->listener->onKernelRequest($this->createEvent($request, HttpKernelInterface::MAIN_REQUEST)); + + self::assertNull($request->attributes->get(ContentLanguageContext::ATTRIBUTE_NAME)); + } + + public function testLanguageCodeIsNotChangedOnSubRequest(): void + { + $request = new Request(); + $this->resolver + ->expects(self::never()) + ->method('resolve'); + + $this->listener->onKernelRequest($this->createEvent($request, HttpKernelInterface::SUB_REQUEST)); + + self::assertNull($request->attributes->get(ContentLanguageContext::ATTRIBUTE_NAME)); + } + + public function testSubscribedEvents(): void + { + self::assertSame( + [KernelEvents::REQUEST => ['onKernelRequest', 12]], + NormalizedLanguageCodeListener::getSubscribedEvents() + ); + } + + private function createEvent(Request $request, int $requestType): RequestEvent + { + return new RequestEvent( + $this->createMock(HttpKernelInterface::class), + $request, + $requestType + ); + } +} diff --git a/tests/lib/Request/Resolver/AdminUiContentLanguageCodeResolverTest.php b/tests/lib/Request/Resolver/AdminUiContentLanguageCodeResolverTest.php new file mode 100644 index 0000000000..7f703d845b --- /dev/null +++ b/tests/lib/Request/Resolver/AdminUiContentLanguageCodeResolverTest.php @@ -0,0 +1,74 @@ +resolver = new AdminUiContentLanguageCodeResolver(); + } + + /** + * @dataProvider provideResolvableLanguageContext + * + * @param array $attributes + */ + public function testResolvesLanguageCode(array $attributes, string $expectedLanguageCode): void + { + $request = new Request([], [], $attributes); + + self::assertSame($expectedLanguageCode, $this->resolver->resolve($request)); + } + + /** + * @dataProvider provideUnresolvableLanguageContext + * + * @param array $attributes + */ + public function testReturnsNullForUnresolvableLanguageContext(array $attributes): void + { + self::assertNull($this->resolver->resolve(new Request([], [], $attributes))); + } + + /** + * @return iterable, string}> + */ + public function provideResolvableLanguageContext(): iterable + { + yield 'language object' => [ + ['language' => new Language(['languageCode' => 'eng-GB'])], + 'eng-GB', + ]; + yield 'language string' => [ + ['language' => 'ger-DE'], + 'ger-DE', + ]; + yield 'languageCode string' => [ + ['languageCode' => 'pol-PL'], + 'pol-PL', + ]; + } + + /** + * @return iterable}> + */ + public function provideUnresolvableLanguageContext(): iterable + { + yield 'missing context' => [[]]; + yield 'empty context' => [['language' => '', 'languageCode' => '']]; + } +} diff --git a/tests/lib/Request/Resolver/ChainContentLanguageCodeResolverTest.php b/tests/lib/Request/Resolver/ChainContentLanguageCodeResolverTest.php new file mode 100644 index 0000000000..b8cc22926f --- /dev/null +++ b/tests/lib/Request/Resolver/ChainContentLanguageCodeResolverTest.php @@ -0,0 +1,50 @@ +createResolverMock(null), + $this->createResolverMock(''), + $this->createResolverMock('eng-GB'), + $this->createResolverMock('ger-DE'), + ]); + + self::assertSame('eng-GB', $resolver->resolve($request)); + } + + public function testReturnsNullWhenNoResolverMatches(): void + { + $resolver = new ChainContentLanguageCodeResolver([ + $this->createResolverMock(null), + $this->createResolverMock(''), + ]); + + self::assertNull($resolver->resolve(new Request())); + } + + private function createResolverMock(?string $value): ContentLanguageCodeResolverInterface + { + $resolver = $this->createMock(ContentLanguageCodeResolverInterface::class); + $resolver + ->method('resolve') + ->willReturn($value); + + return $resolver; + } +}