diff --git a/core/lib/Drupal/Core/Routing/UrlGenerator.php b/core/lib/Drupal/Core/Routing/UrlGenerator.php index d990e40..5b59755 100644 --- a/core/lib/Drupal/Core/Routing/UrlGenerator.php +++ b/core/lib/Drupal/Core/Routing/UrlGenerator.php @@ -310,6 +310,7 @@ public function generateFromRoute($name, $parameters = array(), $options = array $name = $this->getRouteDebugMessage($name); $this->processRoute($name, $route, $parameters, $generated_url); $path = $this->getInternalPathFromRoute($name, $route, $parameters, $query_params); + $options['route_name'] = $name; $path = $this->processPath($path, $options, $generated_url); if (!empty($options['prefix'])) { diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module index 36557cd..8d97a75 100644 --- a/core/modules/content_translation/content_translation.module +++ b/core/modules/content_translation/content_translation.module @@ -53,7 +53,7 @@ function content_translation_help($route_name, RouteMatchInterface $route_match) */ function content_translation_module_implements_alter(&$implementations, $hook) { switch ($hook) { - // Move some of our hook implementations to the end of the list. + // Move our entity_type_alter hook implementation to the end of the list. case 'entity_type_alter': $group = $implementations['content_translation']; unset($implementations['content_translation']); @@ -69,7 +69,6 @@ function content_translation_language_types_info_alter(array &$language_types) { // Make content language negotiation configurable by removing the 'locked' // flag. $language_types[LanguageInterface::TYPE_CONTENT]['locked'] = FALSE; - unset($language_types[LanguageInterface::TYPE_CONTENT]['fixed']); } /** @@ -140,7 +139,11 @@ function content_translation_entity_type_alter(array &$entity_types) { if ($entity_type->hasLinkTemplate('canonical')) { // Provide default route names for the translation paths. if (!$entity_type->hasLinkTemplate('drupal:content-translation-overview')) { - $entity_type->setLinkTemplate('drupal:content-translation-overview', $entity_type->getLinkTemplate('canonical') . '/translations'); + $translations_path = $entity_type->getLinkTemplate('canonical') . '/translations'; + $entity_type->setLinkTemplate('drupal:content-translation-overview', $translations_path); + $entity_type->setLinkTemplate('drupal:content-translation-add', $translations_path . '/add/{source}/{target}'); + $entity_type->setLinkTemplate('drupal:content-translation-edit', $translations_path . '/edit/{language}'); + $entity_type->setLinkTemplate('drupal:content-translation-delete', $translations_path . '/delete/{language}'); } // @todo Remove this as soon as menu access checks rely on the // controller. See https://www.drupal.org/node/2155787. diff --git a/core/modules/content_translation/src/ContentTranslationHandler.php b/core/modules/content_translation/src/ContentTranslationHandler.php index 3979fff..d64a3be 100644 --- a/core/modules/content_translation/src/ContentTranslationHandler.php +++ b/core/modules/content_translation/src/ContentTranslationHandler.php @@ -622,7 +622,7 @@ public function entityFormSourceChange($form, FormStateInterface $form_state) { $source = $form_state->getValue(array('source_langcode', 'source')); $entity_type_id = $entity->getEntityTypeId(); - $form_state->setRedirect('content_translation.translation_add_' . $entity_type_id, array( + $form_state->setRedirect("entity.$entity_type_id.content_translation_add", array( $entity_type_id => $entity->id(), 'source' => $source, 'target' => $form_object->getFormLangcode($form_state), @@ -659,7 +659,7 @@ function entityFormDeleteTranslation($form, FormStateInterface $form_state) { $form_state->setRedirectUrl($entity->urlInfo('delete-form')); } else { - $form_state->setRedirect('content_translation.translation_delete_' . $entity_type_id, [ + $form_state->setRedirect("entity.$entity_type_id.content_translation_delete", [ $entity_type_id => $entity->id(), 'language' => $form_object->getFormLangcode($form_state), ]); diff --git a/core/modules/content_translation/src/Controller/ContentTranslationController.php b/core/modules/content_translation/src/Controller/ContentTranslationController.php index 9c6879a..8c19de1 100644 --- a/core/modules/content_translation/src/Controller/ContentTranslationController.php +++ b/core/modules/content_translation/src/Controller/ContentTranslationController.php @@ -127,7 +127,7 @@ public function overview(RouteMatchInterface $route_match, $entity_type_id = NUL $langcode = $language->getId(); $add_url = new Url( - 'content_translation.translation_add_' . $entity_type_id, + "entity.$entity_type_id.content_translation_add", array( 'source' => $original, 'target' => $language->getId(), @@ -138,7 +138,7 @@ public function overview(RouteMatchInterface $route_match, $entity_type_id = NUL ) ); $edit_url = new Url( - 'content_translation.translation_edit_' . $entity_type_id, + "entity.$entity_type_id.content_translation_edit", array( 'language' => $language->getId(), $entity_type_id => $entity->id(), @@ -148,7 +148,7 @@ public function overview(RouteMatchInterface $route_match, $entity_type_id = NUL ) ); $delete_url = new Url( - 'content_translation.translation_delete_' . $entity_type_id, + "entity.$entity_type_id.content_translation_delete", array( 'language' => $language->getId(), $entity_type_id => $entity->id(), diff --git a/core/modules/content_translation/src/Routing/ContentTranslationRouteSubscriber.php b/core/modules/content_translation/src/Routing/ContentTranslationRouteSubscriber.php index 91d4314..92eea52 100644 --- a/core/modules/content_translation/src/Routing/ContentTranslationRouteSubscriber.php +++ b/core/modules/content_translation/src/Routing/ContentTranslationRouteSubscriber.php @@ -113,7 +113,7 @@ protected function alterRoutes(RouteCollection $collection) { '_admin_route' => $is_admin, ) ); - $collection->add("content_translation.translation_add_$entity_type_id", $route); + $collection->add("entity.$entity_type_id.content_translation_add", $route); $route = new Route( $path . '/edit/{language}', @@ -138,7 +138,7 @@ protected function alterRoutes(RouteCollection $collection) { '_admin_route' => $is_admin, ) ); - $collection->add("content_translation.translation_edit_$entity_type_id", $route); + $collection->add("entity.$entity_type_id.content_translation_edit", $route); $route = new Route( $path . '/delete/{language}', @@ -163,7 +163,7 @@ protected function alterRoutes(RouteCollection $collection) { '_admin_route' => $is_admin, ) ); - $collection->add("content_translation.translation_delete_$entity_type_id", $route); + $collection->add("entity.$entity_type_id.content_translation_delete", $route); } } diff --git a/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php b/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php index a5d6d09..9c35c6b 100644 --- a/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php +++ b/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php @@ -106,7 +106,8 @@ protected function doTestBasicTranslation() { $language = ConfigurableLanguage::load($langcode); $values[$langcode] = $this->getNewEntityValues($langcode); - $add_url = Url::fromRoute('content_translation.translation_add_' . $entity->getEntityTypeId(), [ + $entity_type_id = $entity->getEntityTypeId(); + $add_url = Url::fromRoute("entity.$entity_type_id.content_translation_add", [ $entity->getEntityTypeId() => $entity->id(), 'source' => $default_langcode, 'target' => $langcode @@ -166,7 +167,8 @@ protected function doTestBasicTranslation() { $language = ConfigurableLanguage::load($langcode); $source_langcode = 'it'; $edit = array('source_langcode[source]' => $source_langcode); - $add_url = Url::fromRoute('content_translation.translation_add_' . $entity->getEntityTypeId(), [ + $entity_type_id = $entity->getEntityTypeId(); + $add_url = Url::fromRoute("entity.$entity_type_id.content_translation_add", [ $entity->getEntityTypeId() => $entity->id(), 'source' => $default_langcode, 'target' => $langcode @@ -179,7 +181,8 @@ protected function doTestBasicTranslation() { // Add another translation and mark the other ones as outdated. $values[$langcode] = $this->getNewEntityValues($langcode); $edit = $this->getEditValues($values, $langcode) + array('content_translation[retranslate]' => TRUE); - $add_url = Url::fromRoute('content_translation.translation_add_' . $entity->getEntityTypeId(), [ + $entity_type_id = $entity->getEntityTypeId(); + $add_url = Url::fromRoute("entity.$entity_type_id.content_translation_add", [ $entity->getEntityTypeId() => $entity->id(), 'source' => $source_langcode, 'target' => $langcode @@ -206,13 +209,15 @@ protected function doTestBasicTranslation() { */ protected function doTestTranslationOverview() { $entity = entity_load($this->entityTypeId, $this->entityId, TRUE); - $this->drupalGet($entity->urlInfo('drupal:content-translation-overview')); + $translate_url = $entity->urlInfo('drupal:content-translation-overview'); + $this->drupalGet($translate_url); + $translate_url->setAbsolute(FALSE); foreach ($this->langcodes as $langcode) { if ($entity->hasTranslation($langcode)) { $language = new Language(array('id' => $langcode)); - $view_path = $entity->url('canonical', array('language' => $language)); - $elements = $this->xpath('//table//a[@href=:href]', array(':href' => $view_path)); + $view_url = $entity->url('canonical', ['language' => $language]); + $elements = $this->xpath('//table//a[@href=:href]', array(':href' => $view_url)); $this->assertEqual((string) $elements[0], $entity->getTranslation($langcode)->label(), format_string('Label correctly shown for %language translation.', array('%language' => $langcode))); $edit_path = $entity->url('edit-form', array('language' => $language)); $elements = $this->xpath('//table//ul[@class="dropbutton"]/li/a[@href=:href]', array(':href' => $edit_path)); @@ -342,7 +347,7 @@ protected function doTestTranslationDeletion() { // Check that the translator cannot delete the original translation. $args = [$this->entityTypeId => $entity->id(), 'language' => 'en']; - $this->drupalGet(Url::fromRoute('content_translation.translation_delete_' . $this->entityTypeId, $args)); + $this->drupalGet(Url::fromRoute("entity.$this->entityTypeId.content_translation_delete", $args)); $this->assertResponse(403); } diff --git a/core/modules/content_translation/src/Tests/ContentTranslationWorkflowsTest.php b/core/modules/content_translation/src/Tests/ContentTranslationWorkflowsTest.php index b602be0..1216c0c 100644 --- a/core/modules/content_translation/src/Tests/ContentTranslationWorkflowsTest.php +++ b/core/modules/content_translation/src/Tests/ContentTranslationWorkflowsTest.php @@ -73,7 +73,7 @@ protected function setupEntity() { // Create a translation. $this->drupalLogin($this->translator); - $add_translation_url = Url::fromRoute('content_translation.translation_add_' . $this->entityTypeId, [$this->entityTypeId => $this->entity->id(), 'source' => $default_langcode, 'target' => $this->langcodes[2]]); + $add_translation_url = Url::fromRoute("entity.$this->entityTypeId.content_translation_add", [$this->entityTypeId => $this->entity->id(), 'source' => $default_langcode, 'target' => $this->langcodes[2]]); $this->drupalPostForm($add_translation_url, array(), t('Save')); $this->rebuildContainer(); } @@ -175,7 +175,7 @@ protected function doTestWorkflows(UserInterface $user, $expected_status) { $this->assertResponse($expected_status['overview'], SafeMarkup::format('The @user_label has the expected translation overview access.', $args)); // Check whether the user is allowed to create a translation. - $add_translation_url = Url::fromRoute('content_translation.translation_add_' . $this->entityTypeId, [$this->entityTypeId => $this->entity->id(), 'source' => $default_langcode, 'target' => $langcode], $options); + $add_translation_url = Url::fromRoute("entity.$this->entityTypeId.content_translation_add", [$this->entityTypeId => $this->entity->id(), 'source' => $default_langcode, 'target' => $langcode], $options); if ($expected_status['add_translation'] == 200) { $this->clickLink('Add'); $this->assertUrl($add_translation_url->toString(), [], 'The translation overview points to the translation form when creating translations.'); @@ -193,7 +193,7 @@ protected function doTestWorkflows(UserInterface $user, $expected_status) { // Check whether the user is allowed to edit a translation. $langcode = $this->langcodes[2]; $options['language'] = $languages[$langcode]; - $edit_translation_url = Url::fromRoute('content_translation.translation_edit_' . $this->entityTypeId, [$this->entityTypeId => $this->entity->id(), 'language' => $langcode], $options); + $edit_translation_url = Url::fromRoute("entity.$this->entityTypeId.content_translation_edit", [$this->entityTypeId => $this->entity->id(), 'language' => $langcode], $options); if ($expected_status['edit_translation'] == 200) { $this->drupalGet($translations_url); $editor = $expected_status['edit'] == 200; @@ -221,7 +221,7 @@ protected function doTestWorkflows(UserInterface $user, $expected_status) { // Check whether the user is allowed to delete a translation. $langcode = $this->langcodes[2]; $options['language'] = $languages[$langcode]; - $delete_translation_url = Url::fromRoute('content_translation.translation_delete_' . $this->entityTypeId, [$this->entityTypeId => $this->entity->id(), 'language' => $langcode], $options); + $delete_translation_url = Url::fromRoute("entity.$this->entityTypeId.content_translation_delete", [$this->entityTypeId => $this->entity->id(), 'language' => $langcode], $options); if ($expected_status['delete_translation'] == 200) { $this->drupalGet($translations_url); $editor = $expected_status['delete'] == 200; diff --git a/core/modules/language/src/HttpKernel/PathProcessorLanguage.php b/core/modules/language/src/HttpKernel/PathProcessorLanguage.php index e25e2b5..1b505d8 100644 --- a/core/modules/language/src/HttpKernel/PathProcessorLanguage.php +++ b/core/modules/language/src/HttpKernel/PathProcessorLanguage.php @@ -125,16 +125,31 @@ public function processOutbound($path, &$options = array(), Request $request = N protected function initProcessors($scope) { $interface = '\Drupal\Core\PathProcessor\\' . Unicode::ucfirst($scope) . 'PathProcessorInterface'; $this->processors[$scope] = array(); + $weights = []; foreach ($this->languageManager->getLanguageTypes() as $type) { foreach ($this->negotiator->getNegotiationMethods($type) as $method_id => $method) { if (!isset($this->processors[$scope][$method_id])) { $reflector = new \ReflectionClass($method['class']); if ($reflector->implementsInterface($interface)) { $this->processors[$scope][$method_id] = $this->negotiator->getNegotiationMethodInstance($method_id); + $weights[$method_id] = $method['weight']; } } } } + + // Sort the outbound processors list, so that their functions are called in + // the order specified by the weight of the methods. + uksort($this->processors[$scope], function ($method_id_a, $method_id_b) use($weights) { + $a_weight = $weights[$method_id_a]; + $b_weight = $weights[$method_id_b]; + + if ($a_weight == $b_weight) { + return 0; + } + + return ($a_weight < $b_weight) ? -1 : 1; + }); } } diff --git a/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationContentLanguage.php b/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationContentLanguage.php new file mode 100644 index 0000000..c55b886 --- /dev/null +++ b/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationContentLanguage.php @@ -0,0 +1,140 @@ +get(static::QUERY_PARAMETER); + + $language_enabled = array_key_exists($langcode, $this->languageManager->getLanguages()); + return $language_enabled ? $langcode : NULL; + } + + /** + * Implements Drupal\Core\PathProcessor\InboundPathProcessorInterface::processOutbound(). + */ + public function processOutbound($path, &$options = array(), Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) { + // Only run if the LanguageNegotiationContentLanguage outbound function is + // being executed before the outbound function of LanguageNegotiationUrl. + $enabled_methods = $this->config->get('language.types')->get('negotiation.language_content.enabled') ? : []; + $run = isset($enabled_methods[LanguageNegotiationUrl::METHOD_ID]) ? $enabled_methods[static::METHOD_ID] < $enabled_methods[LanguageNegotiationUrl::METHOD_ID] : TRUE; + + if ($run && $request && isset($options['route_name']) && (isset($options['language']) || $langcode = $this->getLangcode($request))) { + $route_name = $options['route_name']; + $route_provider = \Drupal::getContainer()->get('router.route_provider'); + $route_name_path = $route_provider->getRouteByName($route_name)->getPath(); + + if (in_array($route_name_path, $this->getContentEntityPaths())) { + if (isset($options['language'])) { + $langcode = $options['language']->getId(); + unset($options['language']); + } + + if (isset($options['query']) && is_string($options['query'])) { + $query = array(); + parse_str($options['query'], $query); + $options['query'] = $query; + } + else { + $options['query'] = []; + } + if (!isset($options['query'][static::QUERY_PARAMETER])) { + $query_addon = [static::QUERY_PARAMETER => $langcode]; + $options['query'] += $query_addon; + $path .= (strpos($path, '?') !== FALSE ? '&' : '?') . UrlHelper::buildQuery($query_addon); + } + } + } + + return $path; + } + + /** + * {@inheritdoc} + */ + public function getLanguageSwitchLinks(Request $request, $type, Url $url) { + $links = []; + $query = []; + parse_str($request->getQueryString(), $query); + + foreach ($this->languageManager->getNativeLanguages() as $language) { + $langcode = $language->getId(); + $query[static::QUERY_PARAMETER] = $langcode; + $links[$langcode] = [ + 'url' => $url, + 'title' => $language->getName(), + 'attributes' => ['class' => ['language-link']], + 'query' => $query, + ]; + } + + return $links; + } + + /** + * Returns the paths for the link templates of all content entities. + * + * @return array + * The array of the link templates paths of all content entities. + */ + protected function getContentEntityPaths() { + if ($this->contentEntityPaths === NULL) { + $this->contentEntityPaths = []; + $entity_types = \Drupal::entityManager()->getDefinitions(); + foreach ($entity_types as $entity_type_id => $entity_type) { + if ($entity_type->isSubclassOf('\Drupal\Core\Entity\ContentEntityInterface')) { + $this->contentEntityPaths = array_merge($this->contentEntityPaths, array_values($entity_type->getLinkTemplates())); + } + } + } + + return $this->contentEntityPaths; + } + +} diff --git a/core/modules/node/src/Tests/NodeTranslationUITest.php b/core/modules/node/src/Tests/NodeTranslationUITest.php index 509a0d1..1c5877f 100644 --- a/core/modules/node/src/Tests/NodeTranslationUITest.php +++ b/core/modules/node/src/Tests/NodeTranslationUITest.php @@ -93,7 +93,8 @@ function testPublishedStatusNoFields() { $language = ConfigurableLanguage::load($langcode); $values[$langcode] = array('title' => array(array('value' => $this->randomMachineName()))); - $add_url = Url::fromRoute('content_translation.translation_add_' . $entity->getEntityTypeId(), [ + $entity_type_id = $entity->getEntityTypeId(); + $add_url = Url::fromRoute("entity.$entity_type_id.content_translation_add", [ $entity->getEntityTypeId() => $entity->id(), 'source' => $default_langcode, 'target' => $langcode