Problem/Motivation

The Layout Paragraphs Builder formatter derives its display context from a view mode name. Layout Builder never provides a usable one.

Layout Builder does not render fields through the entity's view display. It renders them through \Drupal\layout_builder\Plugin\Block\FieldBlock::build(), which passes an array of display options to FieldItemListInterface::view(). Core builds an ad-hoc EntityViewDisplay for that array, so the formatter is instantiated with the _custom placeholder as its view mode instead of the view mode that is actually being rendered.

This causes three problems.

1. The placeholder is written into the builder URL.
LayoutParagraphsBuilderFormatter::view() builds /layout-paragraphs-builder/formatter/node/1/field_content/_custom and LayoutParagraphsBuilderForm::buildForm() rebuilds the formatter settings from the view mode in that URL:

$render_display = EntityViewDisplay::collectRenderDisplay($entity, $view_mode);
$renderer = $render_display->getRenderer($field_name);
$layout_paragraphs_settings = $renderer->getSettings() + [...];

_custom is not a real view mode, so collectRenderDisplay() silently falls back to the default display and returns the settings of whatever formatter that display uses. Every Layout Paragraphs setting is lost, and the builder logs:

Warning: Undefined array key "require_layouts" in LayoutParagraphsAllowedTypesSubscriber->typeRestrictions()
Warning: Undefined array key "nesting_depth" in LayoutParagraphsAllowedTypesSubscriber->typeRestrictions()

If the default display does not use the formatter at all, $renderer is NULL and the request ends in Error: Call to a member function getSettings() on null.

This is the symptom reported in #3522459 (nesting settings not found under Layout Builder), where the cause was not identified.

2. Closing the builder wipes the field.
LayoutParagraphsBuilderForm::close() re-renders the field with $field->view($view_mode). Given a string, core resolves the real display, and on a Layout Builder enabled display LayoutBuilderEntityViewDisplay::buildMultiple() puts everything under $build['_layout_builder'] and unsets the fields. EntityViewBuilder::viewField() then finds nothing and returns [], so the ReplaceCommand replaces the builder form with nothing.

3. The route does not validate the view mode.
The builder route accepts any {view_mode}. Because of the fallback described in point 1, .../default, .../teaser and .../nonexistent all return 200 and build the builder from an unrelated display.

The three are not separable. (2) stays hidden until (1) is fixed, and (3) turns the _custom URL that an unfixed (1) still emits into a 404.

Steps to reproduce

  1. Enable Layout Builder on a view mode of a content type (for example full), with a Layout Paragraphs field displayed using the Layout Paragraphs Builder formatter.
  2. As a user who can edit the node, view it in that view mode.
  3. The Edit link points to .../field_content/_custom.
  4. Follow it. The builder is built from the default display: settings are missing (warnings above), or a fatal error if default does not use the formatter.
  5. With a real view mode in the URL, open the builder and press Close. The field disappears from the page.

Confirmed against 3.0.x (ca8405c).

Proposed resolution

  1. The formatter resolves the view mode from the third party setting Layout Builder already provides (third_party_settings.layout_builder.view_mode). Layout Builder builds it from the display's own mode, so it always names a real view mode.
  2. When the view mode still cannot be resolved, the formatter renders the components without the edit link and logs a warning, rather than advertising a builder the route would reject.
  3. close() passes display options instead of a view mode name, so core renders the field in isolation through the ad-hoc _custom display, which LayoutBuilderEntityViewDisplay::buildMultiple() leaves untouched. The real view mode is carried in the third party settings so the formatter keeps building consistent URLs.
  4. buildForm() throws NotFoundHttpException unless the resolved renderer is a LayoutParagraphsBuilderFormatter. The instanceof check also covers the NULL renderer.

A merge request follows, split into three commits (formatter, close, route) so it can be reviewed or split further. It includes a Kernel test that fails without the fix.

Remaining tasks

  • Review.
  • Decide whether to backport to 2.1.x (the same diff applies cleanly there).

User interface changes

None on displays without Layout Builder. Where the view mode cannot be resolved, the Edit link is no longer shown (it was broken before).

API changes

None.

Data model changes

None.

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

tiago created an issue. See original summary.

tiago’s picture

Status: Active » Needs review
StatusFileSize
new17.88 KB

MR !273 fixes all three problems listed in this issue: the placeholder in the builder URL, the field being wiped on Close, and the route accepting any view mode. It includes a Kernel test that fails without the fix.

Pipeline is green, including phpunit and Cypress. Applies cleanly to 3.0.x (ca8405c).

I have also attached the patch file here (same as the MR).