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}