Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
93.10% covered (success)
93.10%
108 / 116
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.10% covered (success)
93.10%
108 / 116
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%
33 / 33
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    return $result_choices;
159  }
160
161  /**
162   * Validate a choice against the source definition and allowed providers.
163   *
164   * @param array $choice
165   *   The choice to validate.
166   * @param array $source_definition
167   *   The source definition.
168   * @param array $exclude_provider
169   *   The excluded providers.
170   *
171   * @return bool
172   *   Whether the choice is valid or not.
173   */
174  private static function isChoiceValid(array &$choice, array &$source_definition, array $exclude_provider = []): bool {
175    $provider = $choice['provider'] ?? '';
176
177    if ($provider) {
178      if (\in_array($provider, $exclude_provider, TRUE)) {
179        return FALSE;
180      }
181    }
182
183    if ($source_definition['id'] === 'block') {
184      $block_id = $choice['original_id'] ?? '';
185
186      if ($block_id && \in_array($block_id, self::HIDE_BLOCK, TRUE)) {
187        return FALSE;
188      }
189    }
190
191    return TRUE;
192  }
193
194  /**
195   * Get the group label for a source.
196   *
197   * @param array $source_definition
198   *   The source definition to use for the group.
199   *
200   * @return string
201   *   The group label for the choice.
202   */
203  private static function getSourceGroupLabel(array $source_definition): string {
204    $group = (string) new TranslatableMarkup('Others');
205
206    $provider = $source_definition['provider'] ?? NULL;
207
208    if (!$provider) {
209      return $group;
210    }
211
212    if (isset($source_definition['metadata']['group'])) {
213      return (string) $source_definition['metadata']['group'];
214    }
215
216    switch ($provider) {
217      case 'display_builder_page_layout':
218        $group = (string) new TranslatableMarkup('Page');
219
220        break;
221
222      case 'display_builder_views':
223      case 'ui_patterns_views':
224        $group = (string) new TranslatableMarkup('Views');
225
226        break;
227
228      case 'display_builder_dev_tools':
229        $group = (string) new TranslatableMarkup('Dev tools');
230
231        break;
232
233      case 'ui_icons_patterns':
234      case 'ui_patterns':
235        $group = (string) new TranslatableMarkup('Utilities');
236
237        break;
238
239      default:
240        break;
241    }
242
243    return $group;
244  }
245
246  /**
247   * Get the group label for a choice.
248   *
249   * @param array $choice
250   *   The choice to get the group for.
251   * @param array $source_definition
252   *   The source definition to use for the group.
253   *
254   * @return string
255   *   The group label for the choice.
256   */
257  private static function getChoiceGroupLabel(array $choice, array $source_definition): string {
258    $group = (string) new TranslatableMarkup('Others');
259    $source_id = $source_definition['id'] ?? NULL;
260
261    if (!$source_id) {
262      return $group;
263    }
264
265    switch ($source_id) {
266      case 'block':
267        if (!empty($choice['group'])) {
268          $group = $choice['group'];
269        }
270
271        break;
272
273      case 'entity_reference':
274        $group = (string) new TranslatableMarkup('Referenced entities');
275
276        break;
277
278      case 'entity_field':
279        $group = (string) new TranslatableMarkup('Fields');
280
281        break;
282
283      default:
284        break;
285    }
286
287    return $group;
288  }
289
290}