Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
84.92% |
152 / 179 |
|
70.16% |
87 / 124 |
|
54.26% |
51 / 94 |
|
67.50% |
27 / 40 |
CRAP | |
0.00% |
0 / 1 |
| Instance | |
84.92% |
152 / 179 |
|
70.16% |
87 / 124 |
|
54.26% |
51 / 94 |
|
77.50% |
31 / 40 |
613.45 | |
0.00% |
0 / 1 |
| baseFieldDefinitions | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| label | |
100.00% |
5 / 5 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| postCreate | |
100.00% |
8 / 8 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| getProfile | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| moveToRoot | |
77.78% |
7 / 9 |
|
60.00% |
3 / 5 |
|
33.33% |
1 / 3 |
|
0.00% |
0 / 1 |
5.67 | |||
| moveToSlot | |
85.71% |
12 / 14 |
|
62.50% |
5 / 8 |
|
25.00% |
1 / 4 |
|
0.00% |
0 / 1 |
10.75 | |||
| attachToRoot | |
77.78% |
7 / 9 |
|
33.33% |
2 / 6 |
|
25.00% |
1 / 4 |
|
0.00% |
0 / 1 |
6.80 | |||
| attachToSlot | |
87.50% |
14 / 16 |
|
54.55% |
6 / 11 |
|
22.22% |
2 / 9 |
|
0.00% |
0 / 1 |
16.76 | |||
| getNode | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getParentId | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| setSource | |
100.00% |
6 / 6 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| setThirdPartySettings | |
100.00% |
9 / 9 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| remove | |
100.00% |
12 / 12 |
|
100.00% |
9 / 9 |
|
60.00% |
3 / 5 |
|
100.00% |
1 / 1 |
5.02 | |||
| getRuntimeContexts | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getAvailableContexts | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getCurrentState | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| publish | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| restore | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| clear | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getPast | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getFuture | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getUsers | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| isPublishable | |
0.00% |
0 / 5 |
|
0.00% |
0 / 5 |
|
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
| getPublishedHash | |
100.00% |
2 / 2 |
|
100.00% |
4 / 4 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| getPublishedTime | |
100.00% |
2 / 2 |
|
75.00% |
3 / 4 |
|
50.00% |
1 / 2 |
|
100.00% |
1 / 1 |
2.50 | |||
| isPublished | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| isPublishedPresent | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getPathIndex | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getHash | |
100.00% |
3 / 3 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| setNewPresent | |
100.00% |
14 / 14 |
|
90.00% |
9 / 10 |
|
50.00% |
6 / 12 |
|
100.00% |
1 / 1 |
8.12 | |||
| getUniqId | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getBuildablePlugin | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| normalizeRootLevel | |
100.00% |
5 / 5 |
|
85.71% |
6 / 7 |
|
33.33% |
2 / 6 |
|
100.00% |
1 / 1 |
5.67 | |||
| getSourceTree | |
100.00% |
3 / 3 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| sampleEntityGenerator | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| slotSourceProxy | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| nodeLabel | |
100.00% |
2 / 2 |
|
75.00% |
3 / 4 |
|
50.00% |
1 / 2 |
|
100.00% |
1 / 1 |
2.50 | |||
| currentUser | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getStorage | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| refreshContexts | |
0.00% |
0 / 10 |
|
0.00% |
0 / 11 |
|
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
30 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Drupal\display_builder\Entity; |
| 6 | |
| 7 | use Drupal\Core\Entity\Attribute\ContentEntityType; |
| 8 | use Drupal\Core\Entity\ContentEntityBase; |
| 9 | use Drupal\Core\Entity\EntityStorageInterface; |
| 10 | use Drupal\Core\Entity\EntityTypeInterface; |
| 11 | use Drupal\Core\Entity\EntityTypeManagerInterface; |
| 12 | use Drupal\Core\Entity\RevisionLogEntityTrait; |
| 13 | use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema; |
| 14 | use Drupal\Core\Field\BaseFieldDefinition; |
| 15 | use Drupal\Core\Field\FieldStorageDefinitionInterface; |
| 16 | use Drupal\Core\Plugin\Context\EntityContext; |
| 17 | use Drupal\Core\Session\AccountInterface; |
| 18 | use Drupal\Core\StringTranslation\TranslatableMarkup; |
| 19 | use Drupal\display_builder\DisplayBuildableInterface; |
| 20 | use Drupal\display_builder\Exception\InvalidNodeException; |
| 21 | use Drupal\display_builder\InstanceAccessControlHandler; |
| 22 | use Drupal\display_builder\InstanceInterface; |
| 23 | use Drupal\display_builder\SlotSourceProxy; |
| 24 | use Drupal\display_builder\SourceTree; |
| 25 | use Drupal\display_builder_ui\InstanceListBuilder; |
| 26 | use Drupal\ui_patterns\Entity\SampleEntityGeneratorInterface; |
| 27 | use Drupal\ui_patterns\Plugin\Context\RequirementsContext; |
| 28 | |
| 29 | /** |
| 30 | * Defines the display builder instance entity class. |
| 31 | */ |
| 32 | #[ContentEntityType( |
| 33 | id: 'display_builder_instance', |
| 34 | label: new TranslatableMarkup('Display Builder instance'), |
| 35 | label_collection: new TranslatableMarkup('Display builder instances'), |
| 36 | label_singular: new TranslatableMarkup('display builder instance'), |
| 37 | label_plural: new TranslatableMarkup('display builder instances'), |
| 38 | entity_keys: [ |
| 39 | 'id' => 'id', |
| 40 | 'revision' => 'revision', |
| 41 | 'langcode' => 'langcode', |
| 42 | 'default_langcode' => 'default_langcode', |
| 43 | ], |
| 44 | handlers: [ |
| 45 | 'access' => InstanceAccessControlHandler::class, |
| 46 | 'storage' => InstanceStorage::class, |
| 47 | 'storage_schema' => SqlContentEntityStorageSchema::class, |
| 48 | // Managed by display_builder_ui. |
| 49 | 'list_builder' => InstanceListBuilder::class, |
| 50 | ], |
| 51 | links: [ |
| 52 | // Managed by display_builder_ui. |
| 53 | 'collection' => '/admin/structure/display-builder/instances', |
| 54 | ], |
| 55 | label_count: [ |
| 56 | 'singular' => '@count instance', |
| 57 | 'plural' => '@count instances', |
| 58 | ], |
| 59 | translatable: TRUE, |
| 60 | base_table: 'display_builder_instance', |
| 61 | data_table: 'display_builder_instance_field_data', |
| 62 | revision_table: 'display_builder_instance_revision', |
| 63 | revision_data_table: 'display_builder_instance_field_revision', |
| 64 | revision_metadata_keys: [ |
| 65 | 'revision_user' => 'revision_user', |
| 66 | 'revision_created' => 'revision_created', |
| 67 | 'revision_log_message' => 'revision_log_message', |
| 68 | ], |
| 69 | )] |
| 70 | class Instance extends ContentEntityBase implements InstanceInterface { |
| 71 | |
| 72 | use RevisionLogEntityTrait; |
| 73 | |
| 74 | /** |
| 75 | * Current user. |
| 76 | */ |
| 77 | public AccountInterface $currentUser; |
| 78 | |
| 79 | /** |
| 80 | * Path index. |
| 81 | * |
| 82 | * A mapping where each key is an slot source node ID and each value has |
| 83 | * two properties: |
| 84 | * - path: the path |
| 85 | * - parent: the node ID of the parent. This is necessary because not every |
| 86 | * SourceWithSlotsInterface implementations has the same "deepness". For |
| 87 | * example, ComponentSource has 4 levels (component, slots, slot_id, |
| 88 | * 'sources), LayoutSource has 2 levels (regions, slot_id), etc. |
| 89 | */ |
| 90 | protected array $pathIndex = []; |
| 91 | |
| 92 | /** |
| 93 | * Entity type manager. |
| 94 | */ |
| 95 | protected EntityTypeManagerInterface $entityTypeManager; |
| 96 | |
| 97 | /** |
| 98 | * Sample entity generator. |
| 99 | */ |
| 100 | protected SampleEntityGeneratorInterface $sampleEntityGenerator; |
| 101 | |
| 102 | /** |
| 103 | * Slot source proxy for resolving node labels. |
| 104 | */ |
| 105 | protected SlotSourceProxy $slotSourceProxy; |
| 106 | |
| 107 | /** |
| 108 | * Cached normalized source tree for the current present state. |
| 109 | * |
| 110 | * Stays valid after mutations (index=FALSE path) and is cleared on undo/redo |
| 111 | * when the present pointer jumps to a different history step. |
| 112 | */ |
| 113 | protected ?SourceTree $sourceTree = NULL; |
| 114 | |
| 115 | /** |
| 116 | * {@inheritdoc} |
| 117 | */ |
| 118 | public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { |
| 119 | $fields = parent::baseFieldDefinitions($entity_type); |
| 120 | // Add the revision metadata fields. |
| 121 | $fields += static::revisionLogBaseFieldDefinitions($entity_type); |
| 122 | // Override from ContentEntityBase. |
| 123 | $fields['id'] = BaseFieldDefinition::create('string')->setRequired(TRUE)->setReadOnly(TRUE); |
| 124 | $fields['buildable'] = BaseFieldDefinition::create('plugin') |
| 125 | ->setSetting('plugin_manager_id', 'plugin.manager.display_buildable') |
| 126 | ->setRequired(TRUE) |
| 127 | ->setReadOnly(TRUE); |
| 128 | $fields['sources'] = BaseFieldDefinition::create('ui_patterns_source') |
| 129 | ->setRevisionable(TRUE) |
| 130 | ->setTranslatable(TRUE) |
| 131 | ->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED); |
| 132 | $fields['hash'] = BaseFieldDefinition::create('integer') |
| 133 | ->setRevisionable(TRUE) |
| 134 | ->setSetting('size', 'big'); |
| 135 | $fields['published'] = BaseFieldDefinition::create('timestamp'); |
| 136 | |
| 137 | return $fields; |
| 138 | } |
| 139 | |
| 140 | /** |
| 141 | * {@inheritdoc} |
| 142 | */ |
| 143 | public function label() { |
| 144 | // Extract a human readable name from an instance id. |
| 145 | // Example: "provider__my_display" -> "My display". |
| 146 | $parts = \explode('__', (string) $this->id()); |
| 147 | |
| 148 | if (\count($parts) > 1) { |
| 149 | \array_shift($parts); |
| 150 | |
| 151 | return \ucfirst(\implode(' ', \str_replace('_', ' ', $parts))); |
| 152 | } |
| 153 | |
| 154 | return (string) $this->id(); |
| 155 | } |
| 156 | |
| 157 | /** |
| 158 | * {@inheritdoc} |
| 159 | * |
| 160 | * @see \Drupal\Core\Entity\EntityInterface |
| 161 | */ |
| 162 | public function postCreate(EntityStorageInterface $storage): void { |
| 163 | $sources = $this->get('sources')->getValue() ?? []; |
| 164 | |
| 165 | if (empty($sources)) { |
| 166 | return; |
| 167 | } |
| 168 | |
| 169 | $this->sourceTree = new SourceTree($sources); |
| 170 | $indexed = $this->sourceTree->getTree(); |
| 171 | |
| 172 | $hash = self::getUniqId($indexed); |
| 173 | $this->set('sources', $indexed); |
| 174 | $this->set('hash', $hash); |
| 175 | } |
| 176 | |
| 177 | /** |
| 178 | * {@inheritdoc} |
| 179 | */ |
| 180 | public function getProfile(): ?ProfileInterface { |
| 181 | return $this->getBuildablePlugin()->getProfile(); |
| 182 | } |
| 183 | |
| 184 | /** |
| 185 | * {@inheritdoc} |
| 186 | */ |
| 187 | public function moveToRoot(string $node_id, int $position): bool { |
| 188 | $tree = $this->getSourceTree(); |
| 189 | $data = $tree->getNodeData($node_id); |
| 190 | |
| 191 | if (!$data) { |
| 192 | return FALSE; |
| 193 | } |
| 194 | |
| 195 | if (!$tree->moveToRoot($node_id, $position)) { |
| 196 | return FALSE; |
| 197 | } |
| 198 | |
| 199 | $log = new TranslatableMarkup('Move @label to root', ['@label' => $this->nodeLabel($data)]); |
| 200 | $this->setNewPresent($tree->getTree(), $log, TRUE, FALSE); |
| 201 | |
| 202 | return TRUE; |
| 203 | } |
| 204 | |
| 205 | /** |
| 206 | * {@inheritdoc} |
| 207 | */ |
| 208 | public function moveToSlot(string $node_id, string $parent_id, string $slot_id, int $position): bool { |
| 209 | $tree = $this->getSourceTree(); |
| 210 | $data = $tree->getNodeData($node_id); |
| 211 | |
| 212 | if (!$data) { |
| 213 | return FALSE; |
| 214 | } |
| 215 | |
| 216 | if (!$tree->moveToSlot($node_id, $parent_id, $slot_id, $position)) { |
| 217 | return FALSE; |
| 218 | } |
| 219 | |
| 220 | $parentData = $tree->getNodeData($parent_id); |
| 221 | $log = new TranslatableMarkup('Move @label to slot @slot_id in @parent_label', [ |
| 222 | '@label' => $this->nodeLabel($data), |
| 223 | '@slot_id' => $slot_id, |
| 224 | '@parent_label' => $parentData ? $this->nodeLabel($parentData) : $parent_id, |
| 225 | ]); |
| 226 | |
| 227 | $this->setNewPresent($tree->getTree(), $log, TRUE, FALSE); |
| 228 | |
| 229 | return TRUE; |
| 230 | } |
| 231 | |
| 232 | /** |
| 233 | * {@inheritdoc} |
| 234 | */ |
| 235 | public function attachToRoot(int $position, string $source_id, array $data, array $third_party_settings = []): string { |
| 236 | $tree = $this->getSourceTree(); |
| 237 | $node_id = $tree->attachToRoot($position, $source_id, $data); |
| 238 | |
| 239 | if ($third_party_settings) { |
| 240 | foreach ($third_party_settings as $island_id => $settings) { |
| 241 | $tree->setThirdPartySettings($node_id, $island_id, $settings); |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | $nodeData = $tree->getNodeData($node_id); |
| 246 | $log = new TranslatableMarkup('Attach @label to root', ['@label' => $this->nodeLabel($nodeData ?? [])]); |
| 247 | $this->setNewPresent($tree->getTree(), $log, FALSE, FALSE); |
| 248 | |
| 249 | return $node_id; |
| 250 | } |
| 251 | |
| 252 | /** |
| 253 | * {@inheritdoc} |
| 254 | */ |
| 255 | public function attachToSlot(string $parent_id, string $slot_id, int $position, string $source_id, array $data, array $third_party_settings = []): string { |
| 256 | $tree = $this->getSourceTree(); |
| 257 | $node_id = $tree->attachToSlot($parent_id, $slot_id, $position, $source_id, $data); |
| 258 | |
| 259 | if (!$node_id) { |
| 260 | throw new InvalidNodeException('Parent or slot not found'); |
| 261 | } |
| 262 | |
| 263 | if ($third_party_settings) { |
| 264 | foreach ($third_party_settings as $island_id => $settings) { |
| 265 | $tree->setThirdPartySettings($node_id, $island_id, $settings); |
| 266 | } |
| 267 | } |
| 268 | |
| 269 | $nodeData = $tree->getNodeData($node_id); |
| 270 | $parentData = $tree->getNodeData($parent_id); |
| 271 | $log = new TranslatableMarkup('Attach @label to slot @slot_id in @parent_label', [ |
| 272 | '@label' => $this->nodeLabel($nodeData ?? []), |
| 273 | '@slot_id' => $slot_id, |
| 274 | '@parent_label' => $parentData ? $this->nodeLabel($parentData) : $parent_id, |
| 275 | ]); |
| 276 | $this->setNewPresent($tree->getTree(), $log, TRUE, FALSE); |
| 277 | |
| 278 | return $node_id; |
| 279 | } |
| 280 | |
| 281 | /** |
| 282 | * {@inheritdoc} |
| 283 | */ |
| 284 | public function getNode(string $node_id): array { |
| 285 | return $this->getSourceTree()->getNode($node_id) ?? []; |
| 286 | } |
| 287 | |
| 288 | /** |
| 289 | * {@inheritdoc} |
| 290 | */ |
| 291 | public function getParentId(string $node_id): ?string { |
| 292 | return $this->getSourceTree()->getParentId($node_id); |
| 293 | } |
| 294 | |
| 295 | /** |
| 296 | * {@inheritdoc} |
| 297 | */ |
| 298 | public function setSource(string $node_id, string $source_id, array $data): void { |
| 299 | $tree = $this->getSourceTree(); |
| 300 | |
| 301 | if (!$tree->setSource($node_id, $source_id, $data)) { |
| 302 | throw new InvalidNodeException('Internal node ID mismatch'); |
| 303 | } |
| 304 | |
| 305 | $nodeData = $tree->getNodeData($node_id); |
| 306 | $log = new TranslatableMarkup('Update @label configuration', ['@label' => $this->nodeLabel($nodeData ?? [])]); |
| 307 | $this->setNewPresent($tree->getTree(), $log, TRUE, FALSE); |
| 308 | } |
| 309 | |
| 310 | /** |
| 311 | * {@inheritdoc} |
| 312 | */ |
| 313 | public function setThirdPartySettings(string $node_id, string $island_id, array $data): void { |
| 314 | $tree = $this->getSourceTree(); |
| 315 | $nodeData = $tree->getNodeData($node_id); |
| 316 | |
| 317 | if (!$tree->setThirdPartySettings($node_id, $island_id, $data)) { |
| 318 | return; |
| 319 | } |
| 320 | |
| 321 | $log = new TranslatableMarkup('Update @island_id configuration in @label', [ |
| 322 | '@label' => $this->nodeLabel($nodeData), |
| 323 | '@island_id' => $island_id, |
| 324 | ]); |
| 325 | $this->setNewPresent($tree->getTree(), $log, TRUE, FALSE); |
| 326 | } |
| 327 | |
| 328 | /** |
| 329 | * {@inheritdoc} |
| 330 | */ |
| 331 | public function remove(string $node_id): void { |
| 332 | $tree = $this->getSourceTree(); |
| 333 | $data = $tree->getNodeData($node_id); |
| 334 | |
| 335 | if (!$data) { |
| 336 | return; |
| 337 | } |
| 338 | $parent_id = $tree->getParentId($node_id); |
| 339 | |
| 340 | $tree->remove($node_id); |
| 341 | |
| 342 | $parentData = $parent_id === NULL ? NULL : $tree->getNodeData($parent_id); |
| 343 | $log = new TranslatableMarkup('Remove @label from @parent_label', [ |
| 344 | '@label' => $this->nodeLabel($data), |
| 345 | '@parent_label' => $parentData ? $this->nodeLabel($parentData) : 'root', |
| 346 | ]); |
| 347 | $this->setNewPresent($tree->getTree(), $log, FALSE, FALSE); |
| 348 | } |
| 349 | |
| 350 | /** |
| 351 | * {@inheritdoc} |
| 352 | */ |
| 353 | public function getRuntimeContexts(array $unqualified_context_ids): ?array { |
| 354 | $contexts = $this->getBuildablePlugin()->getRuntimeContexts($unqualified_context_ids) ?? []; |
| 355 | |
| 356 | return $this->refreshContexts($contexts); |
| 357 | } |
| 358 | |
| 359 | /** |
| 360 | * {@inheritdoc} |
| 361 | */ |
| 362 | public function getAvailableContexts() { |
| 363 | return $this->getRuntimeContexts([]); |
| 364 | } |
| 365 | |
| 366 | /** |
| 367 | * {@inheritdoc} |
| 368 | * |
| 369 | * @see \Drupal\display_builder\HistoryInterface |
| 370 | */ |
| 371 | public function getCurrentState(): array { |
| 372 | return $this->get('sources')->getValue(); |
| 373 | } |
| 374 | |
| 375 | /** |
| 376 | * {@inheritdoc} |
| 377 | */ |
| 378 | public function publish(): void { |
| 379 | $this->getBuildablePlugin()->saveSources(); |
| 380 | $this->set('published', \Drupal::time()->getRequestTime()); |
| 381 | $this->setNewRevision(FALSE); |
| 382 | $this->save(); |
| 383 | } |
| 384 | |
| 385 | /** |
| 386 | * {@inheritdoc} |
| 387 | */ |
| 388 | public function restore(): void { |
| 389 | $this->setNewPresent($this->getBuildablePlugin()->getSources(), new TranslatableMarkup('Restore published data.')); |
| 390 | } |
| 391 | |
| 392 | /** |
| 393 | * {@inheritdoc} |
| 394 | */ |
| 395 | public function clear(): void { |
| 396 | $this->getStorage()->clear($this); |
| 397 | } |
| 398 | |
| 399 | /** |
| 400 | * {@inheritdoc} |
| 401 | * |
| 402 | * @see \Drupal\display_builder\HistoryInterface |
| 403 | */ |
| 404 | public function getPast(): array { |
| 405 | return $this->getStorage()->getPast($this); |
| 406 | } |
| 407 | |
| 408 | /** |
| 409 | * {@inheritdoc} |
| 410 | * |
| 411 | * @see \Drupal\display_builder\HistoryInterface |
| 412 | */ |
| 413 | public function getFuture(): array { |
| 414 | return $this->getStorage()->getFuture($this); |
| 415 | } |
| 416 | |
| 417 | /** |
| 418 | * {@inheritdoc} |
| 419 | */ |
| 420 | public function getUsers(): array { |
| 421 | return $this->getStorage()->getUsers($this); |
| 422 | } |
| 423 | |
| 424 | /** |
| 425 | * {@inheritdoc} |
| 426 | */ |
| 427 | public function isPublishable(): bool { |
| 428 | $contexts = $this->getAvailableContexts(); |
| 429 | |
| 430 | if (!\array_key_exists('context_requirements', $contexts) |
| 431 | || !($contexts['context_requirements'] instanceof RequirementsContext)) { |
| 432 | return FALSE; |
| 433 | } |
| 434 | |
| 435 | return TRUE; |
| 436 | } |
| 437 | |
| 438 | /** |
| 439 | * {@inheritdoc} |
| 440 | */ |
| 441 | public function getPublishedHash(): ?int { |
| 442 | $published_data = $this->getBuildablePlugin()->getSources(); |
| 443 | |
| 444 | return $published_data ? self::getUniqId($published_data) : NULL; |
| 445 | } |
| 446 | |
| 447 | /** |
| 448 | * {@inheritdoc} |
| 449 | */ |
| 450 | public function getPublishedTime(): ?int { |
| 451 | $time = (int) $this->get('published')->getString(); |
| 452 | |
| 453 | return ($time === 0) ? NULL : $time; |
| 454 | } |
| 455 | |
| 456 | /** |
| 457 | * {@inheritdoc} |
| 458 | */ |
| 459 | public function isPublished(): bool { |
| 460 | return (bool) $this->getBuildablePlugin()->getSources(); |
| 461 | } |
| 462 | |
| 463 | /** |
| 464 | * {@inheritdoc} |
| 465 | */ |
| 466 | public function isPublishedPresent(): bool { |
| 467 | return $this->getHash() === $this->getPublishedHash(); |
| 468 | } |
| 469 | |
| 470 | /** |
| 471 | * {@inheritdoc} |
| 472 | */ |
| 473 | public function getPathIndex(): array { |
| 474 | return $this->getSourceTree()->getPathIndex(); |
| 475 | } |
| 476 | |
| 477 | /** |
| 478 | * {@inheritdoc} |
| 479 | */ |
| 480 | public function getHash(): ?int { |
| 481 | if ($hash = $this->get('hash')->first()?->getString()) { |
| 482 | return (int) $hash; |
| 483 | } |
| 484 | |
| 485 | return NULL; |
| 486 | } |
| 487 | |
| 488 | /** |
| 489 | * {@inheritdoc} |
| 490 | * |
| 491 | * @see \Drupal\display_builder\HistoryInterface |
| 492 | */ |
| 493 | public function setNewPresent(array $data, string|\Stringable $log_message = '', bool $check_hash = TRUE, bool $index = TRUE): void { |
| 494 | if ($index) { |
| 495 | // Raw data needs normalizing; build tree and keep it as the new cache. |
| 496 | $this->sourceTree = new SourceTree($data); |
| 497 | $data = $this->sourceTree->getTree(); |
| 498 | } |
| 499 | // When index=FALSE the data was produced by the cached tree's getTree(), |
| 500 | // so $this->sourceTree already reflects the new state — no invalidation. |
| 501 | $hash = self::getUniqId($data); |
| 502 | |
| 503 | // Check if this present is the same to avoid duplicates, for example move |
| 504 | // to the same place. |
| 505 | if ($check_hash && $hash === $this->getHash()) { |
| 506 | return; |
| 507 | } |
| 508 | |
| 509 | // Root is always a list of sources. |
| 510 | $data = \array_is_list($data) ? $data : [$data]; |
| 511 | |
| 512 | $this->setNewRevision(TRUE); |
| 513 | $this->set('sources', $data); |
| 514 | $this->set('hash', $hash); |
| 515 | $this->setRevisionLogMessage((string) $log_message); |
| 516 | $this->setRevisionUserId((int) $this->currentUser()->id()); |
| 517 | $this->setRevisionCreationTime(\time()); |
| 518 | $this->save(); |
| 519 | } |
| 520 | |
| 521 | /** |
| 522 | * {@inheritdoc} |
| 523 | */ |
| 524 | public static function getUniqId(array $data): int { |
| 525 | $data = self::normalizeRootLevel($data); |
| 526 | |
| 527 | return \crc32((string) \serialize($data)); |
| 528 | } |
| 529 | |
| 530 | /** |
| 531 | * Get display buildable plugin. |
| 532 | * |
| 533 | * @return \Drupal\display_builder\DisplayBuildableInterface |
| 534 | * A display buildable plugin instance. |
| 535 | */ |
| 536 | private function getBuildablePlugin(): DisplayBuildableInterface { |
| 537 | /** @var \Drupal\display_builder\Plugin\Field\FieldType\PluginItem $item */ |
| 538 | $item = $this->get('buildable')->first(); |
| 539 | /** @var \Drupal\display_builder\DisplayBuildableInterface $buildable */ |
| 540 | $buildable = $item->getInstance(); |
| 541 | |
| 542 | return $buildable; |
| 543 | } |
| 544 | |
| 545 | /** |
| 546 | * Normalize root level of the data tree. |
| 547 | * |
| 548 | * ::isPublishedPresent() is comparing hashes from two sources data trees: |
| 549 | * - the one from permanent storage where the data is not altered: the hash |
| 550 | * of the data stored is the hash of the data retrieved. |
| 551 | * - the one from Instance entity where we use a UI Patterns Source field. |
| 552 | * Each source is a field item, each property (node_id, source_id, source, |
| 553 | * third_party_settings) is a field property. So, Field API can reorder the |
| 554 | * properties and fill the missing properties with empty values, altering the |
| 555 | * hash calculation and making this comparison difficult. |
| 556 | * |
| 557 | * To be used only in ::getUniqId(). |
| 558 | */ |
| 559 | private static function normalizeRootLevel(array $data): array { |
| 560 | $data = \array_is_list($data) ? $data : [$data]; |
| 561 | |
| 562 | foreach ($data as $index => $source) { |
| 563 | \ksort($source); |
| 564 | $data[$index] = \array_filter($source); |
| 565 | } |
| 566 | |
| 567 | return $data; |
| 568 | } |
| 569 | |
| 570 | /** |
| 571 | * Get or create the cached source tree for the current present state. |
| 572 | * |
| 573 | * The cache is populated lazily on first access and remains valid until |
| 574 | * undo() or redo() changes the present pointer to a different history step. |
| 575 | * Mutations (index=FALSE path) keep it alive since the tree is the source |
| 576 | * of the new present data. The index=TRUE path in setNewPresent() replaces |
| 577 | * it with a freshly normalized tree. |
| 578 | * |
| 579 | * @return \Drupal\display_builder\SourceTree |
| 580 | * The source tree for the current state. |
| 581 | */ |
| 582 | private function getSourceTree(): SourceTree { |
| 583 | if ($this->sourceTree === NULL) { |
| 584 | $this->sourceTree = new SourceTree($this->getCurrentState()); |
| 585 | } |
| 586 | |
| 587 | return $this->sourceTree; |
| 588 | } |
| 589 | |
| 590 | /** |
| 591 | * Sample entity generator. |
| 592 | */ |
| 593 | private function sampleEntityGenerator(): SampleEntityGeneratorInterface { |
| 594 | return $this->sampleEntityGenerator ??= \Drupal::service('ui_patterns.sample_entity_generator'); |
| 595 | } |
| 596 | |
| 597 | /** |
| 598 | * Slot source proxy lazy loader. |
| 599 | */ |
| 600 | private function slotSourceProxy(): SlotSourceProxy { |
| 601 | return $this->slotSourceProxy ??= \Drupal::service('display_builder.slot_sources_proxy'); |
| 602 | } |
| 603 | |
| 604 | /** |
| 605 | * Returns the human-readable label for a node, falling back to source_id. |
| 606 | */ |
| 607 | private function nodeLabel(array $data): string { |
| 608 | $label = $this->slotSourceProxy()->getLabelWithSummary($data, [], TRUE)['label']; |
| 609 | |
| 610 | return $label !== '' ? $label : ($data['source_id'] ?? ''); |
| 611 | } |
| 612 | |
| 613 | /** |
| 614 | * Current user. |
| 615 | */ |
| 616 | private function currentUser(): AccountInterface { |
| 617 | return $this->currentUser ??= \Drupal::service('current_user'); |
| 618 | } |
| 619 | |
| 620 | /** |
| 621 | * Get entity storage. |
| 622 | */ |
| 623 | private function getStorage(): InstanceStorage { |
| 624 | /** @var \Drupal\display_builder\Entity\InstanceStorage $storage */ |
| 625 | $storage = $this->entityTypeManager()->getStorage('display_builder_instance'); |
| 626 | |
| 627 | return $storage; |
| 628 | } |
| 629 | |
| 630 | /** |
| 631 | * Refresh contexts after loaded from storage. |
| 632 | * |
| 633 | * @param \Drupal\Core\Plugin\Context\ContextInterface[] $contexts |
| 634 | * The contexts. |
| 635 | * |
| 636 | * @throws \Drupal\Component\Plugin\Exception\ContextException |
| 637 | * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException |
| 638 | * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException |
| 639 | * |
| 640 | * @return array |
| 641 | * The refreshed contexts or NULL if no context. |
| 642 | */ |
| 643 | private function refreshContexts(array $contexts): array { |
| 644 | foreach ($contexts as &$context) { |
| 645 | if ($context instanceof EntityContext) { |
| 646 | // @todo We should use cache entries here |
| 647 | // with the corresponding cache contexts in it. |
| 648 | // This may avoid some unnecessary entity loads or generation. |
| 649 | $entity = $context->getContextValue(); |
| 650 | |
| 651 | // Check if sample entity. |
| 652 | if ($entity->id()) { |
| 653 | $entity = $this->entityTypeManager()->getStorage($entity->getEntityTypeId())->load($entity->id()); |
| 654 | } |
| 655 | else { |
| 656 | $entity = $this->sampleEntityGenerator()->get($entity->getEntityTypeId(), $entity->bundle()); |
| 657 | } |
| 658 | |
| 659 | // Edge case when the parent entity is deleted but not the builder |
| 660 | // instance. |
| 661 | if (!$entity) { |
| 662 | return $contexts; |
| 663 | } |
| 664 | $context = (\get_class($context))::fromEntity($entity); |
| 665 | } |
| 666 | } |
| 667 | |
| 668 | return $contexts; |
| 669 | } |
| 670 | |
| 671 | } |
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.
| 235 | public function attachToRoot(int $position, string $source_id, array $data, array $third_party_settings = []): string { |
| 236 | $tree = $this->getSourceTree(); |
| 237 | $node_id = $tree->attachToRoot($position, $source_id, $data); |
| 238 | |
| 239 | if ($third_party_settings) { |
| 240 | foreach ($third_party_settings as $island_id => $settings) { |
| 240 | foreach ($third_party_settings as $island_id => $settings) { |
| 240 | foreach ($third_party_settings as $island_id => $settings) { |
| 240 | foreach ($third_party_settings as $island_id => $settings) { |
| 241 | $tree->setThirdPartySettings($node_id, $island_id, $settings); |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | $nodeData = $tree->getNodeData($node_id); |
| 245 | $nodeData = $tree->getNodeData($node_id); |
| 246 | $log = new TranslatableMarkup('Attach @label to root', ['@label' => $this->nodeLabel($nodeData ?? [])]); |
| 247 | $this->setNewPresent($tree->getTree(), $log, FALSE, FALSE); |
| 248 | |
| 249 | return $node_id; |
| 250 | } |
| 255 | public function attachToSlot(string $parent_id, string $slot_id, int $position, string $source_id, array $data, array $third_party_settings = []): string { |
| 256 | $tree = $this->getSourceTree(); |
| 257 | $node_id = $tree->attachToSlot($parent_id, $slot_id, $position, $source_id, $data); |
| 258 | |
| 259 | if (!$node_id) { |
| 260 | throw new InvalidNodeException('Parent or slot not found'); |
| 263 | if ($third_party_settings) { |
| 264 | foreach ($third_party_settings as $island_id => $settings) { |
| 264 | foreach ($third_party_settings as $island_id => $settings) { |
| 264 | foreach ($third_party_settings as $island_id => $settings) { |
| 264 | foreach ($third_party_settings as $island_id => $settings) { |
| 265 | $tree->setThirdPartySettings($node_id, $island_id, $settings); |
| 266 | } |
| 267 | } |
| 268 | |
| 269 | $nodeData = $tree->getNodeData($node_id); |
| 269 | $nodeData = $tree->getNodeData($node_id); |
| 270 | $parentData = $tree->getNodeData($parent_id); |
| 271 | $log = new TranslatableMarkup('Attach @label to slot @slot_id in @parent_label', [ |
| 272 | '@label' => $this->nodeLabel($nodeData ?? []), |
| 273 | '@slot_id' => $slot_id, |
| 274 | '@parent_label' => $parentData ? $this->nodeLabel($parentData) : $parent_id, |
| 274 | '@parent_label' => $parentData ? $this->nodeLabel($parentData) : $parent_id, |
| 274 | '@parent_label' => $parentData ? $this->nodeLabel($parentData) : $parent_id, |
| 274 | '@parent_label' => $parentData ? $this->nodeLabel($parentData) : $parent_id, |
| 275 | ]); |
| 276 | $this->setNewPresent($tree->getTree(), $log, TRUE, FALSE); |
| 277 | |
| 278 | return $node_id; |
| 279 | } |
| 118 | public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { |
| 119 | $fields = parent::baseFieldDefinitions($entity_type); |
| 120 | // Add the revision metadata fields. |
| 121 | $fields += static::revisionLogBaseFieldDefinitions($entity_type); |
| 122 | // Override from ContentEntityBase. |
| 123 | $fields['id'] = BaseFieldDefinition::create('string')->setRequired(TRUE)->setReadOnly(TRUE); |
| 124 | $fields['buildable'] = BaseFieldDefinition::create('plugin') |
| 125 | ->setSetting('plugin_manager_id', 'plugin.manager.display_buildable') |
| 126 | ->setRequired(TRUE) |
| 127 | ->setReadOnly(TRUE); |
| 128 | $fields['sources'] = BaseFieldDefinition::create('ui_patterns_source') |
| 129 | ->setRevisionable(TRUE) |
| 130 | ->setTranslatable(TRUE) |
| 131 | ->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED); |
| 132 | $fields['hash'] = BaseFieldDefinition::create('integer') |
| 133 | ->setRevisionable(TRUE) |
| 134 | ->setSetting('size', 'big'); |
| 135 | $fields['published'] = BaseFieldDefinition::create('timestamp'); |
| 136 | |
| 137 | return $fields; |
| 138 | } |
| 396 | $this->getStorage()->clear($this); |
| 397 | } |
| 617 | return $this->currentUser ??= \Drupal::service('current_user'); |
| 618 | } |
| 363 | return $this->getRuntimeContexts([]); |
| 364 | } |
| 538 | $item = $this->get('buildable')->first(); |
| 539 | /** @var \Drupal\display_builder\DisplayBuildableInterface $buildable */ |
| 540 | $buildable = $item->getInstance(); |
| 541 | |
| 542 | return $buildable; |
| 543 | } |
| 372 | return $this->get('sources')->getValue(); |
| 373 | } |
| 414 | return $this->getStorage()->getFuture($this); |
| 415 | } |
| 481 | if ($hash = $this->get('hash')->first()?->getString()) { |
| 482 | return (int) $hash; |
| 485 | return NULL; |
| 486 | } |
| 284 | public function getNode(string $node_id): array { |
| 285 | return $this->getSourceTree()->getNode($node_id) ?? []; |
| 286 | } |
| 291 | public function getParentId(string $node_id): ?string { |
| 292 | return $this->getSourceTree()->getParentId($node_id); |
| 293 | } |
| 405 | return $this->getStorage()->getPast($this); |
| 406 | } |
| 474 | return $this->getSourceTree()->getPathIndex(); |
| 475 | } |
| 181 | return $this->getBuildablePlugin()->getProfile(); |
| 182 | } |
| 442 | $published_data = $this->getBuildablePlugin()->getSources(); |
| 443 | |
| 444 | return $published_data ? self::getUniqId($published_data) : NULL; |
| 444 | return $published_data ? self::getUniqId($published_data) : NULL; |
| 444 | return $published_data ? self::getUniqId($published_data) : NULL; |
| 444 | return $published_data ? self::getUniqId($published_data) : NULL; |
| 445 | } |
| 451 | $time = (int) $this->get('published')->getString(); |
| 452 | |
| 453 | return ($time === 0) ? NULL : $time; |
| 453 | return ($time === 0) ? NULL : $time; |
| 453 | return ($time === 0) ? NULL : $time; |
| 453 | return ($time === 0) ? NULL : $time; |
| 454 | } |
| 353 | public function getRuntimeContexts(array $unqualified_context_ids): ?array { |
| 354 | $contexts = $this->getBuildablePlugin()->getRuntimeContexts($unqualified_context_ids) ?? []; |
| 355 | |
| 356 | return $this->refreshContexts($contexts); |
| 357 | } |
| 583 | if ($this->sourceTree === NULL) { |
| 584 | $this->sourceTree = new SourceTree($this->getCurrentState()); |
| 585 | } |
| 586 | |
| 587 | return $this->sourceTree; |
| 587 | return $this->sourceTree; |
| 588 | } |
| 625 | $storage = $this->entityTypeManager()->getStorage('display_builder_instance'); |
| 626 | |
| 627 | return $storage; |
| 628 | } |
| 524 | public static function getUniqId(array $data): int { |
| 525 | $data = self::normalizeRootLevel($data); |
| 526 | |
| 527 | return \crc32((string) \serialize($data)); |
| 528 | } |
| 421 | return $this->getStorage()->getUsers($this); |
| 422 | } |
| 428 | $contexts = $this->getAvailableContexts(); |
| 429 | |
| 430 | if (!\array_key_exists('context_requirements', $contexts) |
| 431 | || !($contexts['context_requirements'] instanceof RequirementsContext)) { |
| 431 | || !($contexts['context_requirements'] instanceof RequirementsContext)) { |
| 432 | return FALSE; |
| 435 | return TRUE; |
| 436 | } |
| 460 | return (bool) $this->getBuildablePlugin()->getSources(); |
| 461 | } |
| 467 | return $this->getHash() === $this->getPublishedHash(); |
| 468 | } |
| 146 | $parts = \explode('__', (string) $this->id()); |
| 147 | |
| 148 | if (\count($parts) > 1) { |
| 149 | \array_shift($parts); |
| 150 | |
| 151 | return \ucfirst(\implode(' ', \str_replace('_', ' ', $parts))); |
| 154 | return (string) $this->id(); |
| 155 | } |
| 187 | public function moveToRoot(string $node_id, int $position): bool { |
| 188 | $tree = $this->getSourceTree(); |
| 189 | $data = $tree->getNodeData($node_id); |
| 190 | |
| 191 | if (!$data) { |
| 192 | return FALSE; |
| 195 | if (!$tree->moveToRoot($node_id, $position)) { |
| 196 | return FALSE; |
| 199 | $log = new TranslatableMarkup('Move @label to root', ['@label' => $this->nodeLabel($data)]); |
| 200 | $this->setNewPresent($tree->getTree(), $log, TRUE, FALSE); |
| 201 | |
| 202 | return TRUE; |
| 203 | } |
| 208 | public function moveToSlot(string $node_id, string $parent_id, string $slot_id, int $position): bool { |
| 209 | $tree = $this->getSourceTree(); |
| 210 | $data = $tree->getNodeData($node_id); |
| 211 | |
| 212 | if (!$data) { |
| 213 | return FALSE; |
| 216 | if (!$tree->moveToSlot($node_id, $parent_id, $slot_id, $position)) { |
| 217 | return FALSE; |
| 220 | $parentData = $tree->getNodeData($parent_id); |
| 221 | $log = new TranslatableMarkup('Move @label to slot @slot_id in @parent_label', [ |
| 222 | '@label' => $this->nodeLabel($data), |
| 223 | '@slot_id' => $slot_id, |
| 224 | '@parent_label' => $parentData ? $this->nodeLabel($parentData) : $parent_id, |
| 224 | '@parent_label' => $parentData ? $this->nodeLabel($parentData) : $parent_id, |
| 224 | '@parent_label' => $parentData ? $this->nodeLabel($parentData) : $parent_id, |
| 224 | '@parent_label' => $parentData ? $this->nodeLabel($parentData) : $parent_id, |
| 225 | ]); |
| 226 | |
| 227 | $this->setNewPresent($tree->getTree(), $log, TRUE, FALSE); |
| 228 | |
| 229 | return TRUE; |
| 230 | } |
| 607 | private function nodeLabel(array $data): string { |
| 608 | $label = $this->slotSourceProxy()->getLabelWithSummary($data, [], TRUE)['label']; |
| 609 | |
| 610 | return $label !== '' ? $label : ($data['source_id'] ?? ''); |
| 610 | return $label !== '' ? $label : ($data['source_id'] ?? ''); |
| 610 | return $label !== '' ? $label : ($data['source_id'] ?? ''); |
| 610 | return $label !== '' ? $label : ($data['source_id'] ?? ''); |
| 611 | } |
| 559 | private static function normalizeRootLevel(array $data): array { |
| 560 | $data = \array_is_list($data) ? $data : [$data]; |
| 560 | $data = \array_is_list($data) ? $data : [$data]; |
| 560 | $data = \array_is_list($data) ? $data : [$data]; |
| 560 | $data = \array_is_list($data) ? $data : [$data]; |
| 561 | |
| 562 | foreach ($data as $index => $source) { |
| 562 | foreach ($data as $index => $source) { |
| 562 | foreach ($data as $index => $source) { |
| 562 | foreach ($data as $index => $source) { |
| 563 | \ksort($source); |
| 564 | $data[$index] = \array_filter($source); |
| 565 | } |
| 566 | |
| 567 | return $data; |
| 568 | } |
| 162 | public function postCreate(EntityStorageInterface $storage): void { |
| 163 | $sources = $this->get('sources')->getValue() ?? []; |
| 164 | |
| 165 | if (empty($sources)) { |
| 166 | return; |
| 169 | $this->sourceTree = new SourceTree($sources); |
| 170 | $indexed = $this->sourceTree->getTree(); |
| 171 | |
| 172 | $hash = self::getUniqId($indexed); |
| 173 | $this->set('sources', $indexed); |
| 174 | $this->set('hash', $hash); |
| 175 | } |
| 379 | $this->getBuildablePlugin()->saveSources(); |
| 380 | $this->set('published', \Drupal::time()->getRequestTime()); |
| 381 | $this->setNewRevision(FALSE); |
| 382 | $this->save(); |
| 383 | } |
| 643 | private function refreshContexts(array $contexts): array { |
| 644 | foreach ($contexts as &$context) { |
| 644 | foreach ($contexts as &$context) { |
| 645 | if ($context instanceof EntityContext) { |
| 649 | $entity = $context->getContextValue(); |
| 650 | |
| 651 | // Check if sample entity. |
| 652 | if ($entity->id()) { |
| 652 | if ($entity->id()) { |
| 653 | $entity = $this->entityTypeManager()->getStorage($entity->getEntityTypeId())->load($entity->id()); |
| 656 | $entity = $this->sampleEntityGenerator()->get($entity->getEntityTypeId(), $entity->bundle()); |
| 657 | } |
| 658 | |
| 659 | // Edge case when the parent entity is deleted but not the builder |
| 660 | // instance. |
| 661 | if (!$entity) { |
| 661 | if (!$entity) { |
| 662 | return $contexts; |
| 644 | foreach ($contexts as &$context) { |
| 645 | if ($context instanceof EntityContext) { |
| 646 | // @todo We should use cache entries here |
| 647 | // with the corresponding cache contexts in it. |
| 648 | // This may avoid some unnecessary entity loads or generation. |
| 649 | $entity = $context->getContextValue(); |
| 650 | |
| 651 | // Check if sample entity. |
| 652 | if ($entity->id()) { |
| 653 | $entity = $this->entityTypeManager()->getStorage($entity->getEntityTypeId())->load($entity->id()); |
| 654 | } |
| 655 | else { |
| 656 | $entity = $this->sampleEntityGenerator()->get($entity->getEntityTypeId(), $entity->bundle()); |
| 657 | } |
| 658 | |
| 659 | // Edge case when the parent entity is deleted but not the builder |
| 660 | // instance. |
| 661 | if (!$entity) { |
| 662 | return $contexts; |
| 663 | } |
| 664 | $context = (\get_class($context))::fromEntity($entity); |
| 644 | foreach ($contexts as &$context) { |
| 644 | foreach ($contexts as &$context) { |
| 645 | if ($context instanceof EntityContext) { |
| 646 | // @todo We should use cache entries here |
| 647 | // with the corresponding cache contexts in it. |
| 648 | // This may avoid some unnecessary entity loads or generation. |
| 649 | $entity = $context->getContextValue(); |
| 650 | |
| 651 | // Check if sample entity. |
| 652 | if ($entity->id()) { |
| 653 | $entity = $this->entityTypeManager()->getStorage($entity->getEntityTypeId())->load($entity->id()); |
| 654 | } |
| 655 | else { |
| 656 | $entity = $this->sampleEntityGenerator()->get($entity->getEntityTypeId(), $entity->bundle()); |
| 657 | } |
| 658 | |
| 659 | // Edge case when the parent entity is deleted but not the builder |
| 660 | // instance. |
| 661 | if (!$entity) { |
| 662 | return $contexts; |
| 663 | } |
| 664 | $context = (\get_class($context))::fromEntity($entity); |
| 665 | } |
| 666 | } |
| 667 | |
| 668 | return $contexts; |
| 669 | } |
| 331 | public function remove(string $node_id): void { |
| 332 | $tree = $this->getSourceTree(); |
| 333 | $data = $tree->getNodeData($node_id); |
| 334 | |
| 335 | if (!$data) { |
| 336 | return; |
| 338 | $parent_id = $tree->getParentId($node_id); |
| 339 | |
| 340 | $tree->remove($node_id); |
| 341 | |
| 342 | $parentData = $parent_id === NULL ? NULL : $tree->getNodeData($parent_id); |
| 342 | $parentData = $parent_id === NULL ? NULL : $tree->getNodeData($parent_id); |
| 342 | $parentData = $parent_id === NULL ? NULL : $tree->getNodeData($parent_id); |
| 342 | $parentData = $parent_id === NULL ? NULL : $tree->getNodeData($parent_id); |
| 343 | $log = new TranslatableMarkup('Remove @label from @parent_label', [ |
| 344 | '@label' => $this->nodeLabel($data), |
| 345 | '@parent_label' => $parentData ? $this->nodeLabel($parentData) : 'root', |
| 345 | '@parent_label' => $parentData ? $this->nodeLabel($parentData) : 'root', |
| 345 | '@parent_label' => $parentData ? $this->nodeLabel($parentData) : 'root', |
| 345 | '@parent_label' => $parentData ? $this->nodeLabel($parentData) : 'root', |
| 346 | ]); |
| 347 | $this->setNewPresent($tree->getTree(), $log, FALSE, FALSE); |
| 348 | } |
| 389 | $this->setNewPresent($this->getBuildablePlugin()->getSources(), new TranslatableMarkup('Restore published data.')); |
| 390 | } |
| 594 | return $this->sampleEntityGenerator ??= \Drupal::service('ui_patterns.sample_entity_generator'); |
| 595 | } |
| 493 | public function setNewPresent(array $data, string|\Stringable $log_message = '', bool $check_hash = TRUE, bool $index = TRUE): void { |
| 494 | if ($index) { |
| 496 | $this->sourceTree = new SourceTree($data); |
| 497 | $data = $this->sourceTree->getTree(); |
| 498 | } |
| 499 | // When index=FALSE the data was produced by the cached tree's getTree(), |
| 500 | // so $this->sourceTree already reflects the new state — no invalidation. |
| 501 | $hash = self::getUniqId($data); |
| 501 | $hash = self::getUniqId($data); |
| 502 | |
| 503 | // Check if this present is the same to avoid duplicates, for example move |
| 504 | // to the same place. |
| 505 | if ($check_hash && $hash === $this->getHash()) { |
| 505 | if ($check_hash && $hash === $this->getHash()) { |
| 505 | if ($check_hash && $hash === $this->getHash()) { |
| 506 | return; |
| 510 | $data = \array_is_list($data) ? $data : [$data]; |
| 510 | $data = \array_is_list($data) ? $data : [$data]; |
| 510 | $data = \array_is_list($data) ? $data : [$data]; |
| 510 | $data = \array_is_list($data) ? $data : [$data]; |
| 511 | |
| 512 | $this->setNewRevision(TRUE); |
| 513 | $this->set('sources', $data); |
| 514 | $this->set('hash', $hash); |
| 515 | $this->setRevisionLogMessage((string) $log_message); |
| 516 | $this->setRevisionUserId((int) $this->currentUser()->id()); |
| 517 | $this->setRevisionCreationTime(\time()); |
| 518 | $this->save(); |
| 519 | } |
| 298 | public function setSource(string $node_id, string $source_id, array $data): void { |
| 299 | $tree = $this->getSourceTree(); |
| 300 | |
| 301 | if (!$tree->setSource($node_id, $source_id, $data)) { |
| 302 | throw new InvalidNodeException('Internal node ID mismatch'); |
| 305 | $nodeData = $tree->getNodeData($node_id); |
| 306 | $log = new TranslatableMarkup('Update @label configuration', ['@label' => $this->nodeLabel($nodeData ?? [])]); |
| 307 | $this->setNewPresent($tree->getTree(), $log, TRUE, FALSE); |
| 308 | } |
| 313 | public function setThirdPartySettings(string $node_id, string $island_id, array $data): void { |
| 314 | $tree = $this->getSourceTree(); |
| 315 | $nodeData = $tree->getNodeData($node_id); |
| 316 | |
| 317 | if (!$tree->setThirdPartySettings($node_id, $island_id, $data)) { |
| 318 | return; |
| 321 | $log = new TranslatableMarkup('Update @island_id configuration in @label', [ |
| 322 | '@label' => $this->nodeLabel($nodeData), |
| 323 | '@island_id' => $island_id, |
| 324 | ]); |
| 325 | $this->setNewPresent($tree->getTree(), $log, TRUE, FALSE); |
| 326 | } |
| 601 | return $this->slotSourceProxy ??= \Drupal::service('display_builder.slot_sources_proxy'); |
| 602 | } |