Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
0.00% |
0 / 242 |
|
0.00% |
0 / 72 |
|
0.00% |
0 / 246 |
|
0.00% |
0 / 12 |
CRAP | |
0.00% |
0 / 1 |
| ComponentLibraryPanel | |
0.00% |
0 / 236 |
|
0.00% |
0 / 72 |
|
0.00% |
0 / 246 |
|
0.00% |
0 / 12 |
1482 | |
0.00% |
0 / 1 |
| create | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| label | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| defaultConfiguration | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| buildConfigurationForm | |
0.00% |
0 / 50 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| validateConfigurationForm | |
0.00% |
0 / 8 |
|
0.00% |
0 / 5 |
|
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
20 | |||
| configurationSummary | |
0.00% |
0 / 26 |
|
0.00% |
0 / 20 |
|
0.00% |
0 / 192 |
|
0.00% |
0 / 1 |
110 | |||
| build | |
0.00% |
0 / 40 |
|
0.00% |
0 / 10 |
|
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
30 | |||
| getProviders | |
0.00% |
0 / 11 |
|
0.00% |
0 / 9 |
|
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
20 | |||
| getComponentsGrouped | |
0.00% |
0 / 20 |
|
0.00% |
0 / 7 |
|
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
| getComponentsVariants | |
0.00% |
0 / 37 |
|
0.00% |
0 / 9 |
|
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
20 | |||
| getComponentsMosaic | |
0.00% |
0 / 15 |
|
0.00% |
0 / 4 |
|
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
| getProvidersOptions | |
0.00% |
0 / 11 |
|
0.00% |
0 / 4 |
|
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Drupal\display_builder\Plugin\display_builder\Island; |
| 6 | |
| 7 | use Drupal\Core\Extension\ModuleExtensionList; |
| 8 | use Drupal\Core\Extension\ThemeExtensionList; |
| 9 | use Drupal\Core\Form\FormStateInterface; |
| 10 | use Drupal\Core\StringTranslation\TranslatableMarkup; |
| 11 | use Drupal\Core\Theme\ThemeManagerInterface; |
| 12 | use Drupal\Core\Url; |
| 13 | use Drupal\display_builder\Attribute\Island; |
| 14 | use Drupal\display_builder\ComponentLibraryDefinitionHelper; |
| 15 | use Drupal\display_builder\InstanceInterface; |
| 16 | use Drupal\display_builder\IslandConfigurationFormInterface; |
| 17 | use Drupal\display_builder\IslandConfigurationFormTrait; |
| 18 | use Drupal\display_builder\IslandPluginBase; |
| 19 | use Drupal\display_builder\IslandType; |
| 20 | use Drupal\ui_patterns\SourcePluginManager; |
| 21 | use Symfony\Component\DependencyInjection\ContainerInterface; |
| 22 | |
| 23 | /** |
| 24 | * Component library island plugin implementation. |
| 25 | */ |
| 26 | #[Island( |
| 27 | id: 'component_library', |
| 28 | enabled_by_default: TRUE, |
| 29 | label: new TranslatableMarkup('Components library'), |
| 30 | description: new TranslatableMarkup('List of available components.'), |
| 31 | type: IslandType::Library, |
| 32 | )] |
| 33 | class ComponentLibraryPanel extends IslandPluginBase implements IslandConfigurationFormInterface { |
| 34 | |
| 35 | use IslandConfigurationFormTrait; |
| 36 | |
| 37 | /** |
| 38 | * The module list extension service. |
| 39 | */ |
| 40 | protected ThemeManagerInterface $themeManager; |
| 41 | |
| 42 | /** |
| 43 | * The module list extension service. |
| 44 | */ |
| 45 | protected ThemeExtensionList $themeList; |
| 46 | |
| 47 | /** |
| 48 | * The module list extension service. |
| 49 | */ |
| 50 | protected ModuleExtensionList $moduleList; |
| 51 | |
| 52 | /** |
| 53 | * The UI Patterns source plugin manager. |
| 54 | */ |
| 55 | protected SourcePluginManager $sourceManager; |
| 56 | |
| 57 | /** |
| 58 | * The definitions filtered for current theme. |
| 59 | * |
| 60 | * @var array |
| 61 | * The definitions filtered. |
| 62 | */ |
| 63 | private array $definitionsFiltered = []; |
| 64 | |
| 65 | /** |
| 66 | * The definitions filtered and grouped for current theme. |
| 67 | * |
| 68 | * @var array |
| 69 | * The definitions filtered and grouped. |
| 70 | */ |
| 71 | private array $definitionsGrouped = []; |
| 72 | |
| 73 | /** |
| 74 | * The source data for components. |
| 75 | * |
| 76 | * @var array |
| 77 | * The source data already prepared. |
| 78 | */ |
| 79 | private array $sourcesData = []; |
| 80 | |
| 81 | /** |
| 82 | * {@inheritdoc} |
| 83 | */ |
| 84 | public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static { |
| 85 | $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); |
| 86 | $instance->themeManager = $container->get('theme.manager'); |
| 87 | $instance->themeList = $container->get('extension.list.theme'); |
| 88 | $instance->moduleList = $container->get('extension.list.module'); |
| 89 | $instance->sourceManager = $container->get('plugin.manager.ui_patterns_source'); |
| 90 | |
| 91 | return $instance; |
| 92 | } |
| 93 | |
| 94 | /** |
| 95 | * {@inheritdoc} |
| 96 | */ |
| 97 | public function label(): string { |
| 98 | return 'Components'; |
| 99 | } |
| 100 | |
| 101 | /** |
| 102 | * {@inheritdoc} |
| 103 | */ |
| 104 | public function defaultConfiguration(): array { |
| 105 | return [ |
| 106 | 'exclude' => [], |
| 107 | 'exclude_id' => '', |
| 108 | 'component_status' => [ |
| 109 | 'experimental', |
| 110 | ], |
| 111 | 'include_no_ui' => FALSE, |
| 112 | 'show_grouped' => TRUE, |
| 113 | 'show_variants' => TRUE, |
| 114 | 'show_mosaic' => TRUE, |
| 115 | ]; |
| 116 | } |
| 117 | |
| 118 | /** |
| 119 | * {@inheritdoc} |
| 120 | */ |
| 121 | public function buildConfigurationForm(array $form, FormStateInterface $form_state): array { |
| 122 | $configuration = $this->getConfiguration(); |
| 123 | $components = $this->sdcManager->getDefinitions(); |
| 124 | |
| 125 | $form['exclude'] = [ |
| 126 | '#type' => 'checkboxes', |
| 127 | '#title' => $this->t('Exclude providers'), |
| 128 | '#options' => $this->getProvidersOptions($components, $this->t('component'), $this->t('components')), |
| 129 | '#default_value' => $configuration['exclude'], |
| 130 | ]; |
| 131 | |
| 132 | $form['exclude_id'] = [ |
| 133 | '#type' => 'textarea', |
| 134 | '#title' => $this->t('Exclude by id'), |
| 135 | '#description' => $this->t('Provide a space separated list of components id to exclude, must be prefixed by provider. Example: "ui_suite_bootstrap:card_body<br>ui_suite_bootstrap:table_cell".'), |
| 136 | '#default_value' => $configuration['exclude_id'], |
| 137 | ]; |
| 138 | |
| 139 | // @see https://git.drupalcode.org/project/drupal/-/blob/11.x/core/assets/schemas/v1/metadata.schema.json#L217 |
| 140 | $form['component_status'] = [ |
| 141 | '#type' => 'checkboxes', |
| 142 | '#title' => $this->t('Allowed status'), |
| 143 | '#options' => [ |
| 144 | 'experimental' => $this->t('Experimental'), |
| 145 | 'deprecated' => $this->t('Deprecated'), |
| 146 | 'obsolete' => $this->t('Obsolete'), |
| 147 | ], |
| 148 | '#description' => $this->t('Components with stable or undefined status will always be available.'), |
| 149 | '#default_value' => $configuration['component_status'], |
| 150 | ]; |
| 151 | |
| 152 | $form['show_grouped'] = [ |
| 153 | '#type' => 'checkbox', |
| 154 | '#title' => $this->t('Show components grouped'), |
| 155 | '#description' => $this->t('Provide a list of grouped components for selection.'), |
| 156 | '#default_value' => $configuration['show_grouped'], |
| 157 | ]; |
| 158 | |
| 159 | $form['show_variants'] = [ |
| 160 | '#type' => 'checkbox', |
| 161 | '#title' => $this->t('Show components variants'), |
| 162 | '#description' => $this->t('Provide a list of components per variants for selection.'), |
| 163 | '#default_value' => $configuration['show_variants'], |
| 164 | ]; |
| 165 | |
| 166 | $form['show_mosaic'] = [ |
| 167 | '#type' => 'checkbox', |
| 168 | '#title' => $this->t('Show components mosaic'), |
| 169 | '#description' => $this->t('Provide a list of mosaic components for selection.'), |
| 170 | '#default_value' => $configuration['show_mosaic'], |
| 171 | ]; |
| 172 | |
| 173 | // Drupal 11.3+ new exclude feature. |
| 174 | // @see https://git.drupalcode.org/project/drupal/-/blob/11.x/core/assets/schemas/v1/metadata.schema.json#L228 |
| 175 | $form['include_no_ui'] = [ |
| 176 | '#type' => 'checkbox', |
| 177 | '#title' => $this->t('Include marked as excluded from the UI'), |
| 178 | '#description' => $this->t('Components with no ui flag are meant for internal use only. Force to include them. Drupal 11.3+ only.'), |
| 179 | '#default_value' => $configuration['include_no_ui'], |
| 180 | ]; |
| 181 | |
| 182 | return $form; |
| 183 | } |
| 184 | |
| 185 | /** |
| 186 | * {@inheritdoc} |
| 187 | */ |
| 188 | public function validateConfigurationForm(array &$form, FormStateInterface $form_state): void { |
| 189 | $values = $form_state->getValues(); |
| 190 | |
| 191 | // At least one display must be enabled. |
| 192 | $show_grouped = (bool) $values['show_grouped']; |
| 193 | $show_variants = (bool) $values['show_variants']; |
| 194 | $show_mosaic = (bool) $values['show_mosaic']; |
| 195 | |
| 196 | if (!$show_grouped && !$show_variants && !$show_mosaic) { |
| 197 | $form_state->setError($form['show_grouped'], $this->t('At least one display must be selected!')); |
| 198 | $form_state->setError($form['show_variants'], $this->t('At least one display must be selected!')); |
| 199 | $form_state->setError($form['show_mosaic'], $this->t('At least one display must be selected!')); |
| 200 | } |
| 201 | } |
| 202 | |
| 203 | /** |
| 204 | * {@inheritdoc} |
| 205 | */ |
| 206 | public function configurationSummary(): array { |
| 207 | $configuration = $this->getConfiguration(); |
| 208 | |
| 209 | $summary = []; |
| 210 | |
| 211 | $summary[] = $this->t('Excluded providers: @exclude', [ |
| 212 | '@exclude' => ($exclude = \array_filter($configuration['exclude'] ?? [])) ? \implode(', ', $exclude) : $this->t('None'), |
| 213 | ]); |
| 214 | |
| 215 | if (\strlen($configuration['exclude_id'] ?? '') > 5) { |
| 216 | $value = \preg_split('/\s+/', \trim($configuration['exclude_id'] ?? '')); |
| 217 | |
| 218 | if ($value === FALSE) { |
| 219 | $summary[] = $this->t('Component(s) excluded'); |
| 220 | } |
| 221 | else { |
| 222 | $num = \count($value); |
| 223 | $summary[] = $this->formatPlural($num, '@count component excluded', '@count components excluded'); |
| 224 | } |
| 225 | } |
| 226 | |
| 227 | $summary[] = $this->t('Allowed status: @status', [ |
| 228 | '@status' => \implode(', ', \array_filter(\array_unique(\array_merge(['stable', 'undefined'], $configuration['component_status'] ?? []))) ?: [$this->t('stable, undefined')]), |
| 229 | ]); |
| 230 | |
| 231 | $summary[] = $configuration['include_no_ui'] ? $this->t('Include no UI components') : $this->t('Exclude no UI components'); |
| 232 | |
| 233 | $list = []; |
| 234 | |
| 235 | if ((bool) $configuration['show_grouped']) { |
| 236 | $list[] = $this->t('grouped'); |
| 237 | } |
| 238 | |
| 239 | if ((bool) $configuration['show_variants']) { |
| 240 | $list[] = $this->t('variants'); |
| 241 | } |
| 242 | |
| 243 | if ((bool) $configuration['show_mosaic']) { |
| 244 | $list[] = $this->t('mosaic'); |
| 245 | } |
| 246 | $summary[] = $this->t('Components list as: @list', [ |
| 247 | '@list' => !empty($list) ? \implode(', ', $list) : $this->t('None selected'), |
| 248 | ]); |
| 249 | |
| 250 | return $summary; |
| 251 | } |
| 252 | |
| 253 | /** |
| 254 | * {@inheritdoc} |
| 255 | */ |
| 256 | public function build(InstanceInterface $builder, array $data = [], array $options = []): array { |
| 257 | $builder_id = (string) $builder->id(); |
| 258 | // Run a single time and saved as properties to avoid repeating processing |
| 259 | // in ::getComponentsMosaic(), ::getComponentsVariants() and |
| 260 | // ::getComponentsGrouped(). |
| 261 | $configuration = $this->getConfiguration(); |
| 262 | |
| 263 | $componentDefinitions = new ComponentLibraryDefinitionHelper($this->sdcManager, $this->sourceManager); |
| 264 | $definitions = $componentDefinitions->getDefinitions($configuration); |
| 265 | |
| 266 | $this->definitionsFiltered = $definitions['filtered'] ?? []; |
| 267 | $this->definitionsGrouped = $definitions['grouped'] ?? []; |
| 268 | $this->sourcesData = $definitions['sources'] ?? []; |
| 269 | |
| 270 | $panes = []; |
| 271 | |
| 272 | if ((bool) $configuration['show_grouped']) { |
| 273 | $panes['grouped'] = [ |
| 274 | 'title' => $this->t('Grouped'), |
| 275 | 'content' => $this->getComponentsGrouped($builder_id), |
| 276 | ]; |
| 277 | } |
| 278 | |
| 279 | if ((bool) $configuration['show_variants']) { |
| 280 | $panes['variants'] = [ |
| 281 | 'title' => $this->t('Variants'), |
| 282 | 'content' => $this->getComponentsVariants($builder_id), |
| 283 | ]; |
| 284 | } |
| 285 | |
| 286 | if ((bool) $configuration['show_mosaic']) { |
| 287 | $panes['mosaic'] = [ |
| 288 | 'title' => $this->t('Mosaic'), |
| 289 | 'content' => $this->getComponentsMosaic($builder_id), |
| 290 | ]; |
| 291 | } |
| 292 | |
| 293 | $tabs = []; |
| 294 | $content = []; |
| 295 | |
| 296 | foreach ($panes as $pane_id => $pane) { |
| 297 | $id = 'db-' . $builder_id . '-components-tab---' . $pane_id; |
| 298 | $tabs[] = [ |
| 299 | 'title' => $pane['title'], |
| 300 | 'url' => '#' . $id, |
| 301 | ]; |
| 302 | $content[] = $this->wrapContent($pane['content'], $id); |
| 303 | } |
| 304 | |
| 305 | return [ |
| 306 | '#type' => 'component', |
| 307 | '#component' => 'display_builder:library_panel', |
| 308 | '#slots' => [ |
| 309 | 'tabs' => $this->buildTabs('db-' . $builder_id . '-components-tabs', $tabs), |
| 310 | 'content' => $content, |
| 311 | ], |
| 312 | ]; |
| 313 | } |
| 314 | |
| 315 | /** |
| 316 | * Get all providers. |
| 317 | * |
| 318 | * @param array $definitions |
| 319 | * Plugin definitions. |
| 320 | * |
| 321 | * @return array |
| 322 | * Drupal extension definitions, keyed by extension ID |
| 323 | */ |
| 324 | protected function getProviders(array $definitions): array { |
| 325 | $themes = $this->themeList->getAllInstalledInfo(); |
| 326 | $modules = $this->moduleList->getAllInstalledInfo(); |
| 327 | $providers = []; |
| 328 | |
| 329 | foreach ($definitions as $definition) { |
| 330 | $provider_id = $definition['provider']; |
| 331 | |
| 332 | $provider = $themes[$provider_id] ?? $modules[$provider_id] ?? NULL; |
| 333 | |
| 334 | if (!$provider) { |
| 335 | continue; |
| 336 | } |
| 337 | $provider['count'] = isset($providers[$provider_id]) ? ($providers[$provider_id]['count']) + 1 : 1; |
| 338 | $providers[$provider_id] = $provider; |
| 339 | } |
| 340 | |
| 341 | return $providers; |
| 342 | } |
| 343 | |
| 344 | /** |
| 345 | * Gets the grouped components view. |
| 346 | * |
| 347 | * @param string $builder_id |
| 348 | * Builder ID. |
| 349 | * |
| 350 | * @return array |
| 351 | * A renderable array containing the grouped components. |
| 352 | */ |
| 353 | private function getComponentsGrouped(string $builder_id): array { |
| 354 | $build = []; |
| 355 | |
| 356 | foreach ($this->definitionsGrouped as $group_name => $group) { |
| 357 | $build[] = [ |
| 358 | '#type' => 'html_tag', |
| 359 | '#tag' => 'h4', |
| 360 | '#value' => $group_name, |
| 361 | '#attributes' => [ |
| 362 | 'class' => ['db-filter-hide-on-search'], |
| 363 | ], |
| 364 | ]; |
| 365 | |
| 366 | foreach ($group as $component_id => $definition) { |
| 367 | $component_id = (string) $component_id; |
| 368 | $component_preview_url = Url::fromRoute('display_builder.api_component_preview', ['component_id' => $component_id]); |
| 369 | |
| 370 | $data = [ |
| 371 | 'source_id' => 'component', |
| 372 | 'source' => $this->sourcesData[$component_id], |
| 373 | ]; |
| 374 | // Used for search filter. |
| 375 | $keywords = \sprintf('%s %s', $definition['label'], $definition['provider']); |
| 376 | $build[] = $this->buildPlaceholderButtonWithPreview($builder_id, $definition['annotated_name'], $data, $component_preview_url, $keywords); |
| 377 | } |
| 378 | } |
| 379 | |
| 380 | return $this->buildDraggables($builder_id, $build); |
| 381 | } |
| 382 | |
| 383 | /** |
| 384 | * Gets the components variants view. |
| 385 | * |
| 386 | * @param string $builder_id |
| 387 | * Builder ID. |
| 388 | * |
| 389 | * @return array |
| 390 | * A renderable array containing the variants placeholders. |
| 391 | */ |
| 392 | private function getComponentsVariants(string $builder_id): array { |
| 393 | $build = []; |
| 394 | |
| 395 | foreach ($this->definitionsFiltered as $component_id => $definition) { |
| 396 | $build[] = [ |
| 397 | '#type' => 'html_tag', |
| 398 | '#tag' => 'h4', |
| 399 | '#value' => $definition['annotated_name'], |
| 400 | '#attributes' => [ |
| 401 | 'data-filter-parent' => $definition['machineName'], |
| 402 | ], |
| 403 | ]; |
| 404 | |
| 405 | $data = [ |
| 406 | 'source_id' => 'component', |
| 407 | 'source' => $this->sourcesData[$component_id], |
| 408 | ]; |
| 409 | |
| 410 | if (!isset($definition['variants'])) { |
| 411 | $component_preview_url = Url::fromRoute('display_builder.api_component_preview', ['component_id' => $component_id]); |
| 412 | // Used for search filter. |
| 413 | $keywords = \sprintf('%s %s', $definition['label'], $definition['provider']); |
| 414 | $build_variant = $this->buildPlaceholderButtonWithPreview($builder_id, $this->t('Default'), $data, $component_preview_url, $keywords); |
| 415 | $build_variant['#attributes']['data-filter-child'] = $definition['machineName']; |
| 416 | // Label is used by default to set drawer title when dragging. It is set |
| 417 | // on RenderableBuilderTrait::buildPlaceholderButton(), so here we need |
| 418 | // to override it to have the proper label and not the variant name. |
| 419 | // @see assets/js/db_drawer.js |
| 420 | // @see src/RenderableBuilderTrait::buildPlaceholderButton() |
| 421 | $build_variant['#attributes']['data-node-title'] = $definition['label']; |
| 422 | |
| 423 | $build[] = $build_variant; |
| 424 | |
| 425 | continue; |
| 426 | } |
| 427 | |
| 428 | foreach ($definition['variants'] ?? [] as $variant_id => $variant) { |
| 429 | $params = ['component_id' => $component_id, 'variant_id' => $variant_id]; |
| 430 | $component_preview_url = Url::fromRoute('display_builder.api_component_preview', $params); |
| 431 | $data['source']['component']['variant_id'] = [ |
| 432 | 'source_id' => 'select', |
| 433 | 'source' => [ |
| 434 | 'value' => $variant_id, |
| 435 | ], |
| 436 | ]; |
| 437 | // Used for search filter. |
| 438 | $keywords = \sprintf('%s %s %s', $definition['label'], $variant['title'], $definition['provider']); |
| 439 | $build_variant = $this->buildPlaceholderButtonWithPreview($builder_id, $variant['title'], $data, $component_preview_url, $keywords); |
| 440 | $build_variant['#attributes']['data-filter-child'] = $definition['machineName']; |
| 441 | // Label is used by default to set drawer title when dragging. It is set |
| 442 | // on RenderableBuilderTrait::buildPlaceholderButton(), so here we need |
| 443 | // to override it to have the proper label and not the variant name. |
| 444 | // @see assets/js/db_drawer.js |
| 445 | // @see src/RenderableBuilderTrait::buildPlaceholderButton() |
| 446 | $build_variant['#attributes']['data-node-title'] = $definition['label']; |
| 447 | |
| 448 | $build[] = $build_variant; |
| 449 | } |
| 450 | } |
| 451 | |
| 452 | return $this->buildDraggables($builder_id, $build); |
| 453 | } |
| 454 | |
| 455 | /** |
| 456 | * Gets the mosaic view of components. |
| 457 | * |
| 458 | * @param string $builder_id |
| 459 | * Builder ID. |
| 460 | * |
| 461 | * @return array |
| 462 | * A renderable array containing the mosaic view of components. |
| 463 | */ |
| 464 | private function getComponentsMosaic(string $builder_id): array { |
| 465 | $components = []; |
| 466 | |
| 467 | foreach (\array_keys($this->definitionsFiltered) as $component_id) { |
| 468 | $component_id = (string) $component_id; |
| 469 | $component = $this->sdcManager->find($component_id); |
| 470 | $component_preview_url = Url::fromRoute('display_builder.api_component_preview', ['component_id' => $component_id]); |
| 471 | |
| 472 | $vals = [ |
| 473 | 'source_id' => 'component', |
| 474 | 'source' => $this->sourcesData[$component_id], |
| 475 | ]; |
| 476 | $thumbnail = $component->metadata->getThumbnailPath(); |
| 477 | |
| 478 | // Used for search filter. |
| 479 | $keywords = \sprintf('%s %s', $component->metadata->name, \str_replace(':', ' ', $component_id)); |
| 480 | $build = $this->buildPlaceholderCardWithPreview($component->metadata->name, $vals, $component_preview_url, $keywords, $thumbnail); |
| 481 | // Label is used by default to set drawer title when dragging. It is set |
| 482 | // on RenderableBuilderTrait::buildPlaceholderButton(), so here we need |
| 483 | // to override it to have the proper label and not the variant name. |
| 484 | // @see assets/js/db_drawer.js |
| 485 | // @see src/RenderableBuilderTrait::buildPlaceholderButton() |
| 486 | $build['#attributes']['data-node-title'] = $component->metadata->name; |
| 487 | $components[] = $build; |
| 488 | } |
| 489 | |
| 490 | return $this->buildDraggables($builder_id, $components, 'mosaic'); |
| 491 | } |
| 492 | |
| 493 | /** |
| 494 | * Get providers options for select input. |
| 495 | * |
| 496 | * @param array $definitions |
| 497 | * Plugin definitions. |
| 498 | * @param string|TranslatableMarkup $singular |
| 499 | * Singular label of the plugins. |
| 500 | * @param string|TranslatableMarkup $plural |
| 501 | * Plural label of the plugins. |
| 502 | * |
| 503 | * @return array |
| 504 | * An associative array with extension ID as key and extension description |
| 505 | * as value. |
| 506 | */ |
| 507 | private function getProvidersOptions(array $definitions, string|TranslatableMarkup $singular = 'definition', string|TranslatableMarkup $plural = 'definitions'): array { |
| 508 | $options = []; |
| 509 | |
| 510 | foreach ($this->getProviders($definitions) as $provider_id => $provider) { |
| 511 | $params = [ |
| 512 | '@name' => $provider['name'], |
| 513 | '@type' => $provider['type'], |
| 514 | '@count' => $provider['count'], |
| 515 | '@singular' => $singular, |
| 516 | '@plural' => $plural, |
| 517 | ]; |
| 518 | $options[$provider_id] = $this->formatPlural($provider['count'], '@name (@type, @count @singular)', '@name (@type, @count @plural)', $params); |
| 519 | } |
| 520 | |
| 521 | return $options; |
| 522 | } |
| 523 | |
| 524 | } |
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.
| 256 | public function build(InstanceInterface $builder, array $data = [], array $options = []): array { |
| 257 | $builder_id = (string) $builder->id(); |
| 258 | // Run a single time and saved as properties to avoid repeating processing |
| 259 | // in ::getComponentsMosaic(), ::getComponentsVariants() and |
| 260 | // ::getComponentsGrouped(). |
| 261 | $configuration = $this->getConfiguration(); |
| 262 | |
| 263 | $componentDefinitions = new ComponentLibraryDefinitionHelper($this->sdcManager, $this->sourceManager); |
| 264 | $definitions = $componentDefinitions->getDefinitions($configuration); |
| 265 | |
| 266 | $this->definitionsFiltered = $definitions['filtered'] ?? []; |
| 267 | $this->definitionsGrouped = $definitions['grouped'] ?? []; |
| 268 | $this->sourcesData = $definitions['sources'] ?? []; |
| 269 | |
| 270 | $panes = []; |
| 271 | |
| 272 | if ((bool) $configuration['show_grouped']) { |
| 274 | 'title' => $this->t('Grouped'), |
| 275 | 'content' => $this->getComponentsGrouped($builder_id), |
| 276 | ]; |
| 277 | } |
| 278 | |
| 279 | if ((bool) $configuration['show_variants']) { |
| 279 | if ((bool) $configuration['show_variants']) { |
| 281 | 'title' => $this->t('Variants'), |
| 282 | 'content' => $this->getComponentsVariants($builder_id), |
| 283 | ]; |
| 284 | } |
| 285 | |
| 286 | if ((bool) $configuration['show_mosaic']) { |
| 286 | if ((bool) $configuration['show_mosaic']) { |
| 288 | 'title' => $this->t('Mosaic'), |
| 289 | 'content' => $this->getComponentsMosaic($builder_id), |
| 290 | ]; |
| 291 | } |
| 292 | |
| 293 | $tabs = []; |
| 293 | $tabs = []; |
| 294 | $content = []; |
| 295 | |
| 296 | foreach ($panes as $pane_id => $pane) { |
| 296 | foreach ($panes as $pane_id => $pane) { |
| 296 | foreach ($panes as $pane_id => $pane) { |
| 296 | foreach ($panes as $pane_id => $pane) { |
| 297 | $id = 'db-' . $builder_id . '-components-tab---' . $pane_id; |
| 298 | $tabs[] = [ |
| 299 | 'title' => $pane['title'], |
| 300 | 'url' => '#' . $id, |
| 301 | ]; |
| 302 | $content[] = $this->wrapContent($pane['content'], $id); |
| 303 | } |
| 304 | |
| 305 | return [ |
| 306 | '#type' => 'component', |
| 307 | '#component' => 'display_builder:library_panel', |
| 308 | '#slots' => [ |
| 309 | 'tabs' => $this->buildTabs('db-' . $builder_id . '-components-tabs', $tabs), |
| 310 | 'content' => $content, |
| 121 | public function buildConfigurationForm(array $form, FormStateInterface $form_state): array { |
| 122 | $configuration = $this->getConfiguration(); |
| 123 | $components = $this->sdcManager->getDefinitions(); |
| 124 | |
| 125 | $form['exclude'] = [ |
| 126 | '#type' => 'checkboxes', |
| 127 | '#title' => $this->t('Exclude providers'), |
| 128 | '#options' => $this->getProvidersOptions($components, $this->t('component'), $this->t('components')), |
| 129 | '#default_value' => $configuration['exclude'], |
| 130 | ]; |
| 131 | |
| 132 | $form['exclude_id'] = [ |
| 133 | '#type' => 'textarea', |
| 134 | '#title' => $this->t('Exclude by id'), |
| 135 | '#description' => $this->t('Provide a space separated list of components id to exclude, must be prefixed by provider. Example: "ui_suite_bootstrap:card_body<br>ui_suite_bootstrap:table_cell".'), |
| 136 | '#default_value' => $configuration['exclude_id'], |
| 137 | ]; |
| 138 | |
| 139 | // @see https://git.drupalcode.org/project/drupal/-/blob/11.x/core/assets/schemas/v1/metadata.schema.json#L217 |
| 140 | $form['component_status'] = [ |
| 141 | '#type' => 'checkboxes', |
| 142 | '#title' => $this->t('Allowed status'), |
| 143 | '#options' => [ |
| 144 | 'experimental' => $this->t('Experimental'), |
| 145 | 'deprecated' => $this->t('Deprecated'), |
| 146 | 'obsolete' => $this->t('Obsolete'), |
| 147 | ], |
| 148 | '#description' => $this->t('Components with stable or undefined status will always be available.'), |
| 149 | '#default_value' => $configuration['component_status'], |
| 150 | ]; |
| 151 | |
| 152 | $form['show_grouped'] = [ |
| 153 | '#type' => 'checkbox', |
| 154 | '#title' => $this->t('Show components grouped'), |
| 155 | '#description' => $this->t('Provide a list of grouped components for selection.'), |
| 156 | '#default_value' => $configuration['show_grouped'], |
| 157 | ]; |
| 158 | |
| 159 | $form['show_variants'] = [ |
| 160 | '#type' => 'checkbox', |
| 161 | '#title' => $this->t('Show components variants'), |
| 162 | '#description' => $this->t('Provide a list of components per variants for selection.'), |
| 163 | '#default_value' => $configuration['show_variants'], |
| 164 | ]; |
| 165 | |
| 166 | $form['show_mosaic'] = [ |
| 167 | '#type' => 'checkbox', |
| 168 | '#title' => $this->t('Show components mosaic'), |
| 169 | '#description' => $this->t('Provide a list of mosaic components for selection.'), |
| 170 | '#default_value' => $configuration['show_mosaic'], |
| 171 | ]; |
| 172 | |
| 173 | // Drupal 11.3+ new exclude feature. |
| 174 | // @see https://git.drupalcode.org/project/drupal/-/blob/11.x/core/assets/schemas/v1/metadata.schema.json#L228 |
| 175 | $form['include_no_ui'] = [ |
| 176 | '#type' => 'checkbox', |
| 177 | '#title' => $this->t('Include marked as excluded from the UI'), |
| 178 | '#description' => $this->t('Components with no ui flag are meant for internal use only. Force to include them. Drupal 11.3+ only.'), |
| 179 | '#default_value' => $configuration['include_no_ui'], |
| 180 | ]; |
| 181 | |
| 182 | return $form; |
| 207 | $configuration = $this->getConfiguration(); |
| 208 | |
| 209 | $summary = []; |
| 210 | |
| 211 | $summary[] = $this->t('Excluded providers: @exclude', [ |
| 212 | '@exclude' => ($exclude = \array_filter($configuration['exclude'] ?? [])) ? \implode(', ', $exclude) : $this->t('None'), |
| 212 | '@exclude' => ($exclude = \array_filter($configuration['exclude'] ?? [])) ? \implode(', ', $exclude) : $this->t('None'), |
| 212 | '@exclude' => ($exclude = \array_filter($configuration['exclude'] ?? [])) ? \implode(', ', $exclude) : $this->t('None'), |
| 212 | '@exclude' => ($exclude = \array_filter($configuration['exclude'] ?? [])) ? \implode(', ', $exclude) : $this->t('None'), |
| 213 | ]); |
| 214 | |
| 215 | if (\strlen($configuration['exclude_id'] ?? '') > 5) { |
| 216 | $value = \preg_split('/\s+/', \trim($configuration['exclude_id'] ?? '')); |
| 217 | |
| 218 | if ($value === FALSE) { |
| 218 | if ($value === FALSE) { |
| 219 | $summary[] = $this->t('Component(s) excluded'); |
| 222 | $num = \count($value); |
| 223 | $summary[] = $this->formatPlural($num, '@count component excluded', '@count components excluded'); |
| 224 | } |
| 225 | } |
| 226 | |
| 227 | $summary[] = $this->t('Allowed status: @status', [ |
| 227 | $summary[] = $this->t('Allowed status: @status', [ |
| 228 | '@status' => \implode(', ', \array_filter(\array_unique(\array_merge(['stable', 'undefined'], $configuration['component_status'] ?? []))) ?: [$this->t('stable, undefined')]), |
| 229 | ]); |
| 230 | |
| 231 | $summary[] = $configuration['include_no_ui'] ? $this->t('Include no UI components') : $this->t('Exclude no UI components'); |
| 231 | $summary[] = $configuration['include_no_ui'] ? $this->t('Include no UI components') : $this->t('Exclude no UI components'); |
| 231 | $summary[] = $configuration['include_no_ui'] ? $this->t('Include no UI components') : $this->t('Exclude no UI components'); |
| 231 | $summary[] = $configuration['include_no_ui'] ? $this->t('Include no UI components') : $this->t('Exclude no UI components'); |
| 232 | |
| 233 | $list = []; |
| 234 | |
| 235 | if ((bool) $configuration['show_grouped']) { |
| 236 | $list[] = $this->t('grouped'); |
| 237 | } |
| 238 | |
| 239 | if ((bool) $configuration['show_variants']) { |
| 239 | if ((bool) $configuration['show_variants']) { |
| 240 | $list[] = $this->t('variants'); |
| 241 | } |
| 242 | |
| 243 | if ((bool) $configuration['show_mosaic']) { |
| 243 | if ((bool) $configuration['show_mosaic']) { |
| 244 | $list[] = $this->t('mosaic'); |
| 245 | } |
| 246 | $summary[] = $this->t('Components list as: @list', [ |
| 246 | $summary[] = $this->t('Components list as: @list', [ |
| 247 | '@list' => !empty($list) ? \implode(', ', $list) : $this->t('None selected'), |
| 247 | '@list' => !empty($list) ? \implode(', ', $list) : $this->t('None selected'), |
| 247 | '@list' => !empty($list) ? \implode(', ', $list) : $this->t('None selected'), |
| 247 | '@list' => !empty($list) ? \implode(', ', $list) : $this->t('None selected'), |
| 248 | ]); |
| 249 | |
| 250 | return $summary; |
| 84 | public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static { |
| 85 | $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); |
| 86 | $instance->themeManager = $container->get('theme.manager'); |
| 87 | $instance->themeList = $container->get('extension.list.theme'); |
| 88 | $instance->moduleList = $container->get('extension.list.module'); |
| 89 | $instance->sourceManager = $container->get('plugin.manager.ui_patterns_source'); |
| 90 | |
| 91 | return $instance; |
| 106 | 'exclude' => [], |
| 353 | private function getComponentsGrouped(string $builder_id): array { |
| 354 | $build = []; |
| 355 | |
| 356 | foreach ($this->definitionsGrouped as $group_name => $group) { |
| 356 | foreach ($this->definitionsGrouped as $group_name => $group) { |
| 356 | foreach ($this->definitionsGrouped as $group_name => $group) { |
| 357 | $build[] = [ |
| 358 | '#type' => 'html_tag', |
| 359 | '#tag' => 'h4', |
| 360 | '#value' => $group_name, |
| 361 | '#attributes' => [ |
| 362 | 'class' => ['db-filter-hide-on-search'], |
| 363 | ], |
| 364 | ]; |
| 365 | |
| 366 | foreach ($group as $component_id => $definition) { |
| 366 | foreach ($group as $component_id => $definition) { |
| 366 | foreach ($group as $component_id => $definition) { |
| 356 | foreach ($this->definitionsGrouped as $group_name => $group) { |
| 357 | $build[] = [ |
| 358 | '#type' => 'html_tag', |
| 359 | '#tag' => 'h4', |
| 360 | '#value' => $group_name, |
| 361 | '#attributes' => [ |
| 362 | 'class' => ['db-filter-hide-on-search'], |
| 363 | ], |
| 364 | ]; |
| 365 | |
| 366 | foreach ($group as $component_id => $definition) { |
| 356 | foreach ($this->definitionsGrouped as $group_name => $group) { |
| 357 | $build[] = [ |
| 358 | '#type' => 'html_tag', |
| 359 | '#tag' => 'h4', |
| 360 | '#value' => $group_name, |
| 361 | '#attributes' => [ |
| 362 | 'class' => ['db-filter-hide-on-search'], |
| 363 | ], |
| 364 | ]; |
| 365 | |
| 366 | foreach ($group as $component_id => $definition) { |
| 367 | $component_id = (string) $component_id; |
| 368 | $component_preview_url = Url::fromRoute('display_builder.api_component_preview', ['component_id' => $component_id]); |
| 369 | |
| 370 | $data = [ |
| 371 | 'source_id' => 'component', |
| 372 | 'source' => $this->sourcesData[$component_id], |
| 373 | ]; |
| 374 | // Used for search filter. |
| 375 | $keywords = \sprintf('%s %s', $definition['label'], $definition['provider']); |
| 376 | $build[] = $this->buildPlaceholderButtonWithPreview($builder_id, $definition['annotated_name'], $data, $component_preview_url, $keywords); |
| 377 | } |
| 378 | } |
| 379 | |
| 380 | return $this->buildDraggables($builder_id, $build); |
| 464 | private function getComponentsMosaic(string $builder_id): array { |
| 465 | $components = []; |
| 466 | |
| 467 | foreach (\array_keys($this->definitionsFiltered) as $component_id) { |
| 467 | foreach (\array_keys($this->definitionsFiltered) as $component_id) { |
| 467 | foreach (\array_keys($this->definitionsFiltered) as $component_id) { |
| 468 | $component_id = (string) $component_id; |
| 467 | foreach (\array_keys($this->definitionsFiltered) as $component_id) { |
| 468 | $component_id = (string) $component_id; |
| 469 | $component = $this->sdcManager->find($component_id); |
| 470 | $component_preview_url = Url::fromRoute('display_builder.api_component_preview', ['component_id' => $component_id]); |
| 471 | |
| 472 | $vals = [ |
| 473 | 'source_id' => 'component', |
| 474 | 'source' => $this->sourcesData[$component_id], |
| 475 | ]; |
| 476 | $thumbnail = $component->metadata->getThumbnailPath(); |
| 477 | |
| 478 | // Used for search filter. |
| 479 | $keywords = \sprintf('%s %s', $component->metadata->name, \str_replace(':', ' ', $component_id)); |
| 480 | $build = $this->buildPlaceholderCardWithPreview($component->metadata->name, $vals, $component_preview_url, $keywords, $thumbnail); |
| 481 | // Label is used by default to set drawer title when dragging. It is set |
| 482 | // on RenderableBuilderTrait::buildPlaceholderButton(), so here we need |
| 483 | // to override it to have the proper label and not the variant name. |
| 484 | // @see assets/js/db_drawer.js |
| 485 | // @see src/RenderableBuilderTrait::buildPlaceholderButton() |
| 486 | $build['#attributes']['data-node-title'] = $component->metadata->name; |
| 487 | $components[] = $build; |
| 488 | } |
| 489 | |
| 490 | return $this->buildDraggables($builder_id, $components, 'mosaic'); |
| 392 | private function getComponentsVariants(string $builder_id): array { |
| 393 | $build = []; |
| 394 | |
| 395 | foreach ($this->definitionsFiltered as $component_id => $definition) { |
| 395 | foreach ($this->definitionsFiltered as $component_id => $definition) { |
| 395 | foreach ($this->definitionsFiltered as $component_id => $definition) { |
| 396 | $build[] = [ |
| 397 | '#type' => 'html_tag', |
| 398 | '#tag' => 'h4', |
| 399 | '#value' => $definition['annotated_name'], |
| 400 | '#attributes' => [ |
| 401 | 'data-filter-parent' => $definition['machineName'], |
| 402 | ], |
| 403 | ]; |
| 404 | |
| 405 | $data = [ |
| 406 | 'source_id' => 'component', |
| 407 | 'source' => $this->sourcesData[$component_id], |
| 408 | ]; |
| 409 | |
| 410 | if (!isset($definition['variants'])) { |
| 411 | $component_preview_url = Url::fromRoute('display_builder.api_component_preview', ['component_id' => $component_id]); |
| 412 | // Used for search filter. |
| 413 | $keywords = \sprintf('%s %s', $definition['label'], $definition['provider']); |
| 414 | $build_variant = $this->buildPlaceholderButtonWithPreview($builder_id, $this->t('Default'), $data, $component_preview_url, $keywords); |
| 415 | $build_variant['#attributes']['data-filter-child'] = $definition['machineName']; |
| 416 | // Label is used by default to set drawer title when dragging. It is set |
| 417 | // on RenderableBuilderTrait::buildPlaceholderButton(), so here we need |
| 418 | // to override it to have the proper label and not the variant name. |
| 419 | // @see assets/js/db_drawer.js |
| 420 | // @see src/RenderableBuilderTrait::buildPlaceholderButton() |
| 421 | $build_variant['#attributes']['data-node-title'] = $definition['label']; |
| 422 | |
| 423 | $build[] = $build_variant; |
| 424 | |
| 425 | continue; |
| 428 | foreach ($definition['variants'] ?? [] as $variant_id => $variant) { |
| 428 | foreach ($definition['variants'] ?? [] as $variant_id => $variant) { |
| 428 | foreach ($definition['variants'] ?? [] as $variant_id => $variant) { |
| 395 | foreach ($this->definitionsFiltered as $component_id => $definition) { |
| 396 | $build[] = [ |
| 397 | '#type' => 'html_tag', |
| 398 | '#tag' => 'h4', |
| 399 | '#value' => $definition['annotated_name'], |
| 400 | '#attributes' => [ |
| 401 | 'data-filter-parent' => $definition['machineName'], |
| 402 | ], |
| 403 | ]; |
| 404 | |
| 405 | $data = [ |
| 406 | 'source_id' => 'component', |
| 407 | 'source' => $this->sourcesData[$component_id], |
| 408 | ]; |
| 409 | |
| 410 | if (!isset($definition['variants'])) { |
| 411 | $component_preview_url = Url::fromRoute('display_builder.api_component_preview', ['component_id' => $component_id]); |
| 412 | // Used for search filter. |
| 413 | $keywords = \sprintf('%s %s', $definition['label'], $definition['provider']); |
| 414 | $build_variant = $this->buildPlaceholderButtonWithPreview($builder_id, $this->t('Default'), $data, $component_preview_url, $keywords); |
| 415 | $build_variant['#attributes']['data-filter-child'] = $definition['machineName']; |
| 416 | // Label is used by default to set drawer title when dragging. It is set |
| 417 | // on RenderableBuilderTrait::buildPlaceholderButton(), so here we need |
| 418 | // to override it to have the proper label and not the variant name. |
| 419 | // @see assets/js/db_drawer.js |
| 420 | // @see src/RenderableBuilderTrait::buildPlaceholderButton() |
| 421 | $build_variant['#attributes']['data-node-title'] = $definition['label']; |
| 422 | |
| 423 | $build[] = $build_variant; |
| 424 | |
| 425 | continue; |
| 426 | } |
| 427 | |
| 428 | foreach ($definition['variants'] ?? [] as $variant_id => $variant) { |
| 395 | foreach ($this->definitionsFiltered as $component_id => $definition) { |
| 396 | $build[] = [ |
| 397 | '#type' => 'html_tag', |
| 398 | '#tag' => 'h4', |
| 399 | '#value' => $definition['annotated_name'], |
| 400 | '#attributes' => [ |
| 401 | 'data-filter-parent' => $definition['machineName'], |
| 402 | ], |
| 403 | ]; |
| 404 | |
| 405 | $data = [ |
| 406 | 'source_id' => 'component', |
| 407 | 'source' => $this->sourcesData[$component_id], |
| 408 | ]; |
| 409 | |
| 410 | if (!isset($definition['variants'])) { |
| 411 | $component_preview_url = Url::fromRoute('display_builder.api_component_preview', ['component_id' => $component_id]); |
| 412 | // Used for search filter. |
| 413 | $keywords = \sprintf('%s %s', $definition['label'], $definition['provider']); |
| 414 | $build_variant = $this->buildPlaceholderButtonWithPreview($builder_id, $this->t('Default'), $data, $component_preview_url, $keywords); |
| 415 | $build_variant['#attributes']['data-filter-child'] = $definition['machineName']; |
| 416 | // Label is used by default to set drawer title when dragging. It is set |
| 417 | // on RenderableBuilderTrait::buildPlaceholderButton(), so here we need |
| 418 | // to override it to have the proper label and not the variant name. |
| 419 | // @see assets/js/db_drawer.js |
| 420 | // @see src/RenderableBuilderTrait::buildPlaceholderButton() |
| 421 | $build_variant['#attributes']['data-node-title'] = $definition['label']; |
| 422 | |
| 423 | $build[] = $build_variant; |
| 424 | |
| 425 | continue; |
| 426 | } |
| 427 | |
| 428 | foreach ($definition['variants'] ?? [] as $variant_id => $variant) { |
| 429 | $params = ['component_id' => $component_id, 'variant_id' => $variant_id]; |
| 430 | $component_preview_url = Url::fromRoute('display_builder.api_component_preview', $params); |
| 431 | $data['source']['component']['variant_id'] = [ |
| 432 | 'source_id' => 'select', |
| 433 | 'source' => [ |
| 434 | 'value' => $variant_id, |
| 435 | ], |
| 436 | ]; |
| 437 | // Used for search filter. |
| 438 | $keywords = \sprintf('%s %s %s', $definition['label'], $variant['title'], $definition['provider']); |
| 439 | $build_variant = $this->buildPlaceholderButtonWithPreview($builder_id, $variant['title'], $data, $component_preview_url, $keywords); |
| 440 | $build_variant['#attributes']['data-filter-child'] = $definition['machineName']; |
| 441 | // Label is used by default to set drawer title when dragging. It is set |
| 442 | // on RenderableBuilderTrait::buildPlaceholderButton(), so here we need |
| 443 | // to override it to have the proper label and not the variant name. |
| 444 | // @see assets/js/db_drawer.js |
| 445 | // @see src/RenderableBuilderTrait::buildPlaceholderButton() |
| 446 | $build_variant['#attributes']['data-node-title'] = $definition['label']; |
| 447 | |
| 448 | $build[] = $build_variant; |
| 449 | } |
| 450 | } |
| 451 | |
| 452 | return $this->buildDraggables($builder_id, $build); |
| 324 | protected function getProviders(array $definitions): array { |
| 325 | $themes = $this->themeList->getAllInstalledInfo(); |
| 326 | $modules = $this->moduleList->getAllInstalledInfo(); |
| 327 | $providers = []; |
| 328 | |
| 329 | foreach ($definitions as $definition) { |
| 329 | foreach ($definitions as $definition) { |
| 330 | $provider_id = $definition['provider']; |
| 331 | |
| 332 | $provider = $themes[$provider_id] ?? $modules[$provider_id] ?? NULL; |
| 333 | |
| 334 | if (!$provider) { |
| 335 | continue; |
| 337 | $provider['count'] = isset($providers[$provider_id]) ? ($providers[$provider_id]['count']) + 1 : 1; |
| 337 | $provider['count'] = isset($providers[$provider_id]) ? ($providers[$provider_id]['count']) + 1 : 1; |
| 337 | $provider['count'] = isset($providers[$provider_id]) ? ($providers[$provider_id]['count']) + 1 : 1; |
| 329 | foreach ($definitions as $definition) { |
| 330 | $provider_id = $definition['provider']; |
| 331 | |
| 332 | $provider = $themes[$provider_id] ?? $modules[$provider_id] ?? NULL; |
| 333 | |
| 334 | if (!$provider) { |
| 335 | continue; |
| 336 | } |
| 337 | $provider['count'] = isset($providers[$provider_id]) ? ($providers[$provider_id]['count']) + 1 : 1; |
| 329 | foreach ($definitions as $definition) { |
| 330 | $provider_id = $definition['provider']; |
| 331 | |
| 332 | $provider = $themes[$provider_id] ?? $modules[$provider_id] ?? NULL; |
| 333 | |
| 334 | if (!$provider) { |
| 335 | continue; |
| 336 | } |
| 337 | $provider['count'] = isset($providers[$provider_id]) ? ($providers[$provider_id]['count']) + 1 : 1; |
| 338 | $providers[$provider_id] = $provider; |
| 339 | } |
| 340 | |
| 341 | return $providers; |
| 507 | private function getProvidersOptions(array $definitions, string|TranslatableMarkup $singular = 'definition', string|TranslatableMarkup $plural = 'definitions'): array { |
| 508 | $options = []; |
| 509 | |
| 510 | foreach ($this->getProviders($definitions) as $provider_id => $provider) { |
| 510 | foreach ($this->getProviders($definitions) as $provider_id => $provider) { |
| 510 | foreach ($this->getProviders($definitions) as $provider_id => $provider) { |
| 510 | foreach ($this->getProviders($definitions) as $provider_id => $provider) { |
| 511 | $params = [ |
| 512 | '@name' => $provider['name'], |
| 513 | '@type' => $provider['type'], |
| 514 | '@count' => $provider['count'], |
| 515 | '@singular' => $singular, |
| 516 | '@plural' => $plural, |
| 517 | ]; |
| 518 | $options[$provider_id] = $this->formatPlural($provider['count'], '@name (@type, @count @singular)', '@name (@type, @count @plural)', $params); |
| 519 | } |
| 520 | |
| 521 | return $options; |
| 98 | return 'Components'; |
| 188 | public function validateConfigurationForm(array &$form, FormStateInterface $form_state): void { |
| 189 | $values = $form_state->getValues(); |
| 190 | |
| 191 | // At least one display must be enabled. |
| 192 | $show_grouped = (bool) $values['show_grouped']; |
| 193 | $show_variants = (bool) $values['show_variants']; |
| 194 | $show_mosaic = (bool) $values['show_mosaic']; |
| 195 | |
| 196 | if (!$show_grouped && !$show_variants && !$show_mosaic) { |
| 196 | if (!$show_grouped && !$show_variants && !$show_mosaic) { |
| 196 | if (!$show_grouped && !$show_variants && !$show_mosaic) { |
| 197 | $form_state->setError($form['show_grouped'], $this->t('At least one display must be selected!')); |
| 198 | $form_state->setError($form['show_variants'], $this->t('At least one display must be selected!')); |
| 199 | $form_state->setError($form['show_mosaic'], $this->t('At least one display must be selected!')); |
| 200 | } |
| 201 | } |
| 201 | } |