Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
70.42% |
50 / 71 |
|
60.00% |
12 / 20 |
CRAP | |
0.00% |
0 / 1 |
PageLayout | |
70.42% |
50 / 71 |
|
60.00% |
12 / 20 |
63.91 | |
0.00% |
0 / 1 |
getPrefix | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getPluginCollections | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getContextRequirement | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
checkInstanceId | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
getBuilderUrl | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getUrlFromInstanceId | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getDisplayUrlFromInstanceId | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getProfile | |
83.33% |
5 / 6 |
|
0.00% |
0 / 1 |
2.02 | |||
getInstanceId | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
initInstanceIfMissing | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
getInitialSources | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
getInitialContext | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getSources | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
saveSources | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getConditions | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
calculateDependencies | |
80.00% |
8 / 10 |
|
0.00% |
0 / 1 |
4.13 | |||
delete | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
postSave | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getInstance | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
sourceManager | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | declare(strict_types=1); |
4 | |
5 | namespace Drupal\display_builder_page_layout\Entity; |
6 | |
7 | use Drupal\Core\Condition\ConditionPluginCollection; |
8 | use Drupal\Core\Config\Entity\ConfigEntityBase; |
9 | use Drupal\Core\Entity\Attribute\ConfigEntityType; |
10 | use Drupal\Core\Entity\EntityDeleteForm; |
11 | use Drupal\Core\Entity\EntityStorageInterface; |
12 | use Drupal\Core\StringTranslation\TranslatableMarkup; |
13 | use Drupal\Core\Url; |
14 | use Drupal\display_builder\ConfigFormBuilderInterface; |
15 | use Drupal\display_builder\DisplayBuilderHelpers; |
16 | use Drupal\display_builder\InstanceInterface; |
17 | use Drupal\display_builder\ProfileInterface; |
18 | use Drupal\display_builder_page_layout\AccessControlHandler; |
19 | use Drupal\display_builder_page_layout\Form\PageLayoutForm; |
20 | use Drupal\display_builder_page_layout\PageLayoutInterface; |
21 | use Drupal\display_builder_page_layout\PageLayoutListBuilder; |
22 | use Drupal\ui_patterns\Plugin\Context\RequirementsContext; |
23 | use Drupal\ui_patterns\SourcePluginManager; |
24 | |
25 | /** |
26 | * Defines the page layout entity type. |
27 | */ |
28 | #[ConfigEntityType( |
29 | id: 'page_layout', |
30 | label: new TranslatableMarkup('Page layout'), |
31 | label_collection: new TranslatableMarkup('Page layouts'), |
32 | label_singular: new TranslatableMarkup('page layout'), |
33 | label_plural: new TranslatableMarkup('page layouts'), |
34 | config_prefix: 'page_layout', |
35 | entity_keys: [ |
36 | 'id' => 'id', |
37 | 'label' => 'label', |
38 | 'weight' => 'weight', |
39 | 'uuid' => 'uuid', |
40 | ], |
41 | handlers: [ |
42 | 'access' => AccessControlHandler::class, |
43 | 'list_builder' => PageLayoutListBuilder::class, |
44 | 'form' => [ |
45 | 'add' => PageLayoutForm::class, |
46 | 'edit' => PageLayoutForm::class, |
47 | 'delete' => EntityDeleteForm::class, |
48 | ], |
49 | ], |
50 | links: [ |
51 | 'collection' => '/admin/structure/page-layout', |
52 | 'add-form' => '/admin/structure/page-layout/add', |
53 | 'edit-form' => '/admin/structure/page-layout/{page_layout}', |
54 | 'display-builder' => '/admin/structure/page-layout/{page_layout}/builder', |
55 | 'delete-form' => '/admin/structure/page-layout/{page_layout}/delete', |
56 | ], |
57 | admin_permission: 'administer page_layout', |
58 | label_count: [ |
59 | 'singular' => '@count page layout', |
60 | 'plural' => '@count page layouts', |
61 | ], |
62 | config_export: [ |
63 | 'id', |
64 | 'label', |
65 | 'weight', |
66 | ConfigFormBuilderInterface::PROFILE_PROPERTY, |
67 | ConfigFormBuilderInterface::SOURCES_PROPERTY, |
68 | 'conditions', |
69 | ], |
70 | )] |
71 | final class PageLayout extends ConfigEntityBase implements PageLayoutInterface { |
72 | |
73 | /** |
74 | * The example ID. |
75 | */ |
76 | protected string $id; |
77 | |
78 | /** |
79 | * The example label. |
80 | */ |
81 | protected string $label; |
82 | |
83 | /** |
84 | * Weight of this page layout when negotiating the page variant. |
85 | * |
86 | * The first/lowest that is accessible according to conditions is loaded. |
87 | * |
88 | * @var int |
89 | */ |
90 | protected $weight = 0; |
91 | |
92 | /** |
93 | * Display Builder ID. |
94 | */ |
95 | protected string $display_builder = ''; |
96 | |
97 | /** |
98 | * A list of sources plugins. |
99 | * |
100 | * @var array |
101 | */ |
102 | protected $sources = []; |
103 | |
104 | /** |
105 | * Condition settings for storage. |
106 | * |
107 | * @var array |
108 | */ |
109 | protected $conditions = []; |
110 | |
111 | /** |
112 | * The loaded display builder instance. |
113 | */ |
114 | protected ?InstanceInterface $instance; |
115 | |
116 | /** |
117 | * The conditions plugins for this page. |
118 | */ |
119 | private ConditionPluginCollection $conditionPluginCollection; |
120 | |
121 | /** |
122 | * {@inheritdoc} |
123 | */ |
124 | public static function getPrefix(): string { |
125 | return 'page_layout__'; |
126 | } |
127 | |
128 | /** |
129 | * {@inheritdoc} |
130 | */ |
131 | public function getPluginCollections(): array { |
132 | return [ |
133 | 'conditions' => $this->getConditions(), |
134 | ]; |
135 | } |
136 | |
137 | /** |
138 | * {@inheritdoc} |
139 | */ |
140 | public static function getContextRequirement(): string { |
141 | return 'page'; |
142 | } |
143 | |
144 | /** |
145 | * {@inheritdoc} |
146 | */ |
147 | public static function checkInstanceId(string $instance_id): ?array { |
148 | if (!\str_starts_with($instance_id, self::getPrefix())) { |
149 | return NULL; |
150 | } |
151 | [, $page_layout] = \explode('__', $instance_id); |
152 | |
153 | return [ |
154 | 'page_layout' => $page_layout, |
155 | ]; |
156 | } |
157 | |
158 | /** |
159 | * {@inheritdoc} |
160 | */ |
161 | public function getBuilderUrl(): Url { |
162 | return Url::fromRoute('entity.page_layout.display_builder', ['page_layout' => $this->id()]); |
163 | } |
164 | |
165 | /** |
166 | * {@inheritdoc} |
167 | */ |
168 | public static function getUrlFromInstanceId(string $instance_id): Url { |
169 | $params = self::checkInstanceId($instance_id); |
170 | |
171 | if (!$params) { |
172 | // Fallback to the list of instances. |
173 | return Url::fromRoute('entity.display_builder_instance.collection'); |
174 | } |
175 | |
176 | return Url::fromRoute('entity.page_layout.display_builder', $params); |
177 | } |
178 | |
179 | /** |
180 | * {@inheritdoc} |
181 | */ |
182 | public static function getDisplayUrlFromInstanceId(string $instance_id): Url { |
183 | $params = self::checkInstanceId($instance_id); |
184 | |
185 | if (!$params) { |
186 | // Fallback to the list of instances. |
187 | return Url::fromRoute('entity.display_builder_instance.collection'); |
188 | } |
189 | |
190 | return Url::fromRoute('entity.page_layout.edit_form', $params); |
191 | } |
192 | |
193 | /** |
194 | * {@inheritdoc} |
195 | */ |
196 | public function getProfile(): ?ProfileInterface { |
197 | $storage = $this->entityTypeManager()->getStorage('display_builder_profile'); |
198 | $profile_id = $this->get(ConfigFormBuilderInterface::PROFILE_PROPERTY); |
199 | |
200 | if (!$profile_id) { |
201 | return NULL; |
202 | } |
203 | |
204 | /** @var \Drupal\display_builder\ProfileInterface $builder */ |
205 | $builder = $storage->load($profile_id); |
206 | |
207 | return $builder; |
208 | } |
209 | |
210 | /** |
211 | * {@inheritdoc} |
212 | */ |
213 | public function getInstanceId(): ?string { |
214 | // Usually an entity is new if no ID exists for it yet. |
215 | if ($this->isNew()) { |
216 | return NULL; |
217 | } |
218 | |
219 | return \sprintf('%s%s', self::getPrefix(), $this->id()); |
220 | } |
221 | |
222 | /** |
223 | * {@inheritdoc} |
224 | */ |
225 | public function initInstanceIfMissing(): void { |
226 | /** @var \Drupal\display_builder\InstanceStorage $storage */ |
227 | $storage = $this->entityTypeManager()->getStorage('display_builder_instance'); |
228 | |
229 | /** @var \Drupal\display_builder\InstanceInterface $instance */ |
230 | $instance = $storage->load($this->getInstanceId()); |
231 | |
232 | if (!$instance) { |
233 | $instance = $storage->createFromImplementation($this); |
234 | $instance->save(); |
235 | } |
236 | } |
237 | |
238 | /** |
239 | * {@inheritdoc} |
240 | */ |
241 | public function getInitialSources(): array { |
242 | $sources = $this->getSources(); |
243 | |
244 | if (empty($sources)) { |
245 | // Fallback to a fixture mimicking the standard page layout. |
246 | $sources = DisplayBuilderHelpers::getFixtureDataFromExtension('display_builder_page_layout', 'default_page_layout'); |
247 | } |
248 | |
249 | return $sources; |
250 | } |
251 | |
252 | /** |
253 | * {@inheritdoc} |
254 | */ |
255 | public function getInitialContext(): array { |
256 | $contexts = []; |
257 | $contexts = RequirementsContext::addToContext([self::getContextRequirement()], $contexts); |
258 | |
259 | return $contexts; |
260 | } |
261 | |
262 | /** |
263 | * {@inheritdoc} |
264 | */ |
265 | public function getSources(): array { |
266 | return $this->sources; |
267 | } |
268 | |
269 | /** |
270 | * {@inheritdoc} |
271 | */ |
272 | public function saveSources(): void { |
273 | $this->sources = $this->getInstance()->getCurrentState(); |
274 | $this->save(); |
275 | } |
276 | |
277 | /** |
278 | * {@inheritdoc} |
279 | */ |
280 | public function getConditions(): ConditionPluginCollection { |
281 | if (!isset($this->conditionPluginCollection)) { |
282 | // Static call because EntityBase and descendants don't support |
283 | // dependency injection. |
284 | $manager = \Drupal::service('plugin.manager.condition'); |
285 | $this->conditionPluginCollection = new ConditionPluginCollection($manager, $this->get('conditions')); |
286 | } |
287 | |
288 | return $this->conditionPluginCollection; |
289 | } |
290 | |
291 | /** |
292 | * {@inheritdoc} |
293 | */ |
294 | public function calculateDependencies(): PageLayout { |
295 | parent::calculateDependencies(); |
296 | $display_builder = $this->getProfile(); |
297 | $instance = $this->getInstance(); |
298 | |
299 | if ($display_builder && $instance) { |
300 | $this->addDependency('config', $display_builder->getConfigDependencyName()); |
301 | $contexts = $instance->getContexts() ?? []; |
302 | |
303 | foreach ($this->getSources() as $source_data) { |
304 | /** @var \Drupal\ui_patterns\SourceInterface $source */ |
305 | $source = $this->sourceManager()->getSource('', [], $source_data, $contexts); |
306 | $this->addDependencies($source->calculateDependencies()); |
307 | } |
308 | } |
309 | |
310 | return $this; |
311 | } |
312 | |
313 | /** |
314 | * {@inheritdoc} |
315 | */ |
316 | public function delete(): void { |
317 | if ($this->getInstance()) { |
318 | $storage = $this->entityTypeManager()->getStorage('display_builder_instance'); |
319 | $storage->delete([$this->getInstance()]); |
320 | } |
321 | |
322 | parent::delete(); |
323 | } |
324 | |
325 | /** |
326 | * {@inheritdoc} |
327 | */ |
328 | public function postSave(EntityStorageInterface $storage, $update = TRUE): void { |
329 | $this->initInstanceIfMissing(); |
330 | parent::postSave($storage, $update); |
331 | } |
332 | |
333 | /** |
334 | * Gets the Display Builder instance. |
335 | * |
336 | * @return \Drupal\display_builder\InstanceInterface|null |
337 | * A display builder instance. |
338 | */ |
339 | private function getInstance(): ?InstanceInterface { |
340 | if (!$this->getInstanceId()) { |
341 | return NULL; |
342 | } |
343 | |
344 | if (!isset($this->instance)) { |
345 | /** @var \Drupal\display_builder\InstanceInterface|null $instance */ |
346 | $instance = $this->entityTypeManager()->getStorage('display_builder_instance')->load($this->getInstanceId()); |
347 | $this->instance = $instance; |
348 | } |
349 | |
350 | return $this->instance; |
351 | } |
352 | |
353 | /** |
354 | * Gets the UI Patterns Source plugins manager. |
355 | * |
356 | * @return \Drupal\ui_patterns\SourcePluginManager |
357 | * The manager for source plugins. |
358 | */ |
359 | private function sourceManager(): SourcePluginManager { |
360 | return \Drupal::service('plugin.manager.ui_patterns_source'); |
361 | } |
362 | |
363 | } |