1<?php 2 3namespace Drupal\Tests\language\Functional; 4 5use Drupal\Component\Render\FormattableMarkup; 6use Drupal\Core\Url; 7use Drupal\Core\Language\LanguageInterface; 8use Drupal\language\Entity\ConfigurableLanguage; 9use Drupal\Tests\BrowserTestBase; 10 11/** 12 * Adds and configures languages to check negotiation changes. 13 * 14 * @group language 15 */ 16class LanguageConfigurationTest extends BrowserTestBase { 17 18 /** 19 * Modules to enable. 20 * 21 * @var array 22 */ 23 public static $modules = ['language']; 24 25 /** 26 * {@inheritdoc} 27 */ 28 protected $defaultTheme = 'stark'; 29 30 /** 31 * Functional tests for adding, editing and deleting languages. 32 */ 33 public function testLanguageConfiguration() { 34 // Ensure the after installing the language module the weight of the English 35 // language is still 0. 36 $this->assertEqual(ConfigurableLanguage::load('en')->getWeight(), 0, 'The English language has a weight of 0.'); 37 38 // User to add and remove language. 39 $admin_user = $this->drupalCreateUser([ 40 'administer languages', 41 'access administration pages', 42 ]); 43 $this->drupalLogin($admin_user); 44 45 // Check if the Default English language has no path prefix. 46 $this->drupalGet('admin/config/regional/language/detection/url'); 47 $this->assertFieldByXPath('//input[@name="prefix[en]"]', '', 'Default English has no path prefix.'); 48 49 // Check that Add language is a primary button. 50 $this->drupalGet('admin/config/regional/language/add'); 51 $this->assertFieldByXPath('//input[contains(@class, "button--primary")]', 'Add language', 'Add language is a primary button'); 52 53 // Add predefined language. 54 $edit = [ 55 'predefined_langcode' => 'fr', 56 ]; 57 $this->drupalPostForm(NULL, $edit, 'Add language'); 58 $this->assertText('French'); 59 $this->assertUrl(Url::fromRoute('entity.configurable_language.collection', [], ['absolute' => TRUE])->toString(), [], 'Correct page redirection.'); 60 // Langcode for Languages is always 'en'. 61 $language = $this->config('language.entity.fr')->get(); 62 $this->assertEqual($language['langcode'], 'en'); 63 64 // Check if the Default English language has no path prefix. 65 $this->drupalGet('admin/config/regional/language/detection/url'); 66 $this->assertFieldByXPath('//input[@name="prefix[en]"]', '', 'Default English has no path prefix.'); 67 // Check if French has a path prefix. 68 $this->drupalGet('admin/config/regional/language/detection/url'); 69 $this->assertFieldByXPath('//input[@name="prefix[fr]"]', 'fr', 'French has a path prefix.'); 70 71 // Check if we can change the default language. 72 $this->drupalGet('admin/config/regional/language'); 73 $this->assertFieldChecked('edit-site-default-language-en', 'English is the default language.'); 74 75 // Change the default language. 76 $edit = [ 77 'site_default_language' => 'fr', 78 ]; 79 $this->drupalPostForm(NULL, $edit, t('Save configuration')); 80 $this->rebuildContainer(); 81 $this->assertFieldChecked('edit-site-default-language-fr', 'Default language updated.'); 82 $this->assertUrl(Url::fromRoute('entity.configurable_language.collection', [], ['absolute' => TRUE, 'langcode' => 'fr'])->toString(), [], 'Correct page redirection.'); 83 84 // Check if a valid language prefix is added after changing the default 85 // language. 86 $this->drupalGet('admin/config/regional/language/detection/url'); 87 $this->assertFieldByXPath('//input[@name="prefix[en]"]', 'en', 'A valid path prefix has been added to the previous default language.'); 88 // Check if French still has a path prefix. 89 $this->drupalGet('admin/config/regional/language/detection/url'); 90 $this->assertFieldByXPath('//input[@name="prefix[fr]"]', 'fr', 'French still has a path prefix.'); 91 92 // Check that prefix can be changed. 93 $edit = [ 94 'prefix[fr]' => 'french', 95 ]; 96 $this->drupalPostForm(NULL, $edit, t('Save configuration')); 97 $this->assertFieldByXPath('//input[@name="prefix[fr]"]', 'french', 'French path prefix has changed.'); 98 99 // Check that the prefix can be removed. 100 $edit = [ 101 'prefix[fr]' => '', 102 ]; 103 $this->drupalPostForm(NULL, $edit, t('Save configuration')); 104 $this->assertNoText(t('The prefix may only be left blank for the selected detection fallback language.'), 'The path prefix can be removed for the default language'); 105 106 // Change default negotiation language. 107 $this->config('language.negotiation')->set('selected_langcode', 'fr')->save(); 108 // Check that the prefix of a language that is not the negotiation one 109 // cannot be changed to empty string. 110 $edit = [ 111 'prefix[en]' => '', 112 ]; 113 $this->drupalPostForm(NULL, $edit, t('Save configuration')); 114 $this->assertText(t('The prefix may only be left blank for the selected detection fallback language.')); 115 116 // Check that prefix cannot be changed to contain a slash. 117 $edit = [ 118 'prefix[en]' => 'foo/bar', 119 ]; 120 $this->drupalPostForm(NULL, $edit, t('Save configuration')); 121 $this->assertText(t('The prefix may not contain a slash.'), 'English prefix cannot be changed to contain a slash.'); 122 123 // Remove English language and add a new Language to check if langcode of 124 // Language entity is 'en'. 125 $this->drupalPostForm('admin/config/regional/language/delete/en', [], t('Delete')); 126 $this->rebuildContainer(); 127 $this->assertRaw(t('The %language (%langcode) language has been removed.', ['%language' => 'English', '%langcode' => 'en'])); 128 129 // Ensure that French language has a weight of 1 after being created through 130 // the UI. 131 $french = ConfigurableLanguage::load('fr'); 132 $this->assertEqual($french->getWeight(), 1, 'The French language has a weight of 1.'); 133 // Ensure that French language can now have a weight of 0. 134 $french->setWeight(0)->save(); 135 $this->assertEqual($french->getWeight(), 0, 'The French language has a weight of 0.'); 136 // Ensure that new languages created through the API get a weight of 0. 137 $afrikaans = ConfigurableLanguage::createFromLangcode('af'); 138 $afrikaans->save(); 139 $this->assertEqual($afrikaans->getWeight(), 0, 'The Afrikaans language has a weight of 0.'); 140 // Ensure that a new language can be created with any weight. 141 $arabic = ConfigurableLanguage::createFromLangcode('ar'); 142 $arabic->setWeight(4)->save(); 143 $this->assertEqual($arabic->getWeight(), 4, 'The Arabic language has a weight of 0.'); 144 145 $edit = [ 146 'predefined_langcode' => 'de', 147 ]; 148 $this->drupalPostForm('admin/config/regional/language/add', $edit, 'Add language'); 149 $language = $this->config('language.entity.de')->get(); 150 $this->assertEqual($language['langcode'], 'fr'); 151 152 // Ensure that German language has a weight of 5 after being created through 153 // the UI. 154 $french = ConfigurableLanguage::load('de'); 155 $this->assertEqual($french->getWeight(), 5, 'The German language has a weight of 5.'); 156 } 157 158 /** 159 * Functional tests for setting system language weight on adding, editing and deleting languages. 160 */ 161 public function testLanguageConfigurationWeight() { 162 // User to add and remove language. 163 $admin_user = $this->drupalCreateUser([ 164 'administer languages', 165 'access administration pages', 166 ]); 167 $this->drupalLogin($admin_user); 168 $this->checkConfigurableLanguageWeight(); 169 170 // Add predefined language. 171 $edit = [ 172 'predefined_langcode' => 'fr', 173 ]; 174 $this->drupalPostForm('admin/config/regional/language/add', $edit, 'Add language'); 175 $this->checkConfigurableLanguageWeight('after adding new language'); 176 177 // Re-ordering languages. 178 $edit = [ 179 'languages[en][weight]' => $this->getHighestConfigurableLanguageWeight() + 1, 180 ]; 181 $this->drupalPostForm('admin/config/regional/language', $edit, 'Save configuration'); 182 $this->checkConfigurableLanguageWeight('after re-ordering'); 183 184 // Remove predefined language. 185 $this->drupalPostForm('admin/config/regional/language/delete/fr', [], 'Delete'); 186 $this->checkConfigurableLanguageWeight('after deleting a language'); 187 } 188 189 /** 190 * Validates system languages are ordered after configurable languages. 191 * 192 * @param string $state 193 * (optional) A string for customizing assert messages, containing the 194 * description of the state of the check, for example: 'after re-ordering'. 195 * Defaults to 'by default'. 196 */ 197 protected function checkConfigurableLanguageWeight($state = 'by default') { 198 // Reset language list. 199 \Drupal::languageManager()->reset(); 200 $max_configurable_language_weight = $this->getHighestConfigurableLanguageWeight(); 201 $replacements = ['@event' => $state]; 202 foreach (\Drupal::languageManager()->getLanguages(LanguageInterface::STATE_LOCKED) as $locked_language) { 203 $replacements['%language'] = $locked_language->getName(); 204 $this->assertTrue($locked_language->getWeight() > $max_configurable_language_weight, new FormattableMarkup('System language %language has higher weight than configurable languages @event', $replacements)); 205 } 206 } 207 208 /** 209 * Helper to get maximum weight of configurable (unlocked) languages. 210 * 211 * @return int 212 * Maximum weight of configurable languages. 213 */ 214 protected function getHighestConfigurableLanguageWeight() { 215 $max_weight = 0; 216 217 $storage = $this->container->get('entity_type.manager') 218 ->getStorage('configurable_language'); 219 $storage->resetCache(); 220 /* @var $languages \Drupal\Core\Language\LanguageInterface[] */ 221 $languages = $storage->loadMultiple(); 222 foreach ($languages as $language) { 223 if (!$language->isLocked()) { 224 $max_weight = max($max_weight, $language->getWeight()); 225 } 226 } 227 228 return $max_weight; 229 } 230 231} 232