Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
62.28% covered (warning)
62.28%
71 / 114
51.11% covered (warning)
51.11%
23 / 45
15.91% covered (danger)
15.91%
7 / 44
16.67% covered (danger)
16.67%
1 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
WireframePanelBase
62.28% covered (warning)
62.28%
71 / 114
51.11% covered (warning)
51.11%
23 / 45
15.91% covered (danger)
15.91%
7 / 44
16.67% covered (danger)
16.67%
1 / 6
396.64
0.00% covered (danger)
0.00%
0 / 1
 create
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 build
42.86% covered (danger)
42.86%
3 / 7
66.67% covered (warning)
66.67%
2 / 3
50.00% covered (danger)
50.00%
1 / 2
0.00% covered (danger)
0.00%
0 / 1
2.50
 buildSingleComponent
88.00% covered (warning)
88.00%
44 / 50
64.29% covered (warning)
64.29%
9 / 14
11.76% covered (danger)
11.76%
2 / 17
0.00% covered (danger)
0.00%
0 / 1
40.66
 buildSingleBlock
83.33% covered (warning)
83.33%
15 / 18
71.43% covered (warning)
71.43%
5 / 7
12.50% covered (danger)
12.50%
1 / 8
0.00% covered (danger)
0.00%
0 / 1
21.75
 addThirdPartySettingsSummary
22.22% covered (danger)
22.22%
2 / 9
16.67% covered (danger)
16.67%
2 / 12
12.50% covered (danger)
12.50%
1 / 8
0.00% covered (danger)
0.00%
0 / 1
30.12
 addComponentSettingsSummary
14.81% covered (danger)
14.81%
4 / 27
50.00% covered (danger)
50.00%
4 / 8
12.50% covered (danger)
12.50%
1 / 8
0.00% covered (danger)
0.00%
0 / 1
14.72
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\display_builder\Plugin\display_builder\Island;
6
7use Drupal\Core\StringTranslation\TranslatableMarkup;
8use Drupal\display_builder\InstanceInterface;
9use Drupal\display_builder\Island\IslandPluginManagerInterface;
10use Drupal\display_builder\SourceWithSlotsInterface;
11use Drupal\display_builder\ThirdPartySettingsInterface;
12use Symfony\Component\DependencyInjection\ContainerInterface;
13
14/**
15 * Shared implementation for the schematic (wireframe) View panels.
16 *
17 * Renders a hierarchical, preview-less "card per element" view built from the
18 * display_builder:layer component. Scaffold panel extend it; Scaffold
19 * additionally renders an allowlist of components with their real output.
20 */
21abstract class WireframePanelBase extends BuilderPanel {
22
23  /**
24   * Island plugins manager.
25   */
26  protected IslandPluginManagerInterface $islandManager;
27
28  /**
29   * {@inheritdoc}
30   */
31  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
32    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
33    $instance->islandManager = $container->get('plugin.manager.db_island');
34
35    return $instance;
36  }
37
38  /**
39   * {@inheritdoc}
40   */
41  public function build(InstanceInterface $builder, array $data = [], array $options = []): array {
42    $build = $this->buildRootDropzone($builder, $data);
43
44    if (empty($build['#slots']['content'] ?? [])) {
45      // Load en empty component to have any assets with it.
46      $build['#slots']['content'] = [
47        '#type' => 'component',
48        '#component' => 'display_builder:layer',
49      ];
50    }
51
52    return $build;
53  }
54
55  /**
56   * {@inheritdoc}
57   */
58  protected function buildSingleComponent(InstanceInterface $instance, string $node_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array {
59    $info = $this->resolveComponentInfo($source, $data, $node_id);
60
61    if ($info === NULL) {
62      return NULL;
63    }
64
65    ['label' => $label, 'instance_id' => $node_id] = $info;
66
67    $slots = [];
68
69    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
70      $dropzone = [
71        '#type' => 'component',
72        '#component' => 'display_builder:dropzone',
73        '#attributes' => \array_merge(
74          [
75            // Required for JavaScript @see components/dropzone/dropzone.js.
76            'data-db-id' => (string) $instance->id(),
77            'data-testid' => 'dropzone_' . $slot_id,
78          ],
79          $this->buildSlotAttributes($slot_id, $definition['title'], $node_id, $label)
80        ),
81      ];
82
83      if ($sources = $source->getSlotValue($slot_id)) {
84        $dropzone['#slots']['content'] = $this->digFromSlot($instance, $sources);
85      }
86      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, (string) $instance->id(), $this->getPluginID(), $node_id, $slot_id);
87      $slots[] = [
88        [
89          '#plain_text' => $definition['title'],
90        ],
91        $dropzone,
92      ];
93    }
94
95    $build = [
96      '#type' => 'component',
97      '#component' => 'display_builder:layer',
98      '#slots' => [
99        'title' => $label,
100        'children' => $slots,
101      ],
102      '#attributes' => \array_merge(
103        $this->buildNodeAttributes($label, $index)
104      ),
105    ];
106
107    $testid_source = $data['source']['component']['component_id']
108      ?? $data['source']['plugin_id']
109      ?? $data['source']['derivable_context']
110      ?? NULL;
111
112    if ($testid_source !== NULL) {
113      $build['#attributes']['data-testid'] = \sprintf('layer_%s', $testid_source);
114    }
115    elseif (isset($data['source_id'])) {
116      $build['#attributes']['data-testid'] = \sprintf('layer_%s', $data['source_id']);
117    }
118    elseif (\is_string(\reset($data))) {
119      $build['#attributes']['data-testid'] = \sprintf('layer_%s', \reset($data));
120    }
121
122    $build = $this->addThirdPartySettingsSummary($data, $build);
123    $build = $this->addComponentSettingsSummary($source, $build);
124
125    return $this->htmxEvents->onInstanceClick($build, (string) $instance->id(), $node_id, $source->label(), $index);
126  }
127
128  /**
129   * {@inheritdoc}
130   */
131  protected function buildSingleBlock(InstanceInterface $instance, string $node_id, array $data, int $index = 0): array {
132    $label = $this->slotSourceProxy->getLabelWithSummary($data, $this->configuration['contexts'] ?? []);
133
134    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
135      $label['summary'] = (string) $this->t('Field: @label', ['@label' => $label['label']]);
136    }
137
138    $build = [
139      '#type' => 'component',
140      '#component' => 'display_builder:layer',
141      '#slots' => [
142        'title' => $label['summary'],
143      ],
144    ];
145
146    $node_id = $node_id ?: $data['node_id'] ?? NULL;
147
148    if (!$node_id) {
149      $this->logger->error('[WireframePanelBase::buildSingleBlock] missing instance ID. <pre>' . \print_r($data, TRUE) . '</pre>');
150
151      return $build;
152    }
153
154    $build = $this->addThirdPartySettingsSummary($data, $build);
155
156    $build['#attributes'] = \array_merge($build['#attributes'] ?? [], $this->buildNodeAttributes($label['summary'], $index, $data['source_id'] ?? NULL));
157    $build['#attributes']['data-testid'] = $label['label'] ?? '_' . $index;
158
159    return $this->htmxEvents->onInstanceClick($build, (string) $instance->id(), $node_id, $label['summary'], $index);
160  }
161
162  /**
163   * Add third party settings summary to layer's info slot.
164   *
165   * @param array $data
166   *   The node data.
167   * @param array $build
168   *   The layer component renderable array.
169   *
170   * @return array
171   *   The layer component renderable array.
172   */
173  private function addThirdPartySettingsSummary(array $data, array $build): array {
174    if (!isset($data['third_party_settings'])) {
175      return $build;
176    }
177
178    foreach ($data['third_party_settings'] as $provider => $settings) {
179      // In Display Builder, third_party_settings providers can be:
180      // - an island plugin ID (our 'normal' way)
181      // - a Drupal module name (the Drupal way, found in displays imported and
182      // converted, not leveraged by us for now but we may do it later).
183      // So, let's check the plugin ID exists before running logic.
184      if (!$this->islandManager->hasDefinition($provider)) {
185        continue;
186      }
187      $island = $this->islandManager->createInstance($provider, $settings);
188
189      if ($island instanceof ThirdPartySettingsInterface && $summary = $island->getSummary()) {
190        $build['#slots']['info'] = \array_merge($build['#slots']['info'] ?? [], $summary);
191      }
192    }
193
194    return $build;
195  }
196
197  /**
198   * Add config settings summary to layer's info slot.
199   *
200   * @param \Drupal\display_builder\SourceWithSlotsInterface $source
201   *   The source plugin.
202   * @param array $build
203   *   The layer component renderable array.
204   *
205   * @return array
206   *   The layer component renderable array.
207   */
208  private function addComponentSettingsSummary(SourceWithSlotsInterface $source, array $build): array {
209    $items = [];
210
211    foreach ($source->settingsSummary() as $item) {
212      if ($item !== NULL) {
213        $items[] = [
214          '#type' => 'html_tag',
215          '#tag' => 'li',
216          '#value' => $item,
217        ];
218      }
219    }
220
221    if (empty($items)) {
222      return $build;
223    }
224
225    $summary = [
226      [
227        '#type' => 'html_tag',
228        '#tag' => 'em',
229        '#value' => new TranslatableMarkup('Config'),
230      ],
231      [
232        '#type' => 'html_tag',
233        '#tag' => 'ul',
234        '#attributes' => [
235          'class' => ['summary'],
236        ],
237        0 => $items,
238      ],
239    ];
240
241    $build['#slots']['info'] = \array_merge($build['#slots']['info'] ?? [], $summary);
242
243    return $build;
244  }
245
246}