Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 72
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
DisplayBuilderPageVariant
0.00% covered (danger)
0.00%
0 / 69
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 / 38
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',
27  admin_label: new TranslatableMarkup('Display Builder page')
28)]
29class DisplayBuilderPageVariant 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    /** @var \Drupal\display_builder\InstanceInterface $instance */
147    $instance = $this->entityTypeManager->getStorage('display_builder_instance')->load($instance_id);
148
149    if (!$instance) {
150      $contexts = [];
151    }
152    else {
153      $contexts = $instance->getContexts() ?? [];
154    }
155    $data = [];
156
157    foreach ($sources as $source) {
158      $build = $this->componentElementBuilder->buildSource($data, 'content', [], $source, $contexts);
159      $data[] = $build['#slots']['content'][0] ?? [];
160    }
161
162    $cache = new CacheableMetadata();
163    $cache->addCacheableDependency($this);
164    // See also: PageLayout::postSave().
165    // See also: PageVariantSubscriber::onSelectPageDisplayVariant()
166    $cache->addCacheTags($page_layout->getCacheTags());
167
168    $build = [
169      'display_builder_content' => [
170        '#instance_id' => $instance_id,
171        'data' => $data,
172        '#weight' => -800,
173      ],
174    ];
175    $cache->applyTo($build);
176
177    return $build;
178  }
179
180  /**
181   * {@inheritdoc}
182   */
183  public function setMainContent(array $main_content): self {
184    $this->mainContent = $main_content;
185
186    return $this;
187  }
188
189  /**
190   * {@inheritdoc}
191   */
192  public function setTitle($title): self {
193    $this->title = $title ?? '';
194
195    return $this;
196  }
197
198  /**
199   * Replace title and content blocks.
200   *
201   * @param array $data
202   *   The Display Builder data to alter.
203   * @param mixed $title
204   *   The title to set.
205   * @param array|null $content
206   *   The content to set.
207   *
208   * @todo replace multiple in one pass?
209   */
210  private function replaceTitleAndContent(array &$data, mixed $title, ?array $content): void {
211    if ($content !== NULL) {
212      DisplayBuilderHelpers::findArrayReplaceSource($data, ['source_id' => self::SOURCE_CONTENT_ID], $content);
213    }
214
215    // Try to handle specific title cases.
216    if (\is_string($title)) {
217      $title = $title;
218    }
219    elseif ($title instanceof MarkupInterface) {
220      $title = (string) $title;
221    }
222    elseif (isset($title['#markup'])) {
223      $title = $title['#markup'];
224    }
225
226    if ($title !== NULL && \is_string($title)) {
227      // @todo avoid arbitrary classes.
228      $title = ['#markup' => '<h1 class="title page-title">' . $title . '</h1>'];
229      DisplayBuilderHelpers::findArrayReplaceSource($data, ['source_id' => self::SOURCE_TITLE_ID], $title);
230    }
231  }
232
233}

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.