Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
93.28% covered (success)
93.28%
111 / 119
85.25% covered (warning)
85.25%
52 / 61
28.85% covered (danger)
28.85%
15 / 52
33.33% covered (danger)
33.33%
2 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
BlockLibrarySourceHelper
93.28% covered (success)
93.28%
111 / 119
85.25% covered (warning)
85.25%
52 / 61
28.85% covered (danger)
28.85%
15 / 52
33.33% covered (danger)
33.33%
2 / 6
450.44
0.00% covered (danger)
0.00%
0 / 1
 getGroupedChoices
93.33% covered (success)
93.33%
14 / 15
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
4.00
 sortGroupedChoices
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getChoices
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
11 / 11
28.57% covered (danger)
28.57%
2 / 7
100.00% covered (success)
100.00%
1 / 1
14.11
 isChoiceValid
88.89% covered (warning)
88.89%
8 / 9
88.89% covered (warning)
88.89%
8 / 9
27.27% covered (danger)
27.27%
3 / 11
0.00% covered (danger)
0.00%
0 / 1
19.85
 getSourceGroupLabel
81.82% covered (warning)
81.82%
18 / 22
77.78% covered (warning)
77.78%
14 / 18
31.25% covered (danger)
31.25%
5 / 16
0.00% covered (danger)
0.00%
0 / 1
42.50
 getChoiceGroupLabel
87.50% covered (warning)
87.50%
14 / 16
78.57% covered (warning)
78.57%
11 / 14
36.36% covered (danger)
36.36%
4 / 11
0.00% covered (danger)
0.00%
0 / 1
19.63
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\display_builder;
6
7use Drupal\Component\Render\MarkupInterface;
8use Drupal\Core\StringTranslation\TranslatableMarkup;
9use Drupal\Core\Url;
10
11/**
12 * Helper class for block library source handling.
13 */
14class BlockLibrarySourceHelper {
15
16  private const HIDE_BLOCK = [
17    'htmx_loader',
18    'broken',
19    'system_main_block',
20    'page_title_block',
21  ];
22
23  /**
24   * Get the choices grouped by category.
25   *
26   * @param array $sources
27   *   An array of all possible sources.
28   * @param array $exclude_provider
29   *   (Optional) An array of providers to hide.
30   *
31   * @return array
32   *   An array of grouped choices.
33   */
34  public static function getGroupedChoices(array $sources, array $exclude_provider = []): array {
35    $choices = self::getChoices($sources, $exclude_provider);
36    $default_category = (string) new TranslatableMarkup('Others');
37
38    $categories = [];
39
40    foreach ($choices as $choice) {
41      $category = $choice['group'] ?? $default_category;
42
43      if ($category instanceof MarkupInterface) {
44        $category = (string) $category;
45      }
46
47      if (!isset($categories[$category])) {
48        $categories[$category] = [
49          'label' => $category,
50          'choices' => [],
51        ];
52      }
53      $categories[$category]['choices'][] = $choice;
54    }
55    self::sortGroupedChoices($categories);
56
57    return $categories;
58  }
59
60  /**
61   * Sorts the grouped choices based on arbitrary weight.
62   *
63   * @param array $categories
64   *   The categories to sort, passed by reference.
65   */
66  public static function sortGroupedChoices(array &$categories): void {
67    // Public for reuse by PresetLibraryPanel.
68    $category_weight = [
69      // Different builder contexts.
70      (string) new TranslatableMarkup('Page') => 1,
71      (string) new TranslatableMarkup('Views') => 1,
72      (string) new TranslatableMarkup('Fields') => 1,
73      (string) new TranslatableMarkup('Referenced entities') => 2,
74      // Global categories.
75      (string) new TranslatableMarkup('Menus') => 3,
76      (string) new TranslatableMarkup('System') => 3,
77      (string) new TranslatableMarkup('User') => 3,
78      (string) new TranslatableMarkup('Utilities') => 3,
79      (string) new TranslatableMarkup('Forms') => 4,
80      (string) new TranslatableMarkup('Lists (Views)') => 5,
81      (string) new TranslatableMarkup('Others') => 6,
82      (string) new TranslatableMarkup('Dev tools') => 99,
83    ];
84
85    // Sort categories by predefined weight, then by natural string comparison
86    // of labels.
87    \uasort($categories, static function ($a, $b) use ($category_weight) {
88      $weight_a = $category_weight[$a['label']] ?? 98;
89      $weight_b = $category_weight[$b['label']] ?? 98;
90
91      if ($weight_a === $weight_b) {
92        return \strnatcmp($a['label'], $b['label']);
93      }
94
95      return $weight_a <=> $weight_b;
96    });
97  }
98
99  /**
100   * Get the choices from all sources.
101   *
102   * @param array $sources
103   *   An array of all possible sources.
104   * @param array $exclude_provider
105   *   An array of providers to hide.
106   *
107   * @return array
108   *   An array of choices.
109   */
110  private static function getChoices(array $sources, array $exclude_provider): array {
111    $result_choices = [];
112
113    foreach ($sources as $source_id => $source_data) {
114      $definition = $source_data['definition'];
115      $source = $source_data['source'];
116
117      // If no choices, add the source as a single choice.
118      if (!isset($source_data['choices'])) {
119        $label = $definition['label'] ?? $source_id;
120        $keywords = \sprintf('%s %s %s', $definition['id'], $definition['label'] ?? $source_id, $definition['description'] ?? '');
121
122        $result_choices[] = [
123          'label' => (string) $label,
124          'data' => ['source_id' => $source_id],
125          'keywords' => $keywords,
126          'preview' => FALSE,
127          'group' => self::getSourceGroupLabel($definition),
128        ];
129
130        continue;
131      }
132
133      // Mostly for block source, iterate over choices.
134      $choices = $source_data['choices'];
135
136      foreach ($choices as $choice_id => $choice) {
137        if (!self::isChoiceValid($choice, $definition, $exclude_provider)) {
138          continue;
139        }
140
141        $label = $choice['label'] ?? $choice_id;
142        $keywords = \sprintf('%s %s %s %s', $definition['id'], $label, $definition['description'] ?? '', $choice_id);
143        $preview_url = Url::fromRoute('display_builder.api_block_preview', ['block_id' => $choice_id]);
144
145        $result_choices[] = [
146          'label' => (string) $label,
147          'keywords' => $keywords,
148          'data' => [
149            'source_id' => $source_id,
150            'source' => $source->getChoiceSettings($choice_id),
151          ],
152          'preview' => $preview_url,
153          'group' => self::getChoiceGroupLabel($choice, $definition),
154        ];
155      }
156    }
157
158    // Sort alphabetically within groups by label.
159    \usort($result_choices, static function (array $a, array $b): int {
160      return \strnatcasecmp((string) ($a['label']), (string) ($b['label']));
161    });
162
163    return $result_choices;
164  }
165
166  /**
167   * Validate a choice against the source definition and allowed providers.
168   *
169   * @param array $choice
170   *   The choice to validate.
171   * @param array $source_definition
172   *   The source definition.
173   * @param array $exclude_provider
174   *   The excluded providers.
175   *
176   * @return bool
177   *   Whether the choice is valid or not.
178   */
179  private static function isChoiceValid(array &$choice, array &$source_definition, array $exclude_provider = []): bool {
180    $provider = $choice['provider'] ?? '';
181
182    if ($provider) {
183      if (\in_array($provider, $exclude_provider, TRUE)) {
184        return FALSE;
185      }
186    }
187
188    if ($source_definition['id'] === 'block') {
189      $block_id = $choice['original_id'] ?? '';
190
191      if ($block_id && \in_array($block_id, self::HIDE_BLOCK, TRUE)) {
192        return FALSE;
193      }
194    }
195
196    return TRUE;
197  }
198
199  /**
200   * Get the group label for a source.
201   *
202   * @param array $source_definition
203   *   The source definition to use for the group.
204   *
205   * @return string
206   *   The group label for the choice.
207   */
208  private static function getSourceGroupLabel(array $source_definition): string {
209    $group = (string) new TranslatableMarkup('Others');
210
211    $provider = $source_definition['provider'] ?? NULL;
212
213    if (!$provider) {
214      return $group;
215    }
216
217    if (isset($source_definition['metadata']['group'])) {
218      return (string) $source_definition['metadata']['group'];
219    }
220
221    switch ($provider) {
222      case 'display_builder_page_layout':
223        $group = (string) new TranslatableMarkup('Page');
224
225        break;
226
227      case 'display_builder_views':
228      case 'ui_patterns_views':
229        $group = (string) new TranslatableMarkup('Views');
230
231        break;
232
233      case 'display_builder_dev_tools':
234        $group = (string) new TranslatableMarkup('Dev tools');
235
236        break;
237
238      case 'ui_icons_patterns':
239      case 'ui_patterns':
240        $group = (string) new TranslatableMarkup('Utilities');
241
242        break;
243
244      default:
245        break;
246    }
247
248    return $group;
249  }
250
251  /**
252   * Get the group label for a choice.
253   *
254   * @param array $choice
255   *   The choice to get the group for.
256   * @param array $source_definition
257   *   The source definition to use for the group.
258   *
259   * @return string
260   *   The group label for the choice.
261   */
262  private static function getChoiceGroupLabel(array $choice, array $source_definition): string {
263    $group = (string) new TranslatableMarkup('Others');
264    $source_id = $source_definition['id'] ?? NULL;
265
266    if (!$source_id) {
267      return $group;
268    }
269
270    switch ($source_id) {
271      case 'block':
272        if (!empty($choice['group'])) {
273          $group = $choice['group'];
274        }
275
276        break;
277
278      case 'entity_reference':
279        $group = (string) new TranslatableMarkup('Referenced entities');
280
281        break;
282
283      case 'entity_field':
284        $group = (string) new TranslatableMarkup('Fields');
285
286        break;
287
288      default:
289        break;
290    }
291
292    return $group;
293  }
294
295}