Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
67.14% |
143 / 213 |
|
67.65% |
69 / 102 |
|
0.73% |
21 / 2864 |
|
35.00% |
7 / 20 |
CRAP | |
0.00% |
0 / 1 |
| BuilderPanel | |
69.76% |
143 / 205 |
|
67.65% |
69 / 102 |
|
0.73% |
21 / 2864 |
|
35.00% |
7 / 20 |
3463.99 | |
0.00% |
0 / 1 |
| create | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| keyboardShortcuts | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| build | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| onAttachToSlot | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| onUpdate | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| onDelete | |
0.00% |
0 / 3 |
|
0.00% |
0 / 3 |
|
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
| buildRootDropzone | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| buildSingleComponent | |
80.00% |
4 / 5 |
|
66.67% |
2 / 3 |
|
50.00% |
1 / 2 |
|
0.00% |
0 / 1 |
2.50 | |||
| buildComponentRealRender | |
83.33% |
10 / 12 |
|
87.50% |
7 / 8 |
|
8.33% |
1 / 12 |
|
0.00% |
0 / 1 |
16.32 | |||
| resolveComponentInfo | |
72.22% |
13 / 18 |
|
85.71% |
6 / 7 |
|
12.50% |
1 / 8 |
|
0.00% |
0 / 1 |
21.75 | |||
| buildNodeAttributes | |
100.00% |
8 / 8 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| buildSlotAttributes | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| buildSingleBlock | |
91.89% |
34 / 37 |
|
80.65% |
25 / 31 |
|
0.18% |
5 / 2801 |
|
0.00% |
0 / 1 |
270.63 | |||
| replaceNode | |
0.00% |
0 / 20 |
|
0.00% |
0 / 7 |
|
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 | |||
| useAttributesVariable | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| renderSource | |
21.05% |
4 / 19 |
|
44.44% |
4 / 9 |
|
12.50% |
1 / 8 |
|
0.00% |
0 / 1 |
21.75 | |||
| digFromSlot | |
80.95% |
17 / 21 |
|
80.00% |
12 / 15 |
|
15.38% |
2 / 13 |
|
0.00% |
0 / 1 |
36.69 | |||
| hasMultipleRoot | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| isEmpty | |
50.00% |
2 / 4 |
|
50.00% |
2 / 4 |
|
50.00% |
1 / 2 |
|
0.00% |
0 / 1 |
2.50 | |||
| buildComponentSlot | |
93.33% |
14 / 15 |
|
66.67% |
2 / 3 |
|
50.00% |
1 / 2 |
|
0.00% |
0 / 1 |
2.50 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Drupal\display_builder\Plugin\display_builder\Island; |
| 6 | |
| 7 | use Drupal\Component\Utility\Html; |
| 8 | use Drupal\Core\Render\RendererInterface; |
| 9 | use Drupal\Core\StringTranslation\TranslatableMarkup; |
| 10 | use Drupal\display_builder\Attribute\Island; |
| 11 | use Drupal\display_builder\DisplayBuilderHtmx; |
| 12 | use Drupal\display_builder\InstanceInterface; |
| 13 | use Drupal\display_builder\Island\IslandPluginBase; |
| 14 | use Drupal\display_builder\Island\IslandReloadEventsTrait; |
| 15 | use Drupal\display_builder\Island\IslandType; |
| 16 | use Drupal\display_builder\SlotSourceProxy; |
| 17 | use Drupal\display_builder\SourceWithSlotsInterface; |
| 18 | use Drupal\ui_patterns\Element\ComponentElementBuilder; |
| 19 | use Drupal\ui_patterns\SourcePluginBase; |
| 20 | use Drupal\ui_patterns\SourceWithChoicesInterface; |
| 21 | use Masterminds\HTML5; |
| 22 | use Symfony\Component\DependencyInjection\ContainerInterface; |
| 23 | |
| 24 | /** |
| 25 | * Builder island plugin implementation. |
| 26 | */ |
| 27 | #[Island( |
| 28 | id: 'builder', |
| 29 | enabled_by_default: TRUE, |
| 30 | label: new TranslatableMarkup('Canvas'), |
| 31 | description: new TranslatableMarkup('The Display Builder main island. Build the display with dynamic preview.'), |
| 32 | type: IslandType::View, |
| 33 | default_region: 'main', |
| 34 | icon: 'tools', |
| 35 | )] |
| 36 | class BuilderPanel extends IslandPluginBase { |
| 37 | |
| 38 | use IslandReloadEventsTrait; |
| 39 | |
| 40 | /** |
| 41 | * The renderer service. |
| 42 | */ |
| 43 | protected RendererInterface $renderer; |
| 44 | |
| 45 | /** |
| 46 | * Proxy for slot source operations. |
| 47 | */ |
| 48 | protected SlotSourceProxy $slotSourceProxy; |
| 49 | |
| 50 | /** |
| 51 | * The component element builder. |
| 52 | */ |
| 53 | protected ComponentElementBuilder $componentElementBuilder; |
| 54 | |
| 55 | /** |
| 56 | * {@inheritdoc} |
| 57 | */ |
| 58 | public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static { |
| 59 | $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); |
| 60 | $instance->renderer = $container->get('renderer'); |
| 61 | $instance->slotSourceProxy = $container->get('display_builder.slot_sources_proxy'); |
| 62 | $instance->componentElementBuilder = $container->get('ui_patterns.component_element_builder'); |
| 63 | |
| 64 | return $instance; |
| 65 | } |
| 66 | |
| 67 | /** |
| 68 | * {@inheritdoc} |
| 69 | */ |
| 70 | public static function keyboardShortcuts(): array { |
| 71 | return [ |
| 72 | 'key' => 'c', |
| 73 | 'help' => t('Show the canvas'), |
| 74 | ]; |
| 75 | } |
| 76 | |
| 77 | /** |
| 78 | * {@inheritdoc} |
| 79 | */ |
| 80 | public function build(InstanceInterface $builder, array $data = [], array $options = []): array { |
| 81 | return $this->buildRootDropzone($builder, $data); |
| 82 | } |
| 83 | |
| 84 | /** |
| 85 | * {@inheritdoc} |
| 86 | */ |
| 87 | public function onAttachToSlot(InstanceInterface $instance, string $node_id, string $parent_id): array { |
| 88 | return $this->replaceNode($instance, $parent_id); |
| 89 | } |
| 90 | |
| 91 | /** |
| 92 | * {@inheritdoc} |
| 93 | */ |
| 94 | public function onUpdate(InstanceInterface $instance, string $node_id): array { |
| 95 | return $this->replaceNode($instance, $node_id); |
| 96 | } |
| 97 | |
| 98 | /** |
| 99 | * {@inheritdoc} |
| 100 | */ |
| 101 | public function onDelete(InstanceInterface $instance, ?string $parent_id): array { |
| 102 | if (empty($parent_id)) { |
| 103 | return $this->reloadWithGlobalData($instance); |
| 104 | } |
| 105 | |
| 106 | return $this->replaceNode($instance, $parent_id); |
| 107 | } |
| 108 | |
| 109 | /** |
| 110 | * Builds the root dropzone render array. |
| 111 | * |
| 112 | * Split out from build() so WireframePanelBase can reuse it without also |
| 113 | * inheriting anything Builder-specific. |
| 114 | * |
| 115 | * @param \Drupal\display_builder\InstanceInterface $builder |
| 116 | * The Display Builder instance. |
| 117 | * @param array $data |
| 118 | * The current 'slice' of data. |
| 119 | * |
| 120 | * @return array |
| 121 | * The root dropzone render array. |
| 122 | */ |
| 123 | protected function buildRootDropzone(InstanceInterface $builder, array $data): array { |
| 124 | $builder_id = (string) $builder->id(); |
| 125 | $build = [ |
| 126 | '#type' => 'component', |
| 127 | '#component' => 'display_builder:dropzone', |
| 128 | '#props' => [ |
| 129 | 'variant' => 'root', |
| 130 | ], |
| 131 | '#slots' => [ |
| 132 | 'content' => $this->digFromSlot($builder, $data), |
| 133 | ], |
| 134 | '#attributes' => [ |
| 135 | // Required for JavaScript @see components/dropzone/dropzone.js. |
| 136 | 'data-db-id' => $builder_id, |
| 137 | 'data-node-title' => $this->t('Root container'), |
| 138 | 'data-db-root' => TRUE, |
| 139 | ], |
| 140 | ]; |
| 141 | |
| 142 | return $this->htmxEvents->onRootDrop($build, $builder_id, $this->getPluginID()); |
| 143 | } |
| 144 | |
| 145 | /** |
| 146 | * Build renderable from state data. |
| 147 | * |
| 148 | * @param \Drupal\display_builder\InstanceInterface $instance |
| 149 | * The Display Builder instance ID. |
| 150 | * @param string $node_id |
| 151 | * The tree node ID. |
| 152 | * @param \Drupal\display_builder\SourceWithSlotsInterface $source |
| 153 | * The source plugin. |
| 154 | * @param array $data |
| 155 | * The UI Patterns form state data. |
| 156 | * @param int $index |
| 157 | * (Optional) The index of the block. Default to 0. |
| 158 | * |
| 159 | * @return array|null |
| 160 | * A renderable array. |
| 161 | */ |
| 162 | protected function buildSingleComponent(InstanceInterface $instance, string $node_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array { |
| 163 | $info = $this->resolveComponentInfo($source, $data, $node_id); |
| 164 | |
| 165 | if ($info === NULL) { |
| 166 | return NULL; |
| 167 | } |
| 168 | |
| 169 | ['component_id' => $component_id, 'label' => $label, 'instance_id' => $node_id] = $info; |
| 170 | |
| 171 | return $this->buildComponentRealRender($instance, $node_id, $source, $data, $component_id, $label, $index); |
| 172 | } |
| 173 | |
| 174 | /** |
| 175 | * Renders a component with its real output, slots wired as live dropzones. |
| 176 | * |
| 177 | * The Builder canvas's own rendering: the component's actual SDC/block |
| 178 | * markup, with each of its slots replaced by a real, draggable dropzone |
| 179 | * (@see buildComponentSlot()) - as opposed to WireframePanelBase's |
| 180 | * schematic card. Split out from buildSingleComponent() so ScaffoldPanel |
| 181 | * can reuse it for its own configured allowlist of "layout" components |
| 182 | * (e.g. grid rows), which render this way while everything else stays a |
| 183 | * wireframe-style card. |
| 184 | * |
| 185 | * @param \Drupal\display_builder\InstanceInterface $instance |
| 186 | * The Display Builder instance ID. |
| 187 | * @param string $node_id |
| 188 | * The tree node ID. |
| 189 | * @param \Drupal\display_builder\SourceWithSlotsInterface $source |
| 190 | * The source plugin. |
| 191 | * @param array $data |
| 192 | * The UI Patterns form state data. |
| 193 | * @param string $component_id |
| 194 | * The resolved component ID (@see resolveComponentInfo()). |
| 195 | * @param string $label |
| 196 | * The resolved label (@see resolveComponentInfo()). |
| 197 | * @param int $index |
| 198 | * The index of the component within its parent slot/root. |
| 199 | * |
| 200 | * @return array |
| 201 | * A renderable array. |
| 202 | */ |
| 203 | protected function buildComponentRealRender(InstanceInterface $instance, string $node_id, SourceWithSlotsInterface $source, array $data, string $component_id, string $label, int $index): array { |
| 204 | $build = $this->renderSource($data); |
| 205 | $build['#attributes'] = \array_merge($build['#attributes'] ?? [], $this->buildNodeAttributes($label, $index)); |
| 206 | $build['#attributes']['data-testid'] = $component_id; |
| 207 | |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 209 | $slot = $this->buildComponentSlot($instance, $source, $slot_id, $definition, $node_id); |
| 210 | $build = $source->setSlotRenderable($build, $slot_id, $slot); |
| 211 | } |
| 212 | |
| 213 | if ($this->isEmpty($build)) { |
| 214 | // Keep the placeholder if the component is not renderable. |
| 215 | $message = $component_id . ': ' . $this->t('Empty by default. Configure it to make it visible'); |
| 216 | $build = $this->buildPlaceholder($message); |
| 217 | } |
| 218 | |
| 219 | if (!$this->useAttributesVariable($build)) { |
| 220 | $build = $this->wrapContent($build); |
| 221 | } |
| 222 | |
| 223 | return $this->htmxEvents->onInstanceClick($build, (string) $instance->id(), $node_id, $source->label(), $index); |
| 224 | } |
| 225 | |
| 226 | /** |
| 227 | * Resolves component ID, label, and instance ID from source and data. |
| 228 | * |
| 229 | * Extracts the shared preamble logic used by all buildSingleComponent() |
| 230 | * implementations across BuilderPanel, WireframePanelBase, and TreePanel. |
| 231 | * |
| 232 | * @param \Drupal\display_builder\SourceWithSlotsInterface $source |
| 233 | * The source plugin. |
| 234 | * @param array $data |
| 235 | * The UI Patterns form state data. |
| 236 | * @param string $instance_id |
| 237 | * The instance ID. May be empty; resolved from data['node_id'] as fallback. |
| 238 | * |
| 239 | * @return array{component_id: string, label: string, instance_id: string}|null |
| 240 | * Associative array with 'component_id', 'label', 'instance_id', or NULL |
| 241 | * if either component_id or instance_id could not be resolved. |
| 242 | */ |
| 243 | protected function resolveComponentInfo(SourceWithSlotsInterface $source, array $data, string $instance_id): ?array { |
| 244 | $component_id = $source->getPluginID(); |
| 245 | $label = $source->label(); |
| 246 | |
| 247 | if ($source instanceof SourceWithChoicesInterface) { |
| 248 | $component_id = $source->getChoice($data['source']); |
| 249 | $result = $this->slotSourceProxy->getLabelWithSummary($data, [], TRUE); |
| 250 | $label = $result['label'] ?? $source->label(); |
| 251 | } |
| 252 | |
| 253 | $instance_id = $instance_id ?: $data['node_id'] ?? NULL; |
| 254 | |
| 255 | if (!$instance_id || !$component_id) { |
| 256 | $this->logger->error( |
| 257 | '[' . static::class . '::buildSingleComponent] missing component ID: @component_id or instance ID: @instance_id. <pre>' . \print_r($data, TRUE) . '</pre>', |
| 258 | ['@instance_id' => $instance_id ?? 'NULL', '@component_id' => $component_id], |
| 259 | ); |
| 260 | |
| 261 | return NULL; |
| 262 | } |
| 263 | |
| 264 | return [ |
| 265 | 'component_id' => $component_id, |
| 266 | 'label' => $label, |
| 267 | 'instance_id' => $instance_id, |
| 268 | ]; |
| 269 | } |
| 270 | |
| 271 | /** |
| 272 | * Stamps the node-identity attributes the contextual menu relies on. |
| 273 | * |
| 274 | * Resolves "what did I right-click" - shared across Builder, Wireframe, and |
| 275 | * Tree so a new attribute only needs to be added here once. |
| 276 | * |
| 277 | * Also stamps `data-island-id`, the island that rendered this specific |
| 278 | * node wrapper: Builder, Wireframe, and Tree all share the same Sortable |
| 279 | * group (@see components/dropzone/dropzone.js), so a node can be dragged |
| 280 | * from one panel's dropzone into another's. Sortable only relocates the |
| 281 | * existing DOM node - it never re-renders it - so after a cross-panel |
| 282 | * drop the moved element is still wearing its *source* panel's markup. |
| 283 | * `display_builder.js`'s `addVals()` reads this attribute off the |
| 284 | * dragged element to tell the server which island actually rendered it, |
| 285 | * so `ApiController::attachToRoot()/attachToSlot()` can tell a |
| 286 | * cross-panel move (destination island needs a fresh render, its own |
| 287 | * markup differs) apart from a same-panel reorder (destination is |
| 288 | * already correct, safe to skip). |
| 289 | * |
| 290 | * @param string $title |
| 291 | * Human-readable label for the instance. |
| 292 | * @param int $index |
| 293 | * Position within its parent slot/root. |
| 294 | * @param string|null $node_type |
| 295 | * (Optional) The source ID, for CSS/JS targeting of a given node type. |
| 296 | * |
| 297 | * @return array |
| 298 | * Attributes to merge into the instance wrapper's '#attributes'. |
| 299 | * |
| 300 | * @see components/contextual_menu/contextual_menu.js |
| 301 | */ |
| 302 | protected function buildNodeAttributes(string $title, int $index, ?string $node_type = NULL): array { |
| 303 | $attributes = [ |
| 304 | 'data-node-title' => $title, |
| 305 | 'data-slot-position' => $index, |
| 306 | 'data-island-id' => $this->getPluginID(), |
| 307 | ]; |
| 308 | |
| 309 | if ($node_type !== NULL) { |
| 310 | $attributes['data-node-type'] = $node_type; |
| 311 | } |
| 312 | |
| 313 | return $attributes; |
| 314 | } |
| 315 | |
| 316 | /** |
| 317 | * Stamps the slot-identity attributes the contextual menu relies on. |
| 318 | * |
| 319 | * Resolves "paste/duplicate into this slot" - shared across Builder, |
| 320 | * Wireframe, and Tree so a new attribute only needs to be added here once. |
| 321 | * |
| 322 | * @param string $slot_id |
| 323 | * The slot ID. |
| 324 | * @param string $slot_title |
| 325 | * The slot's human-readable title. |
| 326 | * @param string $parent_node_id |
| 327 | * The node ID of the component owning this slot. |
| 328 | * @param string $parent_title |
| 329 | * The human-readable label of the component owning this slot. |
| 330 | * |
| 331 | * @return array |
| 332 | * Attributes to merge into the slot dropzone/tree-item's '#attributes'. |
| 333 | * |
| 334 | * @see components/contextual_menu/contextual_menu.js |
| 335 | */ |
| 336 | protected function buildSlotAttributes(string $slot_id, string $slot_title, string $parent_node_id, string $parent_title): array { |
| 337 | return [ |
| 338 | 'data-slot-id' => $slot_id, |
| 339 | 'data-slot-title' => \ucfirst($slot_title), |
| 340 | 'data-node-id' => $parent_node_id, |
| 341 | 'data-node-title' => $parent_title, |
| 342 | ]; |
| 343 | } |
| 344 | |
| 345 | /** |
| 346 | * Build renderable from state data. |
| 347 | * |
| 348 | * @param \Drupal\display_builder\InstanceInterface $instance |
| 349 | * The Display Builder instance ID. |
| 350 | * @param string $node_id |
| 351 | * The tree node ID. |
| 352 | * @param array $data |
| 353 | * The UI Patterns form state data. |
| 354 | * @param int $index |
| 355 | * (Optional) The index of the block. Default to 0. |
| 356 | * |
| 357 | * @return array|null |
| 358 | * A renderable array. |
| 359 | */ |
| 360 | protected function buildSingleBlock(InstanceInterface $instance, string $node_id, array $data, int $index = 0): ?array { |
| 361 | $node_id = $node_id ?: $data['node_id'] ?? NULL; |
| 362 | |
| 363 | if (!$node_id) { |
| 364 | return NULL; |
| 365 | } |
| 366 | |
| 367 | $classes = ['db-block']; |
| 368 | |
| 369 | if (isset($data['source']['plugin_id'])) { |
| 370 | $classes[] = 'db-block-' . \strtolower(Html::cleanCssIdentifier($data['source']['plugin_id'])); |
| 371 | } |
| 372 | else { |
| 373 | $classes[] = 'db-block-' . \strtolower(Html::cleanCssIdentifier($data['source_id'])); |
| 374 | } |
| 375 | $build = $this->renderSource($data, $classes); |
| 376 | $is_empty = FALSE; |
| 377 | |
| 378 | if (isset($data['source_id']) && $data['source_id'] === 'token') { |
| 379 | if (isset($build['content']) && empty($build['content'])) { |
| 380 | $is_empty = TRUE; |
| 381 | } |
| 382 | } |
| 383 | |
| 384 | if (($data['source']['plugin_id'] ?? '') === 'system_messages_block') { |
| 385 | // system_messages_block is never empty, but often invisible. |
| 386 | // See: core/modules/system/src/Plugin/Block/SystemMessagesBlock.php |
| 387 | // See: core/lib/Drupal/Core/Render/Element/StatusMessages.php |
| 388 | // Let's always display it in a placeholder. |
| 389 | $is_empty = TRUE; |
| 390 | } |
| 391 | |
| 392 | $label_info = $this->slotSourceProxy->getLabelWithSummary($data, $this->configuration['contexts'] ?? []); |
| 393 | |
| 394 | if (isset($data['source_id'])) { |
| 395 | switch ($data['source_id']) { |
| 396 | case 'entity_field': |
| 397 | $label_info['summary'] = (string) $this->t('Field: @label', ['@label' => $label_info['label']]); |
| 398 | |
| 399 | break; |
| 400 | |
| 401 | case 'block': |
| 402 | $label_info['summary'] = (string) $this->t('Block: @label', ['@label' => $label_info['summary']]); |
| 403 | |
| 404 | break; |
| 405 | } |
| 406 | } |
| 407 | |
| 408 | // This is the placeholder without configuration or content yet. |
| 409 | if ($this->isEmpty($build) || $is_empty) { |
| 410 | $build = $this->buildPlaceholderButton($label_info['summary']); |
| 411 | } |
| 412 | elseif (!$this->useAttributesVariable($build) || $this->hasMultipleRoot($build)) { |
| 413 | $build = [ |
| 414 | '#type' => 'html_tag', |
| 415 | '#tag' => 'div', |
| 416 | '#attributes' => ['class' => $classes], |
| 417 | 'content' => $build, |
| 418 | ]; |
| 419 | } |
| 420 | |
| 421 | // The title is expected to contain a human-readable label or summary |
| 422 | // describing the block instance, used by the contextual menu for user |
| 423 | // actions such as edit, delete. |
| 424 | // @see components/contextual_menu/contextual_menu.js |
| 425 | $title = $label_info['label'] ?? $data['source_id'] ?? $data['node_id'] ?? ''; |
| 426 | $build['#attributes'] = \array_merge($build['#attributes'] ?? [], $this->buildNodeAttributes($title, $index, $data['source_id'] ?? NULL)); |
| 427 | $build['#attributes']['data-testid'] = $data['source_id'] ?? $data['node_id'] ?? '_' . $index; |
| 428 | |
| 429 | $build = $this->htmxEvents->onInstanceClick($build, (string) $instance->id(), $node_id, $label_info['summary'] ?? $label_info['label'] ?? '', $index); |
| 430 | |
| 431 | return $build; |
| 432 | } |
| 433 | |
| 434 | /** |
| 435 | * Helper method to replace a specific instance in the DOM. |
| 436 | * |
| 437 | * @param \Drupal\display_builder\InstanceInterface $instance |
| 438 | * The builder instance. |
| 439 | * @param string $node_id |
| 440 | * The node ID from the source tree. |
| 441 | * |
| 442 | * @return array |
| 443 | * Returns a render array with out-of-band commands. |
| 444 | */ |
| 445 | protected function replaceNode(InstanceInterface $instance, string $node_id): array { |
| 446 | $builder_id = (string) $instance->id(); |
| 447 | $parent_selector = '#' . $this->getHtmlId($builder_id) . ' [data-node-id="' . $node_id . '"]'; |
| 448 | $data = $instance->getNode($node_id); |
| 449 | $build = []; |
| 450 | $slot_definition = ['ui_patterns' => ['type_definition' => $this->sourceManager->getSlotPropType()]]; |
| 451 | |
| 452 | try { |
| 453 | $source = $this->sourceManager->createInstance( |
| 454 | $data['source_id'], |
| 455 | SourcePluginBase::buildConfiguration('slot', $slot_definition, $data, $this->configuration['contexts'] ?? []) |
| 456 | ); |
| 457 | } |
| 458 | catch (\Throwable $e) { |
| 459 | $this->logger->error('Invalid source found: %message', ['%message' => $e->getMessage()]); |
| 460 | |
| 461 | return []; |
| 462 | } |
| 463 | |
| 464 | if ($source instanceof SourceWithSlotsInterface) { |
| 465 | $build = $this->buildSingleComponent($instance, $node_id, $source, $data); |
| 466 | } |
| 467 | else { |
| 468 | $build = $this->buildSingleBlock($instance, $node_id, $data); |
| 469 | } |
| 470 | |
| 471 | return DisplayBuilderHtmx::makeOutOfBand( |
| 472 | $build ?? [], |
| 473 | $parent_selector, |
| 474 | 'outerHTML' |
| 475 | ); |
| 476 | } |
| 477 | |
| 478 | /** |
| 479 | * Does the component use the attributes variable in template? |
| 480 | * |
| 481 | * @param array $renderable |
| 482 | * Component renderable. |
| 483 | * |
| 484 | * @return bool |
| 485 | * Use it or not. |
| 486 | */ |
| 487 | protected function useAttributesVariable(array $renderable): bool { |
| 488 | $random = \bin2hex(\random_bytes(8)); |
| 489 | $renderable['#attributes'][$random] = $random; |
| 490 | $html = $this->renderer->renderInIsolation($renderable); |
| 491 | |
| 492 | return \str_contains((string) $html, $random); |
| 493 | } |
| 494 | |
| 495 | /** |
| 496 | * Get renderable array for a slot source. |
| 497 | * |
| 498 | * @param array $data |
| 499 | * The slot source data array containing: |
| 500 | * - source_id: The source ID |
| 501 | * - source: Array of source configuration. |
| 502 | * @param array $classes |
| 503 | * (Optional) Classes to use to wrap the rendered source if needed. |
| 504 | * |
| 505 | * @return array |
| 506 | * The renderable array for this slot source. |
| 507 | */ |
| 508 | protected function renderSource(array $data, array $classes = []): array { |
| 509 | $build = $this->componentElementBuilder->buildSource([], 'content', [], $data, $this->configuration['contexts'] ?? []) ?? []; |
| 510 | $build = $build['#slots']['content'][0] ?? []; |
| 511 | |
| 512 | // Fixes for token which is simple markup or html. |
| 513 | if (isset($data['source_id']) && $data['source_id'] !== 'token') { |
| 514 | return $build; |
| 515 | } |
| 516 | |
| 517 | // If token is only markup, we don't have a wrapper, add it like styles |
| 518 | // so the placeholder can be styled. |
| 519 | if (!isset($build['#type'])) { |
| 520 | $build = [ |
| 521 | '#type' => 'html_tag', |
| 522 | '#tag' => 'div', |
| 523 | '#attributes' => ['class' => $classes], |
| 524 | 'content' => $build, |
| 525 | ]; |
| 526 | } |
| 527 | |
| 528 | // If a style is applied, we have a wrapper from styles with classes, to |
| 529 | // avoid our placeholder classes to be replaced we need to wrap it. |
| 530 | elseif (isset($build['#attributes'])) { |
| 531 | $build = [ |
| 532 | '#type' => 'html_tag', |
| 533 | '#tag' => 'div', |
| 534 | '#attributes' => ['class' => $classes], |
| 535 | 'content' => $build, |
| 536 | ]; |
| 537 | } |
| 538 | |
| 539 | return $build; |
| 540 | } |
| 541 | |
| 542 | /** |
| 543 | * Build builder renderable, recursively. |
| 544 | * |
| 545 | * @param \Drupal\display_builder\InstanceInterface $instance |
| 546 | * The Display Builder instance ID. |
| 547 | * @param array $data |
| 548 | * The current 'slice' of data. |
| 549 | * |
| 550 | * @return array |
| 551 | * A renderable array. |
| 552 | */ |
| 553 | protected function digFromSlot(InstanceInterface $instance, array $data): array { |
| 554 | $renderable = []; |
| 555 | $slot_definition = ['ui_patterns' => ['type_definition' => $this->sourceManager->getSlotPropType()]]; |
| 556 | |
| 557 | foreach ($data as $index => $source) { |
| 558 | if (!isset($source['source_id'])) { |
| 559 | continue; |
| 560 | } |
| 561 | |
| 562 | try { |
| 563 | $source_plugin = $this->sourceManager->createInstance( |
| 564 | $source['source_id'], |
| 565 | SourcePluginBase::buildConfiguration('slot', $slot_definition, $source, $this->configuration['contexts'] ?? []) |
| 566 | ); |
| 567 | } |
| 568 | catch (\Throwable $e) { |
| 569 | $this->logger->error('Invalid source found: %message', ['%message' => $e->getMessage()]); |
| 570 | |
| 571 | continue; |
| 572 | } |
| 573 | |
| 574 | if ($source_plugin instanceof SourceWithSlotsInterface) { |
| 575 | $component = $this->buildSingleComponent($instance, '', $source_plugin, $source, $index); |
| 576 | |
| 577 | if ($component) { |
| 578 | $renderable[$index] = $component; |
| 579 | } |
| 580 | |
| 581 | continue; |
| 582 | } |
| 583 | |
| 584 | $block = $this->buildSingleBlock($instance, '', $source, $index); |
| 585 | |
| 586 | if ($block) { |
| 587 | $renderable[$index] = $block; |
| 588 | } |
| 589 | } |
| 590 | |
| 591 | return $renderable; |
| 592 | } |
| 593 | |
| 594 | /** |
| 595 | * Check if a renderable has multiple HTML root elements once rendered. |
| 596 | * |
| 597 | * @param array $renderable |
| 598 | * The renderable array to check. |
| 599 | * |
| 600 | * @return bool |
| 601 | * TRUE if the rendered output has multiple root elements, FALSE otherwise. |
| 602 | */ |
| 603 | private function hasMultipleRoot(array $renderable): bool { |
| 604 | $html = (string) $this->renderer->renderInIsolation($renderable); |
| 605 | $dom = new HTML5(['disable_html_ns' => TRUE, 'encoding' => 'UTF-8']); |
| 606 | $dom = $dom->loadHTMLFragment($html); |
| 607 | |
| 608 | return $dom->childElementCount > 1; |
| 609 | } |
| 610 | |
| 611 | /** |
| 612 | * Check if a renderable array is empty. |
| 613 | * |
| 614 | * If assertion is enabled, the render can fail on some specific cases like |
| 615 | * comment form without a commented entity. In that case, we consider the |
| 616 | * renderable as empty. |
| 617 | * |
| 618 | * @todo This is a workaround for a specific case, we should find a better way |
| 619 | * |
| 620 | * @param array $renderable |
| 621 | * The renderable array to check. |
| 622 | * |
| 623 | * @return bool |
| 624 | * TRUE if the rendered output is empty, FALSE otherwise. |
| 625 | */ |
| 626 | private function isEmpty(array $renderable): bool { |
| 627 | try { |
| 628 | $html = $this->renderer->renderInIsolation($renderable); |
| 629 | } |
| 630 | catch (\Throwable $e) { |
| 631 | return TRUE; |
| 632 | } |
| 633 | |
| 634 | return empty(\trim((string) $html)); |
| 635 | } |
| 636 | |
| 637 | /** |
| 638 | * Build a component slot with dropzone. |
| 639 | * |
| 640 | * @param \Drupal\display_builder\InstanceInterface $instance |
| 641 | * The Display Builder instance ID. |
| 642 | * @param \Drupal\display_builder\SourceWithSlotsInterface $source |
| 643 | * The source plugin. |
| 644 | * @param string $slot_id |
| 645 | * The slot ID. |
| 646 | * @param array $definition |
| 647 | * The slot definition. |
| 648 | * @param string $node_id |
| 649 | * The node id of the source. |
| 650 | * |
| 651 | * @return array |
| 652 | * A renderable array for the slot. |
| 653 | */ |
| 654 | private function buildComponentSlot(InstanceInterface $instance, SourceWithSlotsInterface $source, string $slot_id, array $definition, string $node_id): array { |
| 655 | $builder_id = (string) $instance->id(); |
| 656 | $dropzone = [ |
| 657 | '#type' => 'component', |
| 658 | '#component' => 'display_builder:dropzone', |
| 659 | '#attributes' => \array_merge( |
| 660 | [ |
| 661 | // Required for JavaScript @see components/dropzone/dropzone.js. |
| 662 | 'data-db-id' => $builder_id, |
| 663 | 'data-testid' => 'dropzone_' . $slot_id, |
| 664 | ], |
| 665 | $this->buildSlotAttributes($slot_id, $definition['title'], $node_id, $source->label()) |
| 666 | ), |
| 667 | ]; |
| 668 | |
| 669 | if ($sources = $source->getSlotValue($slot_id)) { |
| 670 | $dropzone['#slots']['content'] = $this->digFromSlot($instance, $sources); |
| 671 | } |
| 672 | |
| 673 | return $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $node_id, $slot_id); |
| 674 | } |
| 675 | |
| 676 | } |
Below are the source code lines that represent each code path as identified by Xdebug. Please note a path is not
necessarily coterminous with a line, a line may contain multiple paths and therefore show up more than once.
Please also be aware that some paths may include implicit rather than explicit branches, e.g. an if statement
always has an else as part of its logical flow even if you didn't write one.
| 80 | public function build(InstanceInterface $builder, array $data = [], array $options = []): array { |
| 81 | return $this->buildRootDropzone($builder, $data); |
| 82 | } |
| 203 | protected function buildComponentRealRender(InstanceInterface $instance, string $node_id, SourceWithSlotsInterface $source, array $data, string $component_id, string $label, int $index): array { |
| 204 | $build = $this->renderSource($data); |
| 205 | $build['#attributes'] = \array_merge($build['#attributes'] ?? [], $this->buildNodeAttributes($label, $index)); |
| 206 | $build['#attributes']['data-testid'] = $component_id; |
| 207 | |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 209 | $slot = $this->buildComponentSlot($instance, $source, $slot_id, $definition, $node_id); |
| 210 | $build = $source->setSlotRenderable($build, $slot_id, $slot); |
| 211 | } |
| 212 | |
| 213 | if ($this->isEmpty($build)) { |
| 215 | $message = $component_id . ': ' . $this->t('Empty by default. Configure it to make it visible'); |
| 216 | $build = $this->buildPlaceholder($message); |
| 217 | } |
| 218 | |
| 219 | if (!$this->useAttributesVariable($build)) { |
| 219 | if (!$this->useAttributesVariable($build)) { |
| 220 | $build = $this->wrapContent($build); |
| 221 | } |
| 222 | |
| 223 | return $this->htmxEvents->onInstanceClick($build, (string) $instance->id(), $node_id, $source->label(), $index); |
| 223 | return $this->htmxEvents->onInstanceClick($build, (string) $instance->id(), $node_id, $source->label(), $index); |
| 224 | } |
| 203 | protected function buildComponentRealRender(InstanceInterface $instance, string $node_id, SourceWithSlotsInterface $source, array $data, string $component_id, string $label, int $index): array { |
| 204 | $build = $this->renderSource($data); |
| 205 | $build['#attributes'] = \array_merge($build['#attributes'] ?? [], $this->buildNodeAttributes($label, $index)); |
| 206 | $build['#attributes']['data-testid'] = $component_id; |
| 207 | |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 209 | $slot = $this->buildComponentSlot($instance, $source, $slot_id, $definition, $node_id); |
| 210 | $build = $source->setSlotRenderable($build, $slot_id, $slot); |
| 211 | } |
| 212 | |
| 213 | if ($this->isEmpty($build)) { |
| 215 | $message = $component_id . ': ' . $this->t('Empty by default. Configure it to make it visible'); |
| 216 | $build = $this->buildPlaceholder($message); |
| 217 | } |
| 218 | |
| 219 | if (!$this->useAttributesVariable($build)) { |
| 219 | if (!$this->useAttributesVariable($build)) { |
| 223 | return $this->htmxEvents->onInstanceClick($build, (string) $instance->id(), $node_id, $source->label(), $index); |
| 224 | } |
| 203 | protected function buildComponentRealRender(InstanceInterface $instance, string $node_id, SourceWithSlotsInterface $source, array $data, string $component_id, string $label, int $index): array { |
| 204 | $build = $this->renderSource($data); |
| 205 | $build['#attributes'] = \array_merge($build['#attributes'] ?? [], $this->buildNodeAttributes($label, $index)); |
| 206 | $build['#attributes']['data-testid'] = $component_id; |
| 207 | |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 209 | $slot = $this->buildComponentSlot($instance, $source, $slot_id, $definition, $node_id); |
| 210 | $build = $source->setSlotRenderable($build, $slot_id, $slot); |
| 211 | } |
| 212 | |
| 213 | if ($this->isEmpty($build)) { |
| 219 | if (!$this->useAttributesVariable($build)) { |
| 220 | $build = $this->wrapContent($build); |
| 221 | } |
| 222 | |
| 223 | return $this->htmxEvents->onInstanceClick($build, (string) $instance->id(), $node_id, $source->label(), $index); |
| 223 | return $this->htmxEvents->onInstanceClick($build, (string) $instance->id(), $node_id, $source->label(), $index); |
| 224 | } |
| 203 | protected function buildComponentRealRender(InstanceInterface $instance, string $node_id, SourceWithSlotsInterface $source, array $data, string $component_id, string $label, int $index): array { |
| 204 | $build = $this->renderSource($data); |
| 205 | $build['#attributes'] = \array_merge($build['#attributes'] ?? [], $this->buildNodeAttributes($label, $index)); |
| 206 | $build['#attributes']['data-testid'] = $component_id; |
| 207 | |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 209 | $slot = $this->buildComponentSlot($instance, $source, $slot_id, $definition, $node_id); |
| 210 | $build = $source->setSlotRenderable($build, $slot_id, $slot); |
| 211 | } |
| 212 | |
| 213 | if ($this->isEmpty($build)) { |
| 219 | if (!$this->useAttributesVariable($build)) { |
| 223 | return $this->htmxEvents->onInstanceClick($build, (string) $instance->id(), $node_id, $source->label(), $index); |
| 224 | } |
| 203 | protected function buildComponentRealRender(InstanceInterface $instance, string $node_id, SourceWithSlotsInterface $source, array $data, string $component_id, string $label, int $index): array { |
| 204 | $build = $this->renderSource($data); |
| 205 | $build['#attributes'] = \array_merge($build['#attributes'] ?? [], $this->buildNodeAttributes($label, $index)); |
| 206 | $build['#attributes']['data-testid'] = $component_id; |
| 207 | |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 209 | $slot = $this->buildComponentSlot($instance, $source, $slot_id, $definition, $node_id); |
| 210 | $build = $source->setSlotRenderable($build, $slot_id, $slot); |
| 211 | } |
| 212 | |
| 213 | if ($this->isEmpty($build)) { |
| 215 | $message = $component_id . ': ' . $this->t('Empty by default. Configure it to make it visible'); |
| 216 | $build = $this->buildPlaceholder($message); |
| 217 | } |
| 218 | |
| 219 | if (!$this->useAttributesVariable($build)) { |
| 219 | if (!$this->useAttributesVariable($build)) { |
| 220 | $build = $this->wrapContent($build); |
| 221 | } |
| 222 | |
| 223 | return $this->htmxEvents->onInstanceClick($build, (string) $instance->id(), $node_id, $source->label(), $index); |
| 223 | return $this->htmxEvents->onInstanceClick($build, (string) $instance->id(), $node_id, $source->label(), $index); |
| 224 | } |
| 203 | protected function buildComponentRealRender(InstanceInterface $instance, string $node_id, SourceWithSlotsInterface $source, array $data, string $component_id, string $label, int $index): array { |
| 204 | $build = $this->renderSource($data); |
| 205 | $build['#attributes'] = \array_merge($build['#attributes'] ?? [], $this->buildNodeAttributes($label, $index)); |
| 206 | $build['#attributes']['data-testid'] = $component_id; |
| 207 | |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 209 | $slot = $this->buildComponentSlot($instance, $source, $slot_id, $definition, $node_id); |
| 210 | $build = $source->setSlotRenderable($build, $slot_id, $slot); |
| 211 | } |
| 212 | |
| 213 | if ($this->isEmpty($build)) { |
| 215 | $message = $component_id . ': ' . $this->t('Empty by default. Configure it to make it visible'); |
| 216 | $build = $this->buildPlaceholder($message); |
| 217 | } |
| 218 | |
| 219 | if (!$this->useAttributesVariable($build)) { |
| 219 | if (!$this->useAttributesVariable($build)) { |
| 223 | return $this->htmxEvents->onInstanceClick($build, (string) $instance->id(), $node_id, $source->label(), $index); |
| 224 | } |
| 203 | protected function buildComponentRealRender(InstanceInterface $instance, string $node_id, SourceWithSlotsInterface $source, array $data, string $component_id, string $label, int $index): array { |
| 204 | $build = $this->renderSource($data); |
| 205 | $build['#attributes'] = \array_merge($build['#attributes'] ?? [], $this->buildNodeAttributes($label, $index)); |
| 206 | $build['#attributes']['data-testid'] = $component_id; |
| 207 | |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 209 | $slot = $this->buildComponentSlot($instance, $source, $slot_id, $definition, $node_id); |
| 210 | $build = $source->setSlotRenderable($build, $slot_id, $slot); |
| 211 | } |
| 212 | |
| 213 | if ($this->isEmpty($build)) { |
| 219 | if (!$this->useAttributesVariable($build)) { |
| 220 | $build = $this->wrapContent($build); |
| 221 | } |
| 222 | |
| 223 | return $this->htmxEvents->onInstanceClick($build, (string) $instance->id(), $node_id, $source->label(), $index); |
| 223 | return $this->htmxEvents->onInstanceClick($build, (string) $instance->id(), $node_id, $source->label(), $index); |
| 224 | } |
| 203 | protected function buildComponentRealRender(InstanceInterface $instance, string $node_id, SourceWithSlotsInterface $source, array $data, string $component_id, string $label, int $index): array { |
| 204 | $build = $this->renderSource($data); |
| 205 | $build['#attributes'] = \array_merge($build['#attributes'] ?? [], $this->buildNodeAttributes($label, $index)); |
| 206 | $build['#attributes']['data-testid'] = $component_id; |
| 207 | |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 209 | $slot = $this->buildComponentSlot($instance, $source, $slot_id, $definition, $node_id); |
| 210 | $build = $source->setSlotRenderable($build, $slot_id, $slot); |
| 211 | } |
| 212 | |
| 213 | if ($this->isEmpty($build)) { |
| 219 | if (!$this->useAttributesVariable($build)) { |
| 223 | return $this->htmxEvents->onInstanceClick($build, (string) $instance->id(), $node_id, $source->label(), $index); |
| 224 | } |
| 203 | protected function buildComponentRealRender(InstanceInterface $instance, string $node_id, SourceWithSlotsInterface $source, array $data, string $component_id, string $label, int $index): array { |
| 204 | $build = $this->renderSource($data); |
| 205 | $build['#attributes'] = \array_merge($build['#attributes'] ?? [], $this->buildNodeAttributes($label, $index)); |
| 206 | $build['#attributes']['data-testid'] = $component_id; |
| 207 | |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 209 | $slot = $this->buildComponentSlot($instance, $source, $slot_id, $definition, $node_id); |
| 210 | $build = $source->setSlotRenderable($build, $slot_id, $slot); |
| 211 | } |
| 212 | |
| 213 | if ($this->isEmpty($build)) { |
| 215 | $message = $component_id . ': ' . $this->t('Empty by default. Configure it to make it visible'); |
| 216 | $build = $this->buildPlaceholder($message); |
| 217 | } |
| 218 | |
| 219 | if (!$this->useAttributesVariable($build)) { |
| 219 | if (!$this->useAttributesVariable($build)) { |
| 220 | $build = $this->wrapContent($build); |
| 221 | } |
| 222 | |
| 223 | return $this->htmxEvents->onInstanceClick($build, (string) $instance->id(), $node_id, $source->label(), $index); |
| 223 | return $this->htmxEvents->onInstanceClick($build, (string) $instance->id(), $node_id, $source->label(), $index); |
| 224 | } |
| 203 | protected function buildComponentRealRender(InstanceInterface $instance, string $node_id, SourceWithSlotsInterface $source, array $data, string $component_id, string $label, int $index): array { |
| 204 | $build = $this->renderSource($data); |
| 205 | $build['#attributes'] = \array_merge($build['#attributes'] ?? [], $this->buildNodeAttributes($label, $index)); |
| 206 | $build['#attributes']['data-testid'] = $component_id; |
| 207 | |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 209 | $slot = $this->buildComponentSlot($instance, $source, $slot_id, $definition, $node_id); |
| 210 | $build = $source->setSlotRenderable($build, $slot_id, $slot); |
| 211 | } |
| 212 | |
| 213 | if ($this->isEmpty($build)) { |
| 215 | $message = $component_id . ': ' . $this->t('Empty by default. Configure it to make it visible'); |
| 216 | $build = $this->buildPlaceholder($message); |
| 217 | } |
| 218 | |
| 219 | if (!$this->useAttributesVariable($build)) { |
| 219 | if (!$this->useAttributesVariable($build)) { |
| 223 | return $this->htmxEvents->onInstanceClick($build, (string) $instance->id(), $node_id, $source->label(), $index); |
| 224 | } |
| 203 | protected function buildComponentRealRender(InstanceInterface $instance, string $node_id, SourceWithSlotsInterface $source, array $data, string $component_id, string $label, int $index): array { |
| 204 | $build = $this->renderSource($data); |
| 205 | $build['#attributes'] = \array_merge($build['#attributes'] ?? [], $this->buildNodeAttributes($label, $index)); |
| 206 | $build['#attributes']['data-testid'] = $component_id; |
| 207 | |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 209 | $slot = $this->buildComponentSlot($instance, $source, $slot_id, $definition, $node_id); |
| 210 | $build = $source->setSlotRenderable($build, $slot_id, $slot); |
| 211 | } |
| 212 | |
| 213 | if ($this->isEmpty($build)) { |
| 219 | if (!$this->useAttributesVariable($build)) { |
| 220 | $build = $this->wrapContent($build); |
| 221 | } |
| 222 | |
| 223 | return $this->htmxEvents->onInstanceClick($build, (string) $instance->id(), $node_id, $source->label(), $index); |
| 223 | return $this->htmxEvents->onInstanceClick($build, (string) $instance->id(), $node_id, $source->label(), $index); |
| 224 | } |
| 203 | protected function buildComponentRealRender(InstanceInterface $instance, string $node_id, SourceWithSlotsInterface $source, array $data, string $component_id, string $label, int $index): array { |
| 204 | $build = $this->renderSource($data); |
| 205 | $build['#attributes'] = \array_merge($build['#attributes'] ?? [], $this->buildNodeAttributes($label, $index)); |
| 206 | $build['#attributes']['data-testid'] = $component_id; |
| 207 | |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 208 | foreach ($source->getSlotDefinitions() as $slot_id => $definition) { |
| 209 | $slot = $this->buildComponentSlot($instance, $source, $slot_id, $definition, $node_id); |
| 210 | $build = $source->setSlotRenderable($build, $slot_id, $slot); |
| 211 | } |
| 212 | |
| 213 | if ($this->isEmpty($build)) { |
| 219 | if (!$this->useAttributesVariable($build)) { |
| 223 | return $this->htmxEvents->onInstanceClick($build, (string) $instance->id(), $node_id, $source->label(), $index); |
| 224 | } |
| 654 | private function buildComponentSlot(InstanceInterface $instance, SourceWithSlotsInterface $source, string $slot_id, array $definition, string $node_id): array { |
| 655 | $builder_id = (string) $instance->id(); |
| 656 | $dropzone = [ |
| 657 | '#type' => 'component', |
| 658 | '#component' => 'display_builder:dropzone', |
| 659 | '#attributes' => \array_merge( |
| 660 | [ |
| 661 | // Required for JavaScript @see components/dropzone/dropzone.js. |
| 662 | 'data-db-id' => $builder_id, |
| 663 | 'data-testid' => 'dropzone_' . $slot_id, |
| 664 | ], |
| 665 | $this->buildSlotAttributes($slot_id, $definition['title'], $node_id, $source->label()) |
| 666 | ), |
| 667 | ]; |
| 668 | |
| 669 | if ($sources = $source->getSlotValue($slot_id)) { |
| 670 | $dropzone['#slots']['content'] = $this->digFromSlot($instance, $sources); |
| 671 | } |
| 672 | |
| 673 | return $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $node_id, $slot_id); |
| 673 | return $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $node_id, $slot_id); |
| 674 | } |
| 654 | private function buildComponentSlot(InstanceInterface $instance, SourceWithSlotsInterface $source, string $slot_id, array $definition, string $node_id): array { |
| 655 | $builder_id = (string) $instance->id(); |
| 656 | $dropzone = [ |
| 657 | '#type' => 'component', |
| 658 | '#component' => 'display_builder:dropzone', |
| 659 | '#attributes' => \array_merge( |
| 660 | [ |
| 661 | // Required for JavaScript @see components/dropzone/dropzone.js. |
| 662 | 'data-db-id' => $builder_id, |
| 663 | 'data-testid' => 'dropzone_' . $slot_id, |
| 664 | ], |
| 665 | $this->buildSlotAttributes($slot_id, $definition['title'], $node_id, $source->label()) |
| 666 | ), |
| 667 | ]; |
| 668 | |
| 669 | if ($sources = $source->getSlotValue($slot_id)) { |
| 673 | return $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $node_id, $slot_id); |
| 674 | } |
| 302 | protected function buildNodeAttributes(string $title, int $index, ?string $node_type = NULL): array { |
| 303 | $attributes = [ |
| 304 | 'data-node-title' => $title, |
| 305 | 'data-slot-position' => $index, |
| 306 | 'data-island-id' => $this->getPluginID(), |
| 307 | ]; |
| 308 | |
| 309 | if ($node_type !== NULL) { |
| 310 | $attributes['data-node-type'] = $node_type; |
| 311 | } |
| 312 | |
| 313 | return $attributes; |
| 313 | return $attributes; |
| 314 | } |
| 302 | protected function buildNodeAttributes(string $title, int $index, ?string $node_type = NULL): array { |
| 303 | $attributes = [ |
| 304 | 'data-node-title' => $title, |
| 305 | 'data-slot-position' => $index, |
| 306 | 'data-island-id' => $this->getPluginID(), |
| 307 | ]; |
| 308 | |
| 309 | if ($node_type !== NULL) { |
| 313 | return $attributes; |
| 314 | } |
| 123 | protected function buildRootDropzone(InstanceInterface $builder, array $data): array { |
| 124 | $builder_id = (string) $builder->id(); |
| 125 | $build = [ |
| 126 | '#type' => 'component', |
| 127 | '#component' => 'display_builder:dropzone', |
| 128 | '#props' => [ |
| 129 | 'variant' => 'root', |
| 130 | ], |
| 131 | '#slots' => [ |
| 132 | 'content' => $this->digFromSlot($builder, $data), |
| 133 | ], |
| 134 | '#attributes' => [ |
| 135 | // Required for JavaScript @see components/dropzone/dropzone.js. |
| 136 | 'data-db-id' => $builder_id, |
| 137 | 'data-node-title' => $this->t('Root container'), |
| 138 | 'data-db-root' => TRUE, |
| 139 | ], |
| 140 | ]; |
| 141 | |
| 142 | return $this->htmxEvents->onRootDrop($build, $builder_id, $this->getPluginID()); |
| 143 | } |
| 162 | protected function buildSingleComponent(InstanceInterface $instance, string $node_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array { |
| 163 | $info = $this->resolveComponentInfo($source, $data, $node_id); |
| 164 | |
| 165 | if ($info === NULL) { |
| 166 | return NULL; |
| 162 | protected function buildSingleComponent(InstanceInterface $instance, string $node_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array { |
| 163 | $info = $this->resolveComponentInfo($source, $data, $node_id); |
| 164 | |
| 165 | if ($info === NULL) { |
| 169 | ['component_id' => $component_id, 'label' => $label, 'instance_id' => $node_id] = $info; |
| 170 | |
| 171 | return $this->buildComponentRealRender($instance, $node_id, $source, $data, $component_id, $label, $index); |
| 172 | } |
| 336 | protected function buildSlotAttributes(string $slot_id, string $slot_title, string $parent_node_id, string $parent_title): array { |
| 337 | return [ |
| 338 | 'data-slot-id' => $slot_id, |
| 339 | 'data-slot-title' => \ucfirst($slot_title), |
| 340 | 'data-node-id' => $parent_node_id, |
| 341 | 'data-node-title' => $parent_title, |
| 342 | ]; |
| 343 | } |
| 58 | public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static { |
| 59 | $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); |
| 60 | $instance->renderer = $container->get('renderer'); |
| 61 | $instance->slotSourceProxy = $container->get('display_builder.slot_sources_proxy'); |
| 62 | $instance->componentElementBuilder = $container->get('ui_patterns.component_element_builder'); |
| 63 | |
| 64 | return $instance; |
| 65 | } |
| 553 | protected function digFromSlot(InstanceInterface $instance, array $data): array { |
| 554 | $renderable = []; |
| 555 | $slot_definition = ['ui_patterns' => ['type_definition' => $this->sourceManager->getSlotPropType()]]; |
| 556 | |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 558 | if (!isset($source['source_id'])) { |
| 559 | continue; |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 558 | if (!isset($source['source_id'])) { |
| 559 | continue; |
| 560 | } |
| 561 | |
| 562 | try { |
| 563 | $source_plugin = $this->sourceManager->createInstance( |
| 564 | $source['source_id'], |
| 565 | SourcePluginBase::buildConfiguration('slot', $slot_definition, $source, $this->configuration['contexts'] ?? []) |
| 566 | ); |
| 567 | } |
| 568 | catch (\Throwable $e) { |
| 569 | $this->logger->error('Invalid source found: %message', ['%message' => $e->getMessage()]); |
| 570 | |
| 571 | continue; |
| 572 | } |
| 573 | |
| 574 | if ($source_plugin instanceof SourceWithSlotsInterface) { |
| 575 | $component = $this->buildSingleComponent($instance, '', $source_plugin, $source, $index); |
| 576 | |
| 577 | if ($component) { |
| 578 | $renderable[$index] = $component; |
| 579 | } |
| 580 | |
| 581 | continue; |
| 582 | } |
| 583 | |
| 584 | $block = $this->buildSingleBlock($instance, '', $source, $index); |
| 585 | |
| 586 | if ($block) { |
| 587 | $renderable[$index] = $block; |
| 588 | } |
| 589 | } |
| 590 | |
| 591 | return $renderable; |
| 592 | } |
| 553 | protected function digFromSlot(InstanceInterface $instance, array $data): array { |
| 554 | $renderable = []; |
| 555 | $slot_definition = ['ui_patterns' => ['type_definition' => $this->sourceManager->getSlotPropType()]]; |
| 556 | |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 558 | if (!isset($source['source_id'])) { |
| 562 | try { |
| 563 | $source_plugin = $this->sourceManager->createInstance( |
| 574 | if ($source_plugin instanceof SourceWithSlotsInterface) { |
| 575 | $component = $this->buildSingleComponent($instance, '', $source_plugin, $source, $index); |
| 576 | |
| 577 | if ($component) { |
| 578 | $renderable[$index] = $component; |
| 579 | } |
| 580 | |
| 581 | continue; |
| 581 | continue; |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 558 | if (!isset($source['source_id'])) { |
| 559 | continue; |
| 560 | } |
| 561 | |
| 562 | try { |
| 563 | $source_plugin = $this->sourceManager->createInstance( |
| 564 | $source['source_id'], |
| 565 | SourcePluginBase::buildConfiguration('slot', $slot_definition, $source, $this->configuration['contexts'] ?? []) |
| 566 | ); |
| 567 | } |
| 568 | catch (\Throwable $e) { |
| 569 | $this->logger->error('Invalid source found: %message', ['%message' => $e->getMessage()]); |
| 570 | |
| 571 | continue; |
| 572 | } |
| 573 | |
| 574 | if ($source_plugin instanceof SourceWithSlotsInterface) { |
| 575 | $component = $this->buildSingleComponent($instance, '', $source_plugin, $source, $index); |
| 576 | |
| 577 | if ($component) { |
| 578 | $renderable[$index] = $component; |
| 579 | } |
| 580 | |
| 581 | continue; |
| 582 | } |
| 583 | |
| 584 | $block = $this->buildSingleBlock($instance, '', $source, $index); |
| 585 | |
| 586 | if ($block) { |
| 587 | $renderable[$index] = $block; |
| 588 | } |
| 589 | } |
| 590 | |
| 591 | return $renderable; |
| 592 | } |
| 553 | protected function digFromSlot(InstanceInterface $instance, array $data): array { |
| 554 | $renderable = []; |
| 555 | $slot_definition = ['ui_patterns' => ['type_definition' => $this->sourceManager->getSlotPropType()]]; |
| 556 | |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 558 | if (!isset($source['source_id'])) { |
| 562 | try { |
| 563 | $source_plugin = $this->sourceManager->createInstance( |
| 574 | if ($source_plugin instanceof SourceWithSlotsInterface) { |
| 575 | $component = $this->buildSingleComponent($instance, '', $source_plugin, $source, $index); |
| 576 | |
| 577 | if ($component) { |
| 581 | continue; |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 558 | if (!isset($source['source_id'])) { |
| 559 | continue; |
| 560 | } |
| 561 | |
| 562 | try { |
| 563 | $source_plugin = $this->sourceManager->createInstance( |
| 564 | $source['source_id'], |
| 565 | SourcePluginBase::buildConfiguration('slot', $slot_definition, $source, $this->configuration['contexts'] ?? []) |
| 566 | ); |
| 567 | } |
| 568 | catch (\Throwable $e) { |
| 569 | $this->logger->error('Invalid source found: %message', ['%message' => $e->getMessage()]); |
| 570 | |
| 571 | continue; |
| 572 | } |
| 573 | |
| 574 | if ($source_plugin instanceof SourceWithSlotsInterface) { |
| 575 | $component = $this->buildSingleComponent($instance, '', $source_plugin, $source, $index); |
| 576 | |
| 577 | if ($component) { |
| 578 | $renderable[$index] = $component; |
| 579 | } |
| 580 | |
| 581 | continue; |
| 582 | } |
| 583 | |
| 584 | $block = $this->buildSingleBlock($instance, '', $source, $index); |
| 585 | |
| 586 | if ($block) { |
| 587 | $renderable[$index] = $block; |
| 588 | } |
| 589 | } |
| 590 | |
| 591 | return $renderable; |
| 592 | } |
| 553 | protected function digFromSlot(InstanceInterface $instance, array $data): array { |
| 554 | $renderable = []; |
| 555 | $slot_definition = ['ui_patterns' => ['type_definition' => $this->sourceManager->getSlotPropType()]]; |
| 556 | |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 558 | if (!isset($source['source_id'])) { |
| 562 | try { |
| 563 | $source_plugin = $this->sourceManager->createInstance( |
| 574 | if ($source_plugin instanceof SourceWithSlotsInterface) { |
| 584 | $block = $this->buildSingleBlock($instance, '', $source, $index); |
| 585 | |
| 586 | if ($block) { |
| 557 | foreach ($data as $index => $source) { |
| 558 | if (!isset($source['source_id'])) { |
| 559 | continue; |
| 560 | } |
| 561 | |
| 562 | try { |
| 563 | $source_plugin = $this->sourceManager->createInstance( |
| 564 | $source['source_id'], |
| 565 | SourcePluginBase::buildConfiguration('slot', $slot_definition, $source, $this->configuration['contexts'] ?? []) |
| 566 | ); |
| 567 | } |
| 568 | catch (\Throwable $e) { |
| 569 | $this->logger->error('Invalid source found: %message', ['%message' => $e->getMessage()]); |
| 570 | |
| 571 | continue; |
| 572 | } |
| 573 | |
| 574 | if ($source_plugin instanceof SourceWithSlotsInterface) { |
| 575 | $component = $this->buildSingleComponent($instance, '', $source_plugin, $source, $index); |
| 576 | |
| 577 | if ($component) { |
| 578 | $renderable[$index] = $component; |
| 579 | } |
| 580 | |
| 581 | continue; |
| 582 | } |
| 583 | |
| 584 | $block = $this->buildSingleBlock($instance, '', $source, $index); |
| 585 | |
| 586 | if ($block) { |
| 587 | $renderable[$index] = $block; |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 558 | if (!isset($source['source_id'])) { |
| 559 | continue; |
| 560 | } |
| 561 | |
| 562 | try { |
| 563 | $source_plugin = $this->sourceManager->createInstance( |
| 564 | $source['source_id'], |
| 565 | SourcePluginBase::buildConfiguration('slot', $slot_definition, $source, $this->configuration['contexts'] ?? []) |
| 566 | ); |
| 567 | } |
| 568 | catch (\Throwable $e) { |
| 569 | $this->logger->error('Invalid source found: %message', ['%message' => $e->getMessage()]); |
| 570 | |
| 571 | continue; |
| 572 | } |
| 573 | |
| 574 | if ($source_plugin instanceof SourceWithSlotsInterface) { |
| 575 | $component = $this->buildSingleComponent($instance, '', $source_plugin, $source, $index); |
| 576 | |
| 577 | if ($component) { |
| 578 | $renderable[$index] = $component; |
| 579 | } |
| 580 | |
| 581 | continue; |
| 582 | } |
| 583 | |
| 584 | $block = $this->buildSingleBlock($instance, '', $source, $index); |
| 585 | |
| 586 | if ($block) { |
| 587 | $renderable[$index] = $block; |
| 588 | } |
| 589 | } |
| 590 | |
| 591 | return $renderable; |
| 592 | } |
| 553 | protected function digFromSlot(InstanceInterface $instance, array $data): array { |
| 554 | $renderable = []; |
| 555 | $slot_definition = ['ui_patterns' => ['type_definition' => $this->sourceManager->getSlotPropType()]]; |
| 556 | |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 558 | if (!isset($source['source_id'])) { |
| 562 | try { |
| 563 | $source_plugin = $this->sourceManager->createInstance( |
| 574 | if ($source_plugin instanceof SourceWithSlotsInterface) { |
| 584 | $block = $this->buildSingleBlock($instance, '', $source, $index); |
| 585 | |
| 586 | if ($block) { |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 558 | if (!isset($source['source_id'])) { |
| 559 | continue; |
| 560 | } |
| 561 | |
| 562 | try { |
| 563 | $source_plugin = $this->sourceManager->createInstance( |
| 564 | $source['source_id'], |
| 565 | SourcePluginBase::buildConfiguration('slot', $slot_definition, $source, $this->configuration['contexts'] ?? []) |
| 566 | ); |
| 567 | } |
| 568 | catch (\Throwable $e) { |
| 569 | $this->logger->error('Invalid source found: %message', ['%message' => $e->getMessage()]); |
| 570 | |
| 571 | continue; |
| 572 | } |
| 573 | |
| 574 | if ($source_plugin instanceof SourceWithSlotsInterface) { |
| 575 | $component = $this->buildSingleComponent($instance, '', $source_plugin, $source, $index); |
| 576 | |
| 577 | if ($component) { |
| 578 | $renderable[$index] = $component; |
| 579 | } |
| 580 | |
| 581 | continue; |
| 582 | } |
| 583 | |
| 584 | $block = $this->buildSingleBlock($instance, '', $source, $index); |
| 585 | |
| 586 | if ($block) { |
| 587 | $renderable[$index] = $block; |
| 588 | } |
| 589 | } |
| 590 | |
| 591 | return $renderable; |
| 592 | } |
| 553 | protected function digFromSlot(InstanceInterface $instance, array $data): array { |
| 554 | $renderable = []; |
| 555 | $slot_definition = ['ui_patterns' => ['type_definition' => $this->sourceManager->getSlotPropType()]]; |
| 556 | |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 558 | if (!isset($source['source_id'])) { |
| 559 | continue; |
| 560 | } |
| 561 | |
| 562 | try { |
| 563 | $source_plugin = $this->sourceManager->createInstance( |
| 564 | $source['source_id'], |
| 565 | SourcePluginBase::buildConfiguration('slot', $slot_definition, $source, $this->configuration['contexts'] ?? []) |
| 566 | ); |
| 567 | } |
| 568 | catch (\Throwable $e) { |
| 569 | $this->logger->error('Invalid source found: %message', ['%message' => $e->getMessage()]); |
| 570 | |
| 571 | continue; |
| 572 | } |
| 573 | |
| 574 | if ($source_plugin instanceof SourceWithSlotsInterface) { |
| 575 | $component = $this->buildSingleComponent($instance, '', $source_plugin, $source, $index); |
| 576 | |
| 577 | if ($component) { |
| 578 | $renderable[$index] = $component; |
| 579 | } |
| 580 | |
| 581 | continue; |
| 582 | } |
| 583 | |
| 584 | $block = $this->buildSingleBlock($instance, '', $source, $index); |
| 585 | |
| 586 | if ($block) { |
| 587 | $renderable[$index] = $block; |
| 588 | } |
| 589 | } |
| 590 | |
| 591 | return $renderable; |
| 592 | } |
| 553 | protected function digFromSlot(InstanceInterface $instance, array $data): array { |
| 554 | $renderable = []; |
| 555 | $slot_definition = ['ui_patterns' => ['type_definition' => $this->sourceManager->getSlotPropType()]]; |
| 556 | |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 558 | if (!isset($source['source_id'])) { |
| 559 | continue; |
| 560 | } |
| 561 | |
| 562 | try { |
| 563 | $source_plugin = $this->sourceManager->createInstance( |
| 564 | $source['source_id'], |
| 565 | SourcePluginBase::buildConfiguration('slot', $slot_definition, $source, $this->configuration['contexts'] ?? []) |
| 566 | ); |
| 567 | } |
| 568 | catch (\Throwable $e) { |
| 569 | $this->logger->error('Invalid source found: %message', ['%message' => $e->getMessage()]); |
| 570 | |
| 571 | continue; |
| 572 | } |
| 573 | |
| 574 | if ($source_plugin instanceof SourceWithSlotsInterface) { |
| 575 | $component = $this->buildSingleComponent($instance, '', $source_plugin, $source, $index); |
| 576 | |
| 577 | if ($component) { |
| 578 | $renderable[$index] = $component; |
| 579 | } |
| 580 | |
| 581 | continue; |
| 582 | } |
| 583 | |
| 584 | $block = $this->buildSingleBlock($instance, '', $source, $index); |
| 585 | |
| 586 | if ($block) { |
| 587 | $renderable[$index] = $block; |
| 588 | } |
| 589 | } |
| 590 | |
| 591 | return $renderable; |
| 592 | } |
| 568 | catch (\Throwable $e) { |
| 569 | $this->logger->error('Invalid source found: %message', ['%message' => $e->getMessage()]); |
| 570 | |
| 571 | continue; |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 558 | if (!isset($source['source_id'])) { |
| 559 | continue; |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 558 | if (!isset($source['source_id'])) { |
| 559 | continue; |
| 560 | } |
| 561 | |
| 562 | try { |
| 563 | $source_plugin = $this->sourceManager->createInstance( |
| 564 | $source['source_id'], |
| 565 | SourcePluginBase::buildConfiguration('slot', $slot_definition, $source, $this->configuration['contexts'] ?? []) |
| 566 | ); |
| 567 | } |
| 568 | catch (\Throwable $e) { |
| 569 | $this->logger->error('Invalid source found: %message', ['%message' => $e->getMessage()]); |
| 570 | |
| 571 | continue; |
| 572 | } |
| 573 | |
| 574 | if ($source_plugin instanceof SourceWithSlotsInterface) { |
| 575 | $component = $this->buildSingleComponent($instance, '', $source_plugin, $source, $index); |
| 576 | |
| 577 | if ($component) { |
| 578 | $renderable[$index] = $component; |
| 579 | } |
| 580 | |
| 581 | continue; |
| 582 | } |
| 583 | |
| 584 | $block = $this->buildSingleBlock($instance, '', $source, $index); |
| 585 | |
| 586 | if ($block) { |
| 587 | $renderable[$index] = $block; |
| 588 | } |
| 589 | } |
| 590 | |
| 591 | return $renderable; |
| 592 | } |
| 568 | catch (\Throwable $e) { |
| 569 | $this->logger->error('Invalid source found: %message', ['%message' => $e->getMessage()]); |
| 570 | |
| 571 | continue; |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 558 | if (!isset($source['source_id'])) { |
| 562 | try { |
| 563 | $source_plugin = $this->sourceManager->createInstance( |
| 574 | if ($source_plugin instanceof SourceWithSlotsInterface) { |
| 575 | $component = $this->buildSingleComponent($instance, '', $source_plugin, $source, $index); |
| 576 | |
| 577 | if ($component) { |
| 578 | $renderable[$index] = $component; |
| 579 | } |
| 580 | |
| 581 | continue; |
| 581 | continue; |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 558 | if (!isset($source['source_id'])) { |
| 559 | continue; |
| 560 | } |
| 561 | |
| 562 | try { |
| 563 | $source_plugin = $this->sourceManager->createInstance( |
| 564 | $source['source_id'], |
| 565 | SourcePluginBase::buildConfiguration('slot', $slot_definition, $source, $this->configuration['contexts'] ?? []) |
| 566 | ); |
| 567 | } |
| 568 | catch (\Throwable $e) { |
| 569 | $this->logger->error('Invalid source found: %message', ['%message' => $e->getMessage()]); |
| 570 | |
| 571 | continue; |
| 572 | } |
| 573 | |
| 574 | if ($source_plugin instanceof SourceWithSlotsInterface) { |
| 575 | $component = $this->buildSingleComponent($instance, '', $source_plugin, $source, $index); |
| 576 | |
| 577 | if ($component) { |
| 578 | $renderable[$index] = $component; |
| 579 | } |
| 580 | |
| 581 | continue; |
| 582 | } |
| 583 | |
| 584 | $block = $this->buildSingleBlock($instance, '', $source, $index); |
| 585 | |
| 586 | if ($block) { |
| 587 | $renderable[$index] = $block; |
| 588 | } |
| 589 | } |
| 590 | |
| 591 | return $renderable; |
| 592 | } |
| 568 | catch (\Throwable $e) { |
| 569 | $this->logger->error('Invalid source found: %message', ['%message' => $e->getMessage()]); |
| 570 | |
| 571 | continue; |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 558 | if (!isset($source['source_id'])) { |
| 562 | try { |
| 563 | $source_plugin = $this->sourceManager->createInstance( |
| 574 | if ($source_plugin instanceof SourceWithSlotsInterface) { |
| 575 | $component = $this->buildSingleComponent($instance, '', $source_plugin, $source, $index); |
| 576 | |
| 577 | if ($component) { |
| 581 | continue; |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 558 | if (!isset($source['source_id'])) { |
| 559 | continue; |
| 560 | } |
| 561 | |
| 562 | try { |
| 563 | $source_plugin = $this->sourceManager->createInstance( |
| 564 | $source['source_id'], |
| 565 | SourcePluginBase::buildConfiguration('slot', $slot_definition, $source, $this->configuration['contexts'] ?? []) |
| 566 | ); |
| 567 | } |
| 568 | catch (\Throwable $e) { |
| 569 | $this->logger->error('Invalid source found: %message', ['%message' => $e->getMessage()]); |
| 570 | |
| 571 | continue; |
| 572 | } |
| 573 | |
| 574 | if ($source_plugin instanceof SourceWithSlotsInterface) { |
| 575 | $component = $this->buildSingleComponent($instance, '', $source_plugin, $source, $index); |
| 576 | |
| 577 | if ($component) { |
| 578 | $renderable[$index] = $component; |
| 579 | } |
| 580 | |
| 581 | continue; |
| 582 | } |
| 583 | |
| 584 | $block = $this->buildSingleBlock($instance, '', $source, $index); |
| 585 | |
| 586 | if ($block) { |
| 587 | $renderable[$index] = $block; |
| 588 | } |
| 589 | } |
| 590 | |
| 591 | return $renderable; |
| 592 | } |
| 568 | catch (\Throwable $e) { |
| 569 | $this->logger->error('Invalid source found: %message', ['%message' => $e->getMessage()]); |
| 570 | |
| 571 | continue; |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 558 | if (!isset($source['source_id'])) { |
| 562 | try { |
| 563 | $source_plugin = $this->sourceManager->createInstance( |
| 574 | if ($source_plugin instanceof SourceWithSlotsInterface) { |
| 584 | $block = $this->buildSingleBlock($instance, '', $source, $index); |
| 585 | |
| 586 | if ($block) { |
| 557 | foreach ($data as $index => $source) { |
| 558 | if (!isset($source['source_id'])) { |
| 559 | continue; |
| 560 | } |
| 561 | |
| 562 | try { |
| 563 | $source_plugin = $this->sourceManager->createInstance( |
| 564 | $source['source_id'], |
| 565 | SourcePluginBase::buildConfiguration('slot', $slot_definition, $source, $this->configuration['contexts'] ?? []) |
| 566 | ); |
| 567 | } |
| 568 | catch (\Throwable $e) { |
| 569 | $this->logger->error('Invalid source found: %message', ['%message' => $e->getMessage()]); |
| 570 | |
| 571 | continue; |
| 572 | } |
| 573 | |
| 574 | if ($source_plugin instanceof SourceWithSlotsInterface) { |
| 575 | $component = $this->buildSingleComponent($instance, '', $source_plugin, $source, $index); |
| 576 | |
| 577 | if ($component) { |
| 578 | $renderable[$index] = $component; |
| 579 | } |
| 580 | |
| 581 | continue; |
| 582 | } |
| 583 | |
| 584 | $block = $this->buildSingleBlock($instance, '', $source, $index); |
| 585 | |
| 586 | if ($block) { |
| 587 | $renderable[$index] = $block; |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 558 | if (!isset($source['source_id'])) { |
| 559 | continue; |
| 560 | } |
| 561 | |
| 562 | try { |
| 563 | $source_plugin = $this->sourceManager->createInstance( |
| 564 | $source['source_id'], |
| 565 | SourcePluginBase::buildConfiguration('slot', $slot_definition, $source, $this->configuration['contexts'] ?? []) |
| 566 | ); |
| 567 | } |
| 568 | catch (\Throwable $e) { |
| 569 | $this->logger->error('Invalid source found: %message', ['%message' => $e->getMessage()]); |
| 570 | |
| 571 | continue; |
| 572 | } |
| 573 | |
| 574 | if ($source_plugin instanceof SourceWithSlotsInterface) { |
| 575 | $component = $this->buildSingleComponent($instance, '', $source_plugin, $source, $index); |
| 576 | |
| 577 | if ($component) { |
| 578 | $renderable[$index] = $component; |
| 579 | } |
| 580 | |
| 581 | continue; |
| 582 | } |
| 583 | |
| 584 | $block = $this->buildSingleBlock($instance, '', $source, $index); |
| 585 | |
| 586 | if ($block) { |
| 587 | $renderable[$index] = $block; |
| 588 | } |
| 589 | } |
| 590 | |
| 591 | return $renderable; |
| 592 | } |
| 568 | catch (\Throwable $e) { |
| 569 | $this->logger->error('Invalid source found: %message', ['%message' => $e->getMessage()]); |
| 570 | |
| 571 | continue; |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 558 | if (!isset($source['source_id'])) { |
| 562 | try { |
| 563 | $source_plugin = $this->sourceManager->createInstance( |
| 574 | if ($source_plugin instanceof SourceWithSlotsInterface) { |
| 584 | $block = $this->buildSingleBlock($instance, '', $source, $index); |
| 585 | |
| 586 | if ($block) { |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 558 | if (!isset($source['source_id'])) { |
| 559 | continue; |
| 560 | } |
| 561 | |
| 562 | try { |
| 563 | $source_plugin = $this->sourceManager->createInstance( |
| 564 | $source['source_id'], |
| 565 | SourcePluginBase::buildConfiguration('slot', $slot_definition, $source, $this->configuration['contexts'] ?? []) |
| 566 | ); |
| 567 | } |
| 568 | catch (\Throwable $e) { |
| 569 | $this->logger->error('Invalid source found: %message', ['%message' => $e->getMessage()]); |
| 570 | |
| 571 | continue; |
| 572 | } |
| 573 | |
| 574 | if ($source_plugin instanceof SourceWithSlotsInterface) { |
| 575 | $component = $this->buildSingleComponent($instance, '', $source_plugin, $source, $index); |
| 576 | |
| 577 | if ($component) { |
| 578 | $renderable[$index] = $component; |
| 579 | } |
| 580 | |
| 581 | continue; |
| 582 | } |
| 583 | |
| 584 | $block = $this->buildSingleBlock($instance, '', $source, $index); |
| 585 | |
| 586 | if ($block) { |
| 587 | $renderable[$index] = $block; |
| 588 | } |
| 589 | } |
| 590 | |
| 591 | return $renderable; |
| 592 | } |
| 568 | catch (\Throwable $e) { |
| 569 | $this->logger->error('Invalid source found: %message', ['%message' => $e->getMessage()]); |
| 570 | |
| 571 | continue; |
| 557 | foreach ($data as $index => $source) { |
| 557 | foreach ($data as $index => $source) { |
| 558 | if (!isset($source['source_id'])) { |
| 559 | continue; |
| 560 | } |
| 561 | |
| 562 | try { |
| 563 | $source_plugin = $this->sourceManager->createInstance( |
| 564 | $source['source_id'], |
| 565 | SourcePluginBase::buildConfiguration('slot', $slot_definition, $source, $this->configuration['contexts'] ?? []) |
| 566 | ); |
| 567 | } |
| 568 | catch (\Throwable $e) { |
| 569 | $this->logger->error('Invalid source found: %message', ['%message' => $e->getMessage()]); |
| 570 | |
| 571 | continue; |
| 572 | } |
| 573 | |
| 574 | if ($source_plugin instanceof SourceWithSlotsInterface) { |
| 575 | $component = $this->buildSingleComponent($instance, '', $source_plugin, $source, $index); |
| 576 | |
| 577 | if ($component) { |
| 578 | $renderable[$index] = $component; |
| 579 | } |
| 580 | |
| 581 | continue; |
| 582 | } |
| 583 | |
| 584 | $block = $this->buildSingleBlock($instance, '', $source, $index); |
| 585 | |
| 586 | if ($block) { |
| 587 | $renderable[$index] = $block; |
| 588 | } |
| 589 | } |
| 590 | |
| 591 | return $renderable; |
| 592 | } |
| 603 | private function hasMultipleRoot(array $renderable): bool { |
| 604 | $html = (string) $this->renderer->renderInIsolation($renderable); |
| 605 | $dom = new HTML5(['disable_html_ns' => TRUE, 'encoding' => 'UTF-8']); |
| 606 | $dom = $dom->loadHTMLFragment($html); |
| 607 | |
| 608 | return $dom->childElementCount > 1; |
| 609 | } |
| 626 | private function isEmpty(array $renderable): bool { |
| 627 | try { |
| 628 | $html = $this->renderer->renderInIsolation($renderable); |
| 634 | return empty(\trim((string) $html)); |
| 635 | } |
| 630 | catch (\Throwable $e) { |
| 631 | return TRUE; |
| 72 | 'key' => 'c', |
| 73 | 'help' => t('Show the canvas'), |
| 74 | ]; |
| 75 | } |
| 87 | public function onAttachToSlot(InstanceInterface $instance, string $node_id, string $parent_id): array { |
| 88 | return $this->replaceNode($instance, $parent_id); |
| 89 | } |
| 101 | public function onDelete(InstanceInterface $instance, ?string $parent_id): array { |
| 102 | if (empty($parent_id)) { |
| 103 | return $this->reloadWithGlobalData($instance); |
| 101 | public function onDelete(InstanceInterface $instance, ?string $parent_id): array { |
| 102 | if (empty($parent_id)) { |
| 106 | return $this->replaceNode($instance, $parent_id); |
| 107 | } |
| 94 | public function onUpdate(InstanceInterface $instance, string $node_id): array { |
| 95 | return $this->replaceNode($instance, $node_id); |
| 96 | } |
| 508 | protected function renderSource(array $data, array $classes = []): array { |
| 509 | $build = $this->componentElementBuilder->buildSource([], 'content', [], $data, $this->configuration['contexts'] ?? []) ?? []; |
| 510 | $build = $build['#slots']['content'][0] ?? []; |
| 511 | |
| 512 | // Fixes for token which is simple markup or html. |
| 513 | if (isset($data['source_id']) && $data['source_id'] !== 'token') { |
| 513 | if (isset($data['source_id']) && $data['source_id'] !== 'token') { |
| 513 | if (isset($data['source_id']) && $data['source_id'] !== 'token') { |
| 514 | return $build; |
| 508 | protected function renderSource(array $data, array $classes = []): array { |
| 509 | $build = $this->componentElementBuilder->buildSource([], 'content', [], $data, $this->configuration['contexts'] ?? []) ?? []; |
| 510 | $build = $build['#slots']['content'][0] ?? []; |
| 511 | |
| 512 | // Fixes for token which is simple markup or html. |
| 513 | if (isset($data['source_id']) && $data['source_id'] !== 'token') { |
| 513 | if (isset($data['source_id']) && $data['source_id'] !== 'token') { |
| 513 | if (isset($data['source_id']) && $data['source_id'] !== 'token') { |
| 519 | if (!isset($build['#type'])) { |
| 519 | if (!isset($build['#type'])) { |
| 520 | $build = [ |
| 521 | '#type' => 'html_tag', |
| 539 | return $build; |
| 540 | } |
| 508 | protected function renderSource(array $data, array $classes = []): array { |
| 509 | $build = $this->componentElementBuilder->buildSource([], 'content', [], $data, $this->configuration['contexts'] ?? []) ?? []; |
| 510 | $build = $build['#slots']['content'][0] ?? []; |
| 511 | |
| 512 | // Fixes for token which is simple markup or html. |
| 513 | if (isset($data['source_id']) && $data['source_id'] !== 'token') { |
| 513 | if (isset($data['source_id']) && $data['source_id'] !== 'token') { |
| 513 | if (isset($data['source_id']) && $data['source_id'] !== 'token') { |
| 519 | if (!isset($build['#type'])) { |
| 530 | elseif (isset($build['#attributes'])) { |
| 532 | '#type' => 'html_tag', |
| 533 | '#tag' => 'div', |
| 534 | '#attributes' => ['class' => $classes], |
| 535 | 'content' => $build, |
| 536 | ]; |
| 537 | } |
| 538 | |
| 539 | return $build; |
| 539 | return $build; |
| 540 | } |
| 508 | protected function renderSource(array $data, array $classes = []): array { |
| 509 | $build = $this->componentElementBuilder->buildSource([], 'content', [], $data, $this->configuration['contexts'] ?? []) ?? []; |
| 510 | $build = $build['#slots']['content'][0] ?? []; |
| 511 | |
| 512 | // Fixes for token which is simple markup or html. |
| 513 | if (isset($data['source_id']) && $data['source_id'] !== 'token') { |
| 513 | if (isset($data['source_id']) && $data['source_id'] !== 'token') { |
| 513 | if (isset($data['source_id']) && $data['source_id'] !== 'token') { |
| 519 | if (!isset($build['#type'])) { |
| 530 | elseif (isset($build['#attributes'])) { |
| 539 | return $build; |
| 540 | } |
| 508 | protected function renderSource(array $data, array $classes = []): array { |
| 509 | $build = $this->componentElementBuilder->buildSource([], 'content', [], $data, $this->configuration['contexts'] ?? []) ?? []; |
| 510 | $build = $build['#slots']['content'][0] ?? []; |
| 511 | |
| 512 | // Fixes for token which is simple markup or html. |
| 513 | if (isset($data['source_id']) && $data['source_id'] !== 'token') { |
| 513 | if (isset($data['source_id']) && $data['source_id'] !== 'token') { |
| 514 | return $build; |
| 508 | protected function renderSource(array $data, array $classes = []): array { |
| 509 | $build = $this->componentElementBuilder->buildSource([], 'content', [], $data, $this->configuration['contexts'] ?? []) ?? []; |
| 510 | $build = $build['#slots']['content'][0] ?? []; |
| 511 | |
| 512 | // Fixes for token which is simple markup or html. |
| 513 | if (isset($data['source_id']) && $data['source_id'] !== 'token') { |
| 513 | if (isset($data['source_id']) && $data['source_id'] !== 'token') { |
| 519 | if (!isset($build['#type'])) { |
| 519 | if (!isset($build['#type'])) { |
| 520 | $build = [ |
| 521 | '#type' => 'html_tag', |
| 539 | return $build; |
| 540 | } |
| 508 | protected function renderSource(array $data, array $classes = []): array { |
| 509 | $build = $this->componentElementBuilder->buildSource([], 'content', [], $data, $this->configuration['contexts'] ?? []) ?? []; |
| 510 | $build = $build['#slots']['content'][0] ?? []; |
| 511 | |
| 512 | // Fixes for token which is simple markup or html. |
| 513 | if (isset($data['source_id']) && $data['source_id'] !== 'token') { |
| 513 | if (isset($data['source_id']) && $data['source_id'] !== 'token') { |
| 519 | if (!isset($build['#type'])) { |
| 530 | elseif (isset($build['#attributes'])) { |
| 532 | '#type' => 'html_tag', |
| 533 | '#tag' => 'div', |
| 534 | '#attributes' => ['class' => $classes], |
| 535 | 'content' => $build, |
| 536 | ]; |
| 537 | } |
| 538 | |
| 539 | return $build; |
| 539 | return $build; |
| 540 | } |
| 508 | protected function renderSource(array $data, array $classes = []): array { |
| 509 | $build = $this->componentElementBuilder->buildSource([], 'content', [], $data, $this->configuration['contexts'] ?? []) ?? []; |
| 510 | $build = $build['#slots']['content'][0] ?? []; |
| 511 | |
| 512 | // Fixes for token which is simple markup or html. |
| 513 | if (isset($data['source_id']) && $data['source_id'] !== 'token') { |
| 513 | if (isset($data['source_id']) && $data['source_id'] !== 'token') { |
| 519 | if (!isset($build['#type'])) { |
| 530 | elseif (isset($build['#attributes'])) { |
| 539 | return $build; |
| 540 | } |
| 445 | protected function replaceNode(InstanceInterface $instance, string $node_id): array { |
| 446 | $builder_id = (string) $instance->id(); |
| 447 | $parent_selector = '#' . $this->getHtmlId($builder_id) . ' [data-node-id="' . $node_id . '"]'; |
| 448 | $data = $instance->getNode($node_id); |
| 449 | $build = []; |
| 450 | $slot_definition = ['ui_patterns' => ['type_definition' => $this->sourceManager->getSlotPropType()]]; |
| 451 | |
| 452 | try { |
| 453 | $source = $this->sourceManager->createInstance( |
| 464 | if ($source instanceof SourceWithSlotsInterface) { |
| 464 | if ($source instanceof SourceWithSlotsInterface) { |
| 465 | $build = $this->buildSingleComponent($instance, $node_id, $source, $data); |
| 471 | return DisplayBuilderHtmx::makeOutOfBand( |
| 472 | $build ?? [], |
| 473 | $parent_selector, |
| 474 | 'outerHTML' |
| 475 | ); |
| 476 | } |
| 445 | protected function replaceNode(InstanceInterface $instance, string $node_id): array { |
| 446 | $builder_id = (string) $instance->id(); |
| 447 | $parent_selector = '#' . $this->getHtmlId($builder_id) . ' [data-node-id="' . $node_id . '"]'; |
| 448 | $data = $instance->getNode($node_id); |
| 449 | $build = []; |
| 450 | $slot_definition = ['ui_patterns' => ['type_definition' => $this->sourceManager->getSlotPropType()]]; |
| 451 | |
| 452 | try { |
| 453 | $source = $this->sourceManager->createInstance( |
| 464 | if ($source instanceof SourceWithSlotsInterface) { |
| 468 | $build = $this->buildSingleBlock($instance, $node_id, $data); |
| 469 | } |
| 470 | |
| 471 | return DisplayBuilderHtmx::makeOutOfBand( |
| 471 | return DisplayBuilderHtmx::makeOutOfBand( |
| 472 | $build ?? [], |
| 473 | $parent_selector, |
| 474 | 'outerHTML' |
| 475 | ); |
| 476 | } |
| 458 | catch (\Throwable $e) { |
| 459 | $this->logger->error('Invalid source found: %message', ['%message' => $e->getMessage()]); |
| 460 | |
| 461 | return []; |
| 243 | protected function resolveComponentInfo(SourceWithSlotsInterface $source, array $data, string $instance_id): ?array { |
| 244 | $component_id = $source->getPluginID(); |
| 245 | $label = $source->label(); |
| 246 | |
| 247 | if ($source instanceof SourceWithChoicesInterface) { |
| 248 | $component_id = $source->getChoice($data['source']); |
| 249 | $result = $this->slotSourceProxy->getLabelWithSummary($data, [], TRUE); |
| 250 | $label = $result['label'] ?? $source->label(); |
| 251 | } |
| 252 | |
| 253 | $instance_id = $instance_id ?: $data['node_id'] ?? NULL; |
| 253 | $instance_id = $instance_id ?: $data['node_id'] ?? NULL; |
| 254 | |
| 255 | if (!$instance_id || !$component_id) { |
| 255 | if (!$instance_id || !$component_id) { |
| 255 | if (!$instance_id || !$component_id) { |
| 256 | $this->logger->error( |
| 257 | '[' . static::class . '::buildSingleComponent] missing component ID: @component_id or instance ID: @instance_id. <pre>' . \print_r($data, TRUE) . '</pre>', |
| 258 | ['@instance_id' => $instance_id ?? 'NULL', '@component_id' => $component_id], |
| 259 | ); |
| 260 | |
| 261 | return NULL; |
| 243 | protected function resolveComponentInfo(SourceWithSlotsInterface $source, array $data, string $instance_id): ?array { |
| 244 | $component_id = $source->getPluginID(); |
| 245 | $label = $source->label(); |
| 246 | |
| 247 | if ($source instanceof SourceWithChoicesInterface) { |
| 248 | $component_id = $source->getChoice($data['source']); |
| 249 | $result = $this->slotSourceProxy->getLabelWithSummary($data, [], TRUE); |
| 250 | $label = $result['label'] ?? $source->label(); |
| 251 | } |
| 252 | |
| 253 | $instance_id = $instance_id ?: $data['node_id'] ?? NULL; |
| 253 | $instance_id = $instance_id ?: $data['node_id'] ?? NULL; |
| 254 | |
| 255 | if (!$instance_id || !$component_id) { |
| 255 | if (!$instance_id || !$component_id) { |
| 255 | if (!$instance_id || !$component_id) { |
| 265 | 'component_id' => $component_id, |
| 266 | 'label' => $label, |
| 267 | 'instance_id' => $instance_id, |
| 268 | ]; |
| 269 | } |
| 243 | protected function resolveComponentInfo(SourceWithSlotsInterface $source, array $data, string $instance_id): ?array { |
| 244 | $component_id = $source->getPluginID(); |
| 245 | $label = $source->label(); |
| 246 | |
| 247 | if ($source instanceof SourceWithChoicesInterface) { |
| 248 | $component_id = $source->getChoice($data['source']); |
| 249 | $result = $this->slotSourceProxy->getLabelWithSummary($data, [], TRUE); |
| 250 | $label = $result['label'] ?? $source->label(); |
| 251 | } |
| 252 | |
| 253 | $instance_id = $instance_id ?: $data['node_id'] ?? NULL; |
| 253 | $instance_id = $instance_id ?: $data['node_id'] ?? NULL; |
| 254 | |
| 255 | if (!$instance_id || !$component_id) { |
| 255 | if (!$instance_id || !$component_id) { |
| 256 | $this->logger->error( |
| 257 | '[' . static::class . '::buildSingleComponent] missing component ID: @component_id or instance ID: @instance_id. <pre>' . \print_r($data, TRUE) . '</pre>', |
| 258 | ['@instance_id' => $instance_id ?? 'NULL', '@component_id' => $component_id], |
| 259 | ); |
| 260 | |
| 261 | return NULL; |
| 243 | protected function resolveComponentInfo(SourceWithSlotsInterface $source, array $data, string $instance_id): ?array { |
| 244 | $component_id = $source->getPluginID(); |
| 245 | $label = $source->label(); |
| 246 | |
| 247 | if ($source instanceof SourceWithChoicesInterface) { |
| 248 | $component_id = $source->getChoice($data['source']); |
| 249 | $result = $this->slotSourceProxy->getLabelWithSummary($data, [], TRUE); |
| 250 | $label = $result['label'] ?? $source->label(); |
| 251 | } |
| 252 | |
| 253 | $instance_id = $instance_id ?: $data['node_id'] ?? NULL; |
| 253 | $instance_id = $instance_id ?: $data['node_id'] ?? NULL; |
| 254 | |
| 255 | if (!$instance_id || !$component_id) { |
| 255 | if (!$instance_id || !$component_id) { |
| 265 | 'component_id' => $component_id, |
| 266 | 'label' => $label, |
| 267 | 'instance_id' => $instance_id, |
| 268 | ]; |
| 269 | } |
| 243 | protected function resolveComponentInfo(SourceWithSlotsInterface $source, array $data, string $instance_id): ?array { |
| 244 | $component_id = $source->getPluginID(); |
| 245 | $label = $source->label(); |
| 246 | |
| 247 | if ($source instanceof SourceWithChoicesInterface) { |
| 253 | $instance_id = $instance_id ?: $data['node_id'] ?? NULL; |
| 254 | |
| 255 | if (!$instance_id || !$component_id) { |
| 255 | if (!$instance_id || !$component_id) { |
| 255 | if (!$instance_id || !$component_id) { |
| 256 | $this->logger->error( |
| 257 | '[' . static::class . '::buildSingleComponent] missing component ID: @component_id or instance ID: @instance_id. <pre>' . \print_r($data, TRUE) . '</pre>', |
| 258 | ['@instance_id' => $instance_id ?? 'NULL', '@component_id' => $component_id], |
| 259 | ); |
| 260 | |
| 261 | return NULL; |
| 243 | protected function resolveComponentInfo(SourceWithSlotsInterface $source, array $data, string $instance_id): ?array { |
| 244 | $component_id = $source->getPluginID(); |
| 245 | $label = $source->label(); |
| 246 | |
| 247 | if ($source instanceof SourceWithChoicesInterface) { |
| 253 | $instance_id = $instance_id ?: $data['node_id'] ?? NULL; |
| 254 | |
| 255 | if (!$instance_id || !$component_id) { |
| 255 | if (!$instance_id || !$component_id) { |
| 255 | if (!$instance_id || !$component_id) { |
| 265 | 'component_id' => $component_id, |
| 266 | 'label' => $label, |
| 267 | 'instance_id' => $instance_id, |
| 268 | ]; |
| 269 | } |
| 243 | protected function resolveComponentInfo(SourceWithSlotsInterface $source, array $data, string $instance_id): ?array { |
| 244 | $component_id = $source->getPluginID(); |
| 245 | $label = $source->label(); |
| 246 | |
| 247 | if ($source instanceof SourceWithChoicesInterface) { |
| 253 | $instance_id = $instance_id ?: $data['node_id'] ?? NULL; |
| 254 | |
| 255 | if (!$instance_id || !$component_id) { |
| 255 | if (!$instance_id || !$component_id) { |
| 256 | $this->logger->error( |
| 257 | '[' . static::class . '::buildSingleComponent] missing component ID: @component_id or instance ID: @instance_id. <pre>' . \print_r($data, TRUE) . '</pre>', |
| 258 | ['@instance_id' => $instance_id ?? 'NULL', '@component_id' => $component_id], |
| 259 | ); |
| 260 | |
| 261 | return NULL; |
| 243 | protected function resolveComponentInfo(SourceWithSlotsInterface $source, array $data, string $instance_id): ?array { |
| 244 | $component_id = $source->getPluginID(); |
| 245 | $label = $source->label(); |
| 246 | |
| 247 | if ($source instanceof SourceWithChoicesInterface) { |
| 253 | $instance_id = $instance_id ?: $data['node_id'] ?? NULL; |
| 254 | |
| 255 | if (!$instance_id || !$component_id) { |
| 255 | if (!$instance_id || !$component_id) { |
| 265 | 'component_id' => $component_id, |
| 266 | 'label' => $label, |
| 267 | 'instance_id' => $instance_id, |
| 268 | ]; |
| 269 | } |
| 487 | protected function useAttributesVariable(array $renderable): bool { |
| 488 | $random = \bin2hex(\random_bytes(8)); |
| 489 | $renderable['#attributes'][$random] = $random; |
| 490 | $html = $this->renderer->renderInIsolation($renderable); |
| 491 | |
| 492 | return \str_contains((string) $html, $random); |
| 493 | } |