Problem/Motivation
When reverting a parent entity revision, the parent/child relationship looks like:
+------------+ +------------+ +------------+
|Host Entity | |Host Entity | |Revert VID 1|
|VID: 1 | |VID: 2 | |VID: 3 |
|Timestamp: 1| |Timestamp: 2| |Timestamp: 3|
+------------+ +------------+ +------------+
| | |
v v |
+------------+ +------------+ |
|Block Entity| |Block Entity| |
|VID: 1 | |VID: 2 | |
|Timestamp: 1| |Timestamp: 2| |
+------------+ +------------+ |
^ |
+---------------------------------+
Custom block entity form
+-----------------------------+
|$saved_entity change time: 2 |
|$new_entity change time: 1 |
|Validator throws error |
+-----------------------------+
When block entity VID 1 is loaded into the custom block form, the validator compares it against VID 2 as the "saved entity". The validator sees that a new revision has been created since VID 1 and throws an error.
Node handles this by updating the changed timestamp in \Drupal\node\Form\NodeRevisionRevertForm::submitForm when a node is reverted, but there isn't currently a process to create new entity revisions of custom blocks when a host entity is reverted.
Steps to reproduce
- Create a node and add a basic block to it via LB
- Create a new revision, and edit the block
- Revert to the first revision via the Revisions screen
- Try to edit the block again, on save you will trigger the constraint and get the error message "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
Create a modified version of EntityChangedConstraint for BlockContent entities that skips the constraint validation for non-reusable (also known as "inline") block entities. This validation constraint is not necessary for inline block entities because Layout Builder is already creating new revisions of blocks when the layout is saved (see \Drupal\layout_builder\InlineBlockEntityOperations::handlePreSave). There's no danger of losing content.
Remaining tasks
We're so close. One remaining task:
- Update the patch in #3053881-82: Reverting entity revisions that contain custom blocks erroneously triggers EntityChangedConstraint to reflect feedback in #3053881-95: Reverting entity revisions that contain custom blocks erroneously triggers EntityChangedConstraint.
User interface changes
None
API changes
Block content entities will no longer use the Drupal\Core\Entity\Plugin\Validation\Constraint\EntityChangedConstraint, but will now use Drupal\block_content\Plugin\Validation\Constraint\BlockContentEntityChangedConstraint, which extends Drupal\Core\Entity\Plugin\Validation\Constraint\EntityChangedConstraint instead.
Data model changes
None
Release notes snippet
TBD
| Comment | File | Size | Author |
|---|---|---|---|
| #100 | 3053881 after patch.png | 22.41 KB | aarti zikre |
| #100 | 3053881 before patch.png | 98.09 KB | aarti zikre |
| #98 | 3053881-99.patch | 5.79 KB | rishi.kulshreshtha |
| #98 | interdiff_82-99.txt | 1.92 KB | rishi.kulshreshtha |
| #82 | 3053881-82.patch | 5.77 KB | luke.leber |
Issue fork drupal-3053881
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
- 3053881-reverting-entity-revisions
changes, plain diff MR !1785
Comments
Comment #2
sam152 commentedComment #3
sam152 commentedComment #4
sam152 commentedI'm trying to think of the best approach here. I think inline blocks may simply need to opt out of this constraint, since the validation against overriding other peoples work will happen on the host entity anyway.
The only thing I want to validate here is: modifying "Block Entity VID 1" in the diagram results in VID 3 being created and associated with parent entity VID 3, instead of rewriting history.
Comment #5
sam152 commentedI can see there being two options to resolve this while maintaining integrity:
This would require no changes to the validator, the "changed" timestamp would be bumped when the revision was copied.
The validator would need to be switched off inline blocks
Currently new revisions are being enforced in
\Drupal\layout_builder\InlineBlockEntityOperations::handlePreSave, with a todo pointing to #2937199: Track Layout override revisions on entities which support revisioning, which has made some progress towards not enforcing new block revisions.Here are some reasons why I think option 2 is the correct approach:
#3020448).Comment #6
sam152 commentedIt looks like
\Drupal\layout_builder\Plugin\Block\InlineBlock::saveBlockContentalready attempts to create a new block revision on parent save, but it only does so if the block content was serialized, ie it was loaded into the sidebar and saved by a user.For that reason, there are a lot of parent entity revisions which reference common content block revisions. For any entity that has 2 or more custom blocks, new revisions of a parent are only correlated with new revisions of the child block content when those children were actually modified from the UI. For that reason, I think it entirely rules out option 1, it's simply not how the data model was designed.
I think given that's the case, there also needs to be a way more explicit code path that explicitly decouples revision handling from the serialized asepct of custom blocks.
Comment #7
sam152 commentedComment #8
sam152 commentedComment #9
sam152 commentedI think blocking on consensus in #3053887: Add test coverage and document why inline blocks require a new revision to be created when modified, regardless of whether a new revision of the parent has been created is best.
Comment #10
sam152 commentedTest to reproduce this.
Comment #11
sam152 commentedHere is a possible implementation for this.
Comment #13
tedbowAdding related issues
I ran into a similar issue in #2946333: Allow synced Layout override Translations: translating labels and inline blocks because editing a translation of layout and editing the inline block would cause a similar problem. In that case the inline blocks are also translations and when you create a translation you create a new revision of the block.
In that case I am trying to solve it in the issue with this #3052042: If an inline block has been edited outside of layout builder it can't be edited in layout builder
If we take the approach in this issue and we edit the previous revision of the block would we lose the translation in the new revision?
Comment #14
sam152 commentedI'm not sure what the impact this has on the translation work.
Regardless of this issue though, with all the changes to revisions and translations, I think the test coverage in #3053887: Add test coverage and document why inline blocks require a new revision to be created when modified, regardless of whether a new revision of the parent has been created would probably be pretty valuable?
Comment #15
sam152 commentedCombining #3053881: Reverting entity revisions that contain custom blocks erroneously triggers EntityChangedConstraint, #2946333: Allow synced Layout override Translations: translating labels and inline blocks and #3053887: Add test coverage and document why inline blocks require a new revision to be created when modified, regardless of whether a new revision of the parent has been created to see if anything breaks.
Comment #17
sam152 commentedComment #19
chris burge commentedI closed #3087882: Impossible to update inline blocks after reverting to a previous revision as a newer duplicate of this issue.
In summary, here's the direction that discussion on that issue was headed:
Comment #20
sam152 commentedI explored that in #5, however was put off by the fact that it is significantly more complex and would require a gnarly upgrade path for installations that are already in the current shared references state.
Comment #21
bkosborneI think this is better classified as a major, no? This bug prevents editors from editing a block's contents in LB after reverting the parent entity.
Comment #22
chris burge commentedRe #21, agreed. The repercussions are significant, and there's no viable workaround.
Comment #23
tim.plunkettPer #9, this is postponed.
Comment #24
acbramley commentedThis is extremely easy to reproduce as per the test
error
Comment #25
poindexterous commentedI ran into the same issue as acbramely after my latest round of drupal and 3rd party module updates, but it was happening with inline paragraph blocks as well. Patch #11 seems to work to solve the problem for me.
Comment #26
azovsky commentedI have the same issue as described in #24 (thanks @acbramley).
And the patch from #11 fixed my issue (thanks @sam152)!
Comment #28
ethomas08 commentedAdding a patch for 8.7.14 that works to opt the inline custom blocks out of the entity constraint validation.
Comment #29
pyrello commented#11 applied and resolved the issue for me. Thanks!
Comment #30
acbramley commentedRerolled against 9.1.x, BlockContentUpdateTest was removed as part of #3087644: Remove Drupal 8 updates up to and including 88** so this adds it back again.
Comment #31
acbramley commentedComment #32
sam152 commentedComment #33
tim.plunkett(#3053887: Add test coverage and document why inline blocks require a new revision to be created when modified, regardless of whether a new revision of the parent has been created was the blocker per #9, and is now fixed)
Comment #34
Oscaner commentedI readed #5, but don't know why no set a changed time when
InlineBlock::blockValidate(), it will not affect any behavior of the current system.Comment #35
edwardsay commented#34 has resolved issue with "The content has either been modified by another user, or you have already submitted modifications. As a result, your changes cannot be saved." message when saving inline block in Layout Builder. Thanks.
Comment #37
beanjammin commented#34 worked for me, thank-you!
Comment #38
azinck commented#34 is fixing the problem for me, too.
Comment #39
sam152 commentedI don't think it makes sense to update entity values in a validate function, so a later validator is tricked into passing. I think #30 is probably a better approach.
Comment #40
azinck commentedNow that I look at it more closely I do agree with Sam152...however I'm going to keep using #34 until #30 lands in order to avoid introducing an update hook via a patch.
Comment #41
brooke_heaton commented#34 is working for me.
Comment #42
tyler-paavola commented#34 resolved the issue while using Drupal 8.9.x
#34 fails to apply after updating to Drupal 9.1.x
Reroll of #34 for 9.1.x (while #30 is developed as per Sam152 #39 & azinck #40).
Comment #43
odai atiehI added this patch to be working with
https://www.drupal.org/project/drupal/issues/3049332
Comment #44
tyler-paavola commentedAs Odai Atieh mentioned in #43, Drupal Issue #3053881 conflicts with #3049332 as both attempt to modify InlineBlock's constructor signature.
Unfortunately, #43 fails to apply using Drupal 9.1.4
I've combined #3053881's #34 with #3049332's #56 here in #44. Alternatively, #3049332's older #33 patch works cohesively with #3053881's #34 on its own, as it does not add LoggerInterface to InlineBlock's constructor's signature.
> Unable to provide an interdiff between #43 and #44 due to "interdiff: Whitespace damage detected in input" error, however attached is an interdiff between my previous #42 patch and #44.
>> I'll mention the conflict between these two issues' solutions in #3049332 as well.
Comment #45
tyler-paavola commentedOops, fixed "Parameter comment indentation" error from failed tests in #44
Note, as mentioned in #44, this patch combines #3053881's #42 (aka #34 for D9) with #3049332's #56
Comment #47
kybermanThank you for all the patches, but for my scenario (content blocks nested in content block), none of the patches worked, unfortunately.
But the approach in #30 works nicely for 9.1.9, with these changes:
1) removed the whole "block_content_update_8700" function
2) removed the related "BlockContentUpdateTest" class
3) reversed the "BlockContentEntityChangedConstraintValidator" condition logic, so instead of:
if ($entity instanceof BlockContentInterface && !$entity->isReusable()) {I used:
if ($entity instanceof BlockContentInterface && $entity->isReusable()) {I'm not sure if this is the best way, but at least it solves the urgent situation.
Comment #48
kim.pepperComment #49
chrisolof#47 did not work for me. Reverting the condition logic reversal in core/modules/block_content/src/Plugin/Validation/Constraint/BlockContentEntityChangedConstraintValidator.php does seem to work, however. I think this brings that logic back to what it was in #30.
I would guess the update hook in #30 is necessary, but leaving it out doesn't seem to affect things on my installation, and my status page is clear of any entity definition mismatch warnings. Unfortunately I don't have time to research this area any further at the moment - just sharing my experience.
New patch attached. It's essentially the patch from #47 with that one small logic change (see the interdiff).
Comment #50
chamilsanjeewa commented#49 Not worked for me.
Comment #51
robbt commentedSo from my experience #42 works if anyone is wanting to patch a 9.2 site, I think that all patches after this would require applying multiple patches as they were modified to fit with other in-progress 9.3 changes.
Comment #52
smustgrave commentedSorry if I'm repeating #51 but which patch is addressing the issue here? Is it #42? The other patches seem to be including other tickets are they needed?
Have the issue where a user is unable to save their inline blocks and #42 seems to initially work. #49 does not but I don't know if it's because of the additional changes it includes.
So some clarity would be greatly appreciated!
Comment #53
smustgrave commentedUsing #45 and it seems to be working so far.
Comment #54
rishi.kulshreshthaAs mentioned in #53, the patch provided at #45 works as expected, backporting the same to 8.9.x version as well.
Comment #56
bkosborneTrying to get this back on track with Sam's original approach. Updated the issue summary as such.
Comment #57
bkosborneHere's the patch from #30, unchanged except for comments added which I think were needed. This is Sam's original approach. Also uploading a test-only patch to prove that the test is valid.
Updated issue summary to describe the alternate approach that is represented in most of the more recent patches. I agree with #39 and #40 that it's not the best approach.
Comment #58
bkosborneComment #60
bkosborneRemoved unused use statement.
Comment #64
luke.leberChanges since #60:
drupalPostFormdeprecations in\Drupal\Tests\layout_builder\FunctionalJavascript\InlineBlockTestThis should fix the upgrade path test + deprecations in #60...but can someone explain why we need an update hook implementation for this? On the surface it seems that an entity type alter should do the trick? Is it simply to trigger a cache rebuild?
I'm not entirely convinced that the update test is really testing what it's intended to.
Thanks! :-)
Comment #65
acbramley commentedYou know what, I think you're right. Alter hooks don't require an update hook to solidify what they're doing, they're altering existing definitions. We should instead just have an empty post-update hook to clear caches as you say. That means we can remove the update path test entirely!
Would be good for someone else to confirm.
Comment #66
larowlanI think update hooks are only needed for entity-type changes that impact the database.
So I think what @acbramley is saying is correct.
You can alter an entity-type to add a new constraint without needing an update hook.
Comment #67
luke.leberThanks for the guidance! Hopefully a less risky fix can accelerate the resolution of this nasty little bug.
Comment #68
luke.leberAdd an empty post_update() hook to trigger a cache rebuild.
Comment #69
luke.leberComment #71
luke.leberI think that the test fails in #68 may be red herrings. I see a similar "random" fail in the latest patch in https://www.drupal.org/project/drupal/issues/3192234 (an unrelated issue).
Comment #72
luke.leberComment #75
rajab natshahTested the 3053881-68.patch with Drupal 9.3.x
Thank you Luke
Comment #76
luke.leberComment #77
luke.leberUpdate I.S. to remove data model change.
Comment #78
xjmThis is data-loss-like so promoting to critical.
Comment #79
larowlanLooks good to me, haven't manually tested yet though.
Super-nit:
Can we do this via the API instead?
Saves an extra 2 HTTP requests in the test.
Comment #80
joshua1234511- Manually tested the issue with the test steps from issue summary.
- Issue has been resolved with the latest patch from #68
- Before Patch:
- After Patch: https://nimb.ws/vawIBA
Can be marked as RTBC after question from #79 are resolved.
Comment #81
luke.leberMade the requested change in #79.
Comment #82
luke.leberRemove unneeded assertion.
Comment #83
luke.leberCleaned up IS.
Sam's question from #6 was related to attempting to copy revisions on revert. This is not the path that the patch took.
Comment #84
luke.leberComment #86
luke.leberSetting back to NR. I blame random fails still.
:(
Comment #87
luke.leberComment #89
simgui8 commented#82 fixed the error for me.
Thanks
Comment #90
kristen polSince the test code changed, it would be nice to have a test-only patch to make sure that fails before the fix.
Comment #91
luke.leberAttach test-only fail patch as requested in #90.
Comment #92
omahmThanks for your work on this, I tried #68 on my site and the issue persists.
Steps to reproduce are:
Comment #93
luke.leberThanks for testing the patch out, @omahm.
Can you double check that you cleared caches after applying #82 please? It was my understanding that the old constraint validation should be completely bypassed for inline blocks, meaning that what you're seeing _should be_ impossible with the patch applied and caches flushed.
I won't have time this week to write a fail test, so if you want to take a swing at it, please don't hesitate to. :-)
Cheers
Comment #94
smustgrave commentedVerified #82 fixes the issue following the steps in the description.
Comment #95
catchThe comment makes it look like the empty post update is a mistake. It should mention that it's added purely to flush caches.
I'm not sure the comment shows enough why we have to change this.
This is a problem when the host entity is changed outside of the entity form, not only a revision revert I think?
Layout builder is the only implementation of inline blocks we have in core, but there could be others later or in contrib. Given this code is in block module, I think it should mention layout builder as being an example of this problem.
Something like:
'This prevents saving an update to the block via a host entity's form, if the host entity has had other changes made via the API instead of the entity form, such as a revision revert. For example... [layout builder-specific stuff]".
Comment #96
larowlanUpdating the comment is a Novice task
Comment #97
WebbehUpdating IS for last step - let's get that patch rerolled with changes.
Comment #98
rishi.kulshreshthaUpdated comments as prescribed by @catch in #95
Comment #99
aarti zikre commentedreviewing this
Comment #100
aarti zikre commentedVerified #98 patch.
Testing steps:
# Created new instance of drupal 9.5.x
# Create a node and add a basic block to it via LB
# Create a new revision, and edit the block
# Revert to the first revision via the Revisions screen
# Try to edit the block again.
Problem:
on save you will trigger the constraint and get the error message "The content has either been modified by another user, or you have already submitted modifications. As a result, your changes cannot be saved."
Expected output:
on save content should be save without any error
Test Result:
we can save the content with any issue.
this new patch included all the changes mentioned by the #95.
Attaching SS for reference.
Before:


After:
Can be moved to RTBC
Comment #101
alexpottThis text appears in the UI. It has different rules than regular function text. It should be about why we actually have the update function. So
Clear the entity type cache.We also tend to comment empty functions. Here's an example from 9.4.x...
Given this is a critical issue I've made the changes on commit.
Committed and pushed 7362bc50b2 to 10.1.x and 8672c43544 to 10.0.x and 6a261489b6 to 9.5.x. Thanks!