Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
58.93% |
33 / 56 |
|
53.85% |
28 / 52 |
|
19.30% |
11 / 57 |
|
54.55% |
6 / 11 |
CRAP | |
0.00% |
0 / 1 |
| PatternPreset | |
58.93% |
33 / 56 |
|
53.85% |
28 / 52 |
|
19.30% |
11 / 57 |
|
54.55% |
6 / 11 |
605.37 | |
0.00% |
0 / 1 |
| getGroup | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getSummary | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
| getSources | |
87.50% |
7 / 8 |
|
88.89% |
8 / 9 |
|
16.67% |
2 / 12 |
|
0.00% |
0 / 1 |
26.83 | |||
| getContexts | |
66.67% |
2 / 3 |
|
75.00% |
3 / 4 |
|
33.33% |
1 / 3 |
|
0.00% |
0 / 1 |
5.67 | |||
| calculateDependencies | |
100.00% |
9 / 9 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| areContextsSatisfied | |
0.00% |
0 / 11 |
|
0.00% |
0 / 11 |
|
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
42 | |||
| getContextFromSource | |
75.00% |
3 / 4 |
|
66.67% |
2 / 3 |
|
50.00% |
1 / 2 |
|
0.00% |
0 / 1 |
2.50 | |||
| getContextsFromComponent | |
0.00% |
0 / 9 |
|
0.00% |
0 / 10 |
|
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
20 | |||
| fillInternalId | |
100.00% |
5 / 5 |
|
100.00% |
8 / 8 |
|
8.33% |
1 / 12 |
|
100.00% |
1 / 1 |
24.26 | |||
| sourcePluginManager | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| slotSourceProxy | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Drupal\display_builder\Entity; |
| 6 | |
| 7 | use Drupal\Core\Config\Entity\ConfigEntityBase; |
| 8 | use Drupal\Core\Entity\Attribute\ConfigEntityType; |
| 9 | use Drupal\Core\Entity\EntityDeleteForm; |
| 10 | use Drupal\Core\StringTranslation\TranslatableMarkup; |
| 11 | use Drupal\display_builder\Form\PatternPresetForm; |
| 12 | use Drupal\display_builder\PatternPresetInterface; |
| 13 | use Drupal\display_builder\SlotSourceProxy; |
| 14 | use Drupal\display_builder_ui\PatternPresetListBuilder; |
| 15 | use Drupal\ui_patterns\SourceInterface; |
| 16 | use Drupal\ui_patterns\SourcePluginManager; |
| 17 | |
| 18 | /** |
| 19 | * Defines the Pattern preset entity type. |
| 20 | */ |
| 21 | #[ConfigEntityType( |
| 22 | id: 'pattern_preset', |
| 23 | label: new TranslatableMarkup('Pattern preset'), |
| 24 | label_collection: new TranslatableMarkup('Pattern presets'), |
| 25 | label_singular: new TranslatableMarkup('Pattern preset'), |
| 26 | label_plural: new TranslatableMarkup('Pattern presets'), |
| 27 | entity_keys: [ |
| 28 | 'id' => 'id', |
| 29 | 'label' => 'label', |
| 30 | 'theme' => 'theme', |
| 31 | 'description' => 'description', |
| 32 | 'group' => 'group', |
| 33 | 'sources' => 'sources', |
| 34 | 'weight' => 'weight', |
| 35 | ], |
| 36 | handlers: [ |
| 37 | 'list_builder' => PatternPresetListBuilder::class, |
| 38 | 'form' => [ |
| 39 | 'add' => PatternPresetForm::class, |
| 40 | 'edit' => PatternPresetForm::class, |
| 41 | 'delete' => EntityDeleteForm::class, |
| 42 | ], |
| 43 | ], |
| 44 | links: [ |
| 45 | 'add-form' => '/admin/structure/display-builder/preset/add', |
| 46 | 'delete-form' => '/admin/structure/display-builder/preset/{pattern_preset}/delete', |
| 47 | 'collection' => '/admin/structure/display-builder/preset', |
| 48 | ], |
| 49 | admin_permission: 'administer Pattern preset', |
| 50 | constraints: [ |
| 51 | 'ImmutableProperties' => [ |
| 52 | 'id', |
| 53 | ], |
| 54 | ], |
| 55 | config_export: [ |
| 56 | 'id', |
| 57 | 'label', |
| 58 | 'description', |
| 59 | 'group', |
| 60 | 'sources', |
| 61 | 'weight', |
| 62 | ], |
| 63 | )] |
| 64 | final class PatternPreset extends ConfigEntityBase implements PatternPresetInterface { |
| 65 | |
| 66 | /** |
| 67 | * The preset ID. |
| 68 | */ |
| 69 | protected string $id; |
| 70 | |
| 71 | /** |
| 72 | * The preset label. |
| 73 | */ |
| 74 | protected string $label = ''; |
| 75 | |
| 76 | /** |
| 77 | * The preset description. |
| 78 | */ |
| 79 | protected string $description = ''; |
| 80 | |
| 81 | /** |
| 82 | * The preset group. |
| 83 | */ |
| 84 | protected string $group = ''; |
| 85 | |
| 86 | /** |
| 87 | * The preset sources. |
| 88 | */ |
| 89 | protected array $sources = []; |
| 90 | |
| 91 | /** |
| 92 | * Weight to order the entity in lists. |
| 93 | * |
| 94 | * @var int |
| 95 | */ |
| 96 | protected $weight = 0; |
| 97 | |
| 98 | /** |
| 99 | * The UI Patterns source plugin manager. |
| 100 | */ |
| 101 | protected SourcePluginManager $sourcePluginManager; |
| 102 | |
| 103 | /** |
| 104 | * Slot source proxy. |
| 105 | */ |
| 106 | protected SlotSourceProxy $slotSourceProxy; |
| 107 | |
| 108 | /** |
| 109 | * {@inheritdoc} |
| 110 | */ |
| 111 | public function getGroup(): string { |
| 112 | return $this->group; |
| 113 | } |
| 114 | |
| 115 | /** |
| 116 | * {@inheritdoc} |
| 117 | */ |
| 118 | public function getSummary(): string { |
| 119 | $contexts = []; |
| 120 | $data = $this->getSources($contexts, FALSE); |
| 121 | $data = $this->slotSourceProxy()->getLabelWithSummary($data); |
| 122 | |
| 123 | return $data['summary'] ?: $data['label']; |
| 124 | } |
| 125 | |
| 126 | /** |
| 127 | * {@inheritdoc} |
| 128 | */ |
| 129 | public function getSources(array $contexts = [], bool $fillInternalId = TRUE): array { |
| 130 | $data = $this->get('sources') ?? []; |
| 131 | |
| 132 | if (isset($data[0]) && \count($data) === 1) { |
| 133 | $data = \reset($data); |
| 134 | } |
| 135 | |
| 136 | if (empty($data) || !isset($data['source_id'])) { |
| 137 | return []; |
| 138 | } |
| 139 | |
| 140 | if ($fillInternalId) { |
| 141 | self::fillInternalId($data); |
| 142 | } |
| 143 | |
| 144 | return $data; |
| 145 | } |
| 146 | |
| 147 | /** |
| 148 | * {@inheritdoc} |
| 149 | * |
| 150 | * @see \Drupal\Core\Config\Entity\ConfigEntityInterface |
| 151 | */ |
| 152 | public function getContexts(): array { |
| 153 | // The root level is a single nestable source plugin. |
| 154 | if (!isset($this->sources['source_id']) || !isset($this->sources['source'])) { |
| 155 | return []; |
| 156 | } |
| 157 | |
| 158 | return $this->getContextFromSource($this->sources['source_id'], $this->sources['source']); |
| 159 | } |
| 160 | |
| 161 | /** |
| 162 | * {@inheritdoc} |
| 163 | */ |
| 164 | public function calculateDependencies(): self { |
| 165 | parent::calculateDependencies(); |
| 166 | |
| 167 | // The root level is a single nestable source plugin. |
| 168 | if (!isset($this->sources['source_id'])) { |
| 169 | return $this; |
| 170 | } |
| 171 | // This will automatically be done by parent::calculateDependencies() if we |
| 172 | // implement EntityWithPluginCollectionInterface. |
| 173 | $configuration = [ |
| 174 | 'settings' => $this->sources['source'] ?? [], |
| 175 | ]; |
| 176 | /** @var \Drupal\ui_patterns\SourceInterface $source */ |
| 177 | $source = $this->sourcePluginManager()->createInstance($this->sources['source_id'], $configuration); |
| 178 | $this->addDependencies($source->calculateDependencies()); |
| 179 | |
| 180 | return $this; |
| 181 | } |
| 182 | |
| 183 | /** |
| 184 | * {@inheritdoc} |
| 185 | */ |
| 186 | public function areContextsSatisfied(array $contexts): bool { |
| 187 | $context_definitions = $this->getContexts(); |
| 188 | |
| 189 | if (empty($context_definitions)) { |
| 190 | return TRUE; |
| 191 | } |
| 192 | |
| 193 | foreach ($context_definitions as $key => $context_definition) { |
| 194 | if (!$context_definition->isRequired()) { |
| 195 | continue; |
| 196 | } |
| 197 | |
| 198 | if (!\array_key_exists($key, $contexts)) { |
| 199 | return FALSE; |
| 200 | } |
| 201 | |
| 202 | if (!$context_definition->isSatisfiedBy($contexts[$key])) { |
| 203 | return FALSE; |
| 204 | } |
| 205 | } |
| 206 | |
| 207 | return TRUE; |
| 208 | } |
| 209 | |
| 210 | /** |
| 211 | * Recursively get the source contexts. |
| 212 | * |
| 213 | * @param string $source_id |
| 214 | * Source plugin ID. |
| 215 | * @param array $source |
| 216 | * Source plugin configuration. |
| 217 | * |
| 218 | * @return array |
| 219 | * Context definitions of the source. |
| 220 | */ |
| 221 | private function getContextFromSource(string $source_id, array $source): array { |
| 222 | /** @var \Drupal\ui_patterns\SourceInterface $source */ |
| 223 | $source = $this->sourcePluginManager()->createInstance($source_id, ['settings' => $source]); |
| 224 | |
| 225 | if ($source->getPluginId() === 'component') { |
| 226 | return $this->getContextsFromComponent($source); |
| 227 | } |
| 228 | |
| 229 | // @todo Traverse also context switchers. |
| 230 | return $source->getContextDefinitions(); |
| 231 | } |
| 232 | |
| 233 | /** |
| 234 | * Get contexts from component. |
| 235 | * |
| 236 | * Go through all slots and props to get the nested sources contexts. |
| 237 | * |
| 238 | * @param \Drupal\ui_patterns\SourceInterface $source |
| 239 | * Source plugin. |
| 240 | * |
| 241 | * @return array |
| 242 | * Context definitions of the component source. |
| 243 | */ |
| 244 | private function getContextsFromComponent(SourceInterface $source): array { |
| 245 | $contexts = []; |
| 246 | $slots = $source->getSetting('component')['slots'] ?? []; |
| 247 | |
| 248 | foreach ($slots as $slot) { |
| 249 | foreach ($slot['sources'] ?? [] as $slot_source) { |
| 250 | $contexts = \array_merge($contexts, $this->getContextFromSource($slot_source['source_id'], $slot_source['source'])); |
| 251 | } |
| 252 | } |
| 253 | |
| 254 | $props = $source->getSetting('component')['props'] ?? []; |
| 255 | |
| 256 | foreach ($props as $prop_source) { |
| 257 | $contexts = \array_merge($contexts, $this->getContextFromSource($prop_source['source_id'], $prop_source['source'])); |
| 258 | } |
| 259 | |
| 260 | return $contexts; |
| 261 | } |
| 262 | |
| 263 | /** |
| 264 | * Recursively fill the node_id key. |
| 265 | * |
| 266 | * @param array $array |
| 267 | * The array reference. |
| 268 | */ |
| 269 | private static function fillInternalId(array &$array): void { |
| 270 | if (isset($array['source_id']) && !isset($array['node_id'])) { |
| 271 | $array['node_id'] = \uniqid(); |
| 272 | } |
| 273 | |
| 274 | foreach ($array as &$value) { |
| 275 | if (\is_array($value)) { |
| 276 | self::fillInternalId($value); |
| 277 | } |
| 278 | } |
| 279 | } |
| 280 | |
| 281 | /** |
| 282 | * Gets the source plugin manager. |
| 283 | * |
| 284 | * @return \Drupal\ui_patterns\SourcePluginManager |
| 285 | * The source plugin manager. |
| 286 | */ |
| 287 | private function sourcePluginManager(): SourcePluginManager { |
| 288 | return $this->sourcePluginManager ??= \Drupal::service('plugin.manager.ui_patterns_source'); |
| 289 | } |
| 290 | |
| 291 | /** |
| 292 | * Slot source proxy. |
| 293 | * |
| 294 | * @return \Drupal\display_builder\SlotSourceProxy |
| 295 | * The slot source proxy. |
| 296 | */ |
| 297 | private function slotSourceProxy(): SlotSourceProxy { |
| 298 | return $this->slotSourceProxy ??= \Drupal::service('display_builder.slot_sources_proxy'); |
| 299 | } |
| 300 | |
| 301 | } |
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.
| 186 | public function areContextsSatisfied(array $contexts): bool { |
| 187 | $context_definitions = $this->getContexts(); |
| 188 | |
| 189 | if (empty($context_definitions)) { |
| 190 | return TRUE; |
| 193 | foreach ($context_definitions as $key => $context_definition) { |
| 193 | foreach ($context_definitions as $key => $context_definition) { |
| 193 | foreach ($context_definitions as $key => $context_definition) { |
| 194 | if (!$context_definition->isRequired()) { |
| 195 | continue; |
| 198 | if (!\array_key_exists($key, $contexts)) { |
| 199 | return FALSE; |
| 202 | if (!$context_definition->isSatisfiedBy($contexts[$key])) { |
| 203 | return FALSE; |
| 193 | foreach ($context_definitions as $key => $context_definition) { |
| 194 | if (!$context_definition->isRequired()) { |
| 195 | continue; |
| 196 | } |
| 197 | |
| 198 | if (!\array_key_exists($key, $contexts)) { |
| 199 | return FALSE; |
| 200 | } |
| 201 | |
| 202 | if (!$context_definition->isSatisfiedBy($contexts[$key])) { |
| 203 | return FALSE; |
| 204 | } |
| 205 | } |
| 206 | |
| 207 | return TRUE; |
| 165 | parent::calculateDependencies(); |
| 166 | |
| 167 | // The root level is a single nestable source plugin. |
| 168 | if (!isset($this->sources['source_id'])) { |
| 169 | return $this; |
| 174 | 'settings' => $this->sources['source'] ?? [], |
| 175 | ]; |
| 176 | /** @var \Drupal\ui_patterns\SourceInterface $source */ |
| 177 | $source = $this->sourcePluginManager()->createInstance($this->sources['source_id'], $configuration); |
| 178 | $this->addDependencies($source->calculateDependencies()); |
| 179 | |
| 180 | return $this; |
| 269 | private static function fillInternalId(array &$array): void { |
| 270 | if (isset($array['source_id']) && !isset($array['node_id'])) { |
| 270 | if (isset($array['source_id']) && !isset($array['node_id'])) { |
| 271 | $array['node_id'] = \uniqid(); |
| 272 | } |
| 273 | |
| 274 | foreach ($array as &$value) { |
| 274 | foreach ($array as &$value) { |
| 274 | foreach ($array as &$value) { |
| 275 | if (\is_array($value)) { |
| 274 | foreach ($array as &$value) { |
| 275 | if (\is_array($value)) { |
| 276 | self::fillInternalId($value); |
| 274 | foreach ($array as &$value) { |
| 275 | if (\is_array($value)) { |
| 276 | self::fillInternalId($value); |
| 277 | } |
| 278 | } |
| 279 | } |
| 221 | private function getContextFromSource(string $source_id, array $source): array { |
| 222 | /** @var \Drupal\ui_patterns\SourceInterface $source */ |
| 223 | $source = $this->sourcePluginManager()->createInstance($source_id, ['settings' => $source]); |
| 224 | |
| 225 | if ($source->getPluginId() === 'component') { |
| 226 | return $this->getContextsFromComponent($source); |
| 230 | return $source->getContextDefinitions(); |
| 154 | if (!isset($this->sources['source_id']) || !isset($this->sources['source'])) { |
| 154 | if (!isset($this->sources['source_id']) || !isset($this->sources['source'])) { |
| 155 | return []; |
| 158 | return $this->getContextFromSource($this->sources['source_id'], $this->sources['source']); |
| 244 | private function getContextsFromComponent(SourceInterface $source): array { |
| 245 | $contexts = []; |
| 246 | $slots = $source->getSetting('component')['slots'] ?? []; |
| 247 | |
| 248 | foreach ($slots as $slot) { |
| 248 | foreach ($slots as $slot) { |
| 249 | foreach ($slot['sources'] ?? [] as $slot_source) { |
| 249 | foreach ($slot['sources'] ?? [] as $slot_source) { |
| 249 | foreach ($slot['sources'] ?? [] as $slot_source) { |
| 250 | $contexts = \array_merge($contexts, $this->getContextFromSource($slot_source['source_id'], $slot_source['source'])); |
| 248 | foreach ($slots as $slot) { |
| 249 | foreach ($slot['sources'] ?? [] as $slot_source) { |
| 248 | foreach ($slots as $slot) { |
| 249 | foreach ($slot['sources'] ?? [] as $slot_source) { |
| 250 | $contexts = \array_merge($contexts, $this->getContextFromSource($slot_source['source_id'], $slot_source['source'])); |
| 251 | } |
| 252 | } |
| 253 | |
| 254 | $props = $source->getSetting('component')['props'] ?? []; |
| 255 | |
| 256 | foreach ($props as $prop_source) { |
| 256 | foreach ($props as $prop_source) { |
| 256 | foreach ($props as $prop_source) { |
| 257 | $contexts = \array_merge($contexts, $this->getContextFromSource($prop_source['source_id'], $prop_source['source'])); |
| 256 | foreach ($props as $prop_source) { |
| 257 | $contexts = \array_merge($contexts, $this->getContextFromSource($prop_source['source_id'], $prop_source['source'])); |
| 258 | } |
| 259 | |
| 260 | return $contexts; |
| 112 | return $this->group; |
| 129 | public function getSources(array $contexts = [], bool $fillInternalId = TRUE): array { |
| 130 | $data = $this->get('sources') ?? []; |
| 131 | |
| 132 | if (isset($data[0]) && \count($data) === 1) { |
| 132 | if (isset($data[0]) && \count($data) === 1) { |
| 133 | $data = \reset($data); |
| 134 | } |
| 135 | |
| 136 | if (empty($data) || !isset($data['source_id'])) { |
| 136 | if (empty($data) || !isset($data['source_id'])) { |
| 136 | if (empty($data) || !isset($data['source_id'])) { |
| 137 | return []; |
| 140 | if ($fillInternalId) { |
| 141 | self::fillInternalId($data); |
| 142 | } |
| 143 | |
| 144 | return $data; |
| 144 | return $data; |
| 119 | $contexts = []; |
| 120 | $data = $this->getSources($contexts, FALSE); |
| 121 | $data = $this->slotSourceProxy()->getLabelWithSummary($data); |
| 122 | |
| 123 | return $data['summary'] ?: $data['label']; |
| 298 | return $this->slotSourceProxy ??= \Drupal::service('display_builder.slot_sources_proxy'); |
| 288 | return $this->sourcePluginManager ??= \Drupal::service('plugin.manager.ui_patterns_source'); |