Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 123
0.00% covered (danger)
0.00%
0 / 42
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
LayersPanel
0.00% covered (danger)
0.00%
0 / 116
0.00% covered (danger)
0.00%
0 / 42
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 7
600
0.00% covered (danger)
0.00%
0 / 1
 create
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 keyboardShortcuts
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 build
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 buildSingleComponent
0.00% covered (danger)
0.00%
0 / 45
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
 buildSingleBlock
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
42
 addThirdPartySettingsSummary
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
42
 addComponentSettingsSummary
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
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\Attribute\Island;
9use Drupal\display_builder\InstanceInterface;
10use Drupal\display_builder\Island\IslandPluginManagerInterface;
11use Drupal\display_builder\Island\IslandType;
12use Drupal\display_builder\SourceWithSlotsInterface;
13use Drupal\display_builder\ThirdPartySettingsInterface;
14use Symfony\Component\DependencyInjection\ContainerInterface;
15
16/**
17 * Layers island plugin implementation.
18 */
19#[Island(
20  id: 'layers',
21  label: new TranslatableMarkup('Layers'),
22  description: new TranslatableMarkup('Manage hierarchical layer view of elements without preview.'),
23  type: IslandType::View,
24  default_region: 'main',
25  icon: 'layers',
26)]
27class LayersPanel extends BuilderPanel {
28
29  /**
30   * Island plugins manager.
31   */
32  protected IslandPluginManagerInterface $islandManager;
33
34  /**
35   * {@inheritdoc}
36   */
37  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
38    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
39    $instance->islandManager = $container->get('plugin.manager.db_island');
40
41    return $instance;
42  }
43
44  /**
45   * {@inheritdoc}
46   */
47  public static function keyboardShortcuts(): array {
48    return [
49      'key' => 'y',
50      'help' => t('Show the layer'),
51    ];
52  }
53
54  /**
55   * {@inheritdoc}
56   */
57  public function build(InstanceInterface $builder, array $data = [], array $options = []): array {
58    $build = parent::build($builder, $data, $options);
59
60    if (empty($build['#slots']['content'] ?? [])) {
61      // Load en empty component to have any assets with it.
62      $build['#slots']['content'] = [
63        '#type' => 'component',
64        '#component' => 'display_builder:layer',
65      ];
66    }
67
68    return $build;
69  }
70
71  /**
72   * {@inheritdoc}
73   */
74  protected function buildSingleComponent(string $builder_id, string $instance_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array {
75    $info = $this->resolveComponentInfo($source, $data, $instance_id);
76
77    if ($info === NULL) {
78      return NULL;
79    }
80
81    ['label' => $label, 'instance_id' => $instance_id] = $info;
82
83    $slots = [];
84
85    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
86      $dropzone = [
87        '#type' => 'component',
88        '#component' => 'display_builder:dropzone',
89        '#props' => [
90          'title' => $definition['title'],
91          'variant' => 'highlighted',
92        ],
93        '#attributes' => [
94          // Required for JavaScript @see components/dropzone/dropzone.js.
95          'data-db-id' => $builder_id,
96          // Slot is needed for contextual menu paste.
97          // @see assets/js/contextual_menu.js
98          'data-slot-id' => $slot_id,
99          'data-slot-title' => $definition['title'],
100          'data-node-title' => $label,
101          'data-instance-id' => $instance_id . '_' . $slot_id,
102        ],
103      ];
104
105      if ($sources = $source->getSlotValue($slot_id)) {
106        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
107      }
108      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
109      $slots[] = [
110        [
111          '#plain_text' => $definition['title'],
112        ],
113        $dropzone,
114      ];
115    }
116
117    $build = [
118      '#type' => 'component',
119      '#component' => 'display_builder:layer',
120      '#slots' => [
121        'title' => $label,
122        'children' => $slots,
123      ],
124      // Required for the context menu label.
125      // @see assets/js/contextual_menu.js
126      '#attributes' => [
127        'data-node-title' => $label,
128        'data-instance-id' => $instance_id,
129      ],
130    ];
131    $build = $this->addThirdPartySettingsSummary($data, $build);
132    $build = $this->addComponentSettingsSummary($source, $build);
133
134    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $source->label(), $index);
135  }
136
137  /**
138   * {@inheritdoc}
139   */
140  protected function buildSingleBlock(string $builder_id, string $instance_id, array $data, int $index = 0): array {
141    $label = $this->slotSourceProxy->getLabelWithSummary($data, $this->configuration['contexts'] ?? []);
142
143    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
144      $label['summary'] = (string) $this->t('Field: @label', ['@label' => $label['label']]);
145    }
146
147    $build = [
148      '#type' => 'component',
149      '#component' => 'display_builder:layer',
150      '#slots' => [
151        'title' => $label['summary'],
152      ],
153    ];
154
155    $instance_id = $instance_id ?: $data['node_id'] ?? NULL;
156
157    if (!$instance_id) {
158      $this->logger->error('[LayersPanel::buildSingleBlock] missing instance ID. <pre>' . \print_r($data, TRUE) . '</pre>');
159
160      return $build;
161    }
162
163    $build = $this->addThirdPartySettingsSummary($data, $build);
164
165    // This label is used for contextual menu.
166    // @see assets/js/contextual_menu.js
167    $build['#attributes']['data-node-title'] = $label['summary'];
168    $build['#attributes']['data-slot-position'] = $index;
169    $build['#attributes']['data-instance-id'] = $instance_id;
170
171    // Add data-node-type for easier identification of block types in JS, CSS or
172    // tests.
173    if (isset($data['source_id'])) {
174      $build['#attributes']['data-node-type'] = $data['source_id'];
175    }
176
177    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $label['summary'], $index);
178  }
179
180  /**
181   * Add third party settings summary to layer's info slot.
182   *
183   * @param array $data
184   *   The node data.
185   * @param array $build
186   *   The layer component renderable array.
187   *
188   * @return array
189   *   The layer component renderable array.
190   */
191  private function addThirdPartySettingsSummary(array $data, array $build): array {
192    if (!isset($data['third_party_settings'])) {
193      return $build;
194    }
195
196    foreach ($data['third_party_settings'] as $provider => $settings) {
197      // In Display Builder, third_party_settings providers can be:
198      // - an island plugin ID (our 'normal' way)
199      // - a Drupal module name (the Drupal way, found in displays imported and
200      // converted, not leveraged by us for now but we may do it later).
201      // So, let's check the plugin ID exists before running logic.
202      if (!$this->islandManager->hasDefinition($provider)) {
203        continue;
204      }
205      $island = $this->islandManager->createInstance($provider, $settings);
206
207      if ($island instanceof ThirdPartySettingsInterface && $summary = $island->getSummary()) {
208        $build['#slots']['info'] = \array_merge($build['#slots']['info'] ?? [], $summary);
209      }
210    }
211
212    return $build;
213  }
214
215  /**
216   * Add config settings summary to layer's info slot.
217   *
218   * @param \Drupal\display_builder\SourceWithSlotsInterface $source
219   *   The source plugin.
220   * @param array $build
221   *   The layer component renderable array.
222   *
223   * @return array
224   *   The layer component renderable array.
225   */
226  private function addComponentSettingsSummary(SourceWithSlotsInterface $source, array $build): array {
227    $items = [];
228
229    foreach ($source->settingsSummary() as $item) {
230      if ($item !== NULL) {
231        $items[] = [
232          '#type' => 'html_tag',
233          '#tag' => 'li',
234          '#value' => $item,
235        ];
236      }
237    }
238
239    if (empty($items)) {
240      return $build;
241    }
242
243    $summary = [
244      [
245        '#type' => 'html_tag',
246        '#tag' => 'em',
247        '#value' => new TranslatableMarkup('Config'),
248      ],
249      [
250        '#type' => 'html_tag',
251        '#tag' => 'ul',
252        '#attributes' => [
253          'class' => ['summary'],
254        ],
255        0 => $items,
256      ],
257    ];
258
259    $build['#slots']['info'] = \array_merge($build['#slots']['info'] ?? [], $summary);
260
261    return $build;
262  }
263
264}

Branches

Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once. Please also be aware that some branches may be implicit rather than explicit, e.g. an if statement always has an else as part of its logical flow even if you didn't write one.

LayersPanel->addComponentSettingsSummary
226  private function addComponentSettingsSummary(SourceWithSlotsInterface $source, array $build): array {
227    $items = [];
228
229    foreach ($source->settingsSummary() as $item) {
229    foreach ($source->settingsSummary() as $item) {
230      if ($item !== NULL) {
229    foreach ($source->settingsSummary() as $item) {
230      if ($item !== NULL) {
231        $items[] = [
232          '#type' => 'html_tag',
229    foreach ($source->settingsSummary() as $item) {
229    foreach ($source->settingsSummary() as $item) {
230      if ($item !== NULL) {
231        $items[] = [
232          '#type' => 'html_tag',
233          '#tag' => 'li',
234          '#value' => $item,
235        ];
236      }
237    }
238
239    if (empty($items)) {
240      return $build;
245        '#type' => 'html_tag',
246        '#tag' => 'em',
247        '#value' => new TranslatableMarkup('Config'),
248      ],
249      [
250        '#type' => 'html_tag',
251        '#tag' => 'ul',
252        '#attributes' => [
253          'class' => ['summary'],
254        ],
255        0 => $items,
256      ],
257    ];
258
259    $build['#slots']['info'] = \array_merge($build['#slots']['info'] ?? [], $summary);
260
261    return $build;
262  }
LayersPanel->addThirdPartySettingsSummary
191  private function addThirdPartySettingsSummary(array $data, array $build): array {
192    if (!isset($data['third_party_settings'])) {
193      return $build;
196    foreach ($data['third_party_settings'] as $provider => $settings) {
196    foreach ($data['third_party_settings'] as $provider => $settings) {
196    foreach ($data['third_party_settings'] as $provider => $settings) {
197      // In Display Builder, third_party_settings providers can be:
198      // - an island plugin ID (our 'normal' way)
199      // - a Drupal module name (the Drupal way, found in displays imported and
200      // converted, not leveraged by us for now but we may do it later).
201      // So, let's check the plugin ID exists before running logic.
202      if (!$this->islandManager->hasDefinition($provider)) {
203        continue;
205      $island = $this->islandManager->createInstance($provider, $settings);
206
207      if ($island instanceof ThirdPartySettingsInterface && $summary = $island->getSummary()) {
207      if ($island instanceof ThirdPartySettingsInterface && $summary = $island->getSummary()) {
207      if ($island instanceof ThirdPartySettingsInterface && $summary = $island->getSummary()) {
196    foreach ($data['third_party_settings'] as $provider => $settings) {
197      // In Display Builder, third_party_settings providers can be:
198      // - an island plugin ID (our 'normal' way)
199      // - a Drupal module name (the Drupal way, found in displays imported and
200      // converted, not leveraged by us for now but we may do it later).
201      // So, let's check the plugin ID exists before running logic.
202      if (!$this->islandManager->hasDefinition($provider)) {
203        continue;
204      }
205      $island = $this->islandManager->createInstance($provider, $settings);
206
207      if ($island instanceof ThirdPartySettingsInterface && $summary = $island->getSummary()) {
208        $build['#slots']['info'] = \array_merge($build['#slots']['info'] ?? [], $summary);
196    foreach ($data['third_party_settings'] as $provider => $settings) {
196    foreach ($data['third_party_settings'] as $provider => $settings) {
197      // In Display Builder, third_party_settings providers can be:
198      // - an island plugin ID (our 'normal' way)
199      // - a Drupal module name (the Drupal way, found in displays imported and
200      // converted, not leveraged by us for now but we may do it later).
201      // So, let's check the plugin ID exists before running logic.
202      if (!$this->islandManager->hasDefinition($provider)) {
203        continue;
204      }
205      $island = $this->islandManager->createInstance($provider, $settings);
206
207      if ($island instanceof ThirdPartySettingsInterface && $summary = $island->getSummary()) {
208        $build['#slots']['info'] = \array_merge($build['#slots']['info'] ?? [], $summary);
209      }
210    }
211
212    return $build;
213  }
LayersPanel->build
57  public function build(InstanceInterface $builder, array $data = [], array $options = []): array {
58    $build = parent::build($builder, $data, $options);
59
60    if (empty($build['#slots']['content'] ?? [])) {
62      $build['#slots']['content'] = [
63        '#type' => 'component',
64        '#component' => 'display_builder:layer',
65      ];
66    }
67
68    return $build;
68    return $build;
69  }
LayersPanel->buildSingleBlock
140  protected function buildSingleBlock(string $builder_id, string $instance_id, array $data, int $index = 0): array {
141    $label = $this->slotSourceProxy->getLabelWithSummary($data, $this->configuration['contexts'] ?? []);
142
143    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
143    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
143    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
144      $label['summary'] = (string) $this->t('Field: @label', ['@label' => $label['label']]);
145    }
146
147    $build = [
148      '#type' => 'component',
148      '#type' => 'component',
149      '#component' => 'display_builder:layer',
150      '#slots' => [
151        'title' => $label['summary'],
152      ],
153    ];
154
155    $instance_id = $instance_id ?: $data['node_id'] ?? NULL;
156
157    if (!$instance_id) {
158      $this->logger->error('[LayersPanel::buildSingleBlock] missing instance ID. <pre>' . \print_r($data, TRUE) . '</pre>');
159
160      return $build;
163    $build = $this->addThirdPartySettingsSummary($data, $build);
164
165    // This label is used for contextual menu.
166    // @see assets/js/contextual_menu.js
167    $build['#attributes']['data-node-title'] = $label['summary'];
168    $build['#attributes']['data-slot-position'] = $index;
169    $build['#attributes']['data-instance-id'] = $instance_id;
170
171    // Add data-node-type for easier identification of block types in JS, CSS or
172    // tests.
173    if (isset($data['source_id'])) {
174      $build['#attributes']['data-node-type'] = $data['source_id'];
175    }
176
177    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $label['summary'], $index);
177    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $label['summary'], $index);
178  }
LayersPanel->buildSingleComponent
74  protected function buildSingleComponent(string $builder_id, string $instance_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array {
75    $info = $this->resolveComponentInfo($source, $data, $instance_id);
76
77    if ($info === NULL) {
78      return NULL;
81    ['label' => $label, 'instance_id' => $instance_id] = $info;
82
83    $slots = [];
84
85    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
85    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
85    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
86      $dropzone = [
87        '#type' => 'component',
88        '#component' => 'display_builder:dropzone',
89        '#props' => [
90          'title' => $definition['title'],
91          'variant' => 'highlighted',
92        ],
93        '#attributes' => [
94          // Required for JavaScript @see components/dropzone/dropzone.js.
95          'data-db-id' => $builder_id,
96          // Slot is needed for contextual menu paste.
97          // @see assets/js/contextual_menu.js
98          'data-slot-id' => $slot_id,
99          'data-slot-title' => $definition['title'],
100          'data-node-title' => $label,
101          'data-instance-id' => $instance_id . '_' . $slot_id,
102        ],
103      ];
104
105      if ($sources = $source->getSlotValue($slot_id)) {
106        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
107      }
108      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
85    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
86      $dropzone = [
87        '#type' => 'component',
88        '#component' => 'display_builder:dropzone',
89        '#props' => [
90          'title' => $definition['title'],
91          'variant' => 'highlighted',
92        ],
93        '#attributes' => [
94          // Required for JavaScript @see components/dropzone/dropzone.js.
95          'data-db-id' => $builder_id,
96          // Slot is needed for contextual menu paste.
97          // @see assets/js/contextual_menu.js
98          'data-slot-id' => $slot_id,
99          'data-slot-title' => $definition['title'],
100          'data-node-title' => $label,
101          'data-instance-id' => $instance_id . '_' . $slot_id,
102        ],
103      ];
104
105      if ($sources = $source->getSlotValue($slot_id)) {
106        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
107      }
108      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
85    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
86      $dropzone = [
87        '#type' => 'component',
88        '#component' => 'display_builder:dropzone',
89        '#props' => [
90          'title' => $definition['title'],
91          'variant' => 'highlighted',
92        ],
93        '#attributes' => [
94          // Required for JavaScript @see components/dropzone/dropzone.js.
95          'data-db-id' => $builder_id,
96          // Slot is needed for contextual menu paste.
97          // @see assets/js/contextual_menu.js
98          'data-slot-id' => $slot_id,
99          'data-slot-title' => $definition['title'],
100          'data-node-title' => $label,
101          'data-instance-id' => $instance_id . '_' . $slot_id,
102        ],
103      ];
104
105      if ($sources = $source->getSlotValue($slot_id)) {
106        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
107      }
108      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
109      $slots[] = [
110        [
111          '#plain_text' => $definition['title'],
112        ],
113        $dropzone,
114      ];
115    }
116
117    $build = [
118      '#type' => 'component',
119      '#component' => 'display_builder:layer',
120      '#slots' => [
121        'title' => $label,
122        'children' => $slots,
123      ],
124      // Required for the context menu label.
125      // @see assets/js/contextual_menu.js
126      '#attributes' => [
127        'data-node-title' => $label,
128        'data-instance-id' => $instance_id,
129      ],
130    ];
131    $build = $this->addThirdPartySettingsSummary($data, $build);
132    $build = $this->addComponentSettingsSummary($source, $build);
133
134    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $source->label(), $index);
135  }
LayersPanel->create
37  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
38    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
39    $instance->islandManager = $container->get('plugin.manager.db_island');
40
41    return $instance;
42  }
LayersPanel->keyboardShortcuts
49      'key' => 'y',
50      'help' => t('Show the layer'),
51    ];
52  }