diff --git a/core/lib/Drupal/Core/Entity/ContentEntityForm.php b/core/lib/Drupal/Core/Entity/ContentEntityForm.php index 1fd99dcbc21de4159b609f248142dfab4ac1d54f..eed3cac3f9a369052a23063a8152c1afc47fb8e2 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityForm.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityForm.php @@ -62,6 +62,15 @@ public function form(array $form, FormStateInterface $form_state) { return $form; } + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + parent::submitForm($form, $form_state); + // Update the changed timestamp of the entity. + $this->updateChangedTime($this->entity); + } + /** * {@inheritdoc} */ @@ -269,4 +278,18 @@ public function updateFormLangcode($entity_type_id, EntityInterface $entity, arr } } + /** + * Updates the changed time of the entity. + * + * Applies only if the entity implements the EntityChangedInterface. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity updated with the submitted values. + */ + public function updateChangedTime(EntityInterface $entity) { + if ($entity->getEntityType()->isSubclassOf(EntityChangedInterface::class)) { + $entity->setChangedTime(REQUEST_TIME); + } + } + } diff --git a/core/lib/Drupal/Core/Entity/EntityChangedInterface.php b/core/lib/Drupal/Core/Entity/EntityChangedInterface.php index 5f031a38ae6291466acf595dca984d4690dd2092..2bd29ed65ec2956f192452584a4fe623fb304ffc 100644 --- a/core/lib/Drupal/Core/Entity/EntityChangedInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityChangedInterface.php @@ -29,6 +29,16 @@ interface EntityChangedInterface { */ public function getChangedTime(); + /** + * Sets the timestamp of the last entity change for the current translation. + * + * @param int $timestamp + * The timestamp of the last entity save operation. + * + * @return $this + */ + public function setChangedTime($timestamp); + /** * Gets the timestamp of the last entity change across all translations. * diff --git a/core/lib/Drupal/Core/Entity/EntityChangedTrait.php b/core/lib/Drupal/Core/Entity/EntityChangedTrait.php index 5b34d1abaf233ddd12af712662c5b3eaf412d51b..416d4aaf2e04d749212444df4e6ee00f874c8177 100644 --- a/core/lib/Drupal/Core/Entity/EntityChangedTrait.php +++ b/core/lib/Drupal/Core/Entity/EntityChangedTrait.php @@ -28,4 +28,26 @@ public function getChangedTimeAcrossTranslations() { return $changed; } + /** + * Gets the timestamp of the last entity change for the current translation. + * + * @return int + * The timestamp of the last entity save operation. + */ + public function getChangedTime() { + return $this->get('changed')->value; + } + + /** + * Sets the timestamp of the last entity change for the current translation. + * + * @param int $timestamp + * The timestamp of the last entity save operation. + * + * @return $this + */ + public function setChangedTime($timestamp) { + $this->set('changed', $timestamp); + return $this; + } } 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 8fd44dacc07019d689852360b42965c649bb7049..1d26d0b868c233d536809c9b7af1330d562301a5 100644 --- a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityChangedConstraintValidator.php +++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityChangedConstraintValidator.php @@ -25,7 +25,7 @@ public function validate($entity, Constraint $constraint) { $saved_entity = \Drupal::entityManager()->getStorage($entity->getEntityTypeId())->loadUnchanged($entity->id()); // 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()) { + if ($saved_entity && $saved_entity->getChangedTimeAcrossTranslations() > $entity->getChangedTimeAcrossTranslations()) { $this->context->addViolation($constraint->message); } } diff --git a/core/modules/block_content/src/Entity/BlockContent.php b/core/modules/block_content/src/Entity/BlockContent.php index 7f572bd495b8b5917d5222daffdced5949b50f07..2a052027107bf6098c9fccafa7886108a3658764 100644 --- a/core/modules/block_content/src/Entity/BlockContent.php +++ b/core/modules/block_content/src/Entity/BlockContent.php @@ -219,13 +219,6 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { return $fields; } - /** - * {@inheritdoc} - */ - public function getChangedTime() { - return $this->get('changed')->value; - } - /** * {@inheritdoc} */ diff --git a/core/modules/comment/src/Entity/Comment.php b/core/modules/comment/src/Entity/Comment.php index 56930493a046281cc06b76368326330148884d44..b20d9bb0d77e0f5b61b466b45166b79dcdddc9d3 100644 --- a/core/modules/comment/src/Entity/Comment.php +++ b/core/modules/comment/src/Entity/Comment.php @@ -527,13 +527,6 @@ public function setThread($thread) { return $this; } - /** - * {@inheritdoc} - */ - public function getChangedTime() { - return $this->get('changed')->value; - } - /** * {@inheritdoc} */ diff --git a/core/modules/content_translation/src/ContentTranslationHandler.php b/core/modules/content_translation/src/ContentTranslationHandler.php index 3979fff518237154069262ef64623cc236c103c3..d7eb42ab3e6c15146c6dd5b9026eaa8c6d0d512d 100644 --- a/core/modules/content_translation/src/ContentTranslationHandler.php +++ b/core/modules/content_translation/src/ContentTranslationHandler.php @@ -479,6 +479,13 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, En if (isset($form['actions']['delete'])) { $form['actions']['delete']['#submit'][] = array($this, 'entityFormDelete'); } + + // Handle entity form submission before the entity has been saved. + foreach (Element::children($form['actions']) as $action) { + if (isset($form['actions'][$action]['#type']) && $form['actions'][$action]['#type'] == 'submit') { + array_unshift($form['actions'][$action]['#submit'], [$this, 'entityFormSubmit']); + } + } } /** @@ -579,7 +586,6 @@ public function entityFormEntityBuild($entity_type, EntityInterface $entity, arr $metadata->setAuthor(!empty($values['uid']) ? User::load($values['uid']) : User::load(0)); $metadata->setPublished(!empty($values['status'])); $metadata->setCreatedTime(!empty($values['created']) ? strtotime($values['created']) : REQUEST_TIME); - $metadata->setChangedTime(REQUEST_TIME); $source_langcode = $this->getSourceLangcode($form_state); if ($source_langcode) { @@ -611,6 +617,30 @@ function entityFormValidate($form, FormStateInterface $form_state) { } } + /** + * Form submission handler for ContentTranslationHandler::entityFormAlter(). + * + * Updates metadata fields, which should be updated only after the validation + * has run and before the entity is saved. + */ + function entityFormSubmit($form, FormStateInterface $form_state) { + /** @var \Drupal\Core\Entity\ContentEntityFormInterface $form_object */ + $form_object = $form_state->getFormObject(); + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + $entity = $form_object->getEntity(); + + // ContentEntityForm::submit will update the changed timestamp on submit + // after the entity has been validated, so that it does not break the + // EntityChanged constraint validator. The content translation metadata + // field for the changed timestamp does not have such a constraint defined + // at the moment, but it is correct to update it's value in a submission + // handler as well and have the same logic like in the Form API. + if ($entity->hasField('content_translation_changed')) { + $metadata = $this->manager->getTranslationMetadata($entity); + $metadata->setChangedTime(REQUEST_TIME); + } + } + /** * Form submission handler for ContentTranslationHandler::entityFormAlter(). * diff --git a/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php b/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php index a5d6d09c15550d35efedb65d2d1308d2cf287c94..de8c349ece50536a710d83a81146f744ce0b933d 100644 --- a/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php +++ b/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php @@ -65,6 +65,7 @@ function testTranslationUI() { $this->doTestAuthoringInfo(); $this->doTestTranslationEdit(); $this->doTestTranslationChanged(); + $this->doTestChangedTimeAfterSaveWithoutChanges(); $this->doTestTranslationDeletion(); } @@ -546,4 +547,30 @@ protected function doTestTranslationChanged() { } } + /** + * Test the changed time after API and FORM save without changes. + */ + public function doTestChangedTimeAfterSaveWithoutChanges() { + $entity = entity_load($this->entityTypeId, $this->entityId, TRUE); + // Test only entities, which implement the EntityChangedInterface. + if ($entity->getEntityType()->isSubclassOf('Drupal\Core\Entity\EntityChangedInterface')) { + $changed_timestamp = $entity->getChangedTime(); + + $entity->save(); + $entity = entity_load($this->entityTypeId, $this->entityId, TRUE); + $this->assertEqual($changed_timestamp, $entity->getChangedTime(), 'The entity\'s changed time wasn\'t updated after API save without changes.'); + + // Ensure different save timestamps. + sleep(1); + + // Save the entity on the regular edit form. + $language = $entity->language(); + $edit_path = $entity->urlInfo('edit-form', array('language' => $language)); + $this->drupalPostForm($edit_path, [], $this->getFormSubmitAction($entity, $language->getId())); + + $entity = entity_load($this->entityTypeId, $this->entityId, TRUE); + $this->assertNotEqual($changed_timestamp, $entity->getChangedTime(), 'The entity\'s changed time was updated after form save without changes.'); + } + } + } diff --git a/core/modules/file/src/Entity/File.php b/core/modules/file/src/Entity/File.php index 1a38c16132a136563dc18b8852874038fa281a90..ae5ca764b2183cc8cd891df3f753bf13c4a87a38 100644 --- a/core/modules/file/src/Entity/File.php +++ b/core/modules/file/src/Entity/File.php @@ -110,13 +110,6 @@ public function getCreatedTime() { return $this->get('created')->value; } - /** - * {@inheritdoc} - */ - public function getChangedTime() { - return $this->get('changed')->value; - } - /** * {@inheritdoc} */ diff --git a/core/modules/menu_link_content/src/Entity/MenuLinkContent.php b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php index 7d51ae292147c057e4e01bbae364eb9ce159c613..d83f87115214797b381533e02159751237cee3de 100644 --- a/core/modules/menu_link_content/src/Entity/MenuLinkContent.php +++ b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php @@ -132,13 +132,6 @@ public function getWeight() { return (int) $this->get('weight')->value; } - /** - * {@inheritdoc} - */ - public function getChangedTime() { - return $this->get('changed')->value; - } - /** * {@inheritdoc} */ diff --git a/core/modules/node/src/Entity/Node.php b/core/modules/node/src/Entity/Node.php index 1e5e5ef8210e05870c04d838c6527d208adba8c8..34d57b46ecaf276572bfb8d029a0ed4af8db076f 100644 --- a/core/modules/node/src/Entity/Node.php +++ b/core/modules/node/src/Entity/Node.php @@ -231,13 +231,6 @@ public function setCreatedTime($timestamp) { return $this; } - /** - * {@inheritdoc} - */ - public function getChangedTime() { - return $this->get('changed')->value; - } - /** * {@inheritdoc} */ @@ -498,6 +491,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->setDescription(t('Briefly describe the changes you have made.')) ->setRevisionable(TRUE) ->setTranslatable(TRUE) + ->setDefaultValue('') ->setDisplayOptions('form', array( 'type' => 'string_textarea', 'weight' => 25, diff --git a/core/modules/node/src/Tests/NodeFormSaveChangedTimeTest.php b/core/modules/node/src/Tests/NodeFormSaveChangedTimeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..285a743746f0c89e89793547cdb9363bc471270e --- /dev/null +++ b/core/modules/node/src/Tests/NodeFormSaveChangedTimeTest.php @@ -0,0 +1,76 @@ +<?php + +/** + * @file + * Contains \Drupal\node\Tests\NodeFormSaveChangedTimeTest. + */ + +namespace Drupal\node\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Tests updating the changed time after API and FORM entity save. + * + * @group node + */ +class NodeFormSaveChangedTimeTest extends WebTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = array( + 'node', + ); + + /** + * An user with permissions to create and edit articles. + * + * @var \Drupal\user\UserInterface + */ + protected $authorUser; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + // Create a node type. + $this->drupalCreateContentType(array( + 'type' => 'article', + 'name' => 'Article', + )); + + $this->authorUser = $this->drupalCreateUser(['access content', 'create article content', 'edit any article content'], 'author'); + $this->drupalLogin($this->authorUser); + + // Create one node of the above node type . + $this->drupalCreateNode(array( + 'type' => 'article', + )); + } + + /** + * Test the changed time after API and FORM save without changes. + */ + public function testChangedTimeAfterSaveWithoutChanges() { + $node = entity_load('node', 1); + $changed_timestamp = $node->getChangedTime(); + + $node->save(); + $node = entity_load('node', 1, TRUE); + $this->assertEqual($changed_timestamp, $node->getChangedTime(), "The entity's changed time wasn't updated after API save without changes."); + + // Ensure different save timestamps. + sleep(1); + + // Save the node on the regular node edit form. + $this->drupalPostForm('node/1/edit', array(), t('Save')); + + $node = entity_load('node', 1, TRUE); + $this->assertNotEqual($changed_timestamp, $node->getChangedTime(), "The entity's changed time was updated after form save without changes."); + } +} diff --git a/core/modules/rest/src/Tests/RESTTestBase.php b/core/modules/rest/src/Tests/RESTTestBase.php index 5e84827a9c181a079c07b7cb4540c2a4fcc889c2..b6eb46a7716f6c3e867bd13b2f04fafbf5522149 100644 --- a/core/modules/rest/src/Tests/RESTTestBase.php +++ b/core/modules/rest/src/Tests/RESTTestBase.php @@ -389,6 +389,7 @@ protected function removeNodeFieldsForNonAdminUsers(NodeInterface $node) { $node->set('promote', NULL); $node->set('sticky', NULL); $node->set('revision_timestamp', NULL); + $node->set('revision_log', NULL); $node->set('uid', NULL); return $node; 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 61200f7cb42eb23c7ba7b1513a14d47757359933..e9e9bbf00ab0bdff261d8e2703d37af4390c2f83 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 @@ -46,12 +46,4 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { return $fields; } - - /** - * {@inheritdoc} - */ - public function getChangedTime() { - return $this->get('changed')->value; - } - } diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulChanged.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulChanged.php index 48c3ddca9f05a0592183572f32bba775ed0fd7fe..67811ac925463c17419153aca254839aae1a6484 100644 --- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulChanged.php +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulChanged.php @@ -72,12 +72,4 @@ public function save() { sleep(1); parent::save(); } - - /** - * {@inheritdoc} - */ - public function getChangedTime() { - return $this->get('changed')->value; - } - } diff --git a/core/modules/taxonomy/src/Entity/Term.php b/core/modules/taxonomy/src/Entity/Term.php index 2105a03b684c32153a7ba9bc24986583b29d228d..7020f0fdcf7c9de9de340ffd166a752da198406e 100644 --- a/core/modules/taxonomy/src/Entity/Term.php +++ b/core/modules/taxonomy/src/Entity/Term.php @@ -187,13 +187,6 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { return $fields; } - /** - * {@inheritdoc} - */ - public function getChangedTime() { - return $this->get('changed')->value; - } - /** * {@inheritdoc} */ diff --git a/core/modules/user/src/Entity/User.php b/core/modules/user/src/Entity/User.php index 781061a7f28722bb97b703044e036bc5df79831c..1dcdefbef7994ac95a8704bee640e1287b18795c 100644 --- a/core/modules/user/src/Entity/User.php +++ b/core/modules/user/src/Entity/User.php @@ -389,13 +389,6 @@ public function setUsername($username) { return $this; } - /** - * {@inheritdoc} - */ - public function getChangedTime() { - return $this->get('changed')->value; - } - /** * {@inheritdoc} */