1

We have a number of custom blocks that we've created, and use Layout Builder so that our authors can just drag and drop as needed. We have one Hero block in particular that we would like to see on every page (customized per page), and would love to automatically insert one on node creation for ease of authoring. You can almost do this via default layouts for the content type, but the default block acts more like a reusable block. The first page gets the empty block, user edits it and saves. Any subsequent page then get that edited version.

I'm running the following in a hook_preprocess_HOOK (hook_preprocess_page specifically):

$layoutBuilder = $node->get('layout_builder__layout');
$sections = $layoutBuilder->getSections();
if (isset($sections) && !empty($sections)) {
  $hasHero = FALSE;
  $heroSection = $sections[0];
  $components = $heroSection->getComponents();
  foreach ($components as $component) {
    $blockPlugin = $component->getPlugin();
    if ($blockPlugin instanceof BlockBase) {
      $blockConfig = $blockPlugin->getConfiguration();
      if ($blockConfig['id'] === 'hero_cta') {
        $hasHero = TRUE;
        break;
      }
    }
  }
  if (!$hasHero) {
    $blockEntityManager = \Drupal::entityTypeManager()
      ->getStorage('block_content');

    $block = $blockEntityManager->create(
      [
        'info' => 'Hero CTA for node/' . $nid,
        'type' => 'hero_cta',
        'langcode' => 'en',
      ]
    );
    $block->save();

    $newBlockConfig = [
      'id' => 'hero_cta',
      'provider' => 'hero_cta',
      'label_display' => TRUE,
      'block_id' => $block->id(),
      'context_mapping' => [],
    ];
    $newComponent = new SectionComponent($node->uuid(), 'content', $newBlockConfig);
    $heroSection->appendComponent($newComponent);

    $node->save();
  }
}

The above almost works... When I go to the View tab, I can see it. But, when I go to the layout tab, it is not there. If you save the layout override, and then refresh again, it suddenly appears.

So, what am I doing wrong?

2
  • In which hook_preprocess_HOOK() is that code exactly used? hook_preprocess_HOOK() is probably the wrong place to save a block, though.
    – apaderno
    Commented Mar 12, 2022 at 0:04
  • Ooop, yeah, edited question with that additional info @apaderno - currently using hook_preprocess_page. It seemed like that was the next best place to try and perform this task after the content type is saved the first time. The block is indeed being created, but the connection of it to the section component is where it is kinda failing for me. So yes, appropriate function timing could be my issue.
    – Rodent
    Commented Mar 14, 2022 at 15:08

1 Answer 1

1

Alright, our team eventually found the solution we were after, so posting here in case someone else is trying to do this! Ultimately, as pointed out, I was initially doing this in a preprocess hook - which was then being overwritten by the layout defaults. Here is what worked:

protected function prepareLayout(SectionStorageInterface $section_storage) {
  parent::prepareLayout($section_storage);
  $storageContext = $section_storage->getContexts();
  $node = $storageContext['entity']->getContextData()->getValue();
  if (isset($node)) {
    if ($node->hasField(OverridesSectionStorage::FIELD_NAME)) {
      $nid = $node->id();
      $layoutBuilder = $node->get(OverridesSectionStorage::FIELD_NAME);
      $sections = $layoutBuilder->getSections();
      if (isset($sections) && !empty($sections)) {
        // Set default configuration to compare to make sure page has been generated.
        $defaultHeroSection = $section_storage->getDefaultSectionStorage()->getSection(0);
        $defaultHeroCount = count($defaultHeroSection->getComponents());

        // First section is our 'Hero CTA' region. Check for existence.
        $hasHero = FALSE;
        $heroSection = $layoutBuilder->getSection(0);
        $heroComponents = $heroSection->getComponents();
        $currentHeroCount = count($heroComponents);
        if ($defaultHeroCount === $currentHeroCount) {
          foreach ($heroComponents as $component) {
            $blockPlugin = $component->getPlugin();
            if ($blockPlugin instanceof BlockBase) {
              $blockConfig = $blockPlugin->getConfiguration();
              if ($blockConfig['id'] === 'hero_cta') {
                $hasHero = TRUE;
                break;
              }
            }
          }
          if (!$hasHero) {
            $blockEntityManager = \Drupal::entityTypeManager()->getStorage('block_content');

            // Create a new, empty Hero CTA block.
            $block = $blockEntityManager->create(
              [
                'info' => 'Hero CTA for node/' . $nid,
                'type' => 'hero_cta',
                'langcode' => 'en',
              ]
            );
            $block->save();

            // Insert new Hero CTA block into proper section.
            $newBlockConfig = [
              'id' => 'hero_cta',
              'provider' => 'hero_cta',
              'label_display' => TRUE,
              'block_id' => $block->id(),
              'context_mapping' => [],
            ];
            $newComponent = new SectionComponent($node->uuid(), 'content', $newBlockConfig);
            $heroSection->appendComponent($newComponent);
          }
          // Remove original first section and unused blocks.
          $section_storage->removeSection(0);
          // Reinsert first section.
          $section_storage->insertSection(0, $heroSection);
          $this->layoutTempstoreRepository->set($section_storage);
          // Save the changes.
          $node->save(); 
        }
      }
    }
  }
}
1
  • And, should someone come across this thread and need a D9 solution, you'll need to move this functionality into an event subscriber.
    – Rodent
    Commented Mar 30, 2022 at 15:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.