Initial Drupal 11 with DDEV setup
This commit is contained in:
		
							
								
								
									
										64
									
								
								web/core/modules/rest/src/Annotation/RestResource.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								web/core/modules/rest/src/Annotation/RestResource.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,64 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\rest\Annotation;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Annotation\Plugin;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a REST resource annotation object.
 | 
			
		||||
 *
 | 
			
		||||
 * Plugin Namespace: Plugin\rest\resource
 | 
			
		||||
 *
 | 
			
		||||
 * For a working example, see \Drupal\dblog\Plugin\rest\resource\DbLogResource
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\rest\Plugin\Type\ResourcePluginManager
 | 
			
		||||
 * @see \Drupal\rest\Plugin\ResourceBase
 | 
			
		||||
 * @see \Drupal\rest\Plugin\ResourceInterface
 | 
			
		||||
 * @see plugin_api
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup third_party
 | 
			
		||||
 *
 | 
			
		||||
 * @Annotation
 | 
			
		||||
 */
 | 
			
		||||
class RestResource extends Plugin {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The REST resource plugin ID.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  public $id;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The human-readable name of the REST resource plugin.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Annotation\Translation
 | 
			
		||||
   *
 | 
			
		||||
   * @ingroup plugin_translatable
 | 
			
		||||
   */
 | 
			
		||||
  public $label;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The serialization class to deserialize serialized data into.
 | 
			
		||||
   *
 | 
			
		||||
   * This property is optional and it does not need to be declared.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Symfony\Component\Serializer\SerializerInterface's "type" parameter.
 | 
			
		||||
   */
 | 
			
		||||
  public $serialization_class;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The URI paths that this REST resource plugin provides.
 | 
			
		||||
   *
 | 
			
		||||
   * Key-value pairs, with link relation type plugin IDs as keys, and URL
 | 
			
		||||
   * templates as values.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string[]
 | 
			
		||||
   *
 | 
			
		||||
   * @see core/core.link_relation_types.yml
 | 
			
		||||
   */
 | 
			
		||||
  public $uri_paths = [];
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										54
									
								
								web/core/modules/rest/src/Attribute/RestResource.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								web/core/modules/rest/src/Attribute/RestResource.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,54 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\rest\Attribute;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Plugin\Attribute\Plugin;
 | 
			
		||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a REST resource attribute object.
 | 
			
		||||
 *
 | 
			
		||||
 * Plugin Namespace: Plugin\rest\resource
 | 
			
		||||
 *
 | 
			
		||||
 * For a working example, see \Drupal\dblog\Plugin\rest\resource\DbLogResource
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\rest\Plugin\Type\ResourcePluginManager
 | 
			
		||||
 * @see \Drupal\rest\Plugin\ResourceBase
 | 
			
		||||
 * @see \Drupal\rest\Plugin\ResourceInterface
 | 
			
		||||
 * @see plugin_api
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup third_party
 | 
			
		||||
 */
 | 
			
		||||
#[\Attribute(\Attribute::TARGET_CLASS)]
 | 
			
		||||
class RestResource extends Plugin {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a RestResource attribute.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $id
 | 
			
		||||
   *   The REST resource plugin ID.
 | 
			
		||||
   * @param \Drupal\Core\StringTranslation\TranslatableMarkup $label
 | 
			
		||||
   *   The human-readable name of the REST resource plugin.
 | 
			
		||||
   * @param string|null $serialization_class
 | 
			
		||||
   *   (optional) The serialization class to deserialize serialized data into.
 | 
			
		||||
   * @param class-string|null $deriver
 | 
			
		||||
   *   (optional) The deriver class for the rest resource.
 | 
			
		||||
   * @param array $uri_paths
 | 
			
		||||
   *   (optional) The URI paths that this REST resource plugin provides.
 | 
			
		||||
   *   - key: The link relation type plugin ID.
 | 
			
		||||
   *   - value: The URL template.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Symfony\Component\Serializer\SerializerInterface
 | 
			
		||||
   * @see core/core.link_relation_types.yml
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(
 | 
			
		||||
    public readonly string $id,
 | 
			
		||||
    public readonly TranslatableMarkup $label,
 | 
			
		||||
    public readonly ?string $serialization_class = NULL,
 | 
			
		||||
    public readonly ?string $deriver = NULL,
 | 
			
		||||
    public readonly array $uri_paths = [],
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										275
									
								
								web/core/modules/rest/src/Entity/ConfigDependencies.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										275
									
								
								web/core/modules/rest/src/Entity/ConfigDependencies.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,275 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\rest\Entity;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 | 
			
		||||
use Drupal\rest\RestResourceConfigInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Calculates rest resource config dependencies.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 */
 | 
			
		||||
class ConfigDependencies implements ContainerInjectionInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The serialization format providers, keyed by format.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string[]
 | 
			
		||||
   */
 | 
			
		||||
  protected $formatProviders;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The authentication providers, keyed by ID.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string[]
 | 
			
		||||
   */
 | 
			
		||||
  protected $authProviders;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates a new ConfigDependencies instance.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string[] $format_providers
 | 
			
		||||
   *   The serialization format providers, keyed by format.
 | 
			
		||||
   * @param string[] $auth_providers
 | 
			
		||||
   *   The authentication providers, keyed by ID.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(array $format_providers, array $auth_providers) {
 | 
			
		||||
    $this->formatProviders = $format_providers;
 | 
			
		||||
    $this->authProviders = $auth_providers;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->getParameter('serializer.format_providers'),
 | 
			
		||||
      $container->getParameter('authentication_providers')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Calculates dependencies of a specific rest resource configuration.
 | 
			
		||||
   *
 | 
			
		||||
   * This function returns dependencies in a non-sorted, non-unique manner. It
 | 
			
		||||
   * is therefore the caller's responsibility to sort and remove duplicates
 | 
			
		||||
   * from the result prior to saving it with the configuration or otherwise
 | 
			
		||||
   * using it in a way that requires that. For example,
 | 
			
		||||
   * \Drupal\rest\Entity\RestResourceConfig::calculateDependencies() does this
 | 
			
		||||
   * via its \Drupal\Core\Entity\DependencyTrait::addDependency() method.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\rest\RestResourceConfigInterface $rest_config
 | 
			
		||||
   *   The rest configuration.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string[][]
 | 
			
		||||
   *   Dependencies keyed by dependency type.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\rest\Entity\RestResourceConfig::calculateDependencies()
 | 
			
		||||
   */
 | 
			
		||||
  public function calculateDependencies(RestResourceConfigInterface $rest_config) {
 | 
			
		||||
    $granularity = $rest_config->get('granularity');
 | 
			
		||||
 | 
			
		||||
    // Dependency calculation is the same for either granularity, the most
 | 
			
		||||
    // notable difference is that for the 'resource' granularity, the same
 | 
			
		||||
    // authentication providers and formats are supported for every method.
 | 
			
		||||
    switch ($granularity) {
 | 
			
		||||
      case RestResourceConfigInterface::METHOD_GRANULARITY:
 | 
			
		||||
        $methods = $rest_config->getMethods();
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case RestResourceConfigInterface::RESOURCE_GRANULARITY:
 | 
			
		||||
        $methods = array_slice($rest_config->getMethods(), 0, 1);
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      default:
 | 
			
		||||
        throw new \InvalidArgumentException('Invalid granularity specified.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // The dependency lists for authentication providers and formats
 | 
			
		||||
    // generated on container build.
 | 
			
		||||
    $dependencies = [];
 | 
			
		||||
    foreach ($methods as $request_method) {
 | 
			
		||||
      // Add dependencies based on the supported authentication providers.
 | 
			
		||||
      foreach ($rest_config->getAuthenticationProviders($request_method) as $auth) {
 | 
			
		||||
        if (isset($this->authProviders[$auth])) {
 | 
			
		||||
          $module_name = $this->authProviders[$auth];
 | 
			
		||||
          $dependencies['module'][] = $module_name;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      // Add dependencies based on the supported authentication formats.
 | 
			
		||||
      foreach ($rest_config->getFormats($request_method) as $format) {
 | 
			
		||||
        if (isset($this->formatProviders[$format])) {
 | 
			
		||||
          $module_name = $this->formatProviders[$format];
 | 
			
		||||
          $dependencies['module'][] = $module_name;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $dependencies;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Informs the entity that entities it depends on will be deleted.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\rest\RestResourceConfigInterface $rest_config
 | 
			
		||||
   *   The rest configuration.
 | 
			
		||||
   * @param array $dependencies
 | 
			
		||||
   *   An array of dependencies that will be deleted keyed by dependency type.
 | 
			
		||||
   *   Dependency types are, for example, entity, module and theme.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if the entity has been changed as a result, FALSE if not.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Core\Config\Entity\ConfigEntityInterface::onDependencyRemoval()
 | 
			
		||||
   */
 | 
			
		||||
  public function onDependencyRemoval(RestResourceConfigInterface $rest_config, array $dependencies) {
 | 
			
		||||
    $granularity = $rest_config->get('granularity');
 | 
			
		||||
    switch ($granularity) {
 | 
			
		||||
      case RestResourceConfigInterface::METHOD_GRANULARITY:
 | 
			
		||||
        return $this->onDependencyRemovalForMethodGranularity($rest_config, $dependencies);
 | 
			
		||||
 | 
			
		||||
      case RestResourceConfigInterface::RESOURCE_GRANULARITY:
 | 
			
		||||
        return $this->onDependencyRemovalForResourceGranularity($rest_config, $dependencies);
 | 
			
		||||
 | 
			
		||||
      default:
 | 
			
		||||
        throw new \InvalidArgumentException('Invalid granularity specified.');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Informs the entity that entities it depends on will be deleted.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\rest\RestResourceConfigInterface $rest_config
 | 
			
		||||
   *   The rest configuration.
 | 
			
		||||
   * @param array $dependencies
 | 
			
		||||
   *   An array of dependencies that will be deleted keyed by dependency type.
 | 
			
		||||
   *   Dependency types are, for example, entity, module and theme.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if the entity has been changed as a result, FALSE if not.
 | 
			
		||||
   */
 | 
			
		||||
  protected function onDependencyRemovalForMethodGranularity(RestResourceConfigInterface $rest_config, array $dependencies) {
 | 
			
		||||
    $changed = FALSE;
 | 
			
		||||
    // Only module-related dependencies can be fixed. All other types of
 | 
			
		||||
    // dependencies cannot, because they were not generated based on supported
 | 
			
		||||
    // authentication providers or formats.
 | 
			
		||||
    if (isset($dependencies['module'])) {
 | 
			
		||||
      // Try to fix dependencies.
 | 
			
		||||
      $removed_auth = array_keys(array_intersect($this->authProviders, $dependencies['module']));
 | 
			
		||||
      $removed_formats = array_keys(array_intersect($this->formatProviders, $dependencies['module']));
 | 
			
		||||
      $configuration_before = $configuration = $rest_config->get('configuration');
 | 
			
		||||
      if (!empty($removed_auth) || !empty($removed_formats)) {
 | 
			
		||||
        // Try to fix dependency problems by removing affected
 | 
			
		||||
        // authentication providers and formats.
 | 
			
		||||
        foreach (array_keys($rest_config->get('configuration')) as $request_method) {
 | 
			
		||||
          foreach ($removed_formats as $format) {
 | 
			
		||||
            if (in_array($format, $rest_config->getFormats($request_method), TRUE)) {
 | 
			
		||||
              $configuration[$request_method]['supported_formats'] = array_diff($configuration[$request_method]['supported_formats'], $removed_formats);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          foreach ($removed_auth as $auth) {
 | 
			
		||||
            if (in_array($auth, $rest_config->getAuthenticationProviders($request_method), TRUE)) {
 | 
			
		||||
              $configuration[$request_method]['supported_auth'] = array_diff($configuration[$request_method]['supported_auth'], $removed_auth);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          if (empty($configuration[$request_method]['supported_auth'])) {
 | 
			
		||||
            // Remove the key if there are no more authentication providers
 | 
			
		||||
            // supported by this request method.
 | 
			
		||||
            unset($configuration[$request_method]['supported_auth']);
 | 
			
		||||
          }
 | 
			
		||||
          if (empty($configuration[$request_method]['supported_formats'])) {
 | 
			
		||||
            // Remove the key if there are no more formats supported by this
 | 
			
		||||
            // request method.
 | 
			
		||||
            unset($configuration[$request_method]['supported_formats']);
 | 
			
		||||
          }
 | 
			
		||||
          if (empty($configuration[$request_method])) {
 | 
			
		||||
            // Remove the request method altogether if it no longer has any
 | 
			
		||||
            // supported authentication providers or formats.
 | 
			
		||||
            unset($configuration[$request_method]);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if ($configuration_before != $configuration && !empty($configuration)) {
 | 
			
		||||
        $rest_config->set('configuration', $configuration);
 | 
			
		||||
        // Only mark the dependencies problems as fixed if there is any
 | 
			
		||||
        // configuration left.
 | 
			
		||||
        $changed = TRUE;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // If the dependency problems are not marked as fixed at this point they
 | 
			
		||||
    // should be related to the resource plugin and the config entity should
 | 
			
		||||
    // be deleted.
 | 
			
		||||
    return $changed;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Informs the entity that entities it depends on will be deleted.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\rest\RestResourceConfigInterface $rest_config
 | 
			
		||||
   *   The rest configuration.
 | 
			
		||||
   * @param array $dependencies
 | 
			
		||||
   *   An array of dependencies that will be deleted keyed by dependency type.
 | 
			
		||||
   *   Dependency types are, for example, entity, module and theme.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if the entity has been changed as a result, FALSE if not.
 | 
			
		||||
   */
 | 
			
		||||
  protected function onDependencyRemovalForResourceGranularity(RestResourceConfigInterface $rest_config, array $dependencies) {
 | 
			
		||||
    $changed = FALSE;
 | 
			
		||||
    // Only module-related dependencies can be fixed. All other types of
 | 
			
		||||
    // dependencies cannot, because they were not generated based on supported
 | 
			
		||||
    // authentication providers or formats.
 | 
			
		||||
    if (isset($dependencies['module'])) {
 | 
			
		||||
      // Try to fix dependencies.
 | 
			
		||||
      $removed_auth = array_keys(array_intersect($this->authProviders, $dependencies['module']));
 | 
			
		||||
      $removed_formats = array_keys(array_intersect($this->formatProviders, $dependencies['module']));
 | 
			
		||||
      $configuration_before = $configuration = $rest_config->get('configuration');
 | 
			
		||||
      if (!empty($removed_auth) || !empty($removed_formats)) {
 | 
			
		||||
        // All methods support the same formats and authentication providers, so
 | 
			
		||||
        // get those for whichever the first listed method is.
 | 
			
		||||
        $first_method = $rest_config->getMethods()[0];
 | 
			
		||||
 | 
			
		||||
        // Try to fix dependency problems by removing affected
 | 
			
		||||
        // authentication providers and formats.
 | 
			
		||||
        foreach ($removed_formats as $format) {
 | 
			
		||||
          if (in_array($format, $rest_config->getFormats($first_method), TRUE)) {
 | 
			
		||||
            $configuration['formats'] = array_diff($configuration['formats'], $removed_formats);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        foreach ($removed_auth as $auth) {
 | 
			
		||||
          if (in_array($auth, $rest_config->getAuthenticationProviders($first_method), TRUE)) {
 | 
			
		||||
            $configuration['authentication'] = array_diff($configuration['authentication'], $removed_auth);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        if (empty($configuration['authentication'])) {
 | 
			
		||||
          // Remove the key if there are no more authentication providers
 | 
			
		||||
          // supported.
 | 
			
		||||
          unset($configuration['authentication']);
 | 
			
		||||
        }
 | 
			
		||||
        if (empty($configuration['formats'])) {
 | 
			
		||||
          // Remove the key if there are no more formats supported.
 | 
			
		||||
          unset($configuration['formats']);
 | 
			
		||||
        }
 | 
			
		||||
        if (empty($configuration['authentication']) || empty($configuration['formats'])) {
 | 
			
		||||
          // If there no longer are any supported authentication providers or
 | 
			
		||||
          // formats, this REST resource can no longer function, and so we
 | 
			
		||||
          // cannot fix this config entity to keep it working.
 | 
			
		||||
          $configuration = [];
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if ($configuration_before != $configuration && !empty($configuration)) {
 | 
			
		||||
        $rest_config->set('configuration', $configuration);
 | 
			
		||||
        // Only mark the dependencies problems as fixed if there is any
 | 
			
		||||
        // configuration left.
 | 
			
		||||
        $changed = TRUE;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // If the dependency problems are not marked as fixed at this point they
 | 
			
		||||
    // should be related to the resource plugin and the config entity should
 | 
			
		||||
    // be deleted.
 | 
			
		||||
    return $changed;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										281
									
								
								web/core/modules/rest/src/Entity/RestResourceConfig.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										281
									
								
								web/core/modules/rest/src/Entity/RestResourceConfig.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,281 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\rest\Entity;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Entity\Attribute\ConfigEntityType;
 | 
			
		||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
 | 
			
		||||
use Drupal\Core\Config\Entity\ConfigEntityBase;
 | 
			
		||||
use Drupal\Core\Entity\EntityStorageInterface;
 | 
			
		||||
use Drupal\Core\Plugin\DefaultSingleLazyPluginCollection;
 | 
			
		||||
use Drupal\rest\RestResourceConfigInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a RestResourceConfig configuration entity class.
 | 
			
		||||
 */
 | 
			
		||||
#[ConfigEntityType(
 | 
			
		||||
  id: 'rest_resource_config',
 | 
			
		||||
  label: new TranslatableMarkup('REST resource configuration'),
 | 
			
		||||
  label_collection: new TranslatableMarkup('REST resource configurations'),
 | 
			
		||||
  label_singular: new TranslatableMarkup('REST resource configuration'),
 | 
			
		||||
  label_plural: new TranslatableMarkup('REST resource configurations'),
 | 
			
		||||
  config_prefix: 'resource',
 | 
			
		||||
  entity_keys: [
 | 
			
		||||
    'id' => 'id',
 | 
			
		||||
  ],
 | 
			
		||||
  admin_permission: 'administer rest resources',
 | 
			
		||||
  label_count: [
 | 
			
		||||
    'singular' => '@count REST resource configuration',
 | 
			
		||||
    'plural' => '@count REST resource configurations',
 | 
			
		||||
  ],
 | 
			
		||||
  config_export: [
 | 
			
		||||
    'id',
 | 
			
		||||
    'plugin_id',
 | 
			
		||||
    'granularity',
 | 
			
		||||
    'configuration',
 | 
			
		||||
  ],
 | 
			
		||||
)]
 | 
			
		||||
class RestResourceConfig extends ConfigEntityBase implements RestResourceConfigInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The REST resource config id.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $id;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The REST resource plugin id.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $plugin_id;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The REST resource configuration granularity.
 | 
			
		||||
   *
 | 
			
		||||
   * Currently either:
 | 
			
		||||
   * - \Drupal\rest\RestResourceConfigInterface::METHOD_GRANULARITY
 | 
			
		||||
   * - \Drupal\rest\RestResourceConfigInterface::RESOURCE_GRANULARITY
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $granularity;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The REST resource configuration.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $configuration;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The rest resource plugin manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Component\Plugin\PluginManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $pluginManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(array $values, $entity_type) {
 | 
			
		||||
    parent::__construct($values, $entity_type);
 | 
			
		||||
    // The config entity id looks like the plugin id but uses __ instead of :
 | 
			
		||||
    // because : is not valid for config entities.
 | 
			
		||||
    if (!isset($this->plugin_id) && isset($this->id)) {
 | 
			
		||||
      // Generate plugin_id on first entity creation.
 | 
			
		||||
      $this->plugin_id = str_replace('.', ':', $this->id);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the resource plugin manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Component\Plugin\PluginManagerInterface
 | 
			
		||||
   *   The REST plugin manager service.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getResourcePluginManager() {
 | 
			
		||||
    if (!isset($this->pluginManager)) {
 | 
			
		||||
      $this->pluginManager = \Drupal::service('plugin.manager.rest');
 | 
			
		||||
    }
 | 
			
		||||
    return $this->pluginManager;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getResourcePlugin() {
 | 
			
		||||
    return $this->getPluginCollections()['resource']->get($this->plugin_id);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getMethods() {
 | 
			
		||||
    switch ($this->granularity) {
 | 
			
		||||
      case RestResourceConfigInterface::METHOD_GRANULARITY:
 | 
			
		||||
        return $this->getMethodsForMethodGranularity();
 | 
			
		||||
 | 
			
		||||
      case RestResourceConfigInterface::RESOURCE_GRANULARITY:
 | 
			
		||||
        return $this->configuration['methods'];
 | 
			
		||||
 | 
			
		||||
      default:
 | 
			
		||||
        throw new \InvalidArgumentException('Invalid granularity specified.');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Retrieves a list of supported HTTP methods for this resource.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string[]
 | 
			
		||||
   *   A list of supported HTTP methods.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getMethodsForMethodGranularity() {
 | 
			
		||||
    $methods = array_keys($this->configuration);
 | 
			
		||||
    return array_map([$this, 'normalizeRestMethod'], $methods);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getAuthenticationProviders($method) {
 | 
			
		||||
    switch ($this->granularity) {
 | 
			
		||||
      case RestResourceConfigInterface::METHOD_GRANULARITY:
 | 
			
		||||
        return $this->getAuthenticationProvidersForMethodGranularity($method);
 | 
			
		||||
 | 
			
		||||
      case RestResourceConfigInterface::RESOURCE_GRANULARITY:
 | 
			
		||||
        return $this->configuration['authentication'];
 | 
			
		||||
 | 
			
		||||
      default:
 | 
			
		||||
        throw new \InvalidArgumentException('Invalid granularity specified.');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Retrieves a list of supported authentication providers.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $method
 | 
			
		||||
   *   The request method e.g GET or POST.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string[]
 | 
			
		||||
   *   A list of supported authentication provider IDs.
 | 
			
		||||
   */
 | 
			
		||||
  public function getAuthenticationProvidersForMethodGranularity($method) {
 | 
			
		||||
    $method = $this->normalizeRestMethod($method);
 | 
			
		||||
    if (in_array($method, $this->getMethods()) && isset($this->configuration[$method]['supported_auth'])) {
 | 
			
		||||
      return $this->configuration[$method]['supported_auth'];
 | 
			
		||||
    }
 | 
			
		||||
    return [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getFormats($method) {
 | 
			
		||||
    switch ($this->granularity) {
 | 
			
		||||
      case RestResourceConfigInterface::METHOD_GRANULARITY:
 | 
			
		||||
        return $this->getFormatsForMethodGranularity($method);
 | 
			
		||||
 | 
			
		||||
      case RestResourceConfigInterface::RESOURCE_GRANULARITY:
 | 
			
		||||
        return $this->configuration['formats'];
 | 
			
		||||
 | 
			
		||||
      default:
 | 
			
		||||
        throw new \InvalidArgumentException('Invalid granularity specified.');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Retrieves a list of supported response formats.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $method
 | 
			
		||||
   *   The request method e.g GET or POST.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string[]
 | 
			
		||||
   *   A list of supported format IDs.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getFormatsForMethodGranularity($method) {
 | 
			
		||||
    $method = $this->normalizeRestMethod($method);
 | 
			
		||||
    if (in_array($method, $this->getMethods()) && isset($this->configuration[$method]['supported_formats'])) {
 | 
			
		||||
      return $this->configuration[$method]['supported_formats'];
 | 
			
		||||
    }
 | 
			
		||||
    return [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getPluginCollections() {
 | 
			
		||||
    return [
 | 
			
		||||
      'resource' => new DefaultSingleLazyPluginCollection($this->getResourcePluginManager(), $this->plugin_id, []),
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function calculateDependencies() {
 | 
			
		||||
    parent::calculateDependencies();
 | 
			
		||||
 | 
			
		||||
    foreach ($this->getRestResourceDependencies()->calculateDependencies($this) as $type => $dependencies) {
 | 
			
		||||
      foreach ($dependencies as $dependency) {
 | 
			
		||||
        $this->addDependency($type, $dependency);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function onDependencyRemoval(array $dependencies) {
 | 
			
		||||
    $parent = parent::onDependencyRemoval($dependencies);
 | 
			
		||||
 | 
			
		||||
    // If the dependency problems are not marked as fixed at this point they
 | 
			
		||||
    // should be related to the resource plugin and the config entity should
 | 
			
		||||
    // be deleted.
 | 
			
		||||
    $changed = $this->getRestResourceDependencies()->onDependencyRemoval($this, $dependencies);
 | 
			
		||||
    return $parent || $changed;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the REST resource dependencies.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\rest\Entity\ConfigDependencies
 | 
			
		||||
   *   The REST resource dependencies.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getRestResourceDependencies() {
 | 
			
		||||
    return \Drupal::service('class_resolver')->getInstanceFromDefinition(ConfigDependencies::class);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Normalizes the method.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $method
 | 
			
		||||
   *   The request method.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The normalized request method.
 | 
			
		||||
   */
 | 
			
		||||
  protected function normalizeRestMethod($method) {
 | 
			
		||||
    return strtoupper($method);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function postSave(EntityStorageInterface $storage, $update = TRUE) {
 | 
			
		||||
    parent::postSave($storage, $update);
 | 
			
		||||
 | 
			
		||||
    \Drupal::service('router.builder')->setRebuildNeeded();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function postDelete(EntityStorageInterface $storage, array $entities) {
 | 
			
		||||
    parent::postDelete($storage, $entities);
 | 
			
		||||
 | 
			
		||||
    \Drupal::service('router.builder')->setRebuildNeeded();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,74 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\rest\EventSubscriber;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
 | 
			
		||||
use Drupal\Core\Routing\RouteBuildEvent;
 | 
			
		||||
use Drupal\Core\Routing\RoutingEvents;
 | 
			
		||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Generates a 'create' route for an entity type if it has a REST POST route.
 | 
			
		||||
 */
 | 
			
		||||
class EntityResourcePostRouteSubscriber implements EventSubscriberInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The REST resource config storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityStorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $resourceConfigStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new EntityResourcePostRouteSubscriber instance.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
 | 
			
		||||
   *   The entity type manager.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
 | 
			
		||||
    $this->resourceConfigStorage = $entity_type_manager->getStorage('rest_resource_config');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provides routes on route rebuild time.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Routing\RouteBuildEvent $event
 | 
			
		||||
   *   The route build event.
 | 
			
		||||
   */
 | 
			
		||||
  public function onDynamicRouteEvent(RouteBuildEvent $event) {
 | 
			
		||||
    $route_collection = $event->getRouteCollection();
 | 
			
		||||
 | 
			
		||||
    $resource_configs = $this->resourceConfigStorage->loadMultiple();
 | 
			
		||||
    // Iterate over all REST resource config entities.
 | 
			
		||||
    foreach ($resource_configs as $resource_config) {
 | 
			
		||||
      // We only care about REST resource config entities for the
 | 
			
		||||
      // \Drupal\rest\Plugin\rest\resource\EntityResource plugin.
 | 
			
		||||
      $plugin_id = $resource_config->toArray()['plugin_id'];
 | 
			
		||||
      if (!str_starts_with($plugin_id, 'entity')) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $entity_type_id = substr($plugin_id, 7);
 | 
			
		||||
      $rest_post_route_name = "rest.entity.$entity_type_id.POST";
 | 
			
		||||
      if ($rest_post_route = $route_collection->get($rest_post_route_name)) {
 | 
			
		||||
        // Create a route for the 'create' link relation type for this entity
 | 
			
		||||
        // type that uses the same route definition as the REST 'POST' route
 | 
			
		||||
        // which use that entity type.
 | 
			
		||||
        // @see \Drupal\Core\Entity\EntityBase::toUrl()
 | 
			
		||||
        $entity_create_route_name = "entity.$entity_type_id.create";
 | 
			
		||||
        $route_collection->add($entity_create_route_name, $rest_post_route);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function getSubscribedEvents(): array {
 | 
			
		||||
    // Priority -10, to run after \Drupal\rest\Routing\ResourceRoutes, which has
 | 
			
		||||
    // priority 0.
 | 
			
		||||
    $events[RoutingEvents::DYNAMIC][] = ['onDynamicRouteEvent', -10];
 | 
			
		||||
    return $events;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,210 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\rest\EventSubscriber;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Cache\CacheableMetadata;
 | 
			
		||||
use Drupal\Core\Cache\CacheableResponse;
 | 
			
		||||
use Drupal\Core\Cache\CacheableResponseInterface;
 | 
			
		||||
use Drupal\Core\Render\RendererInterface;
 | 
			
		||||
use Drupal\Core\Routing\RouteMatchInterface;
 | 
			
		||||
use Drupal\rest\ResourceResponseInterface;
 | 
			
		||||
use Drupal\serialization\Normalizer\CacheableNormalizerInterface;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Response;
 | 
			
		||||
use Symfony\Component\HttpKernel\Event\ResponseEvent;
 | 
			
		||||
use Symfony\Component\HttpKernel\KernelEvents;
 | 
			
		||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 | 
			
		||||
use Symfony\Component\Serializer\SerializerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Response subscriber that serializes and removes ResourceResponses' data.
 | 
			
		||||
 */
 | 
			
		||||
class ResourceResponseSubscriber implements EventSubscriberInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The serializer.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Symfony\Component\Serializer\SerializerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $serializer;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The renderer.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Render\RendererInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $renderer;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The current route match.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Routing\RouteMatchInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $routeMatch;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a ResourceResponseSubscriber object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Symfony\Component\Serializer\SerializerInterface $serializer
 | 
			
		||||
   *   The serializer.
 | 
			
		||||
   * @param \Drupal\Core\Render\RendererInterface $renderer
 | 
			
		||||
   *   The renderer.
 | 
			
		||||
   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
 | 
			
		||||
   *   The current route match.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(SerializerInterface $serializer, RendererInterface $renderer, RouteMatchInterface $route_match) {
 | 
			
		||||
    $this->serializer = $serializer;
 | 
			
		||||
    $this->renderer = $renderer;
 | 
			
		||||
    $this->routeMatch = $route_match;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Serializes ResourceResponse responses' data, and removes that data.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Symfony\Component\HttpKernel\Event\ResponseEvent $event
 | 
			
		||||
   *   The event to process.
 | 
			
		||||
   */
 | 
			
		||||
  public function onResponse(ResponseEvent $event) {
 | 
			
		||||
    $response = $event->getResponse();
 | 
			
		||||
    if (!$response instanceof ResourceResponseInterface) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $request = $event->getRequest();
 | 
			
		||||
    $format = $this->getResponseFormat($this->routeMatch, $request);
 | 
			
		||||
    $this->renderResponseBody($request, $response, $this->serializer, $format);
 | 
			
		||||
    $event->setResponse($this->flattenResponse($response));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determines the format to respond in.
 | 
			
		||||
   *
 | 
			
		||||
   * Respects the requested format if one is specified. However, it is common to
 | 
			
		||||
   * forget to specify a response format in case of a POST or PATCH. Rather than
 | 
			
		||||
   * simply throwing an error, we apply the robustness principle: when POSTing
 | 
			
		||||
   * or PATCHing using a certain format, you probably expect a response in that
 | 
			
		||||
   * same format.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
 | 
			
		||||
   *   The current route match.
 | 
			
		||||
   * @param \Symfony\Component\HttpFoundation\Request $request
 | 
			
		||||
   *   The current request.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The response format.
 | 
			
		||||
   */
 | 
			
		||||
  public function getResponseFormat(RouteMatchInterface $route_match, Request $request) {
 | 
			
		||||
    $route = $route_match->getRouteObject();
 | 
			
		||||
    $acceptable_response_formats = $route->hasRequirement('_format') ? explode('|', $route->getRequirement('_format')) : [];
 | 
			
		||||
    $acceptable_request_formats = $route->hasRequirement('_content_type_format') ? explode('|', $route->getRequirement('_content_type_format')) : [];
 | 
			
		||||
    $acceptable_formats = $request->isMethodCacheable() ? $acceptable_response_formats : $acceptable_request_formats;
 | 
			
		||||
 | 
			
		||||
    $requested_format = $request->getRequestFormat();
 | 
			
		||||
    $content_type_format = $request->getContentTypeFormat();
 | 
			
		||||
 | 
			
		||||
    // If an acceptable response format is requested, then use that. Otherwise,
 | 
			
		||||
    // including and particularly when the client forgot to specify a response
 | 
			
		||||
    // format, then use heuristics to select the format that is most likely
 | 
			
		||||
    // expected.
 | 
			
		||||
    if (in_array($requested_format, $acceptable_response_formats, TRUE)) {
 | 
			
		||||
      return $requested_format;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If a request body is present, then use the format corresponding to the
 | 
			
		||||
    // request body's Content-Type for the response, if it's an acceptable
 | 
			
		||||
    // format for the request.
 | 
			
		||||
    if (!empty($request->getContent()) && in_array($content_type_format, $acceptable_request_formats, TRUE)) {
 | 
			
		||||
      return $content_type_format;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Otherwise, use the first acceptable format.
 | 
			
		||||
    if (!empty($acceptable_formats)) {
 | 
			
		||||
      return $acceptable_formats[0];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Sometimes, there are no acceptable formats.
 | 
			
		||||
    return NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Renders a resource response body.
 | 
			
		||||
   *
 | 
			
		||||
   * During serialization, encoders and normalizers are able to explicitly
 | 
			
		||||
   * bubble cacheability metadata via the 'cacheability' key-value pair in the
 | 
			
		||||
   * received context. This bubbled cacheability metadata will be applied to the
 | 
			
		||||
   * the response.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Symfony\Component\HttpFoundation\Request $request
 | 
			
		||||
   *   The request object.
 | 
			
		||||
   * @param \Drupal\rest\ResourceResponseInterface $response
 | 
			
		||||
   *   The response from the REST resource.
 | 
			
		||||
   * @param \Symfony\Component\Serializer\SerializerInterface $serializer
 | 
			
		||||
   *   The serializer to use.
 | 
			
		||||
   * @param string|null $format
 | 
			
		||||
   *   The response format, or NULL in case the response does not need a format.
 | 
			
		||||
   *
 | 
			
		||||
   * @todo Add test coverage for language negotiation contexts in
 | 
			
		||||
   *   https://www.drupal.org/node/2135829.
 | 
			
		||||
   */
 | 
			
		||||
  protected function renderResponseBody(Request $request, ResourceResponseInterface $response, SerializerInterface $serializer, $format) {
 | 
			
		||||
    $data = $response->getResponseData();
 | 
			
		||||
 | 
			
		||||
    // If there is data to send, serialize and set it as the response body.
 | 
			
		||||
    if ($data !== NULL) {
 | 
			
		||||
      $serialization_context = [
 | 
			
		||||
        'request' => $request,
 | 
			
		||||
        CacheableNormalizerInterface::SERIALIZATION_CONTEXT_CACHEABILITY => new CacheableMetadata(),
 | 
			
		||||
      ];
 | 
			
		||||
 | 
			
		||||
      $output = $serializer->serialize($data, $format, $serialization_context);
 | 
			
		||||
 | 
			
		||||
      if ($response instanceof CacheableResponseInterface) {
 | 
			
		||||
        $response->addCacheableDependency($serialization_context[CacheableNormalizerInterface::SERIALIZATION_CONTEXT_CACHEABILITY]);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $response->setContent($output);
 | 
			
		||||
      $response->headers->set('Content-Type', $request->getMimeType($format));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Flattens a fully rendered resource response.
 | 
			
		||||
   *
 | 
			
		||||
   * Ensures that complex data structures in ResourceResponse::getResponseData()
 | 
			
		||||
   * are not serialized. Not doing this means that caching this response object
 | 
			
		||||
   * requires unserializing the PHP data when reading this response object from
 | 
			
		||||
   * cache, which can be very costly, and is unnecessary.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\rest\ResourceResponseInterface $response
 | 
			
		||||
   *   A fully rendered resource response.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Cache\CacheableResponse|\Symfony\Component\HttpFoundation\Response
 | 
			
		||||
   *   The flattened response.
 | 
			
		||||
   */
 | 
			
		||||
  protected function flattenResponse(ResourceResponseInterface $response) {
 | 
			
		||||
    $final_response = ($response instanceof CacheableResponseInterface) ? new CacheableResponse() : new Response();
 | 
			
		||||
    $final_response->setContent($response->getContent());
 | 
			
		||||
    $final_response->setStatusCode($response->getStatusCode());
 | 
			
		||||
    $final_response->setProtocolVersion($response->getProtocolVersion());
 | 
			
		||||
    if ($response->getCharset()) {
 | 
			
		||||
      $final_response->setCharset($response->getCharset());
 | 
			
		||||
    }
 | 
			
		||||
    $final_response->headers = clone $response->headers;
 | 
			
		||||
    if ($final_response instanceof CacheableResponseInterface) {
 | 
			
		||||
      $final_response->addCacheableDependency($response->getCacheableMetadata());
 | 
			
		||||
    }
 | 
			
		||||
    return $final_response;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function getSubscribedEvents(): array {
 | 
			
		||||
    // Run before
 | 
			
		||||
    // \Drupal\dynamic_page_cache\EventSubscriber\DynamicPageCacheSubscriber
 | 
			
		||||
    // (priority 7), so that Dynamic Page Cache can cache flattened responses.
 | 
			
		||||
    $events[KernelEvents::RESPONSE][] = ['onResponse', 128];
 | 
			
		||||
    return $events;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										64
									
								
								web/core/modules/rest/src/Hook/RestHooks.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								web/core/modules/rest/src/Hook/RestHooks.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,64 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\rest\Hook;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
 | 
			
		||||
use Drupal\Core\Url;
 | 
			
		||||
use Drupal\Core\Routing\RouteMatchInterface;
 | 
			
		||||
use Drupal\Core\Hook\Attribute\Hook;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Hook implementations for rest.
 | 
			
		||||
 */
 | 
			
		||||
class RestHooks {
 | 
			
		||||
 | 
			
		||||
  use StringTranslationTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_help().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('help')]
 | 
			
		||||
  public function help($route_name, RouteMatchInterface $route_match): ?string {
 | 
			
		||||
    switch ($route_name) {
 | 
			
		||||
      case 'help.page.rest':
 | 
			
		||||
        $output = '';
 | 
			
		||||
        $output .= '<h2>' . $this->t('About') . '</h2>';
 | 
			
		||||
        $output .= '<p>' . $this->t('The RESTful Web Services module provides a framework for exposing REST resources on your site. It provides support for content entity types such as the main site content, comments, content blocks, taxonomy terms, and user accounts, etc. (see the <a href=":field">Field module help page</a> for more information about entities). REST support for content items of the Node module is installed by default, and support for other types of content entities can be enabled. Other modules may add support for other types of REST resources. For more information, see the <a href=":rest">online documentation for the RESTful Web Services module</a>.', [
 | 
			
		||||
          ':rest' => 'https://www.drupal.org/documentation/modules/rest',
 | 
			
		||||
          ':field' => \Drupal::moduleHandler()->moduleExists('field') ? Url::fromRoute('help.page', [
 | 
			
		||||
            'name' => 'field',
 | 
			
		||||
          ])->toString() : '#',
 | 
			
		||||
        ]) . '</p>';
 | 
			
		||||
        $output .= '<h2>' . $this->t('Uses') . '</h2>';
 | 
			
		||||
        $output .= '<dl>';
 | 
			
		||||
        $output .= '<dt>' . $this->t('Installing supporting modules') . '</dt>';
 | 
			
		||||
        $output .= '<dd>' . $this->t('In order to use REST on a website, you need to install modules that provide serialization and authentication services. You can use the Core module <a href=":serialization">serialization</a> for serialization and <a href=":basic_auth">HTTP Basic Authentication</a> for authentication, or install a contributed or custom module.', [
 | 
			
		||||
          ':serialization' => \Drupal::moduleHandler()->moduleExists('serialization') ? Url::fromRoute('help.page', [
 | 
			
		||||
            'name' => 'serialization',
 | 
			
		||||
          ])->toString() : 'https://www.drupal.org/docs/8/core/modules/serialization/overview',
 | 
			
		||||
          ':basic_auth' => \Drupal::moduleHandler()->moduleExists('basic_auth') ? Url::fromRoute('help.page', [
 | 
			
		||||
            'name' => 'basic_auth',
 | 
			
		||||
          ])->toString() : 'https://www.drupal.org/docs/8/core/modules/basic_auth/overview',
 | 
			
		||||
        ]) . '</dd>';
 | 
			
		||||
        $output .= '<dt>' . $this->t('Enabling REST support for an entity type') . '</dt>';
 | 
			
		||||
        $output .= '<dd>' . $this->t('REST support for content types (provided by the <a href=":node">Node</a> module) is enabled by default. To enable support for other content entity types, you can use a <a href=":config" target="blank">process based on configuration editing</a> or the contributed <a href=":restui">REST UI module</a>.', [
 | 
			
		||||
          ':node' => \Drupal::moduleHandler()->moduleExists('node') ? Url::fromRoute('help.page', [
 | 
			
		||||
            'name' => 'node',
 | 
			
		||||
          ])->toString() : '#',
 | 
			
		||||
          ':config' => 'https://www.drupal.org/documentation/modules/rest',
 | 
			
		||||
          ':restui' => 'https://www.drupal.org/project/restui',
 | 
			
		||||
        ]) . '</dd>';
 | 
			
		||||
        $output .= '<dd>' . $this->t('You will also need to grant anonymous users permission to perform each of the REST operations you want to be available, and set up authentication properly to authorize web requests.') . '</dd>';
 | 
			
		||||
        $output .= '<dt>' . $this->t('General') . '</dt>';
 | 
			
		||||
        $output .= '<dd>' . $this->t('The <a href=":rest-docs">RESTful Web Services</a> and <a href=":jsonapi-docs">JSON:API</a> modules serve similar purposes. <a href=":comparison">Read the comparison of the RESTFul Web Services and JSON:API modules</a> to determine the best choice for your site.', [
 | 
			
		||||
          ':rest-docs' => 'https://www.drupal.org/docs/8/core/modules/rest',
 | 
			
		||||
          ':jsonapi-docs' => 'https://www.drupal.org/docs/8/modules/json-api',
 | 
			
		||||
          ':comparison' => 'https://www.drupal.org/docs/8/modules/jsonapi/jsonapi-vs-cores-rest-module',
 | 
			
		||||
        ]) . '</dd>';
 | 
			
		||||
        $output .= '</dl>';
 | 
			
		||||
        return $output;
 | 
			
		||||
    }
 | 
			
		||||
    return NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										34
									
								
								web/core/modules/rest/src/ModifiedResourceResponse.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								web/core/modules/rest/src/ModifiedResourceResponse.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\rest;
 | 
			
		||||
 | 
			
		||||
use Symfony\Component\HttpFoundation\Response;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A response that does not contain cacheability metadata.
 | 
			
		||||
 *
 | 
			
		||||
 * Used when resources are modified by a request: responses to unsafe requests
 | 
			
		||||
 * (POST/PATCH/DELETE) can never be cached.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\rest\ResourceResponse
 | 
			
		||||
 */
 | 
			
		||||
class ModifiedResourceResponse extends Response implements ResourceResponseInterface {
 | 
			
		||||
 | 
			
		||||
  use ResourceResponseTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructor for ModifiedResourceResponse objects.
 | 
			
		||||
   *
 | 
			
		||||
   * @param mixed $data
 | 
			
		||||
   *   Response data that should be serialized.
 | 
			
		||||
   * @param int $status
 | 
			
		||||
   *   The response status code.
 | 
			
		||||
   * @param array $headers
 | 
			
		||||
   *   An array of response headers.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct($data = NULL, $status = 200, $headers = []) {
 | 
			
		||||
    $this->responseData = $data;
 | 
			
		||||
    parent::__construct('', $status, $headers);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										101
									
								
								web/core/modules/rest/src/Plugin/Deriver/EntityDeriver.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								web/core/modules/rest/src/Plugin/Deriver/EntityDeriver.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,101 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\rest\Plugin\Deriver;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
 | 
			
		||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a resource plugin definition for every entity type.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\rest\Plugin\rest\resource\EntityResource
 | 
			
		||||
 */
 | 
			
		||||
class EntityDeriver implements ContainerDeriverInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * List of derivative definitions.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $derivatives;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity type manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityTypeManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs an EntityDeriver object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
 | 
			
		||||
   *   The entity type manager.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
 | 
			
		||||
    $this->entityTypeManager = $entity_type_manager;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container, $base_plugin_id) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get('entity_type.manager')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getDerivativeDefinition($derivative_id, $base_plugin_definition) {
 | 
			
		||||
    if (!isset($this->derivatives)) {
 | 
			
		||||
      $this->getDerivativeDefinitions($base_plugin_definition);
 | 
			
		||||
    }
 | 
			
		||||
    if (isset($this->derivatives[$derivative_id])) {
 | 
			
		||||
      return $this->derivatives[$derivative_id];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getDerivativeDefinitions($base_plugin_definition) {
 | 
			
		||||
    if (!isset($this->derivatives)) {
 | 
			
		||||
      // Add in the default plugin configuration and the resource type.
 | 
			
		||||
      foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
 | 
			
		||||
        if ($entity_type->isInternal()) {
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->derivatives[$entity_type_id] = [
 | 
			
		||||
          'id' => 'entity:' . $entity_type_id,
 | 
			
		||||
          'entity_type' => $entity_type_id,
 | 
			
		||||
          'serialization_class' => $entity_type->getClass(),
 | 
			
		||||
          'label' => $entity_type->getLabel(),
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        $default_uris = [
 | 
			
		||||
          'canonical' => "/entity/$entity_type_id/" . '{' . $entity_type_id . '}',
 | 
			
		||||
          'create' => "/entity/$entity_type_id",
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        foreach ($default_uris as $link_relation => $default_uri) {
 | 
			
		||||
          // Check if there are link templates defined for the entity type and
 | 
			
		||||
          // use the path from the route instead of the default.
 | 
			
		||||
          if ($link_template = $entity_type->getLinkTemplate($link_relation)) {
 | 
			
		||||
            $this->derivatives[$entity_type_id]['uri_paths'][$link_relation] = $link_template;
 | 
			
		||||
          }
 | 
			
		||||
          else {
 | 
			
		||||
            $this->derivatives[$entity_type_id]['uri_paths'][$link_relation] = $default_uri;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->derivatives[$entity_type_id] += $base_plugin_definition;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $this->derivatives;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										216
									
								
								web/core/modules/rest/src/Plugin/ResourceBase.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								web/core/modules/rest/src/Plugin/ResourceBase.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,216 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\rest\Plugin;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 | 
			
		||||
use Drupal\Core\Plugin\PluginBase;
 | 
			
		||||
use Psr\Log\LoggerInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
use Symfony\Component\Routing\Route;
 | 
			
		||||
use Symfony\Component\Routing\RouteCollection;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Common base class for resource plugins.
 | 
			
		||||
 *
 | 
			
		||||
 * Note that this base class' implementation of the permissions() method
 | 
			
		||||
 * generates a permission for every method for a resource. If your resource
 | 
			
		||||
 * already has its own access control mechanism, you should opt out from this
 | 
			
		||||
 * default permissions() method by overriding it.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\rest\Annotation\RestResource
 | 
			
		||||
 * @see \Drupal\rest\Plugin\Type\ResourcePluginManager
 | 
			
		||||
 * @see \Drupal\rest\Plugin\ResourceInterface
 | 
			
		||||
 * @see plugin_api
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup third_party
 | 
			
		||||
 */
 | 
			
		||||
abstract class ResourceBase extends PluginBase implements ContainerFactoryPluginInterface, ResourceInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The available serialization formats.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $serializerFormats = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A logger instance.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Psr\Log\LoggerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $logger;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a Drupal\rest\Plugin\ResourceBase object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $configuration
 | 
			
		||||
   *   A configuration array containing information about the plugin instance.
 | 
			
		||||
   * @param string $plugin_id
 | 
			
		||||
   *   The plugin ID for the plugin instance.
 | 
			
		||||
   * @param mixed $plugin_definition
 | 
			
		||||
   *   The plugin implementation definition.
 | 
			
		||||
   * @param array $serializer_formats
 | 
			
		||||
   *   The available serialization formats.
 | 
			
		||||
   * @param \Psr\Log\LoggerInterface $logger
 | 
			
		||||
   *   A logger instance.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(array $configuration, $plugin_id, $plugin_definition, array $serializer_formats, LoggerInterface $logger) {
 | 
			
		||||
    parent::__construct($configuration, $plugin_id, $plugin_definition);
 | 
			
		||||
    $this->serializerFormats = $serializer_formats;
 | 
			
		||||
    $this->logger = $logger;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $configuration,
 | 
			
		||||
      $plugin_id,
 | 
			
		||||
      $plugin_definition,
 | 
			
		||||
      $container->getParameter('serializer.formats'),
 | 
			
		||||
      $container->get('logger.factory')->get('rest')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements ResourceInterface::permissions().
 | 
			
		||||
   *
 | 
			
		||||
   * Every plugin operation method gets its own user permission. Example:
 | 
			
		||||
   * "restful delete entity:node" with the title "Access DELETE on Node
 | 
			
		||||
   * resource".
 | 
			
		||||
   */
 | 
			
		||||
  public function permissions() {
 | 
			
		||||
    $permissions = [];
 | 
			
		||||
    $definition = $this->getPluginDefinition();
 | 
			
		||||
    foreach ($this->availableMethods() as $method) {
 | 
			
		||||
      $lowered_method = strtolower($method);
 | 
			
		||||
      $permissions["restful $lowered_method $this->pluginId"] = [
 | 
			
		||||
        'title' => $this->t('Access @method on %label resource', ['@method' => $method, '%label' => $definition['label']]),
 | 
			
		||||
      ];
 | 
			
		||||
    }
 | 
			
		||||
    return $permissions;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function routes() {
 | 
			
		||||
    $collection = new RouteCollection();
 | 
			
		||||
 | 
			
		||||
    $definition = $this->getPluginDefinition();
 | 
			
		||||
    $canonical_path = $definition['uri_paths']['canonical'] ?? '/' . strtr($this->pluginId, ':', '/') . '/{id}';
 | 
			
		||||
    $create_path = $definition['uri_paths']['create'] ?? '/' . strtr($this->pluginId, ':', '/');
 | 
			
		||||
 | 
			
		||||
    $route_name = strtr($this->pluginId, ':', '.');
 | 
			
		||||
 | 
			
		||||
    $methods = $this->availableMethods();
 | 
			
		||||
    foreach ($methods as $method) {
 | 
			
		||||
      $path = $method === 'POST'
 | 
			
		||||
        ? $create_path
 | 
			
		||||
        : $canonical_path;
 | 
			
		||||
      $route = $this->getBaseRoute($path, $method);
 | 
			
		||||
 | 
			
		||||
      // Note that '_format' and '_content_type_format' route requirements are
 | 
			
		||||
      // added in ResourceRoutes::getRoutesForResourceConfig().
 | 
			
		||||
      $collection->add("$route_name.$method", $route);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $collection;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provides predefined HTTP request methods.
 | 
			
		||||
   *
 | 
			
		||||
   * Plugins can override this method to provide additional custom request
 | 
			
		||||
   * methods.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The list of allowed HTTP request method strings.
 | 
			
		||||
   */
 | 
			
		||||
  protected function requestMethods() {
 | 
			
		||||
    return [
 | 
			
		||||
      'HEAD',
 | 
			
		||||
      'GET',
 | 
			
		||||
      'POST',
 | 
			
		||||
      'PUT',
 | 
			
		||||
      'DELETE',
 | 
			
		||||
      'TRACE',
 | 
			
		||||
      'OPTIONS',
 | 
			
		||||
      'CONNECT',
 | 
			
		||||
      'PATCH',
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function availableMethods() {
 | 
			
		||||
    $methods = $this->requestMethods();
 | 
			
		||||
    $available = [];
 | 
			
		||||
    foreach ($methods as $method) {
 | 
			
		||||
      // Only expose methods where the HTTP request method exists on the plugin.
 | 
			
		||||
      if (method_exists($this, strtolower($method))) {
 | 
			
		||||
        $available[] = $method;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $available;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the base route for a particular method.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $canonical_path
 | 
			
		||||
   *   The canonical path for the resource.
 | 
			
		||||
   * @param string $method
 | 
			
		||||
   *   The HTTP method to be used for the route.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Symfony\Component\Routing\Route
 | 
			
		||||
   *   The created base route.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getBaseRoute($canonical_path, $method) {
 | 
			
		||||
    return new Route($canonical_path, [
 | 
			
		||||
      '_controller' => 'Drupal\rest\RequestHandler::handle',
 | 
			
		||||
    ],
 | 
			
		||||
      $this->getBaseRouteRequirements($method),
 | 
			
		||||
      [],
 | 
			
		||||
      '',
 | 
			
		||||
      [],
 | 
			
		||||
      // The HTTP method is a requirement for this route.
 | 
			
		||||
      [$method]
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the base route requirements for a particular method.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $method
 | 
			
		||||
   *   The HTTP method to be used for the route.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An array of requirements for parameters.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getBaseRouteRequirements($method) {
 | 
			
		||||
    $lower_method = strtolower($method);
 | 
			
		||||
    // Every route MUST have requirements that result in the access manager
 | 
			
		||||
    // having access checks to check. If it does not, the route is made
 | 
			
		||||
    // inaccessible. So, we default to granting access to everyone. If a
 | 
			
		||||
    // permission exists, then we add that below. The access manager requires
 | 
			
		||||
    // that ALL access checks must grant access, so this still results in
 | 
			
		||||
    // correct behavior.
 | 
			
		||||
    $requirements = [
 | 
			
		||||
      '_access' => 'TRUE',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // Only specify route requirements if the default permission exists. For any
 | 
			
		||||
    // more advanced route definition, resource plugins extending this base
 | 
			
		||||
    // class must override this method.
 | 
			
		||||
    $permission = "restful $lower_method $this->pluginId";
 | 
			
		||||
    if (isset($this->permissions()[$permission])) {
 | 
			
		||||
      $requirements['_permission'] = $permission;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $requirements;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										53
									
								
								web/core/modules/rest/src/Plugin/ResourceInterface.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								web/core/modules/rest/src/Plugin/ResourceInterface.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,53 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\rest\Plugin;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Plugin\PluginInspectionInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Specifies the publicly available methods of a resource plugin.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\rest\Annotation\RestResource
 | 
			
		||||
 * @see \Drupal\rest\Plugin\Type\ResourcePluginManager
 | 
			
		||||
 * @see \Drupal\rest\Plugin\ResourceBase
 | 
			
		||||
 * @see plugin_api
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup third_party
 | 
			
		||||
 */
 | 
			
		||||
interface ResourceInterface extends PluginInspectionInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns a collection of routes with URL path information for the resource.
 | 
			
		||||
   *
 | 
			
		||||
   * This method determines where a resource is reachable, what path
 | 
			
		||||
   * replacements are used, the required HTTP method for the operation etc.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Symfony\Component\Routing\RouteCollection
 | 
			
		||||
   *   A collection of routes that should be registered for this resource.
 | 
			
		||||
   */
 | 
			
		||||
  public function routes();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provides an array of permissions suitable for .permissions.yml files.
 | 
			
		||||
   *
 | 
			
		||||
   * A resource plugin can define a set of user permissions that are used on the
 | 
			
		||||
   * routes for this resource or for other purposes.
 | 
			
		||||
   *
 | 
			
		||||
   * It is not required for a resource plugin to specify permissions: if they
 | 
			
		||||
   * have their own access control mechanism, they can use that, and return the
 | 
			
		||||
   * empty array.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The permission array.
 | 
			
		||||
   */
 | 
			
		||||
  public function permissions();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the available HTTP request methods on this plugin.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The list of supported methods. For example, ['GET', 'POST', 'PATCH'].
 | 
			
		||||
   */
 | 
			
		||||
  public function availableMethods();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,46 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\rest\Plugin\Type;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Cache\CacheBackendInterface;
 | 
			
		||||
use Drupal\Core\Extension\ModuleHandlerInterface;
 | 
			
		||||
use Drupal\Core\Plugin\DefaultPluginManager;
 | 
			
		||||
use Drupal\rest\Attribute\RestResource;
 | 
			
		||||
use Drupal\rest\Plugin\ResourceInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Manages discovery and instantiation of resource plugins.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\rest\Annotation\RestResource
 | 
			
		||||
 * @see \Drupal\rest\Plugin\ResourceBase
 | 
			
		||||
 * @see \Drupal\rest\Plugin\ResourceInterface
 | 
			
		||||
 * @see plugin_api
 | 
			
		||||
 */
 | 
			
		||||
class ResourcePluginManager extends DefaultPluginManager {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new \Drupal\rest\Plugin\Type\ResourcePluginManager object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Traversable $namespaces
 | 
			
		||||
   *   An object that implements \Traversable which contains the root paths
 | 
			
		||||
   *   keyed by the corresponding namespace to look for plugin implementations.
 | 
			
		||||
   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
 | 
			
		||||
   *   Cache backend instance to use.
 | 
			
		||||
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
 | 
			
		||||
   *   The module handler to invoke the alter hook with.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
 | 
			
		||||
    parent::__construct(
 | 
			
		||||
      'Plugin/rest/resource',
 | 
			
		||||
      $namespaces,
 | 
			
		||||
      $module_handler,
 | 
			
		||||
      ResourceInterface::class,
 | 
			
		||||
      RestResource::class,
 | 
			
		||||
      'Drupal\rest\Annotation\RestResource',
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    $this->setCacheBackend($cache_backend, 'rest_plugins');
 | 
			
		||||
    $this->alterInfo('rest_resource');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,467 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\rest\Plugin\rest\resource;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Plugin\DependentPluginInterface;
 | 
			
		||||
use Drupal\Component\Plugin\PluginManagerInterface;
 | 
			
		||||
use Drupal\Core\Access\AccessResultReasonInterface;
 | 
			
		||||
use Drupal\Core\Cache\CacheableResponseInterface;
 | 
			
		||||
use Drupal\Core\Config\Entity\ConfigEntityType;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
 | 
			
		||||
use Drupal\Core\Entity\FieldableEntityInterface;
 | 
			
		||||
use Drupal\Core\Config\ConfigFactoryInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityStorageException;
 | 
			
		||||
use Drupal\Core\Field\FieldItemListInterface;
 | 
			
		||||
use Drupal\Core\Routing\AccessAwareRouterInterface;
 | 
			
		||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
 | 
			
		||||
use Drupal\rest\Attribute\RestResource;
 | 
			
		||||
use Drupal\rest\Plugin\Deriver\EntityDeriver;
 | 
			
		||||
use Drupal\rest\Plugin\ResourceBase;
 | 
			
		||||
use Drupal\rest\ResourceResponse;
 | 
			
		||||
use Psr\Log\LoggerInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
use Drupal\rest\ModifiedResourceResponse;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Response;
 | 
			
		||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 | 
			
		||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
 | 
			
		||||
use Symfony\Component\HttpKernel\Exception\HttpException;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents entities as resources.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\rest\Plugin\Deriver\EntityDeriver
 | 
			
		||||
 */
 | 
			
		||||
#[RestResource(
 | 
			
		||||
  id: "entity",
 | 
			
		||||
  label: new TranslatableMarkup("Entity"),
 | 
			
		||||
  serialization_class: "Drupal\Core\Entity\Entity",
 | 
			
		||||
  deriver: EntityDeriver::class,
 | 
			
		||||
  uri_paths: [
 | 
			
		||||
    "canonical" => "/entity/{entity_type}/{entity}",
 | 
			
		||||
    "create" => "/entity/{entity_type}",
 | 
			
		||||
  ],
 | 
			
		||||
)]
 | 
			
		||||
class EntityResource extends ResourceBase implements DependentPluginInterface {
 | 
			
		||||
 | 
			
		||||
  use EntityResourceValidationTrait;
 | 
			
		||||
  use EntityResourceAccessTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity type targeted by this resource.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityTypeInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityType;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The config factory.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\ConfigFactoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $configFactory;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The link relation type manager used to create HTTP header links.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Component\Plugin\PluginManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $linkRelationTypeManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a Drupal\rest\Plugin\rest\resource\EntityResource object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $configuration
 | 
			
		||||
   *   A configuration array containing information about the plugin instance.
 | 
			
		||||
   * @param string $plugin_id
 | 
			
		||||
   *   The plugin ID for the plugin instance.
 | 
			
		||||
   * @param mixed $plugin_definition
 | 
			
		||||
   *   The plugin implementation definition.
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
 | 
			
		||||
   *   The entity type manager.
 | 
			
		||||
   * @param array $serializer_formats
 | 
			
		||||
   *   The available serialization formats.
 | 
			
		||||
   * @param \Psr\Log\LoggerInterface $logger
 | 
			
		||||
   *   A logger instance.
 | 
			
		||||
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
 | 
			
		||||
   *   The config factory.
 | 
			
		||||
   * @param \Drupal\Component\Plugin\PluginManagerInterface $link_relation_type_manager
 | 
			
		||||
   *   The link relation type manager.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, $serializer_formats, LoggerInterface $logger, ConfigFactoryInterface $config_factory, PluginManagerInterface $link_relation_type_manager) {
 | 
			
		||||
    parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);
 | 
			
		||||
    $this->entityType = $entity_type_manager->getDefinition($plugin_definition['entity_type']);
 | 
			
		||||
    $this->configFactory = $config_factory;
 | 
			
		||||
    $this->linkRelationTypeManager = $link_relation_type_manager;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $configuration,
 | 
			
		||||
      $plugin_id,
 | 
			
		||||
      $plugin_definition,
 | 
			
		||||
      $container->get('entity_type.manager'),
 | 
			
		||||
      $container->getParameter('serializer.formats'),
 | 
			
		||||
      $container->get('logger.factory')->get('rest'),
 | 
			
		||||
      $container->get('config.factory'),
 | 
			
		||||
      $container->get('plugin.manager.link_relation_type')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Responds to entity GET requests.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityInterface $entity
 | 
			
		||||
   *   The entity object.
 | 
			
		||||
   * @param \Symfony\Component\HttpFoundation\Request $request
 | 
			
		||||
   *   The incoming request.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\rest\ResourceResponse
 | 
			
		||||
   *   The response containing the entity with its accessible fields.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Symfony\Component\HttpKernel\Exception\HttpException
 | 
			
		||||
   */
 | 
			
		||||
  public function get(EntityInterface $entity, Request $request) {
 | 
			
		||||
    $response = new ResourceResponse($entity, 200);
 | 
			
		||||
    // @todo Either remove the line below or remove this todo in https://www.drupal.org/project/drupal/issues/2973356
 | 
			
		||||
    $response->addCacheableDependency($request->attributes->get(AccessAwareRouterInterface::ACCESS_RESULT));
 | 
			
		||||
    $response->addCacheableDependency($entity);
 | 
			
		||||
 | 
			
		||||
    if ($entity instanceof FieldableEntityInterface) {
 | 
			
		||||
      foreach ($entity as $field_name => $field) {
 | 
			
		||||
        /** @var \Drupal\Core\Field\FieldItemListInterface $field */
 | 
			
		||||
        $field_access = $field->access('view', NULL, TRUE);
 | 
			
		||||
        $response->addCacheableDependency($field_access);
 | 
			
		||||
 | 
			
		||||
        if (!$field_access->isAllowed()) {
 | 
			
		||||
          $entity->set($field_name, NULL);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $this->addLinkHeaders($entity, $response);
 | 
			
		||||
 | 
			
		||||
    return $response;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Responds to entity POST requests and saves the new entity.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityInterface $entity
 | 
			
		||||
   *   The entity.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\rest\ModifiedResourceResponse
 | 
			
		||||
   *   The HTTP response object.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Symfony\Component\HttpKernel\Exception\HttpException
 | 
			
		||||
   */
 | 
			
		||||
  public function post(?EntityInterface $entity = NULL) {
 | 
			
		||||
    if ($entity == NULL) {
 | 
			
		||||
      throw new BadRequestHttpException('No entity content received.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $entity_access = $entity->access('create', NULL, TRUE);
 | 
			
		||||
    if (!$entity_access->isAllowed()) {
 | 
			
		||||
      throw new AccessDeniedHttpException($entity_access->getReason() ?: $this->generateFallbackAccessDeniedMessage($entity, 'create'));
 | 
			
		||||
    }
 | 
			
		||||
    $definition = $this->getPluginDefinition();
 | 
			
		||||
    // Verify that the deserialized entity is of the type that we expect to
 | 
			
		||||
    // prevent security issues.
 | 
			
		||||
    if ($entity->getEntityTypeId() != $definition['entity_type']) {
 | 
			
		||||
      throw new BadRequestHttpException('Invalid entity type');
 | 
			
		||||
    }
 | 
			
		||||
    // POSTed entities must not have an ID set, because we always want to create
 | 
			
		||||
    // new entities here.
 | 
			
		||||
    if (!$entity->isNew()) {
 | 
			
		||||
      throw new BadRequestHttpException('Only new entities can be created');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $this->checkEditFieldAccess($entity);
 | 
			
		||||
 | 
			
		||||
    // Validate the received data before saving.
 | 
			
		||||
    $this->validate($entity);
 | 
			
		||||
    try {
 | 
			
		||||
      $entity->save();
 | 
			
		||||
      $this->logger->notice('Created entity %type with ID %id.', ['%type' => $entity->getEntityTypeId(), '%id' => $entity->id()]);
 | 
			
		||||
 | 
			
		||||
      // 201 Created responses return the newly created entity in the response
 | 
			
		||||
      // body. These responses are not cacheable, so we add no cacheability
 | 
			
		||||
      // metadata here.
 | 
			
		||||
      $headers = [];
 | 
			
		||||
      if (in_array('canonical', $entity->uriRelationships(), TRUE)) {
 | 
			
		||||
        $url = $entity->toUrl('canonical', ['absolute' => TRUE])->toString(TRUE);
 | 
			
		||||
        $headers['Location'] = $url->getGeneratedUrl();
 | 
			
		||||
      }
 | 
			
		||||
      return new ModifiedResourceResponse($entity, 201, $headers);
 | 
			
		||||
    }
 | 
			
		||||
    catch (EntityStorageException $e) {
 | 
			
		||||
      throw new HttpException(500, 'Internal Server Error', $e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Responds to entity PATCH requests.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityInterface $original_entity
 | 
			
		||||
   *   The original entity object.
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityInterface $entity
 | 
			
		||||
   *   The entity.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\rest\ModifiedResourceResponse
 | 
			
		||||
   *   The HTTP response object.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Symfony\Component\HttpKernel\Exception\HttpException
 | 
			
		||||
   */
 | 
			
		||||
  public function patch(EntityInterface $original_entity, ?EntityInterface $entity = NULL) {
 | 
			
		||||
    if ($entity == NULL) {
 | 
			
		||||
      throw new BadRequestHttpException('No entity content received.');
 | 
			
		||||
    }
 | 
			
		||||
    $definition = $this->getPluginDefinition();
 | 
			
		||||
    if ($entity->getEntityTypeId() != $definition['entity_type']) {
 | 
			
		||||
      throw new BadRequestHttpException('Invalid entity type');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Overwrite the received fields.
 | 
			
		||||
    // @todo Remove $changed_fields in https://www.drupal.org/project/drupal/issues/2862574.
 | 
			
		||||
    $changed_fields = [];
 | 
			
		||||
    foreach ($entity->_restSubmittedFields as $field_name) {
 | 
			
		||||
      $field = $entity->get($field_name);
 | 
			
		||||
      // It is not possible to set the language to NULL as it is automatically
 | 
			
		||||
      // re-initialized. As it must not be empty, skip it if it is.
 | 
			
		||||
      // @todo Remove in https://www.drupal.org/project/drupal/issues/2933408.
 | 
			
		||||
      if ($entity->getEntityType()->hasKey('langcode') && $field_name === $entity->getEntityType()->getKey('langcode') && $field->isEmpty()) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      if ($this->checkPatchFieldAccess($original_entity->get($field_name), $field)) {
 | 
			
		||||
        $changed_fields[] = $field_name;
 | 
			
		||||
        $original_entity->set($field_name, $field->getValue());
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If no fields are changed, we can send a response immediately!
 | 
			
		||||
    if (empty($changed_fields)) {
 | 
			
		||||
      return new ModifiedResourceResponse($original_entity, 200);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Validate the received data before saving.
 | 
			
		||||
    $this->validate($original_entity, $changed_fields);
 | 
			
		||||
    try {
 | 
			
		||||
      $original_entity->save();
 | 
			
		||||
      $this->logger->notice('Updated entity %type with ID %id.', ['%type' => $original_entity->getEntityTypeId(), '%id' => $original_entity->id()]);
 | 
			
		||||
 | 
			
		||||
      // Return the updated entity in the response body.
 | 
			
		||||
      return new ModifiedResourceResponse($original_entity, 200);
 | 
			
		||||
    }
 | 
			
		||||
    catch (EntityStorageException $e) {
 | 
			
		||||
      throw new HttpException(500, 'Internal Server Error', $e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Checks whether the given field should be PATCHed.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Field\FieldItemListInterface $original_field
 | 
			
		||||
   *   The original (stored) value for the field.
 | 
			
		||||
   * @param \Drupal\Core\Field\FieldItemListInterface $received_field
 | 
			
		||||
   *   The received value for the field.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   Whether the field should be PATCHed or not.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
 | 
			
		||||
   *   Thrown when the user sending the request is not allowed to update the
 | 
			
		||||
   *   field. Only thrown when the user could not abuse this information to
 | 
			
		||||
   *   determine the stored value.
 | 
			
		||||
   *
 | 
			
		||||
   * @internal
 | 
			
		||||
   */
 | 
			
		||||
  protected function checkPatchFieldAccess(FieldItemListInterface $original_field, FieldItemListInterface $received_field) {
 | 
			
		||||
    // The user might not have access to edit the field, but still needs to
 | 
			
		||||
    // submit the current field value as part of the PATCH request. For
 | 
			
		||||
    // example, the entity keys required by denormalizers. Therefore, if the
 | 
			
		||||
    // received value equals the stored value, return FALSE without throwing an
 | 
			
		||||
    // exception. But only for fields that the user has access to view, because
 | 
			
		||||
    // the user has no legitimate way of knowing the current value of fields
 | 
			
		||||
    // that they are not allowed to view, and we must not make the presence or
 | 
			
		||||
    // absence of a 403 response a way to find that out.
 | 
			
		||||
    if ($original_field->access('view') && $original_field->equals($received_field)) {
 | 
			
		||||
      return FALSE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If the user is allowed to edit the field, it is always safe to set the
 | 
			
		||||
    // received value. We may be setting an unchanged value, but that is ok.
 | 
			
		||||
    $field_edit_access = $original_field->access('edit', NULL, TRUE);
 | 
			
		||||
    if ($field_edit_access->isAllowed()) {
 | 
			
		||||
      return TRUE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // It's helpful and safe to let the user know when they are not allowed to
 | 
			
		||||
    // update a field.
 | 
			
		||||
    $field_name = $received_field->getName();
 | 
			
		||||
    $error_message = "Access denied on updating field '$field_name'.";
 | 
			
		||||
    if ($field_edit_access instanceof AccessResultReasonInterface) {
 | 
			
		||||
      $reason = $field_edit_access->getReason();
 | 
			
		||||
      if ($reason) {
 | 
			
		||||
        $error_message .= ' ' . $reason;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    throw new AccessDeniedHttpException($error_message);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Responds to entity DELETE requests.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityInterface $entity
 | 
			
		||||
   *   The entity object.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\rest\ModifiedResourceResponse
 | 
			
		||||
   *   The HTTP response object.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Symfony\Component\HttpKernel\Exception\HttpException
 | 
			
		||||
   */
 | 
			
		||||
  public function delete(EntityInterface $entity) {
 | 
			
		||||
    try {
 | 
			
		||||
      $entity->delete();
 | 
			
		||||
      $this->logger->notice('Deleted entity %type with ID %id.', ['%type' => $entity->getEntityTypeId(), '%id' => $entity->id()]);
 | 
			
		||||
 | 
			
		||||
      // DELETE responses have an empty body.
 | 
			
		||||
      return new ModifiedResourceResponse(NULL, 204);
 | 
			
		||||
    }
 | 
			
		||||
    catch (EntityStorageException $e) {
 | 
			
		||||
      throw new HttpException(500, 'Internal Server Error', $e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Generates a fallback access denied message, when no specific reason is set.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityInterface $entity
 | 
			
		||||
   *   The entity object.
 | 
			
		||||
   * @param string $operation
 | 
			
		||||
   *   The disallowed entity operation.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The proper message to display in the AccessDeniedHttpException.
 | 
			
		||||
   */
 | 
			
		||||
  protected function generateFallbackAccessDeniedMessage(EntityInterface $entity, $operation) {
 | 
			
		||||
    $message = "You are not authorized to {$operation} this {$entity->getEntityTypeId()} entity";
 | 
			
		||||
 | 
			
		||||
    if ($entity->bundle() !== $entity->getEntityTypeId()) {
 | 
			
		||||
      $message .= " of bundle {$entity->bundle()}";
 | 
			
		||||
    }
 | 
			
		||||
    return "{$message}.";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function permissions() {
 | 
			
		||||
    return [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function getBaseRoute($canonical_path, $method) {
 | 
			
		||||
    $route = parent::getBaseRoute($canonical_path, $method);
 | 
			
		||||
 | 
			
		||||
    switch ($method) {
 | 
			
		||||
      case 'GET':
 | 
			
		||||
        $route->setRequirement('_entity_access', $this->entityType->id() . '.view');
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case 'POST':
 | 
			
		||||
        $route->setRequirement('_entity_create_any_access', $this->entityType->id());
 | 
			
		||||
        $route->setOption('_ignore_create_bundle_access', TRUE);
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case 'PATCH':
 | 
			
		||||
        $route->setRequirement('_entity_access', $this->entityType->id() . '.update');
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case 'DELETE':
 | 
			
		||||
        $route->setRequirement('_entity_access', $this->entityType->id() . '.delete');
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $definition = $this->getPluginDefinition();
 | 
			
		||||
 | 
			
		||||
    $parameters = $route->getOption('parameters') ?: [];
 | 
			
		||||
    $parameters[$definition['entity_type']]['type'] = 'entity:' . $definition['entity_type'];
 | 
			
		||||
    $route->setOption('parameters', $parameters);
 | 
			
		||||
 | 
			
		||||
    return $route;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function availableMethods() {
 | 
			
		||||
    $methods = parent::availableMethods();
 | 
			
		||||
    if ($this->isConfigEntityResource()) {
 | 
			
		||||
      // Currently only GET is supported for Config Entities.
 | 
			
		||||
      // @todo Remove when supported https://www.drupal.org/node/2300677
 | 
			
		||||
      $unsupported_methods = ['POST', 'PUT', 'DELETE', 'PATCH'];
 | 
			
		||||
      $methods = array_diff($methods, $unsupported_methods);
 | 
			
		||||
    }
 | 
			
		||||
    return $methods;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Checks if this resource is for a Config Entity.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if the entity is a Config Entity, FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  protected function isConfigEntityResource() {
 | 
			
		||||
    return $this->entityType instanceof ConfigEntityType;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function calculateDependencies() {
 | 
			
		||||
    if (isset($this->entityType)) {
 | 
			
		||||
      return ['module' => [$this->entityType->getProvider()]];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Adds link headers to a response.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityInterface $entity
 | 
			
		||||
   *   The entity.
 | 
			
		||||
   * @param \Symfony\Component\HttpFoundation\Response $response
 | 
			
		||||
   *   The response.
 | 
			
		||||
   *
 | 
			
		||||
   * @see https://tools.ietf.org/html/rfc5988#section-5
 | 
			
		||||
   */
 | 
			
		||||
  protected function addLinkHeaders(EntityInterface $entity, Response $response) {
 | 
			
		||||
    foreach ($entity->uriRelationships() as $relation_name) {
 | 
			
		||||
      if ($this->linkRelationTypeManager->hasDefinition($relation_name)) {
 | 
			
		||||
        /** @var \Drupal\Core\Http\LinkRelationTypeInterface $link_relation_type */
 | 
			
		||||
        $link_relation_type = $this->linkRelationTypeManager->createInstance($relation_name);
 | 
			
		||||
 | 
			
		||||
        $generator_url = $entity->toUrl($relation_name)
 | 
			
		||||
          ->setAbsolute(TRUE)
 | 
			
		||||
          ->toString(TRUE);
 | 
			
		||||
        if ($response instanceof CacheableResponseInterface) {
 | 
			
		||||
          $response->addCacheableDependency($generator_url);
 | 
			
		||||
        }
 | 
			
		||||
        $uri = $generator_url->getGeneratedUrl();
 | 
			
		||||
 | 
			
		||||
        $relationship = $link_relation_type->isRegistered()
 | 
			
		||||
          ? $link_relation_type->getRegisteredName()
 | 
			
		||||
          : $link_relation_type->getExtensionUri();
 | 
			
		||||
 | 
			
		||||
        $link_header = '<' . $uri . '>; rel="' . $relationship . '"';
 | 
			
		||||
        $response->headers->set('Link', $link_header, FALSE);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,35 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\rest\Plugin\rest\resource;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Entity\EntityInterface;
 | 
			
		||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @internal
 | 
			
		||||
 * @todo Consider making public in https://www.drupal.org/node/2300677
 | 
			
		||||
 */
 | 
			
		||||
trait EntityResourceAccessTrait {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Performs edit access checks for fields.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityInterface $entity
 | 
			
		||||
   *   The entity whose fields edit access should be checked for.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
 | 
			
		||||
   *   Throws access denied when the user does not have permissions to edit a
 | 
			
		||||
   *   field.
 | 
			
		||||
   */
 | 
			
		||||
  protected function checkEditFieldAccess(EntityInterface $entity) {
 | 
			
		||||
    // Only check 'edit' permissions for fields that were actually submitted by
 | 
			
		||||
    // the user. Field access makes no difference between 'create' and 'update',
 | 
			
		||||
    // so the 'edit' operation is used here.
 | 
			
		||||
    foreach ($entity->_restSubmittedFields as $field_name) {
 | 
			
		||||
      if (!$entity->get($field_name)->access('edit')) {
 | 
			
		||||
        throw new AccessDeniedHttpException("Access denied on creating field '$field_name'.");
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,59 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\rest\Plugin\rest\resource;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Render\PlainTextOutput;
 | 
			
		||||
use Drupal\Core\Entity\EntityInterface;
 | 
			
		||||
use Drupal\Core\Entity\FieldableEntityInterface;
 | 
			
		||||
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @internal
 | 
			
		||||
 * @todo Consider making public in https://www.drupal.org/node/2300677
 | 
			
		||||
 */
 | 
			
		||||
trait EntityResourceValidationTrait {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Verifies that an entity does not violate any validation constraints.
 | 
			
		||||
   *
 | 
			
		||||
   * The validation errors will be filtered to not include fields to which the
 | 
			
		||||
   * current user does not have access and if $fields_to_validate is provided
 | 
			
		||||
   * will only include fields in that array.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityInterface $entity
 | 
			
		||||
   *   The entity to validate.
 | 
			
		||||
   * @param string[] $fields_to_validate
 | 
			
		||||
   *   (optional) An array of field names. If specified, filters the violations
 | 
			
		||||
   *   list to include only this set of fields.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException
 | 
			
		||||
   *   If validation errors are found.
 | 
			
		||||
   */
 | 
			
		||||
  protected function validate(EntityInterface $entity, array $fields_to_validate = []) {
 | 
			
		||||
    // @todo Update this check in https://www.drupal.org/node/2300677.
 | 
			
		||||
    if (!$entity instanceof FieldableEntityInterface) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    $violations = $entity->validate();
 | 
			
		||||
 | 
			
		||||
    // Remove violations of inaccessible fields as they cannot stem from our
 | 
			
		||||
    // changes.
 | 
			
		||||
    $violations->filterByFieldAccess();
 | 
			
		||||
 | 
			
		||||
    if ($fields_to_validate) {
 | 
			
		||||
      // Filter violations by explicitly provided array of field names.
 | 
			
		||||
      $violations->filterByFields(array_diff(array_keys($entity->getFieldDefinitions()), $fields_to_validate));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($violations->count() > 0) {
 | 
			
		||||
      $message = "Unprocessable Entity: validation failed.\n";
 | 
			
		||||
      foreach ($violations as $violation) {
 | 
			
		||||
        // We strip every HTML from the error message to have a nicer to read
 | 
			
		||||
        // message on REST responses.
 | 
			
		||||
        $message .= $violation->getPropertyPath() . ': ' . PlainTextOutput::renderFromHtml($violation->getMessage()) . "\n";
 | 
			
		||||
      }
 | 
			
		||||
      throw new UnprocessableEntityHttpException($message);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										489
									
								
								web/core/modules/rest/src/Plugin/views/display/RestExport.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										489
									
								
								web/core/modules/rest/src/Plugin/views/display/RestExport.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,489 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\rest\Plugin\views\display;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Utility\Unicode;
 | 
			
		||||
use Drupal\Core\Cache\CacheableMetadata;
 | 
			
		||||
use Drupal\Core\Cache\CacheableResponse;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\Render\RenderContext;
 | 
			
		||||
use Drupal\Core\Render\RendererInterface;
 | 
			
		||||
use Drupal\Core\Routing\RouteProviderInterface;
 | 
			
		||||
use Drupal\Core\State\StateInterface;
 | 
			
		||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
 | 
			
		||||
use Drupal\views\Attribute\ViewsDisplay;
 | 
			
		||||
use Drupal\views\Plugin\views\display\ResponseDisplayPluginInterface;
 | 
			
		||||
use Drupal\views\Render\ViewsRenderPipelineMarkup;
 | 
			
		||||
use Drupal\views\ViewExecutable;
 | 
			
		||||
use Drupal\views\Plugin\views\display\PathPluginBase;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
use Symfony\Component\Routing\Route;
 | 
			
		||||
use Symfony\Component\Routing\RouteCollection;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The plugin that handles Data response callbacks for REST resources.
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup views_display_plugins
 | 
			
		||||
 */
 | 
			
		||||
#[ViewsDisplay(
 | 
			
		||||
  id: "rest_export",
 | 
			
		||||
  title: new TranslatableMarkup("REST export"),
 | 
			
		||||
  help: new TranslatableMarkup("Create a REST export resource."),
 | 
			
		||||
  admin: new TranslatableMarkup("REST export"),
 | 
			
		||||
  uses_route: TRUE,
 | 
			
		||||
  returns_response: TRUE
 | 
			
		||||
)]
 | 
			
		||||
class RestExport extends PathPluginBase implements ResponseDisplayPluginInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected $usesAJAX = FALSE;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected $usesPager = FALSE;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected $usesMore = FALSE;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected $usesAreas = FALSE;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected $usesOptions = FALSE;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Overrides the content type of the data response, if needed.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $contentType = 'json';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The mime type for the response.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $mimeType = 'application/json';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The renderer.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Render\RendererInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $renderer;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The collector of authentication providers.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Authentication\AuthenticationCollectorInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $authenticationCollector;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The authentication providers, like 'cookie' and 'basic_auth'.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string[]
 | 
			
		||||
   */
 | 
			
		||||
  protected $authenticationProviderIds;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The serialization format providers, keyed by format.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string[]
 | 
			
		||||
   */
 | 
			
		||||
  protected $formatProviders;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a RestExport object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $configuration
 | 
			
		||||
   *   A configuration array containing information about the plugin instance.
 | 
			
		||||
   * @param string $plugin_id
 | 
			
		||||
   *   The plugin ID for the plugin instance.
 | 
			
		||||
   * @param mixed $plugin_definition
 | 
			
		||||
   *   The plugin implementation definition.
 | 
			
		||||
   * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
 | 
			
		||||
   *   The route provider.
 | 
			
		||||
   * @param \Drupal\Core\State\StateInterface $state
 | 
			
		||||
   *   The state key value store.
 | 
			
		||||
   * @param \Drupal\Core\Render\RendererInterface $renderer
 | 
			
		||||
   *   The renderer.
 | 
			
		||||
   * @param string[] $authentication_providers
 | 
			
		||||
   *   The authentication providers, keyed by ID.
 | 
			
		||||
   * @param string[] $serializer_format_providers
 | 
			
		||||
   *   The serialization format providers, keyed by format.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(array $configuration, $plugin_id, $plugin_definition, RouteProviderInterface $route_provider, StateInterface $state, RendererInterface $renderer, array $authentication_providers, array $serializer_format_providers) {
 | 
			
		||||
    parent::__construct($configuration, $plugin_id, $plugin_definition, $route_provider, $state);
 | 
			
		||||
 | 
			
		||||
    $this->renderer = $renderer;
 | 
			
		||||
    // $authentication_providers as defined in
 | 
			
		||||
    // \Drupal\Core\DependencyInjection\Compiler\AuthenticationProviderPass
 | 
			
		||||
    // and as such it is an array, with authentication providers (cookie,
 | 
			
		||||
    // basic_auth) as keys and modules providing those as values (user,
 | 
			
		||||
    // basic_auth).
 | 
			
		||||
    $this->authenticationProviderIds = array_keys($authentication_providers);
 | 
			
		||||
    $this->formatProviders = $serializer_format_providers;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $configuration,
 | 
			
		||||
      $plugin_id,
 | 
			
		||||
      $plugin_definition,
 | 
			
		||||
      $container->get('router.route_provider'),
 | 
			
		||||
      $container->get('state'),
 | 
			
		||||
      $container->get('renderer'),
 | 
			
		||||
      $container->getParameter('authentication_providers'),
 | 
			
		||||
      $container->getParameter('serializer.format_providers')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function initDisplay(ViewExecutable $view, array &$display, ?array &$options = NULL) {
 | 
			
		||||
    parent::initDisplay($view, $display, $options);
 | 
			
		||||
 | 
			
		||||
    // If the default 'json' format is not selected as a format option in the
 | 
			
		||||
    // view display, fallback to the first format available for the default.
 | 
			
		||||
    if (!empty($options['style']['options']['formats']) && !isset($options['style']['options']['formats'][$this->getContentType()])) {
 | 
			
		||||
      $default_format = reset($options['style']['options']['formats']);
 | 
			
		||||
      $this->setContentType($default_format);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Only use the requested content type if it's not 'html'. This allows
 | 
			
		||||
    // still falling back to the default for things like views preview.
 | 
			
		||||
    $request_content_type = $this->view->getRequest()->getRequestFormat();
 | 
			
		||||
 | 
			
		||||
    if ($request_content_type !== 'html') {
 | 
			
		||||
      $this->setContentType($request_content_type);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $this->setMimeType($this->view->getRequest()->getMimeType($this->getContentType()));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getType() {
 | 
			
		||||
    return 'data';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function usesExposed() {
 | 
			
		||||
    return TRUE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function displaysExposed() {
 | 
			
		||||
    return FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets the request content type.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $mime_type
 | 
			
		||||
   *   The response mime type. E.g. 'application/json'.
 | 
			
		||||
   */
 | 
			
		||||
  public function setMimeType($mime_type) {
 | 
			
		||||
    $this->mimeType = $mime_type;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the mime type.
 | 
			
		||||
   *
 | 
			
		||||
   * This will return any overridden mime type, otherwise returns the mime type
 | 
			
		||||
   * from the request.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The response mime type. E.g. 'application/json'.
 | 
			
		||||
   */
 | 
			
		||||
  public function getMimeType() {
 | 
			
		||||
    return $this->mimeType;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets the content type.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $content_type
 | 
			
		||||
   *   The content type machine name. E.g. 'json'.
 | 
			
		||||
   */
 | 
			
		||||
  public function setContentType($content_type) {
 | 
			
		||||
    $this->contentType = $content_type;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the content type.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The content type machine name. E.g. 'json'.
 | 
			
		||||
   */
 | 
			
		||||
  public function getContentType() {
 | 
			
		||||
    return $this->contentType;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the auth options available.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string[]
 | 
			
		||||
   *   An array to use as value for "#options" in the form element.
 | 
			
		||||
   */
 | 
			
		||||
  public function getAuthOptions() {
 | 
			
		||||
    return array_combine($this->authenticationProviderIds, $this->authenticationProviderIds);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function defineOptions() {
 | 
			
		||||
    $options = parent::defineOptions();
 | 
			
		||||
 | 
			
		||||
    // Options for REST authentication.
 | 
			
		||||
    $options['auth'] = ['default' => []];
 | 
			
		||||
 | 
			
		||||
    // Set the default style plugin to 'json'.
 | 
			
		||||
    $options['style']['contains']['type']['default'] = 'serializer';
 | 
			
		||||
    $options['row']['contains']['type']['default'] = 'data_entity';
 | 
			
		||||
    $options['defaults']['default']['style'] = FALSE;
 | 
			
		||||
    $options['defaults']['default']['row'] = FALSE;
 | 
			
		||||
 | 
			
		||||
    // Remove css/exposed form settings, as they are not used for the data
 | 
			
		||||
    // display.
 | 
			
		||||
    unset($options['exposed_form']);
 | 
			
		||||
    unset($options['exposed_block']);
 | 
			
		||||
    unset($options['css_class']);
 | 
			
		||||
 | 
			
		||||
    return $options;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function optionsSummary(&$categories, &$options) {
 | 
			
		||||
    parent::optionsSummary($categories, $options);
 | 
			
		||||
 | 
			
		||||
    // Authentication.
 | 
			
		||||
    $auth = $this->getOption('auth') ? implode(', ', $this->getOption('auth')) : $this->t('No authentication is set');
 | 
			
		||||
 | 
			
		||||
    unset($categories['page'], $categories['exposed']);
 | 
			
		||||
    // Hide some settings, as they aren't useful for pure data output.
 | 
			
		||||
    unset($options['show_admin_links'], $options['analyze-theme']);
 | 
			
		||||
 | 
			
		||||
    $categories['path'] = [
 | 
			
		||||
      'title' => $this->t('Path settings'),
 | 
			
		||||
      'column' => 'second',
 | 
			
		||||
      'build' => [
 | 
			
		||||
        '#weight' => -10,
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $options['path']['category'] = 'path';
 | 
			
		||||
    $options['path']['title'] = $this->t('Path');
 | 
			
		||||
    $options['auth'] = [
 | 
			
		||||
      'category' => 'path',
 | 
			
		||||
      'title' => $this->t('Authentication'),
 | 
			
		||||
      'value' => Unicode::truncate($auth, 24, FALSE, TRUE),
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // Remove css/exposed form settings, as they are not used for the data
 | 
			
		||||
    // display.
 | 
			
		||||
    unset($options['exposed_form']);
 | 
			
		||||
    unset($options['exposed_block']);
 | 
			
		||||
    unset($options['css_class']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildOptionsForm(&$form, FormStateInterface $form_state) {
 | 
			
		||||
    parent::buildOptionsForm($form, $form_state);
 | 
			
		||||
    if ($form_state->get('section') === 'auth') {
 | 
			
		||||
      $form['#title'] .= $this->t('The supported authentication methods for this view');
 | 
			
		||||
      $form['auth'] = [
 | 
			
		||||
        '#type' => 'checkboxes',
 | 
			
		||||
        '#title' => $this->t('Authentication methods'),
 | 
			
		||||
        '#description' => $this->t('These are the supported authentication providers for this view. When this view is requested, the client will be forced to authenticate with one of the selected providers. Make sure you set the appropriate requirements at the <em>Access</em> section since the Authentication System will fallback to the anonymous user if it fails to authenticate. For example: require Access: Role | Authenticated User.'),
 | 
			
		||||
        '#options' => $this->getAuthOptions(),
 | 
			
		||||
        '#default_value' => $this->getOption('auth'),
 | 
			
		||||
      ];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function submitOptionsForm(&$form, FormStateInterface $form_state) {
 | 
			
		||||
    parent::submitOptionsForm($form, $form_state);
 | 
			
		||||
 | 
			
		||||
    if ($form_state->get('section') == 'auth') {
 | 
			
		||||
      $this->setOption('auth', array_keys(array_filter($form_state->getValue('auth'))));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function collectRoutes(RouteCollection $collection) {
 | 
			
		||||
    parent::collectRoutes($collection);
 | 
			
		||||
    $view_id = $this->view->storage->id();
 | 
			
		||||
    $display_id = $this->display['id'];
 | 
			
		||||
 | 
			
		||||
    if ($route = $collection->get("view.$view_id.$display_id")) {
 | 
			
		||||
      $style_plugin = $this->getPlugin('style');
 | 
			
		||||
 | 
			
		||||
      // REST exports should only respond to GET methods.
 | 
			
		||||
      $route->setMethods(['GET']);
 | 
			
		||||
 | 
			
		||||
      $formats = $style_plugin->getFormats();
 | 
			
		||||
 | 
			
		||||
      // If there are no configured formats, add all formats that serialization
 | 
			
		||||
      // is known to support.
 | 
			
		||||
      if (!$formats) {
 | 
			
		||||
        $formats = $this->getFormatOptions();
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Format as a string using pipes as a delimiter.
 | 
			
		||||
      $route->setRequirement('_format', implode('|', $formats));
 | 
			
		||||
 | 
			
		||||
      // Add authentication to the route if it was set. If no authentication was
 | 
			
		||||
      // set, the default authentication will be used, which is cookie based by
 | 
			
		||||
      // default.
 | 
			
		||||
      $auth = $this->getOption('auth');
 | 
			
		||||
      if (!empty($auth)) {
 | 
			
		||||
        $route->setOption('_auth', $auth);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determines whether the view overrides the given route.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $view_path
 | 
			
		||||
   *   The path of the view.
 | 
			
		||||
   * @param \Symfony\Component\Routing\Route $view_route
 | 
			
		||||
   *   The route of the view.
 | 
			
		||||
   * @param \Symfony\Component\Routing\Route $route
 | 
			
		||||
   *   The route itself.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE, when the view should override the given route.
 | 
			
		||||
   */
 | 
			
		||||
  protected function overrideApplies($view_path, Route $view_route, Route $route) {
 | 
			
		||||
    $route_has_format = $route->hasRequirement('_format');
 | 
			
		||||
    $route_formats = $route_has_format ? explode('|', $route->getRequirement('_format')) : [];
 | 
			
		||||
    $view_route_formats = $view_route->hasRequirement('_format') ? explode('|', $view_route->getRequirement('_format')) : [];
 | 
			
		||||
    return $this->overrideAppliesPathAndMethod($view_path, $view_route, $route)
 | 
			
		||||
      && (!$route_has_format || array_intersect($route_formats, $view_route_formats) != []);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function buildResponse($view_id, $display_id, array $args = []) {
 | 
			
		||||
    $build = static::buildBasicRenderable($view_id, $display_id, $args);
 | 
			
		||||
 | 
			
		||||
    // Setup an empty response so headers can be added as needed during views
 | 
			
		||||
    // rendering and processing.
 | 
			
		||||
    $response = new CacheableResponse('', 200);
 | 
			
		||||
    $build['#response'] = $response;
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\Core\Render\RendererInterface $renderer */
 | 
			
		||||
    $renderer = \Drupal::service('renderer');
 | 
			
		||||
 | 
			
		||||
    $output = (string) $renderer->renderRoot($build);
 | 
			
		||||
 | 
			
		||||
    $response->setContent($output);
 | 
			
		||||
    $cache_metadata = CacheableMetadata::createFromRenderArray($build);
 | 
			
		||||
    $response->addCacheableDependency($cache_metadata);
 | 
			
		||||
 | 
			
		||||
    $response->headers->set('Content-type', $build['#content_type']);
 | 
			
		||||
 | 
			
		||||
    return $response;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function execute() {
 | 
			
		||||
    parent::execute();
 | 
			
		||||
 | 
			
		||||
    return $this->view->render();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function render() {
 | 
			
		||||
    $build = [];
 | 
			
		||||
    $build['#markup'] = $this->renderer->executeInRenderContext(new RenderContext(), function () {
 | 
			
		||||
      return $this->view->style_plugin->render();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $this->view->element['#content_type'] = $this->getMimeType();
 | 
			
		||||
    $this->view->element['#cache_properties'][] = '#content_type';
 | 
			
		||||
 | 
			
		||||
    // Encode and wrap the output in a pre tag if this is for a live preview.
 | 
			
		||||
    if (!empty($this->view->live_preview)) {
 | 
			
		||||
      $build['#prefix'] = '<pre>';
 | 
			
		||||
      $build['#plain_text'] = $build['#markup'];
 | 
			
		||||
      $build['#suffix'] = '</pre>';
 | 
			
		||||
      unset($build['#markup']);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      // This display plugin is for returning non-HTML formats. However, we
 | 
			
		||||
      // still invoke the renderer to collect cacheability metadata. Because the
 | 
			
		||||
      // renderer is designed for HTML rendering, it filters #markup for XSS
 | 
			
		||||
      // unless it is already known to be safe, but that filter only works for
 | 
			
		||||
      // HTML. Therefore, we mark the contents as safe to bypass the filter. So
 | 
			
		||||
      // long as we are returning this in a non-HTML response,
 | 
			
		||||
      // this is safe, because an XSS attack only works when executed by an HTML
 | 
			
		||||
      // agent.
 | 
			
		||||
      // @todo Decide how to support non-HTML in the render API in
 | 
			
		||||
      //   https://www.drupal.org/node/2501313.
 | 
			
		||||
      $build['#markup'] = ViewsRenderPipelineMarkup::create($build['#markup']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    parent::applyDisplayCacheabilityMetadata($build);
 | 
			
		||||
 | 
			
		||||
    return $build;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   *
 | 
			
		||||
   * The DisplayPluginBase preview method assumes we will be returning a render
 | 
			
		||||
   * array. The data plugin will already return the serialized string.
 | 
			
		||||
   */
 | 
			
		||||
  public function preview() {
 | 
			
		||||
    return $this->view->render();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns an array of format options.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string[]
 | 
			
		||||
   *   An array of format options. Both key and value are the same.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getFormatOptions() {
 | 
			
		||||
    $formats = array_keys($this->formatProviders);
 | 
			
		||||
    return array_combine($formats, $formats);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										157
									
								
								web/core/modules/rest/src/Plugin/views/row/DataEntityRow.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								web/core/modules/rest/src/Plugin/views/row/DataEntityRow.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,157 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\rest\Plugin\views\row;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Entity\EntityRepositoryInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
 | 
			
		||||
use Drupal\Core\Language\LanguageManagerInterface;
 | 
			
		||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
 | 
			
		||||
use Drupal\views\Attribute\ViewsRow;
 | 
			
		||||
use Drupal\views\Entity\Render\EntityTranslationRenderTrait;
 | 
			
		||||
use Drupal\views\Plugin\views\row\RowPluginBase;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Plugin which displays entities as raw data.
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup views_row_plugins
 | 
			
		||||
 */
 | 
			
		||||
#[ViewsRow(
 | 
			
		||||
  id: "data_entity",
 | 
			
		||||
  title: new TranslatableMarkup("Entity"),
 | 
			
		||||
  help: new TranslatableMarkup("Use entities as row data."),
 | 
			
		||||
  display_types: ["data"]
 | 
			
		||||
)]
 | 
			
		||||
class DataEntityRow extends RowPluginBase {
 | 
			
		||||
 | 
			
		||||
  use EntityTranslationRenderTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected $usesOptions = FALSE;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Contains the entity type of this row plugin instance.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityTypeInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityType;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity type manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityTypeManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity repository service.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityRepositoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityRepository;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity display repository.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityDisplayRepository;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The language manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Language\LanguageManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $languageManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new DataEntityRow object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $configuration
 | 
			
		||||
   *   A configuration array containing information about the plugin instance.
 | 
			
		||||
   * @param string $plugin_id
 | 
			
		||||
   *   The plugin ID for the plugin instance.
 | 
			
		||||
   * @param array $plugin_definition
 | 
			
		||||
   *   The plugin implementation definition.
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
 | 
			
		||||
   *   The entity type manager.
 | 
			
		||||
   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
 | 
			
		||||
   *   The language manager.
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
 | 
			
		||||
   *   The entity repository.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(array $configuration, $plugin_id, array $plugin_definition, EntityTypeManagerInterface $entity_type_manager, LanguageManagerInterface $language_manager, EntityRepositoryInterface $entity_repository) {
 | 
			
		||||
    parent::__construct($configuration, $plugin_id, $plugin_definition);
 | 
			
		||||
 | 
			
		||||
    $this->entityTypeManager = $entity_type_manager;
 | 
			
		||||
    $this->languageManager = $language_manager;
 | 
			
		||||
    $this->entityRepository = $entity_repository;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $configuration,
 | 
			
		||||
      $plugin_id,
 | 
			
		||||
      $plugin_definition,
 | 
			
		||||
      $container->get('entity_type.manager'),
 | 
			
		||||
      $container->get('language_manager'),
 | 
			
		||||
      $container->get('entity.repository')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function render($row) {
 | 
			
		||||
    return $this->getEntityTranslationByRelationship($row->_entity, $row);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getEntityTypeId() {
 | 
			
		||||
    return $this->view->getBaseEntityType()->id();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function getEntityTypeManager() {
 | 
			
		||||
    return $this->entityTypeManager;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function getEntityRepository() {
 | 
			
		||||
    return $this->entityRepository;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function getLanguageManager() {
 | 
			
		||||
    return $this->languageManager;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function getView() {
 | 
			
		||||
    return $this->view;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function query() {
 | 
			
		||||
    parent::query();
 | 
			
		||||
    $this->getEntityTranslationRenderer()->query($this->view->getQuery());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										197
									
								
								web/core/modules/rest/src/Plugin/views/row/DataFieldRow.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								web/core/modules/rest/src/Plugin/views/row/DataFieldRow.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,197 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\rest\Plugin\views\row;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
 | 
			
		||||
use Drupal\views\Attribute\ViewsRow;
 | 
			
		||||
use Drupal\views\ViewExecutable;
 | 
			
		||||
use Drupal\views\Plugin\views\display\DisplayPluginBase;
 | 
			
		||||
use Drupal\views\Plugin\views\row\RowPluginBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Plugin which displays fields as raw data.
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup views_row_plugins
 | 
			
		||||
 */
 | 
			
		||||
#[ViewsRow(
 | 
			
		||||
  id: "data_field",
 | 
			
		||||
  title: new TranslatableMarkup("Fields"),
 | 
			
		||||
  help: new TranslatableMarkup("Use fields as row data."),
 | 
			
		||||
  display_types: ["data"]
 | 
			
		||||
)]
 | 
			
		||||
class DataFieldRow extends RowPluginBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected $usesFields = TRUE;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Stores an array of prepared field aliases from options.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $replacementAliases = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Stores an array of options to determine if the raw field output is used.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $rawOutputOptions = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function init(ViewExecutable $view, DisplayPluginBase $display, ?array &$options = NULL) {
 | 
			
		||||
    parent::init($view, $display, $options);
 | 
			
		||||
 | 
			
		||||
    if (!empty($this->options['field_options'])) {
 | 
			
		||||
      $options = (array) $this->options['field_options'];
 | 
			
		||||
      // Prepare a trimmed version of replacement aliases.
 | 
			
		||||
      $aliases = static::extractFromOptionsArray('alias', $options);
 | 
			
		||||
      $this->replacementAliases = array_filter(array_map('trim', $aliases));
 | 
			
		||||
      // Prepare an array of raw output field options.
 | 
			
		||||
      $this->rawOutputOptions = static::extractFromOptionsArray('raw_output', $options);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function defineOptions() {
 | 
			
		||||
    $options = parent::defineOptions();
 | 
			
		||||
    $options['field_options'] = ['default' => []];
 | 
			
		||||
 | 
			
		||||
    return $options;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildOptionsForm(&$form, FormStateInterface $form_state) {
 | 
			
		||||
    parent::buildOptionsForm($form, $form_state);
 | 
			
		||||
 | 
			
		||||
    $form['field_options'] = [
 | 
			
		||||
      '#type' => 'table',
 | 
			
		||||
      '#header' => [$this->t('Field'), $this->t('Alias'), $this->t('Raw output')],
 | 
			
		||||
      '#empty' => $this->t('You have no fields. Add some to your view.'),
 | 
			
		||||
      '#tree' => TRUE,
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $options = $this->options['field_options'];
 | 
			
		||||
 | 
			
		||||
    if ($fields = $this->view->display_handler->getOption('fields')) {
 | 
			
		||||
      foreach ($fields as $id => $field) {
 | 
			
		||||
        // Don't show the field if it has been excluded.
 | 
			
		||||
        if (!empty($field['exclude'])) {
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
        $form['field_options'][$id]['field'] = [
 | 
			
		||||
          '#markup' => $id,
 | 
			
		||||
        ];
 | 
			
		||||
        $form['field_options'][$id]['alias'] = [
 | 
			
		||||
          '#title' => $this->t('Alias for @id', ['@id' => $id]),
 | 
			
		||||
          '#title_display' => 'invisible',
 | 
			
		||||
          '#type' => 'textfield',
 | 
			
		||||
          '#default_value' => $options[$id]['alias'] ?? '',
 | 
			
		||||
          '#element_validate' => [[$this, 'validateAliasName']],
 | 
			
		||||
        ];
 | 
			
		||||
        $form['field_options'][$id]['raw_output'] = [
 | 
			
		||||
          '#title' => $this->t('Raw output for @id', ['@id' => $id]),
 | 
			
		||||
          '#title_display' => 'invisible',
 | 
			
		||||
          '#type' => 'checkbox',
 | 
			
		||||
          '#default_value' => $options[$id]['raw_output'] ?? '',
 | 
			
		||||
        ];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Form element validation handler.
 | 
			
		||||
   */
 | 
			
		||||
  public function validateAliasName($element, FormStateInterface $form_state) {
 | 
			
		||||
    if (preg_match('@[^A-Za-z0-9_-]+@', $element['#value'])) {
 | 
			
		||||
      $form_state->setError($element, $this->t('The machine-readable name must contain only letters, numbers, dashes and underscores.'));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function validateOptionsForm(&$form, FormStateInterface $form_state) {
 | 
			
		||||
    // Collect an array of aliases to validate.
 | 
			
		||||
    $aliases = static::extractFromOptionsArray('alias', $form_state->getValue(['row_options', 'field_options']));
 | 
			
		||||
 | 
			
		||||
    // If array filter returns empty, no values have been entered. Unique keys
 | 
			
		||||
    // should only be validated if we have some.
 | 
			
		||||
    if (($filtered = array_filter($aliases)) && (array_unique($filtered) !== $filtered)) {
 | 
			
		||||
      $form_state->setErrorByName('aliases', $this->t('All field aliases must be unique'));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function render($row) {
 | 
			
		||||
    $output = [];
 | 
			
		||||
 | 
			
		||||
    foreach ($this->view->field as $id => $field) {
 | 
			
		||||
      // If the raw output option has been set, just get the raw value.
 | 
			
		||||
      if (!empty($this->rawOutputOptions[$id])) {
 | 
			
		||||
        $value = $field->getValue($row);
 | 
			
		||||
      }
 | 
			
		||||
      // Otherwise, get rendered field.
 | 
			
		||||
      else {
 | 
			
		||||
        // Advanced render for token replacement.
 | 
			
		||||
        $markup = $field->advancedRender($row);
 | 
			
		||||
        // Post render to support uncacheable fields.
 | 
			
		||||
        $field->postRender($row, $markup);
 | 
			
		||||
        $value = $field->last_render;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Omit excluded fields from the rendered output.
 | 
			
		||||
      if (empty($field->options['exclude'])) {
 | 
			
		||||
        $output[$this->getFieldKeyAlias($id)] = $value;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $output;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Return an alias for a field ID, as set in the options form.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $id
 | 
			
		||||
   *   The field id to lookup an alias for.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The matches user entered alias, or the original ID if nothing is found.
 | 
			
		||||
   */
 | 
			
		||||
  public function getFieldKeyAlias($id) {
 | 
			
		||||
    if (isset($this->replacementAliases[$id])) {
 | 
			
		||||
      return $this->replacementAliases[$id];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $id;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Extracts a set of option values from a nested options array.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $key
 | 
			
		||||
   *   The key to extract from each array item.
 | 
			
		||||
   * @param array $options
 | 
			
		||||
   *   The options array to return values from.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   A regular one dimensional array of values.
 | 
			
		||||
   */
 | 
			
		||||
  protected static function extractFromOptionsArray($key, $options) {
 | 
			
		||||
    return array_map(function ($item) use ($key) {
 | 
			
		||||
      return $item[$key] ?? NULL;
 | 
			
		||||
    }, $options);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										206
									
								
								web/core/modules/rest/src/Plugin/views/style/Serializer.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								web/core/modules/rest/src/Plugin/views/style/Serializer.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,206 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\rest\Plugin\views\style;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Cache\Cache;
 | 
			
		||||
use Drupal\Core\Cache\CacheableDependencyInterface;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
 | 
			
		||||
use Drupal\views\Attribute\ViewsStyle;
 | 
			
		||||
use Drupal\views\Plugin\views\style\StylePluginBase;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
use Symfony\Component\Serializer\SerializerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The style plugin for serialized output formats.
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup views_style_plugins
 | 
			
		||||
 */
 | 
			
		||||
#[ViewsStyle(
 | 
			
		||||
  id: "serializer",
 | 
			
		||||
  title: new TranslatableMarkup("Serializer"),
 | 
			
		||||
  help: new TranslatableMarkup("Serializes views row data using the Serializer component."),
 | 
			
		||||
  display_types: ["data"],
 | 
			
		||||
)]
 | 
			
		||||
class Serializer extends StylePluginBase implements CacheableDependencyInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected $usesRowPlugin = TRUE;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected $usesGrouping = FALSE;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The serializer which serializes the views result.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Symfony\Component\Serializer\Serializer
 | 
			
		||||
   */
 | 
			
		||||
  protected $serializer;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The available serialization formats.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $formats = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The serialization format providers, keyed by format.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string[]
 | 
			
		||||
   */
 | 
			
		||||
  protected $formatProviders;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $configuration,
 | 
			
		||||
      $plugin_id,
 | 
			
		||||
      $plugin_definition,
 | 
			
		||||
      $container->get('serializer'),
 | 
			
		||||
      $container->getParameter('serializer.formats'),
 | 
			
		||||
      $container->getParameter('serializer.format_providers')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a Plugin object.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(array $configuration, $plugin_id, $plugin_definition, SerializerInterface $serializer, array $serializer_formats, array $serializer_format_providers) {
 | 
			
		||||
    parent::__construct($configuration, $plugin_id, $plugin_definition);
 | 
			
		||||
 | 
			
		||||
    $this->definition = $plugin_definition + $configuration;
 | 
			
		||||
    $this->serializer = $serializer;
 | 
			
		||||
    $this->formats = $serializer_formats;
 | 
			
		||||
    $this->formatProviders = $serializer_format_providers;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function defineOptions() {
 | 
			
		||||
    $options = parent::defineOptions();
 | 
			
		||||
    $options['formats'] = ['default' => []];
 | 
			
		||||
 | 
			
		||||
    return $options;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildOptionsForm(&$form, FormStateInterface $form_state) {
 | 
			
		||||
    parent::buildOptionsForm($form, $form_state);
 | 
			
		||||
 | 
			
		||||
    $form['formats'] = [
 | 
			
		||||
      '#type' => 'checkboxes',
 | 
			
		||||
      '#title' => $this->t('Accepted request formats'),
 | 
			
		||||
      '#description' => $this->t('Request formats that will be allowed in responses. If none are selected all formats will be allowed.'),
 | 
			
		||||
      '#options' => $this->getFormatOptions(),
 | 
			
		||||
      '#default_value' => $this->options['formats'],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function submitOptionsForm(&$form, FormStateInterface $form_state) {
 | 
			
		||||
    parent::submitOptionsForm($form, $form_state);
 | 
			
		||||
 | 
			
		||||
    $formats = $form_state->getValue(['style_options', 'formats']);
 | 
			
		||||
    $form_state->setValue(['style_options', 'formats'], array_filter($formats));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function render() {
 | 
			
		||||
    $rows = [];
 | 
			
		||||
    // If the Data Entity row plugin is used, this will be an array of entities
 | 
			
		||||
    // which will pass through Serializer to one of the registered Normalizers,
 | 
			
		||||
    // which will transform it to arrays/scalars. If the Data field row plugin
 | 
			
		||||
    // is used, $rows will not contain objects and will pass directly to the
 | 
			
		||||
    // Encoder.
 | 
			
		||||
    foreach ($this->view->result as $row_index => $row) {
 | 
			
		||||
      $this->view->row_index = $row_index;
 | 
			
		||||
      $rows[] = $this->view->rowPlugin->render($row);
 | 
			
		||||
    }
 | 
			
		||||
    unset($this->view->row_index);
 | 
			
		||||
 | 
			
		||||
    // Get the content type configured in the display or fallback to the
 | 
			
		||||
    // default.
 | 
			
		||||
    if ((empty($this->view->live_preview))) {
 | 
			
		||||
      $content_type = $this->displayHandler->getContentType();
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $content_type = !empty($this->options['formats']) ? reset($this->options['formats']) : 'json';
 | 
			
		||||
    }
 | 
			
		||||
    return $this->serializer->serialize($rows, $content_type, ['views_style_plugin' => $this]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets a list of all available formats that can be requested.
 | 
			
		||||
   *
 | 
			
		||||
   * This will return the configured formats, or all formats if none have been
 | 
			
		||||
   * selected.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An array of formats.
 | 
			
		||||
   */
 | 
			
		||||
  public function getFormats() {
 | 
			
		||||
    return $this->options['formats'];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getCacheMaxAge() {
 | 
			
		||||
    return Cache::PERMANENT;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getCacheContexts() {
 | 
			
		||||
    return ['request_format'];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getCacheTags() {
 | 
			
		||||
    return [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function calculateDependencies() {
 | 
			
		||||
    $dependencies = parent::calculateDependencies();
 | 
			
		||||
    $formats = $this->getFormats();
 | 
			
		||||
    $providers = array_intersect_key($this->formatProviders, array_flip($formats));
 | 
			
		||||
    // The plugin always uses services from the serialization module.
 | 
			
		||||
    $providers[] = 'serialization';
 | 
			
		||||
 | 
			
		||||
    $dependencies += ['module' => []];
 | 
			
		||||
    $dependencies['module'] = array_merge($dependencies['module'], $providers);
 | 
			
		||||
    return $dependencies;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns an array of format options.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string[]
 | 
			
		||||
   *   An array of format options. Both key and value are the same.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getFormatOptions() {
 | 
			
		||||
    $formats = array_keys($this->formatProviders);
 | 
			
		||||
    return array_combine($formats, $formats);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										291
									
								
								web/core/modules/rest/src/RequestHandler.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										291
									
								
								web/core/modules/rest/src/RequestHandler.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,291 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\rest;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Utility\ArgumentsResolver;
 | 
			
		||||
use Drupal\Core\Cache\CacheableResponseInterface;
 | 
			
		||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityInterface;
 | 
			
		||||
use Drupal\Core\Routing\RouteMatchInterface;
 | 
			
		||||
use Drupal\rest\Plugin\ResourceInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
 | 
			
		||||
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
 | 
			
		||||
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
 | 
			
		||||
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
 | 
			
		||||
use Symfony\Component\Serializer\SerializerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Acts as intermediate request forwarder for resource plugins.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\rest\EventSubscriber\ResourceResponseSubscriber
 | 
			
		||||
 */
 | 
			
		||||
class RequestHandler implements ContainerInjectionInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The serializer.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Symfony\Component\Serializer\SerializerInterface|\Symfony\Component\Serializer\Encoder\DecoderInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $serializer;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates a new RequestHandler instance.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Symfony\Component\Serializer\SerializerInterface|\Symfony\Component\Serializer\Encoder\DecoderInterface $serializer
 | 
			
		||||
   *   The serializer.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(SerializerInterface $serializer) {
 | 
			
		||||
    $this->serializer = $serializer;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get('serializer')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Handles a REST API request.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
 | 
			
		||||
   *   The route match.
 | 
			
		||||
   * @param \Symfony\Component\HttpFoundation\Request $request
 | 
			
		||||
   *   The HTTP request object.
 | 
			
		||||
   * @param \Drupal\rest\RestResourceConfigInterface $_rest_resource_config
 | 
			
		||||
   *   The REST resource config entity.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\rest\ResourceResponseInterface|\Symfony\Component\HttpFoundation\Response
 | 
			
		||||
   *   The REST resource response.
 | 
			
		||||
   */
 | 
			
		||||
  public function handle(RouteMatchInterface $route_match, Request $request, RestResourceConfigInterface $_rest_resource_config) {
 | 
			
		||||
    $resource = $_rest_resource_config->getResourcePlugin();
 | 
			
		||||
    $unserialized = $this->deserialize($route_match, $request, $resource);
 | 
			
		||||
    $response = $this->delegateToRestResourcePlugin($route_match, $request, $unserialized, $resource);
 | 
			
		||||
    return $this->prepareResponse($response, $_rest_resource_config);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Handles a REST API request without deserializing the request body.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
 | 
			
		||||
   *   The route match.
 | 
			
		||||
   * @param \Symfony\Component\HttpFoundation\Request $request
 | 
			
		||||
   *   The HTTP request object.
 | 
			
		||||
   * @param \Drupal\rest\RestResourceConfigInterface $_rest_resource_config
 | 
			
		||||
   *   The REST resource config entity.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Symfony\Component\HttpFoundation\Response|\Drupal\rest\ResourceResponseInterface
 | 
			
		||||
   *   The REST resource response.
 | 
			
		||||
   */
 | 
			
		||||
  public function handleRaw(RouteMatchInterface $route_match, Request $request, RestResourceConfigInterface $_rest_resource_config) {
 | 
			
		||||
    $resource = $_rest_resource_config->getResourcePlugin();
 | 
			
		||||
    $response = $this->delegateToRestResourcePlugin($route_match, $request, NULL, $resource);
 | 
			
		||||
    return $this->prepareResponse($response, $_rest_resource_config);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Prepares the REST resource response.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\rest\ResourceResponseInterface $response
 | 
			
		||||
   *   The REST resource response.
 | 
			
		||||
   * @param \Drupal\rest\RestResourceConfigInterface $resource_config
 | 
			
		||||
   *   The REST resource config entity.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\rest\ResourceResponseInterface
 | 
			
		||||
   *   The prepared REST resource response.
 | 
			
		||||
   */
 | 
			
		||||
  protected function prepareResponse($response, RestResourceConfigInterface $resource_config) {
 | 
			
		||||
    if ($response instanceof CacheableResponseInterface) {
 | 
			
		||||
      $response->addCacheableDependency($resource_config);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $response;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the normalized HTTP request method of the matched route.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
 | 
			
		||||
   *   The route match.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The normalized HTTP request method.
 | 
			
		||||
   */
 | 
			
		||||
  protected static function getNormalizedRequestMethod(RouteMatchInterface $route_match) {
 | 
			
		||||
    // Symfony is built to transparently map HEAD requests to a GET request. In
 | 
			
		||||
    // the case of the REST module's RequestHandler though, we essentially have
 | 
			
		||||
    // our own light-weight routing system on top of the Drupal/symfony routing
 | 
			
		||||
    // system. So, we have to respect the decision that the routing system made:
 | 
			
		||||
    // we look not at the request method, but at the route's method. All REST
 | 
			
		||||
    // routes are guaranteed to have _method set.
 | 
			
		||||
    // Response::prepare() will transform it to a HEAD response at the very last
 | 
			
		||||
    // moment.
 | 
			
		||||
    // @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4
 | 
			
		||||
    // @see \Symfony\Component\Routing\Matcher\UrlMatcher::matchCollection()
 | 
			
		||||
    // @see \Symfony\Component\HttpFoundation\Response::prepare()
 | 
			
		||||
    $method = strtolower($route_match->getRouteObject()->getMethods()[0]);
 | 
			
		||||
    assert(count($route_match->getRouteObject()->getMethods()) === 1);
 | 
			
		||||
    return $method;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Deserializes request body, if any.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
 | 
			
		||||
   *   The route match.
 | 
			
		||||
   * @param \Symfony\Component\HttpFoundation\Request $request
 | 
			
		||||
   *   The HTTP request object.
 | 
			
		||||
   * @param \Drupal\rest\Plugin\ResourceInterface $resource
 | 
			
		||||
   *   The REST resource plugin.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array|null
 | 
			
		||||
   *   An object normalization, ikf there is a valid request body. NULL if there
 | 
			
		||||
   *   is no request body.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
 | 
			
		||||
   *   Thrown if the request body cannot be decoded.
 | 
			
		||||
   * @throws \Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException
 | 
			
		||||
   *   Thrown if the request body cannot be denormalized.
 | 
			
		||||
   */
 | 
			
		||||
  protected function deserialize(RouteMatchInterface $route_match, Request $request, ResourceInterface $resource) {
 | 
			
		||||
    // Deserialize incoming data if available.
 | 
			
		||||
    $received = $request->getContent();
 | 
			
		||||
    $unserialized = NULL;
 | 
			
		||||
    if (!empty($received)) {
 | 
			
		||||
      $method = static::getNormalizedRequestMethod($route_match);
 | 
			
		||||
      $format = $request->getContentTypeFormat();
 | 
			
		||||
 | 
			
		||||
      $definition = $resource->getPluginDefinition();
 | 
			
		||||
 | 
			
		||||
      // First decode the request data. We can then determine if the
 | 
			
		||||
      // serialized data was malformed.
 | 
			
		||||
      try {
 | 
			
		||||
        $unserialized = $this->serializer->decode($received, $format, ['request_method' => $method]);
 | 
			
		||||
      }
 | 
			
		||||
      catch (UnexpectedValueException $e) {
 | 
			
		||||
        // If an exception was thrown at this stage, there was a problem
 | 
			
		||||
        // decoding the data. Throw a 400 http exception.
 | 
			
		||||
        throw new BadRequestHttpException($e->getMessage());
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Then attempt to denormalize if there is a serialization class.
 | 
			
		||||
      if (!empty($definition['serialization_class'])) {
 | 
			
		||||
        try {
 | 
			
		||||
          $unserialized = $this->serializer->denormalize($unserialized, $definition['serialization_class'], $format, ['request_method' => $method]);
 | 
			
		||||
        }
 | 
			
		||||
        // These two serialization exception types mean there was a problem
 | 
			
		||||
        // with the structure of the decoded data and it's not valid.
 | 
			
		||||
        catch (UnexpectedValueException $e) {
 | 
			
		||||
          throw new UnprocessableEntityHttpException($e->getMessage());
 | 
			
		||||
        }
 | 
			
		||||
        catch (InvalidArgumentException $e) {
 | 
			
		||||
          throw new UnprocessableEntityHttpException($e->getMessage());
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $unserialized;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Delegates an incoming request to the appropriate REST resource plugin.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
 | 
			
		||||
   *   The route match.
 | 
			
		||||
   * @param \Symfony\Component\HttpFoundation\Request $request
 | 
			
		||||
   *   The HTTP request object.
 | 
			
		||||
   * @param mixed|null $unserialized
 | 
			
		||||
   *   The unserialized request body, if any.
 | 
			
		||||
   * @param \Drupal\rest\Plugin\ResourceInterface $resource
 | 
			
		||||
   *   The REST resource plugin.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Symfony\Component\HttpFoundation\Response|\Drupal\rest\ResourceResponseInterface
 | 
			
		||||
   *   The REST resource response.
 | 
			
		||||
   */
 | 
			
		||||
  protected function delegateToRestResourcePlugin(RouteMatchInterface $route_match, Request $request, $unserialized, ResourceInterface $resource) {
 | 
			
		||||
    $method = static::getNormalizedRequestMethod($route_match);
 | 
			
		||||
 | 
			
		||||
    // Determine the request parameters that should be passed to the resource
 | 
			
		||||
    // plugin.
 | 
			
		||||
    $argument_resolver = $this->createArgumentResolver($route_match, $unserialized, $request);
 | 
			
		||||
    $arguments = $argument_resolver->getArguments([$resource, $method]);
 | 
			
		||||
 | 
			
		||||
    // Invoke the operation on the resource plugin.
 | 
			
		||||
    return call_user_func_array([$resource, $method], $arguments);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates an argument resolver, containing all REST parameters.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
 | 
			
		||||
   *   The route match.
 | 
			
		||||
   * @param mixed $unserialized
 | 
			
		||||
   *   The unserialized data.
 | 
			
		||||
   * @param \Symfony\Component\HttpFoundation\Request $request
 | 
			
		||||
   *   The request.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Component\Utility\ArgumentsResolver
 | 
			
		||||
   *   An instance of the argument resolver containing information like the
 | 
			
		||||
   *   'entity' we process and the 'unserialized' content from the request body.
 | 
			
		||||
   */
 | 
			
		||||
  protected function createArgumentResolver(RouteMatchInterface $route_match, $unserialized, Request $request) {
 | 
			
		||||
    $route = $route_match->getRouteObject();
 | 
			
		||||
 | 
			
		||||
    // Defaults for the parameters defined on the route object need to be added
 | 
			
		||||
    // to the raw arguments.
 | 
			
		||||
    $raw_route_arguments = $route_match->getRawParameters()->all() + $route->getDefaults();
 | 
			
		||||
 | 
			
		||||
    $route_arguments = $route_match->getParameters()->all();
 | 
			
		||||
    $upcasted_route_arguments = $route_arguments;
 | 
			
		||||
 | 
			
		||||
    // For request methods that have request bodies, ResourceInterface plugin
 | 
			
		||||
    // methods historically receive the unserialized request body as the N+1th
 | 
			
		||||
    // method argument, where N is the number of route parameters specified on
 | 
			
		||||
    // the accompanying route. To be able to use the argument resolver, which is
 | 
			
		||||
    // not based on position but on name and type hint, specify commonly used
 | 
			
		||||
    // names here. Similarly, those methods receive the original stored object
 | 
			
		||||
    // as the first method argument.
 | 
			
		||||
 | 
			
		||||
    $route_arguments_entity = NULL;
 | 
			
		||||
    // Try to find a parameter which is an entity.
 | 
			
		||||
    foreach ($route_arguments as $value) {
 | 
			
		||||
      if ($value instanceof EntityInterface) {
 | 
			
		||||
        $route_arguments_entity = $value;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (in_array($request->getMethod(), ['PATCH', 'POST'], TRUE)) {
 | 
			
		||||
      if (is_object($unserialized)) {
 | 
			
		||||
        $upcasted_route_arguments['entity'] = $unserialized;
 | 
			
		||||
        $upcasted_route_arguments['data'] = $unserialized;
 | 
			
		||||
        $upcasted_route_arguments['unserialized'] = $unserialized;
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $raw_route_arguments['data'] = $unserialized;
 | 
			
		||||
        $raw_route_arguments['unserialized'] = $unserialized;
 | 
			
		||||
      }
 | 
			
		||||
      $upcasted_route_arguments['original_entity'] = $route_arguments_entity;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $upcasted_route_arguments['entity'] = $route_arguments_entity;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Parameters which are not defined on the route object, but still are
 | 
			
		||||
    // essential for access checking are passed as wildcards to the argument
 | 
			
		||||
    // resolver.
 | 
			
		||||
    $wildcard_arguments = [$route, $route_match];
 | 
			
		||||
    $wildcard_arguments[] = $request;
 | 
			
		||||
    if (isset($unserialized)) {
 | 
			
		||||
      $wildcard_arguments[] = $unserialized;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return new ArgumentsResolver($raw_route_arguments, $upcasted_route_arguments, $wildcard_arguments);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										41
									
								
								web/core/modules/rest/src/ResourceResponse.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								web/core/modules/rest/src/ResourceResponse.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,41 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\rest;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Cache\CacheableResponseInterface;
 | 
			
		||||
use Drupal\Core\Cache\CacheableResponseTrait;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Response;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Contains data for serialization before sending the response.
 | 
			
		||||
 *
 | 
			
		||||
 * We do not want to abuse the $content property on the Response class to store
 | 
			
		||||
 * our response data. $content implies that the provided data must either be a
 | 
			
		||||
 * string or an object with a __toString() method, which is not a requirement
 | 
			
		||||
 * for data used here.
 | 
			
		||||
 *
 | 
			
		||||
 * Routes that return this response must specify the '_format' requirement.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\rest\ModifiedResourceResponse
 | 
			
		||||
 */
 | 
			
		||||
class ResourceResponse extends Response implements CacheableResponseInterface, ResourceResponseInterface {
 | 
			
		||||
 | 
			
		||||
  use CacheableResponseTrait;
 | 
			
		||||
  use ResourceResponseTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructor for ResourceResponse objects.
 | 
			
		||||
   *
 | 
			
		||||
   * @param mixed $data
 | 
			
		||||
   *   Response data that should be serialized.
 | 
			
		||||
   * @param int $status
 | 
			
		||||
   *   The response status code.
 | 
			
		||||
   * @param array $headers
 | 
			
		||||
   *   An array of response headers.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct($data = NULL, $status = 200, $headers = []) {
 | 
			
		||||
    $this->responseData = $data;
 | 
			
		||||
    parent::__construct('', $status, $headers);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								web/core/modules/rest/src/ResourceResponseInterface.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								web/core/modules/rest/src/ResourceResponseInterface.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\rest;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a common interface for resource responses.
 | 
			
		||||
 */
 | 
			
		||||
interface ResourceResponseInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns response data that should be serialized.
 | 
			
		||||
   *
 | 
			
		||||
   * @return mixed
 | 
			
		||||
   *   Response data that should be serialized.
 | 
			
		||||
   */
 | 
			
		||||
  public function getResponseData();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								web/core/modules/rest/src/ResourceResponseTrait.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								web/core/modules/rest/src/ResourceResponseTrait.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\rest;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a trait for accessing response data that should be serialized.
 | 
			
		||||
 */
 | 
			
		||||
trait ResourceResponseTrait {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Response data that should be serialized.
 | 
			
		||||
   *
 | 
			
		||||
   * @var mixed
 | 
			
		||||
   */
 | 
			
		||||
  protected $responseData;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns response data that should be serialized.
 | 
			
		||||
   *
 | 
			
		||||
   * @return mixed
 | 
			
		||||
   *   Response data that should be serialized.
 | 
			
		||||
   */
 | 
			
		||||
  public function getResponseData() {
 | 
			
		||||
    return $this->responseData;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										75
									
								
								web/core/modules/rest/src/RestPermissions.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								web/core/modules/rest/src/RestPermissions.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,75 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\rest;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Utility\NestedArray;
 | 
			
		||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
 | 
			
		||||
use Drupal\rest\Plugin\Type\ResourcePluginManager;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides rest module permissions.
 | 
			
		||||
 */
 | 
			
		||||
class RestPermissions implements ContainerInjectionInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The rest resource plugin manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\rest\Plugin\Type\ResourcePluginManager
 | 
			
		||||
   */
 | 
			
		||||
  protected $restPluginManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The REST resource config storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityStorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $resourceConfigStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new RestPermissions instance.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\rest\Plugin\Type\ResourcePluginManager $rest_plugin_manager
 | 
			
		||||
   *   The rest resource plugin manager.
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
 | 
			
		||||
   *   The entity type manager.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(ResourcePluginManager $rest_plugin_manager, EntityTypeManagerInterface $entity_type_manager) {
 | 
			
		||||
    $this->restPluginManager = $rest_plugin_manager;
 | 
			
		||||
    $this->resourceConfigStorage = $entity_type_manager->getStorage('rest_resource_config');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container) {
 | 
			
		||||
    return new static($container->get('plugin.manager.rest'), $container->get('entity_type.manager'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns an array of REST permissions.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An array of permissions keyed by permission name.
 | 
			
		||||
   */
 | 
			
		||||
  public function permissions() {
 | 
			
		||||
    $permissions = [];
 | 
			
		||||
    /** @var \Drupal\rest\RestResourceConfigInterface[] $resource_configs */
 | 
			
		||||
    $resource_configs = $this->resourceConfigStorage->loadMultiple();
 | 
			
		||||
    foreach ($resource_configs as $resource_config) {
 | 
			
		||||
      $plugin = $resource_config->getResourcePlugin();
 | 
			
		||||
 | 
			
		||||
      // Add the rest resource configuration entity as a dependency to the
 | 
			
		||||
      // permissions.
 | 
			
		||||
      $permissions += array_map(function (array $permission_info) use ($resource_config) {
 | 
			
		||||
        $merge_info['dependencies'][$resource_config->getConfigDependencyKey()] = [
 | 
			
		||||
          $resource_config->getConfigDependencyName(),
 | 
			
		||||
        ];
 | 
			
		||||
        return NestedArray::mergeDeep($permission_info, $merge_info);
 | 
			
		||||
      }, $plugin->permissions());
 | 
			
		||||
    }
 | 
			
		||||
    return $permissions;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										61
									
								
								web/core/modules/rest/src/RestResourceConfigInterface.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								web/core/modules/rest/src/RestResourceConfigInterface.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,61 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\rest;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\Entity\ConfigEntityInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a configuration entity to store enabled REST resources.
 | 
			
		||||
 */
 | 
			
		||||
interface RestResourceConfigInterface extends ConfigEntityInterface, EntityWithPluginCollectionInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Granularity value for per-method configuration.
 | 
			
		||||
   */
 | 
			
		||||
  const METHOD_GRANULARITY = 'method';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Granularity value for per-resource configuration.
 | 
			
		||||
   */
 | 
			
		||||
  const RESOURCE_GRANULARITY = 'resource';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Retrieves the REST resource plugin.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\rest\Plugin\ResourceInterface
 | 
			
		||||
   *   The resource plugin
 | 
			
		||||
   */
 | 
			
		||||
  public function getResourcePlugin();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Retrieves a list of supported HTTP methods.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string[]
 | 
			
		||||
   *   A list of supported HTTP methods.
 | 
			
		||||
   */
 | 
			
		||||
  public function getMethods();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Retrieves a list of supported authentication providers.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $method
 | 
			
		||||
   *   The request method e.g GET or POST.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string[]
 | 
			
		||||
   *   A list of supported authentication provider IDs.
 | 
			
		||||
   */
 | 
			
		||||
  public function getAuthenticationProviders($method);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Retrieves a list of supported response formats.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $method
 | 
			
		||||
   *   The request method e.g GET or POST.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string[]
 | 
			
		||||
   *   A list of supported format IDs.
 | 
			
		||||
   */
 | 
			
		||||
  public function getFormats($method);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										146
									
								
								web/core/modules/rest/src/Routing/ResourceRoutes.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								web/core/modules/rest/src/Routing/ResourceRoutes.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,146 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\rest\Routing;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
 | 
			
		||||
use Drupal\Core\Routing\RouteBuildEvent;
 | 
			
		||||
use Drupal\Core\Routing\RoutingEvents;
 | 
			
		||||
use Drupal\rest\Plugin\Type\ResourcePluginManager;
 | 
			
		||||
use Drupal\rest\RestResourceConfigInterface;
 | 
			
		||||
use Psr\Log\LoggerInterface;
 | 
			
		||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 | 
			
		||||
use Symfony\Component\Routing\RouteCollection;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Subscriber for REST-style routes.
 | 
			
		||||
 */
 | 
			
		||||
class ResourceRoutes implements EventSubscriberInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The plugin manager for REST plugins.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\rest\Plugin\Type\ResourcePluginManager
 | 
			
		||||
   */
 | 
			
		||||
  protected $manager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The REST resource config storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityStorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $resourceConfigStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A logger instance.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Psr\Log\LoggerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $logger;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a RouteSubscriber object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\rest\Plugin\Type\ResourcePluginManager $manager
 | 
			
		||||
   *   The resource plugin manager.
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
 | 
			
		||||
   *   The entity type manager.
 | 
			
		||||
   * @param \Psr\Log\LoggerInterface $logger
 | 
			
		||||
   *   A logger instance.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(ResourcePluginManager $manager, EntityTypeManagerInterface $entity_type_manager, LoggerInterface $logger) {
 | 
			
		||||
    $this->manager = $manager;
 | 
			
		||||
    $this->resourceConfigStorage = $entity_type_manager->getStorage('rest_resource_config');
 | 
			
		||||
    $this->logger = $logger;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Alters existing routes for a specific collection.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Routing\RouteBuildEvent $event
 | 
			
		||||
   *   The route build event.
 | 
			
		||||
   */
 | 
			
		||||
  public function onDynamicRouteEvent(RouteBuildEvent $event) {
 | 
			
		||||
    // Iterate over all enabled REST resource config entities.
 | 
			
		||||
    /** @var \Drupal\rest\RestResourceConfigInterface[] $resource_configs */
 | 
			
		||||
    $resource_configs = $this->resourceConfigStorage->loadMultiple();
 | 
			
		||||
    foreach ($resource_configs as $resource_config) {
 | 
			
		||||
      if ($resource_config->status()) {
 | 
			
		||||
        $resource_routes = $this->getRoutesForResourceConfig($resource_config);
 | 
			
		||||
        $event->getRouteCollection()->addCollection($resource_routes);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provides all routes for a given REST resource config.
 | 
			
		||||
   *
 | 
			
		||||
   * This method determines where a resource is reachable, what path
 | 
			
		||||
   * replacements are used, the required HTTP method for the operation etc.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\rest\RestResourceConfigInterface $rest_resource_config
 | 
			
		||||
   *   The rest resource config.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Symfony\Component\Routing\RouteCollection
 | 
			
		||||
   *   The route collection.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getRoutesForResourceConfig(RestResourceConfigInterface $rest_resource_config) {
 | 
			
		||||
    $plugin = $rest_resource_config->getResourcePlugin();
 | 
			
		||||
    $collection = new RouteCollection();
 | 
			
		||||
 | 
			
		||||
    foreach ($plugin->routes() as $name => $route) {
 | 
			
		||||
      /** @var \Symfony\Component\Routing\Route $route */
 | 
			
		||||
      // @todo Are multiple methods possible here?
 | 
			
		||||
      $methods = $route->getMethods();
 | 
			
		||||
      // Only expose routes that have an explicit method and allow >=1 format
 | 
			
		||||
      // for that method.
 | 
			
		||||
      if (($methods && ($method = $methods[0]) && $rest_resource_config->getFormats($method))) {
 | 
			
		||||
        $route->setRequirement('_csrf_request_header_token', 'TRUE');
 | 
			
		||||
 | 
			
		||||
        // Check that authentication providers are defined.
 | 
			
		||||
        if (empty($rest_resource_config->getAuthenticationProviders($method))) {
 | 
			
		||||
          $this->logger->error('At least one authentication provider must be defined for resource @id', ['@id' => $rest_resource_config->id()]);
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Check that formats are defined.
 | 
			
		||||
        if (empty($rest_resource_config->getFormats($method))) {
 | 
			
		||||
          $this->logger->error('At least one format must be defined for resource @id', ['@id' => $rest_resource_config->id()]);
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // The configuration has been validated, so we update the route to:
 | 
			
		||||
        // - set the allowed response body content types/formats for methods
 | 
			
		||||
        //   that may send response bodies (unless hardcoded by the plugin)
 | 
			
		||||
        // - set the allowed request body content types/formats for methods that
 | 
			
		||||
        //   allow request bodies to be sent (unless hardcoded by the plugin)
 | 
			
		||||
        // - set the allowed authentication providers
 | 
			
		||||
        if (in_array($method, ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE'], TRUE) && !$route->hasRequirement('_format')) {
 | 
			
		||||
          $route->addRequirements(['_format' => implode('|', $rest_resource_config->getFormats($method))]);
 | 
			
		||||
        }
 | 
			
		||||
        if (in_array($method, ['POST', 'PATCH', 'PUT'], TRUE) && !$route->hasRequirement('_content_type_format')) {
 | 
			
		||||
          $route->addRequirements(['_content_type_format' => implode('|', $rest_resource_config->getFormats($method))]);
 | 
			
		||||
        }
 | 
			
		||||
        $route->setOption('_auth', $rest_resource_config->getAuthenticationProviders($method));
 | 
			
		||||
        $route->setDefault('_rest_resource_config', $rest_resource_config->id());
 | 
			
		||||
        $parameters = $route->getOption('parameters') ?: [];
 | 
			
		||||
        $route->setOption('parameters', $parameters + [
 | 
			
		||||
          '_rest_resource_config' => [
 | 
			
		||||
            'type' => 'entity:' . $rest_resource_config->getEntityTypeId(),
 | 
			
		||||
          ],
 | 
			
		||||
        ]);
 | 
			
		||||
        $collection->add("rest.$name", $route);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    return $collection;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function getSubscribedEvents(): array {
 | 
			
		||||
    $events[RoutingEvents::DYNAMIC] = 'onDynamicRouteEvent';
 | 
			
		||||
    return $events;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user