diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php index 40da4b8..ee97d18 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php @@ -52,6 +52,16 @@ protected $values = array(); /** + * The list of fields that have changed. + * + * The list is keyed by field name and language code, for example + * $changedFields['body']['it'] = TRUE + * + * @var array + */ + protected $changedFields = array(); + + /** * The array of fields, each being an instance of FieldItemListInterface. * * @var array @@ -428,6 +438,14 @@ public function getFields($include_computed = TRUE) { /** * {@inheritdoc} */ + public function resetChangedFieldList() { + $this->changedFields = array(); + return $this; + } + + /** + * {@inheritdoc} + */ public function getIterator() { return new \ArrayIterator($this->getFields()); } @@ -592,6 +610,38 @@ public function onChange($name) { } break; } + + if (!isset($this->changedFields[$name][$this->activeLangcode]) && isset($this->values[$name][$this->activeLangcode])) { + $items = $this->get($name); + $value = $this->values[$name][$this->activeLangcode]; + // If the field items does not consist of the original value, we mark them + // changed. + if (!$items->valuesEqual($value)) { + $this->changedFields[$name][$this->activeLangcode] = TRUE; + } + } + } + + /** + * {@inheritdoc} + */ + public function hasChanges() { + if ($this->hasChangesAcrossTranslations()) { + foreach ($this->changedFields as $langcodes) { + if (isset($langcodes[$this->activeLangcode])) { + return TRUE; + } + } + } + + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function hasChangesAcrossTranslations() { + return (bool) count($this->changedFields); } /** @@ -728,6 +778,12 @@ public function addTranslation($langcode, array $values = array()) { $values[$this->defaultLangcodeKey] = FALSE; $this->translations[$langcode]['status'] = static::TRANSLATION_CREATED; + // To ensure a correct changed timestamp on a new translation we need + // hasChanges() to return TRUE in ChangedItem::preSave(). + // ChangedItem can not use isNew() for that because it doesn't work for + // translations. After the entity has been saved resetChangedFields() will + // remove this dummy entry. + $this->changedFields['__dummy'][$langcode] = TRUE; $translation = $this->getTranslation($langcode); $definitions = $translation->getFieldDefinitions(); @@ -752,6 +808,12 @@ public function removeTranslation($langcode) { } } $this->translations[$langcode]['status'] = static::TRANSLATION_REMOVED; + foreach (array_keys($this->changedFields) as $name) { + unset($this->changedFields[$name][$langcode]); + if (empty($this->changedFields[$name])) { + unset($this->changedFields[$name]); + } + } } else { $message = 'The specified translation (@langcode) cannot be removed.'; @@ -1020,4 +1082,12 @@ public static function bundleFieldDefinitions(EntityTypeInterface $entity_type, return array(); } + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageInterface $storage, $update = TRUE) { + parent::postSave($storage, $update); + $this->resetChangedFieldList(); + } + } diff --git a/core/lib/Drupal/Core/Entity/EntityChangedInterface.php b/core/lib/Drupal/Core/Entity/EntityChangedInterface.php index f5ee395..f8892e3 100644 --- a/core/lib/Drupal/Core/Entity/EntityChangedInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityChangedInterface.php @@ -22,11 +22,20 @@ interface EntityChangedInterface { /** - * Returns the timestamp of the last entity change. + * Returns the timestamp of the last entity change of the current + * translation. * * @return int * The timestamp of the last entity save operation. */ public function getChangedTime(); + /** + * Returns the timestamp of the last entity change across all translations. + * + * @return int + * The timestamp of the last entity save operation across all + * translations. + */ + public function getChangedTimeAcrossTranslations(); } diff --git a/core/lib/Drupal/Core/Entity/EntityChangedTrait.php b/core/lib/Drupal/Core/Entity/EntityChangedTrait.php new file mode 100644 index 0000000..a9cd7f0 --- /dev/null +++ b/core/lib/Drupal/Core/Entity/EntityChangedTrait.php @@ -0,0 +1,33 @@ +getUntranslated()->getChangedTime(); + foreach ($this->getTranslationLanguages(FALSE) as $language) { + $translation_changed = $this->getTranslation($language->getId())->getChangedTime(); + if ($translation_changed > $changed) { + $changed = $translation_changed; + } + } + return $changed; + } + +} diff --git a/core/lib/Drupal/Core/Entity/FieldableEntityInterface.php b/core/lib/Drupal/Core/Entity/FieldableEntityInterface.php index 566f9b2..a2d9297 100644 --- a/core/lib/Drupal/Core/Entity/FieldableEntityInterface.php +++ b/core/lib/Drupal/Core/Entity/FieldableEntityInterface.php @@ -186,6 +186,13 @@ public function set($field_name, $value, $notify = TRUE); public function getFields($include_computed = TRUE); /** + * Resets the list an array of changed field names. + * + * @return $this + */ + public function resetChangedFieldList(); + + /** * Reacts to changes to a field. * * Note that this is invoked after any changes have been applied. @@ -204,6 +211,25 @@ public function getFields($include_computed = TRUE); public function onChange($field_name); /** + * Determines if at least one field item (of the current translation) has been + * changed. + * + * @return bool + * TRUE if the current translation of the entity has changes; + * FALSE otherwise. + */ + public function hasChanges(); + + /** + * Determines if at least one field item has been changed. + * + * @return bool + * TRUE if the the entity has any changes across all translations; + * FALSE otherwise. + */ + public function hasChangesAcrossTranslations(); + + /** * Validates the currently set values. * * @return \Symfony\Component\Validator\ConstraintViolationListInterface diff --git a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityChangedConstraintValidator.php b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityChangedConstraintValidator.php index fe81a63..8fd44da 100644 --- a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityChangedConstraintValidator.php +++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityChangedConstraintValidator.php @@ -23,8 +23,9 @@ public function validate($entity, Constraint $constraint) { /** @var \Drupal\Core\Entity\EntityInterface $entity */ if (!$entity->isNew()) { $saved_entity = \Drupal::entityManager()->getStorage($entity->getEntityTypeId())->loadUnchanged($entity->id()); - - if ($saved_entity && $saved_entity->getChangedTime() > $entity->getChangedTime()) { + // A change to any other translation must add a violation to the current + // translation because there might be untranslatable shared fields. + if ($saved_entity && $saved_entity->getChangedTimeAcrossTranslations() > $entity->getChangedTime()) { $this->context->addViolation($constraint->message); } } diff --git a/core/lib/Drupal/Core/Field/FieldItemList.php b/core/lib/Drupal/Core/Field/FieldItemList.php index 86bac79..16cdcc9 100644 --- a/core/lib/Drupal/Core/Field/FieldItemList.php +++ b/core/lib/Drupal/Core/Field/FieldItemList.php @@ -375,9 +375,19 @@ protected function defaultValueWidget(FormStateInterface $form_state) { * {@inheritdoc} */ public function equals(FieldItemListInterface $list_to_compare) { + return $this->valuesEqual($list_to_compare->getValue()); + } + + /** + * {@inheritdoc} + */ + public function valuesEqual($value2) { + if (!is_array($value2)) { + $value2 = array(0 => $value2); + } $columns = $this->getFieldDefinition()->getFieldStorageDefinition()->getColumns(); $count1 = count($this); - $count2 = count($list_to_compare); + $count2 = count($value2); if ($count1 === 0 && $count2 === 0) { // Both are empty we can safely assume that it did not change. return TRUE; @@ -387,7 +397,6 @@ public function equals(FieldItemListInterface $list_to_compare) { return FALSE; } $value1 = $this->getValue(); - $value2 = $list_to_compare->getValue(); if ($value1 === $value2) { return TRUE; } diff --git a/core/lib/Drupal/Core/Field/FieldItemListInterface.php b/core/lib/Drupal/Core/Field/FieldItemListInterface.php index e4ea12c..ac913fe 100644 --- a/core/lib/Drupal/Core/Field/FieldItemListInterface.php +++ b/core/lib/Drupal/Core/Field/FieldItemListInterface.php @@ -271,4 +271,15 @@ public static function processDefaultValue($default_value, FieldableEntityInterf */ public function equals(FieldItemListInterface $list_to_compare); + /** + * Determines if the field items match a given value list. + * + * @param array $values + * The value list to compare to. + * + * @return bool + * TRUE if the field item list matches $values, FALSE if not. + */ + public function valuesEqual($values); + } diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/ChangedItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/ChangedItem.php index 81625d4..8b3a048 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/ChangedItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/ChangedItem.php @@ -7,6 +7,8 @@ namespace Drupal\Core\Field\Plugin\Field\FieldType; +use Drupal\Core\Entity\FieldableEntityInterface; + /** * Defines the 'changed' entity field type. * @@ -30,7 +32,27 @@ class ChangedItem extends CreatedItem { */ public function preSave() { parent::preSave(); - $this->value = REQUEST_TIME; + + $entity = $this->getEntity(); + if ($entity->isNew() || !$this->getFieldDefinition()->isTranslatable()) { + $this->value = REQUEST_TIME; + } + else { + $field_name = $this->getFieldDefinition()->getName(); + $original = clone $entity->original; + if ($this->getFieldDefinition()->isTranslatable()) { + $original = $original->getTranslation($entity->language()->getId()); + } + // If the timestamp has not been set explicitly auto detect a modification + // of the current translation and set the timestamp if needed. + // An example of setting the timestamp explicitly is + // \Drupal\content_translation\ContentTranslationMetadataWrapperInterface::setChangedTime() + if ($this->value == $original->{$field_name}->value) { + if ($entity->hasChanges()) { + $this->value = REQUEST_TIME; + } + } + } } } diff --git a/core/modules/block_content/src/Entity/BlockContent.php b/core/modules/block_content/src/Entity/BlockContent.php index 046640c..c221863 100644 --- a/core/modules/block_content/src/Entity/BlockContent.php +++ b/core/modules/block_content/src/Entity/BlockContent.php @@ -8,6 +8,7 @@ namespace Drupal\block_content\Entity; use Drupal\Core\Entity\ContentEntityBase; +use Drupal\Core\Entity\EntityChangedTrait; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\BaseFieldDefinition; @@ -77,6 +78,8 @@ class BlockContent extends ContentEntityBase implements BlockContentInterface { */ protected $theme; + use EntityChangedTrait; + /** * {@inheritdoc} */ diff --git a/core/modules/comment/src/CommentInterface.php b/core/modules/comment/src/CommentInterface.php index c141706..0d558b5 100644 --- a/core/modules/comment/src/CommentInterface.php +++ b/core/modules/comment/src/CommentInterface.php @@ -197,14 +197,6 @@ public function getCreatedTime(); public function setCreatedTime($created); /** - * Returns the timestamp of when the comment was updated. - * - * @return int - * The timestamp of when the comment was updated. - */ - public function getChangedTime(); - - /** * Checks if the comment is published. * * @return bool diff --git a/core/modules/comment/src/CommentStatistics.php b/core/modules/comment/src/CommentStatistics.php index 588c831..e1f609f 100644 --- a/core/modules/comment/src/CommentStatistics.php +++ b/core/modules/comment/src/CommentStatistics.php @@ -126,8 +126,9 @@ public function create(FieldableEntityInterface $entity, $fields) { } // Default to REQUEST_TIME when entity does not have a changed property. $last_comment_timestamp = REQUEST_TIME; + // @todo make comment statistics language aware. if ($entity instanceof EntityChangedInterface) { - $last_comment_timestamp = $entity->getChangedTime(); + $last_comment_timestamp = $entity->getChangedTimeAcrossTranslations(); } $query->values(array( 'entity_id' => $entity->id(), @@ -243,9 +244,9 @@ public function update(CommentInterface $comment) { ->fields(array( 'cid' => 0, 'comment_count' => 0, - // Use the created date of the entity if it's set, or default to + // Use the changed date of the entity if it's set, or default to // REQUEST_TIME. - 'last_comment_timestamp' => ($entity instanceof EntityChangedInterface) ? $entity->getChangedTime() : REQUEST_TIME, + 'last_comment_timestamp' => ($entity instanceof EntityChangedInterface) ? $entity->getChangedTimeAcrossTranslations() : REQUEST_TIME, 'last_comment_name' => '', 'last_comment_uid' => $last_comment_uid, )) diff --git a/core/modules/comment/src/Entity/Comment.php b/core/modules/comment/src/Entity/Comment.php index 798a69f..c05b85d 100644 --- a/core/modules/comment/src/Entity/Comment.php +++ b/core/modules/comment/src/Entity/Comment.php @@ -10,6 +10,7 @@ use Drupal\Component\Utility\Number; use Drupal\Core\Entity\ContentEntityBase; use Drupal\comment\CommentInterface; +use Drupal\Core\Entity\EntityChangedTrait; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\BaseFieldDefinition; @@ -63,6 +64,8 @@ class Comment extends ContentEntityBase implements CommentInterface { */ protected $threadLock = ''; + use EntityChangedTrait; + /** * {@inheritdoc} */ diff --git a/core/modules/comment/src/Form/CommentAdminOverview.php b/core/modules/comment/src/Form/CommentAdminOverview.php index 9205de8..c01e32f 100644 --- a/core/modules/comment/src/Form/CommentAdminOverview.php +++ b/core/modules/comment/src/Form/CommentAdminOverview.php @@ -213,7 +213,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $type = ' '#url' => $commented_entity->urlInfo(), ), ), - 'changed' => $this->dateFormatter->format($comment->getChangedTime(), 'short'), + 'changed' => $this->dateFormatter->format($comment->getChangedTimeAcrossTranslations(), 'short'), ); $comment_uri_options = $comment->urlInfo()->getOptions(); $links = array(); diff --git a/core/modules/comment/src/Tests/CommentTokenReplaceTest.php b/core/modules/comment/src/Tests/CommentTokenReplaceTest.php index a5eaad8..c45c0f7 100644 --- a/core/modules/comment/src/Tests/CommentTokenReplaceTest.php +++ b/core/modules/comment/src/Tests/CommentTokenReplaceTest.php @@ -61,7 +61,7 @@ function testCommentTokenReplacement() { $tests['[comment:url]'] = $comment->url('canonical', $url_options + array('fragment' => 'comment-' . $comment->id())); $tests['[comment:edit-url]'] = $comment->url('edit-form', $url_options); $tests['[comment:created:since]'] = \Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - $comment->getCreatedTime(), 2, $language_interface->getId()); - $tests['[comment:changed:since]'] = \Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - $comment->getChangedTime(), 2, $language_interface->getId()); + $tests['[comment:changed:since]'] = \Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - $comment->getChangedTimeAcrossTranslations(), 2, $language_interface->getId()); $tests['[comment:parent:cid]'] = $comment->hasParentComment() ? $comment->getParentComment()->id() : NULL; $tests['[comment:parent:title]'] = SafeMarkup::checkPlain($parent_comment->getSubject()); $tests['[comment:entity]'] = SafeMarkup::checkPlain($node->getTitle()); diff --git a/core/modules/content_translation/src/ContentTranslationMetadataWrapperInterface.php b/core/modules/content_translation/src/ContentTranslationMetadataWrapperInterface.php index 4a0a65b..cfc8622 100644 --- a/core/modules/content_translation/src/ContentTranslationMetadataWrapperInterface.php +++ b/core/modules/content_translation/src/ContentTranslationMetadataWrapperInterface.php @@ -7,8 +7,6 @@ namespace Drupal\content_translation; -use Drupal\Core\Entity\EntityChangedInterface; -use Drupal\Core\Entity\EntityInterface; use Drupal\user\UserInterface; /** @@ -17,7 +15,7 @@ * This acts as a wrapper for an entity translation object, encapsulating the * logic needed to retrieve translation metadata. */ -interface ContentTranslationMetadataWrapperInterface extends EntityChangedInterface { +interface ContentTranslationMetadataWrapperInterface { /** * Retrieves the source language for this translation. @@ -110,6 +108,15 @@ public function getCreatedTime(); public function setCreatedTime($timestamp); /** + * Returns the timestamp of the last entity change of the current + * translation. + * + * @return int + * The timestamp of the last entity save operation. + */ + public function getChangedTime(); + + /** * Sets the translation modification timestamp. * * @param int $timestamp diff --git a/core/modules/content_translation/src/Tests/ContentTranslationUITest.php b/core/modules/content_translation/src/Tests/ContentTranslationUITest.php index 5a7429c..78f5585 100644 --- a/core/modules/content_translation/src/Tests/ContentTranslationUITest.php +++ b/core/modules/content_translation/src/Tests/ContentTranslationUITest.php @@ -42,6 +42,7 @@ function testTranslationUI() { $this->doTestPublishedStatus(); $this->doTestAuthoringInfo(); $this->doTestTranslationEdit(); + $this->doTestTranslationChanged(); $this->doTestTranslationDeletion(); } @@ -384,4 +385,52 @@ protected function doTestTranslationEdit() { } } + /** + * Tests the basic translation workflow. + */ + protected function doTestTranslationChanged() { + $entity = entity_load($this->entityTypeId, $this->entityId, TRUE); + for ($i = 1; $i <= 2; $i++) { + foreach ($entity->getTranslationLanguages() as $language) { + // Ensure different timestamps. + sleep(2); + + $langcode = $language->getId(); + $counters[$langcode] = $i; + + // Set the title and the custom translatable field and the revision log + // to predictable values containing a counter. + $edit = array( + $this->fieldName . '[0][value]' => $this->fieldName . ' ' . $langcode . ' ' . $i, + ); + $edit_path = $entity->urlInfo('edit-form', array('language' => $language)); + $this->drupalPostForm($edit_path, $edit, $this->getFormSubmitAction($entity, $langcode)); + + $entity = entity_load($this->entityTypeId, $this->entityId, TRUE); + $this->assertEqual( + $entity->getChangedTimeAcrossTranslations(), $entity->getTranslation($langcode)->getChangedTime(), + format_string('Changed time for language %language is the latest change over all languages.', array('%language' => $language->getName())) + ); + + if ($i > 1) { + // Because the base of this test created the translations + // without a sleep command, the changed timestamps might not differ + // until the second run of that loop. + $timestamps = array(); + foreach ($entity->getTranslationLanguages() as $language) { + $next_timestamp = $entity->getTranslation($language->getId()) + ->getChangedTime(); + if (!in_array($next_timestamp, $timestamps)) { + $timestamps[] = $next_timestamp; + } + } + $this->assertTrue( + count($timestamps) == count($entity->getTranslationLanguages()), + 'All timestamps from all languages are different.' + ); + } + } + } + } + } diff --git a/core/modules/file/src/Entity/File.php b/core/modules/file/src/Entity/File.php index 42de0ae..9306ee5 100644 --- a/core/modules/file/src/Entity/File.php +++ b/core/modules/file/src/Entity/File.php @@ -8,10 +8,10 @@ namespace Drupal\file\Entity; use Drupal\Core\Entity\ContentEntityBase; +use Drupal\Core\Entity\EntityChangedTrait; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\BaseFieldDefinition; -use Drupal\Core\Language\LanguageInterface; use Drupal\file\FileInterface; use Drupal\user\UserInterface; @@ -38,6 +38,8 @@ */ class File extends ContentEntityBase implements FileInterface { + use EntityChangedTrait; + /** * {@inheritdoc} */ diff --git a/core/modules/menu_link_content/src/Entity/MenuLinkContent.php b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php index 5570a2a..a3ca073 100644 --- a/core/modules/menu_link_content/src/Entity/MenuLinkContent.php +++ b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php @@ -8,6 +8,7 @@ namespace Drupal\menu_link_content\Entity; use Drupal\Core\Entity\ContentEntityBase; +use Drupal\Core\Entity\EntityChangedTrait; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\BaseFieldDefinition; @@ -59,6 +60,8 @@ class MenuLinkContent extends ContentEntityBase implements MenuLinkContentInterf */ protected $insidePlugin = FALSE; + use EntityChangedTrait; + /** * {@inheritdoc} */ diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 4550a6d..16b0c02 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -741,8 +741,8 @@ function node_page_title(NodeInterface $node) { * @param $nid * The ID of a node. * @param string $langcode - * (optional) The language the node has been last modified in. Defaults to the - * node language. + * (optional) The language the node has been last modified in. If omitted the + * last changed time across all translations will be returned. * * @return string * A unix timestamp indicating the last time the node was changed. @@ -751,8 +751,9 @@ function node_page_title(NodeInterface $node) { * for validation, which will be done by EntityChangedConstraintValidator. */ function node_last_changed($nid, $langcode = NULL) { - $changed = \Drupal::entityManager()->getStorage('node')->loadUnchanged($nid)->getChangedTime(); - return $changed ? $changed : FALSE; + $node = \Drupal::entityManager()->getStorage('node')->loadUnchanged($nid); + $changed = $langcode ? $node->getTranslation($langcode)->getChangedTime() : $node->getChangedTimeAcrossTranslations(); + return $changed ?: FALSE; } /** diff --git a/core/modules/node/src/Entity/Node.php b/core/modules/node/src/Entity/Node.php index 7ae8f36..6dd1229 100644 --- a/core/modules/node/src/Entity/Node.php +++ b/core/modules/node/src/Entity/Node.php @@ -8,6 +8,7 @@ namespace Drupal\node\Entity; use Drupal\Core\Entity\ContentEntityBase; +use Drupal\Core\Entity\EntityChangedTrait; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\BaseFieldDefinition; @@ -69,6 +70,8 @@ */ class Node extends ContentEntityBase implements NodeInterface { + use EntityChangedTrait; + /** * {@inheritdoc} */ diff --git a/core/modules/node/src/NodeForm.php b/core/modules/node/src/NodeForm.php index 44fccbf..d88393d 100644 --- a/core/modules/node/src/NodeForm.php +++ b/core/modules/node/src/NodeForm.php @@ -289,7 +289,7 @@ protected function actions(array $form, FormStateInterface $form_state) { public function validate(array $form, FormStateInterface $form_state) { $node = parent::validate($form, $form_state); - if ($node->id() && (node_last_changed($node->id(), $this->getFormLangcode($form_state)) > $node->getChangedTime())) { + if ($node->id() && (node_last_changed($node->id()) > $node->getChangedTimeAcrossTranslations())) { $form_state->setErrorByName('changed', $this->t('The content on this page has either been modified by another user, or you have already submitted modifications using this form. As a result, your changes cannot be saved.')); } diff --git a/core/modules/node/src/Tests/NodeLastChangedTest.php b/core/modules/node/src/Tests/NodeLastChangedTest.php index 4444cc1..23fa4b4 100644 --- a/core/modules/node/src/Tests/NodeLastChangedTest.php +++ b/core/modules/node/src/Tests/NodeLastChangedTest.php @@ -38,7 +38,7 @@ function testNodeLastChanged() { // Test node last changed timestamp. $changed_timestamp = node_last_changed($node->id()); - $this->assertEqual($changed_timestamp, $node->getChangedTime(), 'Expected last changed timestamp returned.'); + $this->assertEqual($changed_timestamp, $node->getChangedTimeAcrossTranslations(), 'Expected last changed timestamp returned.'); $changed_timestamp = node_last_changed($node->id(), $node->language()->getId()); $this->assertEqual($changed_timestamp, $node->getChangedTime(), 'Expected last changed timestamp returned.'); diff --git a/core/modules/node/src/Tests/NodeSaveTest.php b/core/modules/node/src/Tests/NodeSaveTest.php index c9dc9c0..d1dafb5 100644 --- a/core/modules/node/src/Tests/NodeSaveTest.php +++ b/core/modules/node/src/Tests/NodeSaveTest.php @@ -130,7 +130,10 @@ function testTimestamps() { $node->save(); $node = $this->drupalGetNodeByTitle($edit['title'], TRUE); $this->assertEqual($node->getCreatedTime(), 979534800, 'Updating a node uses user-set "created" timestamp.'); - $this->assertNotEqual($node->getChangedTime(), 280299600, 'Updating a node does not use user-set "changed" timestamp.'); + // Allowing setting changed timestamps is required see + // Drupal\content_translation\ContentTranslationMetadataWrapper::setChangedTime($timestamp) + // for example + $this->assertEqual($node->getChangedTime(), 280299600, 'Updating a node uses user-set "changed" timestamp.'); } /** diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestConstraints.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestConstraints.php index 9f92e7a..61200f7 100644 --- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestConstraints.php +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestConstraints.php @@ -7,6 +7,7 @@ namespace Drupal\entity_test\Entity; +use Drupal\Core\Entity\EntityChangedTrait; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityChangedInterface; use Drupal\Core\Field\BaseFieldDefinition; @@ -32,6 +33,8 @@ */ class EntityTestConstraints extends EntityTest implements EntityChangedInterface { + use EntityChangedTrait; + /** * {@inheritdoc} */ diff --git a/core/modules/taxonomy/src/Entity/Term.php b/core/modules/taxonomy/src/Entity/Term.php index 7ca9dd7..db02d8a 100644 --- a/core/modules/taxonomy/src/Entity/Term.php +++ b/core/modules/taxonomy/src/Entity/Term.php @@ -8,6 +8,7 @@ namespace Drupal\taxonomy\Entity; use Drupal\Core\Entity\ContentEntityBase; +use Drupal\Core\Entity\EntityChangedTrait; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\BaseFieldDefinition; @@ -56,6 +57,8 @@ */ class Term extends ContentEntityBase implements TermInterface { + use EntityChangedTrait; + /** * {@inheritdoc} */ diff --git a/core/modules/user/src/Entity/User.php b/core/modules/user/src/Entity/User.php index 1a78c98..3389c80 100644 --- a/core/modules/user/src/Entity/User.php +++ b/core/modules/user/src/Entity/User.php @@ -8,6 +8,7 @@ namespace Drupal\user\Entity; use Drupal\Core\Entity\ContentEntityBase; +use Drupal\Core\Entity\EntityChangedTrait; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\BaseFieldDefinition; @@ -76,6 +77,8 @@ class User extends ContentEntityBase implements UserInterface { */ protected $hostname; + use EntityChangedTrait; + /** * {@inheritdoc} */