PageRenderTime 108ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php

https://gitlab.com/guillaumev/alkarama
PHP | 626 lines | 394 code | 64 blank | 168 comment | 23 complexity | e077564d885d9a69e5525b0558558656 MD5 | raw file
  1. <?php
  2. namespace Drupal\content_translation\Tests;
  3. use Drupal\Core\Cache\Cache;
  4. use Drupal\Core\Entity\EntityInterface;
  5. use Drupal\Core\Language\Language;
  6. use Drupal\Core\Language\LanguageInterface;
  7. use Drupal\Core\Url;
  8. use Drupal\language\Entity\ConfigurableLanguage;
  9. use Drupal\Component\Utility\SafeMarkup;
  10. use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
  11. /**
  12. * Tests the Content Translation UI.
  13. */
  14. abstract class ContentTranslationUITestBase extends ContentTranslationTestBase {
  15. use AssertPageCacheContextsAndTagsTrait;
  16. /**
  17. * The id of the entity being translated.
  18. *
  19. * @var mixed
  20. */
  21. protected $entityId;
  22. /**
  23. * Whether the behavior of the language selector should be tested.
  24. *
  25. * @var bool
  26. */
  27. protected $testLanguageSelector = TRUE;
  28. /**
  29. * Flag that tells whether the HTML escaping of all languages works or not
  30. * after SafeMarkup change.
  31. *
  32. * @var bool
  33. */
  34. protected $testHTMLEscapeForAllLanguages = FALSE;
  35. /**
  36. * Default cache contexts expected on a non-translated entity.
  37. *
  38. * Cache contexts will not be checked if this list is empty.
  39. *
  40. * @var string[]
  41. */
  42. protected $defaultCacheContexts = ['languages:language_interface', 'theme', 'url.query_args:_wrapper_format', 'user.permissions'];
  43. /**
  44. * Tests the basic translation UI.
  45. */
  46. function testTranslationUI() {
  47. $this->doTestBasicTranslation();
  48. $this->doTestTranslationOverview();
  49. $this->doTestOutdatedStatus();
  50. $this->doTestPublishedStatus();
  51. $this->doTestAuthoringInfo();
  52. $this->doTestTranslationEdit();
  53. $this->doTestTranslationChanged();
  54. $this->doTestChangedTimeAfterSaveWithoutChanges();
  55. $this->doTestTranslationDeletion();
  56. }
  57. /**
  58. * Tests the basic translation workflow.
  59. */
  60. protected function doTestBasicTranslation() {
  61. // Create a new test entity with original values in the default language.
  62. $default_langcode = $this->langcodes[0];
  63. $values[$default_langcode] = $this->getNewEntityValues($default_langcode);
  64. // Create the entity with the editor as owner, so that afterwards a new
  65. // translation is created by the translator and the translation author is
  66. // tested.
  67. $this->drupalLogin($this->editor);
  68. $this->entityId = $this->createEntity($values[$default_langcode], $default_langcode);
  69. $this->drupalLogin($this->translator);
  70. $storage = $this->container->get('entity_type.manager')
  71. ->getStorage($this->entityTypeId);
  72. $storage->resetCache([$this->entityId]);
  73. $entity = $storage->load($this->entityId);
  74. $this->assertTrue($entity, 'Entity found in the database.');
  75. $this->drupalGet($entity->urlInfo());
  76. $this->assertResponse(200, 'Entity URL is valid.');
  77. // Ensure that the content language cache context is not yet added to the
  78. // page.
  79. $this->assertCacheContexts($this->defaultCacheContexts);
  80. $this->drupalGet($entity->urlInfo('drupal:content-translation-overview'));
  81. $this->assertNoText('Source language', 'Source language column correctly hidden.');
  82. $translation = $this->getTranslation($entity, $default_langcode);
  83. foreach ($values[$default_langcode] as $property => $value) {
  84. $stored_value = $this->getValue($translation, $property, $default_langcode);
  85. $value = is_array($value) ? $value[0]['value'] : $value;
  86. $message = format_string('@property correctly stored in the default language.', array('@property' => $property));
  87. $this->assertEqual($stored_value, $value, $message);
  88. }
  89. // Add a content translation.
  90. $langcode = 'it';
  91. $language = ConfigurableLanguage::load($langcode);
  92. $values[$langcode] = $this->getNewEntityValues($langcode);
  93. $entity_type_id = $entity->getEntityTypeId();
  94. $add_url = Url::fromRoute("entity.$entity_type_id.content_translation_add", [
  95. $entity->getEntityTypeId() => $entity->id(),
  96. 'source' => $default_langcode,
  97. 'target' => $langcode
  98. ], array('language' => $language));
  99. $this->drupalPostForm($add_url, $this->getEditValues($values, $langcode), $this->getFormSubmitActionForNewTranslation($entity, $langcode));
  100. // Assert that HTML is escaped in "all languages" in UI after SafeMarkup
  101. // change.
  102. if ($this->testHTMLEscapeForAllLanguages) {
  103. $this->assertNoRaw('&lt;span class=&quot;translation-entity-all-languages&quot;&gt;(all languages)&lt;/span&gt;');
  104. $this->assertRaw('<span class="translation-entity-all-languages">(all languages)</span>');
  105. }
  106. // Ensure that the content language cache context is not yet added to the
  107. // page.
  108. $storage = $this->container->get('entity_type.manager')
  109. ->getStorage($this->entityTypeId);
  110. $storage->resetCache([$this->entityId]);
  111. $entity = $storage->load($this->entityId);
  112. $this->drupalGet($entity->urlInfo());
  113. $this->assertCacheContexts(Cache::mergeContexts(['languages:language_content'], $this->defaultCacheContexts));
  114. // Reset the cache of the entity, so that the new translation gets the
  115. // updated values.
  116. $metadata_source_translation = $this->manager->getTranslationMetadata($entity->getTranslation($default_langcode));
  117. $metadata_target_translation = $this->manager->getTranslationMetadata($entity->getTranslation($langcode));
  118. $author_field_name = $entity->hasField('content_translation_uid') ? 'content_translation_uid' : 'uid';
  119. if ($entity->getFieldDefinition($author_field_name)->isTranslatable()) {
  120. $this->assertEqual($metadata_target_translation->getAuthor()->id(), $this->translator->id(),
  121. SafeMarkup::format('Author of the target translation @langcode correctly stored for translatable owner field.', array('@langcode' => $langcode)));
  122. $this->assertNotEqual($metadata_target_translation->getAuthor()->id(), $metadata_source_translation->getAuthor()->id(),
  123. SafeMarkup::format('Author of the target translation @target different from the author of the source translation @source for translatable owner field.',
  124. array('@target' => $langcode, '@source' => $default_langcode)));
  125. }
  126. else {
  127. $this->assertEqual($metadata_target_translation->getAuthor()->id(), $this->editor->id(), 'Author of the entity remained untouched after translation for non translatable owner field.');
  128. }
  129. $created_field_name = $entity->hasField('content_translation_created') ? 'content_translation_created' : 'created';
  130. if ($entity->getFieldDefinition($created_field_name)->isTranslatable()) {
  131. $this->assertTrue($metadata_target_translation->getCreatedTime() > $metadata_source_translation->getCreatedTime(),
  132. SafeMarkup::format('Translation creation timestamp of the target translation @target is newer than the creation timestamp of the source translation @source for translatable created field.',
  133. array('@target' => $langcode, '@source' => $default_langcode)));
  134. }
  135. else {
  136. $this->assertEqual($metadata_target_translation->getCreatedTime(), $metadata_source_translation->getCreatedTime(), 'Creation timestamp of the entity remained untouched after translation for non translatable created field.');
  137. }
  138. if ($this->testLanguageSelector) {
  139. $this->assertNoFieldByXPath('//select[@id="edit-langcode-0-value"]', NULL, 'Language selector correctly disabled on translations.');
  140. }
  141. $storage->resetCache([$this->entityId]);
  142. $entity = $storage->load($this->entityId);
  143. $this->drupalGet($entity->urlInfo('drupal:content-translation-overview'));
  144. $this->assertNoText('Source language', 'Source language column correctly hidden.');
  145. // Switch the source language.
  146. $langcode = 'fr';
  147. $language = ConfigurableLanguage::load($langcode);
  148. $source_langcode = 'it';
  149. $edit = array('source_langcode[source]' => $source_langcode);
  150. $entity_type_id = $entity->getEntityTypeId();
  151. $add_url = Url::fromRoute("entity.$entity_type_id.content_translation_add", [
  152. $entity->getEntityTypeId() => $entity->id(),
  153. 'source' => $default_langcode,
  154. 'target' => $langcode
  155. ], array('language' => $language));
  156. // This does not save anything, it merely reloads the form and fills in the
  157. // fields with the values from the different source language.
  158. $this->drupalPostForm($add_url, $edit, t('Change'));
  159. $this->assertFieldByXPath("//input[@name=\"{$this->fieldName}[0][value]\"]", $values[$source_langcode][$this->fieldName][0]['value'], 'Source language correctly switched.');
  160. // Add another translation and mark the other ones as outdated.
  161. $values[$langcode] = $this->getNewEntityValues($langcode);
  162. $edit = $this->getEditValues($values, $langcode) + array('content_translation[retranslate]' => TRUE);
  163. $entity_type_id = $entity->getEntityTypeId();
  164. $add_url = Url::fromRoute("entity.$entity_type_id.content_translation_add", [
  165. $entity->getEntityTypeId() => $entity->id(),
  166. 'source' => $source_langcode,
  167. 'target' => $langcode
  168. ], array('language' => $language));
  169. $this->drupalPostForm($add_url, $edit, $this->getFormSubmitActionForNewTranslation($entity, $langcode));
  170. $storage->resetCache([$this->entityId]);
  171. $entity = $storage->load($this->entityId);
  172. $this->drupalGet($entity->urlInfo('drupal:content-translation-overview'));
  173. $this->assertText('Source language', 'Source language column correctly shown.');
  174. // Check that the entered values have been correctly stored.
  175. foreach ($values as $langcode => $property_values) {
  176. $translation = $this->getTranslation($entity, $langcode);
  177. foreach ($property_values as $property => $value) {
  178. $stored_value = $this->getValue($translation, $property, $langcode);
  179. $value = is_array($value) ? $value[0]['value'] : $value;
  180. $message = format_string('%property correctly stored with language %language.', array('%property' => $property, '%language' => $langcode));
  181. $this->assertEqual($stored_value, $value, $message);
  182. }
  183. }
  184. }
  185. /**
  186. * Tests that the translation overview shows the correct values.
  187. */
  188. protected function doTestTranslationOverview() {
  189. $storage = $this->container->get('entity_type.manager')
  190. ->getStorage($this->entityTypeId);
  191. $storage->resetCache([$this->entityId]);
  192. $entity = $storage->load($this->entityId);
  193. $translate_url = $entity->urlInfo('drupal:content-translation-overview');
  194. $this->drupalGet($translate_url);
  195. $translate_url->setAbsolute(FALSE);
  196. foreach ($this->langcodes as $langcode) {
  197. if ($entity->hasTranslation($langcode)) {
  198. $language = new Language(array('id' => $langcode));
  199. $view_url = $entity->url('canonical', ['language' => $language]);
  200. $elements = $this->xpath('//table//a[@href=:href]', [':href' => $view_url]);
  201. $this->assertEqual((string) $elements[0], $entity->getTranslation($langcode)->label(), format_string('Label correctly shown for %language translation.', array('%language' => $langcode)));
  202. $edit_path = $entity->url('edit-form', array('language' => $language));
  203. $elements = $this->xpath('//table//ul[@class="dropbutton"]/li/a[@href=:href]', array(':href' => $edit_path));
  204. $this->assertEqual((string) $elements[0], t('Edit'), format_string('Edit link correct for %language translation.', array('%language' => $langcode)));
  205. }
  206. }
  207. }
  208. /**
  209. * Tests up-to-date status tracking.
  210. */
  211. protected function doTestOutdatedStatus() {
  212. $storage = $this->container->get('entity_type.manager')
  213. ->getStorage($this->entityTypeId);
  214. $storage->resetCache([$this->entityId]);
  215. $entity = $storage->load($this->entityId);
  216. $langcode = 'fr';
  217. $languages = \Drupal::languageManager()->getLanguages();
  218. // Mark translations as outdated.
  219. $edit = array('content_translation[retranslate]' => TRUE);
  220. $edit_path = $entity->urlInfo('edit-form', array('language' => $languages[$langcode]));
  221. $this->drupalPostForm($edit_path, $edit, $this->getFormSubmitAction($entity, $langcode));
  222. $storage->resetCache([$this->entityId]);
  223. $entity = $storage->load($this->entityId);
  224. // Check that every translation has the correct "outdated" status, and that
  225. // the Translation fieldset is open if the translation is "outdated".
  226. foreach ($this->langcodes as $added_langcode) {
  227. $url = $entity->urlInfo('edit-form', array('language' => ConfigurableLanguage::load($added_langcode)));
  228. $this->drupalGet($url);
  229. if ($added_langcode == $langcode) {
  230. $this->assertFieldByXPath('//input[@name="content_translation[retranslate]"]', FALSE, 'The retranslate flag is not checked by default.');
  231. $this->assertFalse($this->xpath('//details[@id="edit-content-translation" and @open="open"]'), 'The translation tab should be collapsed by default.');
  232. }
  233. else {
  234. $this->assertFieldByXPath('//input[@name="content_translation[outdated]"]', TRUE, 'The translate flag is checked by default.');
  235. $this->assertTrue($this->xpath('//details[@id="edit-content-translation" and @open="open"]'), 'The translation tab is correctly expanded when the translation is outdated.');
  236. $edit = array('content_translation[outdated]' => FALSE);
  237. $this->drupalPostForm($url, $edit, $this->getFormSubmitAction($entity, $added_langcode));
  238. $this->drupalGet($url);
  239. $this->assertFieldByXPath('//input[@name="content_translation[retranslate]"]', FALSE, 'The retranslate flag is now shown.');
  240. $storage = $this->container->get('entity_type.manager')
  241. ->getStorage($this->entityTypeId);
  242. $storage->resetCache([$this->entityId]);
  243. $entity = $storage->load($this->entityId);
  244. $this->assertFalse($this->manager->getTranslationMetadata($entity->getTranslation($added_langcode))->isOutdated(), 'The "outdated" status has been correctly stored.');
  245. }
  246. }
  247. }
  248. /**
  249. * Tests the translation publishing status.
  250. */
  251. protected function doTestPublishedStatus() {
  252. $storage = $this->container->get('entity_type.manager')
  253. ->getStorage($this->entityTypeId);
  254. $storage->resetCache([$this->entityId]);
  255. $entity = $storage->load($this->entityId);
  256. // Unpublish translations.
  257. foreach ($this->langcodes as $index => $langcode) {
  258. if ($index > 0) {
  259. $url = $entity->urlInfo('edit-form', array('language' => ConfigurableLanguage::load($langcode)));
  260. $edit = array('content_translation[status]' => FALSE);
  261. $this->drupalPostForm($url, $edit, $this->getFormSubmitAction($entity, $langcode));
  262. $storage = $this->container->get('entity_type.manager')
  263. ->getStorage($this->entityTypeId);
  264. $storage->resetCache([$this->entityId]);
  265. $entity = $storage->load($this->entityId);
  266. $this->assertFalse($this->manager->getTranslationMetadata($entity->getTranslation($langcode))->isPublished(), 'The translation has been correctly unpublished.');
  267. }
  268. }
  269. // Check that the last published translation cannot be unpublished.
  270. $this->drupalGet($entity->urlInfo('edit-form'));
  271. $this->assertFieldByXPath('//input[@name="content_translation[status]" and @disabled="disabled"]', TRUE, 'The last translation is published and cannot be unpublished.');
  272. }
  273. /**
  274. * Tests the translation authoring information.
  275. */
  276. protected function doTestAuthoringInfo() {
  277. $storage = $this->container->get('entity_type.manager')
  278. ->getStorage($this->entityTypeId);
  279. $storage->resetCache([$this->entityId]);
  280. $entity = $storage->load($this->entityId);
  281. $values = array();
  282. // Post different authoring information for each translation.
  283. foreach ($this->langcodes as $index => $langcode) {
  284. $user = $this->drupalCreateUser();
  285. $values[$langcode] = array(
  286. 'uid' => $user->id(),
  287. 'created' => REQUEST_TIME - mt_rand(0, 1000),
  288. );
  289. $edit = array(
  290. 'content_translation[uid]' => $user->getUsername(),
  291. 'content_translation[created]' => format_date($values[$langcode]['created'], 'custom', 'Y-m-d H:i:s O'),
  292. );
  293. $url = $entity->urlInfo('edit-form', array('language' => ConfigurableLanguage::load($langcode)));
  294. $this->drupalPostForm($url, $edit, $this->getFormSubmitAction($entity, $langcode));
  295. }
  296. $storage = $this->container->get('entity_type.manager')
  297. ->getStorage($this->entityTypeId);
  298. $storage->resetCache([$this->entityId]);
  299. $entity = $storage->load($this->entityId);
  300. foreach ($this->langcodes as $langcode) {
  301. $metadata = $this->manager->getTranslationMetadata($entity->getTranslation($langcode));
  302. $this->assertEqual($metadata->getAuthor()->id(), $values[$langcode]['uid'], 'Translation author correctly stored.');
  303. $this->assertEqual($metadata->getCreatedTime(), $values[$langcode]['created'], 'Translation date correctly stored.');
  304. }
  305. // Try to post non valid values and check that they are rejected.
  306. $langcode = 'en';
  307. $edit = array(
  308. // User names have by default length 8.
  309. 'content_translation[uid]' => $this->randomMachineName(12),
  310. 'content_translation[created]' => '19/11/1978',
  311. );
  312. $this->drupalPostForm($entity->urlInfo('edit-form'), $edit, $this->getFormSubmitAction($entity, $langcode));
  313. $this->assertTrue($this->xpath('//div[contains(@class, "error")]//ul'), 'Invalid values generate a list of form errors.');
  314. $metadata = $this->manager->getTranslationMetadata($entity->getTranslation($langcode));
  315. $this->assertEqual($metadata->getAuthor()->id(), $values[$langcode]['uid'], 'Translation author correctly kept.');
  316. $this->assertEqual($metadata->getCreatedTime(), $values[$langcode]['created'], 'Translation date correctly kept.');
  317. }
  318. /**
  319. * Tests translation deletion.
  320. */
  321. protected function doTestTranslationDeletion() {
  322. // Confirm and delete a translation.
  323. $this->drupalLogin($this->translator);
  324. $langcode = 'fr';
  325. $storage = $this->container->get('entity_type.manager')
  326. ->getStorage($this->entityTypeId);
  327. $storage->resetCache([$this->entityId]);
  328. $entity = $storage->load($this->entityId);
  329. $language = ConfigurableLanguage::load($langcode);
  330. $url = $entity->urlInfo('edit-form', array('language' => $language));
  331. $this->drupalPostForm($url, array(), t('Delete translation'));
  332. $this->drupalPostForm(NULL, array(), t('Delete @language translation', array('@language' => $language->getName())));
  333. $storage->resetCache([$this->entityId]);
  334. $entity = $storage->load($this->entityId, TRUE);
  335. if ($this->assertTrue(is_object($entity), 'Entity found')) {
  336. $translations = $entity->getTranslationLanguages();
  337. $this->assertTrue(count($translations) == 2 && empty($translations[$langcode]), 'Translation successfully deleted.');
  338. }
  339. // Check that the translator cannot delete the original translation.
  340. $args = [$this->entityTypeId => $entity->id(), 'language' => 'en'];
  341. $this->drupalGet(Url::fromRoute("entity.$this->entityTypeId.content_translation_delete", $args));
  342. $this->assertResponse(403);
  343. }
  344. /**
  345. * Returns an array of entity field values to be tested.
  346. */
  347. protected function getNewEntityValues($langcode) {
  348. return array($this->fieldName => array(array('value' => $this->randomMachineName(16))));
  349. }
  350. /**
  351. * Returns an edit array containing the values to be posted.
  352. */
  353. protected function getEditValues($values, $langcode, $new = FALSE) {
  354. $edit = $values[$langcode];
  355. $langcode = $new ? LanguageInterface::LANGCODE_NOT_SPECIFIED : $langcode;
  356. foreach ($values[$langcode] as $property => $value) {
  357. if (is_array($value)) {
  358. $edit["{$property}[0][value]"] = $value[0]['value'];
  359. unset($edit[$property]);
  360. }
  361. }
  362. return $edit;
  363. }
  364. /**
  365. * Returns the form action value when submitting a new translation.
  366. *
  367. * @param \Drupal\Core\Entity\EntityInterface $entity
  368. * The entity being tested.
  369. * @param string $langcode
  370. * Language code for the form.
  371. *
  372. * @return string
  373. * Name of the button to hit.
  374. */
  375. protected function getFormSubmitActionForNewTranslation(EntityInterface $entity, $langcode) {
  376. $entity->addTranslation($langcode, $entity->toArray());
  377. return $this->getFormSubmitAction($entity, $langcode);
  378. }
  379. /**
  380. * Returns the form action value to be used to submit the entity form.
  381. *
  382. * @param \Drupal\Core\Entity\EntityInterface $entity
  383. * The entity being tested.
  384. * @param string $langcode
  385. * Language code for the form.
  386. *
  387. * @return string
  388. * Name of the button to hit.
  389. */
  390. protected function getFormSubmitAction(EntityInterface $entity, $langcode) {
  391. return t('Save') . $this->getFormSubmitSuffix($entity, $langcode);
  392. }
  393. /**
  394. * Returns appropriate submit button suffix based on translatability.
  395. *
  396. * @param \Drupal\Core\Entity\EntityInterface $entity
  397. * The entity being tested.
  398. * @param string $langcode
  399. * Language code for the form.
  400. *
  401. * @return string
  402. * Submit button suffix based on translatability.
  403. */
  404. protected function getFormSubmitSuffix(EntityInterface $entity, $langcode) {
  405. return '';
  406. }
  407. /**
  408. * Returns the translation object to use to retrieve the translated values.
  409. *
  410. * @param \Drupal\Core\Entity\EntityInterface $entity
  411. * The entity being tested.
  412. * @param string $langcode
  413. * The language code identifying the translation to be retrieved.
  414. *
  415. * @return \Drupal\Core\TypedData\TranslatableInterface
  416. * The translation object to act on.
  417. */
  418. protected function getTranslation(EntityInterface $entity, $langcode) {
  419. return $entity->getTranslation($langcode);
  420. }
  421. /**
  422. * Returns the value for the specified property in the given language.
  423. *
  424. * @param \Drupal\Core\Entity\EntityInterface $translation
  425. * The translation object the property value should be retrieved from.
  426. * @param string $property
  427. * The property name.
  428. * @param string $langcode
  429. * The property value.
  430. *
  431. * @return
  432. * The property value.
  433. */
  434. protected function getValue(EntityInterface $translation, $property, $langcode) {
  435. $key = $property == 'user_id' ? 'target_id' : 'value';
  436. return $translation->get($property)->{$key};
  437. }
  438. /**
  439. * Returns the name of the field that implements the changed timestamp.
  440. *
  441. * @param \Drupal\Core\Entity\EntityInterface $entity
  442. * The entity being tested.
  443. *
  444. * @return string
  445. * The field name.
  446. */
  447. protected function getChangedFieldName($entity) {
  448. return $entity->hasField('content_translation_changed') ? 'content_translation_changed' : 'changed';
  449. }
  450. /**
  451. * Tests edit content translation.
  452. */
  453. protected function doTestTranslationEdit() {
  454. $storage = $this->container->get('entity_type.manager')
  455. ->getStorage($this->entityTypeId);
  456. $storage->resetCache([$this->entityId]);
  457. $entity = $storage->load($this->entityId);
  458. $languages = $this->container->get('language_manager')->getLanguages();
  459. foreach ($this->langcodes as $langcode) {
  460. // We only want to test the title for non-english translations.
  461. if ($langcode != 'en') {
  462. $options = array('language' => $languages[$langcode]);
  463. $url = $entity->urlInfo('edit-form', $options);
  464. $this->drupalGet($url);
  465. $this->assertRaw($entity->getTranslation($langcode)->label());
  466. }
  467. }
  468. }
  469. /**
  470. * Tests the basic translation workflow.
  471. */
  472. protected function doTestTranslationChanged() {
  473. $storage = $this->container->get('entity_type.manager')
  474. ->getStorage($this->entityTypeId);
  475. $storage->resetCache([$this->entityId]);
  476. $entity = $storage->load($this->entityId);
  477. $changed_field_name = $this->getChangedFieldName($entity);
  478. $definition = $entity->getFieldDefinition($changed_field_name);
  479. $config = $definition->getConfig($entity->bundle());
  480. foreach ([FALSE, TRUE] as $translatable_changed_field) {
  481. if ($definition->isTranslatable()) {
  482. // For entities defining a translatable changed field we want to test
  483. // the correct behavior of that field even if the translatability is
  484. // revoked. In that case the changed timestamp should be synchronized
  485. // across all translations.
  486. $config->setTranslatable($translatable_changed_field);
  487. $config->save();
  488. }
  489. elseif ($translatable_changed_field) {
  490. // For entities defining a non-translatable changed field we cannot
  491. // declare the field as translatable on the fly by modifying its config
  492. // because the schema doesn't support this.
  493. break;
  494. }
  495. foreach ($entity->getTranslationLanguages() as $language) {
  496. // Ensure different timestamps.
  497. sleep(1);
  498. $langcode = $language->getId();
  499. $edit = array(
  500. $this->fieldName . '[0][value]' => $this->randomString(),
  501. );
  502. $edit_path = $entity->urlInfo('edit-form', array('language' => $language));
  503. $this->drupalPostForm($edit_path, $edit, $this->getFormSubmitAction($entity, $langcode));
  504. $storage = $this->container->get('entity_type.manager')
  505. ->getStorage($this->entityTypeId);
  506. $storage->resetCache([$this->entityId]);
  507. $entity = $storage->load($this->entityId);
  508. $this->assertEqual(
  509. $entity->getChangedTimeAcrossTranslations(), $entity->getTranslation($langcode)->getChangedTime(),
  510. format_string('Changed time for language %language is the latest change over all languages.', array('%language' => $language->getName()))
  511. );
  512. }
  513. $timestamps = array();
  514. foreach ($entity->getTranslationLanguages() as $language) {
  515. $next_timestamp = $entity->getTranslation($language->getId())->getChangedTime();
  516. if (!in_array($next_timestamp, $timestamps)) {
  517. $timestamps[] = $next_timestamp;
  518. }
  519. }
  520. if ($translatable_changed_field) {
  521. $this->assertEqual(
  522. count($timestamps), count($entity->getTranslationLanguages()),
  523. 'All timestamps from all languages are different.'
  524. );
  525. }
  526. else {
  527. $this->assertEqual(
  528. count($timestamps), 1,
  529. 'All timestamps from all languages are identical.'
  530. );
  531. }
  532. }
  533. }
  534. /**
  535. * Test the changed time after API and FORM save without changes.
  536. */
  537. public function doTestChangedTimeAfterSaveWithoutChanges() {
  538. $storage = $this->container->get('entity_type.manager')
  539. ->getStorage($this->entityTypeId);
  540. $storage->resetCache([$this->entityId]);
  541. $entity = $storage->load($this->entityId);
  542. // Test only entities, which implement the EntityChangedInterface.
  543. if ($entity->getEntityType()->isSubclassOf('Drupal\Core\Entity\EntityChangedInterface')) {
  544. $changed_timestamp = $entity->getChangedTime();
  545. $entity->save();
  546. $storage = $this->container->get('entity_type.manager')
  547. ->getStorage($this->entityTypeId);
  548. $storage->resetCache([$this->entityId]);
  549. $entity = $storage->load($this->entityId);
  550. $this->assertEqual($changed_timestamp, $entity->getChangedTime(), 'The entity\'s changed time wasn\'t updated after API save without changes.');
  551. // Ensure different save timestamps.
  552. sleep(1);
  553. // Save the entity on the regular edit form.
  554. $language = $entity->language();
  555. $edit_path = $entity->urlInfo('edit-form', array('language' => $language));
  556. $this->drupalPostForm($edit_path, [], $this->getFormSubmitAction($entity, $language->getId()));
  557. $storage->resetCache([$this->entityId]);
  558. $entity = $storage->load($this->entityId);
  559. $this->assertNotEqual($changed_timestamp, $entity->getChangedTime(), 'The entity\'s changed time was updated after form save without changes.');
  560. }
  561. }
  562. }