Problem/Motivation

Panelizer (at least in D7) allowed you to override a view mode for an entity and add relationships to pull in fields from additional entities (an obvious example being a User view which pulls in data from a profile attached to the user). Panelizer appears to be winding down now so much of what it did is in core, but this relationship aspect is not currently supported by core and is not easily possible in contrib either without swapping out large parts of core.

Proposed resolution

Make it possible for contrib to provide additional context for the layout builder. There are probably a few ways to do this, but the two obvious options to me seem to be one of:

  1. An event that can be subscribed to: Pro - OO with subscribers able to inject services and possibly better 'applies' logic
  2. A hook that modules can implement: Pro - quick and easy to implement!

The places I have identified which would need to invoke whatever process are:

  1. \Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay::buildMultiple after adding the layout_builder.entity context: Implementers would need to provide real (potentially empty) context.
  2. \Drupal\layout_builder\Plugin\SectionStorage\DefaultsSectionStorage::getContexts: Implementors would need to provide sample context.
  3. \Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage::getContexts: Implementors would need to provide real or sample context.

Remaining tasks

  • Decide what mechanism we want to use (events, hooks or other).
  • Trigger implementations in the identified locations.
  • Make sure there aren't other places that also need to trigger implementations.
  • Test coverage!

User interface changes

None - it would be down to contrib to implement any UI to configure relationships, or to provide them programatically.

API changes

Addition of a mechanism to add additional context.

Data model changes

None - it would be down to contrib to deal with any additional storage requirements.

Issue fork drupal-3001188

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

andrewbelcher created an issue. See original summary.

yanniboi’s picture

StatusFileSize
new2.24 KB

Patch attached.

I have created a hook implementation like the following:

<?php
function hook_layout_builder_view_context_alter(&$contexts, $sample = FALSE) {
  if (!isset($contexts['layout_builder.entity'])) {
    return;
  }

  /* @var \Drupal\Core\Plugin\Context\EntityContext $layout_entity_context */
  $layout_entity_context = $contexts['layout_builder.entity'];

  /* @var \Drupal\Core\Entity\EntityInterface $layout_entity */
  $layout_entity = $layout_entity_context->getContextData()->getValue();
  $sample_generator = \Drupal::service('layout_builder.sample_entity_generator');

  if ($layout_entity instanceof AccountInterface) {
    $profile_types = [
      'customer',
    ];

    foreach ($profile_types as $type) {
      if ($layout_entity->get("profile_{$type}")->target_id) {
        $entity = $layout_entity->get("profile_{$type}")->entity;
      }
      elseif ($sample) {
        $entity = $sample_generator->get('profile', $type);
      }

      if (isset($entity)) {
        $contexts['layout_builder.additional.' . $type] = EntityContext::fromEntity($entity);
      }
    }
  }
}
?>

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

tim.plunkett’s picture

Status: Active » Needs work
Issue tags: +Blocks-Layouts

Nice approach!
Can you add the snippet in #2 to layout_builder.api.php?

rlmumford’s picture

StatusFileSize
new5.36 KB

Updated this to be inline with 8.7.0 layout builder. I've also added the documentation to layout_builder.api.php.

I've decided to pass the SectionStorage to the hook so that implementations can access SectionStorage third party settings when generating or altering context. This would allow a module to provide configurable context relationships, similar to panelizer in drupal 7. Passing the section storage (rather than relying on the 'display' context in the array) allows hook implementers to be agnostic to which SectionStorage plugin is being used - provided it implements the third party settings interface. For example something like:

  function my_module_layout_builder_view_context_alter(&$contexts, SectionStorage $section_storage, $allow_sample) {
     if (!($section_storage intanceof ThirdPartySettingsInterface)) {
       return;
     }

     /** @var \Drupal\ctools\Plugin\RelationshipManager $relationship_manager */
    $relationship_manager = \Drupal::service('plugin.manager.ctools.relationship');
    /** @var \Drupal\Core\Plugin\Context\ContextHandler $context_handler */
    $context_handler = \Drupal::service('context.handler');

    foreach ($section_storage->getThirdPartySettings('ctools', 'relationships') as $machine_name => $relationship) {
      /** @var \Drupal\ctools\Plugin\RelationshipInterface $plugin */
      $plugin = $relationship_manager->createInstance($relationship['plugin'], $relationship['settings'] ?: []);
      $context_handler->applyContextMapping($plugin, $contexts);

      $contexts[$machine_name] = $plugin->getRelationship();
    }
  }
rlmumford’s picture

Status: Needs work » Needs review
rlmumford’s picture

StatusFileSize
new5.28 KB

Adding a patch that works for composer-patches.

bnjmnm’s picture

Issue tags: +Needs tests
StatusFileSize
new1.07 KB
new6.51 KB

Made a change to address an issue that surfaced in #2916876: Add visibility control conditions to blocks within Layout Builder -- in Drupal\layout_builder\SectionComponent::toRenderArray() there's a call to new SectionComponentBuildRenderArrayEvent which is now in a try/catch block as it may result in an acceptable ContextException that simply means the block should not be rendered.

Interdiffing from #5 as #7 cannot be applied. The changes there shouldn't be necessary for use with composer-patches (or else we'd be seeing composer-patches versions of patches all over the issue queue).
If a site's composer configuration requires an altered version of a patch to support composer-patches, it's best to include that patch in your repo and apply it locally - info on how to do this here. This eliminates the need to post a public patch intended for non-standard use cases.

jhedstrom’s picture

This is looking good, just a few comments here:

  1. +++ b/core/modules/layout_builder/layout_builder.api.php
    @@ -26,6 +30,56 @@
    + * Add and alter contexts available to layout builder sections before building
    + * content.
    

    Nit: This description needs to be less than 80 characters.

  2. +++ b/core/modules/layout_builder/layout_builder.api.php
    @@ -26,6 +30,56 @@
    +  if (!isset($contexts['layout_builder.entity'])) {
    +    return;
    +  }
    

    Would this hook ever be called without this context?

  3. +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php
    @@ -265,6 +265,9 @@ public function getContextsDuringPreview() {
    +    \Drupal::moduleHandler()->alter('layout_builder_view_context', $contexts, $this, $allow_sample);
    
    +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
    @@ -351,6 +351,9 @@ public function getContextsDuringPreview() {
    +    \Drupal::moduleHandler()->alter('layout_builder_view_context', $contexts, $this, $allow_sample);
    

    We probably can't alter the __construct parameters at this point, but it would be good to at least set this as a class property in the constructer so that in the future the service can be injected. This is currently out the entity field manager service is set:

        $this->entityFieldManager = \Drupal::service('entity_field.manager');
        parent::__construct($values, $entity_type);
    
    
sam152’s picture

Does this feature already exist to some degree by implementing a 'context_provider' tagged service?

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

hctom’s picture

I'm not sure, if I missed something, but I applied the patch from #8 against 8.7.10 and added a hook_layout_builder_view_context_alter() implementation like this:

/**
 * Implements hook_layout_builder_view_context_alter().
 */
function MY_MODULE_layout_builder_view_context_alter(&$contexts, SectionStorageInterface $section_storage = NULL, $sample = FALSE) {
  if (!isset($contexts['layout_builder.entity'])) {
    return;
  }

  /* @var \Drupal\Core\Plugin\Context\EntityContext $layout_entity_context */
  $layout_entity_context = $contexts['layout_builder.entity'];

  /* @var \Drupal\Core\Entity\EntityInterface $layout_entity */
  $layout_entity = $layout_entity_context->getContextData()->getValue();
  /* @var \Drupal\layout_builder\Entity\SampleEntityGeneratorInterface $sample_generator */
  $sample_generator = \Drupal::service('layout_builder.sample_entity_generator');

  if ($layout_entity->getEntityTypeId() === 'paragraph' && $layout_entity->bundle() === 'image') {
    if ($layout_entity->get('field_media_main')->target_id) {
      $entity = $layout_entity->get('field_media_main')->entity;
    }
    elseif ($sample) {
      $entity = $sample_generator->get('media', 'image');
    }
    if (isset($entity)) {
      $contexts['layout_builder.additional.' . 'media.image'] = EntityContext::fromEntity($entity, 'Image');
    }
  }
}

With this it is possible to add field blocks of media entities referenced via field_media_main field. But unfortunately, when opening a node that uses this layout for an image paragraph, it breaks with a fatal saying: Assigned contexts were not satisfied: entity in Drupal\Core\Plugin\Context\ContextHandler->applyContextMapping().

So do I have to add some more custom code to get this working or is there still a problem with the context handling for field blocks?

I'd appreciate your feedback & thanx in advance!

jparkinson1991’s picture

Patch #4 is working for me.

I noticed similar issues to @hctom and it turns out it was down to the entity context existing under different keys than documented.

When on the layout builder edit pages $contexts['layout_builder.entity'] exists as expected. When viewing the actual content that uses the layout the entity context exists under $contexts['entity']

Can anyone explain this difference in keys, i have been digging around the modules and so far struggling to work out what is going on?

@htcom, to fix your issues try the following

    if(!empty($contexts['layout_builder.entity'])){
        $entityContext = $contexts['layout_builder.entity'];
    }
    else if(!empty($contexts['entity'])){
        $entityContext = $contexts['entity'];
    }
    else{
        return;
    }

    if(!$entityContext instanceof EntityContext){
        return;
    }

Thanks

jparkinson1991’s picture

Just a quick note to say, i have created a patch to add more granular field block categorisation, very useful when using this patch to add in entity relationships to context.

#3111973: Use granular entity type bundle driven field block categories when choosing blocks

tim.plunkett’s picture

Status: Needs review » Needs work

NW for tests

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

johnwebdev’s picture

Another idea would be to add an additional optional argument to ContextRepositoryInterface::getRuntimeContexts, not entirely sure if i think that would be good or not

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

t-lo’s picture

StatusFileSize
new6.79 KB

Re-rolling for 9.1x+

dhirendra.mishra’s picture

StatusFileSize
new6.85 KB

Manually re-rolled it for 9.3.x

dqd’s picture

Is NW referring to tests (#16) ?
Or are we still thinking about the proper solution (#19) ?

Would LOVE to see this getting in. Awesome work in here! +1

bnjmnm’s picture

#24

Is NW referring to tests (#16) ?
Or are we still thinking about the proper solution (#19) ?

The suggestion in #19 doesn't seem to be based on any major concerns with the current implementation, it's an interesting suggestion I'm glad is documented, but I don't think there's a need to pursue it unless it provides obvious benefits over the current one.

The issue definitely needs tests before it can land. Adding those will get this issue to a point where someone can RTBC it.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

andypost’s picture

@bnjmnm please change target branch to 9.4.x as MR outdated a bit

Definitely new hook needs test, btw maybe instead of hook it could use event?

+++ b/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php
@@ -335,11 +335,22 @@ protected function buildSections(FieldableEntityInterface $entity) {
+    $storage = $this->sectionStorageManager()->findByContext($contexts, $cacheability);
...
+    \Drupal::moduleHandler()->alter('layout_builder_view_context', $contexts, $storage);

+++ b/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php
@@ -224,6 +224,9 @@ public function getContextsDuringPreview() {
     $entity = $this->sampleEntityGenerator->get($display->getTargetEntityTypeId(), $display->getTargetBundle());
...
+    \Drupal::moduleHandler()->alter('layout_builder_view_context', $contexts, $this, $allow_sample);

+++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
@@ -316,6 +316,9 @@ public function getContextsDuringPreview() {
+    \Drupal::moduleHandler()->alter('layout_builder_view_context', $contexts, $this, $allow_sample);

The module handler needs to be injected properly - maybe section storage manager can care to invoke hook/event

PS: unpublished patches as the issue using MR

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

liquidcms’s picture

I feel like this is close to solving my issue.

I have a block inside a block placed on a node layout. I need the inner block to control visibility based on the value of a node field. I have the block visibility patch (#2916876: Add visibility control conditions to blocks within Layout Builder) and the patch from here. When i add this hook code:

function mycustom_layout_builder_view_context_alter(&$contexts, SectionStorageInterface $section_storage = NULL, $sample = FALSE) {
  if (!isset($contexts['layout_builder.entity'])) {
    return;
  }

  /* @var \Drupal\Core\Plugin\Context\EntityContext $layout_entity_context */
  $layout_entity_context = $contexts['layout_builder.entity'];

  /* @var \Drupal\Core\Entity\EntityInterface $layout_entity */
  $layout_entity = $layout_entity_context->getContextData()->getValue();

  if ($layout_entity->getEntityTypeId() === 'block_content' && $layout_entity->bundle() === 'layout_block') {
    $entity = Node::create([
      'type' => 'service',
    ]);
    if (isset($entity)) {
      $contexts['layout_builder.entity'] = EntityContext::fromEntity($entity, 'Node');
    }
  }
}

This gives me the ability to add a field condition based off my node bundle when setting visibility of the inner block. But I don't see where, when it comes time to display the actual node, that the block has the context of the specific node it is on. And sure enough, on display, i get this error:

Error: Call to a member function getValue() on null in Drupal\entity_field_condition\Plugin\Condition\FieldValue->evaluate() (line 415 of modules\contrib\entity_field_condition\src\Plugin\Condition\FieldValue.php).

which makes sense as I would need to specify somewhere, the context from the actual node taken from route. What am i missing or is this not possible with the work being done here?

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.