Commit a6c2e2b1 authored by catch's avatar catch

Issue #2506213 by hchonov, mkalkbrenner: Update content entity changed timestamp on UI save

parent 3123ff0a
...@@ -62,6 +62,15 @@ public function form(array $form, FormStateInterface $form_state) { ...@@ -62,6 +62,15 @@ public function form(array $form, FormStateInterface $form_state) {
return $form; 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} * {@inheritdoc}
*/ */
...@@ -269,4 +278,18 @@ public function updateFormLangcode($entity_type_id, EntityInterface $entity, arr ...@@ -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);
}
}
} }
...@@ -29,6 +29,16 @@ interface EntityChangedInterface { ...@@ -29,6 +29,16 @@ interface EntityChangedInterface {
*/ */
public function getChangedTime(); 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. * Gets the timestamp of the last entity change across all translations.
* *
......
...@@ -28,4 +28,26 @@ public function getChangedTimeAcrossTranslations() { ...@@ -28,4 +28,26 @@ public function getChangedTimeAcrossTranslations() {
return $changed; 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;
}
} }
...@@ -25,7 +25,7 @@ public function validate($entity, Constraint $constraint) { ...@@ -25,7 +25,7 @@ public function validate($entity, Constraint $constraint) {
$saved_entity = \Drupal::entityManager()->getStorage($entity->getEntityTypeId())->loadUnchanged($entity->id()); $saved_entity = \Drupal::entityManager()->getStorage($entity->getEntityTypeId())->loadUnchanged($entity->id());
// A change to any other translation must add a violation to the current // A change to any other translation must add a violation to the current
// translation because there might be untranslatable shared fields. // 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); $this->context->addViolation($constraint->message);
} }
} }
......
...@@ -219,13 +219,6 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ...@@ -219,13 +219,6 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
return $fields; return $fields;
} }
/**
* {@inheritdoc}
*/
public function getChangedTime() {
return $this->get('changed')->value;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
......
...@@ -527,13 +527,6 @@ public function setThread($thread) { ...@@ -527,13 +527,6 @@ public function setThread($thread) {
return $this; return $this;
} }
/**
* {@inheritdoc}
*/
public function getChangedTime() {
return $this->get('changed')->value;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
......
...@@ -479,6 +479,13 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, En ...@@ -479,6 +479,13 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, En
if (isset($form['actions']['delete'])) { if (isset($form['actions']['delete'])) {
$form['actions']['delete']['#submit'][] = array($this, 'entityFormDelete'); $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 ...@@ -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->setAuthor(!empty($values['uid']) ? User::load($values['uid']) : User::load(0));
$metadata->setPublished(!empty($values['status'])); $metadata->setPublished(!empty($values['status']));
$metadata->setCreatedTime(!empty($values['created']) ? strtotime($values['created']) : REQUEST_TIME); $metadata->setCreatedTime(!empty($values['created']) ? strtotime($values['created']) : REQUEST_TIME);
$metadata->setChangedTime(REQUEST_TIME);
$source_langcode = $this->getSourceLangcode($form_state); $source_langcode = $this->getSourceLangcode($form_state);
if ($source_langcode) { if ($source_langcode) {
...@@ -611,6 +617,30 @@ function entityFormValidate($form, FormStateInterface $form_state) { ...@@ -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(). * Form submission handler for ContentTranslationHandler::entityFormAlter().
* *
......
...@@ -65,6 +65,7 @@ function testTranslationUI() { ...@@ -65,6 +65,7 @@ function testTranslationUI() {
$this->doTestAuthoringInfo(); $this->doTestAuthoringInfo();
$this->doTestTranslationEdit(); $this->doTestTranslationEdit();
$this->doTestTranslationChanged(); $this->doTestTranslationChanged();
$this->doTestChangedTimeAfterSaveWithoutChanges();
$this->doTestTranslationDeletion(); $this->doTestTranslationDeletion();
} }
...@@ -546,4 +547,30 @@ protected function doTestTranslationChanged() { ...@@ -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.');
}
}
} }
...@@ -110,13 +110,6 @@ public function getCreatedTime() { ...@@ -110,13 +110,6 @@ public function getCreatedTime() {
return $this->get('created')->value; return $this->get('created')->value;
} }
/**
* {@inheritdoc}
*/
public function getChangedTime() {
return $this->get('changed')->value;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
......
...@@ -132,13 +132,6 @@ public function getWeight() { ...@@ -132,13 +132,6 @@ public function getWeight() {
return (int) $this->get('weight')->value; return (int) $this->get('weight')->value;
} }
/**
* {@inheritdoc}
*/
public function getChangedTime() {
return $this->get('changed')->value;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
......
...@@ -231,13 +231,6 @@ public function setCreatedTime($timestamp) { ...@@ -231,13 +231,6 @@ public function setCreatedTime($timestamp) {
return $this; return $this;
} }
/**
* {@inheritdoc}
*/
public function getChangedTime() {
return $this->get('changed')->value;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
...@@ -498,6 +491,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ...@@ -498,6 +491,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
->setDescription(t('Briefly describe the changes you have made.')) ->setDescription(t('Briefly describe the changes you have made.'))
->setRevisionable(TRUE) ->setRevisionable(TRUE)
->setTranslatable(TRUE) ->setTranslatable(TRUE)
->setDefaultValue('')
->setDisplayOptions('form', array( ->setDisplayOptions('form', array(
'type' => 'string_textarea', 'type' => 'string_textarea',
'weight' => 25, 'weight' => 25,
......
<?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.");
}
}
...@@ -389,6 +389,7 @@ protected function removeNodeFieldsForNonAdminUsers(NodeInterface $node) { ...@@ -389,6 +389,7 @@ protected function removeNodeFieldsForNonAdminUsers(NodeInterface $node) {
$node->set('promote', NULL); $node->set('promote', NULL);
$node->set('sticky', NULL); $node->set('sticky', NULL);
$node->set('revision_timestamp', NULL); $node->set('revision_timestamp', NULL);
$node->set('revision_log', NULL);
$node->set('uid', NULL); $node->set('uid', NULL);
return $node; return $node;
......
...@@ -46,12 +46,4 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ...@@ -46,12 +46,4 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
return $fields; return $fields;
} }
/**
* {@inheritdoc}
*/
public function getChangedTime() {
return $this->get('changed')->value;
}
} }
...@@ -72,12 +72,4 @@ public function save() { ...@@ -72,12 +72,4 @@ public function save() {
sleep(1); sleep(1);
parent::save(); parent::save();
} }
/**
* {@inheritdoc}
*/
public function getChangedTime() {
return $this->get('changed')->value;
}
} }
...@@ -187,13 +187,6 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ...@@ -187,13 +187,6 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
return $fields; return $fields;
} }
/**
* {@inheritdoc}
*/
public function getChangedTime() {
return $this->get('changed')->value;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
......
...@@ -389,13 +389,6 @@ public function setUsername($username) { ...@@ -389,13 +389,6 @@ public function setUsername($username) {
return $this; return $this;
} }
/**
* {@inheritdoc}
*/
public function getChangedTime() {
return $this->get('changed')->value;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment