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