Chris@18
|
1 <?php
|
Chris@18
|
2
|
Chris@18
|
3 namespace Drupal\jsonapi\Routing;
|
Chris@18
|
4
|
Chris@18
|
5 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
Chris@18
|
6 use Drupal\jsonapi\Access\RelationshipFieldAccess;
|
Chris@18
|
7 use Drupal\jsonapi\Controller\EntryPoint;
|
Chris@18
|
8 use Drupal\jsonapi\ParamConverter\ResourceTypeConverter;
|
Chris@18
|
9 use Drupal\jsonapi\ResourceType\ResourceType;
|
Chris@18
|
10 use Drupal\jsonapi\ResourceType\ResourceTypeRepositoryInterface;
|
Chris@18
|
11 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
|
Chris@18
|
12 use Symfony\Component\DependencyInjection\ContainerInterface;
|
Chris@18
|
13 use Symfony\Component\Routing\Route;
|
Chris@18
|
14 use Symfony\Component\Routing\RouteCollection;
|
Chris@18
|
15
|
Chris@18
|
16 /**
|
Chris@18
|
17 * Defines dynamic routes.
|
Chris@18
|
18 *
|
Chris@18
|
19 * @internal JSON:API maintains no PHP API since its API is the HTTP API. This
|
Chris@18
|
20 * class may change at any time and this will break any dependencies on it.
|
Chris@18
|
21 *
|
Chris@18
|
22 * @see https://www.drupal.org/project/jsonapi/issues/3032787
|
Chris@18
|
23 * @see jsonapi.api.php
|
Chris@18
|
24 */
|
Chris@18
|
25 class Routes implements ContainerInjectionInterface {
|
Chris@18
|
26
|
Chris@18
|
27 /**
|
Chris@18
|
28 * The service name for the primary JSON:API controller.
|
Chris@18
|
29 *
|
Chris@18
|
30 * All resources except the entrypoint are served by this controller.
|
Chris@18
|
31 *
|
Chris@18
|
32 * @var string
|
Chris@18
|
33 */
|
Chris@18
|
34 const CONTROLLER_SERVICE_NAME = 'jsonapi.entity_resource';
|
Chris@18
|
35
|
Chris@18
|
36 /**
|
Chris@18
|
37 * A key with which to flag a route as belonging to the JSON:API module.
|
Chris@18
|
38 *
|
Chris@18
|
39 * @var string
|
Chris@18
|
40 */
|
Chris@18
|
41 const JSON_API_ROUTE_FLAG_KEY = '_is_jsonapi';
|
Chris@18
|
42
|
Chris@18
|
43 /**
|
Chris@18
|
44 * The route default key for the route's resource type information.
|
Chris@18
|
45 *
|
Chris@18
|
46 * @var string
|
Chris@18
|
47 */
|
Chris@18
|
48 const RESOURCE_TYPE_KEY = 'resource_type';
|
Chris@18
|
49
|
Chris@18
|
50 /**
|
Chris@18
|
51 * The JSON:API resource type repository.
|
Chris@18
|
52 *
|
Chris@18
|
53 * @var \Drupal\jsonapi\ResourceType\ResourceTypeRepositoryInterface
|
Chris@18
|
54 */
|
Chris@18
|
55 protected $resourceTypeRepository;
|
Chris@18
|
56
|
Chris@18
|
57 /**
|
Chris@18
|
58 * List of providers.
|
Chris@18
|
59 *
|
Chris@18
|
60 * @var string[]
|
Chris@18
|
61 */
|
Chris@18
|
62 protected $providerIds;
|
Chris@18
|
63
|
Chris@18
|
64 /**
|
Chris@18
|
65 * The JSON:API base path.
|
Chris@18
|
66 *
|
Chris@18
|
67 * @var string
|
Chris@18
|
68 */
|
Chris@18
|
69 protected $jsonApiBasePath;
|
Chris@18
|
70
|
Chris@18
|
71 /**
|
Chris@18
|
72 * Instantiates a Routes object.
|
Chris@18
|
73 *
|
Chris@18
|
74 * @param \Drupal\jsonapi\ResourceType\ResourceTypeRepositoryInterface $resource_type_repository
|
Chris@18
|
75 * The JSON:API resource type repository.
|
Chris@18
|
76 * @param string[] $authentication_providers
|
Chris@18
|
77 * The authentication providers, keyed by ID.
|
Chris@18
|
78 * @param string $jsonapi_base_path
|
Chris@18
|
79 * The JSON:API base path.
|
Chris@18
|
80 */
|
Chris@18
|
81 public function __construct(ResourceTypeRepositoryInterface $resource_type_repository, array $authentication_providers, $jsonapi_base_path) {
|
Chris@18
|
82 $this->resourceTypeRepository = $resource_type_repository;
|
Chris@18
|
83 $this->providerIds = array_keys($authentication_providers);
|
Chris@18
|
84 assert(is_string($jsonapi_base_path));
|
Chris@18
|
85 assert(
|
Chris@18
|
86 $jsonapi_base_path[0] === '/',
|
Chris@18
|
87 sprintf('The provided base path should contain a leading slash "/". Given: "%s".', $jsonapi_base_path)
|
Chris@18
|
88 );
|
Chris@18
|
89 assert(
|
Chris@18
|
90 substr($jsonapi_base_path, -1) !== '/',
|
Chris@18
|
91 sprintf('The provided base path should not contain a trailing slash "/". Given: "%s".', $jsonapi_base_path)
|
Chris@18
|
92 );
|
Chris@18
|
93 $this->jsonApiBasePath = $jsonapi_base_path;
|
Chris@18
|
94 }
|
Chris@18
|
95
|
Chris@18
|
96 /**
|
Chris@18
|
97 * {@inheritdoc}
|
Chris@18
|
98 */
|
Chris@18
|
99 public static function create(ContainerInterface $container) {
|
Chris@18
|
100 return new static(
|
Chris@18
|
101 $container->get('jsonapi.resource_type.repository'),
|
Chris@18
|
102 $container->getParameter('authentication_providers'),
|
Chris@18
|
103 $container->getParameter('jsonapi.base_path')
|
Chris@18
|
104 );
|
Chris@18
|
105 }
|
Chris@18
|
106
|
Chris@18
|
107 /**
|
Chris@18
|
108 * {@inheritdoc}
|
Chris@18
|
109 */
|
Chris@18
|
110 public function routes() {
|
Chris@18
|
111 $routes = new RouteCollection();
|
Chris@18
|
112 $upload_routes = new RouteCollection();
|
Chris@18
|
113
|
Chris@18
|
114 // JSON:API's routes: entry point + routes for every resource type.
|
Chris@18
|
115 foreach ($this->resourceTypeRepository->all() as $resource_type) {
|
Chris@18
|
116 $routes->addCollection(static::getRoutesForResourceType($resource_type, $this->jsonApiBasePath));
|
Chris@18
|
117 $upload_routes->addCollection(static::getFileUploadRoutesForResourceType($resource_type, $this->jsonApiBasePath));
|
Chris@18
|
118 }
|
Chris@18
|
119 $routes->add('jsonapi.resource_list', static::getEntryPointRoute($this->jsonApiBasePath));
|
Chris@18
|
120
|
Chris@18
|
121 // Require the JSON:API media type header on every route, except on file
|
Chris@18
|
122 // upload routes, where we require `application/octet-stream`.
|
Chris@18
|
123 $routes->addRequirements(['_content_type_format' => 'api_json']);
|
Chris@18
|
124 $upload_routes->addRequirements(['_content_type_format' => 'bin']);
|
Chris@18
|
125
|
Chris@18
|
126 $routes->addCollection($upload_routes);
|
Chris@18
|
127
|
Chris@18
|
128 // Enable all available authentication providers.
|
Chris@18
|
129 $routes->addOptions(['_auth' => $this->providerIds]);
|
Chris@18
|
130
|
Chris@18
|
131 // Flag every route as belonging to the JSON:API module.
|
Chris@18
|
132 $routes->addDefaults([static::JSON_API_ROUTE_FLAG_KEY => TRUE]);
|
Chris@18
|
133
|
Chris@18
|
134 // All routes serve only the JSON:API media type.
|
Chris@18
|
135 $routes->addRequirements(['_format' => 'api_json']);
|
Chris@18
|
136
|
Chris@18
|
137 return $routes;
|
Chris@18
|
138 }
|
Chris@18
|
139
|
Chris@18
|
140 /**
|
Chris@18
|
141 * Gets applicable resource routes for a JSON:API resource type.
|
Chris@18
|
142 *
|
Chris@18
|
143 * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type
|
Chris@18
|
144 * The JSON:API resource type for which to get the routes.
|
Chris@18
|
145 * @param string $path_prefix
|
Chris@18
|
146 * The root path prefix.
|
Chris@18
|
147 *
|
Chris@18
|
148 * @return \Symfony\Component\Routing\RouteCollection
|
Chris@18
|
149 * A collection of routes for the given resource type.
|
Chris@18
|
150 */
|
Chris@18
|
151 protected static function getRoutesForResourceType(ResourceType $resource_type, $path_prefix) {
|
Chris@18
|
152 // Internal resources have no routes.
|
Chris@18
|
153 if ($resource_type->isInternal()) {
|
Chris@18
|
154 return new RouteCollection();
|
Chris@18
|
155 }
|
Chris@18
|
156
|
Chris@18
|
157 $routes = new RouteCollection();
|
Chris@18
|
158
|
Chris@18
|
159 // Collection route like `/jsonapi/node/article`.
|
Chris@18
|
160 if ($resource_type->isLocatable()) {
|
Chris@18
|
161 $collection_route = new Route("/{$resource_type->getPath()}");
|
Chris@18
|
162 $collection_route->addDefaults([RouteObjectInterface::CONTROLLER_NAME => static::CONTROLLER_SERVICE_NAME . ':getCollection']);
|
Chris@18
|
163 $collection_route->setMethods(['GET']);
|
Chris@18
|
164 // Allow anybody access because "view" and "view label" access are checked
|
Chris@18
|
165 // in the controller.
|
Chris@18
|
166 $collection_route->setRequirement('_access', 'TRUE');
|
Chris@18
|
167 $routes->add(static::getRouteName($resource_type, 'collection'), $collection_route);
|
Chris@18
|
168 }
|
Chris@18
|
169
|
Chris@18
|
170 // Creation route.
|
Chris@18
|
171 if ($resource_type->isMutable()) {
|
Chris@18
|
172 $collection_create_route = new Route("/{$resource_type->getPath()}");
|
Chris@18
|
173 $collection_create_route->addDefaults([RouteObjectInterface::CONTROLLER_NAME => static::CONTROLLER_SERVICE_NAME . ':createIndividual']);
|
Chris@18
|
174 $collection_create_route->setMethods(['POST']);
|
Chris@18
|
175 $create_requirement = sprintf("%s:%s", $resource_type->getEntityTypeId(), $resource_type->getBundle());
|
Chris@18
|
176 $collection_create_route->setRequirement('_entity_create_access', $create_requirement);
|
Chris@18
|
177 $collection_create_route->setRequirement('_csrf_request_header_token', 'TRUE');
|
Chris@18
|
178 $routes->add(static::getRouteName($resource_type, 'collection.post'), $collection_create_route);
|
Chris@18
|
179 }
|
Chris@18
|
180
|
Chris@18
|
181 // Individual routes like `/jsonapi/node/article/{uuid}` or
|
Chris@18
|
182 // `/jsonapi/node/article/{uuid}/relationships/uid`.
|
Chris@18
|
183 $routes->addCollection(static::getIndividualRoutesForResourceType($resource_type));
|
Chris@18
|
184
|
Chris@18
|
185 // Add the resource type as a parameter to every resource route.
|
Chris@18
|
186 foreach ($routes as $route) {
|
Chris@18
|
187 static::addRouteParameter($route, static::RESOURCE_TYPE_KEY, ['type' => ResourceTypeConverter::PARAM_TYPE_ID]);
|
Chris@18
|
188 $route->addDefaults([static::RESOURCE_TYPE_KEY => $resource_type->getTypeName()]);
|
Chris@18
|
189 }
|
Chris@18
|
190
|
Chris@18
|
191 // Resource routes all have the same base path.
|
Chris@18
|
192 $routes->addPrefix($path_prefix);
|
Chris@18
|
193
|
Chris@18
|
194 return $routes;
|
Chris@18
|
195 }
|
Chris@18
|
196
|
Chris@18
|
197 /**
|
Chris@18
|
198 * Gets the file upload route collection for the given resource type.
|
Chris@18
|
199 *
|
Chris@18
|
200 * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type
|
Chris@18
|
201 * The resource type for which the route collection should be created.
|
Chris@18
|
202 * @param string $path_prefix
|
Chris@18
|
203 * The root path prefix.
|
Chris@18
|
204 *
|
Chris@18
|
205 * @return \Symfony\Component\Routing\RouteCollection
|
Chris@18
|
206 * The route collection.
|
Chris@18
|
207 */
|
Chris@18
|
208 protected static function getFileUploadRoutesForResourceType(ResourceType $resource_type, $path_prefix) {
|
Chris@18
|
209 $routes = new RouteCollection();
|
Chris@18
|
210
|
Chris@18
|
211 // Internal resources have no routes; individual routes require locations.
|
Chris@18
|
212 if ($resource_type->isInternal() || !$resource_type->isLocatable()) {
|
Chris@18
|
213 return $routes;
|
Chris@18
|
214 }
|
Chris@18
|
215
|
Chris@18
|
216 // File upload routes are only necessary for resource types that have file
|
Chris@18
|
217 // fields.
|
Chris@18
|
218 $has_file_field = array_reduce($resource_type->getRelatableResourceTypes(), function ($carry, array $target_resource_types) {
|
Chris@18
|
219 return $carry || static::hasNonInternalFileTargetResourceTypes($target_resource_types);
|
Chris@18
|
220 }, FALSE);
|
Chris@18
|
221 if (!$has_file_field) {
|
Chris@18
|
222 return $routes;
|
Chris@18
|
223 }
|
Chris@18
|
224
|
Chris@18
|
225 if ($resource_type->isMutable()) {
|
Chris@18
|
226 $path = $resource_type->getPath();
|
Chris@18
|
227 $entity_type_id = $resource_type->getEntityTypeId();
|
Chris@18
|
228
|
Chris@18
|
229 $new_resource_file_upload_route = new Route("/{$path}/{file_field_name}");
|
Chris@18
|
230 $new_resource_file_upload_route->addDefaults([RouteObjectInterface::CONTROLLER_NAME => 'jsonapi.file_upload:handleFileUploadForNewResource']);
|
Chris@18
|
231 $new_resource_file_upload_route->setMethods(['POST']);
|
Chris@18
|
232 $new_resource_file_upload_route->setRequirement('_csrf_request_header_token', 'TRUE');
|
Chris@18
|
233 $routes->add(static::getFileUploadRouteName($resource_type, 'new_resource'), $new_resource_file_upload_route);
|
Chris@18
|
234
|
Chris@18
|
235 $existing_resource_file_upload_route = new Route("/{$path}/{entity}/{file_field_name}");
|
Chris@18
|
236 $existing_resource_file_upload_route->addDefaults([RouteObjectInterface::CONTROLLER_NAME => 'jsonapi.file_upload:handleFileUploadForExistingResource']);
|
Chris@18
|
237 $existing_resource_file_upload_route->setMethods(['POST']);
|
Chris@18
|
238 $existing_resource_file_upload_route->setRequirement('_csrf_request_header_token', 'TRUE');
|
Chris@18
|
239 $routes->add(static::getFileUploadRouteName($resource_type, 'existing_resource'), $existing_resource_file_upload_route);
|
Chris@18
|
240
|
Chris@18
|
241 // Add entity parameter conversion to every route.
|
Chris@18
|
242 $routes->addOptions(['parameters' => ['entity' => ['type' => 'entity:' . $entity_type_id]]]);
|
Chris@18
|
243
|
Chris@18
|
244 // Add the resource type as a parameter to every resource route.
|
Chris@18
|
245 foreach ($routes as $route) {
|
Chris@18
|
246 static::addRouteParameter($route, static::RESOURCE_TYPE_KEY, ['type' => ResourceTypeConverter::PARAM_TYPE_ID]);
|
Chris@18
|
247 $route->addDefaults([static::RESOURCE_TYPE_KEY => $resource_type->getTypeName()]);
|
Chris@18
|
248 }
|
Chris@18
|
249 }
|
Chris@18
|
250
|
Chris@18
|
251 // File upload routes all have the same base path.
|
Chris@18
|
252 $routes->addPrefix($path_prefix);
|
Chris@18
|
253
|
Chris@18
|
254 return $routes;
|
Chris@18
|
255 }
|
Chris@18
|
256
|
Chris@18
|
257 /**
|
Chris@18
|
258 * Determines if the given request is for a JSON:API generated route.
|
Chris@18
|
259 *
|
Chris@18
|
260 * @param array $defaults
|
Chris@18
|
261 * The request's route defaults.
|
Chris@18
|
262 *
|
Chris@18
|
263 * @return bool
|
Chris@18
|
264 * Whether the request targets a generated route.
|
Chris@18
|
265 */
|
Chris@18
|
266 public static function isJsonApiRequest(array $defaults) {
|
Chris@18
|
267 return isset($defaults[RouteObjectInterface::CONTROLLER_NAME])
|
Chris@18
|
268 && strpos($defaults[RouteObjectInterface::CONTROLLER_NAME], static::CONTROLLER_SERVICE_NAME) === 0;
|
Chris@18
|
269 }
|
Chris@18
|
270
|
Chris@18
|
271 /**
|
Chris@18
|
272 * Gets a route collection for the given resource type.
|
Chris@18
|
273 *
|
Chris@18
|
274 * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type
|
Chris@18
|
275 * The resource type for which the route collection should be created.
|
Chris@18
|
276 *
|
Chris@18
|
277 * @return \Symfony\Component\Routing\RouteCollection
|
Chris@18
|
278 * The route collection.
|
Chris@18
|
279 */
|
Chris@18
|
280 protected static function getIndividualRoutesForResourceType(ResourceType $resource_type) {
|
Chris@18
|
281 if (!$resource_type->isLocatable()) {
|
Chris@18
|
282 return new RouteCollection();
|
Chris@18
|
283 }
|
Chris@18
|
284
|
Chris@18
|
285 $routes = new RouteCollection();
|
Chris@18
|
286
|
Chris@18
|
287 $path = $resource_type->getPath();
|
Chris@18
|
288 $entity_type_id = $resource_type->getEntityTypeId();
|
Chris@18
|
289
|
Chris@18
|
290 // Individual read, update and remove.
|
Chris@18
|
291 $individual_route = new Route("/{$path}/{entity}");
|
Chris@18
|
292 $individual_route->addDefaults([RouteObjectInterface::CONTROLLER_NAME => static::CONTROLLER_SERVICE_NAME . ':getIndividual']);
|
Chris@18
|
293 $individual_route->setMethods(['GET']);
|
Chris@18
|
294 // No _entity_access requirement because "view" and "view label" access are
|
Chris@18
|
295 // checked in the controller. So it's safe to allow anybody access.
|
Chris@18
|
296 $individual_route->setRequirement('_access', 'TRUE');
|
Chris@18
|
297 $routes->add(static::getRouteName($resource_type, 'individual'), $individual_route);
|
Chris@18
|
298 if ($resource_type->isMutable()) {
|
Chris@18
|
299 $individual_update_route = new Route($individual_route->getPath());
|
Chris@18
|
300 $individual_update_route->addDefaults([RouteObjectInterface::CONTROLLER_NAME => static::CONTROLLER_SERVICE_NAME . ':patchIndividual']);
|
Chris@18
|
301 $individual_update_route->setMethods(['PATCH']);
|
Chris@18
|
302 $individual_update_route->setRequirement('_entity_access', "entity.update");
|
Chris@18
|
303 $individual_update_route->setRequirement('_csrf_request_header_token', 'TRUE');
|
Chris@18
|
304 $routes->add(static::getRouteName($resource_type, 'individual.patch'), $individual_update_route);
|
Chris@18
|
305 $individual_remove_route = new Route($individual_route->getPath());
|
Chris@18
|
306 $individual_remove_route->addDefaults([RouteObjectInterface::CONTROLLER_NAME => static::CONTROLLER_SERVICE_NAME . ':deleteIndividual']);
|
Chris@18
|
307 $individual_remove_route->setMethods(['DELETE']);
|
Chris@18
|
308 $individual_remove_route->setRequirement('_entity_access', "entity.delete");
|
Chris@18
|
309 $individual_remove_route->setRequirement('_csrf_request_header_token', 'TRUE');
|
Chris@18
|
310 $routes->add(static::getRouteName($resource_type, 'individual.delete'), $individual_remove_route);
|
Chris@18
|
311 }
|
Chris@18
|
312
|
Chris@18
|
313 foreach ($resource_type->getRelatableResourceTypes() as $relationship_field_name => $target_resource_types) {
|
Chris@18
|
314 // Read, update, add, or remove an individual resources relationships to
|
Chris@18
|
315 // other resources.
|
Chris@18
|
316 $relationship_route = new Route("/{$path}/{entity}/relationships/{$relationship_field_name}");
|
Chris@18
|
317 $relationship_route->addDefaults(['_on_relationship' => TRUE]);
|
Chris@18
|
318 $relationship_route->addDefaults(['related' => $relationship_field_name]);
|
Chris@18
|
319 $relationship_route->setRequirement(RelationshipFieldAccess::ROUTE_REQUIREMENT_KEY, $relationship_field_name);
|
Chris@18
|
320 $relationship_route->setRequirement('_csrf_request_header_token', 'TRUE');
|
Chris@18
|
321 $relationship_route_methods = $resource_type->isMutable()
|
Chris@18
|
322 ? ['GET', 'POST', 'PATCH', 'DELETE']
|
Chris@18
|
323 : ['GET'];
|
Chris@18
|
324 $relationship_controller_methods = [
|
Chris@18
|
325 'GET' => 'getRelationship',
|
Chris@18
|
326 'POST' => 'addToRelationshipData',
|
Chris@18
|
327 'PATCH' => 'replaceRelationshipData',
|
Chris@18
|
328 'DELETE' => 'removeFromRelationshipData',
|
Chris@18
|
329 ];
|
Chris@18
|
330 foreach ($relationship_route_methods as $method) {
|
Chris@18
|
331 $method_specific_relationship_route = clone $relationship_route;
|
Chris@18
|
332 $method_specific_relationship_route->addDefaults([RouteObjectInterface::CONTROLLER_NAME => static::CONTROLLER_SERVICE_NAME . ":{$relationship_controller_methods[$method]}"]);
|
Chris@18
|
333 $method_specific_relationship_route->setMethods($method);
|
Chris@18
|
334 $routes->add(static::getRouteName($resource_type, sprintf("%s.relationship.%s", $relationship_field_name, strtolower($method))), $method_specific_relationship_route);
|
Chris@18
|
335 }
|
Chris@18
|
336
|
Chris@18
|
337 // Only create routes for related routes that target at least one
|
Chris@18
|
338 // non-internal resource type.
|
Chris@18
|
339 if (static::hasNonInternalTargetResourceTypes($target_resource_types)) {
|
Chris@18
|
340 // Get an individual resource's related resources.
|
Chris@18
|
341 $related_route = new Route("/{$path}/{entity}/{$relationship_field_name}");
|
Chris@18
|
342 $related_route->setMethods(['GET']);
|
Chris@18
|
343 $related_route->addDefaults([RouteObjectInterface::CONTROLLER_NAME => static::CONTROLLER_SERVICE_NAME . ':getRelated']);
|
Chris@18
|
344 $related_route->addDefaults(['related' => $relationship_field_name]);
|
Chris@18
|
345 $related_route->setRequirement(RelationshipFieldAccess::ROUTE_REQUIREMENT_KEY, $relationship_field_name);
|
Chris@18
|
346 $routes->add(static::getRouteName($resource_type, "$relationship_field_name.related"), $related_route);
|
Chris@18
|
347 }
|
Chris@18
|
348 }
|
Chris@18
|
349
|
Chris@18
|
350 // Add entity parameter conversion to every route.
|
Chris@18
|
351 $routes->addOptions(['parameters' => ['entity' => ['type' => 'entity:' . $entity_type_id]]]);
|
Chris@18
|
352
|
Chris@18
|
353 return $routes;
|
Chris@18
|
354 }
|
Chris@18
|
355
|
Chris@18
|
356 /**
|
Chris@18
|
357 * Provides the entry point route.
|
Chris@18
|
358 *
|
Chris@18
|
359 * @param string $path_prefix
|
Chris@18
|
360 * The root path prefix.
|
Chris@18
|
361 *
|
Chris@18
|
362 * @return \Symfony\Component\Routing\Route
|
Chris@18
|
363 * The entry point route.
|
Chris@18
|
364 */
|
Chris@18
|
365 protected function getEntryPointRoute($path_prefix) {
|
Chris@18
|
366 $entry_point = new Route("/{$path_prefix}");
|
Chris@18
|
367 $entry_point->addDefaults([RouteObjectInterface::CONTROLLER_NAME => EntryPoint::class . '::index']);
|
Chris@18
|
368 $entry_point->setRequirement('_access', 'TRUE');
|
Chris@18
|
369 $entry_point->setMethods(['GET']);
|
Chris@18
|
370 return $entry_point;
|
Chris@18
|
371 }
|
Chris@18
|
372
|
Chris@18
|
373 /**
|
Chris@18
|
374 * Adds a parameter option to a route, overrides options of the same name.
|
Chris@18
|
375 *
|
Chris@18
|
376 * The Symfony Route class only has a method for adding options which
|
Chris@18
|
377 * overrides any previous values. Therefore, it is tedious to add a single
|
Chris@18
|
378 * parameter while keeping those that are already set.
|
Chris@18
|
379 *
|
Chris@18
|
380 * @param \Symfony\Component\Routing\Route $route
|
Chris@18
|
381 * The route to which the parameter is to be added.
|
Chris@18
|
382 * @param string $name
|
Chris@18
|
383 * The name of the parameter.
|
Chris@18
|
384 * @param mixed $parameter
|
Chris@18
|
385 * The parameter's options.
|
Chris@18
|
386 */
|
Chris@18
|
387 protected static function addRouteParameter(Route $route, $name, $parameter) {
|
Chris@18
|
388 $parameters = $route->getOption('parameters') ?: [];
|
Chris@18
|
389 $parameters[$name] = $parameter;
|
Chris@18
|
390 $route->setOption('parameters', $parameters);
|
Chris@18
|
391 }
|
Chris@18
|
392
|
Chris@18
|
393 /**
|
Chris@18
|
394 * Get a unique route name for the JSON:API resource type and route type.
|
Chris@18
|
395 *
|
Chris@18
|
396 * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type
|
Chris@18
|
397 * The resource type for which the route collection should be created.
|
Chris@18
|
398 * @param string $route_type
|
Chris@18
|
399 * The route type. E.g. 'individual' or 'collection'.
|
Chris@18
|
400 *
|
Chris@18
|
401 * @return string
|
Chris@18
|
402 * The generated route name.
|
Chris@18
|
403 */
|
Chris@18
|
404 public static function getRouteName(ResourceType $resource_type, $route_type) {
|
Chris@18
|
405 return sprintf('jsonapi.%s.%s', $resource_type->getTypeName(), $route_type);
|
Chris@18
|
406 }
|
Chris@18
|
407
|
Chris@18
|
408 /**
|
Chris@18
|
409 * Get a unique route name for the file upload resource type and route type.
|
Chris@18
|
410 *
|
Chris@18
|
411 * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type
|
Chris@18
|
412 * The resource type for which the route collection should be created.
|
Chris@18
|
413 * @param string $route_type
|
Chris@18
|
414 * The route type. E.g. 'individual' or 'collection'.
|
Chris@18
|
415 *
|
Chris@18
|
416 * @return string
|
Chris@18
|
417 * The generated route name.
|
Chris@18
|
418 */
|
Chris@18
|
419 protected static function getFileUploadRouteName(ResourceType $resource_type, $route_type) {
|
Chris@18
|
420 return sprintf('jsonapi.%s.%s.%s', $resource_type->getTypeName(), 'file_upload', $route_type);
|
Chris@18
|
421 }
|
Chris@18
|
422
|
Chris@18
|
423 /**
|
Chris@18
|
424 * Determines if an array of resource types has any non-internal ones.
|
Chris@18
|
425 *
|
Chris@18
|
426 * @param \Drupal\jsonapi\ResourceType\ResourceType[] $resource_types
|
Chris@18
|
427 * The resource types to check.
|
Chris@18
|
428 *
|
Chris@18
|
429 * @return bool
|
Chris@18
|
430 * TRUE if there is at least one non-internal resource type in the given
|
Chris@18
|
431 * array; FALSE otherwise.
|
Chris@18
|
432 */
|
Chris@18
|
433 protected static function hasNonInternalTargetResourceTypes(array $resource_types) {
|
Chris@18
|
434 return array_reduce($resource_types, function ($carry, ResourceType $target) {
|
Chris@18
|
435 return $carry || !$target->isInternal();
|
Chris@18
|
436 }, FALSE);
|
Chris@18
|
437 }
|
Chris@18
|
438
|
Chris@18
|
439 /**
|
Chris@18
|
440 * Determines if an array of resource types lists non-internal "file" ones.
|
Chris@18
|
441 *
|
Chris@18
|
442 * @param \Drupal\jsonapi\ResourceType\ResourceType[] $resource_types
|
Chris@18
|
443 * The resource types to check.
|
Chris@18
|
444 *
|
Chris@18
|
445 * @return bool
|
Chris@18
|
446 * TRUE if there is at least one non-internal "file" resource type in the
|
Chris@18
|
447 * given array; FALSE otherwise.
|
Chris@18
|
448 */
|
Chris@18
|
449 protected static function hasNonInternalFileTargetResourceTypes(array $resource_types) {
|
Chris@18
|
450 return array_reduce($resource_types, function ($carry, ResourceType $target) {
|
Chris@18
|
451 return $carry || (!$target->isInternal() && $target->getEntityTypeId() === 'file');
|
Chris@18
|
452 }, FALSE);
|
Chris@18
|
453 }
|
Chris@18
|
454
|
Chris@18
|
455 /**
|
Chris@18
|
456 * Gets the resource type from a route or request's parameters.
|
Chris@18
|
457 *
|
Chris@18
|
458 * @param array $parameters
|
Chris@18
|
459 * An array of parameters. These may be obtained from a route's
|
Chris@18
|
460 * parameter defaults or from a request object.
|
Chris@18
|
461 *
|
Chris@18
|
462 * @return \Drupal\jsonapi\ResourceType\ResourceType|null
|
Chris@18
|
463 * The resource type, NULL if one cannot be found from the given parameters.
|
Chris@18
|
464 */
|
Chris@18
|
465 public static function getResourceTypeNameFromParameters(array $parameters) {
|
Chris@18
|
466 if (isset($parameters[static::JSON_API_ROUTE_FLAG_KEY]) && $parameters[static::JSON_API_ROUTE_FLAG_KEY]) {
|
Chris@18
|
467 return isset($parameters[static::RESOURCE_TYPE_KEY]) ? $parameters[static::RESOURCE_TYPE_KEY] : NULL;
|
Chris@18
|
468 }
|
Chris@18
|
469 return NULL;
|
Chris@18
|
470 }
|
Chris@18
|
471
|
Chris@18
|
472 /**
|
Chris@18
|
473 * Invalidates any JSON:API resource type dependent responses and routes.
|
Chris@18
|
474 */
|
Chris@18
|
475 public static function rebuild() {
|
Chris@18
|
476 \Drupal::service('cache_tags.invalidator')->invalidateTags(['jsonapi_resource_types']);
|
Chris@18
|
477 \Drupal::service('router.builder')->setRebuildNeeded();
|
Chris@18
|
478 }
|
Chris@18
|
479
|
Chris@18
|
480 }
|