Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
DisplayBuilderRoutes
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 6
182
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSubscribedEvents
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onAlterRoutes
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 buildRoutes
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
 buildDisplayBuilderRoute
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 getEntityTypes
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\display_builder_entity_view\Routing;
6
7use Drupal\Component\Utility\NestedArray;
8use Drupal\Core\DependencyInjection\AutowireTrait;
9use Drupal\Core\Entity\EntityTypeInterface;
10use Drupal\Core\Entity\EntityTypeManagerInterface;
11use Drupal\Core\Extension\ModuleHandlerInterface;
12use Drupal\Core\Routing\RouteBuildEvent;
13use Drupal\Core\Routing\RoutingEvents;
14use Drupal\display_builder\DisplayBuilderHelpers;
15use Drupal\display_builder_entity_view\Controller\EntityViewController;
16use Symfony\Component\EventDispatcher\EventSubscriberInterface;
17use Symfony\Component\Routing\Route;
18use Symfony\Component\Routing\RouteCollection;
19
20/**
21 * Provides routes for the Display Builder UI.
22 *
23 * @internal
24 *   Tagged services are internal.
25 */
26final class DisplayBuilderRoutes implements EventSubscriberInterface {
27
28  use AutowireTrait;
29
30  /**
31   * {@inheritdoc}
32   */
33  public function __construct(
34    private EntityTypeManagerInterface $entityTypeManager,
35    private ModuleHandlerInterface $module_handler,
36  ) {}
37
38  /**
39   * {@inheritdoc}
40   */
41  public static function getSubscribedEvents(): array {
42    $events = [];
43    // Run after \Drupal\field_ui\Routing\RouteSubscriber.
44    $events[RoutingEvents::ALTER] = ['onAlterRoutes', -110];
45
46    return $events;
47  }
48
49  /**
50   * Alters existing routes for a specific collection.
51   *
52   * @param \Drupal\Core\Routing\RouteBuildEvent $event
53   *   The route build event.
54   */
55  public function onAlterRoutes(RouteBuildEvent $event): void {
56    $collection = $event->getRouteCollection();
57    $this->buildRoutes($collection);
58  }
59
60  /**
61   * Build Display Builder routes for each existing entity view display.
62   *
63   * @param \Symfony\Component\Routing\RouteCollection $collection
64   *   The route collection to add the routes to.
65   */
66  private function buildRoutes(RouteCollection $collection): void {
67    if (!$this->module_handler->moduleExists('field_ui')) {
68      return;
69    }
70
71    foreach ($this->getEntityTypes() as $entity_type_id => $entity_type) {
72      // Try to get the route from the current collection.
73      if (!$entity_route = $collection->get($entity_type->get('field_ui_base_route'))) {
74        continue;
75      }
76      $route_name = 'display_builder_entity_view.' . $entity_type_id;
77      $route = $this->buildDisplayBuilderRoute($entity_type, $entity_route);
78      $collection->add($route_name, $route);
79    }
80  }
81
82  /**
83   * Build a Display Builder route from an existing Entity View Display route.
84   *
85   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
86   *   The entity type for which to build the route.
87   * @param \Symfony\Component\Routing\Route $entity_route
88   *   The existing entity view display route.
89   *
90   * @return \Symfony\Component\Routing\Route
91   *   The new route for the Display Builder.
92   */
93  private function buildDisplayBuilderRoute(EntityTypeInterface $entity_type, Route $entity_route): Route {
94    $path = $entity_route->getPath() . '/display/{view_mode_name}/display-builder';
95
96    $defaults = [];
97    $entity_type_id = $entity_type->id();
98    $defaults['entity_type_id'] = $entity_type_id;
99
100    // If the entity type has no bundles and it doesn't use {bundle} in its
101    // admin path, use the entity type.
102    if (!\str_contains($path, '{bundle}')) {
103      if (!$entity_type->hasKey('bundle')) {
104        $defaults['bundle'] = $entity_type_id;
105      }
106      else {
107        $defaults['bundle_key'] = $entity_type->getBundleEntityType();
108      }
109    }
110
111    $requirements = [];
112    $requirements['_field_ui_view_mode_access'] = 'administer ' . $entity_type_id . ' display';
113
114    $options = $entity_route->getOptions();
115    $options['_admin_route'] = FALSE;
116
117    // @todo add the display builder access check
118    // $requirements['_display_builder_access'] = 'view';
119    // Trigger the display builder RouteEnhancer.
120    $parameters = [];
121    // Merge the passed in options in after parameters.
122    $options = NestedArray::mergeDeep(['parameters' => $parameters], $options);
123
124    $defaults['_controller'] = EntityViewController::class . '::getBuilder';
125    $defaults['_title_callback'] = EntityViewController::class . '::title';
126    $route = (new Route($path))->setDefaults($defaults)->setRequirements($requirements)->setOptions($options);
127
128    // Set field_ui.route_enhancer to run on the manage layout form?
129    if (isset($defaults['bundle_key'])) {
130      $route->setOption('_field_ui', TRUE)->setDefault('bundle', '');
131    }
132
133    return $route;
134  }
135
136  /**
137   * Returns an array of relevant entity types.
138   *
139   * @return \Drupal\Core\Entity\EntityTypeInterface[]
140   *   An array of entity types.
141   */
142  private function getEntityTypes(): array {
143    return \array_filter($this->entityTypeManager->getDefinitions(), static function (EntityTypeInterface $entity_type) {
144      return DisplayBuilderHelpers::isDisplayBuilderEntityType($entity_type) && $entity_type->get('field_ui_base_route');
145    });
146  }
147
148}