PageRenderTime 56ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php

http://github.com/drupal/drupal
PHP | 1172 lines | 721 code | 181 blank | 270 comment | 9 complexity | b89918d2fb18cd02ac4da9b58c51d8c5 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. namespace Drupal\config_translation\Tests;
  3. use Drupal\Component\Serialization\Json;
  4. use Drupal\Component\Utility\Html;
  5. use Drupal\Component\Utility\SafeMarkup;
  6. use Drupal\Component\Utility\Unicode;
  7. use Drupal\Core\Language\Language;
  8. use Drupal\Core\Language\LanguageInterface;
  9. use Drupal\field\Entity\FieldConfig;
  10. use Drupal\field\Entity\FieldStorageConfig;
  11. use Drupal\filter\Entity\FilterFormat;
  12. use Drupal\language\Entity\ConfigurableLanguage;
  13. use Drupal\simpletest\WebTestBase;
  14. /**
  15. * Translate settings and entities to various languages.
  16. *
  17. * @group config_translation
  18. */
  19. class ConfigTranslationUiTest extends WebTestBase {
  20. /**
  21. * Modules to enable.
  22. *
  23. * @var array
  24. */
  25. public static $modules = [
  26. 'block',
  27. 'config_translation',
  28. 'config_translation_test',
  29. 'contact',
  30. 'contact_test',
  31. 'contextual',
  32. 'entity_test',
  33. 'field_test',
  34. 'field_ui',
  35. 'filter',
  36. 'filter_test',
  37. 'node',
  38. 'views',
  39. 'views_ui',
  40. ];
  41. /**
  42. * Languages to enable.
  43. *
  44. * @var array
  45. */
  46. protected $langcodes = array('fr', 'ta');
  47. /**
  48. * Administrator user for tests.
  49. *
  50. * @var \Drupal\user\UserInterface
  51. */
  52. protected $adminUser;
  53. /**
  54. * Translator user for tests.
  55. *
  56. * @var \Drupal\user\UserInterface
  57. */
  58. protected $translatorUser;
  59. /**
  60. * String translation storage object.
  61. *
  62. * @var \Drupal\locale\StringStorageInterface
  63. */
  64. protected $localeStorage;
  65. protected function setUp() {
  66. parent::setUp();
  67. $translator_permissions = [
  68. 'translate configuration',
  69. ];
  70. /** @var \Drupal\filter\FilterFormatInterface $filter_test_format */
  71. $filter_test_format = FilterFormat::load('filter_test');
  72. /** @var \Drupal\filter\FilterFormatInterface $filtered_html_format */
  73. $filtered_html_format = FilterFormat::load('filtered_html');
  74. /** @var \Drupal\filter\FilterFormatInterface $full_html_format */
  75. $full_html_format = FilterFormat::load('full_html');
  76. $admin_permissions = array_merge(
  77. $translator_permissions,
  78. [
  79. 'administer languages',
  80. 'administer site configuration',
  81. 'link to any page',
  82. 'administer contact forms',
  83. 'administer filters',
  84. $filtered_html_format->getPermissionName(),
  85. $full_html_format->getPermissionName(),
  86. $filter_test_format->getPermissionName(),
  87. 'access site-wide contact form',
  88. 'access contextual links',
  89. 'administer views',
  90. 'administer account settings',
  91. 'administer themes',
  92. 'bypass node access',
  93. 'administer content types',
  94. 'translate interface',
  95. ]
  96. );
  97. // Create and log in user.
  98. $this->translatorUser = $this->drupalCreateUser($translator_permissions);
  99. $this->adminUser = $this->drupalCreateUser($admin_permissions);
  100. // Add languages.
  101. foreach ($this->langcodes as $langcode) {
  102. ConfigurableLanguage::createFromLangcode($langcode)->save();
  103. }
  104. $this->localeStorage = $this->container->get('locale.storage');
  105. $this->drupalPlaceBlock('local_tasks_block');
  106. $this->drupalPlaceBlock('page_title_block');
  107. }
  108. /**
  109. * Tests the site information translation interface.
  110. */
  111. public function testSiteInformationTranslationUi() {
  112. $this->drupalLogin($this->adminUser);
  113. $site_name = 'Name of the site for testing configuration translation';
  114. $site_slogan = 'Site slogan for testing configuration translation';
  115. $site_name_label = 'Site name';
  116. $fr_site_name = 'Nom du site pour tester la configuration traduction';
  117. $fr_site_slogan = 'Slogan du site pour tester la traduction de configuration';
  118. $fr_site_name_label = 'LibellĂŠ du champ "Nom du site"';
  119. $translation_base_url = 'admin/config/system/site-information/translate';
  120. // Set site name and slogan for default language.
  121. $this->setSiteInformation($site_name, $site_slogan);
  122. $this->drupalGet('admin/config/system/site-information');
  123. // Check translation tab exist.
  124. $this->assertLinkByHref($translation_base_url);
  125. $this->drupalGet($translation_base_url);
  126. // Check that the 'Edit' link in the source language links back to the
  127. // original form.
  128. $this->clickLink(t('Edit'));
  129. // Also check that saving the form leads back to the translation overview.
  130. $this->drupalPostForm(NULL, [], t('Save configuration'));
  131. $this->assertUrl($translation_base_url);
  132. // Check 'Add' link of French to visit add page.
  133. $this->assertLinkByHref("$translation_base_url/fr/add");
  134. $this->clickLink(t('Add'));
  135. // Make sure original text is present on this page.
  136. $this->assertRaw($site_name);
  137. $this->assertRaw($site_slogan);
  138. // Update site name and slogan for French.
  139. $edit = array(
  140. 'translation[config_names][system.site][name]' => $fr_site_name,
  141. 'translation[config_names][system.site][slogan]' => $fr_site_slogan,
  142. );
  143. $this->drupalPostForm("$translation_base_url/fr/add", $edit, t('Save translation'));
  144. $this->assertRaw(t('Successfully saved @language translation.', array('@language' => 'French')));
  145. // Check for edit, delete links (and no 'add' link) for French language.
  146. $this->assertNoLinkByHref("$translation_base_url/fr/add");
  147. $this->assertLinkByHref("$translation_base_url/fr/edit");
  148. $this->assertLinkByHref("$translation_base_url/fr/delete");
  149. // Check translation saved proper.
  150. $this->drupalGet("$translation_base_url/fr/edit");
  151. $this->assertFieldByName('translation[config_names][system.site][name]', $fr_site_name);
  152. $this->assertFieldByName('translation[config_names][system.site][slogan]', $fr_site_slogan);
  153. // Place branding block with site name and slogan into header region.
  154. $this->drupalPlaceBlock('system_branding_block', ['region' => 'header']);
  155. // Check French translation of site name and slogan are in place.
  156. $this->drupalGet('fr');
  157. $this->assertRaw($fr_site_name);
  158. $this->assertRaw($fr_site_slogan);
  159. // Visit French site to ensure base language string present as source.
  160. $this->drupalGet("fr/$translation_base_url/fr/edit");
  161. $this->assertText($site_name);
  162. $this->assertText($site_slogan);
  163. // Translate 'Site name' label in French.
  164. $search = array(
  165. 'string' => $site_name_label,
  166. 'langcode' => 'fr',
  167. 'translation' => 'untranslated',
  168. );
  169. $this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
  170. $textarea = current($this->xpath('//textarea'));
  171. $lid = (string) $textarea[0]['name'];
  172. $edit = array(
  173. $lid => $fr_site_name_label,
  174. );
  175. $this->drupalPostForm('admin/config/regional/translate', $edit, t('Save translations'));
  176. // Ensure that the label is in French (and not in English).
  177. $this->drupalGet("fr/$translation_base_url/fr/edit");
  178. $this->assertText($fr_site_name_label);
  179. $this->assertNoText($site_name_label);
  180. // Ensure that the label is also in French (and not in English)
  181. // when editing another language with the interface in French.
  182. $this->drupalGet("fr/$translation_base_url/ta/edit");
  183. $this->assertText($fr_site_name_label);
  184. $this->assertNoText($site_name_label);
  185. // Ensure that the label is not translated when the interface is in English.
  186. $this->drupalGet("$translation_base_url/fr/edit");
  187. $this->assertText($site_name_label);
  188. $this->assertNoText($fr_site_name_label);
  189. }
  190. /**
  191. * Tests the site information translation interface.
  192. */
  193. public function testSourceValueDuplicateSave() {
  194. $this->drupalLogin($this->adminUser);
  195. $site_name = 'Site name for testing configuration translation';
  196. $site_slogan = 'Site slogan for testing configuration translation';
  197. $translation_base_url = 'admin/config/system/site-information/translate';
  198. $this->setSiteInformation($site_name, $site_slogan);
  199. $this->drupalGet($translation_base_url);
  200. // Case 1: Update new value for site slogan and site name.
  201. $edit = array(
  202. 'translation[config_names][system.site][name]' => 'FR ' . $site_name,
  203. 'translation[config_names][system.site][slogan]' => 'FR ' . $site_slogan,
  204. );
  205. // First time, no overrides, so just Add link.
  206. $this->drupalPostForm("$translation_base_url/fr/add", $edit, t('Save translation'));
  207. // Read overridden file from active config.
  208. $override = \Drupal::languageManager()->getLanguageConfigOverride('fr', 'system.site');
  209. // Expect both name and slogan in language specific file.
  210. $expected = array(
  211. 'name' => 'FR ' . $site_name,
  212. 'slogan' => 'FR ' . $site_slogan,
  213. );
  214. $this->assertEqual($expected, $override->get());
  215. // Case 2: Update new value for site slogan and default value for site name.
  216. $this->drupalGet("$translation_base_url/fr/edit");
  217. // Assert that the language configuration does not leak outside of the
  218. // translation form into the actual site name and slogan.
  219. $this->assertNoText('FR ' . $site_name);
  220. $this->assertNoText('FR ' . $site_slogan);
  221. $edit = array(
  222. 'translation[config_names][system.site][name]' => $site_name,
  223. 'translation[config_names][system.site][slogan]' => 'FR ' . $site_slogan,
  224. );
  225. $this->drupalPostForm(NULL, $edit, t('Save translation'));
  226. $this->assertRaw(t('Successfully updated @language translation.', array('@language' => 'French')));
  227. $override = \Drupal::languageManager()->getLanguageConfigOverride('fr', 'system.site');
  228. // Expect only slogan in language specific file.
  229. $expected = 'FR ' . $site_slogan;
  230. $this->assertEqual($expected, $override->get('slogan'));
  231. // Case 3: Keep default value for site name and slogan.
  232. $this->drupalGet("$translation_base_url/fr/edit");
  233. $this->assertNoText('FR ' . $site_slogan);
  234. $edit = array(
  235. 'translation[config_names][system.site][name]' => $site_name,
  236. 'translation[config_names][system.site][slogan]' => $site_slogan,
  237. );
  238. $this->drupalPostForm(NULL, $edit, t('Save translation'));
  239. $override = \Drupal::languageManager()->getLanguageConfigOverride('fr', 'system.site');
  240. // Expect no language specific file.
  241. $this->assertTrue($override->isNew());
  242. // Check configuration page with translator user. Should have no access.
  243. $this->drupalLogout();
  244. $this->drupalLogin($this->translatorUser);
  245. $this->drupalGet('admin/config/system/site-information');
  246. $this->assertResponse(403);
  247. // While translator can access the translation page, the edit link is not
  248. // present due to lack of permissions.
  249. $this->drupalGet($translation_base_url);
  250. $this->assertNoLink(t('Edit'));
  251. // Check 'Add' link for French.
  252. $this->assertLinkByHref("$translation_base_url/fr/add");
  253. }
  254. /**
  255. * Tests the contact form translation.
  256. */
  257. public function testContactConfigEntityTranslation() {
  258. $this->drupalLogin($this->adminUser);
  259. $this->drupalGet('admin/structure/contact');
  260. // Check for default contact form configuration entity from Contact module.
  261. $this->assertLinkByHref('admin/structure/contact/manage/feedback');
  262. // Save default language configuration.
  263. $label = 'Send your feedback';
  264. $edit = array(
  265. 'label' => $label,
  266. 'recipients' => 'sales@example.com,support@example.com',
  267. 'reply' => 'Thank you for your mail',
  268. );
  269. $this->drupalPostForm('admin/structure/contact/manage/feedback', $edit, t('Save'));
  270. // Ensure translation link is present.
  271. $translation_base_url = 'admin/structure/contact/manage/feedback/translate';
  272. $this->assertLinkByHref($translation_base_url);
  273. // Make sure translate tab is present.
  274. $this->drupalGet('admin/structure/contact/manage/feedback');
  275. $this->assertLink(t('Translate @type', array('@type' => 'contact form')));
  276. // Visit the form to confirm the changes.
  277. $this->drupalGet('contact/feedback');
  278. $this->assertText($label);
  279. foreach ($this->langcodes as $langcode) {
  280. $this->drupalGet($translation_base_url);
  281. $this->assertLink(t('Translate @type', array('@type' => 'contact form')));
  282. // 'Add' link should be present for $langcode translation.
  283. $translation_page_url = "$translation_base_url/$langcode/add";
  284. $this->assertLinkByHref($translation_page_url);
  285. // Make sure original text is present on this page.
  286. $this->drupalGet($translation_page_url);
  287. $this->assertText($label);
  288. // Update translatable fields.
  289. $edit = array(
  290. 'translation[config_names][contact.form.feedback][label]' => 'Website feedback - ' . $langcode,
  291. 'translation[config_names][contact.form.feedback][reply]' => 'Thank you for your mail - ' . $langcode,
  292. );
  293. // Save language specific version of form.
  294. $this->drupalPostForm($translation_page_url, $edit, t('Save translation'));
  295. // Expect translated values in language specific file.
  296. $override = \Drupal::languageManager()->getLanguageConfigOverride($langcode, 'contact.form.feedback');
  297. $expected = array(
  298. 'label' => 'Website feedback - ' . $langcode,
  299. 'reply' => 'Thank you for your mail - ' . $langcode,
  300. );
  301. $this->assertEqual($expected, $override->get());
  302. // Check for edit, delete links (and no 'add' link) for $langcode.
  303. $this->assertNoLinkByHref("$translation_base_url/$langcode/add");
  304. $this->assertLinkByHref("$translation_base_url/$langcode/edit");
  305. $this->assertLinkByHref("$translation_base_url/$langcode/delete");
  306. // Visit language specific version of form to check label.
  307. $this->drupalGet($langcode . '/contact/feedback');
  308. $this->assertText('Website feedback - ' . $langcode);
  309. // Submit feedback.
  310. $edit = array(
  311. 'subject[0][value]' => 'Test subject',
  312. 'message[0][value]' => 'Test message',
  313. );
  314. $this->drupalPostForm(NULL, $edit, t('Send message'));
  315. }
  316. // Now that all language translations are present, check translation and
  317. // original text all appear in any translated page on the translation
  318. // forms.
  319. foreach ($this->langcodes as $langcode) {
  320. $langcode_prefixes = array_merge(array(''), $this->langcodes);
  321. foreach ($langcode_prefixes as $langcode_prefix) {
  322. $this->drupalGet(ltrim("$langcode_prefix/$translation_base_url/$langcode/edit", '/'));
  323. $this->assertFieldByName('translation[config_names][contact.form.feedback][label]', 'Website feedback - ' . $langcode);
  324. $this->assertText($label);
  325. }
  326. }
  327. // We get all emails so no need to check inside the loop.
  328. $captured_emails = $this->drupalGetMails();
  329. // Check language specific auto reply text in email body.
  330. foreach ($captured_emails as $email) {
  331. if ($email['id'] == 'contact_page_autoreply') {
  332. // Trim because we get an added newline for the body.
  333. $this->assertEqual(trim($email['body']), 'Thank you for your mail - ' . $email['langcode']);
  334. }
  335. }
  336. // Test that delete links work and operations perform properly.
  337. foreach ($this->langcodes as $langcode) {
  338. $replacements = array('%label' => t('@label @entity_type', array('@label' => $label, '@entity_type' => Unicode::strtolower(t('Contact form')))), '@language' => \Drupal::languageManager()->getLanguage($langcode)->getName());
  339. $this->drupalGet("$translation_base_url/$langcode/delete");
  340. $this->assertRaw(t('Are you sure you want to delete the @language translation of %label?', $replacements));
  341. // Assert link back to list page to cancel delete is present.
  342. $this->assertLinkByHref($translation_base_url);
  343. $this->drupalPostForm(NULL, array(), t('Delete'));
  344. $this->assertRaw(t('@language translation of %label was deleted', $replacements));
  345. $this->assertLinkByHref("$translation_base_url/$langcode/add");
  346. $this->assertNoLinkByHref("translation_base_url/$langcode/edit");
  347. $this->assertNoLinkByHref("$translation_base_url/$langcode/delete");
  348. // Expect no language specific file present anymore.
  349. $override = \Drupal::languageManager()->getLanguageConfigOverride($langcode, 'contact.form.feedback');
  350. $this->assertTrue($override->isNew());
  351. }
  352. // Check configuration page with translator user. Should have no access.
  353. $this->drupalLogout();
  354. $this->drupalLogin($this->translatorUser);
  355. $this->drupalGet('admin/structure/contact/manage/feedback');
  356. $this->assertResponse(403);
  357. // While translator can access the translation page, the edit link is not
  358. // present due to lack of permissions.
  359. $this->drupalGet($translation_base_url);
  360. $this->assertNoLink(t('Edit'));
  361. // Check 'Add' link for French.
  362. $this->assertLinkByHref("$translation_base_url/fr/add");
  363. }
  364. /**
  365. * Tests date format translation.
  366. */
  367. public function testDateFormatTranslation() {
  368. $this->drupalLogin($this->adminUser);
  369. $this->drupalGet('admin/config/regional/date-time');
  370. // Check for medium format.
  371. $this->assertLinkByHref('admin/config/regional/date-time/formats/manage/medium');
  372. // Save default language configuration for a new format.
  373. $edit = array(
  374. 'label' => 'Custom medium date',
  375. 'id' => 'custom_medium',
  376. 'date_format_pattern' => 'Y. m. d. H:i',
  377. );
  378. $this->drupalPostForm('admin/config/regional/date-time/formats/add', $edit, t('Add format'));
  379. // Test translating a default shipped format and our custom format.
  380. $formats = array(
  381. 'medium' => 'Default medium date',
  382. 'custom_medium' => 'Custom medium date',
  383. );
  384. foreach ($formats as $id => $label) {
  385. $translation_base_url = 'admin/config/regional/date-time/formats/manage/' . $id . '/translate';
  386. $this->drupalGet($translation_base_url);
  387. // 'Add' link should be present for French translation.
  388. $translation_page_url = "$translation_base_url/fr/add";
  389. $this->assertLinkByHref($translation_page_url);
  390. // Make sure original text is present on this page.
  391. $this->drupalGet($translation_page_url);
  392. $this->assertText($label);
  393. // Make sure that the date library is added.
  394. $this->assertRaw('core/modules/system/js/system.date.js');
  395. // Update translatable fields.
  396. $edit = array(
  397. 'translation[config_names][core.date_format.' . $id . '][label]' => $id . ' - FR',
  398. 'translation[config_names][core.date_format.' . $id . '][pattern]' => 'D',
  399. );
  400. // Save language specific version of form.
  401. $this->drupalPostForm($translation_page_url, $edit, t('Save translation'));
  402. // Get translation and check we've got the right value.
  403. $override = \Drupal::languageManager()->getLanguageConfigOverride('fr', 'core.date_format.' . $id);
  404. $expected = array(
  405. 'label' => $id . ' - FR',
  406. 'pattern' => 'D',
  407. );
  408. $this->assertEqual($expected, $override->get());
  409. // Formatting the date 8 / 27 / 1985 @ 13:37 EST with pattern D should
  410. // display "Tue".
  411. $formatted_date = format_date(494015820, $id, NULL, 'America/New_York', 'fr');
  412. $this->assertEqual($formatted_date, 'Tue', 'Got the right formatted date using the date format translation pattern.');
  413. }
  414. }
  415. /**
  416. * Tests the account settings translation interface.
  417. *
  418. * This is the only special case so far where we have multiple configuration
  419. * names involved building up one configuration translation form. Test that
  420. * the translations are saved for all configuration names properly.
  421. */
  422. public function testAccountSettingsConfigurationTranslation() {
  423. $this->drupalLogin($this->adminUser);
  424. $this->drupalGet('admin/config/people/accounts');
  425. $this->assertLink(t('Translate @type', array('@type' => 'account settings')));
  426. $this->drupalGet('admin/config/people/accounts/translate');
  427. $this->assertLink(t('Translate @type', array('@type' => 'account settings')));
  428. $this->assertLinkByHref('admin/config/people/accounts/translate/fr/add');
  429. // Update account settings fields for French.
  430. $edit = array(
  431. 'translation[config_names][user.settings][anonymous]' => 'Anonyme',
  432. 'translation[config_names][user.mail][status_blocked][subject]' => 'Testing, your account is blocked.',
  433. 'translation[config_names][user.mail][status_blocked][body]' => 'Testing account blocked body.',
  434. );
  435. $this->drupalPostForm('admin/config/people/accounts/translate/fr/add', $edit, t('Save translation'));
  436. // Make sure the changes are saved and loaded back properly.
  437. $this->drupalGet('admin/config/people/accounts/translate/fr/edit');
  438. foreach ($edit as $key => $value) {
  439. // Check the translations appear in the right field type as well.
  440. $xpath = '//' . (strpos($key, '[body]') ? 'textarea' : 'input') . '[@name="' . $key . '"]';
  441. $this->assertFieldByXPath($xpath, $value);
  442. }
  443. // Check that labels for email settings appear.
  444. $this->assertText(t('Account cancellation confirmation'));
  445. $this->assertText(t('Password recovery'));
  446. }
  447. /**
  448. * Tests source and target language edge cases.
  449. */
  450. public function testSourceAndTargetLanguage() {
  451. $this->drupalLogin($this->adminUser);
  452. // Loading translation page for not-specified language (und)
  453. // should return 403.
  454. $this->drupalGet('admin/config/system/site-information/translate/und/add');
  455. $this->assertResponse(403);
  456. // Check the source language doesn't have 'Add' or 'Delete' link and
  457. // make sure source language edit goes to original configuration page
  458. // not the translation specific edit page.
  459. $this->drupalGet('admin/config/system/site-information/translate');
  460. $this->assertNoLinkByHref('admin/config/system/site-information/translate/en/edit');
  461. $this->assertNoLinkByHref('admin/config/system/site-information/translate/en/add');
  462. $this->assertNoLinkByHref('admin/config/system/site-information/translate/en/delete');
  463. $this->assertLinkByHref('admin/config/system/site-information');
  464. // Translation addition to source language should return 403.
  465. $this->drupalGet('admin/config/system/site-information/translate/en/add');
  466. $this->assertResponse(403);
  467. // Translation editing in source language should return 403.
  468. $this->drupalGet('admin/config/system/site-information/translate/en/edit');
  469. $this->assertResponse(403);
  470. // Translation deletion in source language should return 403.
  471. $this->drupalGet('admin/config/system/site-information/translate/en/delete');
  472. $this->assertResponse(403);
  473. // Set default language of site information to not-specified language (und).
  474. $this->config('system.site')
  475. ->set('langcode', LanguageInterface::LANGCODE_NOT_SPECIFIED)
  476. ->save();
  477. // Make sure translation tab does not exist on the configuration page.
  478. $this->drupalGet('admin/config/system/site-information');
  479. $this->assertNoLinkByHref('admin/config/system/site-information/translate');
  480. // If source language is not specified, translation page should be 403.
  481. $this->drupalGet('admin/config/system/site-information/translate');
  482. $this->assertResponse(403);
  483. }
  484. /**
  485. * Tests the views translation interface.
  486. */
  487. public function testViewsTranslationUI() {
  488. $this->drupalLogin($this->adminUser);
  489. // Assert contextual link related to views.
  490. $ids = array('entity.view.edit_form:view=frontpage:location=page&name=frontpage&display_id=page_1');
  491. $response = $this->renderContextualLinks($ids, 'node');
  492. $this->assertResponse(200);
  493. $json = Json::decode($response);
  494. $this->assertTrue(strpos($json[$ids[0]], 'Translate view'), 'Translate view contextual link added.');
  495. $description = 'All content promoted to the front page.';
  496. $human_readable_name = 'Frontpage';
  497. $display_settings_master = 'Master';
  498. $display_options_master = '(Empty)';
  499. $translation_base_url = 'admin/structure/views/view/frontpage/translate';
  500. $this->drupalGet($translation_base_url);
  501. // Check 'Add' link of French to visit add page.
  502. $this->assertLinkByHref("$translation_base_url/fr/add");
  503. $this->clickLink(t('Add'));
  504. // Make sure original text is present on this page.
  505. $this->assertRaw($description);
  506. $this->assertRaw($human_readable_name);
  507. // Update Views Fields for French.
  508. $edit = array(
  509. 'translation[config_names][views.view.frontpage][description]' => $description . " FR",
  510. 'translation[config_names][views.view.frontpage][label]' => $human_readable_name . " FR",
  511. 'translation[config_names][views.view.frontpage][display][default][display_title]' => $display_settings_master . " FR",
  512. 'translation[config_names][views.view.frontpage][display][default][display_options][title]' => $display_options_master . " FR",
  513. );
  514. $this->drupalPostForm("$translation_base_url/fr/add", $edit, t('Save translation'));
  515. $this->assertRaw(t('Successfully saved @language translation.', array('@language' => 'French')));
  516. // Check for edit, delete links (and no 'add' link) for French language.
  517. $this->assertNoLinkByHref("$translation_base_url/fr/add");
  518. $this->assertLinkByHref("$translation_base_url/fr/edit");
  519. $this->assertLinkByHref("$translation_base_url/fr/delete");
  520. // Check translation saved proper.
  521. $this->drupalGet("$translation_base_url/fr/edit");
  522. $this->assertFieldByName('translation[config_names][views.view.frontpage][description]', $description . " FR");
  523. $this->assertFieldByName('translation[config_names][views.view.frontpage][label]', $human_readable_name . " FR");
  524. $this->assertFieldByName('translation[config_names][views.view.frontpage][display][default][display_title]', $display_settings_master . " FR");
  525. $this->assertFieldByName('translation[config_names][views.view.frontpage][display][default][display_options][title]', $display_options_master . " FR");
  526. }
  527. /**
  528. * Test the number of source elements for plural strings in config translation forms.
  529. */
  530. public function testPluralConfigStringsSourceElements() {
  531. $this->drupalLogin($this->adminUser);
  532. // Languages to test, with various number of plural forms.
  533. $languages = array(
  534. 'vi' => array('plurals' => 1, 'expected' => array(TRUE, FALSE, FALSE, FALSE)),
  535. 'fr' => array('plurals' => 2, 'expected' => array(TRUE, TRUE, FALSE, FALSE)),
  536. 'sl' => array('plurals' => 4, 'expected' => array(TRUE, TRUE, TRUE, TRUE)),
  537. );
  538. foreach ($languages as $langcode => $data) {
  539. // Import a .po file to add a new language with a given number of plural forms
  540. $name = tempnam('temporary://', $langcode . '_') . '.po';
  541. file_put_contents($name, $this->getPoFile($data['plurals']));
  542. $this->drupalPostForm('admin/config/regional/translate/import', array(
  543. 'langcode' => $langcode,
  544. 'files[file]' => $name,
  545. ), t('Import'));
  546. // Change the config langcode of the 'files' view.
  547. $config = \Drupal::service('config.factory')->getEditable('views.view.files');
  548. $config->set('langcode', $langcode);
  549. $config->save();
  550. // Go to the translation page of the 'files' view.
  551. $translation_url = 'admin/structure/views/view/files/translate/en/add';
  552. $this->drupalGet($translation_url);
  553. // Check if the expected number of source elements are present.
  554. foreach ($data['expected'] as $index => $expected) {
  555. if ($expected) {
  556. $this->assertRaw('edit-source-config-names-viewsviewfiles-display-default-display-options-fields-count-format-plural-string-' . $index);
  557. }
  558. else {
  559. $this->assertNoRaw('edit-source-config-names-viewsviewfiles-display-default-display-options-fields-count-format-plural-string-' . $index);
  560. }
  561. }
  562. }
  563. }
  564. /**
  565. * Test translation of plural strings with multiple plural forms in config.
  566. */
  567. public function testPluralConfigStrings() {
  568. $this->drupalLogin($this->adminUser);
  569. // First import a .po file with multiple plural forms.
  570. // This will also automatically add the 'sl' language.
  571. $name = tempnam('temporary://', "sl_") . '.po';
  572. file_put_contents($name, $this->getPoFile(4));
  573. $this->drupalPostForm('admin/config/regional/translate/import', array(
  574. 'langcode' => 'sl',
  575. 'files[file]' => $name,
  576. ), t('Import'));
  577. // Translate the files view, as this one uses numeric formatters.
  578. $description = 'Singular form';
  579. $field_value = '1 place';
  580. $field_value_plural = '@count places';
  581. $translation_url = 'admin/structure/views/view/files/translate/sl/add';
  582. $this->drupalGet($translation_url);
  583. // Make sure original text is present on this page, in addition to 2 new
  584. // empty fields.
  585. $this->assertRaw($description);
  586. $this->assertFieldByName('translation[config_names][views.view.files][display][default][display_options][fields][count][format_plural_string][0]', $field_value);
  587. $this->assertFieldByName('translation[config_names][views.view.files][display][default][display_options][fields][count][format_plural_string][1]', $field_value_plural);
  588. $this->assertFieldByName('translation[config_names][views.view.files][display][default][display_options][fields][count][format_plural_string][2]', '');
  589. $this->assertFieldByName('translation[config_names][views.view.files][display][default][display_options][fields][count][format_plural_string][3]', '');
  590. // Then make sure it also works.
  591. $edit = [
  592. 'translation[config_names][views.view.files][display][default][display_options][fields][count][format_plural_string][0]' => $field_value . ' SL',
  593. 'translation[config_names][views.view.files][display][default][display_options][fields][count][format_plural_string][1]' => $field_value_plural . ' 1 SL',
  594. 'translation[config_names][views.view.files][display][default][display_options][fields][count][format_plural_string][2]' => $field_value_plural . ' 2 SL',
  595. 'translation[config_names][views.view.files][display][default][display_options][fields][count][format_plural_string][3]' => $field_value_plural . ' 3 SL',
  596. ];
  597. $this->drupalPostForm($translation_url, $edit, t('Save translation'));
  598. // Make sure the values have changed.
  599. $this->drupalGet($translation_url);
  600. $this->assertFieldByName('translation[config_names][views.view.files][display][default][display_options][fields][count][format_plural_string][0]', "$field_value SL");
  601. $this->assertFieldByName('translation[config_names][views.view.files][display][default][display_options][fields][count][format_plural_string][1]', "$field_value_plural 1 SL");
  602. $this->assertFieldByName('translation[config_names][views.view.files][display][default][display_options][fields][count][format_plural_string][2]', "$field_value_plural 2 SL");
  603. $this->assertFieldByName('translation[config_names][views.view.files][display][default][display_options][fields][count][format_plural_string][3]', "$field_value_plural 3 SL");
  604. }
  605. /**
  606. * Tests the translation of field and field storage configuration.
  607. */
  608. public function testFieldConfigTranslation() {
  609. // Add a test field which has a translatable field setting and a
  610. // translatable field storage setting.
  611. $field_name = strtolower($this->randomMachineName());
  612. $field_storage = FieldStorageConfig::create([
  613. 'field_name' => $field_name,
  614. 'entity_type' => 'entity_test',
  615. 'type' => 'test_field',
  616. ]);
  617. $translatable_storage_setting = $this->randomString();
  618. $field_storage->setSetting('translatable_storage_setting', $translatable_storage_setting);
  619. $field_storage->save();
  620. $bundle = strtolower($this->randomMachineName());
  621. entity_test_create_bundle($bundle);
  622. $field = FieldConfig::create([
  623. 'field_name' => $field_name,
  624. 'entity_type' => 'entity_test',
  625. 'bundle' => $bundle,
  626. ]);
  627. $translatable_field_setting = $this->randomString();
  628. $field->setSetting('translatable_field_setting', $translatable_field_setting);
  629. $field->save();
  630. $this->drupalLogin($this->translatorUser);
  631. $this->drupalGet("/entity_test/structure/$bundle/fields/entity_test.$bundle.$field_name/translate");
  632. $this->clickLink('Add');
  633. $this->assertText('Translatable field setting');
  634. $this->assertEscaped($translatable_field_setting);
  635. $this->assertText('Translatable storage setting');
  636. $this->assertEscaped($translatable_storage_setting);
  637. }
  638. /**
  639. * Tests the translation of a boolean field settings.
  640. */
  641. public function testBooleanFieldConfigTranslation() {
  642. // Add a test boolean field.
  643. $field_name = strtolower($this->randomMachineName());
  644. FieldStorageConfig::create([
  645. 'field_name' => $field_name,
  646. 'entity_type' => 'entity_test',
  647. 'type' => 'boolean',
  648. ])->save();
  649. $bundle = strtolower($this->randomMachineName());
  650. entity_test_create_bundle($bundle);
  651. $field = FieldConfig::create([
  652. 'field_name' => $field_name,
  653. 'entity_type' => 'entity_test',
  654. 'bundle' => $bundle,
  655. ]);
  656. $on_label = 'On label (with <em>HTML</em> & things)';
  657. $field->setSetting('on_label', $on_label);
  658. $off_label = 'Off label (with <em>HTML</em> & things)';
  659. $field->setSetting('off_label', $off_label);
  660. $field->save();
  661. $this->drupalLogin($this->translatorUser);
  662. $this->drupalGet("/entity_test/structure/$bundle/fields/entity_test.$bundle.$field_name/translate");
  663. $this->clickLink('Add');
  664. // Checks the text of details summary element that surrounds the translation
  665. // options.
  666. $this->assertText(Html::escape(strip_tags($on_label)) . ' Boolean settings');
  667. // Checks that the correct on and off labels appear on the form.
  668. $this->assertEscaped($on_label);
  669. $this->assertEscaped($off_label);
  670. }
  671. /**
  672. * Test translation storage in locale storage.
  673. */
  674. public function testLocaleDBStorage() {
  675. // Enable import of translations. By default this is disabled for automated
  676. // tests.
  677. $this->config('locale.settings')
  678. ->set('translation.import_enabled', TRUE)
  679. ->save();
  680. $this->drupalLogin($this->adminUser);
  681. $langcode = 'xx';
  682. $name = $this->randomMachineName(16);
  683. $edit = array(
  684. 'predefined_langcode' => 'custom',
  685. 'langcode' => $langcode,
  686. 'label' => $name,
  687. 'direction' => Language::DIRECTION_LTR,
  688. );
  689. $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add custom language'));
  690. // Make sure there is no translation stored in locale storage before edit.
  691. $translation = $this->getTranslation('user.settings', 'anonymous', 'fr');
  692. $this->assertTrue(empty($translation));
  693. // Add custom translation.
  694. $edit = array(
  695. 'translation[config_names][user.settings][anonymous]' => 'Anonyme',
  696. );
  697. $this->drupalPostForm('admin/config/people/accounts/translate/fr/add', $edit, t('Save translation'));
  698. // Make sure translation stored in locale storage after saved language
  699. // specific configuration translation.
  700. $translation = $this->getTranslation('user.settings', 'anonymous', 'fr');
  701. $this->assertEqual('Anonyme', $translation->getString());
  702. // revert custom translations to base translation.
  703. $edit = array(
  704. 'translation[config_names][user.settings][anonymous]' => 'Anonymous',
  705. );
  706. $this->drupalPostForm('admin/config/people/accounts/translate/fr/edit', $edit, t('Save translation'));
  707. // Make sure there is no translation stored in locale storage after revert.
  708. $translation = $this->getTranslation('user.settings', 'anonymous', 'fr');
  709. $this->assertEqual('Anonymous', $translation->getString());
  710. }
  711. /**
  712. * Tests the single language existing.
  713. */
  714. public function testSingleLanguageUI() {
  715. $this->drupalLogin($this->adminUser);
  716. // Delete French language
  717. $this->drupalPostForm('admin/config/regional/language/delete/fr', array(), t('Delete'));
  718. $this->assertRaw(t('The %language (%langcode) language has been removed.', array('%language' => 'French', '%langcode' => 'fr')));
  719. // Change default language to Tamil.
  720. $edit = array(
  721. 'site_default_language' => 'ta',
  722. );
  723. $this->drupalPostForm('admin/config/regional/language', $edit, t('Save configuration'));
  724. $this->assertRaw(t('Configuration saved.'));
  725. // Delete English language
  726. $this->drupalPostForm('admin/config/regional/language/delete/en', array(), t('Delete'));
  727. $this->assertRaw(t('The %language (%langcode) language has been removed.', array('%language' => 'English', '%langcode' => 'en')));
  728. // Visit account setting translation page, this should not
  729. // throw any notices.
  730. $this->drupalGet('admin/config/people/accounts/translate');
  731. $this->assertResponse(200);
  732. }
  733. /**
  734. * Tests the config_translation_info_alter() hook.
  735. */
  736. public function testAlterInfo() {
  737. $this->drupalLogin($this->adminUser);
  738. $this->container->get('state')->set('config_translation_test_config_translation_info_alter', TRUE);
  739. $this->container->get('plugin.manager.config_translation.mapper')->clearCachedDefinitions();
  740. // Check out if the translation page has the altered in settings.
  741. $this->drupalGet('admin/config/system/site-information/translate/fr/add');
  742. $this->assertText(t('Feed channel'));
  743. $this->assertText(t('Feed description'));
  744. // Check if the translation page does not have the altered out settings.
  745. $this->drupalGet('admin/config/people/accounts/translate/fr/add');
  746. $this->assertText(t('Name'));
  747. $this->assertNoText(t('Account cancellation confirmation'));
  748. $this->assertNoText(t('Password recovery'));
  749. }
  750. /**
  751. * Tests the sequence data type translation.
  752. */
  753. public function testSequenceTranslation() {
  754. $this->drupalLogin($this->adminUser);
  755. /** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */
  756. $config_factory = $this->container->get('config.factory');
  757. $expected = array(
  758. 'kitten',
  759. 'llama',
  760. 'elephant'
  761. );
  762. $actual = $config_factory
  763. ->getEditable('config_translation_test.content')
  764. ->get('animals');
  765. $this->assertEqual($expected, $actual);
  766. $edit = array(
  767. 'translation[config_names][config_translation_test.content][content][value]' => '<p><strong>Hello World</strong> - FR</p>',
  768. 'translation[config_names][config_translation_test.content][animals][0]' => 'kitten - FR',
  769. 'translation[config_names][config_translation_test.content][animals][1]' => 'llama - FR',
  770. 'translation[config_names][config_translation_test.content][animals][2]' => 'elephant - FR',
  771. );
  772. $this->drupalPostForm('admin/config/media/file-system/translate/fr/add', $edit, t('Save translation'));
  773. $this->container->get('language.config_factory_override')
  774. ->setLanguage(new Language(array('id' => 'fr')));
  775. $expected = array(
  776. 'kitten - FR',
  777. 'llama - FR',
  778. 'elephant - FR',
  779. );
  780. $actual = $config_factory
  781. ->get('config_translation_test.content')
  782. ->get('animals');
  783. $this->assertEqual($expected, $actual);
  784. }
  785. /**
  786. * Test text_format translation.
  787. */
  788. public function testTextFormatTranslation() {
  789. $this->drupalLogin($this->adminUser);
  790. /** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */
  791. $config_factory = $this->container->get('config.factory');
  792. $expected = array(
  793. 'value' => '<p><strong>Hello World</strong></p>',
  794. 'format' => 'plain_text',
  795. );
  796. $actual = $config_factory
  797. ->get('config_translation_test.content')
  798. ->getOriginal('content', FALSE);
  799. $this->assertEqual($expected, $actual);
  800. $translation_base_url = 'admin/config/media/file-system/translate';
  801. $this->drupalGet($translation_base_url);
  802. // 'Add' link should be present for French translation.
  803. $translation_page_url = "$translation_base_url/fr/add";
  804. $this->assertLinkByHref($translation_page_url);
  805. $this->drupalGet($translation_page_url);
  806. // Assert that changing the text format is not possible, even for an
  807. // administrator.
  808. $this->assertNoFieldByName('translation[config_names][config_translation_test.content][content][format]');
  809. // Update translatable fields.
  810. $edit = array(
  811. 'translation[config_names][config_translation_test.content][content][value]' => '<p><strong>Hello World</strong> - FR</p>',
  812. );
  813. // Save language specific version of form.
  814. $this->drupalPostForm($translation_page_url, $edit, t('Save translation'));
  815. // Get translation and check we've got the right value.
  816. $expected = array(
  817. 'value' => '<p><strong>Hello World</strong> - FR</p>',
  818. 'format' => 'plain_text',
  819. );
  820. $this->container->get('language.config_factory_override')
  821. ->setLanguage(new Language(array('id' => 'fr')));
  822. $actual = $config_factory
  823. ->get('config_translation_test.content')
  824. ->get('content');
  825. $this->assertEqual($expected, $actual);
  826. // Change the text format of the source configuration and verify that the
  827. // text format of the translation does not change because that could lead to
  828. // security vulnerabilities.
  829. $config_factory
  830. ->getEditable('config_translation_test.content')
  831. ->set('content.format', 'full_html')
  832. ->save();
  833. $actual = $config_factory
  834. ->get('config_translation_test.content')
  835. ->get('content');
  836. // The translation should not have changed, so re-use $expected.
  837. $this->assertEqual($expected, $actual);
  838. // Because the text is now in a text format that the translator does not
  839. // have access to, the translator should not be able to translate it.
  840. $translation_page_url = "$translation_base_url/fr/edit";
  841. $this->drupalLogin($this->translatorUser);
  842. $this->drupalGet($translation_page_url);
  843. $this->assertDisabledTextarea('edit-translation-config-names-config-translation-testcontent-content-value');
  844. $this->drupalPostForm(NULL, array(), t('Save translation'));
  845. // Check that submitting the form did not update the text format of the
  846. // translation.
  847. $actual = $config_factory
  848. ->get('config_translation_test.content')
  849. ->get('content');
  850. $this->assertEqual($expected, $actual);
  851. // The administrator must explicitly change the text format.
  852. $this->drupalLogin($this->adminUser);
  853. $edit = array(
  854. 'translation[config_names][config_translation_test.content][content][format]' => 'full_html',
  855. );
  856. $this->drupalPostForm($translation_page_url, $edit, t('Save translation'));
  857. $expected = array(
  858. 'value' => '<p><strong>Hello World</strong> - FR</p>',
  859. 'format' => 'full_html',
  860. );
  861. $actual = $config_factory
  862. ->get('config_translation_test.content')
  863. ->get('content');
  864. $this->assertEqual($expected, $actual);
  865. }
  866. /**
  867. * Gets translation from locale storage.
  868. *
  869. * @param $config_name
  870. * Configuration object.
  871. * @param $key
  872. * Translation configuration field key.
  873. * @param $langcode
  874. * String language code to load translation.
  875. *
  876. * @return bool|mixed
  877. * Returns translation if exists, FALSE otherwise.
  878. */
  879. protected function getTranslation($config_name, $key, $langcode) {
  880. $settings_locations = $this->localeStorage->getLocations(array('type' => 'configuration', 'name' => $config_name));
  881. $this->assertTrue(!empty($settings_locations), format_string('Configuration locations found for %config_name.', array('%config_name' => $config_name)));
  882. if (!empty($settings_locations)) {
  883. $source = $this->container->get('config.factory')->get($config_name)->get($key);
  884. $source_string = $this->localeStorage->findString(array('source' => $source, 'type' => 'configuration'));
  885. $this->assertTrue(!empty($source_string), format_string('Found string for %config_name.%key.', array('%config_name' => $config_name, '%key' => $key)));
  886. if (!empty($source_string)) {
  887. $conditions = array(
  888. 'lid' => $source_string->lid,
  889. 'language' => $langcode,
  890. );
  891. $translations = $this->localeStorage->getTranslations($conditions + array('translated' => TRUE));
  892. return reset($translations);
  893. }
  894. }
  895. return FALSE;
  896. }
  897. /**
  898. * Sets site name and slogan for default language, helps in tests.
  899. *
  900. * @param string $site_name
  901. * @param string $site_slogan
  902. */
  903. protected function setSiteInformation($site_name, $site_slogan) {
  904. $edit = array(
  905. 'site_name' => $site_name,
  906. 'site_slogan' => $site_slogan,
  907. );
  908. $this->drupalPostForm('admin/config/system/site-information', $edit, t('Save configuration'));
  909. $this->assertRaw(t('The configuration options have been saved.'));
  910. }
  911. /**
  912. * Get server-rendered contextual links for the given contextual link ids.
  913. *
  914. * @param array $ids
  915. * An array of contextual link ids.
  916. * @param string $current_path
  917. * The Drupal path for the page for which the contextual links are rendered.
  918. *
  919. * @return string
  920. * The response body.
  921. */
  922. protected function renderContextualLinks($ids, $current_path) {
  923. $post = array();
  924. for ($i = 0; $i < count($ids); $i++) {
  925. $post['ids[' . $i . ']'] = $ids[$i];
  926. }
  927. return $this->drupalPostWithFormat('contextual/render', 'json', $post, array('query' => array('destination' => $current_path)));
  928. }
  929. /**
  930. * Asserts that a textarea with a given ID has been disabled from editing.
  931. *
  932. * @param string $id
  933. * The HTML ID of the textarea.
  934. *
  935. * @return bool
  936. * TRUE if the assertion passed; FALSE otherwise.
  937. */
  938. protected function assertDisabledTextarea($id) {
  939. $textarea = $this->xpath('//textarea[@id=:id and contains(@disabled, "disabled")]', array(
  940. ':id' => $id,
  941. ));
  942. $textarea = reset($textarea);
  943. $passed = $this->assertTrue($textarea instanceof \SimpleXMLElement, SafeMarkup::format('Disabled field @id exists.', array(
  944. '@id' => $id,
  945. )));
  946. $expected = 'This field has been disabled because you do not have sufficient permissions to edit it.';
  947. $passed = $passed && $this->assertEqual((string) $textarea, $expected, SafeMarkup::format('Disabled textarea @id hides text in an inaccessible text format.', array(
  948. '@id' => $id,
  949. )));
  950. // Make sure the text format select is not shown.
  951. $select_id = str_replace('value', 'format--2', $id);
  952. $select = $this->xpath('//select[@id=:id]', array(':id' => $select_id));
  953. return $passed && $this->assertFalse($select, SafeMarkup::format('Field @id does not exist.', array(
  954. '@id' => $id,
  955. )));
  956. }
  957. /**
  958. * Helper function that returns a .po file with a given number of plural forms.
  959. */
  960. public function getPoFile($plurals) {
  961. $po_file = array();
  962. $po_file[1] = <<< EOF
  963. msgid ""
  964. msgstr ""
  965. "Project-Id-Version: Drupal 8\\n"
  966. "MIME-Version: 1.0\\n"
  967. "Content-Type: text/plain; charset=UTF-8\\n"
  968. "Content-Transfer-Encoding: 8bit\\n"
  969. "Plural-Forms: nplurals=1; plural=0;\\n"
  970. EOF;
  971. $po_file[2] = <<< EOF
  972. msgid ""
  973. msgstr ""
  974. "Project-Id-Version: Drupal 8\\n"
  975. "MIME-Version: 1.0\\n"
  976. "Content-Type: text/plain; charset=UTF-8\\n"
  977. "Content-Transfer-Encoding: 8bit\\n"
  978. "Plural-Forms: nplurals=2; plural=(n>1);\\n"
  979. EOF;
  980. $po_file[4] = <<< EOF
  981. msgid ""
  982. msgstr ""
  983. "Project-Id-Version: Drupal 8\\n"
  984. "MIME-Version: 1.0\\n"
  985. "Content-Type: text/plain; charset=UTF-8\\n"
  986. "Content-Transfer-Encoding: 8bit\\n"
  987. "Plural-Forms: nplurals=4; plural=(((n%100)==1)?(0):(((n%100)==2)?(1):((((n%100)==3)||((n%100)==4))?(2):3)));\\n"
  988. EOF;
  989. return $po_file[$plurals];
  990. }
  991. }