From bb52d5d6d5638e17dd1de33b195dbbc5dbca56cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20H=C3=A9bert?= Date: Mon, 20 Apr 2020 19:33:00 +0200 Subject: [PATCH 1/3] [HttpFoundation] add a default Content-Language to the Response --- .../DependencyInjection/Configuration.php | 6 ++ .../FrameworkExtension.php | 1 + .../Resources/config/schema/symfony-1.0.xsd | 1 + .../FrameworkBundle/Resources/config/web.xml | 1 + .../DependencyInjection/ConfigurationTest.php | 1 + .../DependencyInjection/Fixtures/php/full.php | 1 + .../DependencyInjection/Fixtures/xml/full.xml | 2 +- .../DependencyInjection/Fixtures/yml/full.yml | 1 + .../FrameworkExtensionTest.php | 1 + .../Functional/app/ConfigDump/config.yml | 1 + .../Tests/Functional/app/Slugger/config.yml | 1 + .../Functional/app/TransDebug/config.yml | 1 + .../Tests/Functional/app/config/framework.yml | 1 + .../Component/HttpFoundation/Response.php | 4 ++ .../HttpFoundation/Tests/ResponseTest.php | 22 +++++++ .../EventListener/LocaleListener.php | 9 ++- .../EventListener/LocaleListenerTest.php | 66 +++++++++++++++++++ .../EventListener/ResponseListenerTest.php | 15 +++++ 18 files changed, 133 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 7caec9b49feab..5195eb32a4c7d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -81,6 +81,12 @@ public function getConfigTreeBuilder() ->scalarNode('ide')->defaultNull()->end() ->booleanNode('test')->end() ->scalarNode('default_locale')->defaultValue('en')->end() + ->arrayNode('available_locales') + ->info('An array of available locales for your application. It will help determine the locale according to the Accept-Language header.') + ->beforeNormalization()->castToArray()->end() + ->defaultValue([]) + ->prototype('scalar')->end() + ->end() ->arrayNode('trusted_hosts') ->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end() ->prototype('scalar')->end() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index cef82f8ce1fc0..752db422b6d65 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -234,6 +234,7 @@ public function load(array $configs, ContainerBuilder $container) $container->setParameter('kernel.http_method_override', $config['http_method_override']); $container->setParameter('kernel.trusted_hosts', $config['trusted_hosts']); $container->setParameter('kernel.default_locale', $config['default_locale']); + $container->setParameter('kernel.available_locales', $config['available_locales']); $container->setParameter('kernel.error_controller', $config['error_controller']); if (!$container->hasParameter('debug.file_link_format')) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index 99ffbabb82cdc..850168fc58b27 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -39,6 +39,7 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml index cbdc5586a27ad..a8c94123b8ad3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml @@ -59,6 +59,7 @@ %kernel.default_locale% + %kernel.available_locales% diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index fad9b09a049fa..703f230bb5450 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -341,6 +341,7 @@ protected static function getBundleDefaultConfig() 'http_method_override' => true, 'ide' => null, 'default_locale' => 'en', + 'available_locales' => [], 'csrf_protection' => [ 'enabled' => false, ], diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php index b11b5e08dcb96..9ad1377a3dea8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php @@ -3,6 +3,7 @@ $container->loadFromExtension('framework', [ 'secret' => 's3cr3t', 'default_locale' => 'fr', + 'available_locales' => ['fr'], 'csrf_protection' => true, 'form' => [ 'csrf_protection' => [ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml index 10a646049d766..a177c9fac42df 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml @@ -6,7 +6,7 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml index 5ad80a2da4db2..497cfd85b1b7c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml @@ -1,6 +1,7 @@ framework: secret: s3cr3t default_locale: fr + available_locales: ['fr'] csrf_protection: true form: csrf_protection: diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 55332ab60bc55..5fcb25f1ac077 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -476,6 +476,7 @@ public function testSession() $this->assertTrue($container->hasDefinition('session'), '->registerSessionConfiguration() loads session.xml'); $this->assertEquals('fr', $container->getParameter('kernel.default_locale')); + $this->assertEquals(['fr'], $container->getParameter('kernel.available_locales')); $this->assertEquals('session.storage.native', (string) $container->getAlias('session.storage')); $this->assertEquals('session.handler.native_file', (string) $container->getAlias('session.handler')); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ConfigDump/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ConfigDump/config.yml index 432e35bd2f24d..81fe01fbb5f7d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ConfigDump/config.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ConfigDump/config.yml @@ -4,6 +4,7 @@ imports: framework: secret: '%secret%' default_locale: '%env(LOCALE)%' + available_locales: ['%env(LOCALE)%'] session: cookie_httponly: '%env(bool:COOKIE_HTTPONLY)%' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Slugger/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Slugger/config.yml index f80091b831e05..689af7f1a62ef 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Slugger/config.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Slugger/config.yml @@ -5,6 +5,7 @@ imports: framework: secret: '%secret%' default_locale: '%env(LOCALE)%' + available_locales: ['%env(LOCALE)%'] translator: fallbacks: - '%env(LOCALE)%' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/TransDebug/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/TransDebug/config.yml index 7f8815b2942fa..2d5e678fc6b93 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/TransDebug/config.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/TransDebug/config.yml @@ -5,6 +5,7 @@ imports: framework: secret: '%secret%' default_locale: '%env(LOCALE)%' + available_locales: ['%env(LOCALE)%'] translator: fallbacks: - '%env(LOCALE)%' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/config/framework.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/config/framework.yml index 1c42894a24d9c..ed02779fdc505 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/config/framework.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/config/framework.yml @@ -6,6 +6,7 @@ framework: form: true test: true default_locale: en + available_locales: ['en', 'fr'] session: storage_id: session.storage.mock_file diff --git a/src/Symfony/Component/HttpFoundation/Response.php b/src/Symfony/Component/HttpFoundation/Response.php index ddfd1826e46cd..3e4a41e527165 100644 --- a/src/Symfony/Component/HttpFoundation/Response.php +++ b/src/Symfony/Component/HttpFoundation/Response.php @@ -316,6 +316,10 @@ public function prepare(Request $request) $headers->set('Content-Length', $length); } } + + if (!$headers->has('Content-Language')) { + $headers->set('Content-Language', $request->getLocale()); + } } // Fix protocol diff --git a/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php b/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php index 2dea5c2b2aecf..55363c52cc628 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php @@ -606,6 +606,28 @@ public function testPrepareSetsCookiesSecure() $this->assertTrue($cookie->isSecure()); } + public function testPrepareSetsContentLanguage() + { + $response = new Response('foo'); + $request = new Request(); + $request->setLocale('fr'); + $response->prepare($request); + + $this->assertSame('fr', $response->headers->get('Content-Language')); + } + + public function testPrepareDoNotOverrideContentLanguage() + { + $response = new Response('foo'); + $response->headers->set('Content-Language', 'fr'); + + $request = new Request(); + $request->setLocale('de'); + $response->prepare($request); + + $this->assertSame('fr', $response->headers->get('Content-Language')); + } + public function testSetCache() { $response = new Response(); diff --git a/src/Symfony/Component/HttpKernel/EventListener/LocaleListener.php b/src/Symfony/Component/HttpKernel/EventListener/LocaleListener.php index 8037e32c0e90e..934ed3ea73fa5 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/LocaleListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/LocaleListener.php @@ -31,10 +31,12 @@ class LocaleListener implements EventSubscriberInterface { private $router; private $defaultLocale; + private $availableLocales; private $requestStack; - public function __construct(RequestStack $requestStack, string $defaultLocale = 'en', RequestContextAwareInterface $router = null) + public function __construct(RequestStack $requestStack, string $defaultLocale = 'en', RequestContextAwareInterface $router = null, ?array $availableLocales = null) { + $this->availableLocales = $availableLocales; $this->defaultLocale = $defaultLocale; $this->requestStack = $requestStack; $this->router = $router; @@ -65,6 +67,11 @@ private function setLocale(Request $request) if ($locale = $request->attributes->get('_locale')) { $request->setLocale($locale); } + + if (null === $locale && null !== $this->availableLocales && + $preferredLanguage = $request->getPreferredLanguage($this->availableLocales)) { + $request->setLocale($preferredLanguage); + } } private function setRouterContext(Request $request) diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/LocaleListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/LocaleListenerTest.php index cb502a89eecc4..d51bce37f912f 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/LocaleListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/LocaleListenerTest.php @@ -114,6 +114,72 @@ public function testRequestLocaleIsNotOverridden() $this->assertEquals('de', $request->getLocale()); } + public function testRequestPreferredLocaleFromAcceptLanguageHeader() + { + $request = Request::create('/'); + $request->headers->set('Accept-Language', ['Accept-Language: fr-FR,fr;q=0.9,en-GB;q=0.8,en;q=0.7,en-US;q=0.6,es;q=0.5']); + + $listener = new LocaleListener($this->requestStack, 'de', null, ['de', 'fr']); + $event = $this->getEvent($request); + + $listener->setDefaultLocale($event); + $listener->onKernelRequest($event); + $this->assertEquals('fr', $request->getLocale()); + } + + public function testRequestSecondPreferredLocaleFromAcceptLanguageHeader() + { + $request = Request::create('/'); + $request->headers->set('Accept-Language', ['Accept-Language: fr-FR,fr;q=0.9,en-GB;q=0.8,en;q=0.7,en-US;q=0.6,es;q=0.5']); + + $listener = new LocaleListener($this->requestStack, 'de', null, ['de', 'en']); + $event = $this->getEvent($request); + + $listener->setDefaultLocale($event); + $listener->onKernelRequest($event); + $this->assertEquals('en', $request->getLocale()); + } + + public function testRequestUnavailablePreferredLocaleFromAcceptLanguageHeader() + { + $request = Request::create('/'); + $request->headers->set('Accept-Language', ['Accept-Language: fr-FR,fr;q=0.9,en-GB;q=0.8,en;q=0.7,en-US;q=0.6,es;q=0.5']); + + $listener = new LocaleListener($this->requestStack, 'de', null, ['de', 'it']); + $event = $this->getEvent($request); + + $listener->setDefaultLocale($event); + $listener->onKernelRequest($event); + $this->assertEquals('de', $request->getLocale()); + } + + public function testRequestNoLocaleFromAcceptLanguageHeader() + { + $request = Request::create('/'); + $request->headers->set('Accept-Language', ['Accept-Language: fr-FR,fr;q=0.9,en-GB;q=0.8,en;q=0.7,en-US;q=0.6,es;q=0.5']); + + $listener = new LocaleListener($this->requestStack, 'de'); + $event = $this->getEvent($request); + + $listener->setDefaultLocale($event); + $listener->onKernelRequest($event); + $this->assertEquals('de', $request->getLocale()); + } + + public function testRequestAttributeLocaleNotOverridenFromAcceptLanguageHeader() + { + $request = Request::create('/'); + $request->attributes->set('_locale', 'it'); + $request->headers->set('Accept-Language', ['Accept-Language: fr-FR,fr;q=0.9,en-GB;q=0.8,en;q=0.7,en-US;q=0.6,es;q=0.5']); + + $listener = new LocaleListener($this->requestStack, 'de', null, ['fr', 'en']); + $event = $this->getEvent($request); + + $listener->setDefaultLocale($event); + $listener->onKernelRequest($event); + $this->assertEquals('it', $request->getLocale()); + } + private function getEvent(Request $request): RequestEvent { return new RequestEvent($this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(), $request, HttpKernelInterface::MASTER_REQUEST); diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/ResponseListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/ResponseListenerTest.php index 1aaa64bc89ced..eb2405dc0fb21 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/ResponseListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/ResponseListenerTest.php @@ -92,4 +92,19 @@ public function testFiltersSetsNonDefaultCharsetIfNotOverriddenOnNonTextContentT $this->assertEquals('ISO-8859-15', $response->getCharset()); } + + public function testSetContentLanguageHeaderWhenEmptyAccceptLocaleHeaders() + { + $listener = new ResponseListener('ISO-8859-15'); + $this->dispatcher->addListener(KernelEvents::RESPONSE, [$listener, 'onKernelResponse'], 1); + + $response = new Response('content'); + $request = Request::create('/'); + $request->setLocale('fr'); + + $event = new ResponseEvent($this->kernel, $request, HttpKernelInterface::MASTER_REQUEST, $response); + $this->dispatcher->dispatch($event, KernelEvents::RESPONSE); + + $this->assertEquals('fr', $response->headers->get('Content-Language')); + } } From 6cb2fe85de0de9000b2ca2d0d7b4f6671cb2ef54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20H=C3=A9bert?= Date: Tue, 21 Apr 2020 14:21:04 +0200 Subject: [PATCH 2/3] fix review --- .../DependencyInjection/Configuration.php | 11 ++++-- .../Resources/config/schema/symfony-1.0.xsd | 3 +- .../Fixtures/php/available_locales.php | 7 ++++ ...ocales_cannot_fallback_enabled_locales.php | 6 ++++ ...lable_locales_fallback_enabled_locales.php | 13 +++++++ .../DependencyInjection/Fixtures/php/full.php | 4 +-- .../Fixtures/xml/available_locales.xml | 13 +++++++ ...ocales_cannot_fallback_enabled_locales.xml | 14 ++++++++ ...lable_locales_fallback_enabled_locales.xml | 16 +++++++++ .../DependencyInjection/Fixtures/xml/full.xml | 4 ++- .../Fixtures/yml/available_locales.yml | 4 +++ ...ocales_cannot_fallback_enabled_locales.yml | 8 +++++ ...lable_locales_fallback_enabled_locales.yml | 9 +++++ .../DependencyInjection/Fixtures/yml/full.yml | 2 +- .../FrameworkExtensionTest.php | 20 ++++++++++- .../Component/HttpFoundation/Response.php | 4 --- .../HttpFoundation/Tests/ResponseTest.php | 22 ------------ .../EventListener/LocaleListener.php | 4 +-- .../EventListener/ResponseListener.php | 9 ++++- .../EventListener/ResponseListenerTest.php | 35 +++++++++++++++++-- 20 files changed, 169 insertions(+), 39 deletions(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/available_locales.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/available_locales_cannot_fallback_enabled_locales.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/available_locales_fallback_enabled_locales.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/available_locales.xml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/available_locales_cannot_fallback_enabled_locales.xml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/available_locales_fallback_enabled_locales.xml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/available_locales.yml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/available_locales_cannot_fallback_enabled_locales.yml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/available_locales_fallback_enabled_locales.yml diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 5195eb32a4c7d..9cb41634a0309 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -72,6 +72,14 @@ public function getConfigTreeBuilder() return $v; }) ->end() + ->beforeNormalization() + ->ifTrue(function ($v) { return !isset($v['available_locales']) && (isset($v['translator']['enabled_locales']) || isset($v['translator']['enabled-locale'])); }) + ->then(function ($v) { + $v['available_locales'] = $v['translator']['enabled_locales'] ?? $v['translator']['enabled-locale']; + + return $v; + }) + ->end() ->children() ->scalarNode('secret')->end() ->scalarNode('http_method_override') @@ -83,8 +91,7 @@ public function getConfigTreeBuilder() ->scalarNode('default_locale')->defaultValue('en')->end() ->arrayNode('available_locales') ->info('An array of available locales for your application. It will help determine the locale according to the Accept-Language header.') - ->beforeNormalization()->castToArray()->end() - ->defaultValue([]) + ->beforeNormalization()->ifEmpty()->thenUnset()->end() ->prototype('scalar')->end() ->end() ->arrayNode('trusted_hosts') diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index 850168fc58b27..f03803a55bb24 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -33,15 +33,16 @@ + - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/available_locales.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/available_locales.php new file mode 100644 index 0000000000000..0e715ec000753 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/available_locales.php @@ -0,0 +1,7 @@ +loadFromExtension('framework', [ + 'secret' => 's3cr3t', + 'default_locale' => 'fr', + 'available_locales' => ['mi', 'fr'], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/available_locales_cannot_fallback_enabled_locales.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/available_locales_cannot_fallback_enabled_locales.php new file mode 100644 index 0000000000000..92fc12a1ac631 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/available_locales_cannot_fallback_enabled_locales.php @@ -0,0 +1,6 @@ +loadFromExtension('framework', [ + 'secret' => 's3cr3t', + 'default_locale' => 'fr', +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/available_locales_fallback_enabled_locales.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/available_locales_fallback_enabled_locales.php new file mode 100644 index 0000000000000..c4bad94f23399 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/available_locales_fallback_enabled_locales.php @@ -0,0 +1,13 @@ +loadFromExtension('framework', [ + 'secret' => 's3cr3t', + 'default_locale' => 'fr', + 'translator' => [ + 'enabled' => true, + 'fallback' => 'fr', + 'paths' => ['%kernel.project_dir%/Fixtures/translations'], + 'cache_dir' => '%kernel.cache_dir%/translations', + 'enabled_locales' => ['mi', 'fr'], + ], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php index 9ad1377a3dea8..ee3054ebf9945 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php @@ -3,7 +3,7 @@ $container->loadFromExtension('framework', [ 'secret' => 's3cr3t', 'default_locale' => 'fr', - 'available_locales' => ['fr'], + 'available_locales' => ['mi', 'fr'], 'csrf_protection' => true, 'form' => [ 'csrf_protection' => [ @@ -51,7 +51,7 @@ 'fallback' => 'fr', 'paths' => ['%kernel.project_dir%/Fixtures/translations'], 'cache_dir' => '%kernel.cache_dir%/translations', - 'enabled_locales' => ['fr', 'en'] + 'enabled_locales' => ['fr', 'en'], ], 'validation' => [ 'enabled' => true, diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/available_locales.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/available_locales.xml new file mode 100644 index 0000000000000..d69430672f1b5 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/available_locales.xml @@ -0,0 +1,13 @@ + + + + + + mi + fr + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/available_locales_cannot_fallback_enabled_locales.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/available_locales_cannot_fallback_enabled_locales.xml new file mode 100644 index 0000000000000..6866841890312 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/available_locales_cannot_fallback_enabled_locales.xml @@ -0,0 +1,14 @@ + + + + + + + %kernel.project_dir%/Fixtures/translations + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/available_locales_fallback_enabled_locales.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/available_locales_fallback_enabled_locales.xml new file mode 100644 index 0000000000000..d9bf05448b019 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/available_locales_fallback_enabled_locales.xml @@ -0,0 +1,16 @@ + + + + + + + %kernel.project_dir%/Fixtures/translations + mi + fr + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml index a177c9fac42df..0a2d73f5403fd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml @@ -6,7 +6,9 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> - + + mi + fr diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/available_locales.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/available_locales.yml new file mode 100644 index 0000000000000..9c858808a4a3a --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/available_locales.yml @@ -0,0 +1,4 @@ +framework: + secret: s3cr3t + default_locale: fr + available_locales: [mi, fr] diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/available_locales_cannot_fallback_enabled_locales.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/available_locales_cannot_fallback_enabled_locales.yml new file mode 100644 index 0000000000000..0cdd21756c145 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/available_locales_cannot_fallback_enabled_locales.yml @@ -0,0 +1,8 @@ +framework: + secret: s3cr3t + default_locale: fr + translator: + enabled: true + fallback: fr + cache_dir: '%kernel.cache_dir%/translations' + paths: ['%kernel.project_dir%/Fixtures/translations'] diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/available_locales_fallback_enabled_locales.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/available_locales_fallback_enabled_locales.yml new file mode 100644 index 0000000000000..5a91c2493f785 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/available_locales_fallback_enabled_locales.yml @@ -0,0 +1,9 @@ +framework: + secret: s3cr3t + default_locale: fr + translator: + enabled: true + fallback: fr + cache_dir: '%kernel.cache_dir%/translations' + paths: ['%kernel.project_dir%/Fixtures/translations'] + enabled_locales: [mi, fr] diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml index 497cfd85b1b7c..b53d82480e809 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml @@ -1,7 +1,7 @@ framework: secret: s3cr3t default_locale: fr - available_locales: ['fr'] + available_locales: ['mi', 'fr'] csrf_protection: true form: csrf_protection: diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 5fcb25f1ac077..5fd335a23b1fe 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -476,7 +476,7 @@ public function testSession() $this->assertTrue($container->hasDefinition('session'), '->registerSessionConfiguration() loads session.xml'); $this->assertEquals('fr', $container->getParameter('kernel.default_locale')); - $this->assertEquals(['fr'], $container->getParameter('kernel.available_locales')); + $this->assertEquals(['mi', 'fr'], $container->getParameter('kernel.available_locales')); $this->assertEquals('session.storage.native', (string) $container->getAlias('session.storage')); $this->assertEquals('session.handler.native_file', (string) $container->getAlias('session.handler')); @@ -727,6 +727,24 @@ public function testMessengerInvalidTransportRouting() $this->createContainerFromFile('messenger_routing_invalid_transport'); } + public function testAvailableLocales() + { + $container = $this->createContainerFromFile('available_locales'); + $this->assertEquals(['mi', 'fr'], $container->getParameter('kernel.available_locales')); + } + + public function testAvailableLocalesFallbackOnEnabledLocales() + { + $container = $this->createContainerFromFile('available_locales_fallback_enabled_locales'); + $this->assertEquals(['mi', 'fr'], $container->getParameter('kernel.available_locales')); + } + + public function testAvailableLocalesCannotFallbackOnEnabledLocales() + { + $container = $this->createContainerFromFile('available_locales_cannot_fallback_enabled_locales'); + $this->assertEquals([], $container->getParameter('kernel.available_locales')); + } + public function testTranslator() { $container = $this->createContainerFromFile('full'); diff --git a/src/Symfony/Component/HttpFoundation/Response.php b/src/Symfony/Component/HttpFoundation/Response.php index 3e4a41e527165..ddfd1826e46cd 100644 --- a/src/Symfony/Component/HttpFoundation/Response.php +++ b/src/Symfony/Component/HttpFoundation/Response.php @@ -316,10 +316,6 @@ public function prepare(Request $request) $headers->set('Content-Length', $length); } } - - if (!$headers->has('Content-Language')) { - $headers->set('Content-Language', $request->getLocale()); - } } // Fix protocol diff --git a/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php b/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php index 55363c52cc628..2dea5c2b2aecf 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php @@ -606,28 +606,6 @@ public function testPrepareSetsCookiesSecure() $this->assertTrue($cookie->isSecure()); } - public function testPrepareSetsContentLanguage() - { - $response = new Response('foo'); - $request = new Request(); - $request->setLocale('fr'); - $response->prepare($request); - - $this->assertSame('fr', $response->headers->get('Content-Language')); - } - - public function testPrepareDoNotOverrideContentLanguage() - { - $response = new Response('foo'); - $response->headers->set('Content-Language', 'fr'); - - $request = new Request(); - $request->setLocale('de'); - $response->prepare($request); - - $this->assertSame('fr', $response->headers->get('Content-Language')); - } - public function testSetCache() { $response = new Response(); diff --git a/src/Symfony/Component/HttpKernel/EventListener/LocaleListener.php b/src/Symfony/Component/HttpKernel/EventListener/LocaleListener.php index 934ed3ea73fa5..103d0423f1119 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/LocaleListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/LocaleListener.php @@ -34,7 +34,7 @@ class LocaleListener implements EventSubscriberInterface private $availableLocales; private $requestStack; - public function __construct(RequestStack $requestStack, string $defaultLocale = 'en', RequestContextAwareInterface $router = null, ?array $availableLocales = null) + public function __construct(RequestStack $requestStack, string $defaultLocale = 'en', RequestContextAwareInterface $router = null, array $availableLocales = []) { $this->availableLocales = $availableLocales; $this->defaultLocale = $defaultLocale; @@ -68,7 +68,7 @@ private function setLocale(Request $request) $request->setLocale($locale); } - if (null === $locale && null !== $this->availableLocales && + if (null === $locale && !empty($this->availableLocales) && $preferredLanguage = $request->getPreferredLanguage($this->availableLocales)) { $request->setLocale($preferredLanguage); } diff --git a/src/Symfony/Component/HttpKernel/EventListener/ResponseListener.php b/src/Symfony/Component/HttpKernel/EventListener/ResponseListener.php index d8292aec43e49..a5fdb6bda9e05 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/ResponseListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/ResponseListener.php @@ -25,10 +25,12 @@ class ResponseListener implements EventSubscriberInterface { private $charset; + private $availableLocales; - public function __construct(string $charset) + public function __construct(string $charset, array $availableLocales = []) { $this->charset = $charset; + $this->availableLocales = $availableLocales; } /** @@ -46,6 +48,11 @@ public function onKernelResponse(ResponseEvent $event) $response->setCharset($this->charset); } + if (!empty($this->availableLocales) && !$response->isInformational() && !$response->isEmpty() && !$response->headers->has('Content-Language')) { + $response->headers->set('Content-Language', $event->getRequest()->getLocale()); + $response->setVary('Accept-Language', false); + } + $response->prepare($event->getRequest()); } diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/ResponseListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/ResponseListenerTest.php index eb2405dc0fb21..8e210a8c4af7c 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/ResponseListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/ResponseListenerTest.php @@ -93,9 +93,9 @@ public function testFiltersSetsNonDefaultCharsetIfNotOverriddenOnNonTextContentT $this->assertEquals('ISO-8859-15', $response->getCharset()); } - public function testSetContentLanguageHeaderWhenEmptyAccceptLocaleHeaders() + public function testSetContentLanguageHeaderWhenEmptyAndWithAvailableLocales() { - $listener = new ResponseListener('ISO-8859-15'); + $listener = new ResponseListener('ISO-8859-15', ['fr']); $this->dispatcher->addListener(KernelEvents::RESPONSE, [$listener, 'onKernelResponse'], 1); $response = new Response('content'); @@ -107,4 +107,35 @@ public function testSetContentLanguageHeaderWhenEmptyAccceptLocaleHeaders() $this->assertEquals('fr', $response->headers->get('Content-Language')); } + + public function testNotOverrideContentLanguageHeaderWhenNotEmpty() + { + $listener = new ResponseListener('ISO-8859-15', ['de']); + $this->dispatcher->addListener(KernelEvents::RESPONSE, [$listener, 'onKernelResponse'], 1); + + $response = new Response('content'); + $response->headers->set('Content-Language', 'mi, en'); + $request = Request::create('/'); + $request->setLocale('de'); + + $event = new ResponseEvent($this->kernel, $request, HttpKernelInterface::MASTER_REQUEST, $response); + $this->dispatcher->dispatch($event, KernelEvents::RESPONSE); + + $this->assertEquals('mi, en', $response->headers->get('Content-Language')); + } + + public function testNotSetContentLanguageHeaderWhenEmptyAvailableLocales() + { + $listener = new ResponseListener('ISO-8859-15'); + $this->dispatcher->addListener(KernelEvents::RESPONSE, [$listener, 'onKernelResponse'], 1); + + $response = new Response('content'); + $request = Request::create('/'); + $request->setLocale('fr'); + + $event = new ResponseEvent($this->kernel, $request, HttpKernelInterface::MASTER_REQUEST, $response); + $this->dispatcher->dispatch($event, KernelEvents::RESPONSE); + + $this->assertNull($response->headers->get('Content-Language')); + } } From 6f0d7a981decfdee2ff00851718157154a6e495a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20H=C3=A9bert?= Date: Tue, 21 Apr 2020 14:28:54 +0200 Subject: [PATCH 3/3] update changelogs --- src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md | 1 + src/Symfony/Component/HttpKernel/CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index fe887ca9c97bd..43139cc0a23d1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 5.1.0 ----- + * Added `available_locales` for `Content-language` header handling * Added link to source for controllers registered as named services * Added link to source on controller on `router:match`/`debug:router` (when `framework.ide` is configured) * Added `Routing\Loader` and `Routing\Loader\Configurator` namespaces to ease defining routes with default controllers diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md index 1b766484b6d3c..4c078c20834b5 100644 --- a/src/Symfony/Component/HttpKernel/CHANGELOG.md +++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 5.1.0 ----- + * Allow request locale and Response `Content-Language` header to be set from `preferredLocale` thanks to `available_locales` * made `WarmableInterface::warmUp()` return a list of classes or files to preload on PHP 7.4+; not returning an array is deprecated * deprecated support for `service:action` syntax to reference controllers, use `serviceOrFqcn::method` instead