Chris@0: serializerFormats = $serializer_formats; Chris@0: $this->logger = $logger; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { Chris@0: return new static( Chris@0: $configuration, Chris@0: $plugin_id, Chris@0: $plugin_definition, Chris@0: $container->getParameter('serializer.formats'), Chris@0: $container->get('logger.factory')->get('rest') Chris@0: ); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements ResourceInterface::permissions(). Chris@0: * Chris@0: * Every plugin operation method gets its own user permission. Example: Chris@0: * "restful delete entity:node" with the title "Access DELETE on Node Chris@0: * resource". Chris@0: */ Chris@0: public function permissions() { Chris@0: $permissions = []; Chris@0: $definition = $this->getPluginDefinition(); Chris@0: foreach ($this->availableMethods() as $method) { Chris@0: $lowered_method = strtolower($method); Chris@0: $permissions["restful $lowered_method $this->pluginId"] = [ Chris@0: 'title' => $this->t('Access @method on %label resource', ['@method' => $method, '%label' => $definition['label']]), Chris@0: ]; Chris@0: } Chris@0: return $permissions; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function routes() { Chris@0: $collection = new RouteCollection(); Chris@0: Chris@0: $definition = $this->getPluginDefinition(); Chris@0: $canonical_path = isset($definition['uri_paths']['canonical']) ? $definition['uri_paths']['canonical'] : '/' . strtr($this->pluginId, ':', '/') . '/{id}'; Chris@0: $create_path = isset($definition['uri_paths']['create']) ? $definition['uri_paths']['create'] : '/' . strtr($this->pluginId, ':', '/'); Chris@0: // BC: the REST module originally created the POST URL for a resource by Chris@0: // reading the 'https://www.drupal.org/link-relations/create' URI path from Chris@0: // the plugin annotation. For consistency with entity type definitions, that Chris@0: // then changed to reading the 'create' URI path. For any REST Resource Chris@0: // plugins that were using the old mechanism, we continue to support that. Chris@0: if (!isset($definition['uri_paths']['create']) && isset($definition['uri_paths']['https://www.drupal.org/link-relations/create'])) { Chris@0: $create_path = $definition['uri_paths']['https://www.drupal.org/link-relations/create']; Chris@0: } Chris@0: Chris@0: $route_name = strtr($this->pluginId, ':', '.'); Chris@0: Chris@0: $methods = $this->availableMethods(); Chris@0: foreach ($methods as $method) { Chris@14: $path = $method === 'POST' Chris@14: ? $create_path Chris@14: : $canonical_path; Chris@14: $route = $this->getBaseRoute($path, $method); Chris@0: Chris@14: // Note that '_format' and '_content_type_format' route requirements are Chris@14: // added in ResourceRoutes::getRoutesForResourceConfig(). Chris@14: $collection->add("$route_name.$method", $route); Chris@0: Chris@14: // BC: the REST module originally created per-format GET routes, instead Chris@14: // of a single route. To minimize the surface of this BC layer, this uses Chris@14: // route definitions that are as empty as possible, plus an outbound route Chris@14: // processor. Chris@14: // @see \Drupal\rest\RouteProcessor\RestResourceGetRouteProcessorBC Chris@14: if ($method === 'GET' || $method === 'HEAD') { Chris@14: foreach ($this->serializerFormats as $format_name) { Chris@14: $collection->add("$route_name.$method.$format_name", (new BcRoute())->setRequirement('_format', $format_name)); Chris@14: } Chris@0: } Chris@0: } Chris@0: Chris@0: return $collection; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Provides predefined HTTP request methods. Chris@0: * Chris@0: * Plugins can override this method to provide additional custom request Chris@0: * methods. Chris@0: * Chris@0: * @return array Chris@0: * The list of allowed HTTP request method strings. Chris@0: */ Chris@0: protected function requestMethods() { Chris@0: return [ Chris@0: 'HEAD', Chris@0: 'GET', Chris@0: 'POST', Chris@0: 'PUT', Chris@0: 'DELETE', Chris@0: 'TRACE', Chris@0: 'OPTIONS', Chris@0: 'CONNECT', Chris@0: 'PATCH', Chris@0: ]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function availableMethods() { Chris@0: $methods = $this->requestMethods(); Chris@0: $available = []; Chris@0: foreach ($methods as $method) { Chris@0: // Only expose methods where the HTTP request method exists on the plugin. Chris@0: if (method_exists($this, strtolower($method))) { Chris@0: $available[] = $method; Chris@0: } Chris@0: } Chris@0: return $available; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the base route for a particular method. Chris@0: * Chris@0: * @param string $canonical_path Chris@0: * The canonical path for the resource. Chris@0: * @param string $method Chris@0: * The HTTP method to be used for the route. Chris@0: * Chris@0: * @return \Symfony\Component\Routing\Route Chris@0: * The created base route. Chris@0: */ Chris@0: protected function getBaseRoute($canonical_path, $method) { Chris@0: return new Route($canonical_path, [ Chris@0: '_controller' => 'Drupal\rest\RequestHandler::handle', Chris@0: ], Chris@0: $this->getBaseRouteRequirements($method), Chris@0: [], Chris@0: '', Chris@0: [], Chris@0: // The HTTP method is a requirement for this route. Chris@0: [$method] Chris@0: ); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the base route requirements for a particular method. Chris@0: * Chris@0: * @param $method Chris@0: * The HTTP method to be used for the route. Chris@0: * Chris@0: * @return array Chris@0: * An array of requirements for parameters. Chris@0: */ Chris@0: protected function getBaseRouteRequirements($method) { Chris@0: $lower_method = strtolower($method); Chris@0: // Every route MUST have requirements that result in the access manager Chris@0: // having access checks to check. If it does not, the route is made Chris@0: // inaccessible. So, we default to granting access to everyone. If a Chris@0: // permission exists, then we add that below. The access manager requires Chris@0: // that ALL access checks must grant access, so this still results in Chris@0: // correct behavior. Chris@0: $requirements = [ Chris@0: '_access' => 'TRUE', Chris@0: ]; Chris@0: Chris@0: // Only specify route requirements if the default permission exists. For any Chris@0: // more advanced route definition, resource plugins extending this base Chris@0: // class must override this method. Chris@0: $permission = "restful $lower_method $this->pluginId"; Chris@0: if (isset($this->permissions()[$permission])) { Chris@0: $requirements['_permission'] = $permission; Chris@0: } Chris@0: Chris@0: return $requirements; Chris@0: } Chris@0: Chris@0: }