Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 108
0.00% covered (danger)
0.00%
0 / 47
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
BlockLibraryPanel
0.00% covered (danger)
0.00%
0 / 102
0.00% covered (danger)
0.00%
0 / 47
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 10
756
0.00% covered (danger)
0.00%
0 / 1
 create
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
 defaultConfiguration
0.00% covered (danger)
0.00%
0 / 7
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
 buildConfigurationForm
0.00% covered (danger)
0.00%
0 / 8
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
 configurationSummary
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 build
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 label
0.00% covered (danger)
0.00%
0 / 1
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
 buildCategorySection
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 getSources
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
30
 getProvidersOptions
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getProviders
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
72
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\display_builder\Plugin\display_builder\Island;
6
7use Drupal\Core\Extension\ModuleExtensionList;
8use Drupal\Core\Form\FormStateInterface;
9use Drupal\Core\StringTranslation\TranslatableMarkup;
10use Drupal\display_builder\Attribute\Island;
11use Drupal\display_builder\BlockLibrarySourceHelper;
12use Drupal\display_builder\InstanceInterface;
13use Drupal\display_builder\IslandConfigurationFormInterface;
14use Drupal\display_builder\IslandConfigurationFormTrait;
15use Drupal\display_builder\IslandPluginBase;
16use Drupal\display_builder\IslandType;
17use Drupal\ui_patterns\SourcePluginBase;
18use Drupal\ui_patterns\SourcePluginManager;
19use Drupal\ui_patterns\SourceWithChoicesInterface;
20use Symfony\Component\DependencyInjection\ContainerInterface;
21
22/**
23 * Block library island plugin implementation.
24 */
25#[Island(
26  id: 'block_library',
27  enabled_by_default: TRUE,
28  label: new TranslatableMarkup('Blocks library'),
29  description: new TranslatableMarkup('List of available blocks.'),
30  type: IslandType::Library,
31)]
32class BlockLibraryPanel extends IslandPluginBase implements IslandConfigurationFormInterface {
33
34  use IslandConfigurationFormTrait;
35
36  private const HIDE_SOURCE = [
37    'component',
38    // Used only for imports from Manage Display and Layout Builder.
39    'extra_field',
40  ];
41
42  private const HIDE_PROVIDER = ['ui_patterns_blocks'];
43
44  /**
45   * The sources.
46   */
47  protected ?array $sources = NULL;
48
49  /**
50   * The module list extension service.
51   */
52  protected ModuleExtensionList $moduleList;
53
54  /**
55   * The UI Patterns source plugin manager.
56   */
57  protected SourcePluginManager $sourceManager;
58
59  /**
60   * {@inheritdoc}
61   */
62  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
63    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
64    $instance->moduleList = $container->get('extension.list.module');
65    $instance->sourceManager = $container->get('plugin.manager.ui_patterns_source');
66
67    return $instance;
68  }
69
70  /**
71   * {@inheritdoc}
72   */
73  public function defaultConfiguration(): array {
74    return [
75      'exclude' => [
76        'devel',
77        'htmx',
78        'shortcut',
79      ],
80    ];
81  }
82
83  /**
84   * {@inheritdoc}
85   */
86  public function buildConfigurationForm(array $form, FormStateInterface $form_state): array {
87    $configuration = $this->getConfiguration();
88
89    $form['exclude'] = [
90      '#type' => 'checkboxes',
91      '#title' => $this->t('Exclude modules'),
92      '#options' => $this->getProvidersOptions(),
93      '#default_value' => $configuration['exclude'],
94    ];
95
96    return $form;
97  }
98
99  /**
100   * {@inheritdoc}
101   */
102  public function configurationSummary(): array {
103    $configuration = $this->getConfiguration();
104
105    return [
106      $this->t('Excluded modules: @exclude', [
107        '@exclude' => \implode(', ', \array_filter($configuration['exclude'] ?? []) ?: [$this->t('None')]),
108      ]),
109    ];
110  }
111
112  /**
113   * {@inheritdoc}
114   */
115  public function build(InstanceInterface $builder, array $data = [], array $options = []): array {
116    $builder_id = (string) $builder->id();
117    $configuration = $this->getConfiguration();
118
119    $exclude_providers = \array_merge(
120      $configuration['exclude'] ?? [],
121      self::HIDE_PROVIDER
122    );
123    $categories = BlockLibrarySourceHelper::getGroupedChoices(
124      $this->getSources(),
125      $exclude_providers,
126    );
127
128    $build = [];
129
130    foreach ($categories as $category_data) {
131      $build[] = $this->buildCategorySection($category_data, $builder_id);
132    }
133
134    return [
135      '#type' => 'component',
136      '#component' => 'display_builder:library_panel',
137      '#slots' => [
138        'content' => $this->buildDraggables($builder_id, $build),
139      ],
140    ];
141  }
142
143  /**
144   * {@inheritdoc}
145   */
146  public function label(): string {
147    return 'Blocks';
148  }
149
150  /**
151   * Build a category section.
152   *
153   * @param array $category_data
154   *   The category data.
155   * @param string $builder_id
156   *   The builder ID.
157   *
158   * @return array
159   *   The render array for the category section.
160   */
161  private function buildCategorySection(array $category_data, string $builder_id): array {
162    $section = [];
163
164    if (!empty($category_data['label'])) {
165      $section[] = [
166        '#type' => 'html_tag',
167        '#tag' => 'h4',
168        '#attributes' => ['class' => 'db-filter-hide-on-search'],
169        '#value' => $category_data['label'],
170      ];
171    }
172
173    foreach ($category_data['choices'] as $choice) {
174      $section[] = $choice['preview']
175        ? $this->buildPlaceholderButtonWithPreview($builder_id, $choice['label'], $choice['data'] ?? [], $choice['preview'], $choice['keywords'] ?? '')
176        : $this->buildPlaceholderButton($choice['label'], $choice['data'] ?? [], $choice['keywords'] ?? '');
177    }
178
179    return $section;
180  }
181
182  /**
183   * Returns all possible sources.
184   *
185   * @throws \Drupal\Component\Plugin\Exception\PluginException
186   *
187   * @return array<string, array>
188   *   An array of sources.
189   */
190  private function getSources(): array {
191    if ($this->sources !== NULL) {
192      return $this->sources;
193    }
194
195    $definitions = $this->sourceManager->getDefinitionsForPropType('slot', $this->configuration['contexts'] ?? []);
196    $slot_definition = ['ui_patterns' => ['type_definition' => $this->sourceManager->getSlotPropType()]];
197
198    foreach ($definitions as $source_id => $definition) {
199      if (\in_array($source_id, self::HIDE_SOURCE, TRUE)) {
200        continue;
201      }
202      $source = $this->sourceManager->createInstance($source_id,
203        SourcePluginBase::buildConfiguration('slot', $slot_definition, ['source' => []], $this->configuration['contexts'] ?? [])
204      );
205      $this->sources[$source_id] = [
206        'definition' => $definition,
207        'source' => $source,
208      ];
209
210      if ($source instanceof SourceWithChoicesInterface) {
211        $this->sources[$source_id]['choices'] = $source->getChoices();
212      }
213    }
214
215    return $this->sources;
216  }
217
218  /**
219   * Get providers options for select input.
220   *
221   * @return array
222   *   An associative array with module ID as key and module description as
223   *   value.
224   */
225  private function getProvidersOptions(): array {
226    $options = [];
227
228    foreach ($this->getProviders() as $provider_id => $provider) {
229      $params = [
230        '@name' => $provider['name'],
231        '@count' => $provider['count'],
232      ];
233      $options[$provider_id] = $this->formatPlural($provider['count'], '@name (@count block)', '@name (@count blocks)', $params);
234    }
235
236    return $options;
237  }
238
239  /**
240   * Get all providers.
241   *
242   * @return array
243   *   Drupal modules definitions, keyed by extension ID
244   */
245  private function getProviders(): array {
246    $sources = $this->getSources();
247    $providers = [];
248    $modules = $this->moduleList->getAllInstalledInfo();
249
250    foreach ($sources as $source_data) {
251      if (!isset($source_data['choices'])) {
252        continue;
253      }
254      $choices = $source_data['choices'];
255
256      foreach ($choices as $choice) {
257        $provider = $choice['provider'] ?? '';
258
259        if (!$provider || \in_array($provider, self::HIDE_PROVIDER, TRUE)) {
260          continue;
261        }
262
263        if (!isset($modules[$provider])) {
264          // If the provider is not a module, skip it.
265          continue;
266        }
267
268        if (!isset($providers[$provider])) {
269          $providers[$provider] = $modules[$provider];
270          $providers[$provider]['count'] = 0;
271        }
272        ++$providers[$provider]['count'];
273      }
274    }
275
276    return $providers;
277  }
278
279}

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.

BlockLibraryPanel->build
115  public function build(InstanceInterface $builder, array $data = [], array $options = []): array {
116    $builder_id = (string) $builder->id();
117    $configuration = $this->getConfiguration();
118
119    $exclude_providers = \array_merge(
120      $configuration['exclude'] ?? [],
121      self::HIDE_PROVIDER
122    );
123    $categories = BlockLibrarySourceHelper::getGroupedChoices(
124      $this->getSources(),
125      $exclude_providers,
126    );
127
128    $build = [];
129
130    foreach ($categories as $category_data) {
130    foreach ($categories as $category_data) {
130    foreach ($categories as $category_data) {
131      $build[] = $this->buildCategorySection($category_data, $builder_id);
130    foreach ($categories as $category_data) {
131      $build[] = $this->buildCategorySection($category_data, $builder_id);
132    }
133
134    return [
135      '#type' => 'component',
136      '#component' => 'display_builder:library_panel',
137      '#slots' => [
138        'content' => $this->buildDraggables($builder_id, $build),
BlockLibraryPanel->buildCategorySection
161  private function buildCategorySection(array $category_data, string $builder_id): array {
162    $section = [];
163
164    if (!empty($category_data['label'])) {
166        '#type' => 'html_tag',
167        '#tag' => 'h4',
168        '#attributes' => ['class' => 'db-filter-hide-on-search'],
169        '#value' => $category_data['label'],
170      ];
171    }
172
173    foreach ($category_data['choices'] as $choice) {
173    foreach ($category_data['choices'] as $choice) {
173    foreach ($category_data['choices'] as $choice) {
174      $section[] = $choice['preview']
175        ? $this->buildPlaceholderButtonWithPreview($builder_id, $choice['label'], $choice['data'] ?? [], $choice['preview'], $choice['keywords'] ?? '')
174      $section[] = $choice['preview']
175        ? $this->buildPlaceholderButtonWithPreview($builder_id, $choice['label'], $choice['data'] ?? [], $choice['preview'], $choice['keywords'] ?? '')
176        : $this->buildPlaceholderButton($choice['label'], $choice['data'] ?? [], $choice['keywords'] ?? '');
173    foreach ($category_data['choices'] as $choice) {
174      $section[] = $choice['preview']
173    foreach ($category_data['choices'] as $choice) {
174      $section[] = $choice['preview']
175        ? $this->buildPlaceholderButtonWithPreview($builder_id, $choice['label'], $choice['data'] ?? [], $choice['preview'], $choice['keywords'] ?? '')
176        : $this->buildPlaceholderButton($choice['label'], $choice['data'] ?? [], $choice['keywords'] ?? '');
177    }
178
179    return $section;
BlockLibraryPanel->buildConfigurationForm
86  public function buildConfigurationForm(array $form, FormStateInterface $form_state): array {
87    $configuration = $this->getConfiguration();
88
89    $form['exclude'] = [
90      '#type' => 'checkboxes',
91      '#title' => $this->t('Exclude modules'),
92      '#options' => $this->getProvidersOptions(),
93      '#default_value' => $configuration['exclude'],
94    ];
95
96    return $form;
BlockLibraryPanel->configurationSummary
103    $configuration = $this->getConfiguration();
104
105    return [
106      $this->t('Excluded modules: @exclude', [
107        '@exclude' => \implode(', ', \array_filter($configuration['exclude'] ?? []) ?: [$this->t('None')]),
BlockLibraryPanel->create
62  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
63    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
64    $instance->moduleList = $container->get('extension.list.module');
65    $instance->sourceManager = $container->get('plugin.manager.ui_patterns_source');
66
67    return $instance;
BlockLibraryPanel->defaultConfiguration
76        'devel',
BlockLibraryPanel->getProviders
246    $sources = $this->getSources();
247    $providers = [];
248    $modules = $this->moduleList->getAllInstalledInfo();
249
250    foreach ($sources as $source_data) {
250    foreach ($sources as $source_data) {
251      if (!isset($source_data['choices'])) {
252        continue;
254      $choices = $source_data['choices'];
255
256      foreach ($choices as $choice) {
256      foreach ($choices as $choice) {
257        $provider = $choice['provider'] ?? '';
258
259        if (!$provider || \in_array($provider, self::HIDE_PROVIDER, TRUE)) {
259        if (!$provider || \in_array($provider, self::HIDE_PROVIDER, TRUE)) {
260          continue;
263        if (!isset($modules[$provider])) {
265          continue;
268        if (!isset($providers[$provider])) {
269          $providers[$provider] = $modules[$provider];
270          $providers[$provider]['count'] = 0;
271        }
272        ++$providers[$provider]['count'];
256      foreach ($choices as $choice) {
257        $provider = $choice['provider'] ?? '';
258
259        if (!$provider || \in_array($provider, self::HIDE_PROVIDER, TRUE)) {
260          continue;
261        }
262
263        if (!isset($modules[$provider])) {
264          // If the provider is not a module, skip it.
265          continue;
266        }
267
268        if (!isset($providers[$provider])) {
269          $providers[$provider] = $modules[$provider];
270          $providers[$provider]['count'] = 0;
271        }
272        ++$providers[$provider]['count'];
250    foreach ($sources as $source_data) {
251      if (!isset($source_data['choices'])) {
252        continue;
253      }
254      $choices = $source_data['choices'];
255
256      foreach ($choices as $choice) {
250    foreach ($sources as $source_data) {
251      if (!isset($source_data['choices'])) {
252        continue;
253      }
254      $choices = $source_data['choices'];
255
256      foreach ($choices as $choice) {
257        $provider = $choice['provider'] ?? '';
258
259        if (!$provider || \in_array($provider, self::HIDE_PROVIDER, TRUE)) {
260          continue;
261        }
262
263        if (!isset($modules[$provider])) {
264          // If the provider is not a module, skip it.
265          continue;
266        }
267
268        if (!isset($providers[$provider])) {
269          $providers[$provider] = $modules[$provider];
270          $providers[$provider]['count'] = 0;
271        }
272        ++$providers[$provider]['count'];
273      }
274    }
275
276    return $providers;
BlockLibraryPanel->getProvidersOptions
226    $options = [];
227
228    foreach ($this->getProviders() as $provider_id => $provider) {
228    foreach ($this->getProviders() as $provider_id => $provider) {
228    foreach ($this->getProviders() as $provider_id => $provider) {
228    foreach ($this->getProviders() as $provider_id => $provider) {
229      $params = [
230        '@name' => $provider['name'],
231        '@count' => $provider['count'],
232      ];
233      $options[$provider_id] = $this->formatPlural($provider['count'], '@name (@count block)', '@name (@count blocks)', $params);
234    }
235
236    return $options;
BlockLibraryPanel->getSources
191    if ($this->sources !== NULL) {
192      return $this->sources;
195    $definitions = $this->sourceManager->getDefinitionsForPropType('slot', $this->configuration['contexts'] ?? []);
196    $slot_definition = ['ui_patterns' => ['type_definition' => $this->sourceManager->getSlotPropType()]];
197
198    foreach ($definitions as $source_id => $definition) {
198    foreach ($definitions as $source_id => $definition) {
198    foreach ($definitions as $source_id => $definition) {
199      if (\in_array($source_id, self::HIDE_SOURCE, TRUE)) {
200        continue;
202      $source = $this->sourceManager->createInstance($source_id,
203        SourcePluginBase::buildConfiguration('slot', $slot_definition, ['source' => []], $this->configuration['contexts'] ?? [])
204      );
205      $this->sources[$source_id] = [
206        'definition' => $definition,
207        'source' => $source,
208      ];
209
210      if ($source instanceof SourceWithChoicesInterface) {
198    foreach ($definitions as $source_id => $definition) {
199      if (\in_array($source_id, self::HIDE_SOURCE, TRUE)) {
200        continue;
201      }
202      $source = $this->sourceManager->createInstance($source_id,
203        SourcePluginBase::buildConfiguration('slot', $slot_definition, ['source' => []], $this->configuration['contexts'] ?? [])
204      );
205      $this->sources[$source_id] = [
206        'definition' => $definition,
207        'source' => $source,
208      ];
209
210      if ($source instanceof SourceWithChoicesInterface) {
211        $this->sources[$source_id]['choices'] = $source->getChoices();
198    foreach ($definitions as $source_id => $definition) {
199      if (\in_array($source_id, self::HIDE_SOURCE, TRUE)) {
200        continue;
201      }
202      $source = $this->sourceManager->createInstance($source_id,
203        SourcePluginBase::buildConfiguration('slot', $slot_definition, ['source' => []], $this->configuration['contexts'] ?? [])
204      );
205      $this->sources[$source_id] = [
206        'definition' => $definition,
207        'source' => $source,
208      ];
209
210      if ($source instanceof SourceWithChoicesInterface) {
211        $this->sources[$source_id]['choices'] = $source->getChoices();
212      }
213    }
214
215    return $this->sources;
BlockLibraryPanel->label
147    return 'Blocks';