Needs review
Project:
Drupal core
Version:
main
Component:
node system
Priority:
Critical
Category:
Bug report
Assigned:
Unassigned
Reporter:
Created:
4 Jun 2018 at 20:53 UTC
Updated:
14 Oct 2026 at 00:31 UTC
Jump to comment: Most recent, Most recent file
Comments
Comment #2
timmillwoodIt's be awesome to get a test to recreate this bug, we can then fix in a test driven development way.
Comment #5
wildfeed commentedConfirming this issue exists.
When one User who has created a revision to a node is deleted, all revisions to that node are attributed to Anonymous - regardless of who created them.
Comment #6
dpiComment #7
dpiReworked
\node_user_cancel()to reassign authors and revision author independently.API Additions
\Drupal\node\NodeStorageInterface::userRevisionAuthorRevisionIdsAPI to get revisions created by a user.Tests
\Drupal\node\NodeStorageInterface::userRevisionIdsand\Drupal\node\NodeStorageInterface::userRevisionAuthorRevisionIds. I could not find any coverage for the pre-existinguserRevisionIdsmethod.Side note: exhaustive ->isPublished check was added in the original user cancellation feature back from this patch #8-122: Let users cancel their accounts. I cant say if accidentally unpublishing revisions was ever an issue.
Comment #8
dpiAlso tests only.
Only the new tests should fail.
Contains new tests only (without the delete/move of existing tests).
Comment #9
sam152 commentedMostly only style/nits, patch looks great! Nice work. Leaving NR for others, but will RTBC if there isn't any more feedback.
I don't think using random IDs and labels adds any value to the test here and personally I think it dilutes the test a bit, harder to parse and doesn't really add extra coverage since the labels and node type ID isn't really being asserted anywhere.
Great test, nice one.
Can be moved to ::setup and removed from the other test case too.
I would have said you'll probably get push back moving code from a functional test into an integration test, but all of this is using entity API in the same way the kernel test is, so I think it's a good clean up and should be accepted. Nice one.
Comment #12
dpiThanks @Sam152,
Addressed feedback removing randomness and moving common node/type creation to setup.
Also fixed test, removed now dead code from tests, and added back misplaced anonymous-user variable.
Comment #13
sam152 commentedWhy did the usage of 'having' get removed here, but then used in the new docblock?
Otherwise, changes LGTM.
Comment #15
dpiAddressed #13, thanks!
Reverted inconsistent comment.
Comment #17
chi commentedA similar issue is that when a user account is deleted its UID can still appear in node_revision table in revision_uid column.
Comment #20
slbrassard commentedRerolling patch for latest 9.1.
When I tested the patch, I found that, after cancelling the user, the nodes that had at least one revision made by that user ended up with one of those revisions becoming the default ("published") version of the node, whether or not there was a newer default revision. Looking at the database, this seems to happen because the older revisions aren't actually altered, but instead new revisions are created with the change in author to anonymous. These new revisions then are seen as the latest revision.
Comment #22
ravi.shankar commentedFixed failed tests of patch #20.
Comment #24
vsujeetkumar commentedFixing test.
Comment #25
vsujeetkumar commentedComment #27
vsujeetkumar commentedFixing test.
Comment #28
abhijith s commentedApplied patch #27 and it works fine.Adding screenshots below.
Before patch:

After patch (Only name of deleted user is set as anonymous now):

Comment #29
sonfdHi all -
I am also seeing the issue where an old revision gets published noted in comment #20. I'm seeing this issue on an existing 8.9.x site (with or without the most recent patch that applies), but I did not see it on a minimal D9 install, but I haven't had time to look into it further (and I think it probably needs its own issue). As a temporary workaround, we are using this simple module to remove the option altogether from the User Cancellation Form (/user/{user}/cancel) and the Bulk User Cancellation Form (/admin/people/cancel): Prevent User Delete, Reassign Content to Anonymous
Comment #31
pixelwhip commentedWe've run into the same issue. I had to backport the patch in 27 to 9.1.x but this solved our issue. Thanks for posting this!
Comment #33
azinck commentedReroll of #27.
Comment #34
azinck commentedIn testing #33 I'm still seeing problematic behavior. I see new revisions created based on the old revisions that belonged to the user who was deleted. They aren't actually getting published in my setup, but they do become the latest revision. That said, I'm testing this on a site with quite a bit of custom code that modifies how revisions behave so this shouldn't be taken as the final word.
Comment #35
azinck commentedI'm really wondering if we want to actually do real entity revision saves as part of this. It's very disruptive. In my case, at least, I have code that responds to what it perceives as transitions by doing things like sending emails and resaving each of the revisions the way this code does effectively replays through the transitions since each successive revision save causes the new revision to be considered the 'latest revision'. I know it's always a debate to determine what sorts of changes warrant going through the API to fire off all associated hooks and events but I think I might argue for doing these changes directly in the DB.
I know that maybe I just need to implement it myself, but I tend to think most people want this to behave, as much as possible, as though nothing at all changed...they just want to swap in the anonymous user in place of the user being deleted. I tend to think doing the swap directly in the appropriate DB tables (and probably firing off some commands to expire caches) is going to be the best way to do that and not have a bunch of inadvertent side-effects.
Comment #36
ranjith_kumar_k_u commentedRe-rolled #33 for 9.4.
Comment #38
sophie.skI'm working with a client who has seen this behaviour as well. The patch applies cleanly and seems to help with accounts as they are cancelled.
However they have already got content that has been reassigned incorrectly. Is there a way to update the existing content/revisions to resolve this? We have over a thousand revisions in the database assigned to a now-cancelled user ID and the customer can't save nodes.
Happy to try and pick at this a bit, but unfortuantely this client is still running on D8, so any input I can offer may be a bit outdated.
Comment #39
smustgrave commentedFollowed the steps in the description and I was not able to reproduce. Are there different steps to follow?
Comment #41
smustgrave commentedThis issue is being reviewed by the kind folks in Slack, #needs-review-queue-initiative. We are working to keep the size of Needs Review queue [2700+ issues] to around 400 (1 month or less), following Review a patch or merge request as a guide.
Retested and can confirm the issue.
At this time we will need a D10 version of the patch.
Elevated to major as this leads to data lose.
Comment #42
_utsavsharma commentedPatch for 10.1.x.
Comment #43
smustgrave commentedComment #44
smustgrave commentedpublic function userRevisionAuthorRevisionIds(AccountInterface $account)1. Should be typehinted as a new function
2. Both spots
Did not test yet.
Comment #45
murilohp commentedJust trying to help you out here @smustgrave, the following patch address #44. I've also took a look at the testing part and it seems good to me.
Comment #46
murilohp commentedForgot to move to NR.
Comment #47
smustgrave commentedThanks @murilohp just retested this and it's working as expected.
And change requested in #44 was addressed.
Comment #49
smustgrave commentedRandom ckeditor5
Comment #51
smustgrave commentedReran tests and they passed.
Comment #52
catchThe patch itself looks fine, but I think we need to consider #35 a bit more - i.e. do we really want to be going through all the revisions that a user created a long time ago and resaving them? There's also #34 and #20 about this patch causing new revisions to be created in some situations - this might need more manual testing with draft + default revisions in history and similar.
Comment #53
smustgrave commentedThat's a good point. I don't think Drupal.org does that as I see anonymous. I actually wonder if the data is being stored correctly and when the revision page loads since it can't load the user (it was deleted) it defaults to anonymous. Wonder if we could have just display as plain text?
Comment #55
gaurav_manerkar commentedComment #57
gaurav_manerkar commentedI have found the root cause. Node revisions are getting created when
save();method is called in function_node_mass_update_helper.The revisions are created with data from loaded node revision
The
_node_mass_update_helperfunction should only updateuidandrevision_uidfields of the node revision entity instead of creating new revisions.Solution: Disable creation of new revision in method
_node_mass_update_helperwhen node revision is getting updated.Please test my patch.
Comment #58
gaurav_manerkar commentedComment #59
smustgrave commentedCan the MR be updated for 11.x or new one started as that’s the current development branch. Issue would be backported
Comment #60
gaurav_manerkar commentedYes, let me add for 11.x as well
Comment #61
gaurav_manerkar commentedSwitched branch to 11.x
Comment #62
smustgrave commentedCan issue summary be updated with new solution.
Also will need test coverage.
Comment #63
bkosborneI found #3089747: Content moderation can wrongly set old revisions as default when they're resaved which asserts from several people the problem mentioned in #29 (and #20 I guess).
Comment #64
jmaguniaI added the
data losstag to this ticket since ournode_field_datatable was showing the UID as 0.Comment #65
eduardo morales albertiSame problem here on Drupal 9.5.10.
Comment #68
lpeidro commentedThere are two different issues here.
The first case is related to what was pointed out in the task description and I think the proper approach is the previous patch. Therefore, I am recovering the this approach in the MR "2977362-incorrect-user-anonymous".
The second case, maybe is related to whether the Content Moderation module is active, which is being discussed in this issue https://www.drupal.org/project/drupal/issues/3089747 and there is a proposed solution.
Comment #69
solideogloria commentedCancelled -> Canceled
Though both spellings are correct, core code uses "canceled" in
everyalmost every case, so I'm changing the issue to use it as well.Comment #70
eduardo morales albertiTests fixed
Tested on local. RTBC.
Please, take into account that most of the changes come from the user dpi, on the MR we applied the patch that he provided.
Comment #71
solideogloria commentedComment #72
eduardo morales albertiAdding PHPUnit tests to the issue https://www.drupal.org/project/drupal/issues/3089747 we found that:
We added the test to this branch to confirm that happens on a clear Drupal.
The problem seems that a new revision is created for en, but not for es, so the langcode es is not synchronised with the table field_data and then the langcode es is not accessible.
Comment #73
eduardo morales albertiUpload only the PHPUnit test to see the differences with and without the fix.
Comment #75
eduardo morales albertiThe result with the fix + PHPUnit test https://www.drupal.org/pift-ci-job/2781901 is the translation "es" lost?
The result without the fix + PHPUnit test https://www.drupal.org/pift-ci-job/2781940, here we are not sure why is failing in this step.
On my local with version 10.1.x only with the PHPUnit test, the result is:
So it is possible that this solution is not stable or needs more investigation.
Comment #76
eduardo morales albertiWe applied the changes from https://www.drupal.org/project/drupal/issues/3089747 because have some relation and both patches collide.
The author was not set correctly because on the hook node_user_cancel, when the function node_mass_update is called to update the nodes, it does not take into account the langcode, so sets all revisions uid to 0.
When a new translation is created, on the original language is also created a new revision, the method userRevisionIds($account), gets the information from the table 'node_field_revision', and if the deleted user is the author of the original revision, when a new translation is created, also in the original language will be created a new revision, so the method userRevisionIds returns that the user author of the original language is author of both translations.
Example:
We patch the function node_user_cancel to get the author of each translation filtering by the langcode.
Those are the tables node_field_data and node_field_revision before and after the patch:
Before applying the patch:
Before deleting the user:
After delete the user:
After applying the patch:
Before deleting the user:
After deleting the user with UID 2:
Comment #78
eduardo morales albertiThe tests are failing on CI, but not in local.
We executed the same tests and is not failing.
We mounted the project using ddev and require drupal core 10.1.x-dev
Why the results are not the same?
Comment #79
fjgarlin commentedDrupal is run in a subdirectory for DrupalCI and GitLab CI so paths will be relative to them.
I think, from seeing the MR, if you change
$this->drupalGet('/es/' . $node->toUrl()->toString());for$this->drupalGet($node->toUrl('canonical', ['language' => 'es'])->toString());, things should work locally and on the CI systems.Comment #80
eduardo morales albertiThank you @fjgarlin seems working on https://git.drupalcode.org/issue/drupal-2977362/-/jobs/235674
We tried with "$this->drupalGet('es/node/' . $node->id());",
Comment #81
eduardo morales albertiComment #83
eduardo morales albertiUpload the patch with only the PHPUnit tests to check that Drupal is failing in some scenarios related to the issue.
Comment #84
eduardo morales albertiUpload only the tests
Comment #85
eduardo morales albertiThe only PHPUnit tests patch gives the following errors:
https://www.drupal.org/pift-ci-job/2797148
Comment #86
eduardo morales albertiReady to review
Comment #87
eduardo morales albertiNightwatch failed on the merge request, but We are not sure why https://git.drupalcode.org/issue/drupal-2977362/-/pipelines/44783 and probably not related to the MR.
Comment #88
smustgrave commented1 nitpicky issue.
FYI you don't need test-only patch anymore there is a test-only feature in gitlab that can be ran.
Reran the nightwatch and it was indeed a random.
Also not sure if needed searched for _node_mass_update_helper http://grep.xnddx.ru/search?text=_node_mass_update_helper&filename= and only found 1 hit.
Comment #89
eduardo morales alberti@smustgrave, sorry I did not understand your question about _node_mass_update_helper. Should we remove or replace the function? what do you mean?
Comment #90
smustgrave commentedOh I was just noting other modules that may call this. Nope you're good to leave as is.
Comment #91
eduardo morales albertiThank you @smustgrave, there is something left?
Comment #92
smustgrave commentedLeft two small nitpicky things, going to leave in review for others to comment too.
Hiding the patches and leaving a note in the issue summary the fix is in MR4713
Comment #93
eduardo morales albertiChanges proposed by @smustgrave applied.
Comment #94
smustgrave commentedCircling back to this one
Ran test-only feature again
Tested following the steps and MR did address the issue.
Learned recently data loss is a critical issue. Will try and get some traction in slack
Comment #95
acbramley commentedMostly minor feedback
Comment #96
eduardo morales albertiWe will try to review the minor changes this week.
Thank you acbramley
Comment #98
eduardo morales albertiWaiting for tests.
Comment #99
eduardo morales albertiAll points suggested by acbramley were addressed.
Comment #105
eduardo morales albertiComment #106
bramdriesenAdded a small nit and a discussion on the function name.
Comment #107
eduardo morales albertiComment #108
bramdriesenSome grammatical suggestions, but that should not hold this back from being reviewed by others.
Comment #109
eduardo morales albertiApplied @BramDriesen grammar suggestions.
Comment #110
smustgrave commentedVerified the test coverage.
Went through the threads and left a checkmark on what's been addressed, which is everything.
Still verified the issue on 11.x and that the MR addresses it.
Comment #111
arisenRTBC +1 for me.
Verified the fix on Drupal 11 setup as per the steps to reproduce.
-Created content using user 1.
-Edited the content using user 2.
-Deleted user 1 using user 3.
-Verified the revision of the content. Working as expected.
Comment #112
catchNeeds work for @lleber's comment.
Comment #113
eduardo morales albertiComment #114
smustgrave commentedBelieve all feedback has been addressed.
Comment #115
longwaveAdded some feedback to the MR.
Comment #116
connbi commentedI merged the #45 and #57 patches, create a new patch for Drupal 10.1.4
it's work for me.
Comment #117
acbramley commentedAll work is being done in the MR, it's much easier for reviewers if we do not confuse things with additional patches.
Composer patches support local files, you do not need to upload them to an issue.
Comment #118
connbi commentedI tried to apply all the code modified by MR4713 in drupal 10.1.8 version and found that when selecting "Delete the account and make its content belong to the Anonymous user. This action cannot be undone. Reassign its groups to the super administrator." The created node by this user will also be deleted.Is there any new progress regarding this ticket?
Comment #119
willibautista commentedI updated the patch from #116 to work with Drupal 10.3.0
Comment #120
willibautista commentedComment #121
acbramley commentedSee #117
Comment #122
eduardo morales albertiIssue #3384789: Allow restriction of available User Cancellation Methods on deletion confirmation form could help to limit the options
Comment #123
acbramley commentedSome rough conflicts in here that need resolving.
Comment #125
hitchshockReroled and refactored the fix.
Updated tests.
Also, provided patch files for different versions,
Comment #126
hitchshockP.S. There is also a related bug described here #3338260: Skip changing 'revision translation affected' field if the old revision is syncing. Without that patch, a revision_translation_affected property might be changed, which can cause the issue when we see a revision of the other(unexpected) language in the revisions list.
Steps to reproduce:
Comment #127
acbramley commentedWe need to move these new methods somewhere else, NodeStorage is in the process of being deprecated. #3396062: Deprecate most methods on NodeStorage
We also should be using entity queries instead of direct db queries.
Comment #128
hitchshockHi @acbramley
I understand your point, but:
- That ticket is still in progress, so it doesn't make sense to move the functions elsewhere and refactor them for now, because it's still a big question which of the tasks will be solved first.
Since there will be a merge conflict here, the task that will merge last will fix it. So, we can ignore it for now.
- A similar opinion as above, since all class methods currently use db queries, it should be the same here. Their refactoring will be part of #3396062 or will happen here if that task is merged first.
Especially since we can't use entity queries to get these values. It will at least be select queries
Conclusion: Your comment cannot be a blocker for this ticket.
Comment #129
smustgrave commentedGoing to agree with with 127, but can leave in review for others
Comment #130
acbramley commentedIt doesn't make sense to add new methods to an interface/class that we know is being deprecated. We can postpone this issue on that one instead if you like.
That code is ancient and is part of the reason why that class is being deprecated, it's not compatible with all db engines. We should do it properly first since we're introducing new functionality, not leave it to a follow up to refactor again.
Comment #131
hitchshock@acbramley
Where exactly to move?
My opinion is that new methods must be in the same place where revisionIds() is located, which is logical, but I'd be happy to hear another suggestion.
And I'm not sure it's right to postpone a critical issue because of a minor one.
But it's not a deprecated class yet, we just have a ticket to make it so, and it's a big question when that ticket will be resolved. And which of the tickets will be solved first.
If this one is merged first, then that one will be refactored according to 11.x. If that one will be merged first, then this one will be refactored.
For example, we had to refactor this one after merging the #3442009: OOP hooks using attributes and event dispatcher
So I see the following ways:
1. Keep it as it is, cuz the other ("normal" priority) refactoring ticket cannot block the "critical" one, this issue breaks the content on the site and must be fixed asap. Later in the context of #3396062 the code will be refactored
2. To locate new methods in the same place as a revisionIds() method, we can make a separate branch as a combination of #2977362 and #3396062 + keep the current one. The two branches should always be up to date. But I don't think this is the right way to combine the two tickets, because it could delay the resolving of a critical issue, but this is a possible way.
I would like to hear other opinions. Maybe even in the form of a vote :)
Comment #132
acbramley commentedComment #134
pfrenssenUpdated code to work with latest 11.x. There were conflicts due to #3511357: Clean up hook implementations in Node module and #3396062: Deprecate most methods on NodeStorage.
None of the remarks have been addressed. Now that #3396062: Deprecate most methods on NodeStorage is in it is expected that there will be deprecation warnings.
Comment #135
pfrenssenAddressed #127 and the remarks on the MR. Extended the test coverage to include a forward draft revision, which covers the change in
ContentEntityBase.Comment #136
pfrenssenThis issue is also affecting contrib, e.g. Entity Reference Revisions: #3549453: Exception when cancelling user account.
Comment #137
smustgrave commentedAppears to have some open threads in the MR.
Comment #139
jorgik commentedRerolled for 11.3.x: adjusted for NodeBulkUpdate service and NodeUserHooks (node.admin.inc now deprecated wrapper);
avoided adding new methods to deprecated NodeStorageInterface by querying revision tables directly.
Added syncing flag flow to NodeBulkUpdate + wrapper and retained the same behavior.
Included ModerationStateUserTest. Patch applies cleanly on 11.3.2.
Comment #140
pfrenssen@jorgik can you please make the changes to the MR instead of posting them in a patch? Maintainers have little time and it is much easier to check changes in the MR instead of having to go through a patch line by line to see what is changed ;)
Comment #141
hemant_raghuvanshi commentedI encountered the same revision author issue in Drupal core version 10.3.1.
The patch referenced in the Drupal.org issue could not be applied directly due to differences in the core implementation for this version. Therefore, we created a custom patch aligned specifically with Drupal 10.3.1 after following the documented reproduction steps.
Steps to Reproduce (Verified)
Issue Observed (Before Patch)
Before applying the patch, all revisions authored by User A and User B were still correctly stored in the database.
After User C (Admin) cancels User A’s account and selects “Reassign content to the anonymous user” (user_cancel_reassign)
After Patch apply
Before Apply Pacth:
select * from node_revision where nid=134626;
+--------+--------+----------+--------------+--------------------+-----------------------------------------------------------------------------------------------+------------------+
| nid | vid | langcode | revision_uid | revision_timestamp | revision_log | revision_default |
+--------+--------+----------+--------------+--------------------+-----------------------------------------------------------------------------------------------+------------------+
| 134626 | 236476 | ja | 3656 | 1617180547 | NULL | 1 |
| 134626 | 236521 | ja | 3656 | 1617181953 | NULL | 0 |
| 134626 | 236531 | ja | 3656 | 1617182124 | NULL | 0 |
| 134626 | 236536 | ja | 3656 | 1617182149 | NULL | 1 |
| 134626 | 448176 | ja | 3656 | 1617182149 | NULL | 1 |
| 134626 | 638421 | ja | 4351 | 1715586369 | NULL | 0 |
| 134626 | 638426 | ja | 4351 | 1715586408 | NULL | 0 |
| 134626 | 638431 | ja | 4351 | 1715586451 | NULL | 0 |
| 134626 | 638436 | ja | 4351 | 1715586494 | NULL | 1 |
| 134626 | 638521 | ja | 4351 | 1715643866 | NULL | 1 |
+--------+--------+----------+--------------+--------------------+-----------------------------------------------------------------------------------------------+------------------+
10 rows in set (0.005 sec)
After Apply Patch
select * from node_revision where nid=134626;
+--------+--------+----------+--------------+--------------------+-----------------------------------------------------------------------------------------------+------------------+
| nid | vid | langcode | revision_uid | revision_timestamp | revision_log | revision_default |
+--------+--------+----------+--------------+--------------------+-----------------------------------------------------------------------------------------------+------------------+
| 134626 | 236476 | ja | 0 | 1617180547 | NULL | 1 |
| 134626 | 236521 | ja | 0 | 1617181953 | NULL | 0 |
| 134626 | 236531 | ja | 0 | 1617182124 | NULL | 0 |
| 134626 | 236536 | ja | 0 | 1617182149 | NULL | 1 |
| 134626 | 448176 | ja | 3656 | 1617182149 | NULL | 1 |
| 134626 | 638421 | ja | 4351 | 1715586369 | NULL | 0 |
| 134626 | 638426 | ja | 4351 | 1715586408 | NULL | 0 |
| 134626 | 638431 | ja | 4351 | 1715586451 | NULL | 0 |
| 134626 | 638436 | ja | 4351 | 1715586494 | NULL | 1 |
| 134626 | 638521 | ja | 4351 | 1715643866 | NULL | 1 |
+--------+--------+----------+--------------+--------------------+-----------------------------------------------------------------------------------------------+------------------+
Comment #142
hemant_raghuvanshi commentedComment #143
pfrenssenI addressed all the open remarks on the MR and merged in latest changes from main. In addition to this:
NodeStorage.user_cancel().I had Claude Opus 5.5 have a look at what is left to do on this issue and it made an interesting suggestion:
This is probably worth a look since it seems like a significant performance gain.
Comment #144
pfrenssenDid some more work on the MR:
Comment #145
acbramley commentedI re-reviewed the full MR after merging in the latest changes on main. Everything was looking pretty good, but wanted to check that we weren't missing any test coverage.
I had Claude analyse the changes and find any gaps in testing. The full list of Gaps is attached as a text file. I went ahead and had it fix the most important ones:
1. The core storage change has no direct test. - Fixed in f03c4bdb
2. An existing core test hid this bug - UserCancelTest said it builds "a node with two revisions" but since it called
get_object_varsit was actually creating 2 separate nodes. That hid the bug with the syncing change in ContentEntityStorageBase. Fixed in c01fdf023. Changes to changed and the revision count aren't checked - Since we're using the syncing swap, we weren't checking that the changed dates or revision ids weren't changing. Fixed in b8e71e99
I think this should be ready now.
Comment #146
acbramley commented