Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 namespace Drupal\rest\Routing;
|
Chris@0
|
4
|
Chris@0
|
5 use Drupal\Core\Entity\EntityTypeManagerInterface;
|
Chris@0
|
6 use Drupal\Core\Routing\RouteBuildEvent;
|
Chris@0
|
7 use Drupal\Core\Routing\RoutingEvents;
|
Chris@0
|
8 use Drupal\rest\Plugin\Type\ResourcePluginManager;
|
Chris@0
|
9 use Drupal\rest\RestResourceConfigInterface;
|
Chris@0
|
10 use Psr\Log\LoggerInterface;
|
Chris@0
|
11 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
Chris@0
|
12 use Symfony\Component\Routing\RouteCollection;
|
Chris@0
|
13
|
Chris@0
|
14 /**
|
Chris@0
|
15 * Subscriber for REST-style routes.
|
Chris@0
|
16 */
|
Chris@0
|
17 class ResourceRoutes implements EventSubscriberInterface {
|
Chris@0
|
18
|
Chris@0
|
19 /**
|
Chris@0
|
20 * The plugin manager for REST plugins.
|
Chris@0
|
21 *
|
Chris@0
|
22 * @var \Drupal\rest\Plugin\Type\ResourcePluginManager
|
Chris@0
|
23 */
|
Chris@0
|
24 protected $manager;
|
Chris@0
|
25
|
Chris@0
|
26 /**
|
Chris@0
|
27 * The REST resource config storage.
|
Chris@0
|
28 *
|
Chris@18
|
29 * @var \Drupal\Core\Entity\EntityStorageInterface
|
Chris@0
|
30 */
|
Chris@0
|
31 protected $resourceConfigStorage;
|
Chris@0
|
32
|
Chris@0
|
33 /**
|
Chris@0
|
34 * A logger instance.
|
Chris@0
|
35 *
|
Chris@0
|
36 * @var \Psr\Log\LoggerInterface
|
Chris@0
|
37 */
|
Chris@0
|
38 protected $logger;
|
Chris@0
|
39
|
Chris@0
|
40 /**
|
Chris@0
|
41 * Constructs a RouteSubscriber object.
|
Chris@0
|
42 *
|
Chris@0
|
43 * @param \Drupal\rest\Plugin\Type\ResourcePluginManager $manager
|
Chris@0
|
44 * The resource plugin manager.
|
Chris@0
|
45 * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
Chris@0
|
46 * The entity type manager
|
Chris@0
|
47 * @param \Psr\Log\LoggerInterface $logger
|
Chris@0
|
48 * A logger instance.
|
Chris@0
|
49 */
|
Chris@0
|
50 public function __construct(ResourcePluginManager $manager, EntityTypeManagerInterface $entity_type_manager, LoggerInterface $logger) {
|
Chris@0
|
51 $this->manager = $manager;
|
Chris@0
|
52 $this->resourceConfigStorage = $entity_type_manager->getStorage('rest_resource_config');
|
Chris@0
|
53 $this->logger = $logger;
|
Chris@0
|
54 }
|
Chris@0
|
55
|
Chris@0
|
56 /**
|
Chris@0
|
57 * Alters existing routes for a specific collection.
|
Chris@0
|
58 *
|
Chris@0
|
59 * @param \Drupal\Core\Routing\RouteBuildEvent $event
|
Chris@0
|
60 * The route build event.
|
Chris@0
|
61 * @return array
|
Chris@0
|
62 */
|
Chris@0
|
63 public function onDynamicRouteEvent(RouteBuildEvent $event) {
|
Chris@0
|
64 // Iterate over all enabled REST resource config entities.
|
Chris@0
|
65 /** @var \Drupal\rest\RestResourceConfigInterface[] $resource_configs */
|
Chris@0
|
66 $resource_configs = $this->resourceConfigStorage->loadMultiple();
|
Chris@0
|
67 foreach ($resource_configs as $resource_config) {
|
Chris@0
|
68 if ($resource_config->status()) {
|
Chris@0
|
69 $resource_routes = $this->getRoutesForResourceConfig($resource_config);
|
Chris@0
|
70 $event->getRouteCollection()->addCollection($resource_routes);
|
Chris@0
|
71 }
|
Chris@0
|
72 }
|
Chris@0
|
73 }
|
Chris@0
|
74
|
Chris@0
|
75 /**
|
Chris@0
|
76 * Provides all routes for a given REST resource config.
|
Chris@0
|
77 *
|
Chris@0
|
78 * This method determines where a resource is reachable, what path
|
Chris@0
|
79 * replacements are used, the required HTTP method for the operation etc.
|
Chris@0
|
80 *
|
Chris@0
|
81 * @param \Drupal\rest\RestResourceConfigInterface $rest_resource_config
|
Chris@0
|
82 * The rest resource config.
|
Chris@0
|
83 *
|
Chris@0
|
84 * @return \Symfony\Component\Routing\RouteCollection
|
Chris@0
|
85 * The route collection.
|
Chris@0
|
86 */
|
Chris@0
|
87 protected function getRoutesForResourceConfig(RestResourceConfigInterface $rest_resource_config) {
|
Chris@0
|
88 $plugin = $rest_resource_config->getResourcePlugin();
|
Chris@0
|
89 $collection = new RouteCollection();
|
Chris@0
|
90
|
Chris@0
|
91 foreach ($plugin->routes() as $name => $route) {
|
Chris@0
|
92 /** @var \Symfony\Component\Routing\Route $route */
|
Chris@0
|
93 // @todo: Are multiple methods possible here?
|
Chris@0
|
94 $methods = $route->getMethods();
|
Chris@14
|
95 // Only expose routes
|
Chris@14
|
96 // - that have an explicit method and allow >=1 format for that method
|
Chris@14
|
97 // - that exist for BC
|
Chris@14
|
98 // @see \Drupal\rest\RouteProcessor\RestResourceGetRouteProcessorBC
|
Chris@14
|
99 if (($methods && ($method = $methods[0]) && $supported_formats = $rest_resource_config->getFormats($method)) || $route->hasOption('bc_route')) {
|
Chris@0
|
100 $route->setRequirement('_csrf_request_header_token', 'TRUE');
|
Chris@0
|
101
|
Chris@0
|
102 // Check that authentication providers are defined.
|
Chris@0
|
103 if (empty($rest_resource_config->getAuthenticationProviders($method))) {
|
Chris@18
|
104 $this->logger->error('At least one authentication provider must be defined for resource @id', ['@id' => $rest_resource_config->id()]);
|
Chris@0
|
105 continue;
|
Chris@0
|
106 }
|
Chris@0
|
107
|
Chris@0
|
108 // Check that formats are defined.
|
Chris@0
|
109 if (empty($rest_resource_config->getFormats($method))) {
|
Chris@18
|
110 $this->logger->error('At least one format must be defined for resource @id', ['@id' => $rest_resource_config->id()]);
|
Chris@0
|
111 continue;
|
Chris@0
|
112 }
|
Chris@0
|
113
|
Chris@14
|
114 // Remove BC routes for unsupported formats.
|
Chris@14
|
115 if ($route->getOption('bc_route') === TRUE) {
|
Chris@14
|
116 $format_requirement = $route->getRequirement('_format');
|
Chris@14
|
117 if ($format_requirement && !in_array($format_requirement, $rest_resource_config->getFormats($method))) {
|
Chris@14
|
118 continue;
|
Chris@14
|
119 }
|
Chris@0
|
120 }
|
Chris@0
|
121
|
Chris@0
|
122 // The configuration has been validated, so we update the route to:
|
Chris@14
|
123 // - set the allowed response body content types/formats for methods
|
Chris@17
|
124 // that may send response bodies (unless hardcoded by the plugin)
|
Chris@0
|
125 // - set the allowed request body content types/formats for methods that
|
Chris@17
|
126 // allow request bodies to be sent (unless hardcoded by the plugin)
|
Chris@0
|
127 // - set the allowed authentication providers
|
Chris@17
|
128 if (in_array($method, ['GET', 'HEAD', 'POST', 'PUT', 'PATCH'], TRUE) && !$route->hasRequirement('_format')) {
|
Chris@14
|
129 $route->addRequirements(['_format' => implode('|', $rest_resource_config->getFormats($method))]);
|
Chris@14
|
130 }
|
Chris@17
|
131 if (in_array($method, ['POST', 'PATCH', 'PUT'], TRUE) && !$route->hasRequirement('_content_type_format')) {
|
Chris@0
|
132 $route->addRequirements(['_content_type_format' => implode('|', $rest_resource_config->getFormats($method))]);
|
Chris@0
|
133 }
|
Chris@0
|
134 $route->setOption('_auth', $rest_resource_config->getAuthenticationProviders($method));
|
Chris@0
|
135 $route->setDefault('_rest_resource_config', $rest_resource_config->id());
|
Chris@14
|
136 $parameters = $route->getOption('parameters') ?: [];
|
Chris@14
|
137 $route->setOption('parameters', $parameters + [
|
Chris@14
|
138 '_rest_resource_config' => [
|
Chris@14
|
139 'type' => 'entity:' . $rest_resource_config->getEntityTypeId(),
|
Chris@14
|
140 ],
|
Chris@14
|
141 ]);
|
Chris@0
|
142 $collection->add("rest.$name", $route);
|
Chris@0
|
143 }
|
Chris@0
|
144
|
Chris@0
|
145 }
|
Chris@0
|
146 return $collection;
|
Chris@0
|
147 }
|
Chris@0
|
148
|
Chris@0
|
149 /**
|
Chris@0
|
150 * {@inheritdoc}
|
Chris@0
|
151 */
|
Chris@0
|
152 public static function getSubscribedEvents() {
|
Chris@0
|
153 $events[RoutingEvents::DYNAMIC] = 'onDynamicRouteEvent';
|
Chris@0
|
154 return $events;
|
Chris@0
|
155 }
|
Chris@0
|
156
|
Chris@0
|
157 }
|