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