diff -u b/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationContentEntity.php b/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationContentEntity.php --- b/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationContentEntity.php +++ b/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationContentEntity.php @@ -56,7 +56,7 @@ * * @var bool */ - protected $meetsConfigurationCondition; + protected $checkLanguageNegotiationOrder; /** * The route provider. @@ -115,7 +115,7 @@ */ public function processOutbound($path, &$options = [], Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) { // Check if processing conditions are met. - if (!($request && isset($options['route_name']) && ($outbound_route_name = $options['route_name']) && $this->meetsConfigurationCondition() && $this->meetsContentEntityRoutesCondition($outbound_route_name, $request))) { + if (!($request && !empty($options['route_name']) && $this->checkLanguageNegotiationOrder() && $this->meetsContentEntityRoutesCondition($options['route_name'], $request))) { return $path; } @@ -186,31 +186,36 @@ * @return bool * TRUE if the configuration condition is met, FALSE otherwise. */ - protected function meetsConfigurationCondition() { - if (!isset($this->meetsConfigurationCondition)) { + protected function checkLanguageNegotiationOrder() { + if (!isset($this->checkLanguageNegotiationOrder)) { // Only run if the LanguageNegotiationContentEntity outbound function is // being executed before the outbound function of LanguageNegotiationUrl. $enabled_methods_content = $this->config->get('language.types')->get('negotiation.language_content.enabled') ?: []; // Check if the content language is configured to be dependent on the // url negotiator directly or indirectly over the interface negotiator. - $check_interface_method = FALSE; - if (isset($enabled_methods_content[LanguageNegotiationUI::METHOD_ID])) { - $enabled_methods_interface = $this->config->get('language.types')->get('negotiation.language_interface.enabled') ?: []; - $check_interface_method = isset($enabled_methods_interface[LanguageNegotiationUrl::METHOD_ID]); - } - if ($check_interface_method) { - $check_against_weight = $enabled_methods_content[LanguageNegotiationUI::METHOD_ID]; - $check_against_weight = isset($enabled_methods_content[LanguageNegotiationUrl::METHOD_ID]) ? max($check_against_weight, $enabled_methods_content[LanguageNegotiationUrl::METHOD_ID]) : $check_against_weight; + if (isset($enabled_methods_content[LanguageNegotiationUrl::METHOD_ID]) && ($enabled_methods_content[static::METHOD_ID] > $enabled_methods_content[LanguageNegotiationUrl::METHOD_ID])) { + $this->checkLanguageNegotiationOrder = FALSE; } else { - $check_against_weight = isset($enabled_methods_content[LanguageNegotiationUrl::METHOD_ID]) ? $enabled_methods_content[LanguageNegotiationUrl::METHOD_ID] : PHP_INT_MAX; - } + $check_interface_method = FALSE; + if (isset($enabled_methods_content[LanguageNegotiationUI::METHOD_ID])) { + $enabled_methods_interface = $this->config->get('language.types')->get('negotiation.language_interface.enabled') ?: []; + $check_interface_method = isset($enabled_methods_interface[LanguageNegotiationUrl::METHOD_ID]); + } + if ($check_interface_method) { + $check_against_weight = $enabled_methods_content[LanguageNegotiationUI::METHOD_ID]; + $check_against_weight = isset($enabled_methods_content[LanguageNegotiationUrl::METHOD_ID]) ? max($check_against_weight, $enabled_methods_content[LanguageNegotiationUrl::METHOD_ID]) : $check_against_weight; + } + else { + $check_against_weight = isset($enabled_methods_content[LanguageNegotiationUrl::METHOD_ID]) ? $enabled_methods_content[LanguageNegotiationUrl::METHOD_ID] : PHP_INT_MAX; + } - $this->meetsConfigurationCondition = $enabled_methods_content[static::METHOD_ID] < $check_against_weight; + $this->checkLanguageNegotiationOrder = $enabled_methods_content[static::METHOD_ID] < $check_against_weight; + } } - return $this->meetsConfigurationCondition; + return $this->checkLanguageNegotiationOrder; } /** @@ -230,9 +235,16 @@ protected function meetsContentEntityRoutesCondition($outbound_route_name, Request $request) { $storage = isset($this->paths[$request]) ? $this->paths[$request] : []; if (!isset($storage[$outbound_route_name])) { - $content_entity_type_id_for_current_route = $this->getContentEntityTypeIdForCurrentRequest($request); - $meets_condition = $content_entity_type_id_for_current_route ? $this->meetsOutboundRouteToCurrentContentEntityCondition($outbound_route_name, $request) : FALSE; - $storage[$outbound_route_name] = $meets_condition; + $storage[$outbound_route_name] = FALSE; + + // Check if the outbound route points to the current entity. + if ($content_entity_type_id_for_current_route = $this->getContentEntityTypeIdForCurrentRequest($request)) { + $outbound_route_path = $this->routeProvider->getRouteByName($outbound_route_name)->getPath(); + if (!empty($this->getContentEntityPaths()[$outbound_route_path]) && $content_entity_type_id_for_current_route == $this->getContentEntityPaths()[$outbound_route_path]) { + $storage[$outbound_route_name] = TRUE; + } + } + $this->paths[$request] = $storage; } @@ -260,29 +272,6 @@ } /** - * Determines if outbound route points to the current entity. - * - * @param $outbound_route_name - * The route name for the current outbound url being processed. - * @param \Symfony\Component\HttpFoundation\Request $request - * The HttpRequest object representing the current request. - * - * @return bool - * TRUE if the outbound route points to the entity on the current route, - * FALSE otherwise. - */ - protected function meetsOutboundRouteToCurrentContentEntityCondition($outbound_route_name, Request $request) { - if ($content_entity_type_id_for_current_route = $this->getContentEntityTypeIdForCurrentRequest($request)) { - $outbound_route_path = $this->routeProvider->getRouteByName($outbound_route_name)->getPath(); - if (!empty($this->getContentEntityPaths()[$outbound_route_path]) && $content_entity_type_id_for_current_route == $this->getContentEntityPaths()[$outbound_route_path]) { - return TRUE; - } - } - - return FALSE; - } - - /** * Returns the paths for the link templates of all content entities. * * @return array diff -u b/core/modules/language/src/Tests/EntityUrlLanguageTest.php b/core/modules/language/src/Tests/EntityUrlLanguageTest.php --- b/core/modules/language/src/Tests/EntityUrlLanguageTest.php +++ b/core/modules/language/src/Tests/EntityUrlLanguageTest.php @@ -83,10 +83,6 @@ ]); $config->save(); - // In order to reflect the changes for a multilingual site in the container - // we have to rebuild it. - $this->kernel->rebuildContainer(); - // Without being on an content entity route the default entity URL tests // should still pass. $this->testEntityUrlLanguage(); @@ -111,10 +107,6 @@ ]); $config->save(); - // In order to reflect the changes for a multilingual site in the container - // we have to rebuild it. - $this->kernel->rebuildContainer(); - // The default entity URL tests should pass again with the current // configuration. $this->testEntityUrlLanguage(); only in patch2: unchanged: --- a/core/modules/language/language.services.yml +++ b/core/modules/language/language.services.yml @@ -9,7 +9,7 @@ services: - [initLanguageManager] language.config_subscriber: class: Drupal\language\EventSubscriber\ConfigSubscriber - arguments: ['@language_manager', '@language.default', '@config.factory'] + arguments: ['@language_manager', '@language.default', '@config.factory', '@language_negotiator'] tags: - { name: event_subscriber } language.config_factory_override: only in patch2: unchanged: --- a/core/modules/language/src/EventSubscriber/ConfigSubscriber.php +++ b/core/modules/language/src/EventSubscriber/ConfigSubscriber.php @@ -14,6 +14,8 @@ use Drupal\Core\Config\ConfigCrudEvent; use Drupal\Core\Config\ConfigEvents; use Drupal\language\ConfigurableLanguageManager; +use Drupal\language\HttpKernel\PathProcessorLanguage; +use Drupal\language\LanguageNegotiatorInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** @@ -43,6 +45,20 @@ class ConfigSubscriber implements EventSubscriberInterface { protected $configFactory; /** + * The language negotiator. + * + * @var \Drupal\language\LanguageNegotiatorInterface + */ + protected $languageNegotiator; + + /** + * The language path processor. + * + * @var \Drupal\language\HttpKernel\PathProcessorLanguage + */ + protected $pathProcessorLanguage; + + /** * Constructs a new class object. * * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager @@ -51,11 +67,14 @@ class ConfigSubscriber implements EventSubscriberInterface { * The default language. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The configuration factory. + * @param \Drupal\language\LanguageNegotiatorInterface $language_negotiator + * The language negotiator. */ - public function __construct(LanguageManagerInterface $language_manager, LanguageDefault $language_default, ConfigFactoryInterface $config_factory) { + public function __construct(LanguageManagerInterface $language_manager, LanguageDefault $language_default, ConfigFactoryInterface $config_factory, LanguageNegotiatorInterface $language_negotiator) { $this->languageManager = $language_manager; $this->languageDefault = $language_default; $this->configFactory = $config_factory; + $this->languageNegotiator = $language_negotiator; } /** @@ -102,6 +121,25 @@ public function onConfigSave(ConfigCrudEvent $event) { // Trigger a container rebuild on the next request by invalidating it. ConfigurableLanguageManager::rebuildServices(); } + elseif($saved_config->getName() == 'language.types' && $event->isChanged('negotiation')) { + // If the negotiation configuration changed the language negotiator and + // the language path processor have to be reset so that they regenerate + // the method instances and also sort them accordingly to the new config. + $this->languageNegotiator->reset(); + if (isset($this->pathProcessorLanguage)) { + $this->pathProcessorLanguage->reset(); + } + } + } + + /** + * Injects the language path processors on multilingual site configuration. + * + * @param \Drupal\language\HttpKernel\PathProcessorLanguage $path_processor_language + * The language path processor. + */ + public function setPathProcessorLanguage(PathProcessorLanguage $path_processor_language) { + $this->pathProcessorLanguage = $path_processor_language; } /** only in patch2: unchanged: --- a/core/modules/language/src/HttpKernel/PathProcessorLanguage.php +++ b/core/modules/language/src/HttpKernel/PathProcessorLanguage.php @@ -13,6 +13,7 @@ use Drupal\Core\PathProcessor\OutboundPathProcessorInterface; use Drupal\Core\Render\BubbleableMetadata; use Drupal\language\ConfigurableLanguageManagerInterface; +use Drupal\language\EventSubscriber\ConfigSubscriber; use Drupal\language\LanguageNegotiatorInterface; use Symfony\Component\HttpFoundation\Request; use Drupal\Core\Session\AccountInterface; @@ -58,6 +59,14 @@ class PathProcessorLanguage implements InboundPathProcessorInterface, OutboundPa protected $multilingual; /** + * The language configuration event subscriber. + * + * @var \Drupal\language\EventSubscriber\ConfigSubscriber + */ + protected $configSubscriber; + + + /** * Constructs a PathProcessorLanguage object. * * @param \Drupal\Core\Config\ConfigFactoryInterface $config @@ -68,12 +77,15 @@ class PathProcessorLanguage implements InboundPathProcessorInterface, OutboundPa * The language negotiator. * @param \Drupal\Core\Session\AccountInterface $current_user * The current active user. + * @param \Drupal\language\EventSubscriber\ConfigSubscriber $config_subscriber + * The language configuration event subscriber. */ - public function __construct(ConfigFactoryInterface $config, ConfigurableLanguageManagerInterface $language_manager, LanguageNegotiatorInterface $negotiator, AccountInterface $current_user) { + public function __construct(ConfigFactoryInterface $config, ConfigurableLanguageManagerInterface $language_manager, LanguageNegotiatorInterface $negotiator, AccountInterface $current_user, ConfigSubscriber $config_subscriber) { $this->config = $config; $this->languageManager = $language_manager; $this->negotiator = $negotiator; $this->negotiator->setCurrentUser($current_user); + $this->configSubscriber = $config_subscriber; } /** @@ -152,4 +164,23 @@ protected function initProcessors($scope) { }); } + /** + * Initializes the injected event subscriber with the language path processor. + * + * The language path processor service is registered only on multilingual + * site configuration, thus we inject it in the event subscriber only when + * it is initialized. + */ + public function initConfigSubscriber() { + $this->configSubscriber->setPathProcessorLanguage($this); + } + + /** + * Resets the collected processors instances. + */ + public function reset() { + debug('reset path processor language'); + $this->processors = array(); + } + } only in patch2: unchanged: --- a/core/modules/language/src/LanguageServiceProvider.php +++ b/core/modules/language/src/LanguageServiceProvider.php @@ -39,7 +39,9 @@ public function register(ContainerBuilder $container) { ->addArgument(new Reference('config.factory')) ->addArgument(new Reference('language_manager')) ->addArgument(new Reference('language_negotiator')) - ->addArgument(new Reference('current_user')); + ->addArgument(new Reference('current_user')) + ->addArgument(new Reference('language.config_subscriber')) + ->addMethodCall('initConfigSubscriber'); } }