Problem/Motivation

After reverting a revision for a published node, you can not longer save the draft that you've just reverted.
The validation EntityChangedConstraintValidator fails

Steps to reproduce:

1. Create a node draft
2. Edit and publish the node.
3. Go to Revisions page and revert to the first revision
4. Edit the node and try saving as draft.
5. See error "The content has either been modified by another user, or you have already submitted modifications. As a result, your changes cannot be saved."

Proposed resolution

Add Revision created and changed time to revision entity.

CommentFileSizeAuthor
#50 2848508-50.patch4.62 KBtimmillwood
#50 interdiff-2848508-50.txt1.04 KBtimmillwood
#48 2848508-47.patch5.39 KBtimmillwood
#47 2848508-47.patch27.48 KBtimmillwood
#47 interdiff-2848508-47.txt2.84 KBtimmillwood
#36 2848508-36.patch7.19 KBtimmillwood
#36 interdiff-2848508-36.txt1.3 KBtimmillwood
#32 2848508-32.patch7.26 KBtimmillwood
#32 interdiff-2848508-32.txt1.71 KBtimmillwood
#30 2848508-30.patch5.56 KBtimmillwood
#30 interdiff-2848508-30.txt2.67 KBtimmillwood
#26 contentinterdif.txt933 bytesPavan B S
#26 content_moderation-revert-validation-changed-2848508-25.patch3.97 KBPavan B S
#23 content_moderation-revert-validation-changed-2848508-23.patch3.98 KBpradeep22saini
#18 content_moderation-revert-validation-changed-2848508-18.patch4.77 KBSam152
#18 content_moderation-revert-validation-changed-2848508-18_TEST_ONLY.patch3.04 KBSam152
#8 content_moderation-revert-validation-changed-2848508.patch4.02 KBdragos-dumi
#3 content_moderation-revert-validation-changed-2848508-test.patch3.04 KBdragos-dumi
#2 content_moderation-revert-validation-changed-2848508-test.patch0 bytesdragos-dumi
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

dragos-dumi created an issue. See original summary.

dragos-dumi’s picture

dragos-dumi’s picture

dragos-dumi’s picture

Status: Active » Needs review
timmillwood’s picture

Status: Needs review » Needs work
Issue tags: +Workflow Initiative

Good spot!

dragos-dumi’s picture

So the EntityChangedConstraintValidator fails because when Drupal\node\Form\NodeRevisionRevertForm::submitForm sets the new revision, just marks it as new revision; this means only node_revision gets a new changed date; in node_field_revision is used the reverted revisions changed date (older than the published revision) and this date is used in validation.

Not sure, does this make it a bug in node module when revert or entity module in validation or we should find a way to change that date from content_moderation ?

dragos-dumi’s picture

Checking Drupal\node\Form\NodeRevisionRevertForm::submitForm

// The revision timestamp will be updated when the revision is saved. Keep
// the original one for the confirmation message.
$original_revision_timestamp = $this->revision->getRevisionCreationTime();

but revision timestamp doesn't get a new value in the new revision added in node_revision

Making these changes solves the issue, but not sure if that would be the correct patch to node module

--- a/core/modules/node/src/Form/NodeRevisionRevertForm.php
+++ b/core/modules/node/src/Form/NodeRevisionRevertForm.php
@@ -114,6 +114,8 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
 
     $this->revision = $this->prepareRevertedRevision($this->revision, $form_state);
     $this->revision->revision_log = t('Copy of the revision from %date.', ['%date' => $this->dateFormatter->format($original_revision_timestamp)]);
+    $this->revision->setRevisionCreationTime(REQUEST_TIME);
+    $this->revision->setChangedTime(REQUEST_TIME);
     $this->revision->save();
 
     $this->logger('content')->notice('@type: reverted %title revision %revision.', ['@type' => $this->revision->bundle(), '%title' => $this->revision->label(), '%revision' => $this->revision->getRevisionId()]);
dragos-dumi’s picture

dragos-dumi’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 8: content_moderation-revert-validation-changed-2848508.patch, failed testing.

timmillwood’s picture

Status: Needs work » Needs review

I'm not 100% sure on the solution here.

+++ b/core/modules/node/src/Form/NodeRevisionRevertForm.php
@@ -114,6 +114,8 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
+    $this->revision->setRevisionCreationTime(\Drupal::time()->getRequestTime());
+    $this->revision->setChangedTime(\Drupal::time()->getRequestTime());

This should use dependency injection.

catch’s picture

The revert is an entirely new revision, it just happens to be based on the older one, so setting the revision timestamp seems correct. If you wanted to know the date/time the revert happened, you'd expect it to be at the moment the revert happened.

Postovan Dumitru’s picture

Hi guys, editing the states and checking the Default Revision checkbox solves the issue, by this, I mean you have to go to the Draft state(/admin/config/workflow/moderation/states/{state}) and set it as Default Revision and then the revision can be reverted, edited and saved.

And some other thing I noticed, I have 4 state transitions for 4 different roles, each of those roles has only permission to access one state transition, as an example: a couple of the revisions for a node are saved with "Needs Review" moderation state, the "Reviewer" can edit it and save, but if you happen to revert to a earlier revision, the moderation state is set to the default moderation state, which is defined in the moderation tab of the Content Type, thus the "Reviewer" is not able to access to edit Form of the node.

dragos-dumi’s picture

Hi. The case described in the ticket is having Published version and reverting to an older one. The expected behavior is to make the reverted version as Draft but still keep the Published revision published.
Setting Draft state to Default Revision won't work because you will not be able to have a Published version and a Draft version at the same time.

Your described situation I guess it works as expected, so you might have to adjust the logic behind your workflow; but if you want a further discussion on it, maybe you can create another ticket for this issue.

Postovan Dumitru’s picture

#14, I'm sorry for misunderstanding the main issue, hoped to help out and failed :)

Anyway, about mine, if all the previous revisions were having the same moderation state besides the draft one, isn't it logical for it to revert to the same moderation state it had when the revision was created?

dragos-dumi’s picture

Your point of view makes sense.
As described by catch in #12, it's a new revision based on the older one, so in my opinion it makes sense to take the default state of a revision. You are not making a workflow transition to a previous state, you just take older fields data and make it current. Reverting should be used when you want to go back to previous fields data, not going back to a previous workflow state - for that you can use transitions. This is how I see it.

You could open a new issue and ask this question. someone that worked on this module could clarify it.

Sam152’s picture

Issue summary: View changes
Status: Needs review » Needs work

I think the main issue at play here is the fact that when reverting to a revision on a draft state, ModerationHandler::onPresave sets the revision as non-default. Commenting out "$entity->isDefaultRevision($default_revision)" passes this test. So think the proper fix is to somehow force content moderation to respect that when you are reverting to a revision you should match the default-ness of that revision too.

It's a weird case because drafts are default when they are the first revision, but never after that. Essentially the following conditions need to respect if the entity is in a state of being reverted.

      // This entity is default if it is new, the default revision, or the
      // default revision is not published.
      $update_default_revision = $entity->isNew()
        || $current_state->isDefaultRevisionState()
        || !$this->isDefaultRevisionPublished($entity, $workflow);

Also this fix doesn't belong in node, as EntityChangedConstraint is in core and used by other entity types.

Sam152’s picture

Here is a patch with the suggestion from #17. I'm not convinced this is the correct solution just yet, and would need a lot of it's own testing and validation.

dragos-dumi’s picture

Thanks. This looks better I guess.
One thing it's unclear for me:
With my patch you still have the published version as current, the reverted one is draft, so the node is still published.
With your patch, the reverted draft is the current revision so the node gets unpublished.

Which of the 2 situations should happen? From the user's perspective I guess both could be valid, depending on what the user want to achieve by reverting.
What do you think?

timmillwood’s picture

+++ b/core/modules/content_moderation/src/EntityOperations.php
@@ -111,6 +111,10 @@ public function entityPresave(EntityInterface $entity) {
+      if (!$entity->isNew() && $entity->getLoadedRevisionId() < $this->moderationInfo->getLatestRevisionId($entity->getEntityTypeId(), $entity->id())) {
+        $update_default_revision = $entity->isDefaultRevision();
+      }

I'm not sure just bolting this on to the existing logic feels right. I'm wondering if we should pass this off to the handler and put a method in there that more logically works out the logic, or if we add a method in ModerationInformation to do this.

Sam152’s picture

Status: Needs review » Needs work

Given this isn't smoothing out differences between entity types I think it's safe to keep hidden from the handlers but something in ModerationInformation could work. The patch is NW for some kernel tests which test this more thoroughly. One thing I didn't document, which I probably should of was why reverting back to a non default revision caused this and if you could trigger this same bug with a draft non-default revision which wasn't the first revision.

#20 raises a good point, there is an element of user expectations here too.

pradeep22saini’s picture

Updating the patch for 8.2.x branch where \Drupal:time() does not exists.

yogeshmpawar’s picture

Status: Needs work » Needs review

Assigning to "Needs Review" state.

timmillwood’s picture

@pradeep22saini - this is not an 8.2.x issue, but I think we should fix it in 8.3.x.

Looking into this further I don't believe it's a Content Moderation specific issue. #8 might be the best patch to solve the issue, but we'd need to move the test out of content moderation.

Pavan B S’s picture

+++ b/core/modules/content_moderation/tests/src/Functional/ModerationRevisionRevertTest.php
@@ -0,0 +1,95 @@
+    $edit = array(
+      'title[0][value]' => 'First draft node',
+    );
...
+    $edit = array(
+      'title[0][value]' => 'Published node',
+    );

Short array syntax should be used, Applying the patch with little modification to the patch 23, please review

timmillwood’s picture

Status: Needs review » Needs work

see #25

pradeep22saini’s picture

@timmillwood
There is already a issue created here and referred here for the solution.
https://www.drupal.org/node/2744851#comment-11971061

timmillwood’s picture

@pradeep22saini - yes, #2744851: Drupal 8 Node Lock Issue was closed in favour of this.

timmillwood’s picture

Status: Needs work » Needs review
FileSize
2.67 KB
5.56 KB

Interdiff based on #8.

I've been trying to test if this is reproducible without Content Moderation, and I can't seem to. So I think #8 is the best approach, just injecting the time service.

Status: Needs review » Needs work

The last submitted patch, 30: 2848508-30.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 32: 2848508-32.patch, failed testing.

timmillwood’s picture

Status: Needs work » Needs review
Manuel Garcia’s picture

Drive by review...

+++ b/core/modules/content_moderation/tests/src/Functional/ModerationRevisionRevertTest.php
@@ -0,0 +1,95 @@
+    $edit = array(
+      'title[0][value]' => 'First draft node',
+    );
...
+    $edit = array(
+      'title[0][value]' => 'Published node',
+    );

Short array syntax ;)

timmillwood’s picture

Switching to sort array syntax whilst tidying up a little.

Manuel Garcia’s picture

Status: Needs review » Reviewed & tested by the community

Patch looks good.
I have manually tested this following the steps on the issue summary and it works.
Can't think of anything else, so RTBCing =)

catch’s picture

Status: Reviewed & tested by the community » Needs review

It's not clear to me whether this is also reproducible in the following case, or whether we have test coverage for it. Copying the steps to reproduce from the issue summary and strikethrough the one change.

1. Create a node draft
2. Edit and publish the node.
3. Go to Revisions page and revert to the first revision
4. Edit the node and try saving as draft.
5. See error "The content has either been modified by another user, or you have already submitted modifications. As a result, your changes cannot be saved."

If it's reproducible without the final save needing to be a draft, then we're missing test coverage without content moderation installed for those steps. And also this should be bumped to critical.

Manuel Garcia’s picture

Priority: Major » Critical

I've just now tried the last step being saved as draft (both publishing it and leaving it as unpublished), and I can see no error either.

timmillwood’s picture

I can't reproduce #38.

In \Drupal\Tests\node\Functional\NodeRevisionsAllTest::testRevisions we are testing the revert form, but we don't test loading the node form after doing a revert. Therefore we don't have a test for #38.

timmillwood’s picture

Priority: Critical » Major
Status: Needs review » Reviewed & tested by the community

Going back to RTBC and major because I can't reproduce #38, so we aren't missing test coverage, and doesn't need to be critical.

catch’s picture

vijaycs85’s picture

Issue summary: View changes

Applied patch in #36 and followed "steps to reproduce" in issue summary. I don't get the error in step 5 and able to save successfully. +1 to RTBC.

  • catch committed 348ff90 on 8.4.x
    Issue #2848508 by timmillwood, dragos-dumi, Sam152, Pavan B S,...
catch’s picture

Version: 8.4.x-dev » 8.3.x-dev
Status: Reviewed & tested by the community » Patch (to be ported)

Committed/pushed to 8.4.x, thanks!

I forgot to sort out commit credit before committing... I'll do issue credit retrospectively though.

Moving this to 8.3.x and 'patch to be ported' since we'd need to not change the constructor of the revision revert form (call out to \Drupal instead).

catch’s picture

Status: Patch (to be ported) » Needs work

CNW is a better status.

timmillwood’s picture

Status: Needs work » Needs review
FileSize
2.84 KB
27.48 KB

Here's a 8.3.x patch as suggested in #45.

timmillwood’s picture

FileSize
5.39 KB

Ooops, lets try that again.

vijaycs85’s picture

+++ b/core/modules/node/src/Form/NodeRevisionRevertForm.php
@@ -2,6 +2,7 @@
+use Drupal\Component\Datetime\TimeInterface;

+++ b/core/modules/node/src/Form/NodeRevisionRevertTranslationForm.php
@@ -2,6 +2,7 @@
+use Drupal\Component\Datetime\TimeInterface;

Looks like we don't need these lines.

timmillwood’s picture

FileSize
1.04 KB
4.62 KB

Here's a fix for that.

vijaycs85’s picture

Status: Needs review » Reviewed & tested by the community

interdiff looks good. As most of the code here already reviewed and committed to 8.4.x and change is just the removal of constructor injection, It's OK to RTBC this.

The last submitted patch, 36: 2848508-36.patch, failed testing.

Wim Leers’s picture

Compared #36 (8.4.x) and #50 (8.3.x):

  • Test coverage is identical.
  • The actual intended changes are identical:
    +    $this->revision->setRevisionCreationTime($this->time->getRequestTime());
    +    $this->revision->setChangedTime($this->time->getRequestTime());
    
  • The only difference is that the datetime.time service is not injected in #50 (because change not allowed in patch release),
    but it is in #36.

Confirming RTBC.

  • catch committed 38b289b on 8.3.x
    Issue #2848508 by timmillwood, dragos-dumi, Sam152, Pavan B S,...
catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed/pushed to 8.3.x, thanks!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.