diff --git a/core/lib/Drupal/Core/Entity/EntityChangedInterface.php b/core/lib/Drupal/Core/Entity/EntityChangedInterface.php index 9337cb0d68..8f57863d94 100644 --- a/core/lib/Drupal/Core/Entity/EntityChangedInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityChangedInterface.php @@ -45,6 +45,23 @@ public function setChangedTime($timestamp); * The timestamp of the last entity save operation across all * translations. */ + public function getLatestChangedTimeAcrossTranslations(); + + /** + * Gets the timestamp of the last entity change across all translations. + * + * This method will return the highest timestamp across all translations. If + * you need to check that no translation was changed after a certain time, you + * have to check each translation's timestamp individually. + * + * @return int + * The timestamp of the last entity save operation across all + * translations. + * + * @deprecated in Drupal 8.5.x and will be removed before Drupal 9.0.0. Use + * \Drupal\Core\Entity\EntityChangedInterface::getLatestChangedTimeAcrossTranslations() + * instead. + */ public function getChangedTimeAcrossTranslations(); } diff --git a/core/lib/Drupal/Core/Entity/EntityChangedTrait.php b/core/lib/Drupal/Core/Entity/EntityChangedTrait.php index f94441bab6..654d4b17a4 100644 --- a/core/lib/Drupal/Core/Entity/EntityChangedTrait.php +++ b/core/lib/Drupal/Core/Entity/EntityChangedTrait.php @@ -8,13 +8,9 @@ trait EntityChangedTrait { /** - * Returns the timestamp of the last entity change across all translations. - * - * @return int - * The timestamp of the last entity save operation across all - * translations. + * {@inheritdoc} */ - public function getChangedTimeAcrossTranslations() { + public function getLatestChangedTimeAcrossTranslations() { $changed = $this->getUntranslated()->getChangedTime(); foreach ($this->getTranslationLanguages(FALSE) as $language) { $translation_changed = $this->getTranslation($language->getId())->getChangedTime(); @@ -24,6 +20,13 @@ public function getChangedTimeAcrossTranslations() { } /** + * {@inheritdoc} + */ + public function getChangedTimeAcrossTranslations() { + return $this->getLatestChangedTimeAcrossTranslations(); + } + + /** * Gets the timestamp of the last entity change for the current translation. * * @return int diff --git a/core/modules/comment/src/CommentStatistics.php b/core/modules/comment/src/CommentStatistics.php index b58452c594..869106adb1 100644 --- a/core/modules/comment/src/CommentStatistics.php +++ b/core/modules/comment/src/CommentStatistics.php @@ -124,7 +124,7 @@ public function create(FieldableEntityInterface $entity, $fields) { // @todo Make comment statistics language aware and add some tests. See // https://www.drupal.org/node/2318875 if ($entity instanceof EntityChangedInterface) { - $last_comment_timestamp = $entity->getChangedTimeAcrossTranslations(); + $last_comment_timestamp = $entity->getLatestChangedTimeAcrossTranslations(); } $query->values([ 'entity_id' => $entity->id(), @@ -242,7 +242,7 @@ public function update(CommentInterface $comment) { 'comment_count' => 0, // Use the changed date of the entity if it's set, or default to // REQUEST_TIME. - 'last_comment_timestamp' => ($entity instanceof EntityChangedInterface) ? $entity->getChangedTimeAcrossTranslations() : REQUEST_TIME, + 'last_comment_timestamp' => ($entity instanceof EntityChangedInterface) ? $entity->getLatestChangedTimeAcrossTranslations() : REQUEST_TIME, 'last_comment_name' => '', 'last_comment_uid' => $last_comment_uid, ]) diff --git a/core/modules/comment/src/Form/CommentAdminOverview.php b/core/modules/comment/src/Form/CommentAdminOverview.php index 1ffa2c618a..9adbae88ca 100644 --- a/core/modules/comment/src/Form/CommentAdminOverview.php +++ b/core/modules/comment/src/Form/CommentAdminOverview.php @@ -218,7 +218,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $type = ' '#url' => $commented_entity->urlInfo(), ], ], - 'changed' => $this->dateFormatter->format($comment->getChangedTimeAcrossTranslations(), 'short'), + 'changed' => $this->dateFormatter->format($comment->getLatestChangedTimeAcrossTranslations(), 'short'), ]; $comment_uri_options = $comment->urlInfo()->getOptions() + ['query' => $destination]; $links = []; diff --git a/core/modules/comment/tests/src/Functional/CommentTokenReplaceTest.php b/core/modules/comment/tests/src/Functional/CommentTokenReplaceTest.php index a7fe6b5e07..1f1ef3cd7d 100644 --- a/core/modules/comment/tests/src/Functional/CommentTokenReplaceTest.php +++ b/core/modules/comment/tests/src/Functional/CommentTokenReplaceTest.php @@ -78,7 +78,7 @@ public function testCommentTokenReplacement() { $tests['[comment:edit-url]'] = $comment->url('edit-form', $url_options); $tests['[comment:created]'] = \Drupal::service('date.formatter')->format($comment->getCreatedTime(), 'medium', ['langcode' => $language_interface->getId()]); $tests['[comment:created:since]'] = \Drupal::service('date.formatter')->formatTimeDiffSince($comment->getCreatedTime(), ['langcode' => $language_interface->getId()]); - $tests['[comment:changed:since]'] = \Drupal::service('date.formatter')->formatTimeDiffSince($comment->getChangedTimeAcrossTranslations(), ['langcode' => $language_interface->getId()]); + $tests['[comment:changed:since]'] = \Drupal::service('date.formatter')->formatTimeDiffSince($comment->getLatestChangedTimeAcrossTranslations(), ['langcode' => $language_interface->getId()]); $tests['[comment:parent:cid]'] = $comment->hasParentComment() ? $comment->getParentComment()->id() : NULL; $tests['[comment:parent:title]'] = $parent_comment->getSubject(); $tests['[comment:entity]'] = Html::escape($node->getTitle()); diff --git a/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php b/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php index e7a7dfc9dd..3de2197b56 100644 --- a/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php +++ b/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php @@ -566,7 +566,7 @@ protected function doTestTranslationChanged() { $storage->resetCache([$this->entityId]); $entity = $storage->load($this->entityId); $this->assertEqual( - $entity->getChangedTimeAcrossTranslations(), $entity->getTranslation($langcode)->getChangedTime(), + $entity->getLatestChangedTimeAcrossTranslations(), $entity->getTranslation($langcode)->getChangedTime(), format_string('Changed time for language %language is the latest change over all languages.', ['%language' => $language->getName()]) ); } diff --git a/core/modules/content_translation/tests/src/Functional/ContentTranslationUITestBase.php b/core/modules/content_translation/tests/src/Functional/ContentTranslationUITestBase.php index c755ac4906..6cd574dad7 100644 --- a/core/modules/content_translation/tests/src/Functional/ContentTranslationUITestBase.php +++ b/core/modules/content_translation/tests/src/Functional/ContentTranslationUITestBase.php @@ -564,7 +564,7 @@ protected function doTestTranslationChanged() { $storage->resetCache([$this->entityId]); $entity = $storage->load($this->entityId); $this->assertEqual( - $entity->getChangedTimeAcrossTranslations(), $entity->getTranslation($langcode)->getChangedTime(), + $entity->getLatestChangedTimeAcrossTranslations(), $entity->getTranslation($langcode)->getChangedTime(), format_string('Changed time for language %language is the latest change over all languages.', ['%language' => $language->getName()]) ); } diff --git a/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityChangedTest.php b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityChangedTest.php index aa0044bd8e..888bbb3da0 100644 --- a/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityChangedTest.php +++ b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityChangedTest.php @@ -83,7 +83,7 @@ public function testChanged() { ); $this->assertEqual( - $entity->getChangedTime(), $entity->getChangedTimeAcrossTranslations(), + $entity->getChangedTime(), $entity->getLatestChangedTimeAcrossTranslations(), 'Changed time of original language is the same as changed time across all translations.' ); @@ -105,7 +105,7 @@ public function testChanged() { ); $this->assertEqual( - $german->getChangedTime(), $entity->getChangedTimeAcrossTranslations(), + $german->getChangedTime(), $entity->getLatestChangedTimeAcrossTranslations(), 'Changed time of the German translation is the newest time across all translations.' ); @@ -162,7 +162,7 @@ public function testChanged() { ); $this->assertEqual( - $entity->getChangedTime(), $entity->getChangedTimeAcrossTranslations(), + $entity->getChangedTime(), $entity->getLatestChangedTimeAcrossTranslations(), 'Changed time of the original language is the newest time across all translations.' ); @@ -311,7 +311,7 @@ public function testRevisionChanged() { ); $this->assertEqual( - $entity->getChangedTime(), $entity->getChangedTimeAcrossTranslations(), + $entity->getChangedTime(), $entity->getLatestChangedTimeAcrossTranslations(), 'Changed time of original language is the same as changed time across all translations.' ); @@ -368,7 +368,7 @@ public function testRevisionChanged() { ); $this->assertEqual( - $german->getChangedTime(), $entity->getChangedTimeAcrossTranslations(), + $german->getChangedTime(), $entity->getLatestChangedTimeAcrossTranslations(), 'Changed time of the German translation is the newest time across all translations.' ); @@ -423,7 +423,7 @@ public function testRevisionChanged() { ); $this->assertEqual( - $german->getChangedTime(), $entity->getChangedTimeAcrossTranslations(), + $german->getChangedTime(), $entity->getLatestChangedTimeAcrossTranslations(), 'Changed time of the German translation is the newest time across all translations.' ); @@ -458,7 +458,7 @@ public function testRevisionChanged() { ); $this->assertEqual( - $french->getChangedTime(), $entity->getChangedTimeAcrossTranslations(), + $french->getChangedTime(), $entity->getLatestChangedTimeAcrossTranslations(), 'Changed time of the French translation is the newest time across all translations.' ); diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityValidationTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityValidationTest.php index 5289942b1c..3b1e63c5f5 100644 --- a/core/tests/Drupal/KernelTests/Core/Entity/EntityValidationTest.php +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityValidationTest.php @@ -226,23 +226,25 @@ public function testEntityChangedConstraintOnFormConcurrentEditing() { $translation = $entity->addTranslation('de'); $entity->save(); - $this->assertNotEquals($entity->getChangedTime(), $translation->getChangedTime()); + // Ensure that the new translation has a newer changed timestamp than the + // default translation. + $this->assertGreaterThan($entity->getChangedTime(), $translation->getChangedTime()); // Simulate concurrent form editing by saving the entity with an altered // non-translatable field in order for the changed timestamp to be updated - // across all entity translations. Afterwards we simulate form submitting of - // an uncached form by setting the previous timestamp of an entity - // translation on the saved entity object. This is what happens in the - // entity form API where we put the changed timestamp of the entity in a - // form hidden value and then set it on the entity which on form submit is - // loaded from the storage. Setting the initial changed timestamp on the - // entity loaded from the storage is used as a prevention from saving a form - // built with a previous version of the entity and thus reverting changes by - // other users. + // across all entity translations. $original_entity_time = $entity->getChangedTime(); $entity->set('not_translatable', $this->randomString()); $entity->save(); + // Simulate form submission of an uncached form by setting the previous + // timestamp of an entity translation on the saved entity object. This + // happens in the entity form API where we put the changed timestamp of + // the entity in a form hidden value and then set it on the entity which on + // form submit is loaded from the storage if the form is not yet cached. $entity->setChangedTime($original_entity_time); + // Setting the changed timestamp from the user input on the entity loaded + // from the storage is used as a prevention from saving a form built with a + // previous version of the entity and thus reverting changes by other users. $violations = $entity->validate(); $this->assertEquals(1, $violations->count()); $this->assertEqual($violations[0]->getMessage(), 'The content has either been modified by another user, or you have already submitted modifications. As a result, your changes cannot be saved.');