Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
0.00% |
0 / 179 |
|
0.00% |
0 / 63 |
|
0.00% |
0 / 158 |
|
0.00% |
0 / 11 |
CRAP | |
0.00% |
0 / 1 |
| ProfileViewBuilder | |
0.00% |
0 / 179 |
|
0.00% |
0 / 63 |
|
0.00% |
0 / 158 |
|
0.00% |
0 / 11 |
1260 | |
0.00% |
0 / 1 |
| view | |
0.00% |
0 / 27 |
|
0.00% |
0 / 7 |
|
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
| buildSlots | |
0.00% |
0 / 34 |
|
0.00% |
0 / 15 |
|
0.00% |
0 / 112 |
|
0.00% |
0 / 1 |
90 | |||
| prepareViewIslands | |
0.00% |
0 / 26 |
|
0.00% |
0 / 12 |
|
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
42 | |||
| buildContextualIslands | |
0.00% |
0 / 16 |
|
0.00% |
0 / 3 |
|
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
| buildPanes | |
0.00% |
0 / 18 |
|
0.00% |
0 / 4 |
|
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
| buildStartButtons | |
0.00% |
0 / 20 |
|
0.00% |
0 / 5 |
|
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
| buildBuilderTabs | |
0.00% |
0 / 14 |
|
0.00% |
0 / 6 |
|
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
| buildMenuWrapper | |
0.00% |
0 / 16 |
|
0.00% |
0 / 4 |
|
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
| getIslandsEnableSorted | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| entityTypeManager | |
0.00% |
0 / 3 |
|
0.00% |
0 / 3 |
|
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
| islandPluginManager | |
0.00% |
0 / 3 |
|
0.00% |
0 / 3 |
|
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Drupal\display_builder; |
| 6 | |
| 7 | use Drupal\Core\Entity\EntityInterface; |
| 8 | use Drupal\Core\Entity\EntityTypeManagerInterface; |
| 9 | use Drupal\Core\Entity\EntityViewBuilder; |
| 10 | use Drupal\Core\Security\TrustedCallbackInterface; |
| 11 | |
| 12 | /** |
| 13 | * View builder handler for display builder profiles. |
| 14 | */ |
| 15 | class ProfileViewBuilder extends EntityViewBuilder implements TrustedCallbackInterface { |
| 16 | |
| 17 | use RenderableBuilderTrait; |
| 18 | |
| 19 | /** |
| 20 | * The entity type manager. |
| 21 | */ |
| 22 | private EntityTypeManagerInterface $entityTypeManager; |
| 23 | |
| 24 | /** |
| 25 | * The display builder island plugin manager. |
| 26 | */ |
| 27 | private IslandPluginManagerInterface $islandPluginManager; |
| 28 | |
| 29 | /** |
| 30 | * The entity we are building the view for. |
| 31 | */ |
| 32 | private ProfileInterface $entity; |
| 33 | |
| 34 | /** |
| 35 | * {@inheritdoc} |
| 36 | */ |
| 37 | public function view(EntityInterface $entity, $view_mode = 'full', $langcode = NULL): array { |
| 38 | // We have 'hacked' the interface by using $view_mode as a way of passing |
| 39 | // the Instance entity ID. |
| 40 | $builder_id = $view_mode; |
| 41 | |
| 42 | /** @var \Drupal\display_builder\ProfileInterface $entity */ |
| 43 | $entity = $entity; |
| 44 | $this->entity = $entity; |
| 45 | |
| 46 | /** @var \Drupal\display_builder\InstanceInterface $builder */ |
| 47 | $builder = $this->entityTypeManager()->getStorage('display_builder_instance')->load($builder_id); |
| 48 | $contexts = $builder->getContexts() ?? []; |
| 49 | $islands_enabled_sorted = $this->getIslandsEnableSorted($contexts); |
| 50 | $build = [ |
| 51 | '#type' => 'component', |
| 52 | '#component' => 'display_builder:display_builder', |
| 53 | '#props' => [ |
| 54 | 'builder_id' => $builder_id, |
| 55 | 'hash' => (string) $builder->getCurrent()->hash, |
| 56 | ], |
| 57 | '#slots' => $this->buildSlots($builder, $islands_enabled_sorted), |
| 58 | '#attached' => [ |
| 59 | 'drupalSettings' => [ |
| 60 | 'dbDebug' => $entity->isDebugModeActivated(), |
| 61 | ], |
| 62 | ], |
| 63 | '#cache' => [ |
| 64 | 'tags' => $builder->getCacheTags(), |
| 65 | ], |
| 66 | ]; |
| 67 | |
| 68 | foreach ($islands_enabled_sorted as $islands) { |
| 69 | foreach ($islands as $island) { |
| 70 | $build = $island->alterRenderable($builder, $build); |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | return $build; |
| 75 | } |
| 76 | |
| 77 | /** |
| 78 | * Builds and returns the value of each slot. |
| 79 | * |
| 80 | * @param \Drupal\display_builder\InstanceInterface $builder |
| 81 | * Display builder instance. |
| 82 | * @param array $islands_enabled_sorted |
| 83 | * An array of enabled islands. |
| 84 | * |
| 85 | * @return array |
| 86 | * An associative array with the value of each slot. |
| 87 | */ |
| 88 | private function buildSlots(InstanceInterface $builder, array $islands_enabled_sorted): array { |
| 89 | $builder_data = $builder->getCurrentState(); |
| 90 | |
| 91 | $button_islands = $islands_enabled_sorted[IslandType::Button->value] ?? []; |
| 92 | $library_islands = $islands_enabled_sorted[IslandType::Library->value] ?? []; |
| 93 | $contextual_islands = $islands_enabled_sorted[IslandType::Contextual->value] ?? []; |
| 94 | $menu_islands = $islands_enabled_sorted[IslandType::Menu->value] ?? []; |
| 95 | $view_islands = $islands_enabled_sorted[IslandType::View->value] ?? []; |
| 96 | |
| 97 | $buttons = []; |
| 98 | |
| 99 | if (!empty($button_islands)) { |
| 100 | $buttons = $this->buildPanes($builder, $button_islands, [], [], 'span'); |
| 101 | } |
| 102 | |
| 103 | if (!empty($menu_islands)) { |
| 104 | $menu_islands = $this->buildMenuWrapper($builder, $menu_islands); |
| 105 | } |
| 106 | |
| 107 | if (!empty($library_islands)) { |
| 108 | $library_islands = [ |
| 109 | $this->buildBuilderTabs($builder, $library_islands, TRUE), |
| 110 | $this->buildPanes($builder, $library_islands, $builder_data), |
| 111 | ]; |
| 112 | } |
| 113 | |
| 114 | $view_islands_data = $this->prepareViewIslands($builder, $view_islands); |
| 115 | $view_sidebar = $view_islands_data['view_sidebar']; |
| 116 | $view_main = $view_islands_data['view_main']; |
| 117 | |
| 118 | // Library content can be in main or sidebar. |
| 119 | // @todo Move the logic to LibrariesPanel::build(). |
| 120 | // @see https://www.drupal.org/project/display_builder/issues/3542866 |
| 121 | if (isset($view_sidebar['library']) && !empty($library_islands)) { |
| 122 | $view_sidebar['library']['content'] = $library_islands; |
| 123 | } |
| 124 | elseif (isset($view_main['library']) && !empty($library_islands)) { |
| 125 | $view_main['library']['content'] = $library_islands; |
| 126 | } |
| 127 | |
| 128 | if (!empty($contextual_islands)) { |
| 129 | $contextual_islands = $this->buildContextualIslands($builder, $islands_enabled_sorted); |
| 130 | } |
| 131 | |
| 132 | return [ |
| 133 | 'view_sidebar_buttons' => $view_islands_data['view_sidebar_buttons'], |
| 134 | 'view_sidebar' => $view_sidebar, |
| 135 | 'view_main_tabs' => $view_islands_data['view_main_tabs'], |
| 136 | 'view_main' => $view_main, |
| 137 | 'buttons' => $buttons, |
| 138 | 'contextual_islands' => $contextual_islands, |
| 139 | 'menu_islands' => $menu_islands, |
| 140 | ]; |
| 141 | } |
| 142 | |
| 143 | /** |
| 144 | * Prepares view islands data. |
| 145 | * |
| 146 | * @param \Drupal\display_builder\InstanceInterface $builder |
| 147 | * Display builder instance. |
| 148 | * @param array $islands |
| 149 | * The sorted, enabled View islands. |
| 150 | * |
| 151 | * @return array |
| 152 | * The prepared view islands data. |
| 153 | */ |
| 154 | private function prepareViewIslands(InstanceInterface $builder, array $islands): array { |
| 155 | $view_islands_sidebar = []; |
| 156 | $view_islands_main = []; |
| 157 | $view_sidebar_buttons = []; |
| 158 | $view_main_tabs = []; |
| 159 | |
| 160 | foreach ($islands as $id => $island) { |
| 161 | if ($island->getTypeId() !== IslandType::View->value) { |
| 162 | continue; |
| 163 | } |
| 164 | |
| 165 | $configuration = $island->getConfiguration(); |
| 166 | |
| 167 | if ($configuration['region'] === 'sidebar') { |
| 168 | $view_islands_sidebar[$id] = $islands[$id]; |
| 169 | $view_sidebar_buttons[$id] = $islands[$id]; |
| 170 | } |
| 171 | else { |
| 172 | $view_islands_main[$id] = $islands[$id]; |
| 173 | $view_main_tabs[$id] = $islands[$id]; |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | if (!empty($view_sidebar_buttons)) { |
| 178 | $view_sidebar_buttons = $this->buildStartButtons($builder, $view_sidebar_buttons); |
| 179 | } |
| 180 | |
| 181 | if (!empty($view_main_tabs)) { |
| 182 | $view_main_tabs = $this->buildBuilderTabs($builder, $view_main_tabs, FALSE); |
| 183 | } |
| 184 | |
| 185 | $builder_data = $builder->getCurrentState(); |
| 186 | $view_sidebar = $this->buildPanes($builder, $view_islands_sidebar, $builder_data); |
| 187 | // Default hidden. |
| 188 | $view_main = $this->buildPanes($builder, $view_islands_main, $builder_data, ['shoelace-tabs__tab--hidden']); |
| 189 | |
| 190 | return [ |
| 191 | 'view_sidebar_buttons' => $view_sidebar_buttons, |
| 192 | 'view_main_tabs' => $view_main_tabs, |
| 193 | 'view_sidebar' => $view_sidebar, |
| 194 | 'view_main' => $view_main, |
| 195 | ]; |
| 196 | } |
| 197 | |
| 198 | /** |
| 199 | * Build contextual islands which are tabbed sub islands. |
| 200 | * |
| 201 | * @param \Drupal\display_builder\InstanceInterface $builder |
| 202 | * Display builder instance. |
| 203 | * @param array $islands_enabled_sorted |
| 204 | * The islands enabled sorted. |
| 205 | * |
| 206 | * @return array |
| 207 | * The contextual islands render array. |
| 208 | */ |
| 209 | private function buildContextualIslands(InstanceInterface $builder, array $islands_enabled_sorted): array { |
| 210 | $contextual_islands = $islands_enabled_sorted[IslandType::Contextual->value] ?? []; |
| 211 | |
| 212 | if (empty($contextual_islands)) { |
| 213 | return []; |
| 214 | } |
| 215 | |
| 216 | $filter = $this->buildInput((string) $builder->id(), '', 'search', 'medium', 'off', $this->t('Filter by name'), TRUE, 'search'); |
| 217 | // @see assets/js/search.js |
| 218 | $filter['#attributes']['class'] = ['db-search-instance']; |
| 219 | |
| 220 | return [ |
| 221 | '#type' => 'html_tag', |
| 222 | '#tag' => 'div', |
| 223 | // Used for custom styling in assets/css/form.css. |
| 224 | '#attributes' => [ |
| 225 | 'id' => \sprintf('%s-contextual', $builder->id()), |
| 226 | 'class' => ['db-form'], |
| 227 | ], |
| 228 | 'tabs' => $this->buildBuilderTabs($builder, $contextual_islands), |
| 229 | 'filter' => $filter, |
| 230 | 'panes' => $this->buildPanes($builder, $contextual_islands, $builder->getCurrentState()), |
| 231 | ]; |
| 232 | } |
| 233 | |
| 234 | /** |
| 235 | * Builds panes. |
| 236 | * |
| 237 | * @param \Drupal\display_builder\InstanceInterface $builder |
| 238 | * Display builder instance. |
| 239 | * @param \Drupal\display_builder\IslandInterface[] $islands |
| 240 | * The islands to build tabs for. |
| 241 | * @param array $data |
| 242 | * (Optional) The data to pass to the islands. |
| 243 | * @param array $classes |
| 244 | * (Optional) The HTML classes to start with. |
| 245 | * @param string $tag |
| 246 | * (Optional) The HTML tag, defaults to 'div'. |
| 247 | * |
| 248 | * @return array |
| 249 | * The tabs render array. |
| 250 | */ |
| 251 | private function buildPanes(InstanceInterface $builder, array $islands, array $data = [], array $classes = [], string $tag = 'div'): array { |
| 252 | $panes = []; |
| 253 | |
| 254 | foreach ($islands as $island_id => $island) { |
| 255 | $island_classes = \array_merge($classes, [ |
| 256 | 'db-island', |
| 257 | \sprintf('db-island-%s', $island->getTypeId()), |
| 258 | \sprintf('db-island-%s', $island->getPluginId()), |
| 259 | ]); |
| 260 | |
| 261 | $panes[$island_id] = [ |
| 262 | '#type' => 'html_tag', |
| 263 | '#tag' => $tag, |
| 264 | 'children' => $island->build($builder, $data), |
| 265 | '#attributes' => [ |
| 266 | // `id` attribute is used by HTMX OOB swap. |
| 267 | 'id' => $island->getHtmlId((string) $builder->id()), |
| 268 | // `sse-swap` attribute is used by HTMX SSE swap. |
| 269 | 'sse-swap' => $island->getHtmlId((string) $builder->id()), |
| 270 | 'class' => $island_classes, |
| 271 | ], |
| 272 | ]; |
| 273 | } |
| 274 | |
| 275 | return $panes; |
| 276 | } |
| 277 | |
| 278 | /** |
| 279 | * Build the buttons to hide/show the drawer. |
| 280 | * |
| 281 | * @param \Drupal\display_builder\InstanceInterface $builder |
| 282 | * Display builder instance. |
| 283 | * @param \Drupal\display_builder\IslandInterface[] $islands |
| 284 | * An array of island objects for which buttons will be created. |
| 285 | * |
| 286 | * @return array |
| 287 | * An array of render arrays for the drawer buttons. |
| 288 | */ |
| 289 | private function buildStartButtons(InstanceInterface $builder, array $islands): array { |
| 290 | $build = []; |
| 291 | |
| 292 | foreach ($islands as $island) { |
| 293 | $island_id = $island->getPluginId(); |
| 294 | |
| 295 | $build[$island_id] = [ |
| 296 | '#type' => 'component', |
| 297 | '#component' => 'display_builder:button', |
| 298 | '#props' => [ |
| 299 | 'id' => \sprintf('start-btn-%s-%s', $builder->id(), $island_id), |
| 300 | 'label' => (string) $island->label(), |
| 301 | 'icon' => $island->getIcon(), |
| 302 | 'attributes' => [ |
| 303 | 'data-open-first-drawer' => TRUE, |
| 304 | 'data-target' => $island_id, |
| 305 | ], |
| 306 | ], |
| 307 | ]; |
| 308 | |
| 309 | if ($keyboard = $island::keyboardShortcuts()) { |
| 310 | $build[$island_id]['#attributes']['data-keyboard-key'] = \key($keyboard); |
| 311 | $build[$island_id]['#attributes']['data-keyboard-help'] = \reset($keyboard); |
| 312 | } |
| 313 | } |
| 314 | |
| 315 | return $build; |
| 316 | } |
| 317 | |
| 318 | /** |
| 319 | * Builds tabs. |
| 320 | * |
| 321 | * @param \Drupal\display_builder\InstanceInterface $builder |
| 322 | * Display builder instance. |
| 323 | * @param \Drupal\display_builder\IslandInterface[] $islands |
| 324 | * The islands to build tabs for. |
| 325 | * @param bool $contextual |
| 326 | * (Optional) Whether the tabs are contextual. |
| 327 | * |
| 328 | * @return array |
| 329 | * The tabs render array. |
| 330 | */ |
| 331 | private function buildBuilderTabs(InstanceInterface $builder, array $islands, bool $contextual = FALSE): array { |
| 332 | // Global id is based on last island. |
| 333 | $id = ''; |
| 334 | $tabs = []; |
| 335 | |
| 336 | foreach ($islands as $island) { |
| 337 | $id = $island_id = $island->getHtmlId((string) $builder->id()); |
| 338 | |
| 339 | $attributes = []; |
| 340 | |
| 341 | if ($keyboard = $island::keyboardShortcuts()) { |
| 342 | $attributes['data-keyboard-key'] = \key($keyboard); |
| 343 | $attributes['data-keyboard-help'] = \reset($keyboard); |
| 344 | } |
| 345 | $tabs[] = [ |
| 346 | 'title' => $island->label(), |
| 347 | 'url' => '#' . $island_id, |
| 348 | 'attributes' => $attributes, |
| 349 | ]; |
| 350 | } |
| 351 | |
| 352 | // Id is needed for storage tabs state, @see component tabs.js file. |
| 353 | return $this->buildTabs($id, $tabs, $contextual); |
| 354 | } |
| 355 | |
| 356 | /** |
| 357 | * Builds menu with islands as entries. |
| 358 | * |
| 359 | * @param \Drupal\display_builder\InstanceInterface $builder |
| 360 | * Display builder instance. |
| 361 | * @param \Drupal\display_builder\IslandInterface[] $islands |
| 362 | * The islands to build tabs for. |
| 363 | * |
| 364 | * @return array |
| 365 | * The islands render array. |
| 366 | * |
| 367 | * @see assets/js/contextual_menu.js |
| 368 | */ |
| 369 | private function buildMenuWrapper(InstanceInterface $builder, array $islands): array { |
| 370 | $build = [ |
| 371 | '#type' => 'component', |
| 372 | '#component' => 'display_builder:contextual_menu', |
| 373 | '#slots' => [ |
| 374 | 'label' => $this->t('Select an action'), |
| 375 | ], |
| 376 | '#attributes' => [ |
| 377 | 'class' => ['db-background', 'db-menu'], |
| 378 | // Require for JavaScript. |
| 379 | // @see assets/js/contextual_menu.js |
| 380 | 'data-db-id' => (string) $builder->id(), |
| 381 | ], |
| 382 | ]; |
| 383 | |
| 384 | $items = []; |
| 385 | |
| 386 | foreach ($islands as $island) { |
| 387 | $items = \array_merge($items, $island->build($builder, $builder->getCurrentState())); |
| 388 | } |
| 389 | $build['#slots']['items'] = $items; |
| 390 | |
| 391 | return $build; |
| 392 | } |
| 393 | |
| 394 | /** |
| 395 | * Get enabled panes sorted by weight. |
| 396 | * |
| 397 | * @param \Drupal\Core\Plugin\Context\ContextInterface[] $contexts |
| 398 | * An array of contexts, keyed by context name. |
| 399 | * |
| 400 | * @return array |
| 401 | * The list of enabled islands sorted. |
| 402 | * |
| 403 | * @todo just key by weight and default weight in Island? |
| 404 | */ |
| 405 | private function getIslandsEnableSorted(array $contexts): array { |
| 406 | // Set island by weight. |
| 407 | // @todo just key by weight and default weight in Island? |
| 408 | $islands_enable_by_weight = $this->entity->getEnabledIslands(); |
| 409 | |
| 410 | return $this->islandPluginManager()->getIslandsByTypes($contexts, $this->entity->getIslandConfigurations(), $islands_enable_by_weight); |
| 411 | } |
| 412 | |
| 413 | /** |
| 414 | * Gets the entity type manager. |
| 415 | * |
| 416 | * @return \Drupal\Core\Entity\EntityTypeManagerInterface |
| 417 | * The entity type manager. |
| 418 | */ |
| 419 | private function entityTypeManager(): EntityTypeManagerInterface { |
| 420 | if (!isset($this->entityTypeManager)) { |
| 421 | $this->entityTypeManager = \Drupal::service('entity_type.manager'); |
| 422 | } |
| 423 | |
| 424 | return $this->entityTypeManager; |
| 425 | } |
| 426 | |
| 427 | /** |
| 428 | * Gets the display builder island plugin manager. |
| 429 | * |
| 430 | * @return \Drupal\display_builder\IslandPluginManagerInterface |
| 431 | * The island plugin manager. |
| 432 | */ |
| 433 | private function islandPluginManager(): IslandPluginManagerInterface { |
| 434 | if (!isset($this->islandPluginManager)) { |
| 435 | $this->islandPluginManager = \Drupal::service('plugin.manager.db_island'); |
| 436 | } |
| 437 | |
| 438 | return $this->islandPluginManager; |
| 439 | } |
| 440 | |
| 441 | } |
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.
| 331 | private function buildBuilderTabs(InstanceInterface $builder, array $islands, bool $contextual = FALSE): array { |
| 332 | // Global id is based on last island. |
| 333 | $id = ''; |
| 334 | $tabs = []; |
| 335 | |
| 336 | foreach ($islands as $island) { |
| 336 | foreach ($islands as $island) { |
| 337 | $id = $island_id = $island->getHtmlId((string) $builder->id()); |
| 338 | |
| 339 | $attributes = []; |
| 340 | |
| 341 | if ($keyboard = $island::keyboardShortcuts()) { |
| 342 | $attributes['data-keyboard-key'] = \key($keyboard); |
| 343 | $attributes['data-keyboard-help'] = \reset($keyboard); |
| 344 | } |
| 345 | $tabs[] = [ |
| 346 | 'title' => $island->label(), |
| 336 | foreach ($islands as $island) { |
| 337 | $id = $island_id = $island->getHtmlId((string) $builder->id()); |
| 338 | |
| 339 | $attributes = []; |
| 340 | |
| 341 | if ($keyboard = $island::keyboardShortcuts()) { |
| 342 | $attributes['data-keyboard-key'] = \key($keyboard); |
| 343 | $attributes['data-keyboard-help'] = \reset($keyboard); |
| 344 | } |
| 345 | $tabs[] = [ |
| 346 | 'title' => $island->label(), |
| 336 | foreach ($islands as $island) { |
| 337 | $id = $island_id = $island->getHtmlId((string) $builder->id()); |
| 338 | |
| 339 | $attributes = []; |
| 340 | |
| 341 | if ($keyboard = $island::keyboardShortcuts()) { |
| 342 | $attributes['data-keyboard-key'] = \key($keyboard); |
| 343 | $attributes['data-keyboard-help'] = \reset($keyboard); |
| 344 | } |
| 345 | $tabs[] = [ |
| 346 | 'title' => $island->label(), |
| 347 | 'url' => '#' . $island_id, |
| 348 | 'attributes' => $attributes, |
| 349 | ]; |
| 350 | } |
| 351 | |
| 352 | // Id is needed for storage tabs state, @see component tabs.js file. |
| 353 | return $this->buildTabs($id, $tabs, $contextual); |
| 209 | private function buildContextualIslands(InstanceInterface $builder, array $islands_enabled_sorted): array { |
| 210 | $contextual_islands = $islands_enabled_sorted[IslandType::Contextual->value] ?? []; |
| 211 | |
| 212 | if (empty($contextual_islands)) { |
| 213 | return []; |
| 216 | $filter = $this->buildInput((string) $builder->id(), '', 'search', 'medium', 'off', $this->t('Filter by name'), TRUE, 'search'); |
| 217 | // @see assets/js/search.js |
| 218 | $filter['#attributes']['class'] = ['db-search-instance']; |
| 219 | |
| 220 | return [ |
| 221 | '#type' => 'html_tag', |
| 222 | '#tag' => 'div', |
| 223 | // Used for custom styling in assets/css/form.css. |
| 224 | '#attributes' => [ |
| 225 | 'id' => \sprintf('%s-contextual', $builder->id()), |
| 226 | 'class' => ['db-form'], |
| 227 | ], |
| 228 | 'tabs' => $this->buildBuilderTabs($builder, $contextual_islands), |
| 229 | 'filter' => $filter, |
| 230 | 'panes' => $this->buildPanes($builder, $contextual_islands, $builder->getCurrentState()), |
| 369 | private function buildMenuWrapper(InstanceInterface $builder, array $islands): array { |
| 370 | $build = [ |
| 371 | '#type' => 'component', |
| 372 | '#component' => 'display_builder:contextual_menu', |
| 373 | '#slots' => [ |
| 374 | 'label' => $this->t('Select an action'), |
| 375 | ], |
| 376 | '#attributes' => [ |
| 377 | 'class' => ['db-background', 'db-menu'], |
| 378 | // Require for JavaScript. |
| 379 | // @see assets/js/contextual_menu.js |
| 380 | 'data-db-id' => (string) $builder->id(), |
| 381 | ], |
| 382 | ]; |
| 383 | |
| 384 | $items = []; |
| 385 | |
| 386 | foreach ($islands as $island) { |
| 386 | foreach ($islands as $island) { |
| 386 | foreach ($islands as $island) { |
| 387 | $items = \array_merge($items, $island->build($builder, $builder->getCurrentState())); |
| 386 | foreach ($islands as $island) { |
| 387 | $items = \array_merge($items, $island->build($builder, $builder->getCurrentState())); |
| 388 | } |
| 389 | $build['#slots']['items'] = $items; |
| 390 | |
| 391 | return $build; |
| 251 | private function buildPanes(InstanceInterface $builder, array $islands, array $data = [], array $classes = [], string $tag = 'div'): array { |
| 252 | $panes = []; |
| 253 | |
| 254 | foreach ($islands as $island_id => $island) { |
| 254 | foreach ($islands as $island_id => $island) { |
| 254 | foreach ($islands as $island_id => $island) { |
| 254 | foreach ($islands as $island_id => $island) { |
| 255 | $island_classes = \array_merge($classes, [ |
| 256 | 'db-island', |
| 257 | \sprintf('db-island-%s', $island->getTypeId()), |
| 258 | \sprintf('db-island-%s', $island->getPluginId()), |
| 259 | ]); |
| 260 | |
| 261 | $panes[$island_id] = [ |
| 262 | '#type' => 'html_tag', |
| 263 | '#tag' => $tag, |
| 264 | 'children' => $island->build($builder, $data), |
| 265 | '#attributes' => [ |
| 266 | // `id` attribute is used by HTMX OOB swap. |
| 267 | 'id' => $island->getHtmlId((string) $builder->id()), |
| 268 | // `sse-swap` attribute is used by HTMX SSE swap. |
| 269 | 'sse-swap' => $island->getHtmlId((string) $builder->id()), |
| 270 | 'class' => $island_classes, |
| 271 | ], |
| 272 | ]; |
| 273 | } |
| 274 | |
| 275 | return $panes; |
| 88 | private function buildSlots(InstanceInterface $builder, array $islands_enabled_sorted): array { |
| 89 | $builder_data = $builder->getCurrentState(); |
| 90 | |
| 91 | $button_islands = $islands_enabled_sorted[IslandType::Button->value] ?? []; |
| 92 | $library_islands = $islands_enabled_sorted[IslandType::Library->value] ?? []; |
| 93 | $contextual_islands = $islands_enabled_sorted[IslandType::Contextual->value] ?? []; |
| 94 | $menu_islands = $islands_enabled_sorted[IslandType::Menu->value] ?? []; |
| 95 | $view_islands = $islands_enabled_sorted[IslandType::View->value] ?? []; |
| 96 | |
| 97 | $buttons = []; |
| 98 | |
| 99 | if (!empty($button_islands)) { |
| 100 | $buttons = $this->buildPanes($builder, $button_islands, [], [], 'span'); |
| 101 | } |
| 102 | |
| 103 | if (!empty($menu_islands)) { |
| 103 | if (!empty($menu_islands)) { |
| 104 | $menu_islands = $this->buildMenuWrapper($builder, $menu_islands); |
| 105 | } |
| 106 | |
| 107 | if (!empty($library_islands)) { |
| 107 | if (!empty($library_islands)) { |
| 109 | $this->buildBuilderTabs($builder, $library_islands, TRUE), |
| 110 | $this->buildPanes($builder, $library_islands, $builder_data), |
| 111 | ]; |
| 112 | } |
| 113 | |
| 114 | $view_islands_data = $this->prepareViewIslands($builder, $view_islands); |
| 114 | $view_islands_data = $this->prepareViewIslands($builder, $view_islands); |
| 115 | $view_sidebar = $view_islands_data['view_sidebar']; |
| 116 | $view_main = $view_islands_data['view_main']; |
| 117 | |
| 118 | // Library content can be in main or sidebar. |
| 119 | // @todo Move the logic to LibrariesPanel::build(). |
| 120 | // @see https://www.drupal.org/project/display_builder/issues/3542866 |
| 121 | if (isset($view_sidebar['library']) && !empty($library_islands)) { |
| 121 | if (isset($view_sidebar['library']) && !empty($library_islands)) { |
| 121 | if (isset($view_sidebar['library']) && !empty($library_islands)) { |
| 122 | $view_sidebar['library']['content'] = $library_islands; |
| 124 | elseif (isset($view_main['library']) && !empty($library_islands)) { |
| 124 | elseif (isset($view_main['library']) && !empty($library_islands)) { |
| 125 | $view_main['library']['content'] = $library_islands; |
| 126 | } |
| 127 | |
| 128 | if (!empty($contextual_islands)) { |
| 128 | if (!empty($contextual_islands)) { |
| 129 | $contextual_islands = $this->buildContextualIslands($builder, $islands_enabled_sorted); |
| 130 | } |
| 131 | |
| 132 | return [ |
| 133 | 'view_sidebar_buttons' => $view_islands_data['view_sidebar_buttons'], |
| 133 | 'view_sidebar_buttons' => $view_islands_data['view_sidebar_buttons'], |
| 134 | 'view_sidebar' => $view_sidebar, |
| 135 | 'view_main_tabs' => $view_islands_data['view_main_tabs'], |
| 136 | 'view_main' => $view_main, |
| 137 | 'buttons' => $buttons, |
| 138 | 'contextual_islands' => $contextual_islands, |
| 139 | 'menu_islands' => $menu_islands, |
| 289 | private function buildStartButtons(InstanceInterface $builder, array $islands): array { |
| 290 | $build = []; |
| 291 | |
| 292 | foreach ($islands as $island) { |
| 292 | foreach ($islands as $island) { |
| 293 | $island_id = $island->getPluginId(); |
| 294 | |
| 295 | $build[$island_id] = [ |
| 296 | '#type' => 'component', |
| 297 | '#component' => 'display_builder:button', |
| 298 | '#props' => [ |
| 299 | 'id' => \sprintf('start-btn-%s-%s', $builder->id(), $island_id), |
| 300 | 'label' => (string) $island->label(), |
| 301 | 'icon' => $island->getIcon(), |
| 302 | 'attributes' => [ |
| 303 | 'data-open-first-drawer' => TRUE, |
| 304 | 'data-target' => $island_id, |
| 305 | ], |
| 306 | ], |
| 307 | ]; |
| 308 | |
| 309 | if ($keyboard = $island::keyboardShortcuts()) { |
| 292 | foreach ($islands as $island) { |
| 293 | $island_id = $island->getPluginId(); |
| 294 | |
| 295 | $build[$island_id] = [ |
| 296 | '#type' => 'component', |
| 297 | '#component' => 'display_builder:button', |
| 298 | '#props' => [ |
| 299 | 'id' => \sprintf('start-btn-%s-%s', $builder->id(), $island_id), |
| 300 | 'label' => (string) $island->label(), |
| 301 | 'icon' => $island->getIcon(), |
| 302 | 'attributes' => [ |
| 303 | 'data-open-first-drawer' => TRUE, |
| 304 | 'data-target' => $island_id, |
| 305 | ], |
| 306 | ], |
| 307 | ]; |
| 308 | |
| 309 | if ($keyboard = $island::keyboardShortcuts()) { |
| 310 | $build[$island_id]['#attributes']['data-keyboard-key'] = \key($keyboard); |
| 292 | foreach ($islands as $island) { |
| 293 | $island_id = $island->getPluginId(); |
| 294 | |
| 295 | $build[$island_id] = [ |
| 296 | '#type' => 'component', |
| 297 | '#component' => 'display_builder:button', |
| 298 | '#props' => [ |
| 299 | 'id' => \sprintf('start-btn-%s-%s', $builder->id(), $island_id), |
| 300 | 'label' => (string) $island->label(), |
| 301 | 'icon' => $island->getIcon(), |
| 302 | 'attributes' => [ |
| 303 | 'data-open-first-drawer' => TRUE, |
| 304 | 'data-target' => $island_id, |
| 305 | ], |
| 306 | ], |
| 307 | ]; |
| 308 | |
| 309 | if ($keyboard = $island::keyboardShortcuts()) { |
| 310 | $build[$island_id]['#attributes']['data-keyboard-key'] = \key($keyboard); |
| 311 | $build[$island_id]['#attributes']['data-keyboard-help'] = \reset($keyboard); |
| 312 | } |
| 313 | } |
| 314 | |
| 315 | return $build; |
| 420 | if (!isset($this->entityTypeManager)) { |
| 421 | $this->entityTypeManager = \Drupal::service('entity_type.manager'); |
| 422 | } |
| 423 | |
| 424 | return $this->entityTypeManager; |
| 424 | return $this->entityTypeManager; |
| 405 | private function getIslandsEnableSorted(array $contexts): array { |
| 406 | // Set island by weight. |
| 407 | // @todo just key by weight and default weight in Island? |
| 408 | $islands_enable_by_weight = $this->entity->getEnabledIslands(); |
| 409 | |
| 410 | return $this->islandPluginManager()->getIslandsByTypes($contexts, $this->entity->getIslandConfigurations(), $islands_enable_by_weight); |
| 434 | if (!isset($this->islandPluginManager)) { |
| 435 | $this->islandPluginManager = \Drupal::service('plugin.manager.db_island'); |
| 436 | } |
| 437 | |
| 438 | return $this->islandPluginManager; |
| 438 | return $this->islandPluginManager; |
| 154 | private function prepareViewIslands(InstanceInterface $builder, array $islands): array { |
| 155 | $view_islands_sidebar = []; |
| 156 | $view_islands_main = []; |
| 157 | $view_sidebar_buttons = []; |
| 158 | $view_main_tabs = []; |
| 159 | |
| 160 | foreach ($islands as $id => $island) { |
| 160 | foreach ($islands as $id => $island) { |
| 160 | foreach ($islands as $id => $island) { |
| 161 | if ($island->getTypeId() !== IslandType::View->value) { |
| 162 | continue; |
| 165 | $configuration = $island->getConfiguration(); |
| 166 | |
| 167 | if ($configuration['region'] === 'sidebar') { |
| 167 | if ($configuration['region'] === 'sidebar') { |
| 168 | $view_islands_sidebar[$id] = $islands[$id]; |
| 160 | foreach ($islands as $id => $island) { |
| 161 | if ($island->getTypeId() !== IslandType::View->value) { |
| 162 | continue; |
| 163 | } |
| 164 | |
| 165 | $configuration = $island->getConfiguration(); |
| 166 | |
| 167 | if ($configuration['region'] === 'sidebar') { |
| 168 | $view_islands_sidebar[$id] = $islands[$id]; |
| 169 | $view_sidebar_buttons[$id] = $islands[$id]; |
| 170 | } |
| 171 | else { |
| 172 | $view_islands_main[$id] = $islands[$id]; |
| 160 | foreach ($islands as $id => $island) { |
| 161 | if ($island->getTypeId() !== IslandType::View->value) { |
| 162 | continue; |
| 163 | } |
| 164 | |
| 165 | $configuration = $island->getConfiguration(); |
| 166 | |
| 167 | if ($configuration['region'] === 'sidebar') { |
| 168 | $view_islands_sidebar[$id] = $islands[$id]; |
| 169 | $view_sidebar_buttons[$id] = $islands[$id]; |
| 170 | } |
| 171 | else { |
| 172 | $view_islands_main[$id] = $islands[$id]; |
| 173 | $view_main_tabs[$id] = $islands[$id]; |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | if (!empty($view_sidebar_buttons)) { |
| 178 | $view_sidebar_buttons = $this->buildStartButtons($builder, $view_sidebar_buttons); |
| 179 | } |
| 180 | |
| 181 | if (!empty($view_main_tabs)) { |
| 181 | if (!empty($view_main_tabs)) { |
| 182 | $view_main_tabs = $this->buildBuilderTabs($builder, $view_main_tabs, FALSE); |
| 183 | } |
| 184 | |
| 185 | $builder_data = $builder->getCurrentState(); |
| 185 | $builder_data = $builder->getCurrentState(); |
| 186 | $view_sidebar = $this->buildPanes($builder, $view_islands_sidebar, $builder_data); |
| 187 | // Default hidden. |
| 188 | $view_main = $this->buildPanes($builder, $view_islands_main, $builder_data, ['shoelace-tabs__tab--hidden']); |
| 189 | |
| 190 | return [ |
| 191 | 'view_sidebar_buttons' => $view_sidebar_buttons, |
| 192 | 'view_main_tabs' => $view_main_tabs, |
| 193 | 'view_sidebar' => $view_sidebar, |
| 194 | 'view_main' => $view_main, |
| 37 | public function view(EntityInterface $entity, $view_mode = 'full', $langcode = NULL): array { |
| 38 | // We have 'hacked' the interface by using $view_mode as a way of passing |
| 39 | // the Instance entity ID. |
| 40 | $builder_id = $view_mode; |
| 41 | |
| 42 | /** @var \Drupal\display_builder\ProfileInterface $entity */ |
| 43 | $entity = $entity; |
| 44 | $this->entity = $entity; |
| 45 | |
| 46 | /** @var \Drupal\display_builder\InstanceInterface $builder */ |
| 47 | $builder = $this->entityTypeManager()->getStorage('display_builder_instance')->load($builder_id); |
| 48 | $contexts = $builder->getContexts() ?? []; |
| 49 | $islands_enabled_sorted = $this->getIslandsEnableSorted($contexts); |
| 50 | $build = [ |
| 51 | '#type' => 'component', |
| 52 | '#component' => 'display_builder:display_builder', |
| 53 | '#props' => [ |
| 54 | 'builder_id' => $builder_id, |
| 55 | 'hash' => (string) $builder->getCurrent()->hash, |
| 56 | ], |
| 57 | '#slots' => $this->buildSlots($builder, $islands_enabled_sorted), |
| 58 | '#attached' => [ |
| 59 | 'drupalSettings' => [ |
| 60 | 'dbDebug' => $entity->isDebugModeActivated(), |
| 61 | ], |
| 62 | ], |
| 63 | '#cache' => [ |
| 64 | 'tags' => $builder->getCacheTags(), |
| 65 | ], |
| 66 | ]; |
| 67 | |
| 68 | foreach ($islands_enabled_sorted as $islands) { |
| 68 | foreach ($islands_enabled_sorted as $islands) { |
| 69 | foreach ($islands as $island) { |
| 69 | foreach ($islands as $island) { |
| 69 | foreach ($islands as $island) { |
| 70 | $build = $island->alterRenderable($builder, $build); |
| 68 | foreach ($islands_enabled_sorted as $islands) { |
| 69 | foreach ($islands as $island) { |
| 68 | foreach ($islands_enabled_sorted as $islands) { |
| 69 | foreach ($islands as $island) { |
| 70 | $build = $island->alterRenderable($builder, $build); |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | return $build; |