Issue #1810394 by hchonov, plach, YesCT, David Hernández, Schnitzel, vijaycs85, penyaskito, tstoeckler, alexpott, Gábor Hojtsy: Site configuration with domain based language negotiation results in redirecting authenticated users to a different domain when accessing a content entity route for translation language different from the interface language
parent
20621c288b
commit
0e507e421b
|
@ -21,8 +21,27 @@ interface OutboundPathProcessorInterface {
|
|||
* @param string $path
|
||||
* The path to process, with a leading slash.
|
||||
* @param array $options
|
||||
* An array of options such as would be passed to the generator's
|
||||
* generateFromRoute() method.
|
||||
* (optional) An associative array of additional options, with the following
|
||||
* elements:
|
||||
* - 'query': An array of query key/value-pairs (without any URL-encoding)
|
||||
* to append to the URL.
|
||||
* - 'fragment': A fragment identifier (named anchor) to append to the URL.
|
||||
* Do not include the leading '#' character.
|
||||
* - 'absolute': Defaults to FALSE. Whether to force the output to be an
|
||||
* absolute link (beginning with http:). Useful for links that will be
|
||||
* displayed outside the site, such as in an RSS feed.
|
||||
* - 'language': An optional language object used to look up the alias
|
||||
* for the URL. If $options['language'] is omitted, it defaults to the
|
||||
* current language for the language type LanguageInterface::TYPE_URL.
|
||||
* - 'https': Whether this URL should point to a secure location. If not
|
||||
* defined, the current scheme is used, so the user stays on HTTP or HTTPS
|
||||
* respectively. TRUE enforces HTTPS and FALSE enforces HTTP.
|
||||
* - 'base_url': Only used internally by a path processor, for example, to
|
||||
* modify the base URL when a language dependent URL requires so.
|
||||
* - 'prefix': Only used internally, to modify the path when a language
|
||||
* dependent URL requires so.
|
||||
* - 'route': The route object for the given path. It will be set by
|
||||
* \Drupal\Core\Routing\UrlGenerator::generateFromRoute().
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The HttpRequest object representing the current request.
|
||||
* @param \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata
|
||||
|
|
|
@ -309,6 +309,9 @@ class UrlGenerator implements UrlGeneratorInterface {
|
|||
$name = $this->getRouteDebugMessage($name);
|
||||
$this->processRoute($name, $route, $parameters, $generated_url);
|
||||
$path = $this->getInternalPathFromRoute($name, $route, $parameters, $query_params);
|
||||
// Outbound path processors might need the route object for the path, e.g.
|
||||
// to get the path pattern.
|
||||
$options['route'] = $route;
|
||||
$path = $this->processPath($path, $options, $generated_url);
|
||||
|
||||
if (!empty($options['prefix'])) {
|
||||
|
|
|
@ -29,3 +29,19 @@ function content_translation_enable() {
|
|||
$message = t('<a href=":settings_url">Enable translation</a> for <em>content types</em>, <em>taxonomy vocabularies</em>, <em>accounts</em>, or any other element you wish to translate.', $t_args);
|
||||
drupal_set_message($message, 'warning');
|
||||
}
|
||||
|
||||
/**
|
||||
* @addtogroup updates-8.0.0-rc
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Rebuild the routes as the content translation routes have now new names.
|
||||
*/
|
||||
function content_translation_update_8001() {
|
||||
\Drupal::service('router.builder')->rebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup updates-8.0.0-rc".
|
||||
*/
|
||||
|
|
|
@ -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 hook_entity_type_alter() implementation to the end of the list.
|
||||
case 'entity_type_alter':
|
||||
$group = $implementations['content_translation'];
|
||||
unset($implementations['content_translation']);
|
||||
|
@ -140,7 +140,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.
|
||||
|
|
|
@ -652,7 +652,7 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
|
|||
$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),
|
||||
|
@ -689,7 +689,7 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
|
|||
$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),
|
||||
]);
|
||||
|
|
|
@ -127,7 +127,7 @@ class ContentTranslationController extends ControllerBase {
|
|||
$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 @@ class ContentTranslationController extends ControllerBase {
|
|||
)
|
||||
);
|
||||
$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 @@ class ContentTranslationController extends ControllerBase {
|
|||
)
|
||||
);
|
||||
$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(),
|
||||
|
|
|
@ -112,7 +112,7 @@ class ContentTranslationRouteSubscriber extends RouteSubscriberBase {
|
|||
'_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}',
|
||||
|
@ -137,7 +137,7 @@ class ContentTranslationRouteSubscriber extends RouteSubscriberBase {
|
|||
'_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}',
|
||||
|
@ -162,7 +162,7 @@ class ContentTranslationRouteSubscriber extends RouteSubscriberBase {
|
|||
'_admin_route' => $is_admin,
|
||||
)
|
||||
);
|
||||
$collection->add("content_translation.translation_delete_$entity_type_id", $route);
|
||||
$collection->add("entity.$entity_type_id.content_translation_delete", $route);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -107,7 +107,8 @@ abstract class ContentTranslationUITestBase extends ContentTranslationTestBase {
|
|||
$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
|
||||
|
@ -167,7 +168,8 @@ abstract class ContentTranslationUITestBase extends ContentTranslationTestBase {
|
|||
$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
|
||||
|
@ -180,7 +182,8 @@ abstract class ContentTranslationUITestBase extends ContentTranslationTestBase {
|
|||
// 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
|
||||
|
@ -207,13 +210,15 @@ abstract class ContentTranslationUITestBase extends ContentTranslationTestBase {
|
|||
*/
|
||||
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]', [':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));
|
||||
|
@ -343,7 +348,7 @@ abstract class ContentTranslationUITestBase extends ContentTranslationTestBase {
|
|||
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ class ContentTranslationWorkflowsTest extends ContentTranslationTestBase {
|
|||
|
||||
// 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 @@ class ContentTranslationWorkflowsTest extends ContentTranslationTestBase {
|
|||
$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 @@ class ContentTranslationWorkflowsTest extends ContentTranslationTestBase {
|
|||
// 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 @@ class ContentTranslationWorkflowsTest extends ContentTranslationTestBase {
|
|||
// 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;
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Update functions for Language module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Rebuild the container as services changed.
|
||||
*/
|
||||
function language_update_8001() {
|
||||
\Drupal::service('kernel')->invalidateContainer();
|
||||
}
|
|
@ -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:
|
||||
|
|
|
@ -14,6 +14,8 @@ use Drupal\Core\Language\LanguageManagerInterface;
|
|||
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;
|
||||
|
||||
/**
|
||||
|
@ -42,6 +44,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.
|
||||
*
|
||||
|
@ -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 @@ class ConfigSubscriber implements EventSubscriberInterface {
|
|||
// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,6 +13,7 @@ use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
|
|||
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;
|
||||
|
@ -57,6 +58,14 @@ class PathProcessorLanguage implements InboundPathProcessorInterface, OutboundPa
|
|||
*/
|
||||
protected $multilingual;
|
||||
|
||||
/**
|
||||
* The language configuration event subscriber.
|
||||
*
|
||||
* @var \Drupal\language\EventSubscriber\ConfigSubscriber
|
||||
*/
|
||||
protected $configSubscriber;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a PathProcessorLanguage object.
|
||||
*
|
||||
|
@ -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,22 @@ class PathProcessorLanguage implements InboundPathProcessorInterface, OutboundPa
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
$this->processors = array();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -39,7 +39,9 @@ class LanguageServiceProvider extends ServiceProviderBase {
|
|||
->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');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,294 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationContentEntity.
|
||||
*/
|
||||
|
||||
namespace Drupal\language\Plugin\LanguageNegotiation;
|
||||
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\language\LanguageNegotiationMethodBase;
|
||||
use Drupal\language\LanguageSwitcherInterface;
|
||||
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Class for identifying the content translation language.
|
||||
*
|
||||
* @LanguageNegotiation(
|
||||
* id = Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationContentEntity::METHOD_ID,
|
||||
* types = {Drupal\Core\Language\LanguageInterface::TYPE_CONTENT},
|
||||
* weight = -9,
|
||||
* name = @Translation("Content language"),
|
||||
* description = @Translation("Determines the content language from a request parameter."),
|
||||
* )
|
||||
*/
|
||||
class LanguageNegotiationContentEntity extends LanguageNegotiationMethodBase implements OutboundPathProcessorInterface, LanguageSwitcherInterface, ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The language negotiation method ID.
|
||||
*/
|
||||
const METHOD_ID = 'language-content-entity';
|
||||
|
||||
/**
|
||||
* The query string parameter.
|
||||
*/
|
||||
const QUERY_PARAMETER = 'language_content_entity';
|
||||
|
||||
/**
|
||||
* A list of all the link paths of enabled content entities.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $contentEntityPaths;
|
||||
|
||||
/**
|
||||
* Static cache for the language negotiation order check.
|
||||
*
|
||||
* @see \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationContentEntity::hasLowerLanguageNegotiationWeight()
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $hasLowerLanguageNegotiationWeightResult;
|
||||
|
||||
/**
|
||||
* Static cache of outbound route paths per request.
|
||||
*
|
||||
* @var \SplObjectStorage
|
||||
*/
|
||||
protected $paths;
|
||||
|
||||
/**
|
||||
* The entity manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* Constructs a new LanguageNegotiationContentEntity instance.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager.
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $entity_manager) {
|
||||
$this->entityManager = $entity_manager;
|
||||
$this->paths = new \SplObjectStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static($container->get('entity.manager'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLangcode(Request $request = NULL) {
|
||||
$langcode = $request->get(static::QUERY_PARAMETER);
|
||||
|
||||
$language_enabled = array_key_exists($langcode, $this->languageManager->getLanguages());
|
||||
return $language_enabled ? $langcode : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processOutbound($path, &$options = [], Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) {
|
||||
// If appropriate, process outbound to add a query parameter to the url and
|
||||
// remove the language option, so that url negotiator does not rewrite the
|
||||
// url.
|
||||
|
||||
// First, check if processing conditions are met.
|
||||
if (!($request && !empty($options['route']) && $this->hasLowerLanguageNegotiationWeight() && $this->meetsContentEntityRoutesCondition($options['route'], $request))) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
if (isset($options['language']) || $langcode = $this->getLangcode($request)) {
|
||||
// If the language option is set, unset it, so that the url language
|
||||
// negotiator does not rewrite the url.
|
||||
if (isset($options['language'])) {
|
||||
$langcode = $options['language']->getId();
|
||||
unset($options['language']);
|
||||
}
|
||||
|
||||
if (isset($options['query']) && is_string($options['query'])) {
|
||||
$query = [];
|
||||
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;
|
||||
// @todo Remove this once https://www.drupal.org/node/2507005 lands.
|
||||
$path .= (strpos($path, '?') !== FALSE ? '&' : '?') . UrlHelper::buildQuery($query_addon);
|
||||
}
|
||||
|
||||
if ($bubbleable_metadata) {
|
||||
// Cached URLs that have been processed by this outbound path
|
||||
// processor must be:
|
||||
$bubbleable_metadata
|
||||
// - varied by the content language query parameter.
|
||||
->addCacheContexts(['url.query_args:' . static::QUERY_PARAMETER]);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if content entity language negotiator has higher priority.
|
||||
*
|
||||
* The content entity language negotiator having higher priority than the url
|
||||
* language negotiator, is a criteria in
|
||||
* \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationContentEntity::processOutbound().
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the the content entity language negotiator has higher priority
|
||||
* than the url language negotiator, FALSE otherwise.
|
||||
*/
|
||||
protected function hasLowerLanguageNegotiationWeight() {
|
||||
if (!isset($this->hasLowerLanguageNegotiationWeightResult)) {
|
||||
// Only run if the LanguageNegotiationContentEntity outbound function is
|
||||
// being executed before the outbound function of LanguageNegotiationUrl.
|
||||
$content_method_weights = $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.
|
||||
if (isset($content_method_weights[LanguageNegotiationUrl::METHOD_ID]) && ($content_method_weights[static::METHOD_ID] > $content_method_weights[LanguageNegotiationUrl::METHOD_ID])) {
|
||||
$this->hasLowerLanguageNegotiationWeightResult = FALSE;
|
||||
}
|
||||
else {
|
||||
$check_interface_method = FALSE;
|
||||
if (isset($content_method_weights[LanguageNegotiationUI::METHOD_ID])) {
|
||||
$interface_method_weights = $this->config->get('language.types')->get('negotiation.language_interface.enabled') ?: [];
|
||||
$check_interface_method = isset($interface_method_weights[LanguageNegotiationUrl::METHOD_ID]);
|
||||
}
|
||||
if ($check_interface_method) {
|
||||
$max_weight = $content_method_weights[LanguageNegotiationUI::METHOD_ID];
|
||||
$max_weight = isset($content_method_weights[LanguageNegotiationUrl::METHOD_ID]) ? max($max_weight, $content_method_weights[LanguageNegotiationUrl::METHOD_ID]) : $max_weight;
|
||||
}
|
||||
else {
|
||||
$max_weight = isset($content_method_weights[LanguageNegotiationUrl::METHOD_ID]) ? $content_method_weights[LanguageNegotiationUrl::METHOD_ID] : PHP_INT_MAX;
|
||||
}
|
||||
|
||||
$this->hasLowerLanguageNegotiationWeightResult = $content_method_weights[static::METHOD_ID] < $max_weight;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->hasLowerLanguageNegotiationWeightResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if content entity route condition is met.
|
||||
*
|
||||
* Requirements: currently being on an content entity route and processing
|
||||
* outbound url pointing to the same content entity.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $outbound_route
|
||||
* The route object 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 content entity route condition is met, FALSE otherwise.
|
||||
*/
|
||||
protected function meetsContentEntityRoutesCondition(Route $outbound_route, Request $request) {
|
||||
$outbound_path_pattern = $outbound_route->getPath();
|
||||
$storage = isset($this->paths[$request]) ? $this->paths[$request] : [];
|
||||
if (!isset($storage[$outbound_path_pattern])) {
|
||||
$storage[$outbound_path_pattern] = FALSE;
|
||||
|
||||
// Check if the outbound route points to the current entity.
|
||||
if ($content_entity_type_id_for_current_route = $this->getContentEntityTypeIdForCurrentRequest($request)) {
|
||||
if (!empty($this->getContentEntityPaths()[$outbound_path_pattern]) && $content_entity_type_id_for_current_route == $this->getContentEntityPaths()[$outbound_path_pattern]) {
|
||||
$storage[$outbound_path_pattern] = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
$this->paths[$request] = $storage;
|
||||
}
|
||||
|
||||
return $storage[$outbound_path_pattern];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content entity type ID from the current request for the route.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The HttpRequest object representing the current request.
|
||||
*
|
||||
* @return string
|
||||
* The entity type ID for the route from the request.
|
||||
*/
|
||||
protected function getContentEntityTypeIdForCurrentRequest(Request $request) {
|
||||
$content_entity_type_id_for_current_route = '';
|
||||
|
||||
if ($current_route = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)) {
|
||||
$current_route_path = $current_route->getPath();
|
||||
$content_entity_type_id_for_current_route = isset($this->getContentEntityPaths()[$current_route_path]) ? $this->getContentEntityPaths()[$current_route_path] : '';
|
||||
}
|
||||
|
||||
return $content_entity_type_id_for_current_route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the paths for the link templates of all content entities.
|
||||
*
|
||||
* @return array
|
||||
* An array of all content entity type IDs, keyed by the corresponding link
|
||||
* template paths.
|
||||
*/
|
||||
protected function getContentEntityPaths() {
|
||||
if (!isset($this->contentEntityPaths)) {
|
||||
$this->contentEntityPaths = [];
|
||||
$entity_types = $this->entityManager->getDefinitions();
|
||||
foreach ($entity_types as $entity_type_id => $entity_type) {
|
||||
if ($entity_type->isSubclassOf(ContentEntityInterface::class)) {
|
||||
$entity_paths = array_fill_keys($entity_type->getLinkTemplates(), $entity_type_id);
|
||||
$this->contentEntityPaths = array_merge($this->contentEntityPaths, $entity_paths);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->contentEntityPaths;
|
||||
}
|
||||
|
||||
}
|
|
@ -7,22 +7,34 @@
|
|||
|
||||
namespace Drupal\language\Tests;
|
||||
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationContentEntity;
|
||||
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl;
|
||||
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Tests the language of entity URLs.
|
||||
* @group language
|
||||
*/
|
||||
class EntityUrlLanguageTest extends KernelTestBase {
|
||||
class EntityUrlLanguageTest extends LanguageTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['language', 'entity_test', 'user', 'system'];
|
||||
public static $modules = ['entity_test', 'user'];
|
||||
|
||||
/**
|
||||
* The entity being used for testing.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\ContentEntityInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
@ -37,33 +49,93 @@ class EntityUrlLanguageTest extends KernelTestBase {
|
|||
ConfigurableLanguage::create(['id' => 'es'])->save();
|
||||
ConfigurableLanguage::create(['id' => 'fr'])->save();
|
||||
|
||||
$this->config('language.types')->setData([
|
||||
'configurable' => ['language_interface'],
|
||||
'negotiation' => ['language_interface' => ['enabled' => ['language-url' => 0]]],
|
||||
])->save();
|
||||
$this->config('language.negotiation')->setData([
|
||||
'url' => [
|
||||
'source' => 'path_prefix',
|
||||
'prefixes' => ['en' => 'en', 'es' => 'es', 'fr' => 'fr']
|
||||
],
|
||||
])->save();
|
||||
$config = $this->config('language.negotiation');
|
||||
$config->set('url.prefixes', ['en' => 'en', 'es' => 'es', 'fr' => 'fr'])
|
||||
->save();
|
||||
$this->kernel->rebuildContainer();
|
||||
$this->container = $this->kernel->getContainer();
|
||||
\Drupal::setContainer($this->container);
|
||||
|
||||
$this->createTranslatableEntity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that entity URLs in a language have the right language prefix.
|
||||
*/
|
||||
public function testEntityUrlLanguage() {
|
||||
$entity = EntityTest::create();
|
||||
$entity->addTranslation('es', ['name' => 'name spanish']);
|
||||
$entity->addTranslation('fr', ['name' => 'name french']);
|
||||
$entity->save();
|
||||
$this->assertTrue(strpos($this->entity->urlInfo()->toString(), '/en/entity_test/' . $this->entity->id()) !== FALSE);
|
||||
$this->assertTrue(strpos($this->entity->getTranslation('es')->urlInfo()->toString(), '/es/entity_test/' . $this->entity->id()) !== FALSE);
|
||||
$this->assertTrue(strpos($this->entity->getTranslation('fr')->urlInfo()->toString(), '/fr/entity_test/' . $this->entity->id()) !== FALSE);
|
||||
}
|
||||
|
||||
$this->assertTrue(strpos($entity->urlInfo()->toString(), '/en/entity_test/' . $entity->id()) !== FALSE);
|
||||
$this->assertTrue(strpos($entity->getTranslation('es')->urlInfo()->toString(), '/es/entity_test/' . $entity->id()) !== FALSE);
|
||||
$this->assertTrue(strpos($entity->getTranslation('fr')->urlInfo()->toString(), '/fr/entity_test/' . $entity->id()) !== FALSE);
|
||||
/**
|
||||
* Ensures correct entity URLs with the method language-content-entity enabled.
|
||||
*
|
||||
* Test case with the method language-content-entity enabled and configured
|
||||
* with higher and also with lower priority than the method language-url.
|
||||
*/
|
||||
public function testEntityUrlLanguageWithLanguageContentEnabled() {
|
||||
// Define the method language-content-entity with a higher priority than
|
||||
// language-url.
|
||||
$config = $this->config('language.types');
|
||||
$config->set('configurable', [LanguageInterface::TYPE_INTERFACE, LanguageInterface::TYPE_CONTENT]);
|
||||
$config->set('negotiation.language_content.enabled', [
|
||||
LanguageNegotiationContentEntity::METHOD_ID => 0,
|
||||
LanguageNegotiationUrl::METHOD_ID => 1
|
||||
]);
|
||||
$config->save();
|
||||
|
||||
// Without being on an content entity route the default entity URL tests
|
||||
// should still pass.
|
||||
$this->testEntityUrlLanguage();
|
||||
|
||||
// Now switching to an entity route, so that the URL links are generated
|
||||
// while being on an entity route.
|
||||
$this->setCurrentRequestForRoute('/entity_test/{entity_test}', 'entity.entity_test.canonical');
|
||||
|
||||
// The method language-content-entity should run before language-url and
|
||||
// append query parameter for the content language and prevent language-url
|
||||
// from overwriting the url.
|
||||
$this->assertTrue(strpos($this->entity->urlInfo('canonical')->toString(), '/en/entity_test/' . $this->entity->id() . '?' . LanguageNegotiationContentEntity::QUERY_PARAMETER . '=en') !== FALSE);
|
||||
$this->assertTrue(strpos($this->entity->getTranslation('es')->urlInfo('canonical')->toString(), '/en/entity_test/' . $this->entity->id() . '?' . LanguageNegotiationContentEntity::QUERY_PARAMETER . '=es') !== FALSE);
|
||||
$this->assertTrue(strpos($this->entity->getTranslation('fr')->urlInfo('canonical')->toString(), '/en/entity_test/' . $this->entity->id() . '?' . LanguageNegotiationContentEntity::QUERY_PARAMETER . '=fr') !== FALSE);
|
||||
|
||||
// Define the method language-url with a higher priority than
|
||||
// language-content-entity. This configuration should match the default one,
|
||||
// where the language-content-entity is turned off.
|
||||
$config->set('negotiation.language_content.enabled', [
|
||||
LanguageNegotiationUrl::METHOD_ID => 0,
|
||||
LanguageNegotiationContentEntity::METHOD_ID => 1
|
||||
]);
|
||||
$config->save();
|
||||
|
||||
// The default entity URL tests should pass again with the current
|
||||
// configuration.
|
||||
$this->testEntityUrlLanguage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a translated entity.
|
||||
*/
|
||||
protected function createTranslatableEntity() {
|
||||
$this->entity = EntityTest::create();
|
||||
$this->entity->addTranslation('es', ['name' => 'name spanish']);
|
||||
$this->entity->addTranslation('fr', ['name' => 'name french']);
|
||||
$this->entity->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current request to a specific path with the corresponding route.
|
||||
*
|
||||
* @param string $path
|
||||
* The path for which the current request should be created.
|
||||
* @param string $route_name
|
||||
* The route name for which the route object for the request should be
|
||||
* created.
|
||||
*/
|
||||
protected function setCurrentRequestForRoute($path, $route_name) {
|
||||
$request = Request::create($path);
|
||||
$request->attributes->set(RouteObjectInterface::ROUTE_NAME, $route_name);
|
||||
$request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route($path));
|
||||
$this->container->get('request_stack')->push($request);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\language\Tests\LanguageNegotiationLanguageContentEntityTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\language\Tests;
|
||||
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationContentEntity;
|
||||
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Tests language negotiation with the language negotiator content entity.
|
||||
*
|
||||
* @group language
|
||||
*/
|
||||
class LanguageNegotiationContentEntityTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('language', 'language_test', 'entity_test', 'system');
|
||||
|
||||
/**
|
||||
* The entity being used for testing.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\ContentEntityInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
ConfigurableLanguage::create(['id' => 'es'])->save();
|
||||
ConfigurableLanguage::create(['id' => 'fr'])->save();
|
||||
|
||||
// In order to reflect the changes for a multilingual site in the container
|
||||
// we have to rebuild it.
|
||||
$this->rebuildContainer();
|
||||
|
||||
$this->createTranslatableEntity();
|
||||
|
||||
$user = $this->drupalCreateUser(array('view test entity'));
|
||||
$this->drupalLogin($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests default with content language remaining same as interface language.
|
||||
*/
|
||||
public function testDefaultConfiguration() {
|
||||
$translation = $this->entity;
|
||||
$this->drupalGet($translation->urlInfo());
|
||||
$last = $this->container->get('state')->get('language_test.language_negotiation_last');
|
||||
$last_content_language = $last[LanguageInterface::TYPE_CONTENT];
|
||||
$last_interface_language = $last[LanguageInterface::TYPE_INTERFACE];
|
||||
$this->assertTrue(($last_interface_language == $last_content_language) && ($last_content_language == $translation->language()->getId()), new FormattableMarkup('Interface language %interface_language and Content language %content_language are the same as the translation language %translation_language of the entity.', ['%interface_language' => $last_interface_language, '%content_language' => $last_content_language, '%translation_language' => $translation->language()->getId()]));
|
||||
|
||||
$translation = $this->entity->getTranslation('es');
|
||||
$this->drupalGet($translation->urlInfo());
|
||||
$last = $this->container->get('state')->get('language_test.language_negotiation_last');
|
||||
$last_content_language = $last[LanguageInterface::TYPE_CONTENT];
|
||||
$last_interface_language = $last[LanguageInterface::TYPE_INTERFACE];
|
||||
$this->assertTrue(($last_interface_language == $last_content_language) && ($last_content_language == $translation->language()->getId()), new FormattableMarkup('Interface language %interface_language and Content language %content_language are the same as the translation language %translation_language of the entity.', ['%interface_language' => $last_interface_language, '%content_language' => $last_content_language, '%translation_language' => $translation->language()->getId()]));
|
||||
|
||||
$translation = $this->entity->getTranslation('fr');
|
||||
$this->drupalGet($translation->urlInfo());
|
||||
$last = $this->container->get('state')->get('language_test.language_negotiation_last');
|
||||
$last_content_language = $last[LanguageInterface::TYPE_CONTENT];
|
||||
$last_interface_language = $last[LanguageInterface::TYPE_INTERFACE];
|
||||
$this->assertTrue(($last_interface_language == $last_content_language) && ($last_content_language == $translation->language()->getId()), new FormattableMarkup('Interface language %interface_language and Content language %content_language are the same as the translation language %translation_language of the entity.', ['%interface_language' => $last_interface_language, '%content_language' => $last_content_language, '%translation_language' => $translation->language()->getId()]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests enabling the language negotiator language_content_entity.
|
||||
*/
|
||||
public function testEnabledLanguageContentNegotiator() {
|
||||
// Define the method language-url with a higher priority than
|
||||
// language-content-entity. This configuration should match the default one,
|
||||
// where the language-content-entity is turned off.
|
||||
$config = $this->config('language.types');
|
||||
$config->set('configurable', [LanguageInterface::TYPE_INTERFACE, LanguageInterface::TYPE_CONTENT]);
|
||||
$config->set('negotiation.language_content.enabled', [
|
||||
LanguageNegotiationUrl::METHOD_ID => 0,
|
||||
LanguageNegotiationContentEntity::METHOD_ID => 1
|
||||
]);
|
||||
$config->save();
|
||||
|
||||
// In order to reflect the changes for a multilingual site in the container
|
||||
// we have to rebuild it.
|
||||
$this->rebuildContainer();
|
||||
|
||||
// The tests for the default configuration should still pass.
|
||||
$this->testDefaultConfiguration();
|
||||
|
||||
// Define the method language-content-entity with a higher priority than
|
||||
// language-url.
|
||||
$config->set('negotiation.language_content.enabled', [
|
||||
LanguageNegotiationContentEntity::METHOD_ID => 0,
|
||||
LanguageNegotiationUrl::METHOD_ID => 1
|
||||
]);
|
||||
$config->save();
|
||||
|
||||
// In order to reflect the changes for a multilingual site in the container
|
||||
// we have to rebuild it.
|
||||
$this->rebuildContainer();
|
||||
|
||||
// The method language-content-entity should run before language-url and
|
||||
// append query parameter for the content language and prevent language-url
|
||||
// from overwriting the URL.
|
||||
$default_site_langcode = $this->config('system.site')->get('default_langcode');
|
||||
|
||||
// Now switching to an entity route, so that the URL links are generated
|
||||
// while being on an entity route.
|
||||
$this->setCurrentRequestForRoute('/entity_test/{entity_test}', 'entity.entity_test.canonical');
|
||||
|
||||
$translation = $this->entity;
|
||||
$this->drupalGet($translation->urlInfo());
|
||||
$last = $this->container->get('state')->get('language_test.language_negotiation_last');
|
||||
$last_content_language = $last[LanguageInterface::TYPE_CONTENT];
|
||||
$last_interface_language = $last[LanguageInterface::TYPE_INTERFACE];
|
||||
$this->assertTrue(($last_interface_language == $default_site_langcode) && ($last_interface_language == $last_content_language) && ($last_content_language == $translation->language()->getId()), 'Interface language and Content language are the same as the default translation language of the entity.');
|
||||
$this->assertTrue($last_interface_language == $default_site_langcode, 'Interface language did not change from the default site language.');
|
||||
$this->assertTrue($last_content_language == $translation->language()->getId(), 'Content language matches the current entity translation language.');
|
||||
|
||||
$translation = $this->entity->getTranslation('es');
|
||||
$this->drupalGet($translation->urlInfo());
|
||||
$last = $this->container->get('state')->get('language_test.language_negotiation_last');
|
||||
$last_content_language = $last[LanguageInterface::TYPE_CONTENT];
|
||||
$last_interface_language = $last[LanguageInterface::TYPE_INTERFACE];
|
||||
$this->assertTrue($last_interface_language == $default_site_langcode, 'Interface language did not change from the default site language.');
|
||||
$this->assertTrue($last_content_language == $translation->language()->getId(), 'Content language matches the current entity translation language.');
|
||||
|
||||
$translation = $this->entity->getTranslation('fr');
|
||||
$this->drupalGet($translation->urlInfo());
|
||||
$last = $this->container->get('state')->get('language_test.language_negotiation_last');
|
||||
$last_content_language = $last[LanguageInterface::TYPE_CONTENT];
|
||||
$last_interface_language = $last[LanguageInterface::TYPE_INTERFACE];
|
||||
$this->assertTrue($last_interface_language == $default_site_langcode, 'Interface language did not change from the default site language.');
|
||||
$this->assertTrue($last_content_language == $translation->language()->getId(), 'Content language matches the current entity translation language.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a translated entity.
|
||||
*/
|
||||
protected function createTranslatableEntity() {
|
||||
$this->entity = EntityTest::create();
|
||||
$this->entity->addTranslation('es', ['name' => 'name spanish']);
|
||||
$this->entity->addTranslation('fr', ['name' => 'name french']);
|
||||
$this->entity->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current request to a specific path with the corresponding route.
|
||||
*
|
||||
* @param string $path
|
||||
* The path for which the current request should be created.
|
||||
* @param string $route_name
|
||||
* The route name for which the route object for the request should be
|
||||
* created.
|
||||
*/
|
||||
protected function setCurrentRequestForRoute($path, $route_name) {
|
||||
$request = Request::create($path);
|
||||
$request->attributes->set(RouteObjectInterface::ROUTE_NAME, $route_name);
|
||||
$request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route($path));
|
||||
$this->container->get('request_stack')->push($request);
|
||||
}
|
||||
|
||||
}
|
|
@ -221,8 +221,13 @@ class LanguageUILanguageNegotiationTest extends WebTestBase {
|
|||
|
||||
// Unknown language prefix should return 404.
|
||||
$definitions = \Drupal::languageManager()->getNegotiator()->getNegotiationMethods();
|
||||
// Enable only methods, which are either not limited to a specific language
|
||||
// type or are supporting the interface language type.
|
||||
$language_interface_method_definitions = array_filter($definitions, function ($method_definition) {
|
||||
return !isset($method_definition['types']) || (isset($method_definition['types']) && in_array(LanguageInterface::TYPE_INTERFACE, $method_definition['types']));
|
||||
});
|
||||
$this->config('language.types')
|
||||
->set('negotiation.' . LanguageInterface::TYPE_INTERFACE . '.enabled', array_flip(array_keys($definitions)))
|
||||
->set('negotiation.' . LanguageInterface::TYPE_INTERFACE . '.enabled', array_flip(array_keys($language_interface_method_definitions)))
|
||||
->save();
|
||||
$this->drupalGet("$langcode_unknown/admin/config", array(), $http_header_browser_fallback);
|
||||
$this->assertResponse(404, "Unknown language path prefix should return 404");
|
||||
|
|
|
@ -94,7 +94,8 @@ class NodeTranslationUITest extends ContentTranslationUITestBase {
|
|||
$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
|
||||
|
|
|
@ -30,7 +30,6 @@ class UpdatePathRC1TestBaseFilledTest extends UpdatePathRC1TestBaseTest {
|
|||
* Tests that the content and configuration were properly updated.
|
||||
*/
|
||||
public function testUpdatedSite() {
|
||||
// @todo there are no updates to run.
|
||||
$this->runUpdates();
|
||||
|
||||
$spanish = \Drupal::languageManager()->getLanguage('es');
|
||||
|
|
|
@ -150,11 +150,16 @@ class PathProcessorTest extends UnitTestCase {
|
|||
$current_user = $this->getMockBuilder('Drupal\Core\Session\AccountInterface')
|
||||
->getMock();
|
||||
|
||||
// Create a config event subscriber stub.
|
||||
$config_subscriber = $this->getMockBuilder('Drupal\language\EventSubscriber\ConfigSubscriber')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
// Create the processors.
|
||||
$alias_processor = new PathProcessorAlias($alias_manager);
|
||||
$decode_processor = new PathProcessorDecode();
|
||||
$front_processor = new PathProcessorFront($config_factory_stub);
|
||||
$language_processor = new PathProcessorLanguage($config_factory_stub, $this->languageManager, $negotiator, $current_user);
|
||||
$language_processor = new PathProcessorLanguage($config_factory_stub, $this->languageManager, $negotiator, $current_user, $config_subscriber);
|
||||
|
||||
// First, test the processor manager with the processors in the incorrect
|
||||
// order. The alias processor will run before the language processor, meaning
|
||||
|
|
Loading…
Reference in New Issue