first commit

This commit is contained in:
2024-07-15 12:33:27 +02:00
commit ce50ae282b
22084 changed files with 2623791 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
name: 'Language config override test'
type: module
description: 'Support module for the language config override test.'
package: Testing
# version: VERSION
# Information added by Drupal.org packaging script on 2024-07-04
version: '10.3.1'
project: 'drupal'
datestamp: 1720094222

View File

@@ -0,0 +1,12 @@
name: 'Language form elements test'
type: module
description: 'Support module for the language form elements tests.'
package: Testing
# version: VERSION
dependencies:
- drupal:entity_test
# Information added by Drupal.org packaging script on 2024-07-04
version: '10.3.1'
project: 'drupal'
datestamp: 1720094222

View File

@@ -0,0 +1,15 @@
language_elements_test.config_element:
path: '/language-tests/language_configuration_element'
defaults:
_form: '\Drupal\language_elements_test\Form\LanguageConfigurationElement'
_title: 'Language configuration form element'
requirements:
_access: 'TRUE'
language_elements_test.config_element_test:
path: '/language-tests/language_configuration_element_test'
defaults:
_form: '\Drupal\language_elements_test\Form\LanguageConfigurationElementTest'
_title: 'Language configuration form element'
requirements:
_access: 'TRUE'

View File

@@ -0,0 +1,52 @@
<?php
namespace Drupal\language_elements_test\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\language\Entity\ContentLanguageSettings;
/**
* A form containing a language configuration element.
*
* @internal
*/
class LanguageConfigurationElement extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'language_elements_configuration_element';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$conf = ContentLanguageSettings::loadByEntityTypeBundle('entity_test', 'some_bundle');
$form['lang_configuration'] = [
'#type' => 'language_configuration',
'#entity_information' => [
'entity_type' => 'entity_test',
'bundle' => 'some_bundle',
],
'#default_value' => $conf,
];
$form['submit'] = [
'#type' => 'submit',
'#value' => 'Save',
];
$form['#submit'][] = 'language_configuration_element_submit';
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace Drupal\language_elements_test\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* A form containing a language select element.
*
* @internal
*/
class LanguageConfigurationElementTest extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'language_elements_configuration_element_test';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['langcode'] = [
'#title' => t('Language select'),
'#type' => 'language_select',
'#default_value' => language_get_default_langcode('entity_test', 'some_bundle'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
}
}

View File

@@ -0,0 +1,16 @@
name: 'Language entity field access test'
type: module
description: 'Support module for verifying entity field access and the language selector.'
package: Testing
# version: VERSION
dependencies:
- drupal:node
- drupal:text
- drupal:field
- drupal:filter
- drupal:language
# Information added by Drupal.org packaging script on 2024-07-04
version: '10.3.1'
project: 'drupal'
datestamp: 1720094222

View File

@@ -0,0 +1,18 @@
<?php
/**
* @file
* Hook implementations for language_entity_field_access_test.
*/
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Implements hook_entity_field_access().
*/
function language_entity_field_access_test_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, ?FieldItemListInterface $items = NULL) {
return AccessResult::forbidden();
}

View File

@@ -0,0 +1,9 @@
name: 'Language events test'
type: module
package: Testing
# version: VERSION
# Information added by Drupal.org packaging script on 2024-07-04
version: '10.3.1'
project: 'drupal'
datestamp: 1720094222

View File

@@ -0,0 +1,6 @@
services:
language_events_test.event_subscriber:
class: Drupal\language_events_test\EventSubscriber
arguments: ['@state']
tags:
- { name: event_subscriber }

View File

@@ -0,0 +1,56 @@
<?php
namespace Drupal\language_events_test;
use Drupal\Core\State\StateInterface;
use Drupal\language\Config\LanguageConfigOverrideEvents;
use Drupal\language\Config\LanguageConfigOverrideCrudEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class EventSubscriber implements EventSubscriberInterface {
/**
* Constructs the Event Subscriber object.
*
* @param \Drupal\Core\State\StateInterface $state
* The state key value store.
*/
public function __construct(private StateInterface $state) {
}
/**
* Reacts to config event.
*
* @param \Drupal\language\Config\LanguageConfigOverrideCrudEvent $event
* The language configuration event.
* @param string $event_name
* The event name.
*/
public function configEventRecorder(LanguageConfigOverrideCrudEvent $event, string $event_name): void {
$override = $event->getLanguageConfigOverride();
$event_info = [
'event_name' => $event_name,
'current_override_data' => $override->get(),
'original_override_data' => $override->getOriginal(),
];
// Record all events that occur.
$all_events = $this->state->get('language_events_test.all_events', []);
$override_name = $override->getName();
if (!isset($all_events[$event_name][$override_name])) {
$all_events[$event_name][$override_name] = [];
}
$all_events[$event_name][$override_name][] = $event_info;
$this->state->set('language_events_test.all_events', $all_events);
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
$events[LanguageConfigOverrideEvents::SAVE_OVERRIDE][] = ['configEventRecorder'];
$events[LanguageConfigOverrideEvents::DELETE_OVERRIDE][] = ['configEventRecorder'];
return $events;
}
}

View File

@@ -0,0 +1,10 @@
name: 'Language test'
type: module
description: 'Support module for the language layer tests.'
package: Testing
# version: VERSION
# Information added by Drupal.org packaging script on 2024-07-04
version: '10.3.1'
project: 'drupal'
datestamp: 1720094222

View File

@@ -0,0 +1,110 @@
<?php
/**
* @file
* Mock module for language layer tests.
*/
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Url;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUI;
/**
* Implements hook_page_top().
*/
function language_test_page_top() {
if (\Drupal::moduleHandler()->moduleExists('language')) {
language_test_store_language_negotiation();
\Drupal::messenger()->addStatus(t('Language negotiation method: @name', ['@name' => \Drupal::languageManager()->getNegotiatedLanguageMethod() ?? 'Not defined']));
}
}
/**
* Implements hook_language_types_info().
*/
function language_test_language_types_info() {
if (\Drupal::state()->get('language_test.language_types')) {
return [
'test_language_type' => [
'name' => t('Test'),
'description' => t('A test language type.'),
],
'fixed_test_language_type' => [
'fixed' => ['test_language_negotiation_method'],
'locked' => TRUE,
],
];
}
}
/**
* Implements hook_language_types_info_alter().
*/
function language_test_language_types_info_alter(array &$language_types) {
if (\Drupal::state()->get('language_test.content_language_type')) {
$language_types[LanguageInterface::TYPE_CONTENT]['locked'] = FALSE;
unset($language_types[LanguageInterface::TYPE_CONTENT]['fixed']);
// By default languages are not configurable. Make
// LanguageInterface::TYPE_CONTENT configurable.
$config = \Drupal::configFactory()->getEditable('language.types');
$configurable = $config->get('configurable');
if (!in_array(LanguageInterface::TYPE_CONTENT, $configurable)) {
$configurable[] = LanguageInterface::TYPE_CONTENT;
$config->set('configurable', $configurable)->save();
}
}
}
/**
* Implements hook_language_negotiation_info_alter().
*/
function language_test_language_negotiation_info_alter(array &$negotiation_info) {
if (\Drupal::state()->get('language_test.language_negotiation_info_alter')) {
unset($negotiation_info[LanguageNegotiationUI::METHOD_ID]);
}
}
/**
* Store the last negotiated languages.
*/
function language_test_store_language_negotiation() {
$last = [];
foreach (\Drupal::languageManager()->getDefinedLanguageTypes() as $type) {
$last[$type] = \Drupal::languageManager()->getCurrentLanguage($type)->getId();
}
\Drupal::keyValue('language_test')->set('language_negotiation_last', $last);
}
/**
* Implements hook_language_fallback_candidates_alter().
*/
function language_test_language_fallback_candidates_alter(array &$candidates, array $context) {
if (Drupal::state()->get('language_test.fallback_alter.candidates')) {
unset($candidates[LanguageInterface::LANGCODE_NOT_SPECIFIED]);
}
}
/**
* Implements hook_language_fallback_candidates_OPERATION_alter().
*/
function language_test_language_fallback_candidates_test_alter(array &$candidates, array $context) {
if (Drupal::state()->get('language_test.fallback_operation_alter.candidates')) {
$langcode = LanguageInterface::LANGCODE_NOT_APPLICABLE;
$candidates[$langcode] = $langcode;
}
}
/**
* Implements hook_module_preinstall().
*/
function language_test_module_preinstall() {
\Drupal::state()->set('language_test.language_count_preinstall', count(\Drupal::languageManager()->getLanguages()));
}
/**
* Implements hook_language_switch_links_alter().
*/
function language_test_language_switch_links_alter(array &$links, $type, Url $url) {
// Record which languages had links passed in.
\Drupal::state()->set('language_test.language_switch_link_ids', array_keys($links));
}

View File

@@ -0,0 +1,33 @@
language_test.l_active_class:
path: '/language_test/type-link-active-class'
defaults:
_controller: '\Drupal\language_test\Controller\LanguageTestController::typeLinkActiveClass'
requirements:
_access: 'TRUE'
language_test.subrequest:
path: '/language_test/subrequest'
defaults:
_controller: '\Drupal\language_test\Controller\LanguageTestController::testSubRequest'
requirements:
_access: 'TRUE'
language_test.entity_using_original_language:
path: '/admin/language_test/entity_using_original_language/{configurable_language}'
defaults:
_controller: '\Drupal\language_test\Controller\LanguageTestController::testEntity'
requirements:
_access: 'TRUE'
language_test.entity_using_current_language:
path: '/admin/language_test/entity_using_current_language/{configurable_language}'
defaults:
_controller: '\Drupal\language_test\Controller\LanguageTestController::testEntity'
requirements:
_access: 'TRUE'
options:
parameters:
configurable_language:
type: entity:configurable_language
# Force load in the interface text language selected for page.
with_config_overrides: TRUE

View File

@@ -0,0 +1,139 @@
<?php
namespace Drupal\language_test\Controller;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
use Drupal\language\ConfigurableLanguageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Controller routines for language_test routes.
*/
class LanguageTestController implements ContainerInjectionInterface {
use StringTranslationTrait;
/**
* The HTTP kernel service.
*
* @var \Symfony\Component\HttpKernel\HttpKernelInterface
*/
protected $httpKernel;
/**
* The language manager service.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* Constructs a new LanguageTestController object.
*
* @param \Symfony\Component\HttpKernel\HttpKernelInterface $httpKernel
* An HTTP kernel.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/
public function __construct(HttpKernelInterface $httpKernel, LanguageManagerInterface $language_manager) {
$this->httpKernel = $httpKernel;
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static($container->get('http_kernel'), $container->get('language_manager'));
}
/**
* Route entity upcasting test helper.
*
* @param \Drupal\language\ConfigurableLanguageInterface $configurable_language
* The ConfigurableLanguage object from the route.
*
* @return string
* Testing feedback based on (translated) entity title.
*/
public function testEntity(ConfigurableLanguageInterface $configurable_language) {
return ['#markup' => $this->t('Loaded %label.', ['%label' => $configurable_language->label()])];
}
/**
* Returns links to the current page with different langcodes.
*
* Using #type 'link' causes these links to be rendered with the link
* generator.
*/
public function typeLinkActiveClass() {
// We assume that 'en' and 'fr' have been configured.
$languages = $this->languageManager->getLanguages();
return [
'no_language' => [
'#type' => 'link',
'#title' => t('Link to the current path with no langcode provided.'),
'#url' => Url::fromRoute('<current>'),
'#options' => [
'attributes' => [
'id' => 'no_lang_link',
],
'set_active_class' => TRUE,
],
],
'fr' => [
'#type' => 'link',
'#title' => t('Link to a French version of the current path.'),
'#url' => Url::fromRoute('<current>'),
'#options' => [
'language' => $languages['fr'],
'attributes' => [
'id' => 'fr_link',
],
'set_active_class' => TRUE,
],
],
'en' => [
'#type' => 'link',
'#title' => t('Link to an English version of the current path.'),
'#url' => Url::fromRoute('<current>'),
'#options' => [
'language' => $languages['en'],
'attributes' => [
'id' => 'en_link',
],
'set_active_class' => TRUE,
],
],
];
}
/**
* Uses a sub request to retrieve the 'user' page.
*
* @return \Symfony\Component\HttpFoundation\Response
* The kernels response to the sub request.
*/
public function testSubRequest() {
$request = Request::createFromGlobals();
$server = $request->server->all();
if (basename($server['SCRIPT_FILENAME']) != basename($server['SCRIPT_NAME'])) {
// We need this for when the test is executed by run-tests.sh.
// @todo Remove this once run-tests.sh has been converted to use a Request
// object.
$server['SCRIPT_FILENAME'] = $server['SCRIPT_NAME'];
$base_path = ltrim($server['REQUEST_URI'], '/');
}
else {
$base_path = $request->getBasePath();
}
$sub_request = Request::create($base_path . '/user', 'GET', $request->query->all(), $request->cookies->all(), [], $server);
return $this->httpKernel->handle($sub_request, HttpKernelInterface::SUB_REQUEST);
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Drupal\language_test\Entity;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
/**
* Defines the test entity class.
*
* @ContentEntityType(
* id = "no_language_entity_test",
* label = @Translation("Test entity without language support"),
* handlers = {
* "views_data" = "Drupal\entity_test\EntityTestViewsData"
* },
* base_table = "no_language_entity_test",
* persistent_cache = FALSE,
* entity_keys = {
* "id" = "id",
* "uuid" = "uuid",
* },
* )
*/
class NoLanguageEntityTest extends ContentEntityBase {
/**
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['id'] = BaseFieldDefinition::create('integer')
->setLabel(t('ID'))
->setDescription(t('The ID of the test entity.'))
->setReadOnly(TRUE)
->setSetting('unsigned', TRUE);
$fields['uuid'] = BaseFieldDefinition::create('uuid')
->setLabel(t('UUID'))
->setDescription(t('The UUID of the test entity.'))
->setReadOnly(TRUE);
return $fields;
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Drupal\language_test\Plugin\LanguageNegotiation;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\language\Attribute\LanguageNegotiation;
use Drupal\language\LanguageNegotiationMethodBase;
use Symfony\Component\HttpFoundation\Request;
/**
* Class for identifying language from a selected language.
*/
#[LanguageNegotiation(
id: LanguageNegotiationTest::METHOD_ID,
name: new TranslatableMarkup('Test'),
types: [LanguageInterface::TYPE_CONTENT,
'test_language_type',
'fixed_test_language_type',
],
weight: -10,
description: new TranslatableMarkup('This is a test language negotiation method.'),
)]
class LanguageNegotiationTest extends LanguageNegotiationMethodBase {
/**
* The language negotiation method id.
*/
const METHOD_ID = 'test_language_negotiation_method';
/**
* {@inheritdoc}
*/
public function getLangcode(?Request $request = NULL) {
return 'it';
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Drupal\language_test\Plugin\LanguageNegotiation;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\language\Attribute\LanguageNegotiation;
/**
* Class for identifying language from a selected language.
*/
#[LanguageNegotiation(
id: LanguageNegotiationTestTs::METHOD_ID,
name: new TranslatableMarkup('Type-specific test'),
types: ['test_language_type'],
weight: -10,
description: new TranslatableMarkup('This is a test language negotiation method.'),
)]
class LanguageNegotiationTestTs extends LanguageNegotiationTest {
/**
* The language negotiation method id.
*/
const METHOD_ID = 'test_language_negotiation_method_ts';
}

View File

@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\Tests\BrowserTestBase;
/**
* Test administration path based conversion of entities.
*
* @group language
*/
class AdminPathEntityConverterLanguageTest extends BrowserTestBase {
protected static $modules = ['language', 'language_test'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$permissions = [
'access administration pages',
'administer site configuration',
];
$this->drupalLogin($this->drupalCreateUser($permissions));
ConfigurableLanguage::createFromLangcode('es')->save();
}
/**
* Tests the translated and untranslated config entities are loaded properly.
*/
public function testConfigUsingCurrentLanguage(): void {
\Drupal::languageManager()
->getLanguageConfigOverride('es', 'language.entity.es')
->set('label', 'Español')
->save();
$this->drupalGet('es/admin/language_test/entity_using_current_language/es');
$this->assertSession()->pageTextNotContains('Loaded Spanish.');
$this->assertSession()->pageTextContains('Loaded Español.');
$this->drupalGet('es/admin/language_test/entity_using_original_language/es');
$this->assertSession()->pageTextContains('Loaded Spanish.');
$this->assertSession()->pageTextNotContains('Loaded Español.');
}
}

View File

@@ -0,0 +1,281 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\language\Entity\ContentLanguageSettings;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\WaitTerminateTestTrait;
// cspell:ignore funciona
/**
* Tests Language Negotiation.
*
* Uses different negotiators for content and interface.
*
* @group language
*/
class ConfigurableLanguageManagerTest extends BrowserTestBase {
use WaitTerminateTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'language',
'content_translation',
'node',
'locale',
'block',
'system',
'user',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// The \Drupal\locale\LocaleTranslation service clears caches after the
// response is flushed to the client. We use WaitTerminateTestTrait to wait
// for Drupal to perform its termination work before continuing.
$this->setWaitForTerminate();
/** @var \Drupal\user\UserInterface $user */
$user = $this->createUser([], '', TRUE);
$this->drupalLogin($user);
ConfigurableLanguage::createFromLangcode('es')->save();
// Create a page node type and make it translatable.
NodeType::create([
'type' => 'page',
'name' => 'Page',
])->save();
$config = ContentLanguageSettings::loadByEntityTypeBundle('node', 'page');
$config->setDefaultLangcode('en')
->setLanguageAlterable(TRUE)
->save();
// Create a Node with title 'English' and translate it to Spanish.
$node = Node::create([
'type' => 'page',
'title' => 'English',
]);
$node->save();
$node->addTranslation('es', ['title' => 'Español']);
$node->save();
// Enable both language_interface and language_content language negotiation.
\Drupal::getContainer()->get('language_negotiator')->updateConfiguration([
'language_interface',
'language_content',
]);
// Set the preferred language of the user for admin pages to English.
$user->set('preferred_admin_langcode', 'en')->save();
// Make sure node edit pages are administration pages.
$this->config('node.settings')->set('use_admin_theme', '1')->save();
$this->container->get('router.builder')->rebuild();
// Place a Block with a translatable string on the page.
$this->placeBlock('system_powered_by_block', ['region' => 'content']);
// Load the Spanish Node page once, to register the translatable string.
$this->drupalGet('/es/node/1');
// Translate the Powered by string.
/** @var \Drupal\locale\StringStorageInterface $string_storage */
$string_storage = \Drupal::getContainer()->get('locale.storage');
$source = $string_storage->findString(['source' => 'Powered by <a href=":poweredby">Drupal</a>']);
$string_storage->createTranslation([
'lid' => $source->lid,
'language' => 'es',
'translation' => 'Funciona con ...',
])->save();
// Invalidate caches so that the new translation will be used.
Cache::invalidateTags(['rendered', 'locale']);
}
/**
* Tests translation with URL and Preferred Admin Language negotiators.
*
* The interface language uses the preferred language for admin pages of the
* user and after that the URL. The Content uses just the URL.
*/
public function testUrlContentTranslationWithPreferredAdminLanguage(): void {
$assert_session = $this->assertSession();
// Set the interface language to use the preferred administration language
// and then the URL.
/** @var \Drupal\language\LanguageNegotiatorInterface $language_negotiator */
$language_negotiator = \Drupal::getContainer()->get('language_negotiator');
$language_negotiator->saveConfiguration('language_interface', [
'language-user-admin' => 1,
'language-url' => 2,
'language-selected' => 3,
]);
// Set Content Language Negotiator to use just the URL.
$language_negotiator->saveConfiguration('language_content', [
'language-url' => 4,
'language-selected' => 5,
]);
// See if the full view of the node in english is present and the
// string in the Powered By Block is in English.
$this->drupalGet('/node/1');
$assert_session->pageTextContains('English');
$assert_session->pageTextContains('Powered by');
// Load the spanish node page again and see if both the node and the string
// are translated.
$this->drupalGet('/es/node/1');
$assert_session->pageTextContains('Español');
$assert_session->pageTextContains('Funciona con');
$assert_session->pageTextNotContains('Powered by');
// Check if the Powered by string is shown in English on an
// administration page, and the node content is shown in Spanish.
$this->drupalGet('/es/node/1/edit');
$assert_session->pageTextContains('Español');
$assert_session->pageTextContains('Powered by');
$assert_session->pageTextNotContains('Funciona con');
}
/**
* Tests translation with URL and Session Language Negotiators.
*/
public function testUrlContentTranslationWithSessionLanguage(): void {
$assert_session = $this->assertSession();
/** @var \Drupal\language\LanguageNegotiatorInterface $language_negotiator */
$language_negotiator = \Drupal::getContainer()->get('language_negotiator');
// Set Interface Language Negotiator to Session.
$language_negotiator->saveConfiguration('language_interface', [
'language-session' => 1,
'language-url' => 2,
'language-selected' => 3,
]);
// Set Content Language Negotiator to URL.
$language_negotiator->saveConfiguration('language_content', [
'language-url' => 4,
'language-selected' => 5,
]);
// See if the full view of the node in english is present and the
// string in the Powered By Block is in English.
$this->drupalGet('/node/1');
$assert_session->pageTextContains('English');
$assert_session->pageTextContains('Powered by');
// The language session variable has not been set yet, so
// The string should be in Spanish.
$this->drupalGet('/es/node/1');
$assert_session->pageTextContains('Español');
$assert_session->pageTextNotContains('Powered by');
$assert_session->pageTextContains('Funciona con');
// Set the session language to Spanish but load the English node page.
$this->drupalGet('/node/1', ['query' => ['language' => 'es']]);
$assert_session->pageTextContains('English');
$assert_session->pageTextNotContains('Español');
$assert_session->pageTextContains('Funciona con');
$assert_session->pageTextNotContains('Powered by');
// Set the session language to English but load the node page in Spanish.
$this->drupalGet('/es/node/1', ['query' => ['language' => 'en']]);
$assert_session->pageTextNotContains('English');
$assert_session->pageTextContains('Español');
$assert_session->pageTextNotContains('Funciona con');
$assert_session->pageTextContains('Powered by');
}
/**
* Tests translation of the user profile edit form.
*
* The user profile edit form is a special case when used with the preferred
* admin language negotiator because of the recursive way that the negotiator
* is called.
*/
public function testUserProfileTranslationWithPreferredAdminLanguage(): void {
$assert_session = $this->assertSession();
// Set the interface language to use the preferred administration language.
/** @var \Drupal\language\LanguageNegotiatorInterface $language_negotiator */
$language_negotiator = \Drupal::getContainer()->get('language_negotiator');
$language_negotiator->saveConfiguration('language_interface', [
'language-user-admin' => 1,
'language-selected' => 2,
]);
// Create a field on the user entity.
$field_name = $this->randomMachineName();
$label = $this->randomMachineName();
$field_label_en = "English $label";
$field_label_es = "Español $label";
$field_storage = FieldStorageConfig::create([
'field_name' => $field_name,
'entity_type' => 'user',
'type' => 'string',
]);
$field_storage->save();
$instance = FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'user',
'label' => $field_label_en,
]);
$instance->save();
// Add a Spanish translation.
\Drupal::languageManager()
->getLanguageConfigOverride('es', "field.field.user.user.$field_name")
->set('label', $field_label_es)
->save();
// Add the new field to the edit form.
EntityFormDisplay::create([
'targetEntityType' => 'user',
'bundle' => 'user',
'mode' => 'default',
'status' => TRUE,
])
->setComponent($field_name, [
'type' => 'string_textfield',
])
->save();
$user_id = \Drupal::currentUser()->id();
$this->drupalGet("/user/$user_id/edit");
// Admin language choice is "No preference" so we should get the default.
$assert_session->pageTextContains($field_label_en);
$assert_session->pageTextNotContains($field_label_es);
// Set admin language to Spanish.
$this->submitForm(['edit-preferred-admin-langcode' => 'es'], 'edit-submit');
$assert_session->pageTextContains($field_label_es);
$assert_session->pageTextNotContains($field_label_en);
// Set admin language to English.
$this->submitForm(['edit-preferred-admin-langcode' => 'en'], 'edit-submit');
$assert_session->pageTextContains($field_label_en);
$assert_session->pageTextNotContains($field_label_es);
}
}

View File

@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Tests entity type without language support.
*
* This is to ensure that an entity type without language support can not
* enable the language select from the content language settings page.
*
* @covers \Drupal\language\Form\ContentLanguageSettingsForm
* @group language
*/
class EntityTypeWithoutLanguageFormTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'language',
'language_test',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create and log in administrative user.
$admin_user = $this->drupalCreateUser([
'administer languages',
]);
$this->drupalLogin($admin_user);
}
/**
* Tests configuration options with an entity without language definition.
*/
public function testEmptyLangcode(): void {
// Assert that we can not enable language select from
// content language settings page.
$this->drupalGet('admin/config/regional/content-language');
$this->assertSession()->fieldNotExists('entity_types[no_language_entity_test]');
}
}

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional;
use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
/**
* Generic module test for language.
*
* @group language
*/
class GenericTest extends GenericModuleTestBase {}

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Tests that the language settings on block config appears correctly.
*
* @group language
*/
class LanguageBlockSettingsVisibilityTest extends BrowserTestBase {
protected static $modules = ['block', 'language'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
public function testUnnecessaryLanguageSettingsVisibility(): void {
$admin_user = $this->drupalCreateUser([
'administer languages',
'access administration pages',
'administer blocks',
]);
$this->drupalLogin($admin_user);
$this->drupalGet('admin/config/regional/language/add');
$this->submitForm(['predefined_langcode' => 'hu'], 'Add language');
$this->drupalGet('admin/structure/block/add/system_menu_block:admin/stark');
$this->assertSession()->fieldNotExists("edit-visibility-language-langcodes-und");
$this->assertSession()->fieldNotExists("edit-visibility-language-langcodes-zxx");
$this->assertSession()->fieldExists("edit-visibility-language-langcodes-en");
$this->assertSession()->fieldExists("edit-visibility-language-langcodes-hu");
}
}

View File

@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\system\Functional\Menu\AssertBreadcrumbTrait;
/**
* Tests breadcrumbs functionality.
*
* @group Menu
*/
class LanguageBreadcrumbTest extends BrowserTestBase {
use AssertBreadcrumbTrait;
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['language', 'block', 'filter'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalPlaceBlock('system_breadcrumb_block');
ConfigurableLanguage::createFromLangcode('de')->save();
ConfigurableLanguage::createFromLangcode('gsw-berne')->save();
}
/**
* Tests breadcrumbs with URL prefixes.
*/
public function testBreadCrumbs(): void {
// /user/login is the default frontpage which only works for an anonymous
// user. Access the frontpage in different languages, ensure that no
// breadcrumb is displayed.
$this->assertBreadcrumb('user/login', []);
$this->assertBreadcrumb('de/user/login', []);
$this->assertBreadcrumb('gsw-berne/user/login', []);
$admin_user = $this->drupalCreateUser(['access administration pages', 'administer blocks']);
$this->drupalLogin($admin_user);
// Use administration routes to assert that breadcrumb is displayed
// correctly on pages other than the frontpage.
$this->assertBreadcrumb('admin', [
'' => 'Home',
]);
$this->assertBreadcrumb('de/admin', [
'de' => 'Home',
]);
$this->assertBreadcrumb('admin/structure', [
'' => 'Home',
'admin' => 'Administration',
]);
$this->assertBreadcrumb('de/admin/structure', [
'de' => 'Home',
'de/admin' => 'Administration',
]);
}
}

View File

@@ -0,0 +1,129 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional;
use Drupal\Tests\BrowserTestBase;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests browser language detection with different accept-language headers.
*
* @group language
*/
class LanguageBrowserDetectionAcceptLanguageTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'language',
'locale',
'content_translation',
'system_test',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// User to manage languages.
$admin = $this->drupalCreateUser([], NULL, TRUE);
$this->drupalLogin($admin);
// Create FR.
ConfigurableLanguage::createFromLangcode('fr')->save();
// Set language detection to URL and browser detection.
$this->drupalGet('/admin/config/regional/language/detection');
$this->submitForm([
'language_interface[enabled][language-url]' => TRUE,
'language_interface[enabled][language-browser]' => TRUE,
'language_interface[enabled][language-selected]' => TRUE,
], 'Save settings');
// Set prefixes to en and fr.
$this->drupalGet('/admin/config/regional/language/detection/url');
$this->submitForm([
'prefix[en]' => 'en',
'prefix[fr]' => 'fr',
], 'Save configuration');
// Add language codes to browser detection.
$this->drupalGet('/admin/config/regional/language/detection/browser');
$this->submitForm([
'new_mapping[browser_langcode]' => 'fr',
'new_mapping[drupal_langcode]' => 'fr',
], 'Save configuration');
$this->drupalGet('/admin/config/regional/language/detection/browser');
$this->submitForm([
'new_mapping[browser_langcode]' => 'en',
'new_mapping[drupal_langcode]' => 'en',
], 'Save configuration');
$this->drupalGet('/admin/config/regional/language/detection/selected');
$this->submitForm(['edit-selected-langcode' => 'en'], 'Save configuration');
$this->drupalLogout();
}
/**
* Tests with browsers with and without Accept-Language header.
*/
public function testAcceptLanguageEmptyDefault(): void {
// Check correct headers.
$this->drupalGet('/en/system-test/echo/language test', [], ['Accept-Language' => 'en']);
$this->assertSession()->responseHeaderEquals('Content-Language', 'en');
$this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'MISS');
$this->drupalGet('/fr/system-test/echo/language test', [], ['Accept-Language' => 'en']);
$this->assertSession()->responseHeaderEquals('Content-Language', 'fr');
$this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'MISS');
$this->drupalGet('/system-test/echo/language test', [], ['Accept-Language' => 'en']);
$this->assertSession()->responseHeaderEquals('Content-Language', 'en');
$this->assertSession()->responseHeaderDoesNotExist('X-Drupal-Cache');
// Check with UK browser.
$this->drupalGet('/system-test/echo/language test', [], ['Accept-Language' => 'en-UK,en']);
$this->assertSession()->responseHeaderEquals('Content-Language', 'en');
$this->assertSession()->responseHeaderDoesNotExist('X-Drupal-Cache');
// Check with french browser.
$this->drupalGet('/system-test/echo/language test', [], ['Accept-Language' => 'fr-FR,fr']);
$this->assertSession()->responseHeaderEquals('Content-Language', 'fr');
$this->assertSession()->responseHeaderDoesNotExist('X-Drupal-Cache');
// Check with browser without language settings - should return fallback language.
$this->drupalGet('/system-test/echo/language test', [], ['Accept-Language' => '']);
$this->assertSession()->responseHeaderEquals('Content-Language', 'en');
$this->assertSession()->responseHeaderDoesNotExist('X-Drupal-Cache');
// Check with french browser again.
$this->drupalGet('/system-test/echo/language test', [], ['Accept-Language' => 'fr-FR,fr']);
$this->assertSession()->responseHeaderEquals('Content-Language', 'fr');
$this->assertSession()->responseHeaderDoesNotExist('X-Drupal-Cache');
// Check with UK browser.
$this->drupalGet('/system-test/echo/language test', [], ['Accept-Language' => 'en-UK,en']);
$this->assertSession()->responseHeaderEquals('Content-Language', 'en');
$this->assertSession()->responseHeaderDoesNotExist('X-Drupal-Cache');
// Check if prefixed URLs are still cached.
$this->drupalGet('/en/system-test/echo/language test', [], ['Accept-Language' => 'en']);
$this->assertSession()->responseHeaderEquals('Content-Language', 'en');
$this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'HIT');
$this->drupalGet('/fr/system-test/echo/language test', [], ['Accept-Language' => 'en']);
$this->assertSession()->responseHeaderEquals('Content-Language', 'fr');
$this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'HIT');
}
}

View File

@@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional;
use Drupal\Core\Url;
use Drupal\Tests\BrowserTestBase;
/**
* Tests browser language detection.
*
* @group language
*/
class LanguageBrowserDetectionTest extends BrowserTestBase {
protected static $modules = ['language'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests mappings between browser language codes and Drupal language codes.
*/
public function testUIBrowserLanguageMappings(): void {
// User to manage languages.
$admin_user = $this->drupalCreateUser([
'administer languages',
'access administration pages',
]);
$this->drupalLogin($admin_user);
// Check that the configure link exists.
$this->drupalGet('admin/config/regional/language/detection');
$this->assertSession()->linkByHrefExists('admin/config/regional/language/detection/browser');
// Check that defaults are loaded from language.mappings.yml.
$this->drupalGet('admin/config/regional/language/detection/browser');
$this->assertSession()->fieldValueEquals('edit-mappings-zh-cn-browser-langcode', 'zh-cn');
$this->assertSession()->fieldValueEquals('edit-mappings-zh-cn-drupal-langcode', 'zh-hans');
// Delete zh-cn language code.
$browser_langcode = 'zh-cn';
$this->drupalGet('admin/config/regional/language/detection/browser/delete/' . $browser_langcode);
$this->assertSession()->pageTextContains("Are you sure you want to delete {$browser_langcode}?");
// Confirm the delete.
$edit = [];
$this->drupalGet('admin/config/regional/language/detection/browser/delete/' . $browser_langcode);
$this->submitForm($edit, 'Confirm');
$this->assertSession()->statusMessageContains("The mapping for the {$browser_langcode} browser language code has been deleted.", 'status');
// Check we went back to the browser negotiation mapping overview.
$this->assertSession()->addressEquals(Url::fromRoute('language.negotiation_browser'));
// Check that Chinese browser language code no longer exists.
$this->assertSession()->fieldNotExists('edit-mappings-zh-cn-browser-langcode');
// Add a new custom mapping.
$edit = [
'new_mapping[browser_langcode]' => 'xx',
'new_mapping[drupal_langcode]' => 'en',
];
$this->drupalGet('admin/config/regional/language/detection/browser');
$this->submitForm($edit, 'Save configuration');
$this->assertSession()->addressEquals(Url::fromRoute('language.negotiation_browser'));
$this->assertSession()->fieldValueEquals('edit-mappings-xx-browser-langcode', 'xx');
$this->assertSession()->fieldValueEquals('edit-mappings-xx-drupal-langcode', 'en');
// Add the same custom mapping again.
$this->drupalGet('admin/config/regional/language/detection/browser');
$this->submitForm($edit, 'Save configuration');
$this->assertSession()->statusMessageContains('Browser language codes must be unique.', 'error');
// Change browser language code of our custom mapping to zh-sg.
$edit = [
'mappings[xx][browser_langcode]' => 'zh-sg',
'mappings[xx][drupal_langcode]' => 'en',
];
$this->drupalGet('admin/config/regional/language/detection/browser');
$this->submitForm($edit, 'Save configuration');
$this->assertSession()->statusMessageContains('Browser language codes must be unique.', 'error');
// Change Drupal language code of our custom mapping to zh-hans.
$edit = [
'mappings[xx][browser_langcode]' => 'xx',
'mappings[xx][drupal_langcode]' => 'zh-hans',
];
$this->drupalGet('admin/config/regional/language/detection/browser');
$this->submitForm($edit, 'Save configuration');
$this->assertSession()->addressEquals(Url::fromRoute('language.negotiation_browser'));
$this->assertSession()->fieldValueEquals('edit-mappings-xx-browser-langcode', 'xx');
$this->assertSession()->fieldValueEquals('edit-mappings-xx-drupal-langcode', 'zh-hans');
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Ensures the language config is installed but not altered on install.
*
* @group language
*/
class LanguageConfigInstallOverrideExistingTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected $profile = 'test_language_negotiation';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests when language config is installed existing config is not overridden.
*/
public function testLanguageConfigInstallOverrideExisting(): void {
/** @var \Drupal\Core\Config\StorageInterface $storage */
$storage = $this->container->get('config.storage');
$config = $this->config('language.types');
// The negotiation methods that have been removed should be disabled after
// purging if not avoided in language_modules_installed().
$language_types_data = $storage->read('language.types');
$this->assertTrue(isset($language_types_data['negotiation']['language_content']['enabled']['test_language_negotiation_method']));
$this->assertTrue(isset($language_types_data['negotiation']['language_content']['enabled']['language-selected']));
$this->assertEquals(-10, $config->get('negotiation.language_content.enabled.test_language_negotiation_method'));
$this->assertEquals(12, $config->get('negotiation.language_content.enabled.language-selected'));
}
}

View File

@@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional;
use Drupal\Core\Config\ConfigCollectionEvents;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\Tests\BrowserTestBase;
/**
* Ensures the language config overrides can be synchronized.
*
* @group language
*/
class LanguageConfigOverrideImportTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'language',
'config',
'locale',
'config_translation',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests that language can be enabled and overrides are created during a sync.
*/
public function testConfigOverrideImport(): void {
ConfigurableLanguage::createFromLangcode('fr')->save();
/** @var \Drupal\Core\Config\StorageInterface $sync */
$sync = \Drupal::service('config.storage.sync');
$this->copyConfig(\Drupal::service('config.storage'), $sync);
// Uninstall the language module and its dependencies so we can test
// enabling the language module and creating overrides at the same time
// during a configuration synchronization.
\Drupal::service('module_installer')->uninstall(['language']);
// Ensure that the current site has no overrides registered to the
// ConfigFactory.
$this->rebuildContainer();
/** @var \Drupal\Core\Config\StorageInterface $override_sync */
$override_sync = $sync->createCollection('language.fr');
// Create some overrides in sync.
$override_sync->write('system.site', ['name' => 'FR default site name']);
$override_sync->write('system.maintenance', ['message' => 'FR message: @site is currently under maintenance. We should be back shortly. Thank you for your patience']);
$this->configImporter()->import();
$this->rebuildContainer();
$override = \Drupal::languageManager()->getLanguageConfigOverride('fr', 'system.site');
$this->assertEquals('FR default site name', $override->get('name'));
$this->drupalGet('fr');
$this->assertSession()->pageTextContains('FR default site name');
$this->drupalLogin($this->drupalCreateUser([
'translate configuration',
]));
$this->drupalGet('admin/config/development/maintenance/translate/fr/edit');
$this->assertSession()->pageTextContains('FR message: @site is currently under maintenance. We should be back shortly. Thank you for your patience');
}
/**
* Tests that configuration events are not fired during a sync of overrides.
*/
public function testConfigOverrideImportEvents(): void {
// Enable the config_events_test module so we can record events occurring.
\Drupal::service('module_installer')->install(['config_events_test']);
$this->rebuildContainer();
ConfigurableLanguage::createFromLangcode('fr')->save();
/** @var \Drupal\Core\Config\StorageInterface $sync */
$sync = \Drupal::service('config.storage.sync');
$this->copyConfig(\Drupal::service('config.storage'), $sync);
/** @var \Drupal\Core\Config\StorageInterface $override_sync */
$override_sync = $sync->createCollection('language.fr');
// Create some overrides in sync.
$override_sync->write('system.site', ['name' => 'FR default site name']);
\Drupal::state()->set('config_events_test.event', FALSE);
$this->configImporter()->import();
$this->rebuildContainer();
// Test that no config save event has been fired during the import because
// language configuration overrides do not fire events.
$event_recorder = \Drupal::state()->get('config_events_test.event', FALSE);
$this->assertSame([
'event_name' => ConfigCollectionEvents::SAVE_IN_COLLECTION,
'current_config_data' => ['name' => 'FR default site name'],
'original_config_data' => [],
'raw_config_data' => ['name' => 'FR default site name'],
], $event_recorder);
$this->drupalGet('fr');
$this->assertSession()->pageTextContains('FR default site name');
}
}

View File

@@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional;
use Drupal\Tests\SchemaCheckTestTrait;
use Drupal\Tests\BrowserTestBase;
/**
* Ensures the language config schema is correct.
*
* @group language
*/
class LanguageConfigSchemaTest extends BrowserTestBase {
use SchemaCheckTestTrait;
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['language', 'menu_link_content'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* A user with administrative permissions.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create user.
$this->adminUser = $this->drupalCreateUser(['administer languages']);
$this->drupalLogin($this->adminUser);
}
/**
* Tests whether the language config schema is valid.
*/
public function testValidLanguageConfigSchema(): void {
// Make sure no language configuration available by default.
$config_data = $this->config('language.settings')->get();
$this->assertEmpty($config_data);
$settings_path = 'admin/config/regional/content-language';
// Enable translation for menu link.
$edit['entity_types[menu_link_content]'] = TRUE;
$edit['settings[menu_link_content][menu_link_content][settings][language][language_alterable]'] = TRUE;
// Enable translation for user.
$edit['entity_types[user]'] = TRUE;
$edit['settings[user][user][settings][language][language_alterable]'] = TRUE;
$edit['settings[user][user][settings][language][langcode]'] = 'en';
$this->drupalGet($settings_path);
$this->submitForm($edit, 'Save configuration');
$config_data = $this->config('language.content_settings.menu_link_content.menu_link_content');
// Make sure configuration saved correctly.
$this->assertTrue($config_data->get('language_alterable'));
$this->assertConfigSchema(\Drupal::service('config.typed'), $config_data->getName(), $config_data->get());
}
}

View File

@@ -0,0 +1,284 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional;
use Drupal\Core\Language\LanguageInterface;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\language\Entity\ContentLanguageSettings;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\Tests\BrowserTestBase;
/**
* Tests the features of the language configuration element field.
*
* @group language
*/
class LanguageConfigurationElementTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'taxonomy',
'node',
'language',
'language_elements_test',
'field_ui',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$user = $this->drupalCreateUser([
'access administration pages',
'administer languages',
'administer content types',
]);
$this->drupalLogin($user);
}
/**
* Tests the language settings have been saved.
*/
public function testLanguageConfigurationElement(): void {
$this->drupalGet('language-tests/language_configuration_element');
$edit['lang_configuration[langcode]'] = 'current_interface';
$edit['lang_configuration[language_alterable]'] = FALSE;
$this->submitForm($edit, 'Save');
$lang_conf = ContentLanguageSettings::loadByEntityTypeBundle('entity_test', 'some_bundle');
// Check that the settings have been saved.
$this->assertEquals('current_interface', $lang_conf->getDefaultLangcode());
$this->assertFalse($lang_conf->isLanguageAlterable());
$this->drupalGet('language-tests/language_configuration_element');
$this->assertTrue($this->assertSession()->optionExists('edit-lang-configuration-langcode', 'current_interface')->isSelected());
$this->assertSession()->checkboxNotChecked('edit-lang-configuration-language-alterable');
// Reload the page and save again.
$this->drupalGet('language-tests/language_configuration_element');
$edit['lang_configuration[langcode]'] = 'authors_default';
$edit['lang_configuration[language_alterable]'] = TRUE;
$this->submitForm($edit, 'Save');
$lang_conf = ContentLanguageSettings::loadByEntityTypeBundle('entity_test', 'some_bundle');
// Check that the settings have been saved.
$this->assertEquals('authors_default', $lang_conf->getDefaultLangcode());
$this->assertTrue($lang_conf->isLanguageAlterable());
$this->drupalGet('language-tests/language_configuration_element');
$this->assertTrue($this->assertSession()->optionExists('edit-lang-configuration-langcode', 'authors_default')->isSelected());
$this->assertSession()->checkboxChecked('edit-lang-configuration-language-alterable');
// Test if content type settings have been saved.
$edit = [
'name' => 'Page',
'type' => 'page',
'language_configuration[langcode]' => 'authors_default',
'language_configuration[language_alterable]' => TRUE,
];
$this->drupalGet('admin/structure/types/add');
$this->submitForm($edit, 'Save and manage fields');
// Make sure the settings are saved when creating the content type.
$this->drupalGet('admin/structure/types/manage/page');
$this->assertTrue($this->assertSession()->optionExists('edit-language-configuration-langcode', 'authors_default')->isSelected());
$this->assertSession()->checkboxChecked('edit-language-configuration-language-alterable');
}
/**
* Tests that the language_get_default_langcode() returns the correct values.
*/
public function testDefaultLangcode(): void {
// Add some custom languages.
foreach (['aa', 'bb', 'cc'] as $language_code) {
ConfigurableLanguage::create([
'id' => $language_code,
'label' => $this->randomMachineName(),
])->save();
}
// Ensure the bundles under test exist, to avoid config validation errors.
entity_test_create_bundle('custom_bundle');
entity_test_create_bundle('some_bundle');
// Fixed language.
ContentLanguageSettings::loadByEntityTypeBundle('entity_test', 'custom_bundle')
->setLanguageAlterable(TRUE)
->setDefaultLangcode('bb')
->save();
$langcode = language_get_default_langcode('entity_test', 'custom_bundle');
$this->assertEquals('bb', $langcode);
// Current interface.
ContentLanguageSettings::loadByEntityTypeBundle('entity_test', 'custom_bundle')
->setLanguageAlterable(TRUE)
->setDefaultLangcode('current_interface')
->save();
$langcode = language_get_default_langcode('entity_test', 'custom_bundle');
$language_interface = \Drupal::languageManager()->getCurrentLanguage();
$this->assertEquals($langcode, $language_interface->getId());
// Site's default.
$old_default = \Drupal::languageManager()->getDefaultLanguage();
// Ensure the language entity default value is correct.
$configurable_language = ConfigurableLanguage::load($old_default->getId());
$this->assertTrue($configurable_language->isDefault(), 'The en language entity is flagged as the default language.');
$this->config('system.site')->set('default_langcode', 'cc')->save();
ContentLanguageSettings::loadByEntityTypeBundle('entity_test', 'custom_bundle')
->setLanguageAlterable(TRUE)
->setDefaultLangcode(LanguageInterface::LANGCODE_SITE_DEFAULT)
->save();
$langcode = language_get_default_langcode('entity_test', 'custom_bundle');
$this->assertEquals('cc', $langcode);
// Ensure the language entity default value is correct.
$configurable_language = ConfigurableLanguage::load($old_default->getId());
$this->assertFalse($configurable_language->isDefault(), 'The en language entity is not flagged as the default language.');
$configurable_language = ConfigurableLanguage::load('cc');
// Check calling the
// \Drupal\language\ConfigurableLanguageInterface::isDefault() method
// directly.
$this->assertTrue($configurable_language->isDefault(), 'The cc language entity is flagged as the default language.');
// Check the default value of a language field when authors preferred option
// is selected.
// First create a user, then assign a langcode.
$some_user = $this->drupalCreateUser();
$some_user->preferred_langcode = 'bb';
$some_user->save();
$this->drupalLogin($some_user);
ContentLanguageSettings::create([
'target_entity_type_id' => 'entity_test',
'target_bundle' => 'some_bundle',
])->setLanguageAlterable(TRUE)
->setDefaultLangcode('authors_default')
->save();
$this->drupalGet('language-tests/language_configuration_element_test');
$this->assertTrue($this->assertSession()->optionExists('edit-langcode', 'bb')->isSelected());
}
/**
* Tests that the configuration is retained when the node type is updated.
*/
public function testNodeTypeUpdate(): void {
// Create the article content type first if the profile used is not the
// standard one.
if ($this->profile != 'standard') {
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
}
$admin_user = $this->drupalCreateUser(['administer content types']);
$this->drupalLogin($admin_user);
$edit = [
'language_configuration[langcode]' => 'current_interface',
'language_configuration[language_alterable]' => TRUE,
];
$this->drupalGet('admin/structure/types/manage/article');
$this->submitForm($edit, 'Save');
// Check the language default configuration for the articles.
$configuration = ContentLanguageSettings::loadByEntityTypeBundle('node', 'article');
$uuid = $configuration->uuid();
$this->assertEquals('current_interface', $configuration->getDefaultLangcode(), 'The default language configuration has been saved on the Article content type.');
$this->assertTrue($configuration->isLanguageAlterable(), 'The alterable language configuration has been saved on the Article content type.');
// Update the article content type by changing the title label.
$edit = [
'title_label' => 'Name',
];
$this->drupalGet('admin/structure/types/manage/article');
$this->submitForm($edit, 'Save');
// Check that we still have the settings for the updated node type.
$configuration = ContentLanguageSettings::loadByEntityTypeBundle('node', 'article');
$this->assertEquals('current_interface', $configuration->getDefaultLangcode(), 'The default language configuration has been kept on the updated Article content type.');
$this->assertTrue($configuration->isLanguageAlterable(), 'The alterable language configuration has been kept on the updated Article content type.');
$this->assertEquals($uuid, $configuration->uuid(), 'The language configuration uuid has been kept on the updated Article content type.');
}
/**
* Tests the language settings are deleted on bundle delete.
*/
public function testNodeTypeDelete(): void {
// Create the article content type first if the profile used is not the
// standard one.
if ($this->profile != 'standard') {
$this->drupalCreateContentType([
'type' => 'article',
'name' => 'Article',
]);
}
$admin_user = $this->drupalCreateUser(['administer content types']);
$this->drupalLogin($admin_user);
// Create language configuration for the articles.
$edit = [
'language_configuration[langcode]' => 'authors_default',
'language_configuration[language_alterable]' => TRUE,
];
$this->drupalGet('admin/structure/types/manage/article');
$this->submitForm($edit, 'Save');
// Check the language default configuration for articles is present.
$configuration = \Drupal::entityTypeManager()->getStorage('language_content_settings')->load('node.article');
$this->assertNotEmpty($configuration, 'The language configuration is present.');
// Delete 'article' bundle.
$this->drupalGet('admin/structure/types/manage/article/delete');
$this->submitForm([], 'Delete');
// Check that the language configuration has been deleted.
\Drupal::entityTypeManager()->getStorage('language_content_settings')->resetCache();
$configuration = \Drupal::entityTypeManager()->getStorage('language_content_settings')->load('node.article');
$this->assertNull($configuration, 'The language configuration was deleted after bundle was deleted.');
}
/**
* Tests that the configuration is retained when a vocabulary is updated.
*/
public function testTaxonomyVocabularyUpdate(): void {
$vocabulary = Vocabulary::create([
'name' => 'Country',
'vid' => 'country',
]);
$vocabulary->save();
$admin_user = $this->drupalCreateUser(['administer taxonomy']);
$this->drupalLogin($admin_user);
$edit = [
'default_language[langcode]' => 'current_interface',
'default_language[language_alterable]' => TRUE,
];
$this->drupalGet('admin/structure/taxonomy/manage/country');
$this->submitForm($edit, 'Save');
// Check the language default configuration.
$configuration = ContentLanguageSettings::loadByEntityTypeBundle('taxonomy_term', 'country');
$uuid = $configuration->uuid();
$this->assertEquals('current_interface', $configuration->getDefaultLangcode(), 'The default language configuration has been saved on the Country vocabulary.');
$this->assertTrue($configuration->isLanguageAlterable(), 'The alterable language configuration has been saved on the Country vocabulary.');
// Update the vocabulary.
$edit = [
'name' => 'Nation',
];
$this->drupalGet('admin/structure/taxonomy/manage/country');
$this->submitForm($edit, 'Save');
// Check that we still have the settings for the updated vocabulary.
$configuration = ContentLanguageSettings::loadByEntityTypeBundle('taxonomy_term', 'country');
$this->assertEquals('current_interface', $configuration->getDefaultLangcode(), 'The default language configuration has been kept on the updated Country vocabulary.');
$this->assertTrue($configuration->isLanguageAlterable(), 'The alterable language configuration has been kept on the updated Country vocabulary.');
$this->assertEquals($uuid, $configuration->uuid(), 'The language configuration uuid has been kept on the updated Country vocabulary.');
}
}

View File

@@ -0,0 +1,236 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional;
use Drupal\Core\Url;
use Drupal\Core\Language\LanguageInterface;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\Tests\BrowserTestBase;
/**
* Adds and configures languages to check negotiation changes.
*
* @group language
*/
class LanguageConfigurationTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['language'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Functional tests for adding, editing and deleting languages.
*/
public function testLanguageConfiguration(): void {
// Ensure the after installing the language module the weight of the English
// language is still 0.
$this->assertEquals(0, ConfigurableLanguage::load('en')->getWeight(), 'The English language has a weight of 0.');
// User to add and remove language.
$admin_user = $this->drupalCreateUser([
'administer languages',
'access administration pages',
]);
$this->drupalLogin($admin_user);
// Check if the Default English language has no path prefix.
$this->drupalGet('admin/config/regional/language/detection/url');
$this->assertSession()->fieldValueEquals("prefix[en]", '');
// Check that Add language is a primary button.
$this->drupalGet('admin/config/regional/language/add');
$button = $this->assertSession()->buttonExists('Add language');
$this->assertTrue($button->hasClass("button--primary"));
// Add predefined language.
$edit = [
'predefined_langcode' => 'fr',
];
$this->submitForm($edit, 'Add language');
$this->assertSession()->pageTextContains('French');
$this->assertSession()->addressEquals(Url::fromRoute('entity.configurable_language.collection'));
// Langcode for Languages is always 'en'.
$language = $this->config('language.entity.fr')->get();
$this->assertEquals('en', $language['langcode']);
// Check if the Default English language has no path prefix.
$this->drupalGet('admin/config/regional/language/detection/url');
$this->assertSession()->fieldValueEquals("prefix[en]", '');
// Check if French has a path prefix.
$this->drupalGet('admin/config/regional/language/detection/url');
$this->assertSession()->fieldValueEquals("prefix[fr]", 'fr');
// Check if we can change the default language.
$this->drupalGet('admin/config/regional/language');
$this->assertSession()->checkboxChecked('edit-site-default-language-en');
// Change the default language.
$edit = [
'site_default_language' => 'fr',
];
$this->submitForm($edit, 'Save configuration');
$this->rebuildContainer();
$this->assertSession()->checkboxChecked('edit-site-default-language-fr');
$this->assertSession()->addressEquals(Url::fromRoute('entity.configurable_language.collection', [], ['langcode' => 'fr']));
// Check if a valid language prefix is added after changing the default
// language.
$this->drupalGet('admin/config/regional/language/detection/url');
$this->assertSession()->fieldValueEquals("prefix[en]", 'en');
// Check if French still has a path prefix.
$this->drupalGet('admin/config/regional/language/detection/url');
$this->assertSession()->fieldValueEquals("prefix[fr]", 'fr');
// Check that prefix can be changed.
$edit = [
'prefix[fr]' => 'french',
];
$this->submitForm($edit, 'Save configuration');
$this->assertSession()->fieldValueEquals("prefix[fr]", 'french');
// Check that the prefix can be removed.
$edit = [
'prefix[fr]' => '',
];
$this->submitForm($edit, 'Save configuration');
$this->assertSession()->statusMessageNotContains('The prefix may only be left blank for the selected detection fallback language.');
// Change default negotiation language.
$this->config('language.negotiation')->set('selected_langcode', 'fr')->save();
// Check that the prefix of a language that is not the negotiation one
// cannot be changed to empty string.
$edit = [
'prefix[en]' => '',
];
$this->submitForm($edit, 'Save configuration');
$this->assertSession()->statusMessageContains('The prefix may only be left blank for the selected detection fallback language.', 'error');
// Check that prefix cannot be changed to contain a slash.
$edit = [
'prefix[en]' => 'foo/bar',
];
$this->submitForm($edit, 'Save configuration');
$this->assertSession()->statusMessageContains('The prefix may not contain a slash.', 'error');
// Remove English language and add a new Language to check if langcode of
// Language entity is 'en'.
$this->drupalGet('admin/config/regional/language/delete/en');
$this->submitForm([], 'Delete');
$this->rebuildContainer();
$this->assertSession()->statusMessageContains('The English (en) language has been removed.', 'status');
// Ensure that French language has a weight of 1 after being created through
// the UI.
$french = ConfigurableLanguage::load('fr');
$this->assertEquals(1, $french->getWeight(), 'The French language has a weight of 1.');
// Ensure that French language can now have a weight of 0.
$french->setWeight(0)->save();
$this->assertEquals(0, $french->getWeight(), 'The French language has a weight of 0.');
// Ensure that new languages created through the API get a weight of 0.
$afrikaans = ConfigurableLanguage::createFromLangcode('af');
$afrikaans->save();
$this->assertEquals(0, $afrikaans->getWeight(), 'The Afrikaans language has a weight of 0.');
// Ensure that a new language can be created with any weight.
$arabic = ConfigurableLanguage::createFromLangcode('ar');
$arabic->setWeight(4)->save();
$this->assertEquals(4, $arabic->getWeight(), 'The Arabic language has a weight of 0.');
$edit = [
'predefined_langcode' => 'de',
];
$this->drupalGet('admin/config/regional/language/add');
$this->submitForm($edit, 'Add language');
$language = $this->config('language.entity.de')->get();
$this->assertEquals('fr', $language['langcode']);
// Ensure that German language has a weight of 5 after being created through
// the UI.
$french = ConfigurableLanguage::load('de');
$this->assertEquals(5, $french->getWeight(), 'The German language has a weight of 5.');
}
/**
* Functional tests for setting system language weight on adding, editing and deleting languages.
*/
public function testLanguageConfigurationWeight(): void {
// User to add and remove language.
$admin_user = $this->drupalCreateUser([
'administer languages',
'access administration pages',
]);
$this->drupalLogin($admin_user);
$this->checkConfigurableLanguageWeight();
// Add predefined language.
$edit = [
'predefined_langcode' => 'fr',
];
$this->drupalGet('admin/config/regional/language/add');
$this->submitForm($edit, 'Add language');
$this->checkConfigurableLanguageWeight('after adding new language');
// Re-ordering languages.
$edit = [
'languages[en][weight]' => $this->getHighestConfigurableLanguageWeight() + 1,
];
$this->drupalGet('admin/config/regional/language');
$this->submitForm($edit, 'Save configuration');
$this->checkConfigurableLanguageWeight('after re-ordering');
// Remove predefined language.
$this->drupalGet('admin/config/regional/language/delete/fr');
$this->submitForm([], 'Delete');
$this->checkConfigurableLanguageWeight('after deleting a language');
}
/**
* Validates system languages are ordered after configurable languages.
*
* @param string $state
* (optional) A string for customizing assert messages, containing the
* description of the state of the check, for example: 'after re-ordering'.
* Defaults to 'by default'.
*/
protected function checkConfigurableLanguageWeight($state = 'by default') {
// Reset language list.
\Drupal::languageManager()->reset();
$max_configurable_language_weight = $this->getHighestConfigurableLanguageWeight();
foreach (\Drupal::languageManager()->getLanguages(LanguageInterface::STATE_LOCKED) as $locked_language) {
$this->assertGreaterThan($max_configurable_language_weight, $locked_language->getWeight(), sprintf('System language %s does not have higher weight than configurable languages %s', $locked_language->getName(), $state));
}
}
/**
* Helper to get maximum weight of configurable (unlocked) languages.
*
* @return int
* Maximum weight of configurable languages.
*/
protected function getHighestConfigurableLanguageWeight() {
$max_weight = 0;
$storage = $this->container->get('entity_type.manager')
->getStorage('configurable_language');
$storage->resetCache();
/** @var \Drupal\Core\Language\LanguageInterface[] $languages */
$languages = $storage->loadMultiple();
foreach ($languages as $language) {
if (!$language->isLocked()) {
$max_weight = max($max_weight, $language->getWeight());
}
}
return $max_weight;
}
}

View File

@@ -0,0 +1,106 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional;
use Drupal\Core\Url;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Tests\BrowserTestBase;
/**
* Adds and configures custom languages.
*
* @group language
*/
class LanguageCustomLanguageConfigurationTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['language'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Functional tests for adding, editing and deleting languages.
*/
public function testLanguageConfiguration(): void {
// Create user with permissions to add and remove languages.
$admin_user = $this->drupalCreateUser([
'administer languages',
'access administration pages',
]);
$this->drupalLogin($admin_user);
// Add custom language.
$edit = [
'predefined_langcode' => 'custom',
];
$this->drupalGet('admin/config/regional/language/add');
$this->submitForm($edit, 'Add custom language');
// Test validation on missing values.
$this->assertSession()->statusMessageContains('Language code field is required.', 'error');
$this->assertSession()->statusMessageContains('Language name field is required.', 'error');
$empty_language = new Language();
$this->assertSession()->checkboxChecked('edit-direction-' . $empty_language->getDirection());
$this->assertSession()->addressEquals(Url::fromRoute('language.add'));
// Test validation of invalid values.
$edit = [
'predefined_langcode' => 'custom',
'langcode' => 'white space',
'label' => '<strong>evil markup</strong>',
'direction' => LanguageInterface::DIRECTION_LTR,
];
$this->drupalGet('admin/config/regional/language/add');
$this->submitForm($edit, 'Add custom language');
$this->assertSession()->statusMessageContains('Language code must be a valid language tag as defined by the W3C.', 'error');
$this->assertSession()->linkExists("defined by the W3C");
$this->assertSession()->linkByHrefExists("http://www.w3.org/International/articles/language-tags/");
$this->assertSession()->statusMessageContains('Language name cannot contain any markup.', 'error');
$this->assertSession()->addressEquals(Url::fromRoute('language.add'));
// Test adding a custom language with a numeric region code.
$edit = [
'predefined_langcode' => 'custom',
'langcode' => 'es-419',
'label' => 'Latin American Spanish',
'direction' => LanguageInterface::DIRECTION_LTR,
];
$this->drupalGet('admin/config/regional/language/add');
$this->submitForm($edit, 'Add custom language');
$this->assertSession()->statusMessageContains("The language {$edit['label']} has been created and can now be used.", 'status');
$this->assertSession()->addressEquals(Url::fromRoute('entity.configurable_language.collection'));
// Test validation of existing language values.
$edit = [
'predefined_langcode' => 'custom',
'langcode' => 'de',
'label' => 'German',
'direction' => LanguageInterface::DIRECTION_LTR,
];
// Add the language the first time.
$this->drupalGet('admin/config/regional/language/add');
$this->submitForm($edit, 'Add custom language');
$this->assertSession()->statusMessageContains("The language {$edit['label']} has been created and can now be used.", 'status');
$this->assertSession()->addressEquals(Url::fromRoute('entity.configurable_language.collection'));
// Add the language a second time and confirm that this is not allowed.
$this->drupalGet('admin/config/regional/language/add');
$this->submitForm($edit, 'Add custom language');
$this->assertSession()->statusMessageContains("The language {$edit['label']} ({$edit['langcode']}) already exists.", 'error');
$this->assertSession()->addressEquals(Url::fromRoute('language.add'));
}
}

View File

@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Tests language picker compatibility with hook_entity_field_access.
*
* @group language
*/
class LanguageEntityFieldAccessHookTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'node',
'text',
'field',
'filter',
'language',
'language_entity_field_access_test',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests compatibility with hook_entity_field_access().
*/
public function testHookEntityFieldAccess(): void {
// Create an admin user and do the login.
$user = $this->drupalCreateUser([], NULL, TRUE);
$this->drupalLogin($user);
// Assess the field is not visible.
$this->drupalGet('node/add/page');
$this->assertSession()->fieldNotExists('langcode[0][value]');
$this->drupalLogout();
}
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Tests that the language list is not empty when language is installed.
*
* @group language
*/
class LanguageListModuleInstallTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['language_test'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests enabling Language.
*/
public function testModuleInstallLanguageList(): void {
// Since LanguageManager::getLanguages() uses static caches we need to do
// this by enabling the module using the UI.
$admin_user = $this->drupalCreateUser([
'access administration pages',
'administer modules',
]);
$this->drupalLogin($admin_user);
$edit = [];
$edit['modules[language][enable]'] = 'language';
$this->drupalGet('admin/modules');
$this->submitForm($edit, 'Install');
$this->assertEquals(1, \Drupal::state()->get('language_test.language_count_preinstall', 0), 'Using LanguageManager::getLanguages() returns 1 language during Language installation.');
// Get updated module list by rebuilding container.
$this->rebuildContainer();
$this->assertTrue(\Drupal::moduleHandler()->moduleExists('language'), 'Language module is enabled');
}
}

View File

@@ -0,0 +1,234 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional;
use Drupal\Core\Url;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Tests\BrowserTestBase;
/**
* Adds a new language and tests changing its status and the default language.
*
* @group language
*/
class LanguageListTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['language'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Functional tests for adding, editing and deleting languages.
*/
public function testLanguageList(): void {
// User to add and remove language.
$admin_user = $this->drupalCreateUser([
'administer languages',
'access administration pages',
]);
$this->drupalLogin($admin_user);
// Get the weight of the last language.
$languages = \Drupal::service('language_manager')->getLanguages();
$last_language_weight = end($languages)->getWeight();
// Add predefined language.
$edit = [
'predefined_langcode' => 'fr',
];
$this->drupalGet('admin/config/regional/language/add');
$this->submitForm($edit, 'Add language');
$this->assertSession()->pageTextContains('French');
$this->assertSession()->addressEquals(Url::fromRoute('entity.configurable_language.collection'));
// Get the weight of the last language and check that the weight is one unit
// heavier than the last configurable language.
$this->rebuildContainer();
$languages = \Drupal::service('language_manager')->getLanguages();
$last_language = end($languages);
$this->assertEquals($last_language_weight + 1, $last_language->getWeight());
$this->assertEquals($edit['predefined_langcode'], $last_language->getId());
// Add custom language.
$langcode = 'xx';
$name = $this->randomMachineName(16);
$edit = [
'predefined_langcode' => 'custom',
'langcode' => $langcode,
'label' => $name,
'direction' => Language::DIRECTION_LTR,
];
$this->drupalGet('admin/config/regional/language/add');
$this->submitForm($edit, 'Add custom language');
$this->assertSession()->addressEquals(Url::fromRoute('entity.configurable_language.collection'));
$this->assertSession()->responseContains('"edit-languages-' . $langcode . '-weight"');
$this->assertSession()->pageTextContains($name);
$language = \Drupal::service('language_manager')->getLanguage($langcode);
$english = \Drupal::service('language_manager')->getLanguage('en');
// Check if we can change the default language.
$path = 'admin/config/regional/language';
$this->drupalGet($path);
$this->assertSession()->checkboxChecked('edit-site-default-language-en');
// Change the default language.
$edit = [
'site_default_language' => $langcode,
];
$this->submitForm($edit, 'Save configuration');
$this->rebuildContainer();
$this->assertSession()->checkboxNotChecked('edit-site-default-language-en');
$this->assertSession()->addressEquals(Url::fromRoute('entity.configurable_language.collection', [], ['language' => $language]));
// Ensure we can't delete the default language.
$this->drupalGet('admin/config/regional/language/delete/' . $langcode);
$this->assertSession()->statusCodeEquals(403);
// Ensure 'Edit' link works.
$this->drupalGet('admin/config/regional/language');
$this->clickLink('Edit');
$this->assertSession()->titleEquals('Edit language | Drupal');
// Edit a language.
$name = $this->randomMachineName(16);
$edit = [
'label' => $name,
];
$this->drupalGet('admin/config/regional/language/edit/' . $langcode);
$this->submitForm($edit, 'Save language');
$this->assertSession()->pageTextContains($name);
$this->assertSession()->addressEquals(Url::fromRoute('entity.configurable_language.collection', [], ['language' => $language]));
// Change back the default language.
$edit = [
'site_default_language' => 'en',
];
$this->drupalGet($path);
$this->submitForm($edit, 'Save configuration');
$this->rebuildContainer();
// Ensure 'delete' link works.
$this->drupalGet('admin/config/regional/language');
$this->clickLink('Delete');
$this->assertSession()->pageTextContains('Are you sure you want to delete the language');
// Delete a language.
$this->drupalGet('admin/config/regional/language/delete/' . $langcode);
// First test the 'cancel' link.
$this->clickLink('Cancel');
$this->assertSession()->addressEquals(Url::fromRoute('entity.configurable_language.collection', [], ['language' => $english]));
$this->assertSession()->pageTextContains($name);
// Delete the language for real. This a confirm form, we do not need any
// fields changed.
$this->drupalGet('admin/config/regional/language/delete/' . $langcode);
$this->submitForm([], 'Delete');
$this->assertSession()->statusMessageContains("The {$name} ({$langcode}) language has been removed.", 'status');
$this->assertSession()->addressEquals(Url::fromRoute('entity.configurable_language.collection', [], ['language' => $english]));
// Verify that language is no longer found.
$this->drupalGet('admin/config/regional/language/delete/' . $langcode);
$this->assertSession()->statusCodeEquals(404);
// Delete French.
$this->drupalGet('admin/config/regional/language/delete/fr');
$this->submitForm([], 'Delete');
// Make sure the "language_count" state has been updated correctly.
$this->rebuildContainer();
$this->assertSession()->statusMessageContains('The French (fr) language has been removed.', 'status');
$this->assertSession()->addressEquals(Url::fromRoute('entity.configurable_language.collection'));
// Verify that language is no longer found.
$this->drupalGet('admin/config/regional/language/delete/fr');
$this->assertSession()->statusCodeEquals(404);
// Make sure the "language_count" state has not changed.
// Ensure we can delete the English language. Right now English is the only
// language so we must add a new language and make it the default before
// deleting English.
$langcode = 'xx';
$name = $this->randomMachineName(16);
$edit = [
'predefined_langcode' => 'custom',
'langcode' => $langcode,
'label' => $name,
'direction' => Language::DIRECTION_LTR,
];
$this->drupalGet('admin/config/regional/language/add');
$this->submitForm($edit, 'Add custom language');
$this->assertSession()->addressEquals(Url::fromRoute('entity.configurable_language.collection'));
$this->assertSession()->pageTextContains($name);
// Check if we can change the default language.
$path = 'admin/config/regional/language';
$this->drupalGet($path);
$this->assertSession()->checkboxChecked('edit-site-default-language-en');
// Change the default language.
$edit = [
'site_default_language' => $langcode,
];
$this->submitForm($edit, 'Save configuration');
$this->rebuildContainer();
$this->assertSession()->checkboxNotChecked('edit-site-default-language-en');
$this->assertSession()->addressEquals(Url::fromRoute('entity.configurable_language.collection', [], ['language' => $language]));
$this->drupalGet('admin/config/regional/language/delete/en');
$this->submitForm([], 'Delete');
$this->assertSession()->statusMessageContains('The English (en) language has been removed.', 'status');
$this->rebuildContainer();
// Ensure we can't delete a locked language.
$this->drupalGet('admin/config/regional/language/delete/und');
$this->assertSession()->statusCodeEquals(403);
// Ensure that NL cannot be set default when it's not available.
// First create the NL language.
$edit = [
'predefined_langcode' => 'nl',
];
$this->drupalGet('admin/config/regional/language/add');
$this->submitForm($edit, 'Add language');
// Load the form which has now the additional NL language option.
$this->drupalGet('admin/config/regional/language');
// Delete the NL language in the background.
$language_storage = $this->container->get('entity_type.manager')->getStorage('configurable_language');
$language_storage->load('nl')->delete();
$this->submitForm(['site_default_language' => 'nl'], 'Save configuration');
$this->assertSession()->statusMessageContains('Selected default language no longer exists.', 'error');
$this->assertSession()->checkboxNotChecked('edit-site-default-language-xx');
}
/**
* Functional tests for the language states (locked or configurable).
*/
public function testLanguageStates(): void {
// Add some languages, and also lock some of them.
ConfigurableLanguage::create(['label' => $this->randomMachineName(), 'id' => 'l1'])->save();
ConfigurableLanguage::create(['label' => $this->randomMachineName(), 'id' => 'l2', 'locked' => TRUE])->save();
ConfigurableLanguage::create(['label' => $this->randomMachineName(), 'id' => 'l3'])->save();
ConfigurableLanguage::create(['label' => $this->randomMachineName(), 'id' => 'l4', 'locked' => TRUE])->save();
$expected_locked_languages = ['l4' => 'l4', 'l2' => 'l2', 'und' => 'und', 'zxx' => 'zxx'];
$expected_all_languages = ['l4' => 'l4', 'l3' => 'l3', 'l2' => 'l2', 'l1' => 'l1', 'en' => 'en', 'und' => 'und', 'zxx' => 'zxx'];
$expected_conf_languages = ['l3' => 'l3', 'l1' => 'l1', 'en' => 'en'];
$locked_languages = $this->container->get('language_manager')->getLanguages(LanguageInterface::STATE_LOCKED);
$this->assertEquals([], array_diff_key($expected_locked_languages, $locked_languages), 'Locked languages loaded correctly.');
$all_languages = $this->container->get('language_manager')->getLanguages(LanguageInterface::STATE_ALL);
$this->assertEquals([], array_diff_key($expected_all_languages, $all_languages), 'All languages loaded correctly.');
$conf_languages = $this->container->get('language_manager')->getLanguages();
$this->assertEquals([], array_diff_key($expected_conf_languages, $conf_languages), 'Configurable languages loaded correctly.');
}
}

View File

@@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional;
use Drupal\Core\Url;
use Drupal\locale\StringStorageInterface;
use Drupal\Tests\BrowserTestBase;
// cspell:ignore espagnol
/**
* Adds a new language with translations and tests language list order.
*
* @group language
*/
class LanguageLocaleListTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['language', 'locale'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* @var \Drupal\locale\StringStorageInterface
*/
protected StringStorageInterface $storage;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Add a default locale storage for all these tests.
$this->storage = $this->container->get('locale.storage');
}
/**
* Tests adding, editing, and deleting languages.
*/
public function testLanguageLocaleList(): void {
// User to add and remove language.
$admin_user = $this->drupalCreateUser([
'administer languages',
'access administration pages',
]);
$this->drupalLogin($admin_user);
// Add predefined language.
$edit = [
'predefined_langcode' => 'fr',
];
$this->drupalGet('admin/config/regional/language/add');
$this->submitForm($edit, 'Add language');
$this->assertSession()->statusMessageContains('The language French has been created and can now be used', 'status');
$this->assertSession()->addressEquals(Url::fromRoute('entity.configurable_language.collection'));
$this->rebuildContainer();
// Translate Spanish language to French (Espagnol).
$source = $this->storage->createString([
'source' => 'Spanish',
'context' => '',
])->save();
$this->storage->createTranslation([
'lid' => $source->lid,
'language' => 'fr',
'translation' => 'Espagnol',
])->save();
// Get language list displayed in select list.
$this->drupalGet('fr/admin/config/regional/language/add');
$options = $this->assertSession()->selectExists('edit-predefined-langcode')->findAll('css', 'option');
$options = array_map(function ($item) {
return $item->getText();
}, $options);
// Remove the 'Custom language...' option form the end.
array_pop($options);
// Order language list.
$options_ordered = $options;
natcasesort($options_ordered);
// Check the language list displayed is ordered.
$this->assertSame($options, $options_ordered, 'Language list is ordered.');
}
}

View File

@@ -0,0 +1,196 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional;
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\Tests\BrowserTestBase;
use Drupal\Core\Routing\RouteObjectInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
use Symfony\Component\Routing\Route;
/**
* Tests language negotiation with the language negotiator content entity.
*
* @group language
*/
class LanguageNegotiationContentEntityTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'language',
'language_test',
'entity_test',
'system',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* The entity being used for testing.
*
* @var \Drupal\Core\Entity\ContentEntityInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
ConfigurableLanguage::createFromLangcode('es')->save();
ConfigurableLanguage::createFromLangcode('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(['view test entity']);
$this->drupalLogin($user);
}
/**
* Tests default with content language remaining same as interface language.
*/
public function testDefaultConfiguration(): void {
$translation = $this->entity;
$this->drupalGet($translation->toUrl());
$last = \Drupal::keyValue('language_test')->get('language_negotiation_last');
$last_content_language = $last[LanguageInterface::TYPE_CONTENT];
$last_interface_language = $last[LanguageInterface::TYPE_INTERFACE];
$this->assertSame($last_content_language, $last_interface_language);
$this->assertSame($translation->language()->getId(), $last_content_language);
$translation = $this->entity->getTranslation('es');
$this->drupalGet($translation->toUrl());
$last = \Drupal::keyValue('language_test')->get('language_negotiation_last');
$last_content_language = $last[LanguageInterface::TYPE_CONTENT];
$last_interface_language = $last[LanguageInterface::TYPE_INTERFACE];
$this->assertSame($last_content_language, $last_interface_language);
$this->assertSame($translation->language()->getId(), $last_content_language);
$translation = $this->entity->getTranslation('fr');
$this->drupalGet($translation->toUrl());
$last = \Drupal::keyValue('language_test')->get('language_negotiation_last');
$last_content_language = $last[LanguageInterface::TYPE_CONTENT];
$last_interface_language = $last[LanguageInterface::TYPE_INTERFACE];
$this->assertSame($last_content_language, $last_interface_language);
$this->assertSame($translation->language()->getId(), $last_content_language);
}
/**
* Tests enabling the language negotiator language_content_entity.
*/
public function testEnabledLanguageContentNegotiator(): void {
// 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->toUrl());
$last = \Drupal::keyValue('language_test')->get('language_negotiation_last');
$last_content_language = $last[LanguageInterface::TYPE_CONTENT];
$last_interface_language = $last[LanguageInterface::TYPE_INTERFACE];
// Check that interface language and content language are the same as the
// default translation language of the entity.
$this->assertSame($default_site_langcode, $last_interface_language);
$this->assertSame($last_content_language, $last_interface_language);
$this->assertSame($translation->language()->getId(), $last_content_language);
$translation = $this->entity->getTranslation('es');
$this->drupalGet($translation->toUrl());
$last = \Drupal::keyValue('language_test')->get('language_negotiation_last');
$last_content_language = $last[LanguageInterface::TYPE_CONTENT];
$last_interface_language = $last[LanguageInterface::TYPE_INTERFACE];
$this->assertSame($last_interface_language, $default_site_langcode, 'Interface language did not change from the default site language.');
$this->assertSame($last_content_language, $translation->language()->getId(), 'Content language matches the current entity translation language.');
$translation = $this->entity->getTranslation('fr');
$this->drupalGet($translation->toUrl());
$last = \Drupal::keyValue('language_test')->get('language_negotiation_last');
$last_content_language = $last[LanguageInterface::TYPE_CONTENT];
$last_interface_language = $last[LanguageInterface::TYPE_INTERFACE];
$this->assertSame($last_interface_language, $default_site_langcode, 'Interface language did not change from the default site language.');
$this->assertSame($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));
$request->setSession(new Session(new MockArraySessionStorage()));
$this->container->get('request_stack')->push($request);
}
}

View File

@@ -0,0 +1,223 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional;
use Drupal\Core\Language\LanguageInterface;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUI;
use Drupal\Tests\BrowserTestBase;
/**
* Tests alterations to language types/negotiation info.
*
* @group language
*/
class LanguageNegotiationInfoTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['language', 'content_translation'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$admin_user = $this->drupalCreateUser([
'administer languages',
'access administration pages',
'view the administration theme',
'administer modules',
]);
$this->drupalLogin($admin_user);
$this->drupalGet('admin/config/regional/language/add');
$this->submitForm(['predefined_langcode' => 'it'], 'Add language');
}
/**
* Returns the configurable language manager.
*
* @return \Drupal\language\ConfigurableLanguageManager
*/
protected function languageManager() {
return $this->container->get('language_manager');
}
/**
* Sets state flags for language_test module.
*
* Ensures to correctly update data both in the child site and the test runner
* environment.
*
* @param array $values
* The key/value pairs to set in state.
*/
protected function stateSet(array $values) {
// Set the new state values.
$this->container->get('state')->setMultiple($values);
// Refresh in-memory static state/config caches and static variables.
$this->refreshVariables();
// Refresh/rewrite language negotiation configuration, in order to pick up
// the manipulations performed by language_test module's info alter hooks.
$this->container->get('language_negotiator')->purgeConfiguration();
}
/**
* Tests alterations to language types/negotiation info.
*/
public function testInfoAlterations(): void {
$this->stateSet([
// Enable language_test type info.
'language_test.language_types' => TRUE,
// Enable language_test negotiation info (not altered yet).
'language_test.language_negotiation_info' => TRUE,
// Alter LanguageInterface::TYPE_CONTENT to be configurable.
'language_test.content_language_type' => TRUE,
]);
$this->container->get('module_installer')->install(['language_test']);
$this->resetAll();
// Check that fixed language types are properly configured without the need
// of saving the language negotiation settings.
$this->checkFixedLanguageTypes();
$type = LanguageInterface::TYPE_CONTENT;
$language_types = $this->languageManager()->getLanguageTypes();
$this->assertContains($type, $language_types, 'Content language type is configurable.');
// Enable some core and custom language negotiation methods. The test
// language type is supposed to be configurable.
$test_type = 'test_language_type';
$interface_method_id = LanguageNegotiationUI::METHOD_ID;
$test_method_id = 'test_language_negotiation_method';
$form_field = $type . '[enabled][' . $interface_method_id . ']';
$edit = [
$form_field => TRUE,
$type . '[enabled][' . $test_method_id . ']' => TRUE,
$test_type . '[enabled][' . $test_method_id . ']' => TRUE,
$test_type . '[configurable]' => TRUE,
];
$this->drupalGet('admin/config/regional/language/detection');
$this->submitForm($edit, 'Save settings');
// Alter language negotiation info to remove interface language negotiation
// method.
$this->stateSet([
'language_test.language_negotiation_info_alter' => TRUE,
]);
$negotiation = $this->config('language.types')->get('negotiation.' . $type . '.enabled');
$this->assertFalse(isset($negotiation[$interface_method_id]), 'Interface language negotiation method removed from the stored settings.');
// Check that the interface language negotiation method is unavailable.
$this->drupalGet('admin/config/regional/language/detection');
$this->assertSession()->fieldNotExists($form_field);
// Check that type-specific language negotiation methods can be assigned
// only to the corresponding language types.
foreach ($this->languageManager()->getLanguageTypes() as $type) {
$form_field = $type . '[enabled][test_language_negotiation_method_ts]';
if ($type == $test_type) {
$this->assertSession()->fieldExists($form_field);
}
else {
$this->assertSession()->fieldNotExists($form_field);
}
}
// Check language negotiation results.
$this->drupalGet('');
$last = \Drupal::keyValue('language_test')->get('language_negotiation_last');
foreach ($this->languageManager()->getDefinedLanguageTypes() as $type) {
$langcode = $last[$type];
$value = $type == LanguageInterface::TYPE_CONTENT || str_contains($type, 'test') ? 'it' : 'en';
$this->assertEquals($langcode, $value, "The negotiated language for $type is $value");
}
// Uninstall language_test and check that everything is set back to the
// original status.
$this->container->get('module_installer')->uninstall(['language_test']);
$this->rebuildContainer();
// Check that only the core language types are available.
foreach ($this->languageManager()->getDefinedLanguageTypes() as $type) {
$this->assertStringNotContainsString('test', $type, "The $type language is still available");
}
// Check that fixed language types are properly configured, even those
// previously set to configurable.
$this->checkFixedLanguageTypes();
// Check that unavailable language negotiation methods are not present in
// the negotiation settings.
$negotiation = $this->config('language.types')->get('negotiation.' . $type . '.enabled');
$this->assertFalse(isset($negotiation[$test_method_id]), 'The disabled test language negotiation method is not part of the content language negotiation settings.');
// Check that configuration page presents the correct options and settings.
$this->assertSession()->pageTextNotContains("Test language detection");
$this->assertSession()->pageTextNotContains("This is a test language negotiation method");
}
/**
* Check that language negotiation for fixed types matches the stored one.
*/
protected function checkFixedLanguageTypes() {
$configurable = $this->languageManager()->getLanguageTypes();
foreach ($this->languageManager()->getDefinedLanguageTypesInfo() as $type => $info) {
if (!in_array($type, $configurable) && isset($info['fixed'])) {
$negotiation = $this->config('language.types')->get('negotiation.' . $type . '.enabled');
$equal = array_keys($negotiation) === array_values($info['fixed']);
$this->assertTrue($equal, "language negotiation for $type is properly set up");
}
}
}
/**
* Tests altering config of configurable language types.
*/
public function testConfigLangTypeAlterations(): void {
// Default of config.
$test_type = LanguageInterface::TYPE_CONTENT;
$this->assertFalse($this->isLanguageTypeConfigurable($test_type), 'Language type is not configurable.');
// Editing config.
$edit = [$test_type . '[configurable]' => TRUE];
$this->drupalGet('admin/config/regional/language/detection');
$this->submitForm($edit, 'Save settings');
$this->assertTrue($this->isLanguageTypeConfigurable($test_type), 'Language type is now configurable.');
// After installing another module, the config should be the same.
$this->drupalGet('admin/modules');
$this->submitForm(['modules[test_module][enable]' => 1], 'Install');
$this->assertTrue($this->isLanguageTypeConfigurable($test_type), 'Language type is still configurable.');
// After uninstalling the other module, the config should be the same.
$this->drupalGet('admin/modules/uninstall');
$this->submitForm(['uninstall[test_module]' => 1], 'Uninstall');
$this->assertTrue($this->isLanguageTypeConfigurable($test_type), 'Language type is still configurable.');
}
/**
* Checks whether the given language type is configurable.
*
* @param string $type
* The language type.
*
* @return bool
* TRUE if the specified language type is configurable, FALSE otherwise.
*/
protected function isLanguageTypeConfigurable($type) {
$configurable_types = $this->config('language.types')->get('configurable');
return in_array($type, $configurable_types);
}
}

View File

@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\Tests\BrowserTestBase;
/**
* Tests the session language negotiation method.
*
* @group language
*/
class LanguageNegotiationSessionTest extends BrowserTestBase {
/**
* An administrative user to configure the test environment.
*
* @var \Drupal\user\Entity\User
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
protected static $modules = ['language'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create a new user with permission to manage the languages.
$this->adminUser = $this->drupalCreateUser(['administer languages']);
$this->drupalLogin($this->adminUser);
ConfigurableLanguage::createFromLangcode('fr')->save();
}
/**
* Tests language negotiation via query/session parameters.
*/
public function testSessionLanguageNegotiationMethod(): void {
// Enable Session and Selected language for interface language detection.
$this->drupalGet('admin/config/regional/language/detection');
$edit = [
'language_interface[enabled][language-session]' => 1,
'language_interface[enabled][language-selected]' => 1,
'language_interface[weight][language-session]' => -6,
'language_interface[weight][language-selected]' => 12,
];
$this->submitForm($edit, 'Save settings');
// Set language via query parameter.
$this->drupalGet('user/' . $this->adminUser->id(), ['query' => ['language' => 'fr']]);
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->responseHeaderEquals('Content-language', 'fr');
// Verify that the language is persisted in the session.
$this->drupalGet('user/' . $this->adminUser->id());
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->responseHeaderEquals('Content-language', 'fr');
// Set language via query parameter.
$this->drupalGet('user/' . $this->adminUser->id(), ['query' => ['language' => 'en']]);
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->responseHeaderEquals('Content-language', 'en');
// Verify that the language is persisted in the session.
$this->drupalGet('admin/config/regional/language');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->responseHeaderEquals('Content-language', 'en');
}
}

View File

@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* @coversDefaultClass \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl
* @group language
*/
class LanguageNegotiationUrlTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'language',
'node',
'path',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* @var \Drupal\user\Entity\User
*/
protected $user;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create an Article node type.
if ($this->profile != 'standard') {
$this->drupalCreateContentType(['type' => 'article']);
}
$this->user = $this->drupalCreateUser([
'administer languages',
'access administration pages',
'view the administration theme',
'administer nodes',
'create article content',
'create url aliases',
]);
$this->drupalLogin($this->user);
$this->drupalGet('admin/config/regional/language/add');
$this->submitForm(['predefined_langcode' => 'de'], 'Add language');
}
/**
* @covers ::processInbound
*/
public function testDomain(): void {
// Check if paths that contain language prefixes can be reached when
// language is taken from the domain.
$edit = [
'language_negotiation_url_part' => 'domain',
'prefix[en]' => 'eng',
'prefix[de]' => 'de',
'domain[en]' => $_SERVER['HTTP_HOST'],
'domain[de]' => "de.$_SERVER[HTTP_HOST]",
];
$this->drupalGet('admin/config/regional/language/detection/url');
$this->submitForm($edit, 'Save configuration');
$nodeValues = [
'title[0][value]' => 'Test',
'path[0][alias]' => '/eng/test',
];
$this->drupalGet('node/add/article');
$this->submitForm($nodeValues, 'Save');
$this->assertSession()->statusCodeEquals(200);
}
}

View File

@@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Confirm that paths are not changed on monolingual non-English sites.
*
* @group language
*/
class LanguagePathMonolingualTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['block', 'language', 'path'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create and log in user.
$web_user = $this->drupalCreateUser([
'administer languages',
'access administration pages',
'administer site configuration',
]);
$this->drupalLogin($web_user);
// Enable French language.
$edit = [];
$edit['predefined_langcode'] = 'fr';
$this->drupalGet('admin/config/regional/language/add');
$this->submitForm($edit, 'Add language');
// Make French the default language.
$edit = [
'site_default_language' => 'fr',
];
$this->drupalGet('admin/config/regional/language');
$this->submitForm($edit, 'Save configuration');
// Delete English.
$this->drupalGet('admin/config/regional/language/delete/en');
$this->submitForm([], 'Delete');
// Changing the default language causes a container rebuild. Therefore need
// to rebuild the container in the test environment.
$this->rebuildContainer();
// Verify that French is the only language.
$this->container->get('language_manager')->reset();
$this->assertFalse(\Drupal::languageManager()->isMultilingual(), 'Site is mono-lingual');
$this->assertEquals('fr', \Drupal::languageManager()->getDefaultLanguage()->getId(), 'French is the default language');
// Set language detection to URL.
$edit = ['language_interface[enabled][language-url]' => TRUE];
$this->drupalGet('admin/config/regional/language/detection');
$this->submitForm($edit, 'Save settings');
$this->drupalPlaceBlock('local_actions_block');
}
/**
* Verifies that links do not have language prefixes in them.
*/
public function testPageLinks(): void {
// Navigate to 'admin/config' path.
$this->drupalGet('admin/config');
// Verify that links in this page do not have a 'fr/' prefix.
$this->assertSession()->linkByHrefNotExists('/fr/', 'Links do not contain language prefix');
// Verify that links in this page can be followed and work.
$this->clickLink('Languages');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextContains('Add language');
}
}

View File

@@ -0,0 +1,112 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\language\Traits\LanguageTestTrait;
/**
* Tests the content translation settings language selector options.
*
* @covers \Drupal\language\Form\ContentLanguageSettingsForm
* @group language
*/
class LanguageSelectorTranslatableTest extends BrowserTestBase {
use LanguageTestTrait;
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'language',
'content_translation',
'node',
'comment',
'field_ui',
'entity_test',
'locale',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* The user with administrator privileges.
*
* @var \Drupal\user\Entity\User
*/
public $administrator;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create user and set permissions.
$this->administrator = $this->drupalCreateUser($this->getAdministratorPermissions(), 'administrator');
$this->drupalLogin($this->administrator);
}
/**
* Returns an array of permissions needed for the translator.
*/
protected function getAdministratorPermissions() {
return array_filter(
['translate interface',
'administer content translation',
'create content translations',
'update content translations',
'delete content translations',
'administer languages',
]
);
}
/**
* Tests content translation language selectors are correctly translated.
*/
public function testLanguageStringSelector(): void {
// Add another language.
static::createLanguageFromLangcode('es');
// Translate the string English in Spanish (Inglés). Override config entity.
$name_translation = 'Inglés';
\Drupal::languageManager()
->getLanguageConfigOverride('es', 'language.entity.en')
->set('label', $name_translation)
->save();
// Check content translation overview selector.
$path = 'es/admin/config/regional/content-language';
$this->drupalGet($path);
// Get en language from selector.
$option = $this->assertSession()->optionExists('edit-settings-user-user-settings-language-langcode', 'en');
// Check that the language text is translated.
$this->assertSame($name_translation, $option->getText());
}
/**
* Tests that correct title is displayed for content translation page.
*/
public function testContentTranslationPageTitle(): void {
$this->drupalGet('admin/config/regional/content-language');
$this->assertSession()->pageTextContains('Content language and translation');
$this->assertSession()->pageTextNotMatches('#Content language$#');
\Drupal::service('module_installer')->uninstall(['content_translation']);
$this->drupalGet('admin/config/regional/content-language');
$this->assertSession()->pageTextContains('Content language');
$this->assertSession()->pageTextNotContains('Content language and translation');
}
}

View File

@@ -0,0 +1,732 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl;
use Drupal\menu_link_content\Entity\MenuLinkContent;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Tests\BrowserTestBase;
use Drupal\Core\Url;
// cspell:ignore publi publié
/**
* Functional tests for the language switching feature.
*
* @group language
*/
class LanguageSwitchingTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'locale',
'locale_test',
'language',
'block',
'language_test',
'menu_ui',
'node',
];
/**
* The theme to install as the default for testing.
*
* @var string
*/
protected $defaultTheme = 'starterkit_theme';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create and log in user.
$admin_user = $this->drupalCreateUser([
'administer blocks',
'administer languages',
'administer site configuration',
'access administration pages',
'access content',
]);
$this->drupalLogin($admin_user);
}
/**
* Functional tests for the language switcher block.
*/
public function testLanguageBlock(): void {
// Add language.
$edit = [
'predefined_langcode' => 'fr',
];
$this->drupalGet('admin/config/regional/language/add');
$this->submitForm($edit, 'Add language');
// Set the native language name.
$this->saveNativeLanguageName('fr', 'français');
// Enable URL language detection and selection.
$edit = ['language_interface[enabled][language-url]' => '1'];
$this->drupalGet('admin/config/regional/language/detection');
$this->submitForm($edit, 'Save settings');
// Enable the language switching block.
$block = $this->drupalPlaceBlock('language_block:' . LanguageInterface::TYPE_INTERFACE, [
'id' => 'test_language_block',
// Ensure a 2-byte UTF-8 sequence is in the tested output.
'label' => $this->randomMachineName(8) . '×',
]);
$this->doTestLanguageBlockAuthenticated($block->label());
$this->doTestHomePageLinks($block->label());
$this->doTestLanguageBlockAnonymous($block->label());
$this->doTestLanguageBlock404($block->label(), 'system/404');
// Test 404s with big_pipe where the behavior is different for logged-in
// users.
\Drupal::service('module_installer')->install(['big_pipe']);
$this->rebuildAll();
$this->doTestLanguageBlock404($block->label(), 'system/404');
$this->drupalLogin($this->drupalCreateUser());
// @todo This is testing the current behavior with the big_pipe module
// enabled. This behavior is a bug will be fixed in
// https://www.drupal.org/project/drupal/issues/3349201.
$this->doTestLanguageBlock404($block->label(), '<front>');
}
/**
* The home page link should be "/" or "/{language_prefix}".
*
* @param string $block_label
* The label of the language switching block.
*
* @see self::testLanguageBlock()
*/
protected function doTestHomePageLinks($block_label) {
// Create a node and set as home page.
$this->createHomePage();
// Go to home page.
$this->DrupalGet('<front>');
// The language switcher block should display.
$this->assertSession()->pageTextContains($block_label);
// Assert that each list item and anchor element has the appropriate data-
// attributes.
$language_switchers = $this->xpath('//div[@id=:id]/ul/li', [':id' => 'block-test-language-block']);
$list_items = [];
$anchors = [];
$labels = [];
foreach ($language_switchers as $list_item) {
$list_items[] = [
'hreflang' => $list_item->getAttribute('hreflang'),
'data-drupal-link-system-path' => $list_item->getAttribute('data-drupal-link-system-path'),
];
$link = $list_item->find('xpath', 'a');
$anchors[] = [
'hreflang' => $link->getAttribute('hreflang'),
'data-drupal-link-system-path' => $link->getAttribute('data-drupal-link-system-path'),
'href' => $link->getAttribute('href'),
];
$labels[] = $link->getText();
}
$expected_list_items = [
0 => [
'hreflang' => 'en',
'data-drupal-link-system-path' => '<front>',
],
1 => [
'hreflang' => 'fr',
'data-drupal-link-system-path' => '<front>',
],
];
$this->assertSame($expected_list_items, $list_items, 'The list items have the correct attributes that will contain the correct home page links.');
$expected_anchors = [
0 => [
'hreflang' => 'en',
'data-drupal-link-system-path' => '<front>',
'href' => Url::fromRoute('<front>')->toString(),
],
1 => [
'hreflang' => 'fr',
'data-drupal-link-system-path' => '<front>',
'href' => Url::fromRoute('<front>')->toString() . 'fr',
],
];
$this->assertSame($expected_anchors, $anchors, 'The anchors have the correct attributes that will link to the correct home page in that language.');
$this->assertSame(['English', 'français'], $labels, 'The language links labels are in their own language on the language switcher block.');
}
/**
* For authenticated users, the "active" class is set by JavaScript.
*
* @param string $block_label
* The label of the language switching block.
*
* @see self::testLanguageBlock()
*/
protected function doTestLanguageBlockAuthenticated($block_label) {
// Assert that the language switching block is displayed on the frontpage.
$this->drupalGet('');
$this->assertSession()->pageTextContains($block_label);
// Assert that each list item and anchor element has the appropriate data-
// attributes.
$language_switchers = $this->xpath('//div[@id=:id]/ul/li', [':id' => 'block-test-language-block']);
$list_items = [];
$anchors = [];
$labels = [];
foreach ($language_switchers as $list_item) {
$list_items[] = [
'hreflang' => $list_item->getAttribute('hreflang'),
'data-drupal-link-system-path' => $list_item->getAttribute('data-drupal-link-system-path'),
];
$link = $list_item->find('xpath', 'a');
$anchors[] = [
'hreflang' => $link->getAttribute('hreflang'),
'data-drupal-link-system-path' => $link->getAttribute('data-drupal-link-system-path'),
];
$labels[] = $link->getText();
}
$expected_list_items = [
0 => ['hreflang' => 'en', 'data-drupal-link-system-path' => 'user/2'],
1 => ['hreflang' => 'fr', 'data-drupal-link-system-path' => 'user/2'],
];
$this->assertSame($expected_list_items, $list_items, 'The list items have the correct attributes that will allow the drupal.active-link library to mark them as active.');
$expected_anchors = [
0 => ['hreflang' => 'en', 'data-drupal-link-system-path' => 'user/2'],
1 => ['hreflang' => 'fr', 'data-drupal-link-system-path' => 'user/2'],
];
$this->assertSame($expected_anchors, $anchors, 'The anchors have the correct attributes that will allow the drupal.active-link library to mark them as active.');
$settings = $this->getDrupalSettings();
$this->assertSame('user/2', $settings['path']['currentPath'], 'drupalSettings.path.currentPath is set correctly to allow drupal.active-link to mark the correct links as active.');
$this->assertFalse($settings['path']['isFront'], 'drupalSettings.path.isFront is set correctly to allow drupal.active-link to mark the correct links as active.');
$this->assertSame('en', $settings['path']['currentLanguage'], 'drupalSettings.path.currentLanguage is set correctly to allow drupal.active-link to mark the correct links as active.');
$this->assertSame(['English', 'français'], $labels, 'The language links labels are in their own language on the language switcher block.');
}
/**
* For anonymous users, the "active" class is set by PHP.
*
* @param string $block_label
* The label of the language switching block.
*
* @see self::testLanguageBlock()
*/
protected function doTestLanguageBlockAnonymous($block_label) {
$this->drupalLogout();
// Assert that the language switching block is displayed on the frontpage
// and ensure that the active class is added when query params are present.
$this->drupalGet('', ['query' => ['foo' => 'bar']]);
$this->assertSession()->pageTextContains($block_label);
// Assert that only the current language is marked as active.
$language_switchers = $this->xpath('//div[@id=:id]/ul/li', [':id' => 'block-test-language-block']);
$links = [
'active' => [],
'inactive' => [],
];
$anchors = [
'active' => [],
'inactive' => [],
];
$labels = [];
foreach ($language_switchers as $list_item) {
$langcode = $list_item->getAttribute('hreflang');
if ($list_item->hasClass('is-active')) {
$links['active'][] = $langcode;
}
else {
$links['inactive'][] = $langcode;
}
$link = $list_item->find('xpath', 'a');
$anchor_classes = explode(" ", $link->getAttribute('class'));
if (in_array('is-active', $anchor_classes)) {
$anchors['active'][] = $langcode;
}
else {
$anchors['inactive'][] = $langcode;
}
$labels[] = $link->getText();
}
$this->assertSame(['active' => ['en'], 'inactive' => ['fr']], $links, 'Only the current language list item is marked as active on the language switcher block.');
$this->assertSame(['active' => ['en'], 'inactive' => ['fr']], $anchors, 'Only the current language anchor is marked as active on the language switcher block.');
$this->assertSame(['English', 'français'], $labels, 'The language links labels are in their own language on the language switcher block.');
}
/**
* Tests the language switcher block on 404 pages.
*
* @param string $block_label
* The label of the language switching block.
* @param string $system_path
* The expected system path for the links in the language switcher.
*
* @see self::testLanguageBlock()
*/
protected function doTestLanguageBlock404(string $block_label, string $system_path) {
$this->drupalGet('does-not-exist-' . $this->randomMachineName());
$this->assertSession()->pageTextContains($block_label);
// Assert that each list item and anchor element has the appropriate data-
// attributes.
$language_switchers = $this->xpath('//div[@id=:id]/ul/li', [':id' => 'block-test-language-block']);
$list_items = [];
$anchors = [];
$labels = [];
foreach ($language_switchers as $list_item) {
$list_items[] = [
'hreflang' => $list_item->getAttribute('hreflang'),
'data-drupal-link-system-path' => $list_item->getAttribute('data-drupal-link-system-path'),
];
$link = $list_item->find('xpath', 'a');
$anchors[] = [
'hreflang' => $link->getAttribute('hreflang'),
'data-drupal-link-system-path' => $link->getAttribute('data-drupal-link-system-path'),
];
$labels[] = $link->getText();
}
$expected_list_items = [
0 => ['hreflang' => 'en', 'data-drupal-link-system-path' => $system_path],
1 => ['hreflang' => 'fr', 'data-drupal-link-system-path' => $system_path],
];
$this->assertSame($expected_list_items, $list_items, 'The list items have the correct attributes that will allow the drupal.active-link library to mark them as active.');
$expected_anchors = [
0 => ['hreflang' => 'en', 'data-drupal-link-system-path' => $system_path],
1 => ['hreflang' => 'fr', 'data-drupal-link-system-path' => $system_path],
];
$this->assertSame($expected_anchors, $anchors, 'The anchors have the correct attributes that will allow the drupal.active-link library to mark them as active.');
$this->assertSame(['English', 'français'], $labels, 'The language links labels are in their own language on the language switcher block.');
}
/**
* Tests language switcher links for domain based negotiation.
*/
public function testLanguageBlockWithDomain(): void {
// Add the Italian language.
ConfigurableLanguage::createFromLangcode('it')->save();
// Rebuild the container so that the new language is picked up by services
// that hold a list of languages.
$this->rebuildContainer();
$languages = $this->container->get('language_manager')->getLanguages();
// Enable browser and URL language detection.
$edit = [
'language_interface[enabled][language-url]' => TRUE,
'language_interface[weight][language-url]' => -10,
];
$this->drupalGet('admin/config/regional/language/detection');
$this->submitForm($edit, 'Save settings');
// Do not allow blank domain.
$edit = [
'language_negotiation_url_part' => LanguageNegotiationUrl::CONFIG_DOMAIN,
'domain[en]' => '',
];
$this->drupalGet('admin/config/regional/language/detection/url');
$this->submitForm($edit, 'Save configuration');
$this->assertSession()->statusMessageContains('The domain may not be left blank for English', 'error');
// Change the domain for the Italian language.
$edit = [
'language_negotiation_url_part' => LanguageNegotiationUrl::CONFIG_DOMAIN,
'domain[en]' => \Drupal::request()->getHost(),
'domain[it]' => 'it.example.com',
];
$this->drupalGet('admin/config/regional/language/detection/url');
$this->submitForm($edit, 'Save configuration');
$this->assertSession()->statusMessageContains('The configuration options have been saved', 'status');
// Enable the language switcher block.
$this->drupalPlaceBlock('language_block:' . LanguageInterface::TYPE_INTERFACE, ['id' => 'test_language_block']);
$this->drupalGet('');
/** @var \Drupal\Core\Routing\UrlGenerator $generator */
$generator = $this->container->get('url_generator');
// Verify the English URL is correct
$english_url = $generator->generateFromRoute('entity.user.canonical', ['user' => 2], ['language' => $languages['en']]);
$this->assertSession()->elementAttributeContains('xpath', '//div[@id="block-test-language-block"]/ul/li/a[@hreflang="en"]', 'href', $english_url);
// Verify the Italian URL is correct
$italian_url = $generator->generateFromRoute('entity.user.canonical', ['user' => 2], ['language' => $languages['it']]);
$this->assertSession()->elementAttributeContains('xpath', '//div[@id="block-test-language-block"]/ul/li/a[@hreflang="it"]', 'href', $italian_url);
}
/**
* Tests active class on links when switching languages.
*/
public function testLanguageLinkActiveClass(): void {
// Add language.
$edit = [
'predefined_langcode' => 'fr',
];
$this->drupalGet('admin/config/regional/language/add');
$this->submitForm($edit, 'Add language');
// Enable URL language detection and selection.
$edit = ['language_interface[enabled][language-url]' => '1'];
$this->drupalGet('admin/config/regional/language/detection');
$this->submitForm($edit, 'Save settings');
$this->doTestLanguageLinkActiveClassAuthenticated();
$this->doTestLanguageLinkActiveClassAnonymous();
}
/**
* Check the path-admin class, as same as on default language.
*/
public function testLanguageBodyClass(): void {
// Add language.
$edit = [
'predefined_langcode' => 'fr',
];
$this->drupalGet('admin/config/regional/language/add');
$this->submitForm($edit, 'Add language');
// Enable URL language detection and selection.
$edit = ['language_interface[enabled][language-url]' => '1'];
$this->drupalGet('admin/config/regional/language/detection');
$this->submitForm($edit, 'Save settings');
// Check if the default (English) admin/config page has the right class.
$this->drupalGet('admin/config');
$this->assertSession()->elementAttributeContains('xpath', '//body', 'class', 'path-admin');
// Check if the French admin/config page has the right class.
$this->drupalGet('fr/admin/config');
$this->assertSession()->elementAttributeContains('xpath', '//body', 'class', 'path-admin');
// The testing profile sets the user/login page as the frontpage. That
// redirects authenticated users to their profile page, so check with an
// anonymous user instead.
$this->drupalLogout();
// Check if the default (English) frontpage has the right class.
$this->drupalGet('<front>');
$this->assertSession()->elementAttributeContains('xpath', '//body', 'class', 'path-frontpage');
// Check if the French frontpage has the right class.
$this->drupalGet('fr');
$this->assertSession()->elementAttributeContains('xpath', '//body', 'class', 'path-frontpage');
}
/**
* For authenticated users, the "active" class is set by JavaScript.
*
* @see self::testLanguageLinkActiveClass()
*/
protected function doTestLanguageLinkActiveClassAuthenticated() {
$function_name = '#type link';
$path = 'language_test/type-link-active-class';
// Test links generated by the link generator on an English page.
$current_language = 'English';
$this->drupalGet($path);
// Language code 'none' link should be active.
$this->assertSession()->elementAttributeContains('named', ['id', 'no_lang_link'], 'data-drupal-link-system-path', $path);
// Language code 'en' link should be active.
$this->assertSession()->elementAttributeContains('named', ['id', 'en_link'], 'hreflang', 'en');
$this->assertSession()->elementAttributeContains('named', ['id', 'en_link'], 'data-drupal-link-system-path', $path);
// Language code 'fr' link should not be active.
$this->assertSession()->elementAttributeContains('named', ['id', 'fr_link'], 'hreflang', 'fr');
$this->assertSession()->elementAttributeContains('named', ['id', 'fr_link'], 'data-drupal-link-system-path', $path);
// Verify that drupalSettings contains the correct values.
$settings = $this->getDrupalSettings();
$this->assertSame($path, $settings['path']['currentPath'], 'drupalSettings.path.currentPath is set correctly to allow drupal.active-link to mark the correct links as active.');
$this->assertFalse($settings['path']['isFront'], 'drupalSettings.path.isFront is set correctly to allow drupal.active-link to mark the correct links as active.');
$this->assertSame('en', $settings['path']['currentLanguage'], 'drupalSettings.path.currentLanguage is set correctly to allow drupal.active-link to mark the correct links as active.');
// Test links generated by the link generator on a French page.
$current_language = 'French';
$this->drupalGet('fr/language_test/type-link-active-class');
// Language code 'none' link should be active.
$this->assertSession()->elementAttributeContains('named', ['id', 'no_lang_link'], 'data-drupal-link-system-path', $path);
// Language code 'en' link should not be active.
$this->assertSession()->elementAttributeContains('named', ['id', 'en_link'], 'hreflang', 'en');
$this->assertSession()->elementAttributeContains('named', ['id', 'en_link'], 'data-drupal-link-system-path', $path);
// Language code 'fr' link should be active.
$this->assertSession()->elementAttributeContains('named', ['id', 'fr_link'], 'hreflang', 'fr');
$this->assertSession()->elementAttributeContains('named', ['id', 'fr_link'], 'data-drupal-link-system-path', $path);
// Verify that drupalSettings contains the correct values.
$settings = $this->getDrupalSettings();
$this->assertSame($path, $settings['path']['currentPath'], 'drupalSettings.path.currentPath is set correctly to allow drupal.active-link to mark the correct links as active.');
$this->assertFalse($settings['path']['isFront'], 'drupalSettings.path.isFront is set correctly to allow drupal.active-link to mark the correct links as active.');
$this->assertSame('fr', $settings['path']['currentLanguage'], 'drupalSettings.path.currentLanguage is set correctly to allow drupal.active-link to mark the correct links as active.');
}
/**
* For anonymous users, the "active" class is set by PHP.
*
* @see self::testLanguageLinkActiveClass()
*/
protected function doTestLanguageLinkActiveClassAnonymous() {
$function_name = '#type link';
$this->drupalLogout();
// Test links generated by the link generator on an English page.
$current_language = 'English';
$this->drupalGet('language_test/type-link-active-class');
// Language code 'none' link should be active.
$this->assertSession()->elementExists('xpath', "//a[@id = 'no_lang_link' and contains(@class, 'is-active')]");
// Language code 'en' link should be active.
$this->assertSession()->elementExists('xpath', "//a[@id = 'en_link' and contains(@class, 'is-active')]");
// Language code 'fr' link should not be active.
$this->assertSession()->elementExists('xpath', "//a[@id = 'fr_link' and not(contains(@class, 'is-active'))]");
// Test links generated by the link generator on a French page.
$current_language = 'French';
$this->drupalGet('fr/language_test/type-link-active-class');
// Language code 'none' link should be active.
$this->assertSession()->elementExists('xpath', "//a[@id = 'no_lang_link' and contains(@class, 'is-active')]");
// Language code 'en' link should not be active.
$this->assertSession()->elementExists('xpath', "//a[@id = 'en_link' and not(contains(@class, 'is-active'))]");
// Language code 'fr' link should be active.
$this->assertSession()->elementExists('xpath', "//a[@id = 'fr_link' and contains(@class, 'is-active')]");
}
/**
* Tests language switcher links for session based negotiation.
*/
public function testLanguageSessionSwitchLinks(): void {
// Add language.
$edit = [
'predefined_langcode' => 'fr',
];
$this->drupalGet('admin/config/regional/language/add');
$this->submitForm($edit, 'Add language');
// Enable session language detection and selection.
$edit = [
'language_interface[enabled][language-url]' => FALSE,
'language_interface[enabled][language-session]' => TRUE,
];
$this->drupalGet('admin/config/regional/language/detection');
$this->submitForm($edit, 'Save settings');
// Enable the language switching block.
$this->drupalPlaceBlock('language_block:' . LanguageInterface::TYPE_INTERFACE, [
'id' => 'test_language_block',
]);
// Enable the main menu block.
$this->drupalPlaceBlock('system_menu_block:main', [
'id' => 'test_menu',
]);
// Add a link to the homepage.
$link = MenuLinkContent::create([
'title' => 'Home',
'menu_name' => 'main',
'bundle' => 'menu_link_content',
'link' => [['uri' => 'entity:user/2']],
]);
$link->save();
// Go to the homepage.
$this->drupalGet('');
// Click on the French link.
$this->clickLink('French');
// There should be a query parameter to set the session language.
$this->assertSession()->addressEquals('user/2?language=fr');
// Click on the 'Home' Link.
$this->clickLink('Home');
// There should be no query parameter.
$this->assertSession()->addressEquals('user/2');
// Click on the French link.
$this->clickLink('French');
// There should be no query parameter.
$this->assertSession()->addressEquals('user/2');
}
/**
* Test that the language switching block does not expose restricted paths.
*/
public function testRestrictedPaths(): void {
$entity_type_manager = \Drupal::entityTypeManager();
// Add the French language.
ConfigurableLanguage::createFromLangcode('fr')->save();
// Enable URL language detection and selection.
$this->config('language.types')
->set('negotiation.language_interface.enabled.language-url', 1)
->save();
// Enable the language switching block.
$block = $this->drupalPlaceBlock('language_block:' . LanguageInterface::TYPE_INTERFACE);
// Create a node type and make it translatable.
$entity_type_manager->getStorage('node_type')
->create([
'type' => 'page',
'name' => 'Page',
])
->save();
// Create a published node with an unpublished translation.
$node = $entity_type_manager->getStorage('node')
->create([
'type' => 'page',
'title' => $this->randomMachineName(),
'status' => 1,
]);
$node->save();
$node->addTranslation('fr', ['title' => 'Non publié', 'status' => 0]);
$node->save();
// Create path aliases.
$alias_storage = $entity_type_manager->getStorage('path_alias');
$alias_storage->create([
'path' => '/user/1',
'alias' => '/secret-identity/peter-parker',
])->save();
$alias_storage->create([
'path' => '/node/1',
'langcode' => 'en',
'alias' => '/press-release/published-report',
])->save();
$alias_storage->create([
'path' => '/node/1',
'langcode' => 'fr',
'alias' => '/press-release/rapport-non-publié',
])->save();
// Visit a restricted user page.
// Assert that the language switching block is displayed on the
// access-denied page, but it does not contain the path alias.
$this->assertLinkMarkup('/user/1', 403, $block->label(), 'peter-parker');
// Visit the node and its translation. Use internal paths and aliases. The
// non-ASCII character may be escaped, so remove it from the search string.
$this->assertLinkMarkup('/node/1', 200, $block->label(), 'rapport-non-publi');
$this->assertLinkMarkup('/press-release/published-report', 200, $block->label(), 'rapport-non-publi');
$this->assertLinkMarkup('/fr/node/1', 403, $block->label(), 'rapport-non-publi');
$this->assertLinkMarkup('/fr/press-release/rapport-non-publié', 403, $block->label(), 'rapport-non-publi');
// Test as a user with access to other users and unpublished content.
$privileged_user = $this->drupalCreateUser([
'access user profiles',
'bypass node access',
]);
$this->drupalLogin($privileged_user);
$this->assertLinkMarkup('/user/1', 200, $block->label(), 'peter-parker', TRUE);
$this->assertLinkMarkup('/node/1', 200, $block->label(), 'rapport-non-publi', TRUE);
$this->assertLinkMarkup('/press-release/published-report', 200, $block->label(), 'rapport-non-publi', TRUE);
$this->assertLinkMarkup('/fr/node/1', 200, $block->label(), 'rapport-non-publi', TRUE);
$this->assertLinkMarkup('/fr/press-release/rapport-non-publié', 200, $block->label(), 'rapport-non-publi', TRUE);
// Test as an anonymous user.
$this->drupalLogout();
$this->assertLinkMarkup('/user/1', 403, $block->label(), 'peter-parker');
$this->assertLinkMarkup('/node/1', 200, $block->label(), 'rapport-non-publi');
$this->assertLinkMarkup('/press-release/published-report', 200, $block->label(), 'rapport-non-publi');
$this->assertLinkMarkup('/fr/node/1', 403, $block->label(), 'rapport-non-publi');
$this->assertLinkMarkup('/fr/press-release/rapport-non-publié', 403, $block->label(), 'rapport-non-publi');
}
/**
* Asserts that restricted text is or is not present in the page response.
*
* @param string $path
* The path to test.
* @param int $status
* The HTTP status code, such as 200 or 403.
* @param string $marker
* Text that should always be present.
* @param string $restricted
* Text that should be tested.
* @param bool $found
* (optional) If TRUE, then the restricted text is present. Defaults to
* FALSE.
*/
protected function assertLinkMarkup(string $path, int $status, string $marker, string $restricted, bool $found = FALSE): void {
$this->drupalGet($path);
$this->assertSession()->statusCodeEquals($status);
$this->assertSession()->pageTextContains($marker);
if ($found) {
$this->assertSession()->responseContains($restricted);
}
else {
$this->assertSession()->responseNotContains($restricted);
}
// Assert that all languages had a link passed to
// hook_language_switch_links_alter() to allow alternatives to be provided.
$languages = \Drupal::languageManager()->getNativeLanguages();
$links_for_alter = \Drupal::state()->get('language_test.language_switch_link_ids');
$this->assertSame(array_keys($languages), $links_for_alter);
}
/**
* Saves the native name of a language entity in configuration as a label.
*
* @param string $langcode
* The language code of the language.
* @param string $label
* The native name of the language.
*/
protected function saveNativeLanguageName($langcode, $label) {
\Drupal::service('language.config_factory_override')
->getOverride($langcode, 'language.entity.' . $langcode)->set('label', $label)->save();
}
/**
* Create a node and set it as the home pages.
*/
protected function createHomePage() {
$entity_type_manager = \Drupal::entityTypeManager();
// Create a node type and make it translatable.
$entity_type_manager->getStorage('node_type')
->create([
'type' => 'page',
'name' => 'Page',
])
->save();
// Create a published node.
$node = $entity_type_manager->getStorage('node')
->create([
'type' => 'page',
'title' => $this->randomMachineName(),
'status' => 1,
]);
$node->save();
// Change the front page to /node/1.
$edit = ['site_frontpage' => '/node/1'];
$this->drupalGet('admin/config/system/site-information');
$this->submitForm($edit, 'Save configuration');
}
}

View File

@@ -0,0 +1,626 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Url;
use Drupal\file\Entity\File;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationBrowser;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationSelected;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationSession;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl;
use Drupal\Tests\BrowserTestBase;
use Drupal\user\Plugin\LanguageNegotiation\LanguageNegotiationUser;
use Drupal\user\Plugin\LanguageNegotiation\LanguageNegotiationUserAdmin;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageInterface;
use Symfony\Component\HttpFoundation\Request;
use Drupal\language\LanguageNegotiatorInterface;
use Drupal\block\Entity\Block;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
/**
* Tests the language UI for language switching.
*
* The uses cases that get tested, are:
* - URL (path) > default: Test that the URL prefix setting gets precedence over
* the default language. The browser language preference does not have any
* influence.
* - URL (path) > browser > default: Test that the URL prefix setting gets
* precedence over the browser language preference, which in turn gets
* precedence over the default language.
* - URL (domain) > default: Tests that the URL domain setting gets precedence
* over the default language.
*
* The paths that are used for each of these, are:
* - admin/config: Tests the UI using the precedence rules.
* - zh-hans/admin/config: Tests the UI in Chinese.
* - blah-blah/admin/config: Tests the 404 page.
*
* @group language
*/
class LanguageUILanguageNegotiationTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected static $configSchemaCheckerExclusions = [
// Necessary to allow setting `selected_langcode` to NULL.
// @see testUILanguageNegotiation()
'language.negotiation',
];
/**
* The admin user for testing.
*
* @var \Drupal\user\Entity\User
*/
protected $adminUser;
/**
* Modules to enable.
*
* We marginally use interface translation functionality here, so need to use
* the locale module instead of language only, but the 90% of the test is
* about the negotiation process which is solely in language module.
*
* @var array
*/
protected static $modules = [
'locale',
'language_test',
'block',
'user',
'content_translation',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->adminUser = $this->drupalCreateUser([
'administer languages',
'translate interface',
'access administration pages',
'administer blocks',
]);
$this->drupalLogin($this->adminUser);
}
/**
* Tests for language switching by URL path.
*/
public function testUILanguageNegotiation(): void {
// A few languages to switch to.
// This one is unknown, should get the default lang version.
$langcode_unknown = 'blah-blah';
// For testing browser lang preference.
$langcode_browser_fallback = 'vi';
// For testing path prefix.
$langcode = 'zh-hans';
// For setting browser language preference to 'vi'.
$http_header_browser_fallback = ["Accept-Language" => "$langcode_browser_fallback;q=1"];
// For setting browser language preference to some unknown.
$http_header_blah = ["Accept-Language" => "blah;q=1"];
// Create a private file for testing accessible by the admin user.
\Drupal::service('file_system')->mkdir($this->privateFilesDirectory . '/test');
$filepath = 'private://test/private-file-test.txt';
$contents = "file_put_contents() doesn't seem to appreciate empty strings so let's put in some data.";
file_put_contents($filepath, $contents);
$file = File::create([
'uri' => $filepath,
'uid' => $this->adminUser->id(),
]);
$file->save();
// Setup the site languages by installing two languages.
// Set the default language in order for the translated string to be registered
// into database when seen by t(). Without doing this, our target string
// is for some reason not found when doing translate search. This might
// be some bug.
$default_language = \Drupal::languageManager()->getDefaultLanguage();
ConfigurableLanguage::createFromLangcode($langcode_browser_fallback)->save();
$this->config('system.site')->set('default_langcode', $langcode_browser_fallback)->save();
ConfigurableLanguage::createFromLangcode($langcode)->save();
// We will look for this string in the admin/config screen to see if the
// corresponding translated string is shown.
$default_string = 'Hide descriptions';
// First visit this page to make sure our target string is searchable.
$this->drupalGet('admin/config');
// Now the t()'ed string is in db so switch the language back to default.
// This will rebuild the container so we need to rebuild the container in
// the test environment.
$this->config('system.site')->set('default_langcode', $default_language->getId())->save();
$this->config('language.negotiation')->set('url.prefixes.en', '')->save();
$this->rebuildContainer();
// Translate the string.
$language_browser_fallback_string = "In $langcode_browser_fallback In $langcode_browser_fallback In $langcode_browser_fallback";
$language_string = "In $langcode In $langcode In $langcode";
// Do a translate search of our target string.
$search = [
'string' => $default_string,
'langcode' => $langcode_browser_fallback,
];
$this->drupalGet('admin/config/regional/translate');
$this->submitForm($search, 'Filter');
$textarea = $this->assertSession()->elementExists('xpath', '//textarea');
$lid = $textarea->getAttribute('name');
$edit = [
$lid => $language_browser_fallback_string,
];
$this->drupalGet('admin/config/regional/translate');
$this->submitForm($edit, 'Save translations');
$search = [
'string' => $default_string,
'langcode' => $langcode,
];
$this->drupalGet('admin/config/regional/translate');
$this->submitForm($search, 'Filter');
$textarea = $this->assertSession()->elementExists('xpath', '//textarea');
$lid = $textarea->getAttribute('name');
$edit = [
$lid => $language_string,
];
$this->drupalGet('admin/config/regional/translate');
$this->submitForm($edit, 'Save translations');
// Configure selected language negotiation to use zh-hans.
$edit = ['selected_langcode' => $langcode];
$this->drupalGet('admin/config/regional/language/detection/selected');
$this->submitForm($edit, 'Save configuration');
$test = [
'language_negotiation' => [LanguageNegotiationSelected::METHOD_ID],
'path' => 'admin/config',
'expect' => $language_string,
'expected_method_id' => LanguageNegotiationSelected::METHOD_ID,
'http_header' => $http_header_browser_fallback,
'message' => 'SELECTED: UI language is switched based on selected language.',
];
$this->doRunTest($test);
// An invalid language is selected.
$this->config('language.negotiation')->set('selected_langcode', NULL)->save();
$test = [
'language_negotiation' => [LanguageNegotiationSelected::METHOD_ID],
'path' => 'admin/config',
'expect' => $default_string,
'expected_method_id' => LanguageNegotiatorInterface::METHOD_ID,
'http_header' => $http_header_browser_fallback,
'message' => 'SELECTED > DEFAULT: UI language is switched based on selected language.',
];
$this->doRunTest($test);
// No selected language is available.
$this->config('language.negotiation')->set('selected_langcode', $langcode_unknown)->save();
$test = [
'language_negotiation' => [LanguageNegotiationSelected::METHOD_ID],
'path' => 'admin/config',
'expect' => $default_string,
'expected_method_id' => LanguageNegotiatorInterface::METHOD_ID,
'http_header' => $http_header_browser_fallback,
'message' => 'SELECTED > DEFAULT: UI language is switched based on selected language.',
];
$this->doRunTest($test);
$tests = [
// Default, browser preference should have no influence.
[
'language_negotiation' => [LanguageNegotiationUrl::METHOD_ID, LanguageNegotiationSelected::METHOD_ID],
'path' => 'admin/config',
'expect' => $default_string,
'expected_method_id' => LanguageNegotiatorInterface::METHOD_ID,
'http_header' => $http_header_browser_fallback,
'message' => 'URL (PATH) > DEFAULT: no language prefix, UI language is default and the browser language preference setting is not used.',
],
// Language prefix.
[
'language_negotiation' => [LanguageNegotiationUrl::METHOD_ID, LanguageNegotiationSelected::METHOD_ID],
'path' => "$langcode/admin/config",
'expect' => $language_string,
'expected_method_id' => LanguageNegotiationUrl::METHOD_ID,
'http_header' => $http_header_browser_fallback,
'message' => 'URL (PATH) > DEFAULT: with language prefix, UI language is switched based on path prefix',
],
// Default, go by browser preference.
[
'language_negotiation' => [LanguageNegotiationUrl::METHOD_ID, LanguageNegotiationBrowser::METHOD_ID],
'path' => 'admin/config',
'expect' => $language_browser_fallback_string,
'expected_method_id' => LanguageNegotiationBrowser::METHOD_ID,
'http_header' => $http_header_browser_fallback,
'message' => 'URL (PATH) > BROWSER: no language prefix, UI language is determined by browser language preference',
],
// Prefix, switch to the language.
[
'language_negotiation' => [LanguageNegotiationUrl::METHOD_ID, LanguageNegotiationBrowser::METHOD_ID],
'path' => "$langcode/admin/config",
'expect' => $language_string,
'expected_method_id' => LanguageNegotiationUrl::METHOD_ID,
'http_header' => $http_header_browser_fallback,
'message' => 'URL (PATH) > BROWSER: with language prefix, UI language is based on path prefix',
],
// Default, browser language preference is not one of site's lang.
[
'language_negotiation' => [LanguageNegotiationUrl::METHOD_ID, LanguageNegotiationBrowser::METHOD_ID, LanguageNegotiationSelected::METHOD_ID],
'path' => 'admin/config',
'expect' => $default_string,
'expected_method_id' => LanguageNegotiatorInterface::METHOD_ID,
'http_header' => $http_header_blah,
'message' => 'URL (PATH) > BROWSER > DEFAULT: no language prefix and browser language preference set to unknown language should use default language',
],
];
foreach ($tests as $test) {
$this->doRunTest($test);
}
// 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($language_interface_method_definitions)))
->save();
$this->drupalGet("$langcode_unknown/admin/config", [], $http_header_browser_fallback);
$this->assertSession()->statusCodeEquals(404);
// Set preferred langcode for user to NULL.
$account = $this->loggedInUser;
$account->preferred_langcode = NULL;
$account->save();
$test = [
'language_negotiation' => [LanguageNegotiationUser::METHOD_ID, LanguageNegotiationSelected::METHOD_ID],
'path' => 'admin/config',
'expect' => $default_string,
'expected_method_id' => LanguageNegotiatorInterface::METHOD_ID,
'http_header' => [],
'message' => 'USER > DEFAULT: no preferred user language setting, the UI language is default',
];
$this->doRunTest($test);
// Set preferred langcode for user to default langcode.
$account = $this->loggedInUser;
$account->preferred_langcode = $default_language->getId();
$account->save();
$test = [
'language_negotiation' => [LanguageNegotiationUser::METHOD_ID, LanguageNegotiationUrl::METHOD_ID],
'path' => "$langcode/admin/config",
'expect' => $default_string,
'expected_method_id' => LanguageNegotiationUser::METHOD_ID,
'http_header' => [],
'message' => 'USER > URL: User has default language as preferred user language setting, the UI language is default',
];
$this->doRunTest($test);
// Set preferred langcode for user to unknown language.
$account = $this->loggedInUser;
$account->preferred_langcode = $langcode_unknown;
$account->save();
$test = [
'language_negotiation' => [LanguageNegotiationUser::METHOD_ID, LanguageNegotiationSelected::METHOD_ID],
'path' => 'admin/config',
'expect' => $default_string,
'expected_method_id' => LanguageNegotiatorInterface::METHOD_ID,
'http_header' => [],
'message' => 'USER > DEFAULT: invalid preferred user language setting, the UI language is default',
];
$this->doRunTest($test);
// Set preferred langcode for user to non default.
$account->preferred_langcode = $langcode;
$account->save();
$test = [
'language_negotiation' => [LanguageNegotiationUser::METHOD_ID, LanguageNegotiationSelected::METHOD_ID],
'path' => 'admin/config',
'expect' => $language_string,
'expected_method_id' => LanguageNegotiationUser::METHOD_ID,
'http_header' => [],
'message' => 'USER > DEFAULT: defined preferred user language setting, the UI language is based on user setting',
];
$this->doRunTest($test);
// Set preferred admin langcode for user to NULL.
$account->preferred_admin_langcode = NULL;
$account->save();
$test = [
'language_negotiation' => [LanguageNegotiationUserAdmin::METHOD_ID, LanguageNegotiationSelected::METHOD_ID],
'path' => 'admin/config',
'expect' => $default_string,
'expected_method_id' => LanguageNegotiatorInterface::METHOD_ID,
'http_header' => [],
'message' => 'USER ADMIN > DEFAULT: no preferred user admin language setting, the UI language is default',
];
$this->doRunTest($test);
// Set preferred admin langcode for user to unknown language.
$account->preferred_admin_langcode = $langcode_unknown;
$account->save();
$test = [
'language_negotiation' => [LanguageNegotiationUserAdmin::METHOD_ID, LanguageNegotiationSelected::METHOD_ID],
'path' => 'admin/config',
'expect' => $default_string,
'expected_method_id' => LanguageNegotiatorInterface::METHOD_ID,
'http_header' => [],
'message' => 'USER ADMIN > DEFAULT: invalid preferred user admin language setting, the UI language is default',
];
$this->doRunTest($test);
// Set preferred admin langcode for user to non default.
$account->preferred_admin_langcode = $langcode;
$account->save();
$test = [
'language_negotiation' => [LanguageNegotiationUserAdmin::METHOD_ID, LanguageNegotiationSelected::METHOD_ID],
'path' => 'admin/config',
'expect' => $language_string,
'expected_method_id' => LanguageNegotiationUserAdmin::METHOD_ID,
'http_header' => [],
'message' => 'USER ADMIN > DEFAULT: defined preferred user admin language setting, the UI language is based on user setting',
];
$this->doRunTest($test);
// Go by session preference.
$language_negotiation_session_param = $this->randomMachineName();
$edit = ['language_negotiation_session_param' => $language_negotiation_session_param];
$this->drupalGet('admin/config/regional/language/detection/session');
$this->submitForm($edit, 'Save configuration');
$tests = [
[
'language_negotiation' => [LanguageNegotiationSession::METHOD_ID],
'path' => "admin/config",
'expect' => $default_string,
'expected_method_id' => LanguageNegotiatorInterface::METHOD_ID,
'http_header' => $http_header_browser_fallback,
'message' => 'SESSION > DEFAULT: no language given, the UI language is default',
],
[
'language_negotiation' => [LanguageNegotiationSession::METHOD_ID],
'path' => 'admin/config',
'path_options' => ['query' => [$language_negotiation_session_param => $langcode]],
'expect' => $language_string,
'expected_method_id' => LanguageNegotiationSession::METHOD_ID,
'http_header' => $http_header_browser_fallback,
'message' => 'SESSION > DEFAULT: language given, UI language is determined by session language preference',
],
];
foreach ($tests as $test) {
$this->doRunTest($test);
}
}
protected function doRunTest($test) {
$test += ['path_options' => []];
if (!empty($test['language_negotiation'])) {
$method_weights = array_flip($test['language_negotiation']);
$this->container->get('language_negotiator')->saveConfiguration(LanguageInterface::TYPE_INTERFACE, $method_weights);
}
if (!empty($test['language_negotiation_url_part'])) {
$this->config('language.negotiation')
->set('url.source', $test['language_negotiation_url_part'])
->save();
}
if (!empty($test['language_test_domain'])) {
\Drupal::state()->set('language_test.domain', $test['language_test_domain']);
}
$this->container->get('language_manager')->reset();
$this->drupalGet($test['path'], $test['path_options'], $test['http_header']);
$this->assertSession()->pageTextContains($test['expect']);
$this->assertSession()->statusMessageContains('Language negotiation method: ' . $test['expected_method_id'], 'status');
// Get the private file and ensure it is a 200. It is important to
// invalidate the router cache to ensure the routing system runs a full
// match.
Cache::invalidateTags(['route_match']);
$this->drupalGet('system/files/test/private-file-test.txt');
$this->assertSession()->statusCodeEquals(200);
}
/**
* Tests URL language detection when the requested URL has no language.
*/
public function testUrlLanguageFallback(): void {
// Add the Italian language.
$langcode_browser_fallback = 'it';
ConfigurableLanguage::createFromLangcode($langcode_browser_fallback)->save();
$languages = $this->container->get('language_manager')->getLanguages();
// Enable the path prefix for the default language: this way any unprefixed
// URL must have a valid fallback value.
$edit = ['prefix[en]' => 'en'];
$this->drupalGet('admin/config/regional/language/detection/url');
$this->submitForm($edit, 'Save configuration');
// Enable browser and URL language detection.
$edit = [
'language_interface[enabled][language-browser]' => TRUE,
'language_interface[enabled][language-url]' => TRUE,
'language_interface[weight][language-browser]' => -8,
'language_interface[weight][language-url]' => -10,
];
$this->drupalGet('admin/config/regional/language/detection');
$this->submitForm($edit, 'Save settings');
$this->drupalGet('admin/config/regional/language/detection');
// Enable the language switcher block.
$this->drupalPlaceBlock('language_block:' . LanguageInterface::TYPE_INTERFACE, ['id' => 'test_language_block']);
// Log out, because for anonymous users, the "active" class is set by PHP
// (which means we can easily test it here), whereas for authenticated users
// it is set by JavaScript.
$this->drupalLogout();
// Place a site branding block in the header region.
$this->drupalPlaceBlock('system_branding_block', [
'region' => 'header',
'id' => 'site_branding',
]);
// Access the front page without specifying any valid URL language prefix
// and having as browser language preference a non-default language.
$http_header = ["Accept-Language" => "$langcode_browser_fallback;q=1"];
$language = new Language(['id' => '']);
$this->drupalGet('', ['language' => $language], $http_header);
// Check that the language switcher active link matches the given browser
// language.
$href = Url::fromRoute('<front>')->toString() . $langcode_browser_fallback;
$this->assertSession()->elementTextEquals('xpath', "//div[@id='block-test-language-block']//a[@class='language-link is-active' and starts-with(@href, '$href')]", $languages[$langcode_browser_fallback]->getName());
// Check that URLs are rewritten using the given browser language.
$this->assertSession()->elementTextEquals('xpath', "//div[@id='block-site-branding']/a[@rel='home' and @href='$href'][2]", 'Drupal');
}
/**
* Tests URL handling when separate domains are used for multiple languages.
*/
public function testLanguageDomain(): void {
global $base_url;
// Get the current host URI we're running on.
$base_url_host = parse_url($base_url, PHP_URL_HOST);
// Add the Italian language.
ConfigurableLanguage::createFromLangcode('it')->save();
$languages = $this->container->get('language_manager')->getLanguages();
// Enable browser and URL language detection.
$edit = [
'language_interface[enabled][language-url]' => TRUE,
'language_interface[weight][language-url]' => -10,
];
$this->drupalGet('admin/config/regional/language/detection');
$this->submitForm($edit, 'Save settings');
// Do not allow blank domain.
$edit = [
'language_negotiation_url_part' => LanguageNegotiationUrl::CONFIG_DOMAIN,
'domain[en]' => '',
];
$this->drupalGet('admin/config/regional/language/detection/url');
$this->submitForm($edit, 'Save configuration');
$this->assertSession()->statusMessageContains('The domain may not be left blank for English', 'error');
$this->rebuildContainer();
// Change the domain for the Italian language.
$edit = [
'language_negotiation_url_part' => LanguageNegotiationUrl::CONFIG_DOMAIN,
'domain[en]' => $base_url_host,
'domain[it]' => 'it.example.com',
];
$this->drupalGet('admin/config/regional/language/detection/url');
$this->submitForm($edit, 'Save configuration');
$this->assertSession()->statusMessageContains('The configuration options have been saved', 'status');
$this->rebuildContainer();
// Try to use an invalid domain.
$edit = [
'language_negotiation_url_part' => LanguageNegotiationUrl::CONFIG_DOMAIN,
'domain[en]' => $base_url_host,
'domain[it]' => 'it.example.com/',
];
$this->drupalGet('admin/config/regional/language/detection/url');
$this->submitForm($edit, 'Save configuration');
$this->assertSession()->statusMessageContains("The domain for Italian may only contain the domain name, not a trailing slash, protocol and/or port.", 'error');
// Build the link we're going to test.
$link = 'it.example.com' . rtrim(base_path(), '/') . '/admin';
// Test URL in another language: http://it.example.com/admin.
// Base path gives problems on the testbot, so $correct_link is hard-coded.
// @see UrlAlterFunctionalTest::assertUrlOutboundAlter (path.test).
$italian_url = Url::fromRoute('system.admin', [], ['language' => $languages['it']])->toString();
$url_scheme = \Drupal::request()->isSecure() ? 'https://' : 'http://';
$correct_link = $url_scheme . $link;
$this->assertEquals($correct_link, $italian_url, "The right URL ($italian_url) in accordance with the chosen language");
// Test HTTPS via options.
$italian_url = Url::fromRoute('system.admin', [], ['https' => TRUE, 'language' => $languages['it']])->toString();
$correct_link = 'https://' . $link;
$this->assertSame($correct_link, $italian_url, "The right HTTPS URL (via options) ($italian_url) in accordance with the chosen language");
// Test HTTPS via current URL scheme.
$request = Request::create('', 'GET', [], [], [], ['HTTPS' => 'on']);
$request->setSession(new Session(new MockArraySessionStorage()));
$this->container->get('request_stack')->push($request);
$italian_url = Url::fromRoute('system.admin', [], ['language' => $languages['it']])->toString();
$correct_link = 'https://' . $link;
$this->assertSame($correct_link, $italian_url, "The right URL (via current URL scheme) ($italian_url) in accordance with the chosen language");
}
/**
* Tests persistence of negotiation settings for the content language type.
*/
public function testContentCustomization(): void {
// Customize content language settings from their defaults.
$edit = [
'language_content[configurable]' => TRUE,
'language_content[enabled][language-url]' => FALSE,
'language_content[enabled][language-session]' => TRUE,
];
$this->drupalGet('admin/config/regional/language/detection');
$this->submitForm($edit, 'Save settings');
// Check if configurability persisted.
$config = $this->config('language.types');
$this->assertContains('language_interface', $config->get('configurable'), 'Interface language is configurable.');
$this->assertContains('language_content', $config->get('configurable'), 'Content language is configurable.');
// Ensure configuration was saved.
$this->assertArrayNotHasKey('language-url', $config->get('negotiation.language_content.enabled'));
$this->assertArrayHasKey('language-session', $config->get('negotiation.language_content.enabled'));
}
/**
* Tests if the language switcher block gets deleted when a language type has been made not configurable.
*/
public function testDisableLanguageSwitcher(): void {
$block_id = 'test_language_block';
// Enable the language switcher block.
$this->drupalPlaceBlock('language_block:' . LanguageInterface::TYPE_CONTENT, ['id' => $block_id]);
// Check if the language switcher block has been created.
$block = Block::load($block_id);
$this->assertNotEmpty($block, 'Language switcher block was created.');
// Make sure language_content is not configurable.
$edit = [
'language_content[configurable]' => FALSE,
];
$this->drupalGet('admin/config/regional/language/detection');
$this->submitForm($edit, 'Save settings');
$this->assertSession()->statusCodeEquals(200);
// Check if the language switcher block has been removed.
$block = Block::load($block_id);
$this->assertNull($block, 'Language switcher block was removed.');
}
}

View File

@@ -0,0 +1,172 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Url;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl;
use Drupal\Tests\BrowserTestBase;
use Symfony\Component\HttpFoundation\Request;
/**
* Tests that URL rewriting works as expected.
*
* @group language
*/
class LanguageUrlRewritingTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['language', 'language_test'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* A user with permissions to administer languages.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create and log in user.
$this->webUser = $this->drupalCreateUser([
'administer languages',
'access administration pages',
]);
$this->drupalLogin($this->webUser);
// Install French language.
$edit = [];
$edit['predefined_langcode'] = 'fr';
$this->drupalGet('admin/config/regional/language/add');
$this->submitForm($edit, 'Add language');
// Enable URL language detection and selection.
$edit = ['language_interface[enabled][language-url]' => 1];
$this->drupalGet('admin/config/regional/language/detection');
$this->submitForm($edit, 'Save settings');
// Check that drupalSettings contains path prefix.
$this->drupalGet('fr/admin/config/regional/language/detection');
$this->assertSession()->responseContains('"pathPrefix":"fr\/"');
}
/**
* Check that non-installed languages are not considered.
*/
public function testUrlRewritingEdgeCases(): void {
// Check URL rewriting with a non-installed language.
$non_existing = new Language(['id' => $this->randomMachineName()]);
$this->checkUrl($non_existing, 'Path language is ignored if language is not installed.');
// Check that URL rewriting is not applied to subrequests.
$this->drupalGet('language_test/subrequest');
$this->assertSession()->pageTextContains($this->webUser->getAccountName());
}
/**
* Check URL rewriting for the given language.
*
* The test is performed with a fixed URL (the default front page) to simply
* check that language prefixes are not added to it and that the prefixed URL
* is actually not working.
*
* @param \Drupal\Core\Language\LanguageInterface $language
* The language object.
* @param string $message
* Message to display in assertion that language prefixes are not added.
*/
private function checkUrl(LanguageInterface $language, $message) {
$options = ['language' => $language, 'script' => ''];
$base_path = trim(base_path(), '/');
$rewritten_path = trim(str_replace($base_path, '', Url::fromRoute('<front>', [], $options)->toString()), '/');
$segments = explode('/', $rewritten_path, 2);
$prefix = $segments[0];
$path = $segments[1] ?? $prefix;
// If the rewritten URL has not a language prefix we pick a random prefix so
// we can always check the prefixed URL.
$prefixes = $this->config('language.negotiation')->get('url.prefixes');
$stored_prefix = $prefixes[$language->getId()] ?? $this->randomMachineName();
$this->assertNotEquals($prefix, $stored_prefix, $message);
$prefix = $stored_prefix;
$this->drupalGet("$prefix/$path");
$this->assertSession()->statusCodeEquals(404);
}
/**
* Check URL rewriting when using a domain name and a non-standard port.
*/
public function testDomainNameNegotiationPort(): void {
global $base_url;
$language_domain = 'example.fr';
// Get the current host URI we're running on.
$base_url_host = parse_url($base_url, PHP_URL_HOST);
$edit = [
'language_negotiation_url_part' => LanguageNegotiationUrl::CONFIG_DOMAIN,
'domain[en]' => $base_url_host,
'domain[fr]' => $language_domain,
];
$this->drupalGet('admin/config/regional/language/detection/url');
$this->submitForm($edit, 'Save configuration');
// Rebuild the container so that the new language gets picked up by services
// that hold the list of languages.
$this->rebuildContainer();
// Enable domain configuration.
$this->config('language.negotiation')
->set('url.source', LanguageNegotiationUrl::CONFIG_DOMAIN)
->save();
// Reset static caching.
$this->container->get('language_manager')->reset();
// In case index.php is part of the URLs, we need to adapt the asserted
// URLs as well.
$index_php = str_contains(Url::fromRoute('<front>', [], ['absolute' => TRUE])->toString(), 'index.php');
$request = Request::createFromGlobals();
$server = $request->server->all();
$request = $this->prepareRequestForGenerator(TRUE, ['HTTP_HOST' => $server['HTTP_HOST'] . ':88']);
// Create an absolute French link.
$language = \Drupal::languageManager()->getLanguage('fr');
$url = Url::fromRoute('<front>', [], [
'absolute' => TRUE,
'language' => $language,
])->toString();
$expected = ($index_php ? 'http://example.fr:88/index.php' : 'http://example.fr:88') . rtrim(base_path(), '/') . '/';
$this->assertEquals($expected, $url, 'The right port is used.');
// If we set the port explicitly, it should not be overridden.
$url = Url::fromRoute('<front>', [], [
'absolute' => TRUE,
'language' => $language,
'base_url' => $request->getBaseUrl() . ':90',
])->toString();
$expected = $index_php ? 'http://example.fr:90/index.php' : 'http://example.fr:90' . rtrim(base_path(), '/') . '/';
$this->assertEquals($expected, $url, 'A given port is not overridden.');
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class ConfigurableLanguageJsonAnonTest extends ConfigurableLanguageResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class ConfigurableLanguageJsonBasicAuthTest extends ConfigurableLanguageResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
*/
class ConfigurableLanguageJsonCookieTest extends ConfigurableLanguageResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional\Rest;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Url;
use Drupal\Tests\rest\Functional\EntityResource\ConfigEntityResourceTestBase;
use Drupal\language\Entity\ConfigurableLanguage;
abstract class ConfigurableLanguageResourceTestBase extends ConfigEntityResourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['language'];
/**
* {@inheritdoc}
*/
protected static $entityTypeId = 'configurable_language';
/**
* @var \Drupal\language\ConfigurableLanguageInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
$this->grantPermissionsToTestedRole(['administer languages']);
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
$configurable_language = ConfigurableLanguage::create([
'id' => 'll',
'label' => 'Llama Language',
]);
$configurable_language->save();
return $configurable_language;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
return [
'dependencies' => [],
'direction' => 'ltr',
'id' => 'll',
'label' => 'Llama Language',
'langcode' => 'en',
'locked' => FALSE,
'status' => TRUE,
'uuid' => $this->entity->uuid(),
'weight' => 0,
];
}
/**
* {@inheritdoc}
*/
protected function getExpectedCacheContexts() {
return Cache::mergeContexts(parent::getExpectedCacheContexts(), ['languages:language_interface']);
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
// @todo Update in https://www.drupal.org/node/2300677.
return [];
}
/**
* Tests a GET request for a default config entity, which has a _core key.
*
* @see https://www.drupal.org/node/2915414
*/
public function testGetDefaultConfig(): void {
$this->initAuthentication();
$url = Url::fromUri('base:/entity/configurable_language/en')->setOption('query', ['_format' => static::$format]);
$request_options = $this->getAuthenticationRequestOptions('GET');
$this->provisionEntityResource();
$this->setUpAuthorization('GET');
$response = $this->request('GET', $url, $request_options);
$normalization = $this->serializer->decode((string) $response->getBody(), static::$format);
$this->assertArrayNotHasKey('_core', $normalization);
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class ConfigurableLanguageXmlAnonTest extends ConfigurableLanguageResourceTestBase {
use AnonResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class ConfigurableLanguageXmlBasicAuthTest extends ConfigurableLanguageResourceTestBase {
use BasicAuthResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class ConfigurableLanguageXmlCookieTest extends ConfigurableLanguageResourceTestBase {
use CookieResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class ContentLanguageSettingsJsonAnonTest extends ContentLanguageSettingsResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class ContentLanguageSettingsJsonBasicAuthTest extends ContentLanguageSettingsResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
*/
class ContentLanguageSettingsJsonCookieTest extends ContentLanguageSettingsResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional\Rest;
use Drupal\language\Entity\ContentLanguageSettings;
use Drupal\node\Entity\NodeType;
use Drupal\Tests\rest\Functional\EntityResource\ConfigEntityResourceTestBase;
abstract class ContentLanguageSettingsResourceTestBase extends ConfigEntityResourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['language', 'node'];
/**
* {@inheritdoc}
*/
protected static $entityTypeId = 'language_content_settings';
/**
* @var \Drupal\language\ContentLanguageSettingsInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
$this->grantPermissionsToTestedRole(['administer languages']);
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a "Camelids" node type.
$camelids = NodeType::create([
'name' => 'Camelids',
'type' => 'camelids',
]);
$camelids->save();
$entity = ContentLanguageSettings::create([
'target_entity_type_id' => 'node',
'target_bundle' => 'camelids',
]);
$entity->setDefaultLangcode('site_default')
->save();
return $entity;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
return [
'default_langcode' => 'site_default',
'dependencies' => [
'config' => [
'node.type.camelids',
],
],
'id' => 'node.camelids',
'langcode' => 'en',
'language_alterable' => FALSE,
'status' => TRUE,
'target_bundle' => 'camelids',
'target_entity_type_id' => 'node',
'uuid' => $this->entity->uuid(),
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
// @todo Update in https://www.drupal.org/node/2300677.
return [];
}
/**
* {@inheritdoc}
*/
protected function getExpectedCacheContexts() {
return [
'languages:language_interface',
'user.permissions',
];
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class ContentLanguageSettingsXmlAnonTest extends ContentLanguageSettingsResourceTestBase {
use AnonResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class ContentLanguageSettingsXmlBasicAuthTest extends ContentLanguageSettingsResourceTestBase {
use BasicAuthResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class ContentLanguageSettingsXmlCookieTest extends ContentLanguageSettingsResourceTestBase {
use CookieResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@@ -0,0 +1,101 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel\Condition;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests the language condition plugin.
*
* @group language
*/
class LanguageConditionTest extends KernelTestBase {
/**
* The condition plugin manager.
*
* @var \Drupal\Core\Condition\ConditionManager
*/
protected $manager;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['system', 'language'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installConfig(['language']);
// Setup Italian.
ConfigurableLanguage::createFromLangcode('it')->save();
$this->manager = $this->container->get('plugin.manager.condition');
}
/**
* Tests the language condition.
*/
public function testConditions(): void {
// Grab the language condition and configure it to check the content
// language.
$language = \Drupal::languageManager()->getLanguage('en');
$condition = $this->manager->createInstance('language')
->setConfig('langcodes', ['en' => 'en', 'it' => 'it'])
->setContextValue('language', $language);
$this->assertTrue($condition->execute(), 'Language condition passes as expected.');
// Check for the proper summary.
$this->assertEquals('The language is English, Italian.', $condition->summary());
// Change to Italian only.
$condition->setConfig('langcodes', ['it' => 'it']);
$this->assertFalse($condition->execute(), 'Language condition fails as expected.');
// Check for the proper summary.
$this->assertEquals('The language is Italian.', $condition->summary());
// Negate the condition
$condition->setConfig('negate', TRUE);
$this->assertTrue($condition->execute(), 'Language condition passes as expected.');
// Check for the proper summary.
$this->assertEquals('The language is not Italian.', $condition->summary());
// Change the default language to Italian.
$language = \Drupal::languageManager()->getLanguage('it');
$condition = $this->manager->createInstance('language')
->setConfig('langcodes', ['en' => 'en', 'it' => 'it'])
->setContextValue('language', $language);
$this->assertTrue($condition->execute(), 'Language condition passes as expected.');
// Check for the proper summary.
$this->assertEquals('The language is English, Italian.', $condition->summary());
// Change to Italian only.
$condition->setConfig('langcodes', ['it' => 'it']);
$this->assertTrue($condition->execute(), 'Language condition passes as expected.');
// Check for the proper summary.
$this->assertEquals('The language is Italian.', $condition->summary());
// Negate the condition
$condition->setConfig('negate', TRUE);
$this->assertFalse($condition->execute(), 'Language condition fails as expected.');
// Check for the proper summary.
$this->assertEquals('The language is not Italian.', $condition->summary());
}
}

View File

@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Url;
/**
* Tests the ConfigurableLanguage entity.
*
* @group language
* @coversDefaultClass \Drupal\language\ConfigurableLanguageManager
*/
class ConfigurableLanguageManagerTest extends LanguageTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['user'];
/**
* The language negotiator.
*
* @var \Drupal\language\LanguageNegotiatorInterface
*/
protected $languageNegotiator;
/**
* The language manager.
*
* @var \Drupal\language\ConfigurableLanguageManagerInterface
*/
protected $languageManager;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('user');
$this->languageNegotiator = $this->container->get('language_negotiator');
$this->languageManager = $this->container->get('language_manager');
}
/**
* @covers ::getLanguageSwitchLinks
*/
public function testLanguageSwitchLinks(): void {
$this->languageNegotiator->setCurrentUser($this->prophesize('Drupal\Core\Session\AccountInterface')->reveal());
$this->languageManager->getLanguageSwitchLinks(LanguageInterface::TYPE_INTERFACE, new Url('<current>'));
}
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests the ConfigurableLanguage entity.
*
* @group language
* @see \Drupal\language\Entity\ConfigurableLanguage.
*/
class ConfigurableLanguageTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['language'];
/**
* Tests configurable language name methods.
*/
public function testName(): void {
$name = $this->randomMachineName();
$language_code = $this->randomMachineName(2);
$configurableLanguage = new ConfigurableLanguage(['label' => $name, 'id' => $language_code], 'configurable_language');
$this->assertEquals($name, $configurableLanguage->getName());
$this->assertEquals('Test language', $configurableLanguage->setName('Test language')->getName());
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel;
use Drupal\KernelTests\Core\Config\ConfigEntityValidationTestBase;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests validation of configurable_language entities.
*
* @group language
* @group #slow
*/
class ConfigurableLanguageValidationTest extends ConfigEntityValidationTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['language'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->entity = ConfigurableLanguage::createFromLangcode('fr');
$this->entity->save();
}
}

View File

@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel;
use Drupal\entity_test\Entity\EntityTestBundle;
use Drupal\KernelTests\Core\Config\ConfigEntityValidationTestBase;
use Drupal\language\Entity\ContentLanguageSettings;
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
/**
* Tests validation of content_language_settings entities.
*
* @group language
* @group #slow
*/
class ContentLanguageSettingsValidationTest extends ConfigEntityValidationTestBase {
use ContentTypeCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'entity_test',
'field',
'language',
'node',
'text',
'user',
];
/**
* {@inheritdoc}
*/
protected bool $hasLabel = FALSE;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installConfig('node');
$this->createContentType(['type' => 'alpha']);
$this->createContentType(['type' => 'bravo']);
EntityTestBundle::create(['id' => 'alpha'])->save();
EntityTestBundle::create(['id' => 'bravo'])->save();
$this->entity = ContentLanguageSettings::create([
'target_entity_type_id' => 'node',
'target_bundle' => 'alpha',
]);
$this->entity->save();
}
/**
* Tests that the target bundle of the language content settings is checked.
*/
public function testTargetBundleMustExist(): void {
$this->entity->set('target_bundle', 'superhero');
$this->assertValidationErrors([
'' => "The 'target_bundle' property cannot be changed.",
'target_bundle' => "The 'superhero' bundle does not exist on the 'node' entity type.",
]);
}
/**
* {@inheritdoc}
*/
public function testImmutableProperties(array $valid_values = []): void {
parent::testImmutableProperties([
'target_entity_type_id' => 'entity_test_with_bundle',
'target_bundle' => 'bravo',
]);
}
}

View File

@@ -0,0 +1,151 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel;
use Drupal\Core\Language\LanguageInterface;
use Drupal\language\Entity\ContentLanguageSettings;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests default language code is properly generated for entities.
*
* @group language
*/
class EntityDefaultLanguageTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'language',
'node',
'field',
'text',
'user',
'system',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('user');
// Activate Spanish language, so there are two languages activated.
$language = $this->container->get('entity_type.manager')->getStorage('configurable_language')->create([
'id' => 'es',
'label' => 'Spanish',
]);
$language->save();
// Create a new content type which has Undefined language by default.
$this->createContentType('content_und', LanguageInterface::LANGCODE_NOT_SPECIFIED);
// Create a new content type which has Spanish language by default.
$this->createContentType('content_es', 'es');
}
/**
* Tests that default language code is properly set for new nodes.
*/
public function testEntityTranslationDefaultLanguageViaCode(): void {
// With language module activated, and a content type that is configured to
// have no language by default, a new node of this content type will have
// "und" language code when language is not specified.
$node = $this->createNode('content_und');
$this->assertEquals(LanguageInterface::LANGCODE_NOT_SPECIFIED, $node->langcode->value);
// With language module activated, and a content type that is configured to
// have no language by default, a new node of this content type will have
// "es" language code when language is specified as "es".
$node = $this->createNode('content_und', 'es');
$this->assertEquals('es', $node->langcode->value);
// With language module activated, and a content type that is configured to
// have language "es" by default, a new node of this content type will have
// "es" language code when language is not specified.
$node = $this->createNode('content_es');
$this->assertEquals('es', $node->langcode->value);
// With language module activated, and a content type that is configured to
// have language "es" by default, a new node of this content type will have
// "en" language code when language "en" is specified.
$node = $this->createNode('content_es', 'en');
$this->assertEquals('en', $node->langcode->value);
// Disable language module.
$this->disableModules(['language']);
// With language module disabled, and a content type that is configured to
// have no language specified by default, a new node of this content type
// will have site's default language code when language is not specified.
$node = $this->createNode('content_und');
$this->assertEquals('en', $node->langcode->value);
// With language module disabled, and a content type that is configured to
// have no language specified by default, a new node of this type will have
// "es" language code when language "es" is specified.
$node = $this->createNode('content_und', 'es');
$this->assertEquals('es', $node->langcode->value);
// With language module disabled, and a content type that is configured to
// have language "es" by default, a new node of this type will have site's
// default language code when language is not specified.
$node = $this->createNode('content_es');
$this->assertEquals('en', $node->langcode->value);
// With language module disabled, and a content type that is configured to
// have language "es" by default, a new node of this type will have "en"
// language code when language "en" is specified.
$node = $this->createNode('content_es', 'en');
$this->assertEquals('en', $node->langcode->value);
}
/**
* Creates a new node content type.
*
* @param string $name
* The content type name.
* @param string $langcode
* Default language code of the nodes of this type.
*/
protected function createContentType($name, $langcode) {
$content_type = $this->container->get('entity_type.manager')->getStorage('node_type')->create([
'name' => 'Test ' . $name,
'title_label' => 'Title',
'type' => $name,
'create_body' => FALSE,
]);
$content_type->save();
ContentLanguageSettings::loadByEntityTypeBundle('node', $name)
->setLanguageAlterable(FALSE)
->setDefaultLangcode($langcode)
->save();
}
/**
* Creates a new node of given type and language using Entity API.
*
* @param string $type
* The node content type.
* @param string $langcode
* (optional) Language code to pass to entity create.
*
* @return \Drupal\node\NodeInterface
* The node created.
*/
protected function createNode($type, $langcode = NULL) {
$values = [
'type' => $type,
'title' => $this->randomString(),
];
if (!empty($langcode)) {
$values['langcode'] = $langcode;
}
$node = $this->container->get('entity_type.manager')->getStorage('node')->create($values);
return $node;
}
}

View File

@@ -0,0 +1,143 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel;
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\Core\Routing\RouteObjectInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
use Symfony\Component\Routing\Route;
/**
* Tests the language of entity URLs.
* @group language
*/
class EntityUrlLanguageTest extends LanguageTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['entity_test', 'user'];
/**
* The entity being used for testing.
*
* @var \Drupal\Core\Entity\ContentEntityInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('entity_test');
$this->installEntitySchema('configurable_language');
// In order to reflect the changes for a multilingual site in the container
// we have to rebuild it.
ConfigurableLanguage::createFromLangcode('es')->save();
ConfigurableLanguage::createFromLangcode('fr')->save();
$config = $this->config('language.negotiation');
$config->set('url.prefixes', ['en' => 'en', 'es' => 'es', 'fr' => 'fr'])
->save();
\Drupal::service('kernel')->rebuildContainer();
$this->createTranslatableEntity();
}
/**
* Ensures that entity URLs in a language have the right language prefix.
*/
public function testEntityUrlLanguage(): void {
$this->assertStringContainsString('/en/entity_test/' . $this->entity->id(), $this->entity->toUrl()->toString());
$this->assertStringContainsString('/es/entity_test/' . $this->entity->id(), $this->entity->getTranslation('es')->toUrl()->toString());
$this->assertStringContainsString('/fr/entity_test/' . $this->entity->id(), $this->entity->getTranslation('fr')->toUrl()->toString());
}
/**
* 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(): void {
// 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 a 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->assertStringContainsString('/en/entity_test/' . $this->entity->id() . '?' . LanguageNegotiationContentEntity::QUERY_PARAMETER . '=en', $this->entity->toUrl('canonical')->toString());
$this->assertStringContainsString('/en/entity_test/' . $this->entity->id() . '?' . LanguageNegotiationContentEntity::QUERY_PARAMETER . '=es', $this->entity->getTranslation('es')->toUrl('canonical')->toString());
$this->assertStringContainsString('/en/entity_test/' . $this->entity->id() . '?' . LanguageNegotiationContentEntity::QUERY_PARAMETER . '=fr', $this->entity->getTranslation('fr')->toUrl('canonical')->toString());
// 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));
$request->setSession(new Session(new MockArraySessionStorage()));
$this->container->get('request_stack')->push($request);
}
}

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests \Drupal\language\Config\LanguageConfigFactoryOverride.
*
* @group language
*/
class LanguageConfigFactoryOverrideTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['system', 'language'];
/**
* Tests language.config_factory_override service has the default language.
*/
public function testLanguageConfigFactoryOverride(): void {
$this->installConfig('system');
$this->installConfig('language');
/** @var \Drupal\language\Config\LanguageConfigFactoryOverride $config_factory_override */
$config_factory_override = \Drupal::service('language.config_factory_override');
$this->assertEquals('en', $config_factory_override->getLanguage()->getId());
ConfigurableLanguage::createFromLangcode('de')->save();
// Invalidate the container.
$this->config('system.site')->set('default_langcode', 'de')->save();
$this->container->get('kernel')->rebuildContainer();
$config_factory_override = \Drupal::service('language.config_factory_override');
$this->assertEquals('de', $config_factory_override->getLanguage()->getId());
}
}

View File

@@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel;
use Drupal\Core\Config\ConfigCollectionEvents;
use Drupal\language\Config\LanguageConfigOverrideEvents;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\KernelTests\KernelTestBase;
// cspell:ignore deutsch
/**
* Ensures the language config overrides can be installed.
*
* @group language
*/
class LanguageConfigOverrideInstallTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['language', 'config_events_test', 'language_events_test'];
/**
* Tests the configuration events are not fired during install of overrides.
*/
public function testLanguageConfigOverrideInstall(): void {
$this->installConfig(['language']);
ConfigurableLanguage::createFromLangcode('de')->save();
// Need to enable test module after creating the language otherwise saving
// the language will install the configuration.
$this->enableModules(['language_config_override_test']);
\Drupal::state()->set('config_events_test.event', FALSE);
\Drupal::state()->set('language_events_test.all_events', []);
$this->installConfig(['language_config_override_test']);
// Ensure the save-in-collection event is triggered when saving data in
// config collections during an install.
$event_recorder = \Drupal::state()->get('config_events_test.event', FALSE);
$this->assertSame([
'event_name' => ConfigCollectionEvents::SAVE_IN_COLLECTION,
'current_config_data' => ['name' => 'Deutsch'],
'original_config_data' => [],
'raw_config_data' => ['name' => 'Deutsch'],
], $event_recorder);
$config = \Drupal::service('language.config_factory_override')->getOverride('de', 'language_config_override_test.settings');
$this->assertEquals('Deutsch', $config->get('name'));
// Ensure the save override event is triggered when saving overrides during
// an install.
$event_recorder = \Drupal::state()->get('language_events_test.all_events', []);
$this->assertArrayHasKey(LanguageConfigOverrideEvents::SAVE_OVERRIDE, $event_recorder);
$this->assertArrayHasKey('language_config_override_test.settings', $event_recorder[LanguageConfigOverrideEvents::SAVE_OVERRIDE]);
$this->assertSame([
'event_name' => LanguageConfigOverrideEvents::SAVE_OVERRIDE,
'current_override_data' => ['name' => 'Deutsch'],
'original_override_data' => [],
], $event_recorder['language.save_override']['language_config_override_test.settings'][0]);
// Test events during uninstall.
\Drupal::state()->set('config_events_test.all_events', []);
\Drupal::state()->set('language_events_test.all_events', []);
$this->container->get('module_installer')->uninstall(['language_config_override_test']);
// Ensure the delete-in-collection event is triggered when deleting data in
// config collections during an uninstall.
$event_recorder = \Drupal::state()->get('config_events_test.all_events', []);
$this->assertArrayHasKey(ConfigCollectionEvents::DELETE_IN_COLLECTION, $event_recorder);
$this->assertArrayHasKey('language_config_override_test.settings', $event_recorder[ConfigCollectionEvents::DELETE_IN_COLLECTION]);
$this->assertSame([
'event_name' => ConfigCollectionEvents::DELETE_IN_COLLECTION,
'current_config_data' => [],
'original_config_data' => ['name' => 'Deutsch'],
'raw_config_data' => [],
], $event_recorder[ConfigCollectionEvents::DELETE_IN_COLLECTION]['language_config_override_test.settings'][0]);
// Ensure the delete override event is triggered when deleting overrides
// during an uninstall.
$event_recorder = \Drupal::state()->get('language_events_test.all_events', []);
$this->assertArrayHasKey(LanguageConfigOverrideEvents::DELETE_OVERRIDE, $event_recorder);
$this->assertArrayHasKey('language_config_override_test.settings', $event_recorder[LanguageConfigOverrideEvents::DELETE_OVERRIDE]);
$this->assertSame([
'event_name' => LanguageConfigOverrideEvents::DELETE_OVERRIDE,
'current_override_data' => [],
'original_override_data' => ['name' => 'Deutsch'],
], $event_recorder['language.delete_override']['language_config_override_test.settings'][0]);
}
}

View File

@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\language\Exception\DeleteDefaultLanguageException;
/**
* Tests that a language object can be injected.
*
* @group language
*/
class LanguageDependencyInjectionTest extends LanguageTestBase {
/**
* Tests dependency injected languages against a new Language object.
*
* @see \Drupal\Core\Language\LanguageInterface
*/
public function testDependencyInjectedNewLanguage(): void {
$expected = $this->languageManager->getDefaultLanguage();
$result = $this->languageManager->getCurrentLanguage();
$this->assertSame($expected, $result);
}
/**
* Tests dependency injected Language object.
*
* @see \Drupal\Core\Language\Language
*/
public function testDependencyInjectedNewDefaultLanguage(): void {
$default_language = ConfigurableLanguage::load(\Drupal::languageManager()->getDefaultLanguage()->getId());
// Change the language default object to different values.
$fr = ConfigurableLanguage::createFromLangcode('fr');
$fr->save();
$this->config('system.site')->set('default_langcode', 'fr')->save();
// The language system creates a Language object which contains the
// same properties as the new default language object.
$result = \Drupal::languageManager()->getCurrentLanguage();
$this->assertSame('fr', $result->getId());
// Delete the language to check that we fallback to the default.
try {
$fr->delete();
$this->fail('Expected DeleteDefaultLanguageException thrown.');
}
catch (DeleteDefaultLanguageException $e) {
// Expected exception; just continue testing.
}
// Re-save the previous default language and the delete should work.
$this->config('system.site')->set('default_langcode', $default_language->getId())->save();
$fr->delete();
$result = \Drupal::languageManager()->getCurrentLanguage();
$this->assertSame($default_language->getId(), $result->getId());
}
}

View File

@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel;
use Drupal\Core\Language\LanguageInterface;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests the language fallback behavior.
*
* @group language
*/
class LanguageFallbackTest extends LanguageTestBase {
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$i = 0;
foreach (['af', 'am', 'ar'] as $langcode) {
$language = ConfigurableLanguage::createFromLangcode($langcode);
$language->set('weight', $i--);
$language->save();
}
}
/**
* Tests language fallback candidates.
*/
public function testCandidates(): void {
$language_list = $this->languageManager->getLanguages();
$expected = array_keys($language_list + [LanguageInterface::LANGCODE_NOT_SPECIFIED => NULL]);
// Check that language fallback candidates by default are all the available
// languages sorted by weight.
$candidates = $this->languageManager->getFallbackCandidates();
$this->assertEquals($expected, array_values($candidates), 'Language fallback candidates are properly returned.');
// Check that candidates are alterable.
$this->state->set('language_test.fallback_alter.candidates', TRUE);
$expected = array_slice($expected, 0, count($expected) - 1);
$candidates = $this->languageManager->getFallbackCandidates();
$this->assertEquals($expected, array_values($candidates), 'Language fallback candidates are alterable.');
// Check that candidates are alterable for specific operations.
$this->state->set('language_test.fallback_alter.candidates', FALSE);
$this->state->set('language_test.fallback_operation_alter.candidates', TRUE);
$expected[] = LanguageInterface::LANGCODE_NOT_SPECIFIED;
$expected[] = LanguageInterface::LANGCODE_NOT_APPLICABLE;
$candidates = $this->languageManager->getFallbackCandidates(['operation' => 'test']);
$this->assertEquals($expected, array_values($candidates), 'Language fallback candidates are alterable for specific operations.');
// Check that when the site is monolingual no language fallback is applied.
/** @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface $configurable_language_storage */
$configurable_language_storage = $this->container->get('entity_type.manager')->getStorage('configurable_language');
foreach ($language_list as $langcode => $language) {
if (!$language->isDefault()) {
$configurable_language_storage->load($langcode)->delete();
}
}
$candidates = $this->languageManager->getFallbackCandidates();
$this->assertEquals([LanguageInterface::LANGCODE_DEFAULT], array_values($candidates), 'Language fallback is not applied when the Language module is not enabled.');
}
}

View File

@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Logger\LoggerChannelFactory;
use Drupal\KernelTests\KernelTestBase;
use Symfony\Component\ErrorHandler\BufferingLogger;
/**
* Tests PluginNotFoundException.
*
* @group language
*/
class LanguageNegotiatorPluginTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['language', 'user'];
/**
* Tests for PluginNotFoundException.
*/
public function testLanguageNegotiatorNoPlugin(): void {
$logger = new BufferingLogger();
$logger_factory = $this->createMock(LoggerChannelFactory::class);
$logger_factory->expects($this->once())
->method('get')
->with('language')
->willReturn($logger);
$this->container->set('logger.factory', $logger_factory);
$this->installEntitySchema('user');
// Test unavailable plugin.
$config = $this->config('language.types');
$config->set('configurable', [LanguageInterface::TYPE_URL]);
$config->set('negotiation.language_url.enabled', [
self::CLASS => -3,
]);
$config->save();
$languageNegotiator = $this->container->get('language_negotiator');
$languageNegotiator->setCurrentUser($this->prophesize('Drupal\Core\Session\AccountInterface')->reveal());
try {
$languageNegotiator->initializeType(LanguageInterface::TYPE_URL);
}
catch (PluginNotFoundException $exception) {
$this->fail('Plugin not found exception unhandled.');
}
$log_message = $logger->cleanLogs()[0];
$this->assertEquals('error', $log_message[0]);
$this->assertStringContainsString('The "Drupal\Tests\language\Kernel\LanguageNegotiatorPluginTest" plugin does not exist.', $log_message[1]);
}
}

View File

@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests the language select widget.
*
* @group language
*/
class LanguageSelectWidgetTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'entity_test',
'language',
'user',
'system',
];
/**
* The entity form display.
*
* @var \Drupal\Core\Entity\Entity\EntityFormDisplay
*/
protected $entityFormDisplay;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('entity_test');
$this->installEntitySchema('user');
$storage = $this->container->get('entity_type.manager')->getStorage('entity_form_display');
$this->entityFormDisplay = $storage->create([
'targetEntityType' => 'entity_test',
'bundle' => 'entity_test',
'mode' => 'default',
'status' => TRUE,
]);
}
/**
* Tests the widget with the locked languages.
*/
public function testWithIncludedLockedLanguage(): void {
$this->entityFormDisplay->setComponent('langcode', [
'type' => 'language_select',
])->save();
$entity = EntityTest::create(['name' => $this->randomString()]);
$form = $this->container->get('entity.form_builder')->getForm($entity);
$options = array_keys($form['langcode']['widget'][0]['value']['#options']);
$this->assertSame(['en', 'und', 'zxx'], $options);
}
/**
* Tests the widget without the locked languages.
*/
public function testWithoutIncludedLockedLanguage(): void {
$this->entityFormDisplay->setComponent('langcode', [
'type' => 'language_select',
'settings' => ['include_locked' => FALSE],
])->save();
$entity = EntityTest::create(['name' => $this->randomString()]);
$form = $this->container->get('entity.form_builder')->getForm($entity);
$options = array_keys($form['langcode']['widget'][0]['value']['#options']);
$this->assertSame(['en'], $options);
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel;
use Drupal\KernelTests\KernelTestBase;
/**
* Test for dependency injected language object.
*/
abstract class LanguageTestBase extends KernelTestBase {
protected static $modules = ['system', 'language', 'language_test'];
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* The state storage service.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installConfig(['language']);
$this->state = $this->container->get('state');
// Ensure we are building a new Language object for each test.
$this->languageManager = $this->container->get('language_manager');
$this->languageManager->reset();
}
}

View File

@@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel\Migrate\d6;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* Tests the default language variable migration.
*
* @group migrate_drupal_6
*/
class MigrateDefaultLanguageTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['language'];
/**
* Tests language_default migration with an existing language.
*/
public function testMigrationWithExistingLanguage(): void {
$this->setDefaultLanguage('fr');
$this->startCollectingMessages();
$this->executeMigrations(['language', 'default_language']);
// Tests the language is loaded and is the default language.
$default_language = ConfigurableLanguage::load('fr');
$this->assertNotNull($default_language);
$this->assertSame('fr', $this->config('system.site')->get('default_langcode'));
}
/**
* Tests language_default migration with a non-existing language.
*/
public function testMigrationWithNonExistentLanguage(): void {
$this->setDefaultLanguage('tv');
$this->startCollectingMessages();
$this->executeMigrations(['language', 'default_language']);
// Tests the migration log contains an error message.
$messages = $this->migration->getIdMap()->getMessages();
$count = 0;
foreach ($messages as $message) {
$count++;
$this->assertSame("The language 'tv' does not exist on this site.", $message->message);
$this->assertSame(MigrationInterface::MESSAGE_ERROR, (int) $message->level);
}
$this->assertSame(1, $count);
}
/**
* Tests language_default migration with unset default language variable.
*/
public function testMigrationWithUnsetVariable(): void {
// Delete the language_default variable.
$this->sourceDatabase->delete('variable')
->condition('name', 'language_default')
->execute();
$this->startCollectingMessages();
$this->executeMigrations(['language', 'default_language']);
$messages = $this->migration->getIdMap()->getMessages()->fetchAll();
// Make sure there's no migration exceptions.
$this->assertEmpty($messages);
// Make sure the default langcode is 'en', as it was the default on D6 & D7.
$this->assertSame('en', $this->config('system.site')->get('default_langcode'));
}
/**
* Helper method to test the migration.
*
* @param string $langcode
* The langcode of the default language.
*/
protected function setDefaultLanguage($langcode) {
// The default language of the test fixture is English. Change it to
// something else before migrating, to be sure that the source site
// default language is migrated.
$value = 'O:8:"stdClass":11:{s:8:"language";s:2:"' . $langcode . '";s:4:"name";s:6:"French";s:6:"native";s:6:"French";s:9:"direction";s:1:"0";s:7:"enabled";i:1;s:7:"plurals";s:1:"0";s:7:"formula";s:0:"";s:6:"domain";s:0:"";s:6:"prefix";s:0:"";s:6:"weight";s:1:"0";s:10:"javascript";s:0:"";}';
$this->sourceDatabase->update('variable')
->fields(['value' => $value])
->condition('name', 'language_default')
->execute();
}
}

View File

@@ -0,0 +1,97 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel\Migrate\d6;
use Drupal\language\Entity\ContentLanguageSettings;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* Tests migration of language content comment settings.
*
* @group migrate_drupal_6
*/
class MigrateLanguageContentCommentSettingsTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'comment',
'language',
'content_translation',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installConfig(['comment']);
$this->executeMigrations([
'language',
'd6_comment_type',
'd6_language_content_comment_settings',
]);
}
/**
* Tests migration of comment content language settings.
*/
public function testLanguageCommentSettings(): void {
// Article and Employee content type have multilingual settings of 'Enabled,
// with Translation'. Assert that comments are not translatable and the
// default language is 'current_interface'.
$config = ContentLanguageSettings::loadByEntityTypeBundle('comment', 'comment_node_article');
$this->assertSame('comment', $config->getTargetEntityTypeId());
$this->assertSame('comment_node_article', $config->getTargetBundle());
$this->assertSame('current_interface', $config->getDefaultLangcode());
$this->assertTrue($config->isLanguageAlterable());
$third_party_settings = [
'content_translation' => [
'enabled' => FALSE,
],
];
$this->assertSame($third_party_settings, $config->get('third_party_settings'));
$config = ContentLanguageSettings::loadByEntityTypeBundle('comment', 'comment_node_employee');
$this->assertSame('comment', $config->getTargetEntityTypeId());
$this->assertSame('comment_node_employee', $config->getTargetBundle());
$this->assertSame('current_interface', $config->getDefaultLangcode());
$this->assertTrue($config->isLanguageAlterable());
$this->assertSame($third_party_settings, $config->get('third_party_settings'));
// Sponsor content type has multilingual settings of 'Enabled'. Assert that
// comments are not translatable and the default language is
// 'current_interface'.
$config = ContentLanguageSettings::loadByEntityTypeBundle('comment', 'comment_node_sponsor');
$this->assertSame('comment', $config->getTargetEntityTypeId());
$this->assertSame('comment_node_sponsor', $config->getTargetBundle());
$this->assertSame('current_interface', $config->getDefaultLangcode());
$this->assertTrue($config->isLanguageAlterable());
$this->assertSame($third_party_settings, $config->get('third_party_settings'));
// Assert that non-translatable content is not translatable and the default
// language is 'site_default.
$not_translatable = [
'comment_node_company',
'comment_node_event',
'comment_node_page',
'comment_node_story',
'comment_node_test_event',
'comment_node_test_page',
'comment_node_test_planet',
'comment_node_test_story',
'comment_forum',
'comment_node_event',
];
foreach ($not_translatable as $bundle) {
$config = ContentLanguageSettings::loadByEntityTypeBundle('comment', $bundle);
$this->assertTrue($config->isDefaultConfiguration());
$this->assertFalse($config->isLanguageAlterable());
$this->assertSame('site_default', $config->getDefaultLangcode(), "Default language is not 'site_default' for comment bundle $bundle");
}
}
}

View File

@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel\Migrate\d6;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\language\Entity\ContentLanguageSettings;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* Tests migration of the ability to translate menu content.
*
* @group migrate_drupal_6
*/
class MigrateLanguageContentMenuSettingsTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'language',
'content_translation',
'menu_link_content',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create some languages.
ConfigurableLanguage::createFromLangcode('en')->save();
ConfigurableLanguage::createFromLangcode('fr')->save();
$this->executeMigrations([
'language',
'd6_language_content_menu_settings',
]);
}
/**
* Tests migration of menu translation ability.
*/
public function testLanguageMenuContent(): void {
$config = ContentLanguageSettings::load('menu_link_content.menu_link_content');
$this->assertInstanceOf(ContentLanguageSettings::class, $config);
$this->assertSame('menu_link_content', $config->getTargetEntityTypeId());
$this->assertSame('menu_link_content', $config->getTargetBundle());
$this->assertSame(LanguageInterface::LANGCODE_SITE_DEFAULT, $config->getDefaultLangcode());
$this->assertTrue($config->isLanguageAlterable());
// Test that menus are not alterable when the i18nmenu is not enabled.
$this->sourceDatabase->update('system')
->fields(['status' => 0])
->condition('name', 'i18nmenu')
->execute();
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
$migration = $this->getMigration('d6_language_content_menu_settings');
// Indicate we're rerunning a migration that's already run.
$migration->getIdMap()->prepareUpdate();
$this->executeMigration($migration);
$config = ContentLanguageSettings::load('menu_link_content.menu_link_content');
$this->assertInstanceOf(ContentLanguageSettings::class, $config);
$this->assertSame('menu_link_content', $config->getTargetEntityTypeId());
$this->assertSame('menu_link_content', $config->getTargetBundle());
$this->assertSame(LanguageInterface::LANGCODE_SITE_DEFAULT, $config->getDefaultLangcode());
$this->assertFalse($config->isLanguageAlterable());
}
}

View File

@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel\Migrate\d6;
use Drupal\language\Entity\ContentLanguageSettings;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* Tests the migration of language-related settings.
*
* Settings tested include language content setting variables,
* language_content_type_$type, i18n_node_options_* and i18n_lock_node_*.
*
* @group migrate_drupal_6
*/
class MigrateLanguageContentSettingsTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'node',
'text',
'language',
'content_translation',
'menu_ui',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installConfig(['node']);
$this->installEntitySchema('node');
$this->executeMigrations([
'language',
'd6_node_type',
'd6_language_content_settings',
]);
}
/**
* Tests migration of content language settings.
*/
public function testLanguageContent(): void {
// Assert that a translatable content is still translatable.
$config = $this->config('language.content_settings.node.article');
$this->assertSame($config->get('target_entity_type_id'), 'node');
$this->assertSame($config->get('target_bundle'), 'article');
$this->assertSame($config->get('default_langcode'), 'current_interface');
$this->assertTrue($config->get('third_party_settings.content_translation.enabled'));
// Assert that a non-translatable content is not translatable.
$config = ContentLanguageSettings::loadByEntityTypeBundle('node', 'company');
$this->assertTrue($config->isDefaultConfiguration());
$this->assertFalse($config->isLanguageAlterable());
$this->assertSame($config->getDefaultLangcode(), 'site_default');
// Assert that a we can assign a language when there is no language lock.
$config = ContentLanguageSettings::loadByEntityTypeBundle('node', 'employee');
$this->assertSame($config->getDefaultLangcode(), 'current_interface');
$this->assertTrue($config->isLanguageAlterable());
}
}

View File

@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel\Migrate\d6;
use Drupal\language\Entity\ContentLanguageSettings;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* Tests migration of i18ntaxonomy vocabulary settings.
*
* @group migrate_drupal_6
*/
class MigrateLanguageContentTaxonomyVocabularySettingsTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'language',
'content_translation',
'taxonomy',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('taxonomy_term');
$this->executeMigrations([
'language',
'd6_taxonomy_vocabulary',
'd6_language_content_taxonomy_vocabulary_settings',
]);
}
/**
* Tests migration of 18ntaxonomy vocabulary settings.
*/
public function testLanguageContentTaxonomy(): void {
$target_entity = 'taxonomy_term';
// Per Language.
$this->assertLanguageContentSettings($target_entity, 'vocabulary_1_i_0_', LanguageInterface::LANGCODE_SITE_DEFAULT, TRUE, ['enabled' => FALSE]);
// Set language to vocabulary.
$this->assertLanguageContentSettings($target_entity, 'vocabulary_2_i_1_', 'fr', FALSE, ['enabled' => FALSE]);
// Localize terms.
$this->assertLanguageContentSettings($target_entity, 'vocabulary_3_i_2_', LanguageInterface::LANGCODE_SITE_DEFAULT, TRUE, ['enabled' => FALSE]);
// None translation enabled.
$this->assertLanguageContentSettings($target_entity, 'vocabulary_name_much_longer_th', LanguageInterface::LANGCODE_SITE_DEFAULT, TRUE, ['enabled' => TRUE]);
$this->assertLanguageContentSettings($target_entity, 'tags', LanguageInterface::LANGCODE_SITE_DEFAULT, FALSE, ['enabled' => FALSE]);
$this->assertLanguageContentSettings($target_entity, 'forums', LanguageInterface::LANGCODE_SITE_DEFAULT, FALSE, ['enabled' => FALSE]);
$this->assertLanguageContentSettings($target_entity, 'type', LanguageInterface::LANGCODE_SITE_DEFAULT, FALSE, ['enabled' => FALSE]);
}
/**
* Asserts a content language settings configuration.
*
* @param string $target_entity
* The expected target entity type.
* @param string $bundle
* The expected bundle.
* @param string $default_langcode
* The default language code.
* @param bool $language_alterable
* The expected state of language alterable.
* @param array $third_party_settings
* The content translation setting.
*
* @internal
*/
public function assertLanguageContentSettings(string $target_entity, string $bundle, string $default_langcode, bool $language_alterable, array $third_party_settings): void {
$config = ContentLanguageSettings::load($target_entity . "." . $bundle);
$this->assertInstanceOf(ContentLanguageSettings::class, $config);
$this->assertSame($target_entity, $config->getTargetEntityTypeId());
$this->assertSame($bundle, $config->getTargetBundle());
$this->assertSame($default_langcode, $config->getDefaultLangcode());
$this->assertSame($language_alterable, $config->isLanguageAlterable());
$this->assertSame($third_party_settings, $config->getThirdPartySettings('content_translation'));
}
}

View File

@@ -0,0 +1,169 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel\Migrate\d6;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* Tests the migration of language negotiation and language types.
*
* @group migrate_drupal_6
*/
class MigrateLanguageNegotiationSettingsTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['language'];
/**
* Tests the migration with LANGUAGE_NEGOTIATION_PATH_DEFAULT.
*/
public function testLanguageNegotiationWithDefaultPathPrefix(): void {
$this->executeMigrations([
'language',
'd6_language_negotiation_settings',
'language_prefixes_and_domains',
'd6_language_types',
]);
$config = $this->config('language.negotiation');
$this->assertSame('language', $config->get('session.parameter'));
$this->assertSame(LanguageNegotiationUrl::CONFIG_PATH_PREFIX, $config->get('url.source'));
$this->assertSame('site_default', $config->get('selected_langcode'));
$expected_prefixes = [
'en' => '',
'fr' => 'fr',
'zu' => 'zu',
];
$this->assertSame($expected_prefixes, $config->get('url.prefixes'));
$config = $this->config('language.types');
$this->assertSame(['language_interface', 'language_content', 'language_url'], $config->get('all'));
$this->assertSame(['language_interface'], $config->get('configurable'));
$this->assertSame(['language-interface' => 0], $config->get('negotiation.language_content.enabled'));
$this->assertSame(['language-url' => 0, 'language-url-fallback' => 1], $config->get('negotiation.language_url.enabled'));
$expected_language_interface = [
'language-url' => 0,
'language-selected' => 1,
];
$this->assertSame($expected_language_interface, $config->get('negotiation.language_interface.enabled'));
}
/**
* Tests the migration with LANGUAGE_NEGOTIATION_NONE.
*/
public function testLanguageNegotiationWithNoNegotiation(): void {
$this->sourceDatabase->update('variable')
->fields(['value' => serialize(0)])
->condition('name', 'language_negotiation')
->execute();
$this->executeMigrations([
'language',
'd6_language_negotiation_settings',
'language_prefixes_and_domains',
'd6_language_types',
]);
$config = $this->config('language.negotiation');
$this->assertSame('language', $config->get('session.parameter'));
$this->assertSame(LanguageNegotiationUrl::CONFIG_PATH_PREFIX, $config->get('url.source'));
$this->assertSame('site_default', $config->get('selected_langcode'));
$config = $this->config('language.types');
$this->assertSame(['language_interface', 'language_content', 'language_url'], $config->get('all'));
$this->assertSame(['language_interface'], $config->get('configurable'));
$this->assertSame(['language-interface' => 0], $config->get('negotiation.language_content.enabled'));
$this->assertSame(['language-url' => 0, 'language-url-fallback' => 1], $config->get('negotiation.language_url.enabled'));
$expected_language_interface = [
'language-selected' => 0,
];
$this->assertSame($expected_language_interface, $config->get('negotiation.language_interface.enabled'));
}
/**
* Tests the migration with LANGUAGE_NEGOTIATION_PATH.
*/
public function testLanguageNegotiationWithPathPrefix(): void {
$this->sourceDatabase->update('variable')
->fields(['value' => serialize(2)])
->condition('name', 'language_negotiation')
->execute();
$this->executeMigrations([
'language',
'd6_language_negotiation_settings',
'language_prefixes_and_domains',
'd6_language_types',
]);
$config = $this->config('language.negotiation');
$this->assertSame('language', $config->get('session.parameter'));
$this->assertSame(LanguageNegotiationUrl::CONFIG_PATH_PREFIX, $config->get('url.source'));
$this->assertSame('site_default', $config->get('selected_langcode'));
$expected_prefixes = [
'en' => '',
'fr' => 'fr',
'zu' => 'zu',
];
$this->assertSame($expected_prefixes, $config->get('url.prefixes'));
$config = $this->config('language.types');
$this->assertSame(['language_interface', 'language_content', 'language_url'], $config->get('all'));
$this->assertSame(['language_interface'], $config->get('configurable'));
$this->assertSame(['language-interface' => 0], $config->get('negotiation.language_content.enabled'));
$this->assertSame(['language-url' => 0, 'language-url-fallback' => 1], $config->get('negotiation.language_url.enabled'));
$expected_language_interface = [
'language-url' => 0,
'language-user' => 1,
'language-browser' => 2,
'language-selected' => 3,
];
$this->assertSame($expected_language_interface, $config->get('negotiation.language_interface.enabled'));
}
/**
* Tests the migration with LANGUAGE_NEGOTIATION_DOMAIN.
*/
public function testLanguageNegotiationWithDomain(): void {
$this->sourceDatabase->update('variable')
->fields(['value' => serialize(3)])
->condition('name', 'language_negotiation')
->execute();
$this->executeMigrations([
'language',
'd6_language_negotiation_settings',
'language_prefixes_and_domains',
'd6_language_types',
]);
global $base_url;
$config = $this->config('language.negotiation');
$this->assertSame('language', $config->get('session.parameter'));
$this->assertSame(LanguageNegotiationUrl::CONFIG_DOMAIN, $config->get('url.source'));
$this->assertSame('site_default', $config->get('selected_langcode'));
$expected_domains = [
'en' => parse_url($base_url, PHP_URL_HOST),
'fr' => 'fr.drupal.org',
'zu' => 'zu.drupal.org',
];
$this->assertSame($expected_domains, $config->get('url.domains'));
$config = $this->config('language.types');
$this->assertSame(['language_interface', 'language_content', 'language_url'], $config->get('all'));
$this->assertSame(['language_interface'], $config->get('configurable'));
$this->assertSame(['language-interface' => 0], $config->get('negotiation.language_content.enabled'));
$this->assertSame(['language-url' => 0, 'language-url-fallback' => 1], $config->get('negotiation.language_url.enabled'));
$expected_language_interface = [
'language-url' => 0,
'language-selected' => 1,
];
$this->assertSame($expected_language_interface, $config->get('negotiation.language_interface.enabled'));
}
}

View File

@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel\Migrate\d6;
use Drupal\language\ConfigurableLanguageInterface;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* @group migrate_drupal_6
*/
class MigrateLanguageTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['language'];
/**
* Asserts various properties of a configurable language entity.
*
* @param string $id
* The language ID.
* @param string $label
* The language name.
* @param string $direction
* (optional) The language's direction (one of the DIRECTION_* constants in
* ConfigurableLanguageInterface). Defaults to LTR.
* @param int $weight
* (optional) The weight of the language. Defaults to 0.
*
* @internal
*/
protected function assertLanguage(string $id, string $label, string $direction = ConfigurableLanguageInterface::DIRECTION_LTR, int $weight = 0): void {
/** @var \Drupal\language\ConfigurableLanguageInterface $language */
$language = ConfigurableLanguage::load($id);
$this->assertInstanceOf(ConfigurableLanguageInterface::class, $language);
$this->assertSame($label, $language->label());
$this->assertSame($direction, $language->getDirection());
$this->assertSame(0, $language->getWeight());
$this->assertFalse($language->isLocked());
}
/**
* Tests migration of Drupal 6 languages to configurable language entities.
*/
public function testLanguageMigration(): void {
$this->executeMigration('language');
$this->assertLanguage('en', 'English');
$this->assertLanguage('fr', 'French');
}
}

View File

@@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel\Migrate\d7;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Tests the default language variable migration.
*
* @group migrate_drupal_7
*/
class MigrateDefaultLanguageTest extends MigrateDrupal7TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['language'];
/**
* Tests language_default migration with a non-existing language.
*/
public function testMigrationWithExistingLanguage(): void {
$this->setDefaultLanguage('is');
$this->startCollectingMessages();
$this->executeMigrations(['language', 'default_language']);
// Tests the language is loaded and is the default language.
$default_language = ConfigurableLanguage::load('is');
$this->assertNotNull($default_language);
$this->assertSame('is', $this->config('system.site')->get('default_langcode'));
}
/**
* Tests language_default migration with a non-existing language.
*/
public function testMigrationWithNonExistentLanguage(): void {
$this->setDefaultLanguage('tv');
$this->startCollectingMessages();
$this->executeMigrations(['language', 'default_language']);
// Tests the migration log contains an error message.
$messages = $this->migration->getIdMap()->getMessages();
$count = 0;
foreach ($messages as $message) {
$count++;
$this->assertSame("The language 'tv' does not exist on this site.", $message->message);
$this->assertSame(MigrationInterface::MESSAGE_ERROR, (int) $message->level);
}
$this->assertSame(1, $count);
}
/**
* Tests language_default migration with unset default language variable.
*/
public function testMigrationWithUnsetVariable(): void {
// Delete the language_default variable.
$this->sourceDatabase->delete('variable')
->condition('name', 'language_default')
->execute();
$this->startCollectingMessages();
$this->executeMigrations(['language', 'default_language']);
$messages = $this->migration->getIdMap()->getMessages()->fetchAll();
// Make sure there's no migration exceptions.
$this->assertEmpty($messages);
// Make sure the default langcode is 'en', as it was the default on D6 & D7.
$this->assertSame('en', $this->config('system.site')->get('default_langcode'));
}
/**
* Helper method to test the migration.
*
* @param string $langcode
* The langcode of the default language.
*/
protected function setDefaultLanguage($langcode) {
// The default language of the test fixture is English. Change it to
// something else before migrating, to be sure that the source site
// default language is migrated.
$value = 'O:8:"stdClass":11:{s:8:"language";s:2:"' . $langcode . '";s:4:"name";s:6:"French";s:6:"native";s:6:"French";s:9:"direction";s:1:"0";s:7:"enabled";i:1;s:7:"plurals";s:1:"0";s:7:"formula";s:0:"";s:6:"domain";s:0:"";s:6:"prefix";s:0:"";s:6:"weight";s:1:"0";s:10:"javascript";s:0:"";}';
$this->sourceDatabase->update('variable')
->fields(['value' => $value])
->condition('name', 'language_default')
->execute();
}
}

View File

@@ -0,0 +1,134 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel\Migrate\d7;
use Drupal\Core\Database\Database;
use Drupal\KernelTests\KernelTestBase;
use Drupal\language\Entity\ContentLanguageSettings;
use Drupal\Tests\migrate\Kernel\MigrateDumpAlterInterface;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Tests language content comment settings migration with no entity translation.
*
* @group migrate_drupal_7
*/
class MigrateLanguageContentCommentSettingsNoEntityTranslationTest extends MigrateDrupal7TestBase implements MigrateDumpAlterInterface {
/**
* {@inheritdoc}
*/
protected static $modules = [
'comment',
'content_translation',
'language',
'node',
'text',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->startCollectingMessages();
$this->migrateCommentTypes();
$this->executeMigrations([
'language',
'd7_language_content_comment_settings',
]);
}
/**
* {@inheritdoc}
*/
public static function migrateDumpAlter(KernelTestBase $test) {
// Disable comment entity translation.
$db = Database::getConnection('default', 'migrate');
$db->update('variable')
->condition('name', 'entity_translation_entity_types')
->fields([
'value' => 'a:4:{s:4:"node";s:4:"node";s:13:"taxonomy_term";i:0;s:7:"comment";i:0;s:4:"user";i:0;}',
])
->execute();
}
/**
* Tests migration of content language settings.
*/
public function testLanguageCommentSettings(): void {
// Confirm there is no message about a missing bundle.
$this->assertEmpty($this->migrateMessages, $this->migrateMessages['error'][0] ?? '');
// Article and Blog content type have multilingual settings of 'Enabled,
// with Translation'. Assert that comments are translatable and the default
// language is 'current_interface'.
$config = ContentLanguageSettings::loadByEntityTypeBundle('comment', 'comment_node_article');
$this->assertSame('comment', $config->getTargetEntityTypeId());
$this->assertSame('comment_node_article', $config->getTargetBundle());
$this->assertSame('current_interface', $config->getDefaultLangcode());
$this->assertTrue($config->isLanguageAlterable());
$third_party_settings = [
'content_translation' => [
'enabled' => FALSE,
],
];
$this->assertSame($third_party_settings, $config->get('third_party_settings'));
$config = ContentLanguageSettings::loadByEntityTypeBundle('comment', 'comment_node_blog');
$this->assertSame('comment', $config->getTargetEntityTypeId());
$this->assertSame('comment_node_blog', $config->getTargetBundle());
$this->assertSame('current_interface', $config->getDefaultLangcode());
$this->assertTrue($config->isLanguageAlterable());
$this->assertSame($third_party_settings, $config->get('third_party_settings'));
// Page content type has multilingual settings of 'Enabled'. Assert that
// comments are translatable and default language is 'current_interface'.
$config = ContentLanguageSettings::loadByEntityTypeBundle('comment', 'comment_node_page');
$this->assertSame('comment', $config->getTargetEntityTypeId());
$this->assertSame('comment_node_page', $config->getTargetBundle());
$this->assertSame('current_interface', $config->getDefaultLangcode());
$this->assertTrue($config->isLanguageAlterable());
$this->assertSame($third_party_settings, $config->get('third_party_settings'));
// The content type with a long name has multilingual settings of 'Enabled'.
$config = ContentLanguageSettings::loadByEntityTypeBundle('comment', 'comment_node_a_thirty_two_char');
$this->assertFalse($config->isNew());
$this->assertSame('comment', $config->getTargetEntityTypeId());
$this->assertSame('comment_node_a_thirty_two_char', $config->getTargetBundle());
$this->assertSame('current_interface', $config->getDefaultLangcode());
$this->assertTrue($config->isLanguageAlterable());
$this->assertSame($third_party_settings, $config->get('third_party_settings'));
// Test content type has multilingual settings of 'Enabled, with field
// translation'.
$config = ContentLanguageSettings::loadByEntityTypeBundle('comment', 'comment_node_test_content_type');
$this->assertSame('comment', $config->getTargetEntityTypeId());
$this->assertSame('comment_node_test_content_type', $config->getTargetBundle());
$this->assertSame('current_interface', $config->getDefaultLangcode());
$this->assertTrue($config->isLanguageAlterable());
$third_party_settings = [
'content_translation' => [
'enabled' => FALSE,
],
];
$this->assertSame($third_party_settings, $config->get('third_party_settings'));
// Assert that non-translatable content is not translatable and the default
// language is 'site_default.
$not_translatable = [
'comment_node_book',
'comment_forum',
];
foreach ($not_translatable as $bundle) {
$config = ContentLanguageSettings::loadByEntityTypeBundle('comment', $bundle);
$this->assertTrue($config->isDefaultConfiguration());
$this->assertFalse($config->isLanguageAlterable());
$this->assertSame('site_default', $config->getDefaultLangcode(), "Default language is not 'site_default' for comment bundle $bundle");
}
}
}

View File

@@ -0,0 +1,116 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel\Migrate\d7;
use Drupal\language\Entity\ContentLanguageSettings;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Tests migration of language comment settings.
*
* @group migrate_drupal_7
*/
class MigrateLanguageContentCommentSettingsTest extends MigrateDrupal7TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'comment',
'content_translation',
'language',
'node',
'text',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->startCollectingMessages();
$this->migrateCommentTypes();
$this->executeMigrations([
'language',
'd7_language_content_comment_settings',
]);
}
/**
* Tests migration of content language settings.
*/
public function testLanguageCommentSettings(): void {
// Confirm there is no message about a missing bundle.
$this->assertEmpty($this->migrateMessages, $this->migrateMessages['error'][0] ?? '');
// Article and Blog content type have multilingual settings of 'Enabled,
// with Translation'. Assert that comments are translatable and the default
// language is 'current_interface'.
$config = ContentLanguageSettings::loadByEntityTypeBundle('comment', 'comment_node_article');
$this->assertSame('comment', $config->getTargetEntityTypeId());
$this->assertSame('comment_node_article', $config->getTargetBundle());
$this->assertSame('current_interface', $config->getDefaultLangcode());
$this->assertTrue($config->isLanguageAlterable());
$third_party_settings = [
'content_translation' => [
'enabled' => FALSE,
],
];
$this->assertSame($third_party_settings, $config->get('third_party_settings'));
$config = ContentLanguageSettings::loadByEntityTypeBundle('comment', 'comment_node_blog');
$this->assertSame('comment', $config->getTargetEntityTypeId());
$this->assertSame('comment_node_blog', $config->getTargetBundle());
$this->assertSame('current_interface', $config->getDefaultLangcode());
$this->assertTrue($config->isLanguageAlterable());
$this->assertSame($third_party_settings, $config->get('third_party_settings'));
// Page content type has multilingual settings of 'Enabled'. Assert that
// comments are translatable and default language is 'current_interface'.
$config = ContentLanguageSettings::loadByEntityTypeBundle('comment', 'comment_node_page');
$this->assertSame('comment', $config->getTargetEntityTypeId());
$this->assertSame('comment_node_page', $config->getTargetBundle());
$this->assertSame('current_interface', $config->getDefaultLangcode());
$this->assertTrue($config->isLanguageAlterable());
$this->assertSame($third_party_settings, $config->get('third_party_settings'));
// The content type with a long name has multilingual settings of 'Enabled'.
$config = ContentLanguageSettings::loadByEntityTypeBundle('comment', 'comment_node_a_thirty_two_char');
$this->assertFalse($config->isNew());
$this->assertSame('comment', $config->getTargetEntityTypeId());
$this->assertSame('comment_node_a_thirty_two_char', $config->getTargetBundle());
$this->assertSame('current_interface', $config->getDefaultLangcode());
$this->assertTrue($config->isLanguageAlterable());
$this->assertSame($third_party_settings, $config->get('third_party_settings'));
// Test content type has multilingual settings of 'Enabled, with field
// translation'.
$config = ContentLanguageSettings::loadByEntityTypeBundle('comment', 'comment_node_test_content_type');
$this->assertSame('comment', $config->getTargetEntityTypeId());
$this->assertSame('comment_node_test_content_type', $config->getTargetBundle());
$this->assertSame('current_interface', $config->getDefaultLangcode());
$this->assertTrue($config->isLanguageAlterable());
$third_party_settings = [
'content_translation' => [
'enabled' => TRUE,
],
];
$this->assertSame($third_party_settings, $config->get('third_party_settings'));
// Assert that non-translatable content is not translatable and the default
// language is 'site_default.
$not_translatable = [
'comment_node_book',
'comment_forum',
];
foreach ($not_translatable as $bundle) {
$config = ContentLanguageSettings::loadByEntityTypeBundle('comment', $bundle);
$this->assertTrue($config->isDefaultConfiguration());
$this->assertFalse($config->isLanguageAlterable());
$this->assertSame('site_default', $config->getDefaultLangcode(), "Default language is not 'site_default' for comment bundle $bundle");
}
}
}

View File

@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel\Migrate\d7;
use Drupal\language\Entity\ContentLanguageSettings;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Tests migration of i18n_menu settings.
*
* @group migrate_drupal_7
*/
class MigrateLanguageContentMenuSettingsTest extends MigrateDrupal7TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'content_translation',
'language',
'link',
'menu_link_content',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('menu_link_content');
$this->executeMigrations([
'language',
'd7_menu',
'd7_language_content_menu_settings',
]);
}
/**
* Tests migration of menu translation ability.
*/
public function testLanguageContentMenu(): void {
$config = ContentLanguageSettings::load('menu_link_content.menu_link_content');
$this->assertInstanceOf(ContentLanguageSettings::class, $config);
$this->assertSame('menu_link_content', $config->getTargetEntityTypeId());
$this->assertSame('menu_link_content', $config->getTargetBundle());
$this->assertSame(LanguageInterface::LANGCODE_SITE_DEFAULT, $config->getDefaultLangcode());
$this->assertTrue($config->isLanguageAlterable());
$settings = [
'enabled' => TRUE,
'bundle_settings' => [
'untranslatable_fields_hide' => '0',
],
];
$this->assertSame($settings, $config->getThirdPartySettings('content_translation'));
}
}

View File

@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel\Migrate\d7;
use Drupal\language\Entity\ContentLanguageSettings;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Tests the migration of language-related settings.
*
* Settings tested include the language content setting variables,
* language_content_type_$type, i18n_node_options_* and i18n_lock_node_*.
*
* @group migrate_drupal_7
*/
class MigrateLanguageContentSettingsTest extends MigrateDrupal7TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'node',
'text',
'language',
'content_translation',
'menu_ui',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->migrateContentTypes();
$this->executeMigrations([
'language',
'd7_language_content_settings',
]);
}
/**
* Tests migration of content language settings.
*/
public function testLanguageContent(): void {
// Assert that a translatable content is still translatable.
$config = $this->config('language.content_settings.node.blog');
$this->assertSame($config->get('target_entity_type_id'), 'node');
$this->assertSame($config->get('target_bundle'), 'blog');
$this->assertSame($config->get('default_langcode'), 'current_interface');
$this->assertFalse($config->get('language_alterable'));
$this->assertTrue($config->get('third_party_settings.content_translation.enabled'));
// Assert that a translatable content is translatable.
$config = ContentLanguageSettings::loadByEntityTypeBundle('node', 'page');
$this->assertFalse($config->isDefaultConfiguration());
$this->assertTrue($config->isLanguageAlterable());
$this->assertSame($config->getDefaultLangcode(), 'current_interface');
// Assert that a non-translatable content is not translatable.
$config = ContentLanguageSettings::loadByEntityTypeBundle('node', 'forum');
$this->assertTrue($config->isDefaultConfiguration());
$this->assertFalse($config->isLanguageAlterable());
$this->assertSame($config->getDefaultLangcode(), 'site_default');
// Make sure there's no migration exceptions.
$messages = $this->migration->getIdMap()->getMessages()->fetchAll();
$this->assertEmpty($messages);
// Assert that a content type translatable with entity_translation is still
// translatable.
$config = $this->config('language.content_settings.node.test_content_type');
$this->assertTrue($config->get('third_party_settings.content_translation.enabled'));
$this->assertSame($config->get('default_langcode'), 'und');
// Assert that a content type without a 'language_content_type' variable is
// not translatable
$config = ContentLanguageSettings::loadByEntityTypeBundle('node', 'book');
$this->assertTrue($config->isDefaultConfiguration());
$this->assertFalse($config->isLanguageAlterable());
$this->assertSame($config->getDefaultLangcode(), 'site_default');
}
}

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel\Migrate\d7;
use Drupal\language\Entity\ContentLanguageSettings;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Tests migration of i18ntaxonomy vocabulary settings.
*
* @group migrate_drupal_7
*/
class MigrateLanguageContentTaxonomyVocabularySettingsTest extends MigrateDrupal7TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'language',
'content_translation',
'taxonomy',
'text',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('taxonomy_term');
$this->executeMigrations([
'language',
'd7_taxonomy_vocabulary',
'd7_language_content_taxonomy_vocabulary_settings',
]);
}
/**
* Tests migration of 18ntaxonomy vocabulary settings.
*/
public function testLanguageContentTaxonomy(): void {
$target_entity = 'taxonomy_term';
// No multilingual options for terms, i18n_mode = 0.
$this->assertLanguageContentSettings($target_entity, 'tags', LanguageInterface::LANGCODE_NOT_SPECIFIED, FALSE, ['enabled' => FALSE]);
$this->assertLanguageContentSettings($target_entity, 'sujet_de_discussion', LanguageInterface::LANGCODE_NOT_SPECIFIED, FALSE, ['enabled' => FALSE]);
$this->assertLanguageContentSettings($target_entity, 'vocabulary_name_much_longer_th', LanguageInterface::LANGCODE_NOT_SPECIFIED, FALSE, ['enabled' => FALSE]);
$this->assertLanguageContentSettings($target_entity, 'test_vocabulary', LanguageInterface::LANGCODE_NOT_SPECIFIED, FALSE, ['enabled' => FALSE]);
// Localize, i18n_mode = 1.
$this->assertLanguageContentSettings($target_entity, 'vocablocalized', LanguageInterface::LANGCODE_NOT_SPECIFIED, TRUE, ['enabled' => TRUE]);
// Translate, i18n_mode = 4.
$this->assertLanguageContentSettings($target_entity, 'vocabtranslate', LanguageInterface::LANGCODE_NOT_SPECIFIED, TRUE, ['enabled' => FALSE]);
// Fixed language, i18n_mode = 2.
$this->assertLanguageContentSettings($target_entity, 'vocabfixed', 'fr', FALSE, ['enabled' => FALSE]);
}
/**
* Asserts a content language settings configuration.
*
* @param string $target_entity
* The expected target entity type.
* @param string $bundle
* The expected bundle.
* @param string $default_langcode
* The default language code.
* @param bool $language_alterable
* The expected state of language alterable.
* @param array $third_party_settings
* The content translation setting.
*
* @internal
*/
public function assertLanguageContentSettings(string $target_entity, string $bundle, string $default_langcode, bool $language_alterable, array $third_party_settings): void {
$config = ContentLanguageSettings::load($target_entity . '.' . $bundle);
$this->assertInstanceOf(ContentLanguageSettings::class, $config);
$this->assertSame($target_entity, $config->getTargetEntityTypeId());
$this->assertSame($bundle, $config->getTargetBundle());
$this->assertSame($default_langcode, $config->getDefaultLangcode());
$this->assertSame($language_alterable, $config->isLanguageAlterable());
$this->assertSame($third_party_settings, $config->getThirdPartySettings('content_translation'));
}
}

View File

@@ -0,0 +1,144 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel\Migrate\d7;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Tests the migration of language negotiation.
*
* @group migrate_drupal_7
*/
class MigrateLanguageNegotiationSettingsTest extends MigrateDrupal7TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['language'];
/**
* Tests migration of language types variables to language.types.yml.
*/
public function testLanguageTypes(): void {
$this->executeMigrations([
'language',
'd7_language_negotiation_settings',
'd7_language_types',
]);
$config = $this->config('language.types');
$this->assertSame(['language_content', 'language_url', 'language_interface'], $config->get('all'));
$this->assertSame(['language_content', 'language_interface'], $config->get('configurable'));
$this->assertSame(['enabled' => ['language-interface' => 0]], $config->get('negotiation.language_content'));
$this->assertSame(['enabled' => ['language-url' => 0, 'language-url-fallback' => 1]], $config->get('negotiation.language_url'));
$expected_language_interface = [
'enabled' => [
'language-url' => -9,
'language-user' => -10,
'language-selected' => -6,
],
'method_weights' => [
'language-url' => -9,
'language-session' => -8,
'language-user' => -10,
'language-browser' => -7,
'language-selected' => -6,
],
];
$this->assertSame($expected_language_interface, $config->get('negotiation.language_interface'));
}
/**
* Tests the migration with prefix negotiation.
*/
public function testLanguageNegotiationWithPrefix(): void {
$this->sourceDatabase->update('languages')
->fields(['domain' => ''])
->execute();
$this->executeMigrations([
'language',
'd7_language_negotiation_settings',
'language_prefixes_and_domains',
]);
$config = $this->config('language.negotiation');
$this->assertSame('language', $config->get('session.parameter'));
$this->assertSame(LanguageNegotiationUrl::CONFIG_PATH_PREFIX, $config->get('url.source'));
$this->assertSame('site_default', $config->get('selected_langcode'));
$expected_prefixes = [
'en' => '',
'fr' => 'fr',
'is' => 'is',
];
$this->assertSame($expected_prefixes, $config->get('url.prefixes'));
// If prefix negotiation is used, make sure that no domains are migrated.
// Otherwise there will be validation errors when trying to save URL
// language detection configuration from the UI.
$expected_domains = [
'en' => '',
'fr' => '',
'is' => '',
];
$this->assertSame($expected_domains, $config->get('url.domains'));
}
/**
* Tests the migration with domain negotiation.
*/
public function testLanguageNegotiationWithDomain(): void {
$this->sourceDatabase->update('variable')
->fields(['value' => serialize(1)])
->condition('name', 'locale_language_negotiation_url_part')
->execute();
$this->executeMigrations([
'language',
'd7_language_negotiation_settings',
'language_prefixes_and_domains',
]);
global $base_url;
$config = $this->config('language.negotiation');
$this->assertSame('language', $config->get('session.parameter'));
$this->assertSame(LanguageNegotiationUrl::CONFIG_DOMAIN, $config->get('url.source'));
$this->assertSame('site_default', $config->get('selected_langcode'));
$expected_domains = [
'en' => parse_url($base_url, PHP_URL_HOST),
'fr' => 'fr.drupal.org',
'is' => 'is.drupal.org',
];
$this->assertSame($expected_domains, $config->get('url.domains'));
}
/**
* Tests the migration with non-existent variables.
*/
public function testLanguageNegotiationWithNonExistentVariables(): void {
$this->sourceDatabase->delete('variable')
->condition('name', ['local_language_negotiation_url_part', 'local_language_negotiation_session_param'], 'IN')
->execute();
$this->executeMigrations([
'language',
'd6_language_negotiation_settings',
'language_prefixes_and_domains',
]);
$config = $this->config('language.negotiation');
$this->assertSame('language', $config->get('session.parameter'));
$this->assertSame(LanguageNegotiationUrl::CONFIG_PATH_PREFIX, $config->get('url.source'));
$this->assertSame('site_default', $config->get('selected_langcode'));
$expected_prefixes = [
'en' => '',
'fr' => 'fr',
'is' => 'is',
];
$this->assertSame($expected_prefixes, $config->get('url.prefixes'));
}
}

View File

@@ -0,0 +1,102 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel;
use Drupal\Core\Config\ConfigImporter;
use Drupal\Core\Config\StorageComparer;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests importing of config with language overrides.
*
* @group language
*/
class OverriddenConfigImportTest extends KernelTestBase {
/**
* Config Importer object used for testing.
*
* @var \Drupal\Core\Config\ConfigImporter
*/
protected $configImporter;
/**
* {@inheritdoc}
*/
protected static $modules = ['system', 'language'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installConfig(['system']);
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync'));
// Set up the ConfigImporter object for testing.
$storage_comparer = new StorageComparer(
$this->container->get('config.storage.sync'),
$this->container->get('config.storage')
);
$this->configImporter = new ConfigImporter(
$storage_comparer->createChangelist(),
$this->container->get('event_dispatcher'),
$this->container->get('config.manager'),
$this->container->get('lock'),
$this->container->get('config.typed'),
$this->container->get('module_handler'),
$this->container->get('module_installer'),
$this->container->get('theme_handler'),
$this->container->get('string_translation'),
$this->container->get('extension.list.module'),
$this->container->get('extension.list.theme')
);
}
/**
* Tests importing overridden config alongside config in the default language.
*/
public function testConfigImportUpdates(): void {
$storage = $this->container->get('config.storage');
$sync = $this->container->get('config.storage.sync');
/** @var \Drupal\language\ConfigurableLanguageManagerInterface $language_manager */
$language_manager = $this->container->get('language_manager');
// Make a change to the site configuration in the default collection.
$data = $storage->read('system.site');
$data['name'] = 'English site name';
$sync->write('system.site', $data);
// Also make a change to the same config object, but using a language
// override.
/** @var \Drupal\Core\Config\StorageInterface $overridden_sync */
$overridden_sync = $sync->createCollection('language.fr');
$overridden_sync->write('system.site', ['name' => 'French site name']);
// Before we start the import, the change to the site name should not be
// present. This action also primes the cache in the config factory so that
// we can test whether the cached data is correctly updated.
$config = $this->config('system.site');
$this->assertNotEquals('English site name', $config->getRawData()['name']);
// Before the import is started the site name should not yet be overridden.
$this->assertFalse($config->hasOverrides());
$override = $language_manager->getLanguageConfigOverride('fr', 'system.site');
$this->assertTrue($override->isNew());
// Start the import of the new configuration.
$this->configImporter->reset()->import();
// Verify the new site name in the default language.
$config = $this->config('system.site')->getRawData();
$this->assertEquals('English site name', $config['name']);
// Verify the overridden site name.
$override = $language_manager->getLanguageConfigOverride('fr', 'system.site');
$this->assertEquals('French site name', $override->get('name'));
}
}

View File

@@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel\Plugin\migrate\source;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests the language source plugin.
*
* @covers \Drupal\language\Plugin\migrate\source\Language
* @group language
*/
class LanguageTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['language', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public static function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['languages'] = [
[
'language' => 'en',
'name' => 'English',
'native' => 'English',
'direction' => '0',
'enabled' => '1',
'plurals' => '0',
'formula' => '',
'domain' => '',
'prefix' => '',
'weight' => '0',
'javascript' => '',
],
[
'language' => 'fr',
'name' => 'French',
'native' => 'Français',
'direction' => '0',
'enabled' => '0',
'plurals' => '2',
'formula' => '($n>1)',
'domain' => '',
'prefix' => 'fr',
'weight' => '0',
'javascript' => '',
],
];
// The expected results.
$tests[0]['expected_data'] = [
[
'language' => 'en',
'name' => 'English',
'native' => 'English',
'direction' => '0',
'enabled' => '1',
'plurals' => '0',
'formula' => '',
'domain' => '',
'prefix' => '',
'weight' => '0',
'javascript' => '',
],
[
'language' => 'fr',
'name' => 'French',
'native' => 'Français',
'direction' => '0',
'enabled' => '0',
'plurals' => '2',
'formula' => '($n>1)',
'domain' => '',
'prefix' => 'fr',
'weight' => '0',
'javascript' => '',
],
];
return $tests;
}
}

View File

@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel\Plugin\migrate\source\d6;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests menu source plugin.
*
* @covers \Drupal\language\Plugin\migrate\source\d6\LanguageContentSettings
*
* @group language
*/
class LanguageContentSettingsTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['language', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public static function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['node_type'] = [
[
'type' => 'article',
'name' => 'Article',
'module' => 'node',
'description' => 'An <em>article</em>, content type.',
'help' => '',
'has_title' => 1,
'title_label' => 'Title',
'has_body' => 1,
'body_label' => 'Body',
'min_word_count' => 0,
'custom' => 1,
'modified' => 1,
'locked' => 0,
'orig_type' => 'story',
],
[
'type' => 'company',
'name' => 'Company',
'module' => 'node',
'description' => 'Company node type',
'help' => '',
'has_title' => 1,
'title_label' => 'Name',
'has_body' => 1,
'body_label' => 'Description',
'min_word_count' => 0,
'custom' => 0,
'modified' => 1,
'locked' => 0,
'orig_type' => 'company',
],
];
foreach ($tests[0]['source_data']['node_type'] as $node_type) {
$tests[0]['expected_data'][] = [
'type' => $node_type['type'],
'language_content_type' => NULL,
'i18n_lock_node' => 0,
];
}
return $tests;
}
}

View File

@@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel\Plugin\migrate\source\d6;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests i18ntaxonomy vocabulary setting source plugin.
*
* @covers \Drupal\language\Plugin\migrate\source\d6\LanguageContentSettingsTaxonomyVocabulary
*
* @group language
*/
class LanguageContentTaxonomyVocabularySettingsTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'language', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public static function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['vocabulary'] = [
[
'vid' => 1,
'name' => 'Tags',
'description' => 'Tags description.',
'help' => 1,
'relations' => 0,
'hierarchy' => 0,
'multiple' => 0,
'required' => 0,
'tags' => 1,
'module' => 'taxonomy',
'weight' => 0,
'language' => '',
],
[
'vid' => 2,
'name' => 'Categories',
'description' => 'Categories description.',
'help' => 1,
'relations' => 1,
'hierarchy' => 1,
'multiple' => 0,
'required' => 1,
'tags' => 0,
'module' => 'taxonomy',
'weight' => 0,
'language' => 'zu',
],
];
$tests[0]['source_data']['variable'] = [
[
'name' => 'i18ntaxonomy_vocabulary',
'value' => 'a:4:{i:1;s:1:"3";i:2;s:1:"2";i:3;s:1:"3";i:5;s:1:"1";}',
],
];
$tests[0]['expected_data'] = [
[
'vid' => 1,
'language' => '',
'state' => 3,
],
[
'vid' => 2,
'language' => 'zu',
'state' => 2,
],
];
// Test without a language column in the database.
$tests[1] = $tests[0];
foreach ($tests[1]['source_data']['vocabulary'] as $key => $row) {
unset($tests[1]['source_data']['vocabulary'][$key]['language']);
}
$tests[1]['source_data']['variable'] = [
[
'name' => 'i18ntaxonomy_vocabulary',
'value' => 'a:4:{i:1;s:1:"0";i:2;s:1:"0";i:3;s:1:"3";i:5;s:1:"1";}',
],
];
$tests[1]['expected_data'] = [
[
'vid' => 1,
'state' => 0,
],
[
'vid' => 2,
'state' => 0,
],
];
// Test without a i18ntaxonomy_vocabulary variable.
$tests[2] = $tests[1];
unset($tests[2]['source_data']['variable']);
return $tests;
}
}

View File

@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel\Plugin\migrate\source\d7;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests menu source plugin.
*
* @covers \Drupal\language\Plugin\migrate\source\d7\LanguageContentSettings
*
* @group language
*/
class LanguageContentSettingsTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['language', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public static function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['node_type'] = [
[
'type' => 'article',
'name' => 'Article',
'base' => 'node_content',
'module' => 'node',
'description' => 'Use <em>articles</em> for time-sensitive content like news, press releases or blog posts.',
'help' => 'Help text for articles',
'has_title' => 1,
'title_label' => 'Title',
'custom' => 1,
'modified' => 1,
'locked' => 0,
'disabled' => 0,
'orig_type' => 'article',
],
[
'type' => 'blog',
'name' => 'Blog entry',
'base' => 'blog',
'module' => 'blog',
'description' => 'Use for multi-user blogs. Every user gets a personal blog.',
'help' => 'Blog away, good sir!',
'has_title' => 1,
'title_label' => 'Title',
'custom' => 0,
'modified' => 1,
'locked' => 1,
'disabled' => 0,
'orig_type' => 'blog',
],
];
foreach ($tests[0]['source_data']['node_type'] as $node_type) {
$tests[0]['expected_data'][] = [
'type' => $node_type['type'],
'language_content_type' => NULL,
'i18n_lock_node' => 0,
];
}
return $tests;
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel\Plugin\migrate\source\d7;
use Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source\d7\VocabularyTest;
/**
* Tests i18ntaxonomy vocabulary setting source plugin.
*
* @covers \Drupal\language\Plugin\migrate\source\d7\LanguageContentSettingsTaxonomyVocabulary
*
* @group language
*/
class LanguageContentTaxonomyVocabularySettingsTest extends VocabularyTest {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'language', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public static function providerSource() {
// Get the source data from parent.
$tests = parent::providerSource();
foreach ($tests as &$test) {
// Add the extra columns provided by i18n_taxonomy.
foreach ($test['source_data']['taxonomy_vocabulary'] as &$vocabulary) {
$vocabulary['language'] = 'und';
$vocabulary['i18n_mode'] = 2;
}
foreach ($test['expected_data'] as &$expected) {
$expected['language'] = 'und';
$expected['i18n_mode'] = 2;
}
}
return $tests;
}
}

View File

@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel\Views;
use Drupal\views\Views;
/**
* Tests the argument language handler.
*
* @group language
* @see \Drupal\language\Plugin\views\argument\Language.php
*/
class ArgumentLanguageTest extends LanguageTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_view'];
/**
* Tests the language argument.
*/
public function testArgument(): void {
$view = Views::getView('test_view');
foreach (['en' => 'John', 'xx-lolspeak' => 'George'] as $langcode => $name) {
$view->setDisplay();
$view->displayHandlers->get('default')->overrideOption('arguments', [
'langcode' => [
'id' => 'langcode',
'table' => 'views_test_data',
'field' => 'langcode',
],
]);
$this->executeView($view, [$langcode]);
$expected = [
['name' => $name],
];
$this->assertIdenticalResultset($view, $expected, ['views_test_data_name' => 'name']);
$view->destroy();
}
}
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel\Views;
use Drupal\views\Views;
/**
* Tests the field language handler.
*
* @group language
* @see \Drupal\language\Plugin\views\field\Language
*/
class FieldLanguageTest extends LanguageTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_view'];
/**
* Tests the language field.
*/
public function testField(): void {
$view = Views::getView('test_view');
$view->setDisplay();
$view->displayHandlers->get('default')->overrideOption('fields', [
'langcode' => [
'id' => 'langcode',
'table' => 'views_test_data',
'field' => 'langcode',
],
]);
$this->executeView($view);
$this->assertEquals('English', $view->field['langcode']->advancedRender($view->result[0]));
$this->assertEquals('Lolspeak', $view->field['langcode']->advancedRender($view->result[1]));
}
}

View File

@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel\Views;
use Drupal\views\Views;
/**
* Tests the filter language handler.
*
* @group language
* @see \Drupal\language\Plugin\views\filter\Language
*/
class FilterLanguageTest extends LanguageTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_view'];
/**
* Tests the language filter.
*/
public function testFilter(): void {
$view = Views::getView('test_view');
foreach (['en' => 'John', 'xx-lolspeak' => 'George'] as $langcode => $name) {
$view->setDisplay();
$view->displayHandlers->get('default')->overrideOption('filters', [
'langcode' => [
'id' => 'langcode',
'table' => 'views_test_data',
'field' => 'langcode',
'value' => [$langcode => $langcode],
],
]);
$this->executeView($view);
$expected = [
['name' => $name],
];
$this->assertIdenticalResultset($view, $expected, ['views_test_data_name' => 'name']);
$expected = [
'***LANGUAGE_site_default***',
'***LANGUAGE_language_interface***',
'en',
'xx-lolspeak',
'und',
'zxx',
];
$this->assertSame($expected, array_keys($view->filter['langcode']->getValueOptions()));
$view->destroy();
}
}
}

View File

@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Kernel\Views;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
/**
* Defines the base class for all Language handler tests.
*/
abstract class LanguageTestBase extends ViewsKernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['system', 'language'];
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE): void {
parent::setUp();
$this->installConfig(['language']);
// Create another language beside English.
ConfigurableLanguage::create(['id' => 'xx-lolspeak', 'label' => 'Lolspeak'])->save();
}
/**
* {@inheritdoc}
*/
protected function schemaDefinition() {
$schema = parent::schemaDefinition();
$schema['views_test_data']['fields']['langcode'] = [
'description' => 'The {language}.langcode of this beatle.',
'type' => 'varchar',
'length' => 12,
'default' => '',
];
return $schema;
}
/**
* {@inheritdoc}
*/
protected function viewsData() {
$data = parent::viewsData();
$data['views_test_data']['langcode'] = [
'title' => t('Langcode'),
'help' => t('Langcode'),
'field' => [
'id' => 'language',
],
'argument' => [
'id' => 'language',
],
'filter' => [
'id' => 'language',
],
];
return $data;
}
/**
* {@inheritdoc}
*/
protected function dataSet() {
$data = parent::dataSet();
$data[0]['langcode'] = 'en';
$data[1]['langcode'] = 'xx-lolspeak';
$data[2]['langcode'] = '';
$data[3]['langcode'] = '';
$data[4]['langcode'] = '';
return $data;
}
}

View File

@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Traits;
use Drupal\Core\Language\LanguageInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\language\ConfigurableLanguageInterface;
use Drupal\language\ContentLanguageSettingsInterface;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\language\Entity\ContentLanguageSettings;
/**
* Provides an API to programmatically manage languages in tests.
*/
trait LanguageTestTrait {
/**
* Creates a configurable language object from a langcode.
*
* @param string $langcode
* The language code to use to create the object.
*
* @return \Drupal\Core\Language\ConfigurableLanguageInterface
* The created language.
*
* @see \Drupal\Core\Language\LanguageManager::getStandardLanguageList()
*/
public static function createLanguageFromLangcode(string $langcode): ConfigurableLanguageInterface {
$configurable_language = ConfigurableLanguage::createFromLangcode($langcode);
$configurable_language->save();
return $configurable_language;
}
/**
* Enables translations for the given entity type bundle.
*
* @param string $entity_type_id
* The ID of the entity type.
* @param string $bundle
* The bundle name.
* @param string|null $default_langcode
* The language code to use as the default language.
*
* @return \Drupal\language\ContentLanguageSettingsInterface
* The saved content language config entity.
*/
public static function enableBundleTranslation(string $entity_type_id, string $bundle, ?string $default_langcode = LanguageInterface::LANGCODE_SITE_DEFAULT): ContentLanguageSettingsInterface {
$content_language_settings = ContentLanguageSettings::loadByEntityTypeBundle($entity_type_id, $bundle)
->setDefaultLangcode($default_langcode)
->setLanguageAlterable(TRUE);
$content_language_settings->save();
return $content_language_settings;
}
/**
* Disables translations for the given entity type bundle.
*
* @param string $entity_type_id
* The ID of the entity type.
* @param string $bundle
* The bundle name.
*/
public static function disableBundleTranslation(string $entity_type_id, string $bundle) {
// @todo Move to API call when it exists, to be added at
// https://www.drupal.org/project/drupal/issues/3408046
$content_language_settings = ContentLanguageSettings::loadByEntityTypeBundle($entity_type_id, $bundle);
$content_language_settings->setLanguageAlterable(FALSE)
->save();
$content_language_settings->delete();
}
/**
* Sets and saves a given field instance translation status.
*
* @param string $entity_type_id
* The ID of the entity type.
* @param string $bundle
* The bundle name.
* @param string $field_name
* The name of the field.
* @param bool $status
* Whether the field should be translatable or not.
*/
public static function setFieldTranslatable(string $entity_type_id, string $bundle, string $field_name, bool $status): void {
FieldConfig::loadByName($entity_type_id, $bundle, $field_name)
->setTranslatable($status)
->save();
}
}

View File

@@ -0,0 +1,103 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Unit\Config;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\language\Config\LanguageConfigOverride;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\language\Config\LanguageConfigOverride
* @group Config
* @group language
*/
class LanguageConfigOverrideTest extends UnitTestCase {
/**
* Language configuration override.
*
* @var \Drupal\language\Config\LanguageConfigOverride
*/
protected $configTranslation;
/**
* Storage.
*
* @var \Drupal\Core\Config\StorageInterface|\PHPUnit\Framework\MockObject\MockObject
*/
protected $storage;
/**
* Event Dispatcher.
*
* @var \Symfony\Contracts\EventDispatcher\EventDispatcherInterface|\PHPUnit\Framework\MockObject\MockObject
*/
protected $eventDispatcher;
/**
* Typed Config.
*
* @var \Drupal\Core\Config\TypedConfigManagerInterface|\PHPUnit\Framework\MockObject\MockObject
*/
protected $typedConfig;
/**
* The mocked cache tags invalidator.
*
* @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface|\PHPUnit\Framework\MockObject\MockObject
*/
protected $cacheTagsInvalidator;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->storage = $this->createMock('Drupal\Core\Config\StorageInterface');
$this->eventDispatcher = $this->createMock('Symfony\Contracts\EventDispatcher\EventDispatcherInterface');
$this->typedConfig = $this->createMock('\Drupal\Core\Config\TypedConfigManagerInterface');
$this->configTranslation = new LanguageConfigOverride('config.test', $this->storage, $this->typedConfig, $this->eventDispatcher);
$this->cacheTagsInvalidator = $this->createMock('Drupal\Core\Cache\CacheTagsInvalidatorInterface');
$container = new ContainerBuilder();
$container->set('cache_tags.invalidator', $this->cacheTagsInvalidator);
\Drupal::setContainer($container);
}
/**
* @covers ::save
*/
public function testSaveNew(): void {
$this->cacheTagsInvalidator->expects($this->once())
->method('invalidateTags')
->with(['config:config.test']);
$this->assertTrue($this->configTranslation->isNew());
$this->configTranslation->save();
}
/**
* @covers ::save
*/
public function testSaveExisting(): void {
$this->cacheTagsInvalidator->expects($this->once())
->method('invalidateTags')
->with(['config:config.test']);
$this->configTranslation->initWithData([]);
$this->configTranslation->save();
}
/**
* @covers ::delete
*/
public function testDelete(): void {
$this->cacheTagsInvalidator->expects($this->once())
->method('invalidateTags')
->with(['config:config.test']);
$this->configTranslation->initWithData([]);
$this->configTranslation->delete();
}
}

View File

@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Unit;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\Tests\UnitTestCase;
/**
* Tests the ConfigurableLanguage entity class.
*
* @group language
* @coversDefaultClass \Drupal\language\Entity\ConfigurableLanguage
* @see \Drupal\language\Entity\ConfigurableLanguage.
*/
class ConfigurableLanguageUnitTest extends UnitTestCase {
/**
* @covers ::getDirection
*/
public function testDirection(): void {
// Direction of language writing, an integer. Usually either
// ConfigurableLanguage::DIRECTION_LTR or
// ConfigurableLanguage::DIRECTION_RTL.
$configurableLanguage = new ConfigurableLanguage(['direction' => ConfigurableLanguage::DIRECTION_LTR], 'configurable_language');
$this->assertEquals(ConfigurableLanguage::DIRECTION_LTR, $configurableLanguage->getDirection());
// Test direction again, setting direction to RTL.
$configurableLanguage = new ConfigurableLanguage(['direction' => ConfigurableLanguage::DIRECTION_RTL], 'configurable_language');
$this->assertEquals(ConfigurableLanguage::DIRECTION_RTL, $configurableLanguage->getDirection());
}
/**
* @covers ::getWeight
* @covers ::setWeight
*/
public function testWeight(): void {
// The weight, an integer. Used to order languages with larger positive
// weights sinking items toward the bottom of lists.
$configurableLanguage = new ConfigurableLanguage(['weight' => -5], 'configurable_language');
$this->assertEquals(-5, $configurableLanguage->getWeight());
$this->assertEquals(13, $configurableLanguage->setWeight(13)->getWeight());
}
}

View File

@@ -0,0 +1,311 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\language\Unit;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\EntityTypeRepositoryInterface;
use Drupal\language\Entity\ContentLanguageSettings;
use Drupal\Tests\UnitTestCase;
use Drupal\TestTools\Random;
/**
* @coversDefaultClass \Drupal\language\Entity\ContentLanguageSettings
* @group language
*/
class ContentLanguageSettingsUnitTest extends UnitTestCase {
/**
* The entity type used for testing.
*
* @var \Drupal\Core\Entity\EntityTypeInterface|\PHPUnit\Framework\MockObject\MockObject
*/
protected $entityType;
/**
* The entity type manager used for testing.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit\Framework\MockObject\MockObject
*/
protected $entityTypeManager;
/**
* The ID of the type of the entity under test.
*
* @var string
*/
protected $entityTypeId;
/**
* The UUID generator used for testing.
*
* @var \Drupal\Component\Uuid\UuidInterface|\PHPUnit\Framework\MockObject\MockObject
*/
protected $uuid;
/**
* The typed configuration manager used for testing.
*
* @var \Drupal\Core\Config\TypedConfigManagerInterface|\PHPUnit\Framework\MockObject\MockObject
*/
protected $typedConfigManager;
/**
* The typed configuration manager used for testing.
*
* @var \Drupal\Core\Config\Entity\ConfigEntityStorage|\PHPUnit\Framework\MockObject\MockObject
*/
protected $configEntityStorageInterface;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->entityTypeId = $this->randomMachineName();
$this->entityType = $this->createMock('\Drupal\Core\Entity\EntityTypeInterface');
$this->entityTypeManager = $this->createMock(EntityTypeManagerInterface::class);
$this->uuid = $this->createMock('\Drupal\Component\Uuid\UuidInterface');
$this->typedConfigManager = $this->createMock('Drupal\Core\Config\TypedConfigManagerInterface');
$this->configEntityStorageInterface = $this->createMock('Drupal\Core\Entity\EntityStorageInterface');
$container = new ContainerBuilder();
$container->set('entity_type.manager', $this->entityTypeManager);
$container->set('uuid', $this->uuid);
$container->set('config.typed', $this->typedConfigManager);
$container->set('config.storage', $this->configEntityStorageInterface);
\Drupal::setContainer($container);
}
/**
* @covers ::calculateDependencies
*/
public function testCalculateDependencies(): void {
// Mock the interfaces necessary to create a dependency on a bundle entity.
$target_entity_type = $this->createMock('\Drupal\Core\Entity\EntityTypeInterface');
$target_entity_type->expects($this->any())
->method('getBundleConfigDependency')
->willReturn(['type' => 'config', 'name' => 'test.test_entity_type.id']);
$this->entityTypeManager->expects($this->any())
->method('getDefinition')
->with('test_entity_type')
->willReturn($target_entity_type);
$config = new ContentLanguageSettings([
'target_entity_type_id' => 'test_entity_type',
'target_bundle' => 'test_bundle',
], 'language_content_settings');
$dependencies = $config->calculateDependencies()->getDependencies();
$this->assertContains('test.test_entity_type.id', $dependencies['config']);
}
/**
* @covers ::id
*/
public function testId(): void {
$config = new ContentLanguageSettings([
'target_entity_type_id' => 'test_entity_type',
'target_bundle' => 'test_bundle',
], 'language_content_settings');
$this->assertSame('test_entity_type.test_bundle', $config->id());
}
/**
* @covers ::getTargetEntityTypeId
*/
public function testTargetEntityTypeId(): void {
$config = new ContentLanguageSettings([
'target_entity_type_id' => 'test_entity_type',
'target_bundle' => 'test_bundle',
], 'language_content_settings');
$this->assertSame('test_entity_type', $config->getTargetEntityTypeId());
}
/**
* @covers ::getTargetBundle
*/
public function testTargetBundle(): void {
$config = new ContentLanguageSettings([
'target_entity_type_id' => 'test_entity_type',
'target_bundle' => 'test_bundle',
], 'language_content_settings');
$this->assertSame('test_bundle', $config->getTargetBundle());
}
/**
* @covers ::getDefaultLangcode
* @covers ::setDefaultLangcode
*
* @dataProvider providerDefaultLangcode
*/
public function testDefaultLangcode(ContentLanguageSettings $config, $expected): void {
$this->assertSame($expected, $config->getDefaultLangcode());
}
public static function providerDefaultLangcode() {
$langcode = Random::machineName();
$config = new ContentLanguageSettings([
'target_entity_type_id' => 'test_entity_type',
'target_bundle' => 'test_bundle',
], 'language_content_settings');
$config->setDefaultLangcode($langcode);
$defaultConfig = new ContentLanguageSettings([
'target_entity_type_id' => 'test_entity_type',
'target_bundle' => 'test_default_language_bundle',
], 'language_content_settings');
return [
[$config, $langcode],
[$defaultConfig, LanguageInterface::LANGCODE_SITE_DEFAULT],
];
}
/**
* @covers ::setLanguageAlterable
* @covers ::isLanguageAlterable
*
* @dataProvider providerLanguageAlterable
*/
public function testLanguageAlterable(ContentLanguageSettings $config, $expected): void {
$this->assertSame($expected, $config->isLanguageAlterable());
}
public static function providerLanguageAlterable() {
$alterableConfig = new ContentLanguageSettings([
'target_entity_type_id' => 'test_entity_type',
'target_bundle' => 'test_bundle',
], 'language_content_settings');
$alterableConfig->setLanguageAlterable(TRUE);
$nonAlterableConfig = new ContentLanguageSettings([
'target_entity_type_id' => 'test_entity_type',
'target_bundle' => 'test_fixed_language_bundle',
], 'language_content_settings');
$nonAlterableConfig->setLanguageAlterable(FALSE);
$defaultConfig = new ContentLanguageSettings([
'target_entity_type_id' => 'test_entity_type',
'target_bundle' => 'test_default_language_bundle',
], 'language_content_settings');
return [
[$alterableConfig, TRUE],
[$nonAlterableConfig, FALSE],
[$defaultConfig, FALSE],
];
}
/**
* @covers ::isDefaultConfiguration
*
* @dataProvider providerIsDefaultConfiguration
*/
public function testIsDefaultConfiguration(ContentLanguageSettings $config, $expected): void {
$this->assertSame($expected, $config->isDefaultConfiguration());
}
public static function providerIsDefaultConfiguration() {
$alteredLanguage = new ContentLanguageSettings([
'target_entity_type_id' => 'test_entity_type',
'target_bundle' => 'test_bundle',
], 'language_content_settings');
$alteredLanguage->setLanguageAlterable(TRUE);
$alteredDefaultLangcode = new ContentLanguageSettings([
'target_entity_type_id' => 'test_entity_type',
'target_bundle' => 'test_fixed_language_bundle',
], 'language_content_settings');
$alteredDefaultLangcode->setDefaultLangcode(Random::machineName());
$defaultConfig = new ContentLanguageSettings([
'target_entity_type_id' => 'test_entity_type',
'target_bundle' => 'test_default_language_bundle',
], 'language_content_settings');
return [
[$alteredLanguage, FALSE],
[$alteredDefaultLangcode, FALSE],
[$defaultConfig, TRUE],
];
}
/**
* @covers ::loadByEntityTypeBundle
*
* @dataProvider providerLoadByEntityTypeBundle
*/
public function testLoadByEntityTypeBundle($config_id, ?ContentLanguageSettings $existing_config, $expected_langcode, $expected_language_alterable): void {
[$type, $bundle] = explode('.', $config_id);
$nullConfig = new ContentLanguageSettings([
'target_entity_type_id' => $type,
'target_bundle' => $bundle,
], 'language_content_settings');
$this->configEntityStorageInterface
->expects($this->any())
->method('load')
->with($config_id)
->willReturn($existing_config);
$this->configEntityStorageInterface
->expects($this->any())
->method('create')
->willReturn($nullConfig);
$this->entityTypeManager
->expects($this->any())
->method('getStorage')
->with('language_content_settings')
->willReturn($this->configEntityStorageInterface);
$entity_type_repository = $this->createMock(EntityTypeRepositoryInterface::class);
$entity_type_repository->expects($this->any())
->method('getEntityTypeFromClass')
->with(ContentLanguageSettings::class)
->willReturn('language_content_settings');
\Drupal::getContainer()->set('entity_type.repository', $entity_type_repository);
$config = ContentLanguageSettings::loadByEntityTypeBundle($type, $bundle);
$this->assertSame($expected_langcode, $config->getDefaultLangcode());
$this->assertSame($expected_language_alterable, $config->isLanguageAlterable());
}
public static function providerLoadByEntityTypeBundle() {
$alteredLanguage = new ContentLanguageSettings([
'target_entity_type_id' => 'test_entity_type',
'target_bundle' => 'test_bundle',
], 'language_content_settings');
$alteredLanguage->setLanguageAlterable(TRUE);
$langcode = Random::machineName();
$alteredDefaultLangcode = new ContentLanguageSettings([
'target_entity_type_id' => 'test_entity_type',
'target_bundle' => 'test_fixed_language_bundle',
], 'language_content_settings');
$alteredDefaultLangcode->setDefaultLangcode($langcode);
$defaultConfig = new ContentLanguageSettings([
'target_entity_type_id' => 'test_entity_type',
'target_bundle' => 'test_default_language_bundle',
], 'language_content_settings');
return [
['test_entity_type.test_bundle', $alteredLanguage, LanguageInterface::LANGCODE_SITE_DEFAULT, TRUE],
['test_entity_type.test_fixed_language_bundle', $alteredDefaultLangcode, $langcode, FALSE],
['test_entity_type.test_default_language_bundle', $defaultConfig, LanguageInterface::LANGCODE_SITE_DEFAULT, FALSE],
['test_entity_type.null_bundle', NULL, LanguageInterface::LANGCODE_SITE_DEFAULT, FALSE],
];
}
}

Some files were not shown because too many files have changed in this diff Show More