Problem/Motivation

node_user_predelete() removes a user's content in two steps:

1. An entity query for nodes currently owned by the account, which are then deleted.
2. NodeStorage::userRevisionIds(), which returns every node_field_revision row whose uid is the account, and deleteRevision() on each.

There is no check between the two steps. If a node owned by the account is created or saved after step 1 ran — by another request, a queue worker, a sync process, or by a hook_node_delete() implementation
reacting to the deletions in step 1 — its default revision is returned by step 2, and ContentEntityStorageBase::deleteRevision() throws:

Drupal\Core\Entity\EntityStorageException: Default revision can not be deleted
core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php:817 (deleteRevision)
core/modules/node/node.module (node_user_predelete)

Steps to reproduce

1. Enable a test module implementing hook_node_delete() that creates a new node owned by the deleted node's owner.
2. Create user A and one node owned by A.
3. $userA->delete().

Step 1 of node_user_predelete() deletes the node, the hook creates a replacement owned by A, step 2 finds its default revision and throws.

Proposed resolution

In the revision loop, load each revision and handle the default-revision case instead of letting deleteRevision() throw. Two options:

- (a) Treat it like the nodes from step 1 and delete the node (consistent with the function's intent: the account's nodes go away).
- (b) Skip default revisions and leave the node in place.

Either way, skip revision IDs that no longer load (a node deleted earlier in the same loop). Attached MR implements (a); happy to switch to (b) if preferred.

Remaining tasks

An MR on the main branch

User interface changes

API changes

API changes

None

Issue fork drupal-3618071

Command icon 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:

Comments

aron novak created an issue. See original summary.

aron novak’s picture

Status: Active » Needs review
jeffmattson’s picture

Tested !16766 locally against 11.x. The fix is correct and matches the proposed resolution (option a): in NodeEntityHooks::userPredelete(), each of the account's node revision IDs is now loaded first — if it no longer loads it's skipped, and if it is a node's default revision the whole node is deleted ($storage_controller->delete()) instead of calling deleteRevision(), which throws on defaults.

Verified:
- New kernel test NodeUserPredeleteTest::testNodeSavedDuringUserDelete passes with the patch (1 test, 9 assertions).
- Reverting only NodeEntityHooks.php makes that same test fail with the exact reported error — EntityStorageException: Default revision can not be deleted — confirming it's a valid regression test.
- Existing UserDeleteTest still passes (1 test, 18 assertions), so normal user deletion is unaffected.
- PHPCS clean on all changed files.

Change is minimal and contained.

jeffmattson’s picture

Status: Needs review » Reviewed & tested by the community
quietone’s picture

Version: 11.x-dev » main
Assigned: aron novak » Unassigned
Issue summary: View changes
Status: Reviewed & tested by the community » Needs work

Hi, thanks for the fix.

Issues for Drupal core should be targeted to the 'main' branch, our primary development branch. Changes are made on the main branch first, and are then back ported as needed according to the Core change policies. The version the problem was discovered on should be stated in the issue summary Problem/Motivation section. Thanks.

Un-assigning per Assigning ownership of a Drupal core issue.

This issue has failing tests so is this really RTBC? If there are known random test failures they should be reported in the issue summary.

Oh and I am not sure what the title means by this phrase, 'its nodes are being deleted'. The title should be a description of what is being fixed or improved. The title is used as the git commit message so it should be meaningful and concise. Thanks!