Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
47.57% |
49 / 103 |
|
79.17% |
38 / 48 |
|
25.81% |
16 / 62 |
|
50.00% |
6 / 12 |
CRAP | |
0.00% |
0 / 1 |
| PageLayout | |
81.67% |
49 / 60 |
|
79.17% |
38 / 48 |
|
25.81% |
16 / 62 |
|
50.00% |
6 / 12 |
372.47 | |
0.00% |
0 / 1 |
| getPluginCollections | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getConditions | |
100.00% |
4 / 4 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| calculateDependencies | |
80.00% |
8 / 10 |
|
87.50% |
7 / 8 |
|
25.00% |
2 / 8 |
|
0.00% |
0 / 1 |
10.75 | |||
| delete | |
100.00% |
4 / 4 |
|
100.00% |
3 / 3 |
|
50.00% |
1 / 2 |
|
100.00% |
1 / 1 |
2.50 | |||
| postSave | |
77.78% |
7 / 9 |
|
80.00% |
4 / 5 |
|
50.00% |
2 / 4 |
|
0.00% |
0 / 1 |
4.12 | |||
| getProfile | |
83.33% |
5 / 6 |
|
66.67% |
2 / 3 |
|
50.00% |
1 / 2 |
|
0.00% |
0 / 1 |
2.50 | |||
| getSources | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| setSources | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getInstance | |
100.00% |
7 / 7 |
|
100.00% |
5 / 5 |
|
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
| isImpactingPageVariantDetection | |
63.64% |
7 / 11 |
|
68.75% |
11 / 16 |
|
5.56% |
2 / 36 |
|
0.00% |
0 / 1 |
61.91 | |||
| displayBuildable | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| sourceManager | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
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\display_builder\DisplayBuildableInterface; |
| 14 | use Drupal\display_builder\InstanceInterface; |
| 15 | use Drupal\display_builder\ProfileInterface; |
| 16 | use Drupal\display_builder_page_layout\AccessControlHandler; |
| 17 | use Drupal\display_builder_page_layout\Form\PageLayoutForm; |
| 18 | use Drupal\display_builder_page_layout\PageLayoutInterface; |
| 19 | use Drupal\display_builder_page_layout\PageLayoutListBuilder; |
| 20 | use Drupal\ui_patterns\SourcePluginManager; |
| 21 | |
| 22 | /** |
| 23 | * Defines the page layout entity type. |
| 24 | */ |
| 25 | #[ConfigEntityType( |
| 26 | id: 'page_layout', |
| 27 | label: new TranslatableMarkup('Page layout'), |
| 28 | label_collection: new TranslatableMarkup('Page layouts'), |
| 29 | label_singular: new TranslatableMarkup('page layout'), |
| 30 | label_plural: new TranslatableMarkup('page layouts'), |
| 31 | config_prefix: 'page_layout', |
| 32 | entity_keys: [ |
| 33 | 'id' => 'id', |
| 34 | 'label' => 'label', |
| 35 | 'weight' => 'weight', |
| 36 | 'uuid' => 'uuid', |
| 37 | ], |
| 38 | handlers: [ |
| 39 | 'access' => AccessControlHandler::class, |
| 40 | 'list_builder' => PageLayoutListBuilder::class, |
| 41 | 'form' => [ |
| 42 | 'add' => PageLayoutForm::class, |
| 43 | 'edit' => PageLayoutForm::class, |
| 44 | 'delete' => EntityDeleteForm::class, |
| 45 | ], |
| 46 | ], |
| 47 | links: [ |
| 48 | 'collection' => '/admin/structure/page-layout', |
| 49 | 'add-form' => '/admin/structure/page-layout/add', |
| 50 | 'edit-form' => '/admin/structure/page-layout/{page_layout}', |
| 51 | 'display-builder' => '/admin/structure/page-layout/{page_layout}/builder', |
| 52 | 'delete-form' => '/admin/structure/page-layout/{page_layout}/delete', |
| 53 | 'duplicate-form' => '/admin/structure/page-layout/{page_layout}/duplicate', |
| 54 | ], |
| 55 | admin_permission: 'administer page_layout', |
| 56 | label_count: [ |
| 57 | 'singular' => '@count page layout', |
| 58 | 'plural' => '@count page layouts', |
| 59 | ], |
| 60 | config_export: [ |
| 61 | 'id', |
| 62 | 'label', |
| 63 | 'weight', |
| 64 | DisplayBuildableInterface::PROFILE_PROPERTY, |
| 65 | DisplayBuildableInterface::SOURCES_PROPERTY, |
| 66 | 'conditions', |
| 67 | ], |
| 68 | )] |
| 69 | final class PageLayout extends ConfigEntityBase implements PageLayoutInterface { |
| 70 | |
| 71 | /** |
| 72 | * The ID of the page layout entity. |
| 73 | * |
| 74 | * This property's type was changed from `string` to `?string` (nullable) |
| 75 | * to support the entity duplication process. The original non-nullable type |
| 76 | * would cause a fatal error, as a new, duplicated entity does not have an |
| 77 | * ID until it is saved. |
| 78 | * |
| 79 | * @var string|null |
| 80 | * The unique identifier for the page layout. |
| 81 | */ |
| 82 | protected ?string $id; |
| 83 | |
| 84 | /** |
| 85 | * The example label. |
| 86 | */ |
| 87 | protected string $label; |
| 88 | |
| 89 | /** |
| 90 | * Weight of this page layout when negotiating the page variant. |
| 91 | * |
| 92 | * The first/lowest that is accessible according to conditions is loaded. |
| 93 | * |
| 94 | * @var int |
| 95 | */ |
| 96 | protected $weight = 0; |
| 97 | |
| 98 | /** |
| 99 | * Display Builder Profile ID. |
| 100 | */ |
| 101 | protected string $profile = ''; |
| 102 | |
| 103 | /** |
| 104 | * A list of sources plugins. |
| 105 | * |
| 106 | * @var array |
| 107 | */ |
| 108 | protected $sources = []; |
| 109 | |
| 110 | /** |
| 111 | * Condition settings for storage. |
| 112 | * |
| 113 | * @var array |
| 114 | */ |
| 115 | protected $conditions = []; |
| 116 | |
| 117 | /** |
| 118 | * The loaded display builder instance. |
| 119 | */ |
| 120 | protected ?InstanceInterface $instance; |
| 121 | |
| 122 | /** |
| 123 | * The conditions plugins for this page. |
| 124 | */ |
| 125 | private ConditionPluginCollection $conditionPluginCollection; |
| 126 | |
| 127 | /** |
| 128 | * {@inheritdoc} |
| 129 | */ |
| 130 | public function getPluginCollections(): array { |
| 131 | return [ |
| 132 | 'conditions' => $this->getConditions(), |
| 133 | ]; |
| 134 | } |
| 135 | |
| 136 | /** |
| 137 | * {@inheritdoc} |
| 138 | */ |
| 139 | public function getConditions(): ConditionPluginCollection { |
| 140 | if (!isset($this->conditionPluginCollection)) { |
| 141 | // Static call because EntityBase and descendants don't support |
| 142 | // dependency injection. |
| 143 | $manager = \Drupal::service('plugin.manager.condition'); |
| 144 | $this->conditionPluginCollection = new ConditionPluginCollection($manager, $this->get('conditions')); |
| 145 | } |
| 146 | |
| 147 | return $this->conditionPluginCollection; |
| 148 | } |
| 149 | |
| 150 | /** |
| 151 | * {@inheritdoc} |
| 152 | */ |
| 153 | public function calculateDependencies(): PageLayout { |
| 154 | parent::calculateDependencies(); |
| 155 | $display_builder = $this->getProfile(); |
| 156 | $instance = $this->getInstance(); |
| 157 | |
| 158 | if ($display_builder && $instance) { |
| 159 | $this->addDependency('config', $display_builder->getConfigDependencyName()); |
| 160 | $contexts = $instance->getContexts() ?? []; |
| 161 | |
| 162 | foreach ($this->displayBuildable()->getSources() as $source_data) { |
| 163 | /** @var \Drupal\ui_patterns\SourceInterface $source */ |
| 164 | $source = $this->sourceManager()->getSource('', [], $source_data, $contexts); |
| 165 | $this->addDependencies($source->calculateDependencies()); |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | return $this; |
| 170 | } |
| 171 | |
| 172 | /** |
| 173 | * {@inheritdoc} |
| 174 | */ |
| 175 | public function delete(): void { |
| 176 | if ($this->getInstance()) { |
| 177 | $storage = $this->entityTypeManager()->getStorage('display_builder_instance'); |
| 178 | $storage->delete([$this->getInstance()]); |
| 179 | } |
| 180 | |
| 181 | parent::delete(); |
| 182 | } |
| 183 | |
| 184 | /** |
| 185 | * {@inheritdoc} |
| 186 | */ |
| 187 | public function postSave(EntityStorageInterface $storage, $update = TRUE): void { |
| 188 | $this->displayBuildable()->initInstanceIfMissing(); |
| 189 | $instance = $this->getInstance(); |
| 190 | |
| 191 | // Save the profile in the instance if changed. |
| 192 | if ($instance->getProfile()?->id() !== $this->profile) { |
| 193 | $instance->setProfile($this->profile); |
| 194 | $instance->save(); |
| 195 | } |
| 196 | |
| 197 | if ($this->isImpactingPageVariantDetection($update)) { |
| 198 | // In DisplayBuilderPageVariant we add PageLayout::>getCacheTags() to the |
| 199 | // page renderable but it works only for the pages already managed by |
| 200 | // Display Builder. |
| 201 | // In PageVariantSubscriber::onSelectPageDisplayVariant() we add a custom |
| 202 | // tag for the others. |
| 203 | /** @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $entity_type */ |
| 204 | $entity_type = $this->getEntityType(); |
| 205 | \Drupal::service('cache_tags.invalidator')->invalidateTags([$entity_type->getConfigPrefix()]); |
| 206 | } |
| 207 | |
| 208 | parent::postSave($storage, $update); |
| 209 | } |
| 210 | |
| 211 | /** |
| 212 | * {@inheritdoc} |
| 213 | */ |
| 214 | public function getProfile(): ?ProfileInterface { |
| 215 | $storage = $this->entityTypeManager()->getStorage('display_builder_profile'); |
| 216 | $profile_id = $this->get(DisplayBuildableInterface::PROFILE_PROPERTY); |
| 217 | |
| 218 | if (!$profile_id) { |
| 219 | return NULL; |
| 220 | } |
| 221 | |
| 222 | /** @var \Drupal\display_builder\ProfileInterface $builder */ |
| 223 | $builder = $storage->load($profile_id); |
| 224 | |
| 225 | return $builder; |
| 226 | } |
| 227 | |
| 228 | /** |
| 229 | * {@inheritdoc} |
| 230 | */ |
| 231 | public function getSources(): array { |
| 232 | return $this->sources; |
| 233 | } |
| 234 | |
| 235 | /** |
| 236 | * {@inheritdoc} |
| 237 | */ |
| 238 | public function setSources(array $sources): void { |
| 239 | $this->sources = $sources; |
| 240 | } |
| 241 | |
| 242 | /** |
| 243 | * Gets the Display Builder instance. |
| 244 | * |
| 245 | * @return \Drupal\display_builder\InstanceInterface|null |
| 246 | * A display builder instance. |
| 247 | */ |
| 248 | protected function getInstance(): ?InstanceInterface { |
| 249 | if (!$this->displayBuildable()->getInstanceId()) { |
| 250 | return NULL; |
| 251 | } |
| 252 | |
| 253 | if (!isset($this->instance)) { |
| 254 | $instance_id = $this->displayBuildable()->getInstanceId(); |
| 255 | /** @var \Drupal\display_builder\InstanceInterface|null $instance */ |
| 256 | $instance = $this->entityTypeManager()->getStorage('display_builder_instance')->load($instance_id); |
| 257 | $this->instance = $instance; |
| 258 | } |
| 259 | |
| 260 | return $this->instance; |
| 261 | } |
| 262 | |
| 263 | /** |
| 264 | * Does the page cache need to be flushed? |
| 265 | * |
| 266 | * Flushing a cache is something to be careful enough. Let's flush only when |
| 267 | * needed. |
| 268 | * |
| 269 | * @param bool $update |
| 270 | * TRUE if the entity has been updated, or FALSE if it has been inserted. |
| 271 | * |
| 272 | * @return bool |
| 273 | * TRUE if the cache need to be flushed. |
| 274 | */ |
| 275 | private function isImpactingPageVariantDetection(bool $update = TRUE): bool { |
| 276 | // A new active page layout has been added. |
| 277 | if (!$update && $this->status && !empty($this->sources)) { |
| 278 | return TRUE; |
| 279 | } |
| 280 | |
| 281 | // Other additions have no impact. |
| 282 | if (!$update) { |
| 283 | return FALSE; |
| 284 | } |
| 285 | |
| 286 | $previous = $this->originalEntity; |
| 287 | |
| 288 | // Those properties are impacting AccessControlHandler logic and |
| 289 | // PageVariantSubscriber results. |
| 290 | foreach (['weight', 'conditions', 'status'] as $property) { |
| 291 | if ($this->get($property) !== $previous->get($property)) { |
| 292 | return TRUE; |
| 293 | } |
| 294 | } |
| 295 | |
| 296 | // A page layout with empty sources is skipped by AccessControlHandler. |
| 297 | // This is also altering PageVariantSubscriber results. |
| 298 | if (empty($this->sources) !== empty($previous->get('sources'))) { |
| 299 | return TRUE; |
| 300 | } |
| 301 | |
| 302 | return FALSE; |
| 303 | } |
| 304 | |
| 305 | /** |
| 306 | * Gets the display buildable manager. |
| 307 | * |
| 308 | * @return \Drupal\display_builder\DisplayBuildableInterface |
| 309 | * The manager for display buildable. |
| 310 | */ |
| 311 | private function displayBuildable(): DisplayBuildableInterface { |
| 312 | /** @var \Drupal\display_builder\DisplayBuildablePluginManager $manager */ |
| 313 | $manager = \Drupal::service('plugin.manager.display_buildable'); |
| 314 | /** @var \Drupal\display_builder\DisplayBuildableInterface $buildable */ |
| 315 | $buildable = $manager->createInstance('page_layout', ['entity' => $this]); |
| 316 | |
| 317 | return $buildable; |
| 318 | } |
| 319 | |
| 320 | /** |
| 321 | * Gets the UI Patterns Source plugins manager. |
| 322 | * |
| 323 | * @return \Drupal\ui_patterns\SourcePluginManager |
| 324 | * The manager for source plugins. |
| 325 | */ |
| 326 | private function sourceManager(): SourcePluginManager { |
| 327 | return \Drupal::service('plugin.manager.ui_patterns_source'); |
| 328 | } |
| 329 | |
| 330 | } |
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.
| 154 | parent::calculateDependencies(); |
| 155 | $display_builder = $this->getProfile(); |
| 156 | $instance = $this->getInstance(); |
| 157 | |
| 158 | if ($display_builder && $instance) { |
| 158 | if ($display_builder && $instance) { |
| 158 | if ($display_builder && $instance) { |
| 159 | $this->addDependency('config', $display_builder->getConfigDependencyName()); |
| 160 | $contexts = $instance->getContexts() ?? []; |
| 161 | |
| 162 | foreach ($this->displayBuildable()->getSources() as $source_data) { |
| 162 | foreach ($this->displayBuildable()->getSources() as $source_data) { |
| 162 | foreach ($this->displayBuildable()->getSources() as $source_data) { |
| 163 | /** @var \Drupal\ui_patterns\SourceInterface $source */ |
| 164 | $source = $this->sourceManager()->getSource('', [], $source_data, $contexts); |
| 162 | foreach ($this->displayBuildable()->getSources() as $source_data) { |
| 163 | /** @var \Drupal\ui_patterns\SourceInterface $source */ |
| 164 | $source = $this->sourceManager()->getSource('', [], $source_data, $contexts); |
| 165 | $this->addDependencies($source->calculateDependencies()); |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | return $this; |
| 169 | return $this; |
| 170 | } |
| 176 | if ($this->getInstance()) { |
| 177 | $storage = $this->entityTypeManager()->getStorage('display_builder_instance'); |
| 178 | $storage->delete([$this->getInstance()]); |
| 179 | } |
| 180 | |
| 181 | parent::delete(); |
| 181 | parent::delete(); |
| 182 | } |
| 313 | $manager = \Drupal::service('plugin.manager.display_buildable'); |
| 314 | /** @var \Drupal\display_builder\DisplayBuildableInterface $buildable */ |
| 315 | $buildable = $manager->createInstance('page_layout', ['entity' => $this]); |
| 316 | |
| 317 | return $buildable; |
| 318 | } |
| 140 | if (!isset($this->conditionPluginCollection)) { |
| 143 | $manager = \Drupal::service('plugin.manager.condition'); |
| 144 | $this->conditionPluginCollection = new ConditionPluginCollection($manager, $this->get('conditions')); |
| 145 | } |
| 146 | |
| 147 | return $this->conditionPluginCollection; |
| 147 | return $this->conditionPluginCollection; |
| 148 | } |
| 249 | if (!$this->displayBuildable()->getInstanceId()) { |
| 250 | return NULL; |
| 253 | if (!isset($this->instance)) { |
| 254 | $instance_id = $this->displayBuildable()->getInstanceId(); |
| 255 | /** @var \Drupal\display_builder\InstanceInterface|null $instance */ |
| 256 | $instance = $this->entityTypeManager()->getStorage('display_builder_instance')->load($instance_id); |
| 257 | $this->instance = $instance; |
| 258 | } |
| 259 | |
| 260 | return $this->instance; |
| 260 | return $this->instance; |
| 261 | } |
| 132 | 'conditions' => $this->getConditions(), |
| 133 | ]; |
| 134 | } |
| 215 | $storage = $this->entityTypeManager()->getStorage('display_builder_profile'); |
| 216 | $profile_id = $this->get(DisplayBuildableInterface::PROFILE_PROPERTY); |
| 217 | |
| 218 | if (!$profile_id) { |
| 219 | return NULL; |
| 223 | $builder = $storage->load($profile_id); |
| 224 | |
| 225 | return $builder; |
| 226 | } |
| 232 | return $this->sources; |
| 233 | } |
| 275 | private function isImpactingPageVariantDetection(bool $update = TRUE): bool { |
| 276 | // A new active page layout has been added. |
| 277 | if (!$update && $this->status && !empty($this->sources)) { |
| 277 | if (!$update && $this->status && !empty($this->sources)) { |
| 277 | if (!$update && $this->status && !empty($this->sources)) { |
| 277 | if (!$update && $this->status && !empty($this->sources)) { |
| 277 | if (!$update && $this->status && !empty($this->sources)) { |
| 278 | return TRUE; |
| 282 | if (!$update) { |
| 283 | return FALSE; |
| 286 | $previous = $this->originalEntity; |
| 287 | |
| 288 | // Those properties are impacting AccessControlHandler logic and |
| 289 | // PageVariantSubscriber results. |
| 290 | foreach (['weight', 'conditions', 'status'] as $property) { |
| 290 | foreach (['weight', 'conditions', 'status'] as $property) { |
| 291 | if ($this->get($property) !== $previous->get($property)) { |
| 292 | return TRUE; |
| 290 | foreach (['weight', 'conditions', 'status'] as $property) { |
| 290 | foreach (['weight', 'conditions', 'status'] as $property) { |
| 291 | if ($this->get($property) !== $previous->get($property)) { |
| 292 | return TRUE; |
| 293 | } |
| 294 | } |
| 295 | |
| 296 | // A page layout with empty sources is skipped by AccessControlHandler. |
| 297 | // This is also altering PageVariantSubscriber results. |
| 298 | if (empty($this->sources) !== empty($previous->get('sources'))) { |
| 299 | return TRUE; |
| 302 | return FALSE; |
| 303 | } |
| 187 | public function postSave(EntityStorageInterface $storage, $update = TRUE): void { |
| 188 | $this->displayBuildable()->initInstanceIfMissing(); |
| 189 | $instance = $this->getInstance(); |
| 190 | |
| 191 | // Save the profile in the instance if changed. |
| 192 | if ($instance->getProfile()?->id() !== $this->profile) { |
| 193 | $instance->setProfile($this->profile); |
| 194 | $instance->save(); |
| 195 | } |
| 196 | |
| 197 | if ($this->isImpactingPageVariantDetection($update)) { |
| 197 | if ($this->isImpactingPageVariantDetection($update)) { |
| 204 | $entity_type = $this->getEntityType(); |
| 205 | \Drupal::service('cache_tags.invalidator')->invalidateTags([$entity_type->getConfigPrefix()]); |
| 206 | } |
| 207 | |
| 208 | parent::postSave($storage, $update); |
| 208 | parent::postSave($storage, $update); |
| 209 | } |
| 238 | public function setSources(array $sources): void { |
| 239 | $this->sources = $sources; |
| 240 | } |
| 327 | return \Drupal::service('plugin.manager.ui_patterns_source'); |
| 328 | } |