Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 175
0.00% covered (danger)
0.00%
0 / 74
0.00% covered (danger)
0.00%
0 / 62
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
BlockLibraryPanel
0.00% covered (danger)
0.00%
0 / 168
0.00% covered (danger)
0.00%
0 / 74
0.00% covered (danger)
0.00%
0 / 62
0.00% covered (danger)
0.00%
0 / 9
1482
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
 defaultConfiguration
0.00% covered (danger)
0.00%
0 / 10
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 / 38
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 / 18
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
56
 build
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
42
 buildCategorySection
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
 getSources
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
56
 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 / 17
0.00% covered (danger)
0.00%
0 / 13
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\Island\IslandConfigurationFormInterface;
14use Drupal\display_builder\Island\IslandConfigurationFormTrait;
15use Drupal\display_builder\Island\IslandPluginBase;
16use Drupal\display_builder\Island\IslandType;
17use Drupal\display_builder\SourceWithSlotsInterface;
18use Drupal\ui_patterns\SourcePluginBase;
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'),
29  description: new TranslatableMarkup('List of available blocks.'),
30  type: IslandType::Library,
31  icon: 'bricks',
32)]
33class BlockLibraryPanel extends IslandPluginBase implements IslandConfigurationFormInterface {
34
35  use IslandConfigurationFormTrait;
36
37  private const HIDE_SOURCE = [
38    'component',
39    // Token is deprecated in UI Patterns; 'Textfield' allow token.
40    'token',
41    'wysiwyg',
42  ];
43
44  private const HIDE_PROVIDER = ['ui_patterns_blocks'];
45
46  /**
47   * The sources.
48   */
49  protected array $sources = [];
50
51  /**
52   * The module extension list service.
53   */
54  protected ModuleExtensionList $moduleList;
55
56  /**
57   * {@inheritdoc}
58   */
59  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
60    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
61    $instance->moduleList = $container->get('extension.list.module');
62
63    return $instance;
64  }
65
66  /**
67   * {@inheritdoc}
68   */
69  public function defaultConfiguration(): array {
70    return [
71      'exclude' => [
72        'devel',
73        'htmx',
74        'shortcut',
75      ],
76      'exclude_id' => '',
77      'show' => 'grouped',
78      'preview' => TRUE,
79    ];
80  }
81
82  /**
83   * {@inheritdoc}
84   */
85  public function buildConfigurationForm(array $form, FormStateInterface $form_state): array {
86    $configuration = $this->getConfiguration();
87
88    $form['display'] = [
89      '#type' => 'fieldset',
90      '#title' => $this->t('Display'),
91    ];
92
93    $form['display']['show'] = [
94      '#type' => 'select',
95      '#title' => $this->t('Display blocks as'),
96      '#default_value' => $configuration['show'],
97      '#options' => [
98        'grouped' => $this->t('grouped'),
99        'flat' => $this->t('flat list'),
100      ],
101    ];
102
103    $form['display']['preview'] = [
104      '#type' => 'checkbox',
105      '#title' => $this->t('Enable preview on hover'),
106      '#description' => $this->t('Enable or disable the preview of components when hovering over them.'),
107      '#default_value' => $configuration['preview'],
108    ];
109
110    $form['configuration'] = [
111      '#type' => 'fieldset',
112      '#title' => $this->t('Configuration'),
113    ];
114
115    $form['configuration']['exclude'] = [
116      '#type' => 'checkboxes',
117      '#title' => $this->t('Exclude from modules'),
118      '#description' => $this->t('Select the modules which blocks have to be excluded from the block library.'),
119      '#options' => $this->getProvidersOptions(),
120      '#default_value' => $configuration['exclude'],
121    ];
122
123    $form['configuration']['exclude_id'] = [
124      '#type' => 'textarea',
125      '#title' => $this->t('Exclude by id'),
126      '#description' => $this->t('Provide a list of id to exclude. One id by line, source id is used. Example: "help_block views_block:comments_recent-block_1 system_menu_block:account field:node:article:vid".'),
127      '#default_value' => $configuration['exclude_id'],
128    ];
129
130    return $form;
131  }
132
133  /**
134   * {@inheritdoc}
135   */
136  public function configurationSummary(): array {
137    $configuration = $this->getConfiguration();
138
139    $summary[] = $this->t('Blocks displayed as: <em>@list</em>', [
140      '@list' => match ($configuration['show']) {
141        'flat' => $this->t('flat'),
142        default => $this->t('grouped'),
143      },
144    ]);
145
146    if ($configuration['preview']) {
147      $summary[] = $this->t('Preview on hover enabled');
148    }
149
150    $summary[] = $this->t('Excluded modules: @exclude', [
151      '@exclude' => \implode(', ', \array_filter($configuration['exclude'] ?? []) ?: [$this->t('None')]),
152    ]);
153
154    if (\strlen($configuration['exclude_id'] ?? '') > 5) {
155      $value = \preg_split('/\r\n|\r|\n/', \trim($configuration['exclude_id'] ?? ''));
156
157      if ($value === FALSE) {
158        $summary[] = $this->t('Block(s) excluded');
159      }
160      else {
161        $summary[] = $this->formatPlural(\count($value), '@count block excluded', '@count blocks excluded by id');
162      }
163    }
164
165    return $summary;
166  }
167
168  /**
169   * {@inheritdoc}
170   */
171  public function build(InstanceInterface $builder, array $data = [], array $options = []): array {
172    $builder_id = (string) $builder->id();
173    $configuration = $this->getConfiguration();
174
175    $exclude_providers = \array_merge(
176      $configuration['exclude'] ?? [],
177      self::HIDE_PROVIDER
178    );
179    $exclude_by_id = \preg_split('/\r\n|\r|\n/', \trim($configuration['exclude_id'] ?? '')) ?: [];
180
181    $build = [];
182
183    if ($configuration['show'] === 'grouped') {
184      $categories = BlockLibrarySourceHelper::getGroupedChoices(
185        $this->getSources(),
186        $exclude_providers,
187        $exclude_by_id,
188        (bool) $configuration['preview'],
189      );
190
191      foreach ($categories as $category_data) {
192        $build[] = $this->buildCategorySection($category_data, $builder_id, (bool) $configuration['preview']);
193      }
194    }
195    else {
196      $choices = BlockLibrarySourceHelper::getChoices(
197        $this->getSources(),
198        $exclude_providers,
199        $exclude_by_id,
200        (bool) $configuration['preview'],
201      );
202
203      foreach ($choices as $choice) {
204        if ($configuration['preview']) {
205          $build[] = $this->buildPlaceholderListWithPreview($builder_id, $choice['label'], $data, $choice['preview'], $choice['keywords']);
206        }
207        else {
208          $build[] = $this->buildPlaceholderList($choice['label'], $data, $choice['keywords']);
209        }
210      }
211    }
212
213    return [
214      '#type' => 'component',
215      '#component' => 'display_builder:library_panel',
216      '#slots' => [
217        'content' => $this->buildDraggables($builder_id, $build),
218      ],
219    ];
220  }
221
222  /**
223   * Build a category section.
224   *
225   * @param array $category_data
226   *   The category data.
227   * @param string $builder_id
228   *   The builder ID.
229   * @param bool $preview
230   *   Whether to show preview on hover.
231   *
232   * @return array
233   *   The render array for the category section.
234   */
235  private function buildCategorySection(array $category_data, string $builder_id, bool $preview): array {
236    $section = [];
237
238    if (!empty($category_data['label'])) {
239      $section[] = [
240        '#type' => 'html_tag',
241        '#tag' => 'h4',
242        '#attributes' => [
243          'class' => ['db-placeholder__group', 'db-filter-hide-on-search'],
244        ],
245        '#value' => $category_data['label'],
246      ];
247    }
248
249    foreach ($category_data['choices'] as $choice) {
250      $label = $choice['label'] ?? '-';
251
252      if ($preview && $choice['preview']) {
253        $section[] = $this->buildPlaceholderListWithPreview($builder_id, $label, $choice['data'] ?? [], $choice['preview'], $choice['keywords'] ?? '');
254      }
255      else {
256        $section[] = $this->buildPlaceholderList($label, $choice['data'] ?? [], $choice['keywords'] ?? '');
257      }
258    }
259
260    return $section;
261  }
262
263  /**
264   * Returns all possible sources.
265   *
266   * @throws \Drupal\Component\Plugin\Exception\PluginException
267   *
268   * @return array<string, array>
269   *   An array of sources.
270   */
271  private function getSources(): array {
272    if (!empty($this->sources)) {
273      return $this->sources;
274    }
275
276    $definitions = $this->sourceManager->getDefinitionsForPropType('slot', $this->configuration['contexts'] ?? []);
277    $slot_definition = ['ui_patterns' => ['type_definition' => $this->sourceManager->getSlotPropType()]];
278
279    foreach ($definitions as $source_id => $definition) {
280      // A block is a source for slots but without slots.
281      if (\is_a($definition['class'], SourceWithSlotsInterface::class, TRUE)) {
282        continue;
283      }
284
285      if (\in_array($source_id, self::HIDE_SOURCE, TRUE)) {
286        continue;
287      }
288
289      try {
290        $source = $this->sourceManager->createInstance(
291          $source_id,
292          SourcePluginBase::buildConfiguration('slot', $slot_definition, ['source' => []], $this->configuration['contexts'] ?? [])
293        );
294      }
295      catch (\Throwable $e) {
296        $this->logger->error('Invalid source found: %message', ['%message' => $e->getMessage()]);
297
298        continue;
299      }
300
301      $this->sources[$source_id] = [
302        'definition' => $definition,
303        'source' => $source,
304      ];
305
306      if ($source instanceof SourceWithChoicesInterface) {
307        $this->sources[$source_id]['choices'] = $source->getChoices();
308      }
309    }
310
311    return $this->sources;
312  }
313
314  /**
315   * Get providers options for select input.
316   *
317   * @return array
318   *   An associative array with module ID as key and module description as
319   *   value.
320   */
321  private function getProvidersOptions(): array {
322    $options = [];
323
324    foreach ($this->getProviders() as $provider_id => $provider) {
325      $params = [
326        '@name' => $provider['name'],
327        '@count' => $provider['count'],
328      ];
329      $options[$provider_id] = $this->formatPlural($provider['count'], '@name (@count block)', '@name (@count blocks)', $params);
330    }
331
332    return $options;
333  }
334
335  /**
336   * Get all providers.
337   *
338   * @return array
339   *   Drupal modules definitions, keyed by extension ID
340   */
341  private function getProviders(): array {
342    $sources = $this->getSources();
343    $providers = [];
344    $modules = $this->moduleList->getAllInstalledInfo();
345
346    foreach ($sources as $source_data) {
347      if (!isset($source_data['choices'])) {
348        continue;
349      }
350      $choices = $source_data['choices'];
351
352      foreach ($choices as $choice) {
353        $provider = $choice['provider'] ?? '';
354
355        if (!$provider || \in_array($provider, self::HIDE_PROVIDER, TRUE)) {
356          continue;
357        }
358
359        if (!isset($modules[$provider])) {
360          // If the provider is not a module, skip it.
361          continue;
362        }
363
364        if (!isset($providers[$provider])) {
365          $providers[$provider] = $modules[$provider];
366          $providers[$provider]['count'] = 0;
367        }
368        ++$providers[$provider]['count'];
369      }
370    }
371
372    return $providers;
373  }
374
375}

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
171  public function build(InstanceInterface $builder, array $data = [], array $options = []): array {
172    $builder_id = (string) $builder->id();
173    $configuration = $this->getConfiguration();
174
175    $exclude_providers = \array_merge(
176      $configuration['exclude'] ?? [],
177      self::HIDE_PROVIDER
178    );
179    $exclude_by_id = \preg_split('/\r\n|\r|\n/', \trim($configuration['exclude_id'] ?? '')) ?: [];
180
181    $build = [];
182
183    if ($configuration['show'] === 'grouped') {
184      $categories = BlockLibrarySourceHelper::getGroupedChoices(
185        $this->getSources(),
186        $exclude_providers,
187        $exclude_by_id,
188        (bool) $configuration['preview'],
189      );
190
191      foreach ($categories as $category_data) {
191      foreach ($categories as $category_data) {
191      foreach ($categories as $category_data) {
192        $build[] = $this->buildCategorySection($category_data, $builder_id, (bool) $configuration['preview']);
183    if ($configuration['show'] === 'grouped') {
184      $categories = BlockLibrarySourceHelper::getGroupedChoices(
185        $this->getSources(),
186        $exclude_providers,
187        $exclude_by_id,
188        (bool) $configuration['preview'],
189      );
190
191      foreach ($categories as $category_data) {
196      $choices = BlockLibrarySourceHelper::getChoices(
197        $this->getSources(),
198        $exclude_providers,
199        $exclude_by_id,
200        (bool) $configuration['preview'],
201      );
202
203      foreach ($choices as $choice) {
203      foreach ($choices as $choice) {
204        if ($configuration['preview']) {
204        if ($configuration['preview']) {
205          $build[] = $this->buildPlaceholderListWithPreview($builder_id, $choice['label'], $data, $choice['preview'], $choice['keywords']);
203      foreach ($choices as $choice) {
204        if ($configuration['preview']) {
205          $build[] = $this->buildPlaceholderListWithPreview($builder_id, $choice['label'], $data, $choice['preview'], $choice['keywords']);
206        }
207        else {
208          $build[] = $this->buildPlaceholderList($choice['label'], $data, $choice['keywords']);
203      foreach ($choices as $choice) {
203      foreach ($choices as $choice) {
204        if ($configuration['preview']) {
205          $build[] = $this->buildPlaceholderListWithPreview($builder_id, $choice['label'], $data, $choice['preview'], $choice['keywords']);
206        }
207        else {
208          $build[] = $this->buildPlaceholderList($choice['label'], $data, $choice['keywords']);
209        }
210      }
211    }
212
213    return [
214      '#type' => 'component',
214      '#type' => 'component',
215      '#component' => 'display_builder:library_panel',
216      '#slots' => [
217        'content' => $this->buildDraggables($builder_id, $build),
218      ],
219    ];
220  }
BlockLibraryPanel->buildCategorySection
235  private function buildCategorySection(array $category_data, string $builder_id, bool $preview): array {
236    $section = [];
237
238    if (!empty($category_data['label'])) {
240        '#type' => 'html_tag',
241        '#tag' => 'h4',
242        '#attributes' => [
243          'class' => ['db-placeholder__group', 'db-filter-hide-on-search'],
244        ],
245        '#value' => $category_data['label'],
246      ];
247    }
248
249    foreach ($category_data['choices'] as $choice) {
249    foreach ($category_data['choices'] as $choice) {
249    foreach ($category_data['choices'] as $choice) {
250      $label = $choice['label'] ?? '-';
251
252      if ($preview && $choice['preview']) {
252      if ($preview && $choice['preview']) {
252      if ($preview && $choice['preview']) {
252      if ($preview && $choice['preview']) {
253        $section[] = $this->buildPlaceholderListWithPreview($builder_id, $label, $choice['data'] ?? [], $choice['preview'], $choice['keywords'] ?? '');
249    foreach ($category_data['choices'] as $choice) {
250      $label = $choice['label'] ?? '-';
251
252      if ($preview && $choice['preview']) {
253        $section[] = $this->buildPlaceholderListWithPreview($builder_id, $label, $choice['data'] ?? [], $choice['preview'], $choice['keywords'] ?? '');
254      }
255      else {
256        $section[] = $this->buildPlaceholderList($label, $choice['data'] ?? [], $choice['keywords'] ?? '');
249    foreach ($category_data['choices'] as $choice) {
249    foreach ($category_data['choices'] as $choice) {
250      $label = $choice['label'] ?? '-';
251
252      if ($preview && $choice['preview']) {
253        $section[] = $this->buildPlaceholderListWithPreview($builder_id, $label, $choice['data'] ?? [], $choice['preview'], $choice['keywords'] ?? '');
254      }
255      else {
256        $section[] = $this->buildPlaceholderList($label, $choice['data'] ?? [], $choice['keywords'] ?? '');
257      }
258    }
259
260    return $section;
261  }
BlockLibraryPanel->buildConfigurationForm
85  public function buildConfigurationForm(array $form, FormStateInterface $form_state): array {
86    $configuration = $this->getConfiguration();
87
88    $form['display'] = [
89      '#type' => 'fieldset',
90      '#title' => $this->t('Display'),
91    ];
92
93    $form['display']['show'] = [
94      '#type' => 'select',
95      '#title' => $this->t('Display blocks as'),
96      '#default_value' => $configuration['show'],
97      '#options' => [
98        'grouped' => $this->t('grouped'),
99        'flat' => $this->t('flat list'),
100      ],
101    ];
102
103    $form['display']['preview'] = [
104      '#type' => 'checkbox',
105      '#title' => $this->t('Enable preview on hover'),
106      '#description' => $this->t('Enable or disable the preview of components when hovering over them.'),
107      '#default_value' => $configuration['preview'],
108    ];
109
110    $form['configuration'] = [
111      '#type' => 'fieldset',
112      '#title' => $this->t('Configuration'),
113    ];
114
115    $form['configuration']['exclude'] = [
116      '#type' => 'checkboxes',
117      '#title' => $this->t('Exclude from modules'),
118      '#description' => $this->t('Select the modules which blocks have to be excluded from the block library.'),
119      '#options' => $this->getProvidersOptions(),
120      '#default_value' => $configuration['exclude'],
121    ];
122
123    $form['configuration']['exclude_id'] = [
124      '#type' => 'textarea',
125      '#title' => $this->t('Exclude by id'),
126      '#description' => $this->t('Provide a list of id to exclude. One id by line, source id is used. Example: "help_block views_block:comments_recent-block_1 system_menu_block:account field:node:article:vid".'),
127      '#default_value' => $configuration['exclude_id'],
128    ];
129
130    return $form;
131  }
BlockLibraryPanel->configurationSummary
137    $configuration = $this->getConfiguration();
138
139    $summary[] = $this->t('Blocks displayed as: <em>@list</em>', [
140      '@list' => match ($configuration['show']) {
141        'flat' => $this->t('flat'),
141        'flat' => $this->t('flat'),
141        'flat' => $this->t('flat'),
142        default => $this->t('grouped'),
142        default => $this->t('grouped'),
143      },
144    ]);
145
146    if ($configuration['preview']) {
147      $summary[] = $this->t('Preview on hover enabled');
148    }
149
150    $summary[] = $this->t('Excluded modules: @exclude', [
150    $summary[] = $this->t('Excluded modules: @exclude', [
151      '@exclude' => \implode(', ', \array_filter($configuration['exclude'] ?? []) ?: [$this->t('None')]),
152    ]);
153
154    if (\strlen($configuration['exclude_id'] ?? '') > 5) {
155      $value = \preg_split('/\r\n|\r|\n/', \trim($configuration['exclude_id'] ?? ''));
156
157      if ($value === FALSE) {
157      if ($value === FALSE) {
158        $summary[] = $this->t('Block(s) excluded');
161        $summary[] = $this->formatPlural(\count($value), '@count block excluded', '@count blocks excluded by id');
162      }
163    }
164
165    return $summary;
165    return $summary;
166  }
BlockLibraryPanel->create
59  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
60    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
61    $instance->moduleList = $container->get('extension.list.module');
62
63    return $instance;
64  }
BlockLibraryPanel->defaultConfiguration
72        'devel',
73        'htmx',
74        'shortcut',
75      ],
76      'exclude_id' => '',
77      'show' => 'grouped',
78      'preview' => TRUE,
79    ];
80  }
BlockLibraryPanel->getProviders
342    $sources = $this->getSources();
343    $providers = [];
344    $modules = $this->moduleList->getAllInstalledInfo();
345
346    foreach ($sources as $source_data) {
346    foreach ($sources as $source_data) {
347      if (!isset($source_data['choices'])) {
348        continue;
350      $choices = $source_data['choices'];
351
352      foreach ($choices as $choice) {
352      foreach ($choices as $choice) {
353        $provider = $choice['provider'] ?? '';
354
355        if (!$provider || \in_array($provider, self::HIDE_PROVIDER, TRUE)) {
355        if (!$provider || \in_array($provider, self::HIDE_PROVIDER, TRUE)) {
355        if (!$provider || \in_array($provider, self::HIDE_PROVIDER, TRUE)) {
356          continue;
359        if (!isset($modules[$provider])) {
361          continue;
364        if (!isset($providers[$provider])) {
365          $providers[$provider] = $modules[$provider];
366          $providers[$provider]['count'] = 0;
367        }
368        ++$providers[$provider]['count'];
352      foreach ($choices as $choice) {
353        $provider = $choice['provider'] ?? '';
354
355        if (!$provider || \in_array($provider, self::HIDE_PROVIDER, TRUE)) {
356          continue;
357        }
358
359        if (!isset($modules[$provider])) {
360          // If the provider is not a module, skip it.
361          continue;
362        }
363
364        if (!isset($providers[$provider])) {
365          $providers[$provider] = $modules[$provider];
366          $providers[$provider]['count'] = 0;
367        }
368        ++$providers[$provider]['count'];
346    foreach ($sources as $source_data) {
347      if (!isset($source_data['choices'])) {
348        continue;
349      }
350      $choices = $source_data['choices'];
351
352      foreach ($choices as $choice) {
346    foreach ($sources as $source_data) {
347      if (!isset($source_data['choices'])) {
348        continue;
349      }
350      $choices = $source_data['choices'];
351
352      foreach ($choices as $choice) {
353        $provider = $choice['provider'] ?? '';
354
355        if (!$provider || \in_array($provider, self::HIDE_PROVIDER, TRUE)) {
356          continue;
357        }
358
359        if (!isset($modules[$provider])) {
360          // If the provider is not a module, skip it.
361          continue;
362        }
363
364        if (!isset($providers[$provider])) {
365          $providers[$provider] = $modules[$provider];
366          $providers[$provider]['count'] = 0;
367        }
368        ++$providers[$provider]['count'];
369      }
370    }
371
372    return $providers;
373  }
BlockLibraryPanel->getProvidersOptions
322    $options = [];
323
324    foreach ($this->getProviders() as $provider_id => $provider) {
324    foreach ($this->getProviders() as $provider_id => $provider) {
324    foreach ($this->getProviders() as $provider_id => $provider) {
324    foreach ($this->getProviders() as $provider_id => $provider) {
325      $params = [
326        '@name' => $provider['name'],
327        '@count' => $provider['count'],
328      ];
329      $options[$provider_id] = $this->formatPlural($provider['count'], '@name (@count block)', '@name (@count blocks)', $params);
330    }
331
332    return $options;
333  }
BlockLibraryPanel->getSources
272    if (!empty($this->sources)) {
273      return $this->sources;
276    $definitions = $this->sourceManager->getDefinitionsForPropType('slot', $this->configuration['contexts'] ?? []);
277    $slot_definition = ['ui_patterns' => ['type_definition' => $this->sourceManager->getSlotPropType()]];
278
279    foreach ($definitions as $source_id => $definition) {
279    foreach ($definitions as $source_id => $definition) {
279    foreach ($definitions as $source_id => $definition) {
280      // A block is a source for slots but without slots.
281      if (\is_a($definition['class'], SourceWithSlotsInterface::class, TRUE)) {
282        continue;
285      if (\in_array($source_id, self::HIDE_SOURCE, TRUE)) {
286        continue;
289      try {
290        $source = $this->sourceManager->createInstance(
295      catch (\Throwable $e) {
296        $this->logger->error('Invalid source found: %message', ['%message' => $e->getMessage()]);
297
298        continue;
302        'definition' => $definition,
303        'source' => $source,
304      ];
305
306      if ($source instanceof SourceWithChoicesInterface) {
279    foreach ($definitions as $source_id => $definition) {
280      // A block is a source for slots but without slots.
281      if (\is_a($definition['class'], SourceWithSlotsInterface::class, TRUE)) {
282        continue;
283      }
284
285      if (\in_array($source_id, self::HIDE_SOURCE, TRUE)) {
286        continue;
287      }
288
289      try {
290        $source = $this->sourceManager->createInstance(
291          $source_id,
292          SourcePluginBase::buildConfiguration('slot', $slot_definition, ['source' => []], $this->configuration['contexts'] ?? [])
293        );
294      }
295      catch (\Throwable $e) {
296        $this->logger->error('Invalid source found: %message', ['%message' => $e->getMessage()]);
297
298        continue;
299      }
300
301      $this->sources[$source_id] = [
302        'definition' => $definition,
303        'source' => $source,
304      ];
305
306      if ($source instanceof SourceWithChoicesInterface) {
307        $this->sources[$source_id]['choices'] = $source->getChoices();
279    foreach ($definitions as $source_id => $definition) {
279    foreach ($definitions as $source_id => $definition) {
280      // A block is a source for slots but without slots.
281      if (\is_a($definition['class'], SourceWithSlotsInterface::class, TRUE)) {
282        continue;
283      }
284
285      if (\in_array($source_id, self::HIDE_SOURCE, TRUE)) {
286        continue;
287      }
288
289      try {
290        $source = $this->sourceManager->createInstance(
291          $source_id,
292          SourcePluginBase::buildConfiguration('slot', $slot_definition, ['source' => []], $this->configuration['contexts'] ?? [])
293        );
294      }
295      catch (\Throwable $e) {
296        $this->logger->error('Invalid source found: %message', ['%message' => $e->getMessage()]);
297
298        continue;
299      }
300
301      $this->sources[$source_id] = [
302        'definition' => $definition,
303        'source' => $source,
304      ];
305
306      if ($source instanceof SourceWithChoicesInterface) {
307        $this->sources[$source_id]['choices'] = $source->getChoices();
308      }
309    }
310
311    return $this->sources;
312  }