Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
93.86% |
107 / 114 |
|
86.21% |
50 / 58 |
|
30.61% |
15 / 49 |
|
33.33% |
2 / 6 |
CRAP | |
0.00% |
0 / 1 |
| BlockLibrarySourceHelper | |
93.86% |
107 / 114 |
|
86.21% |
50 / 58 |
|
30.61% |
15 / 49 |
|
33.33% |
2 / 6 |
396.81 | |
0.00% |
0 / 1 |
| getGroupedChoices | |
93.33% |
14 / 15 |
|
87.50% |
7 / 8 |
|
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
4.00 | |||
| getChoices | |
100.00% |
33 / 33 |
|
100.00% |
11 / 11 |
|
28.57% |
2 / 7 |
|
100.00% |
1 / 1 |
14.11 | |||
| isChoiceValid | |
88.89% |
8 / 9 |
|
87.50% |
7 / 8 |
|
33.33% |
3 / 9 |
|
0.00% |
0 / 1 |
16.67 | |||
| getSourceGroupLabel | |
85.00% |
17 / 20 |
|
81.25% |
13 / 16 |
|
33.33% |
5 / 15 |
|
0.00% |
0 / 1 |
33.00 | |||
| getChoiceGroupLabel | |
87.50% |
14 / 16 |
|
78.57% |
11 / 14 |
|
36.36% |
4 / 11 |
|
0.00% |
0 / 1 |
19.63 | |||
| sortGroupedChoices | |
100.00% |
21 / 21 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Drupal\display_builder; |
| 6 | |
| 7 | use Drupal\Component\Render\MarkupInterface; |
| 8 | use Drupal\Core\StringTranslation\TranslatableMarkup; |
| 9 | use Drupal\Core\Url; |
| 10 | |
| 11 | /** |
| 12 | * Helper class for block library source handling. |
| 13 | */ |
| 14 | class 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 | } |
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.
| 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; |
| 222 | switch ($source_id) { |
| 223 | case 'block': |
| 230 | case 'entity_reference': |
| 235 | case 'entity_field': |
| 235 | case 'entity_field': |
| 224 | if (!empty($choice['group'])) { |
| 225 | $group = $choice['group']; |
| 226 | } |
| 227 | |
| 228 | break; |
| 228 | break; |
| 231 | $group = (string) new TranslatableMarkup('Referenced entities'); |
| 232 | |
| 233 | break; |
| 236 | $group = (string) new TranslatableMarkup('Fields'); |
| 237 | |
| 238 | break; |
| 241 | break; |
| 242 | } |
| 243 | |
| 244 | return $group; |
| 244 | return $group; |
| 71 | private static function getChoices(array $sources, array $exclude_provider): array { |
| 72 | $result_choices = []; |
| 73 | |
| 74 | foreach ($sources as $source_id => $source_data) { |
| 74 | foreach ($sources as $source_id => $source_data) { |
| 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; |
| 95 | $choices = $source_data['choices']; |
| 96 | |
| 97 | foreach ($choices as $choice_id => $choice) { |
| 97 | foreach ($choices as $choice_id => $choice) { |
| 97 | foreach ($choices as $choice_id => $choice) { |
| 98 | if (!self::isChoiceValid($choice, $definition, $exclude_provider)) { |
| 99 | continue; |
| 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; |
| 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) { |
| 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; |
| 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) { |
| 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])) { |
| 47 | if (!isset($categories[$category])) { |
| 49 | 'label' => $category, |
| 50 | 'choices' => [], |
| 51 | ]; |
| 52 | } |
| 53 | $categories[$category]['choices'][] = $choice; |
| 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; |
| 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; |
| 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; |
| 173 | switch ($provider) { |
| 174 | case 'display_builder_page_layout': |
| 179 | case 'display_builder_views': |
| 180 | case 'ui_patterns_views': |
| 185 | case 'display_builder_dev_tools': |
| 190 | case 'ui_icons_patterns': |
| 191 | case 'ui_patterns': |
| 191 | case 'ui_patterns': |
| 175 | $group = (string) new TranslatableMarkup('Page'); |
| 176 | |
| 177 | break; |
| 181 | $group = (string) new TranslatableMarkup('Views'); |
| 182 | |
| 183 | break; |
| 186 | $group = (string) new TranslatableMarkup('Dev tools'); |
| 187 | |
| 188 | break; |
| 192 | $group = (string) new TranslatableMarkup('Utilities'); |
| 193 | |
| 194 | break; |
| 197 | break; |
| 198 | } |
| 199 | |
| 200 | return $group; |
| 200 | return $group; |
| 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; |
| 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)) { |
| 147 | if ($block_id && \in_array($block_id, self::HIDE_BLOCK, TRUE)) { |
| 148 | return FALSE; |
| 152 | return TRUE; |
| 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 | } |
| 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']); |
| 281 | return $weight_a <=> $weight_b; |