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

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.