Problem/Motivation
Since 4.2.0 (commit 3f86fe82, released as the fix for #3346580) ParagraphBlocksEntityManager::getRefererEntity() reloads the host entity from the database through loadFreshEntity(), so that paragraphs added on the node form become available in the Layout Builder block listing.
loadFreshEntity() does not load the latest revision though. It asks for the latest translation affected revision, while the comment directly above it states a different intent:
// Load the latest revision directly from the database. if ($entity->getEntityType()->isRevisionable()) { $latest_revision_id = $storage->getLatestTranslationAffectedRevisionId( $entity->id(), $entity->language()->getId() );
Those are not the same revision. revision_translation_affected is only set when core detects a change for that translation, and appending an item to an entity_reference_revisions field usually does not qualify: EntityReferenceRevisionsFieldItemList::hasAffectingChanges() returns during its first loop iteration whenever the referenced entity has a translation in the given langcode, so items after delta 0 are never inspected. If delta 0 is unchanged, the whole field reports "no changes" and ContentEntityStorageBase::populateAffectedRevisionTranslations() leaves the flag empty on that revision.
As a result loadFreshEntity() returns a revision that predates the paragraph the editor just added, and the "Choose a block" listing is built from that stale revision. To editors the behaviour looks arbitrary, because a save that also changes a translatable field, or a "Save layout" in Layout Builder (which writes layout_builder__layout and is compared normally), does set the flag and makes the listing catch up. That also explains why the workaround reported in #3346580 (save the layout first) still appears to help.
Revision data from a site where an editor added one paragraph per save:
rev affected paragraphs changed fields 1693 yes 1 (node created) 1694 yes 1 field_paragraphs, layout_builder__layout 1695 no 2 field_paragraphs 1696 no 2 field_paragraphs 1697 no 3 field_paragraphs 1698 no 3 field_paragraphs
getLatestTranslationAffectedRevisionId() returns revision 1694 with one paragraph, while the current revision 1698 holds three. Only that one paragraph is offered in the block listing, no matter how many are added afterwards.
Reproduced on Drupal 11.3.16, paragraph_blocks 4.2.2, paragraphs 1.21.0, entity_reference_revisions 1.14.0, layout_paragraphs 2.1.3. Content moderation and Workspaces are not enabled.
Steps to reproduce
1. Enable Layout Builder overrides on a node type that has a multi-value paragraph field, and enable that field for paragraph_blocks including "Allow each paragraph".
2. Create a node with one paragraph, give it an admin title, save.
3. Open the Layout tab, place the paragraph block, save the layout.
4. Edit the node, append a second paragraph with an admin title, and save. Do not change the title, body or any other translatable field.
5. Open the Layout tab and click "Add block". The second paragraph is missing from the listing.
The revision lookup can be compared directly:
drush php:eval '$s = \Drupal::entityTypeManager()->getStorage("node"); print $s->getLatestTranslationAffectedRevisionId(NID, "en") . " vs " . $s->getLatestRevisionId(NID) . "\n";'
Proposed resolution
Use getLatestRevisionId(), which is what the comment already describes. See merge request.
Nothing in this code path is language specific. ParagraphBlocksLabeller never calls getTranslation(), and loadRevision() returns the revision in its source language, so the langcode only ever selected a revision, it never influenced which field values were read.
The two other callers of loadFreshEntity(), in ParagraphBlock::build() and ParagraphBlock::blockSubmit(), only use it as a fallback when the paragraph is not found on the context entity. There the newest revision can only contain more paragraphs than an older one, never fewer. The one behavioural difference is that a paragraph removed in a later revision is no longer resurrected from an older revision, which seems correct.
Remaining tasks
- Review
- Automated test coverage for appending a paragraph and asserting it appears in the block listing
User interface changes
None, other than the block listing now offering every paragraph in the field.
API changes
None. The signature and return type of loadFreshEntity() are unchanged.
Data model changes
None.
Issue fork paragraph_blocks-3613815
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 #3
matsalkemade commentedComment #4
matsalkemade commentedMR !34 opened.
Verified on Drupal 11.3.16 with paragraph_blocks 4.2.2, paragraphs 1.21.0 and entity_reference_revisions 1.14.0. Content moderation and Workspaces are not enabled, so the latest revision is always the default revision on this site.
The test node had five paragraphs, each added in a separate save through the node form. Its current revision was 1702, while getLatestTranslationAffectedRevisionId() returned 1700. Requesting layout_builder.choose_block for that node as the same non-admin editor account, with only this change differing:
- before: four of the five paragraphs are listed, the one added in the most recent save is missing
- after: all five are listed
The same request also confirmed the listing is identical for an administrator and for a role without any administrative permissions, so this is not a permission issue even though it presents like one to editors: whether the newly added paragraph shows up depends on whether that particular save happened to set revision_translation_affected, not on who saved it.
Comment #5
basvredelingComment #6
basvredelingHi @matsalkemade
thanks for you contribution. I've added another commit to the MR to address code quality issues. Could you double check / test please?
Comment #7
tim_djCode looks good and works fine in our environment.
Comment #9
basvredelingComment #11
basvredelingThis is in release 4.2.3