Commit 0e507e42 authored by alexpott's avatar alexpott

Issue #1810394 by hchonov, plach, YesCT, David Hernández, Schnitzel,...

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 20621c28
......@@ -21,8 +21,27 @@
* @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 @@ 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);
// 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 @@ 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),
......@@ -689,7 +689,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),
]);
......
......@@ -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(),
......
......@@ -112,7 +112,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}',
......@@ -137,7 +137,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}',
......@@ -162,7 +162,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);
}
}
......
......@@ -107,7 +107,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
......@@ -167,7 +168,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
......@@ -180,7 +182,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
......@@ -207,13 +210,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]', [':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 @@ 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);
}
......
......@@ -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;
......
<?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\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 @@ 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;
}
/**
......
......@@ -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;
......@@ -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 @@ 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() {
$this->processors = array();
}
}
......@@ -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');
}
}
......
......@@ -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 @@ protected function setUp() {
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($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);
$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);
}
/**
* 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);
}
}
<?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();