Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 61
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 16
CRAP
0.00% covered (danger)
0.00%
0 / 1
PageLayout
0.00% covered (danger)
0.00%
0 / 57
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 16
702
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 checkInstanceId
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 getBuilderUrl
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 checkAccess
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 getUrlFromInstanceId
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 getDisplayUrlFromInstanceId
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 getProfile
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSources
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 saveSources
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 collectInstances
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getInstanceId
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 getRuntimeContexts
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getInitializationMessage
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 getInitialSources
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 converter
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 pluginCacheClearer
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\display_builder_page_layout\Plugin\display_builder\Buildable;
6
7use Drupal\Core\Access\AccessResult;
8use Drupal\Core\Access\AccessResultInterface;
9use Drupal\Core\Plugin\CachedDiscoveryClearerInterface;
10use Drupal\Core\Session\AccountInterface;
11use Drupal\Core\StringTranslation\TranslatableMarkup;
12use Drupal\Core\Url;
13use Drupal\display_builder\Attribute\DisplayBuildable;
14use Drupal\display_builder\DisplayBuildablePluginBase;
15use Drupal\display_builder\Entity\ProfileInterface;
16use Drupal\display_builder_page_layout\BuilderDataConverter;
17use Drupal\display_builder_page_layout\Entity\PageLayout as PageLayoutEntity;
18use Drupal\display_builder_page_layout\PageLayoutInterface;
19use Drupal\ui_patterns\Plugin\Context\RequirementsContext;
20
21/**
22 * Plugin implementation of the display_buildable.
23 */
24#[DisplayBuildable(
25  id: 'page_layout',
26  label: new TranslatableMarkup('Page layout'),
27  instance_prefix: 'page_layout__',
28)]
29final class PageLayout extends DisplayBuildablePluginBase {
30
31  /**
32   * The page layout entity storing the display.
33   */
34  public ?PageLayoutInterface $entity = NULL;
35
36  /**
37   * {@inheritdoc}
38   */
39  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
40    parent::__construct($configuration, $plugin_id, $plugin_definition);
41
42    if (isset($configuration['entity'])) {
43      $this->entity = $configuration['entity'];
44      // Configuration to store in the Instance entity.
45      unset($this->configuration['entity']);
46      $this->configuration['entity_id'] = $this->entity->id();
47
48      return;
49    }
50
51    // Configuration stored in Instance entity:
52    // - entity_id (string): Page layout entity ID.
53    // No dependency injection in plugin constructors.
54    $this->entity = PageLayoutEntity::load($configuration['entity_id']);
55  }
56
57  /**
58   * {@inheritdoc}
59   */
60  public static function checkInstanceId(string $instance_id): ?array {
61    if (!\str_starts_with($instance_id, self::getPrefix())) {
62      return NULL;
63    }
64    [, $page_layout] = \explode('__', $instance_id);
65
66    return [
67      'page_layout' => $page_layout,
68    ];
69  }
70
71  /**
72   * {@inheritdoc}
73   */
74  public function getBuilderUrl(): Url {
75    return Url::fromRoute('entity.page_layout.display_builder', ['page_layout' => $this->entity->id()]);
76  }
77
78  /**
79   * {@inheritdoc}
80   */
81  public static function checkAccess(string $instance_id, AccountInterface $account): AccessResultInterface {
82    return $account->hasPermission('administer page_layout') ? AccessResult::allowed() : AccessResult::forbidden();
83  }
84
85  /**
86   * {@inheritdoc}
87   */
88  public static function getUrlFromInstanceId(string $instance_id): Url {
89    $params = self::checkInstanceId($instance_id);
90
91    if (!$params) {
92      // Fallback to the list of instances.
93      return Url::fromRoute('entity.display_builder_instance.collection');
94    }
95
96    return Url::fromRoute('entity.page_layout.display_builder', $params);
97  }
98
99  /**
100   * {@inheritdoc}
101   */
102  public static function getDisplayUrlFromInstanceId(string $instance_id): Url {
103    $params = self::checkInstanceId($instance_id);
104
105    if (!$params) {
106      // Fallback to the list of instances.
107      return Url::fromRoute('entity.display_builder_instance.collection');
108    }
109
110    return Url::fromRoute('entity.page_layout.edit_form', $params);
111  }
112
113  /**
114   * {@inheritdoc}
115   */
116  public function getProfile(): ?ProfileInterface {
117    return $this->entity?->getProfile();
118  }
119
120  /**
121   * {@inheritdoc}
122   */
123  public function getSources(): array {
124    // We reload because the Drupal cache is very strong on this data.
125    /** @var \Drupal\display_builder_page_layout\PageLayoutInterface $entity */
126    $entity = $this->entityTypeManager->getStorage('page_layout')->load($this->entity->id());
127    $this->entity = $entity;
128
129    return $this->entity->getSources();
130  }
131
132  /**
133   * {@inheritdoc}
134   */
135  public function saveSources(): void {
136    $this->entity->setSources($this->getInstance()->getCurrentState());
137    $this->entity->save();
138    // Clearing plugin cache seems enough to get the new layout.
139    // @todo It looks very costly. Check if it is still needed.
140    $this->pluginCacheClearer()->clearCachedDefinitions();
141  }
142
143  /**
144   * {@inheritdoc}
145   */
146  public static function collectInstances(): array {
147    $entityTypeManager = \Drupal::service('entity_type.manager');
148    $displayBuildableManager = \Drupal::service('plugin.manager.display_buildable');
149
150    $instances = [];
151    $entities = $entityTypeManager->getStorage('page_layout')->loadMultiple();
152
153    foreach ($entities as $page_layout) {
154      /** @var \Drupal\display_builder_page_layout\PageLayoutInterface $page_layout */
155      /** @var \Drupal\display_builder\DisplayBuildableInterface $buildable */
156      $buildable = $displayBuildableManager->createInstance('page_layout', ['entity' => $page_layout]);
157      $buildable->initInstanceIfMissing();
158      $instance_id = $buildable->getInstanceId();
159      $instances[$instance_id] = $buildable->getInstance();
160    }
161
162    return $instances;
163  }
164
165  /**
166   * {@inheritdoc}
167   */
168  public function getInstanceId(): ?string {
169    // Usually an entity is new if no ID exists for it yet.
170    if ($this->entity->isNew()) {
171      return NULL;
172    }
173
174    return \sprintf('%s%s', self::getPrefix(), $this->entity->id());
175  }
176
177  /**
178   * {@inheritdoc}
179   */
180  public function getRuntimeContexts(array $unqualified_context_ids): array {
181    $contexts = [];
182    $contexts = RequirementsContext::addToContext(['page'], $contexts);
183
184    return $contexts;
185  }
186
187  /**
188   * {@inheritdoc}
189   */
190  protected function getInitializationMessage(): TranslatableMarkup {
191    if ($this->initialDataSource === 'theme') {
192      return $this->t('Import display from Block Layout configuration');
193    }
194
195    return $this->t('Initialize display from existing Page Layout configuration');
196  }
197
198  /**
199   * {@inheritdoc}
200   */
201  protected function getInitialSources(): array {
202    $sources = $this->getSources();
203
204    if (empty($sources)) {
205      $sources = $this->converter()->convertPage();
206      // Sources root is always a list of source data structures.
207      $sources = \array_is_list($sources) ? $sources : [$sources];
208      $this->initialDataSource = 'theme';
209    }
210
211    return $sources;
212  }
213
214  /**
215   * Gets the builder data converter.
216   *
217   * @return \Drupal\display_builder_page_layout\BuilderDataConverter
218   *   The converter service.
219   */
220  private function converter(): BuilderDataConverter {
221    return \Drupal::service('display_builder_page_layout.builder_data_converter');
222  }
223
224  /**
225   * Gets the builder data converter.
226   *
227   * @return \Drupal\Core\Plugin\CachedDiscoveryClearerInterface
228   *   The plugin cache clearer.
229   */
230  private function pluginCacheClearer(): CachedDiscoveryClearerInterface {
231    return \Drupal::service('plugin.cache_clearer');
232  }
233
234}