Skip to content

[5.x]: ContentBlock content deleted during batched resave when the owner is a revision #19543

Description

@jpwdesigns

Description

ContentBlock::_normalizeValueInternal() constrains its batched lookup by the owner's revision status:

https://github.com/craftcms/cms/blob/5.10.13.2/src/fields/ContentBlock.php#L503-L511

->revisions($element->getIsRevision())

When a batched resave processes a revision-owned nested element, this lookup misses. The method falls through to createContentBlockElement(), and NestedElementManager::deleteOtherNestedElements() then soft-deletes the canonical ContentBlock, leaving the new empty one in its place.

Conditions required

  1. A ContentBlock field on an entry type used as a Matrix block type (nested, not top-level)
  2. Revisions exist for the owning entries
  3. A batch resave that includes revisions — Entries::saveSection() queues exactly this ('revisions' => null) on any uriFormat / hasUrls change

Single-entry resaves and single-entry-type batches do not trigger it. I could not reproduce with a ContentBlock directly on a top-level entry type; the nesting appears to be required.

Steps to reproduce

  1. Multi-site install, ContentBlock field inside a Matrix block entry type, entries populated and with revisions
  2. Change a section's uriFormat (adding a key for a new entry type is enough — no existing URI need change)
  3. Run the queued ResaveElements job

Populated ContentBlocks are soft-deleted and replaced with empty ones.

Observed

Instrumented _normalizeValueInternal() against a production dataset during a section-wide resave: 125 lookup misses, 123 of them on revision elements. Two failure shapes — the query returns 0 rows, or returns rows for other owners but not the wanted ownerId:

[marketoFields el=Entry#3168499/site2:REV] BRANCH=batched qResult=13 sameSite=13
  types[border=1,singleColumn=8,twoColumn=3,singleImage=1]
  blocksReturned=0 keys=[] wanted=3168499  -> MISS, new empty block created

Result: 1,824 canonical ContentBlocks soft-deleted and replaced.

Confirmation

Changing that single line to ->revisions(null) — same job, same data:

before after
batched lookup misses 125 4
canonical blocks deleted 1,824 1,118
deleted blocks holding content 10 0

The remaining deletions are empty placeholder blocks only. (revisions(null) is a diagnostic, not a proposed fix — it presumably reverts what #18161 addressed.)

Worth noting: the vast majority of churned blocks are empty, so this is normally invisible. It only surfaces where an author had actually populated a block, which is why it can run for months unnoticed and then appear as sudden, selective content loss.

Possibly related

#19058 / CMS-2186 — ContentBlock content loss, closed as unreproducible, same symptom (populated ContentBlock empty in a later revision after an unrelated change).

Craft CMS version

5.10.13.2 (ContentBlock.php, NestedElementManager.php, Elements.php and ResaveElements.php are byte-identical in 5.10.14). Line introduced in 5.9.0 — absent in 5.8.0.

PHP version

8.3

Database type and version

MySQL 8.0

Notes

Multi-site install (8 sites).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions