Problem/Motivation
`field_pagedesigner_content` is translatable, so each node translation can reference its own
container element. `PagedesignerService::createContainer()` (src/PagedesignerService.php:190-198)
deliberately skips the `entity` back-reference while the node is still unsaved:
if (!empty($entity) && !$entity->isNew()) {
$container->entity->entity = $entity;
}
`saveContainer()` (:141-150), called from `pagedesigner_entity_insert()`
(pagedesigner.module:153-156), is what repairs it afterwards — but it only handles the
translation being saved. Containers belonging to the other translations keep `entity = NULL`
permanently, and every element created beneath them inherits the gap
(HandlerPluginBase::copy():170,184; CompoundHandlerBase::copy():300 and patch():347,384).
Nothing self-heals: `entity` is not `setRequired(TRUE)`, has no constraint, and
`Element::preSave()` never derives it.
The visible consequence is a **completely blank pagedesigner**. `ElementResource::patch()`
does:
if (!$this->updateLock($entity->entity->entity, $identifier)) { // line 239
and `updateLock()` is typed `ContentEntityInterface $entity` (:292), so a NULL back-reference
throws a TypeError before anything is saved. Because every subsequent editor request hits the
same element, the page never loads. Same unguarded dereference in `delete()` (:276). Note that
`$element->entity->entity` is also NULL when `entity` points at a node that has since been
deleted.
Found on a production site (D10, de/fr/it): one node had three separate containers — `de` with
`entity` set, `fr` and `it` with NULL — and all ~396 elements of its `fr` tree had no node
reference.
Secondary, same origin: `saveContainer()` unconditionally does `$container->entity = $entity`.
When a node is cloned by a module that copies field values by reference (quick_node_clone only
deep-clones entity_reference_revisions fields), the new node ends up sharing the original's
container and takes over its back-reference. On the same site: 24 containers shared by 2-7
nodes. Deleting any of them triggers `pagedesigner_entity_predelete()` ->
`clearPagedesigner()`, which hard-deletes the shared tree and blanks the sibling pages.
Steps to reproduce
- Enable translation on a content type with a `pagedesigner_item` field.
- Create a node with several translations in one save.
- Check `pagedesigner_element_field_data`: the default translation's container has `entity`
set, the others are NULL. - Open a non-default translation in the pagedesigner and change any element — the PATCH
fails with a TypeError and the page stays blank.
Proposed resolution
1. Guard `ElementResource::patch()` and `delete()`: throw
`UnprocessableEntityHttpException` when the back-reference resolves to NULL, so one broken
element causes one failed request instead of an unusable page.
2. `saveContainer()`: iterate `getTranslationLanguages(TRUE)` and repair every translation's
container. Write `entity` only when it is empty or already this node — if it points at a
different existing node, log a warning and leave it alone, rather than stealing another
node's container.
3. `Element::preSave()`: when `entity` is empty and `container` is set, derive the target from
the container. Future gaps then self-heal and the inheritance cascade stops.
Note for (3): `pagedesigner_layout` legitimately creates detached template trees with *both*
`entity` and `container` NULL (LayoutResource.php:156), so the `container` check is required to
leave those untouched.
Remaining tasks
- Patch + tests. `tests/src/Kernel/EndToEnd/KernelNodeTest.php:139` already asserts
`$node->id() === $container->entity->target_id`; add a multi-translation case, and one
asserting `saveContainer()` does not reassign a container owned by another node.
- Separate issues, out of scope here: `pagedesigner_gallery` creates `gallery_item` /
`gallery_gallery` elements with no `entity` and no `container` on every gallery edit
(GalleryGallery.php:84-93, Gallery.php:122); and `pagedesigner_debug`'s
`Cleaner::getPagedesignerOrphans()` classifies a NULL-`entity` container as an orphan and
hard-deletes its entire tree, which is a data-loss risk in the repair tooling.
User interface changes
API changes
Data model changes
Issue fork pagedesigner-3616300
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
Comment #2
guillaumepacilly commentedComment #4
guillaumepacilly commentedComment #6
guillaumepacilly commentedComment #8
guillaumepacilly commented