Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 71
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
PageLayoutPageVariant
0.00% covered (danger)
0.00%
0 / 68
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 6
342
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 6
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 1
2
 create
0.00% covered (danger)
0.00%
0 / 10
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 1
2
 build
0.00% covered (danger)
0.00%
0 / 37
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 1
56
 setMainContent
0.00% covered (danger)
0.00%
0 / 2
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 1
2
 setTitle
0.00% covered (danger)
0.00%
0 / 2
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 1
2
 replaceTitleAndContent
0.00% covered (danger)
0.00%
0 / 11
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 1
56
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\display_builder_page_layout\Plugin\DisplayVariant;
6
7use Drupal\Component\Render\MarkupInterface;
8use Drupal\Core\Cache\CacheableMetadata;
9use Drupal\Core\Display\Attribute\PageDisplayVariant;
10use Drupal\Core\Display\PageVariantInterface;
11use Drupal\Core\Display\VariantBase;
12use Drupal\Core\Entity\EntityTypeManagerInterface;
13use Drupal\Core\Extension\ExtensionList;
14use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
15use Drupal\Core\StringTranslation\TranslatableMarkup;
16use Drupal\Core\Theme\Registry;
17use Drupal\display_builder\DisplayBuildablePluginManager;
18use Drupal\display_builder\DisplayBuilderHelpers;
19use Drupal\ui_patterns\Element\ComponentElementBuilder;
20use Symfony\Component\DependencyInjection\ContainerInterface;
21
22/**
23 * A variant for pages managed by Display Builder Page Layout.
24 */
25#[PageDisplayVariant(
26  id: 'display_builder_page_layout',
27  admin_label: new TranslatableMarkup('Display Builder Page Layout')
28)]
29class PageLayoutPageVariant extends VariantBase implements ContainerFactoryPluginInterface, PageVariantInterface {
30
31  private const SOURCE_CONTENT_ID = 'main_page_content';
32
33  private const SOURCE_TITLE_ID = 'page_title';
34
35  /**
36   * The render array representing the main content.
37   */
38  protected array $mainContent;
39
40  /**
41   * The page title.
42   *
43   * Can be a string (plain title), Markup or a render array (formatted title).
44   */
45  protected array|MarkupInterface|string $title;
46
47  /**
48   * Component element builder.
49   */
50  protected ComponentElementBuilder $componentElementBuilder;
51
52  /**
53   * The entity type manager.
54   */
55  protected EntityTypeManagerInterface $entityTypeManager;
56
57  /**
58   * The theme registry.
59   */
60  protected Registry $themeRegistry;
61
62  /**
63   * The list of modules.
64   */
65  protected ExtensionList $modules;
66
67  /**
68   * The display buildable plugin manager.
69   */
70  private DisplayBuildablePluginManager $displayBuildableManager;
71
72  public function __construct(
73    array $configuration,
74    $plugin_id,
75    $plugin_definition,
76    ComponentElementBuilder $component_element_builder,
77    EntityTypeManagerInterface $entity_type_manager,
78    Registry $theme_registry,
79    ExtensionList $modules,
80    DisplayBuildablePluginManager $display_buildable_manager,
81  ) {
82    parent::__construct($configuration, $plugin_id, $plugin_definition);
83    $this->componentElementBuilder = $component_element_builder;
84    $this->entityTypeManager = $entity_type_manager;
85    $this->themeRegistry = $theme_registry;
86    $this->modules = $modules;
87    $this->displayBuildableManager = $display_buildable_manager;
88  }
89
90  /**
91   * {@inheritdoc}
92   */
93  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
94    return new static(
95      $configuration,
96      $plugin_id,
97      $plugin_definition,
98      $container->get('ui_patterns.component_element_builder'),
99      $container->get('entity_type.manager'),
100      $container->get('theme.registry'),
101      $container->get('extension.list.module'),
102      $container->get('plugin.manager.display_buildable'),
103    );
104  }
105
106  /**
107   * {@inheritdoc}
108   */
109  public function build(): array {
110    /** @var \Drupal\display_builder_page_layout\AccessControlHandler $access_control */
111    $access_control = $this->entityTypeManager->getAccessControlHandler('page_layout');
112    $page_layout = $access_control->loadCurrentPageLayout();
113
114    if (!$page_layout) {
115      // This is not supposed to happen. PageVariantSubscriber must not load
116      // this plugin, and fallback to Block Layout if there is no suitable
117      // Page Layout entities.
118      // @todo raise an error
119      return [];
120    }
121
122    // We alter the registry here instead of implementing
123    // hook_theme_registry_alter in order keep the alteration specific to each
124    // page.
125    $theme_registry = $this->themeRegistry->get();
126    $template_uri = $this->modules->getPath('display_builder_page_layout') . '/templates';
127    $runtime = $this->themeRegistry->getRuntime();
128    $theme_registry['page']['path'] = $template_uri;
129    $runtime->set('page', $theme_registry['page']);
130    $theme_registry['region']['path'] = $template_uri;
131    $runtime->set('region', $theme_registry['region']);
132
133    // Also skip the related template suggestions.
134    foreach (\array_keys($theme_registry) as $renderable_id) {
135      if (\str_starts_with($renderable_id, 'page__') || \str_starts_with($renderable_id, 'region__')) {
136        $runtime->delete($renderable_id);
137      }
138    }
139
140    $sources = $page_layout->getSources();
141    /** @var \Drupal\display_builder\DisplayBuildableInterface $buildable */
142    $buildable = $this->displayBuildableManager->createInstance('page_layout', ['entity' => $page_layout]);
143    $instance_id = $buildable->getInstanceId();
144    $this->replaceTitleAndContent($sources, $this->title, $this->mainContent);
145
146    $data = $contexts = [];
147    /** @var \Drupal\display_builder\InstanceInterface $instance */
148    $instance = $this->entityTypeManager->getStorage('display_builder_instance')->load($instance_id);
149
150    if ($instance) {
151      $contexts = $instance->getContexts();
152    }
153
154    foreach ($sources as $source) {
155      $build = $this->componentElementBuilder->buildSource($data, 'content', [], $source, $contexts);
156      $data[] = $build['#slots']['content'][0] ?? [];
157    }
158
159    $cache = new CacheableMetadata();
160    $cache->addCacheableDependency($this);
161    // See also: PageLayout::postSave().
162    // See also: PageVariantSubscriber::onSelectPageDisplayVariant()
163    $cache->addCacheTags($page_layout->getCacheTags());
164
165    $build = [
166      'display_builder_content' => [
167        '#instance_id' => $instance_id,
168        'data' => $data,
169        '#weight' => -800,
170      ],
171    ];
172    $cache->applyTo($build);
173
174    return $build;
175  }
176
177  /**
178   * {@inheritdoc}
179   */
180  public function setMainContent(array $main_content): self {
181    $this->mainContent = $main_content;
182
183    return $this;
184  }
185
186  /**
187   * {@inheritdoc}
188   */
189  public function setTitle($title): self {
190    $this->title = $title ?? '';
191
192    return $this;
193  }
194
195  /**
196   * Replace title and content blocks.
197   *
198   * @param array $data
199   *   The Display Builder data to alter.
200   * @param mixed $title
201   *   The title to set.
202   * @param array|null $content
203   *   The content to set.
204   *
205   * @todo replace multiple in one pass?
206   */
207  private function replaceTitleAndContent(array &$data, mixed $title, ?array $content): void {
208    if ($content !== NULL) {
209      DisplayBuilderHelpers::findArrayReplaceSource($data, ['source_id' => self::SOURCE_CONTENT_ID], $content);
210    }
211
212    // Try to handle specific title cases.
213    if (\is_string($title)) {
214      $title = $title;
215    }
216    elseif ($title instanceof MarkupInterface) {
217      $title = (string) $title;
218    }
219    elseif (isset($title['#markup'])) {
220      $title = $title['#markup'];
221    }
222
223    if ($title !== NULL && \is_string($title)) {
224      // @todo avoid arbitrary classes.
225      $title = ['#markup' => '<h1 class="title page-title">' . $title . '</h1>'];
226      DisplayBuilderHelpers::findArrayReplaceSource($data, ['source_id' => self::SOURCE_TITLE_ID], $title);
227    }
228  }
229
230}

Branches

Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once. Please also be aware that some branches may be implicit rather than explicit, e.g. an if statement always has an else as part of its logical flow even if you didn't write one.