<?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 { /** * {@inheritdoc} */ 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 * The language manager. */ protected function languageManager() { return $this->container->get('language_manager'); } /** * Sets key/value pairs 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 the key value store. */ protected function keysValuesSet(array $values): void { // Set the new key value values. $this->container->get('keyvalue')->get('language_test')->setMultiple($values); // Refresh in-memory static key value/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->keysValuesSet([ // Enable language_test type info. 'language_types' => TRUE, // Enable language_test negotiation info (not altered yet). 'language_negotiation_info' => TRUE, // Alter LanguageInterface::TYPE_CONTENT to be configurable. '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->keysValuesSet([ '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(): void { $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): bool { $configurable_types = $this->config('language.types')->get('configurable'); return in_array($type, $configurable_types); } }