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}