Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
93.86% covered (success)
93.86%
107 / 114
86.21% covered (warning)
86.21%
50 / 58
30.61% covered (danger)
30.61%
15 / 49
33.33% covered (danger)
33.33%
2 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
BlockLibrarySourceHelper
93.86% covered (success)
93.86%
107 / 114
86.21% covered (warning)
86.21%
50 / 58
30.61% covered (danger)
30.61%
15 / 49
33.33% covered (danger)
33.33%
2 / 6
396.81
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
 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
87.50% covered (warning)
87.50%
7 / 8
33.33% covered (danger)
33.33%
3 / 9
0.00% covered (danger)
0.00%
0 / 1
16.67
 getSourceGroupLabel
85.00% covered (warning)
85.00%
17 / 20
81.25% covered (warning)
81.25%
13 / 16
33.33% covered (danger)
33.33%
5 / 15
0.00% covered (danger)
0.00%
0 / 1
33.00
 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
 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
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   * Get the choices from all sources.
62   *
63   * @param array $sources
64   *   An array of all possible sources.
65   * @param array $exclude_provider
66   *   An array of providers to hide.
67   *
68   * @return array
69   *   An array of choices.
70   */
71  private static function getChoices(array $sources, array $exclude_provider): array {
72    $result_choices = [];
73
74    foreach ($sources as $source_id => $source_data) {
75      $definition = $source_data['definition'];
76      $source = $source_data['source'];
77
78      // If no choices, add the source as a single choice.
79      if (!isset($source_data['choices'])) {
80        $label = $definition['label'] ?? $source_id;
81        $keywords = \sprintf('%s %s %s', $definition['id'], $definition['label'] ?? $source_id, $definition['description'] ?? '');
82
83        $result_choices[] = [
84          'label' => (string) $label,
85          'data' => ['source_id' => $source_id],
86          'keywords' => $keywords,
87          'preview' => FALSE,
88          'group' => self::getSourceGroupLabel($definition),
89        ];
90
91        continue;
92      }
93
94      // Mostly for block source, iterate over choices.
95      $choices = $source_data['choices'];
96
97      foreach ($choices as $choice_id => $choice) {
98        if (!self::isChoiceValid($choice, $definition, $exclude_provider)) {
99          continue;
100        }
101
102        $label = $choice['label'] ?? $choice_id;
103        $keywords = \sprintf('%s %s %s %s', $definition['id'], $label, $definition['description'] ?? '', $choice_id);
104        $preview_url = Url::fromRoute('display_builder.api_block_preview', ['block_id' => $choice_id]);
105
106        $result_choices[] = [
107          'label' => (string) $label,
108          'keywords' => $keywords,
109          'data' => [
110            'source_id' => $source_id,
111            'source' => $source->getChoiceSettings($choice_id),
112          ],
113          'preview' => $preview_url,
114          'group' => self::getChoiceGroupLabel($choice, $definition),
115        ];
116      }
117    }
118
119    return $result_choices;
120  }
121
122  /**
123   * Validate a choice against the source definition and allowed providers.
124   *
125   * @param array $choice
126   *   The choice to validate.
127   * @param array $source_definition
128   *   The source definition.
129   * @param array $exclude_provider
130   *   The excluded providers.
131   *
132   * @return bool
133   *   Whether the choice is valid or not.
134   */
135  private static function isChoiceValid(array &$choice, array &$source_definition, array $exclude_provider = []): bool {
136    $provider = $choice['provider'] ?? '';
137
138    if ($provider) {
139      if (\in_array($provider, $exclude_provider, TRUE)) {
140        return FALSE;
141      }
142    }
143
144    if ($source_definition['id'] === 'block') {
145      $block_id = $choice['original_id'] ?? '';
146
147      if ($block_id && \in_array($block_id, self::HIDE_BLOCK, TRUE)) {
148        return FALSE;
149      }
150    }
151
152    return TRUE;
153  }
154
155  /**
156   * Get the group label for a source.
157   *
158   * @param array $source_definition
159   *   The source definition to use for the group.
160   *
161   * @return string
162   *   The group label for the choice.
163   */
164  private static function getSourceGroupLabel(array $source_definition): string {
165    $group = (string) new TranslatableMarkup('Others');
166
167    $provider = $source_definition['provider'] ?? NULL;
168
169    if (!$provider) {
170      return $group;
171    }
172
173    switch ($provider) {
174      case 'display_builder_page_layout':
175        $group = (string) new TranslatableMarkup('Page');
176
177        break;
178
179      case 'display_builder_views':
180      case 'ui_patterns_views':
181        $group = (string) new TranslatableMarkup('Views');
182
183        break;
184
185      case 'display_builder_dev_tools':
186        $group = (string) new TranslatableMarkup('Dev tools');
187
188        break;
189
190      case 'ui_icons_patterns':
191      case 'ui_patterns':
192        $group = (string) new TranslatableMarkup('Utilities');
193
194        break;
195
196      default:
197        break;
198    }
199
200    return $group;
201  }
202
203  /**
204   * Get the group label for a choice.
205   *
206   * @param array $choice
207   *   The choice to get the group for.
208   * @param array $source_definition
209   *   The source definition to use for the group.
210   *
211   * @return string
212   *   The group label for the choice.
213   */
214  private static function getChoiceGroupLabel(array $choice, array $source_definition): string {
215    $group = (string) new TranslatableMarkup('Others');
216    $source_id = $source_definition['id'] ?? NULL;
217
218    if (!$source_id) {
219      return $group;
220    }
221
222    switch ($source_id) {
223      case 'block':
224        if (!empty($choice['group'])) {
225          $group = $choice['group'];
226        }
227
228        break;
229
230      case 'entity_reference':
231        $group = (string) new TranslatableMarkup('Referenced entities');
232
233        break;
234
235      case 'entity_field':
236        $group = (string) new TranslatableMarkup('Fields');
237
238        break;
239
240      default:
241        break;
242    }
243
244    return $group;
245  }
246
247  /**
248   * Sorts the grouped choices based on arbitrary weight.
249   *
250   * @param array $categories
251   *   The categories to sort, passed by reference.
252   */
253  private static function sortGroupedChoices(array &$categories): void {
254    $category_weight = [
255      // Different builder contexts.
256      (string) new TranslatableMarkup('Page') => 1,
257      (string) new TranslatableMarkup('Views') => 1,
258      (string) new TranslatableMarkup('Fields') => 1,
259      (string) new TranslatableMarkup('Referenced entities') => 2,
260      // Global categories.
261      (string) new TranslatableMarkup('Menus') => 3,
262      (string) new TranslatableMarkup('System') => 3,
263      (string) new TranslatableMarkup('User') => 3,
264      (string) new TranslatableMarkup('Utilities') => 3,
265      (string) new TranslatableMarkup('Forms') => 4,
266      (string) new TranslatableMarkup('Lists (Views)') => 5,
267      (string) new TranslatableMarkup('Others') => 6,
268      (string) new TranslatableMarkup('Dev tools') => 99,
269    ];
270
271    // Sort categories by predefined weight, then by natural string comparison
272    // of labels.
273    \uasort($categories, static function ($a, $b) use ($category_weight) {
274      $weight_a = $category_weight[$a['label']] ?? 98;
275      $weight_b = $category_weight[$b['label']] ?? 98;
276
277      if ($weight_a === $weight_b) {
278        return \strnatcmp($a['label'], $b['label']);
279      }
280
281      return $weight_a <=> $weight_b;
282    });
283  }
284
285}