Move all files to 2017/
This commit is contained in:
parent
ac7370f67f
commit
2875863330
15717 changed files with 0 additions and 0 deletions
|
@ -0,0 +1,7 @@
|
|||
# Before Drupal 8.2, EntityResource used permissions as well as the entity
|
||||
# access system for access checking. This was confusing, and it only did this
|
||||
# for historical reasons. New Drupal installations opt out from this by default
|
||||
# (hence this is set to false), existing installations opt in to it.
|
||||
# @see rest_update_8203()
|
||||
# @see https://www.drupal.org/node/2664780
|
||||
bc_entity_resource_permissions: false
|
|
@ -0,0 +1,20 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- basic_auth
|
||||
- hal
|
||||
- node
|
||||
id: entity.node
|
||||
plugin_id: 'entity:node'
|
||||
granularity: resource
|
||||
configuration:
|
||||
methods:
|
||||
- GET
|
||||
- POST
|
||||
- PATCH
|
||||
- DELETE
|
||||
formats:
|
||||
- hal_json
|
||||
authentication:
|
||||
- basic_auth
|
101
2017/web/core/modules/rest/config/schema/rest.schema.yml
Normal file
101
2017/web/core/modules/rest/config/schema/rest.schema.yml
Normal file
|
@ -0,0 +1,101 @@
|
|||
# Schema for the configuration files of the REST module.
|
||||
rest.settings:
|
||||
type: config_object
|
||||
label: 'REST settings'
|
||||
mapping:
|
||||
# @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0.
|
||||
# @see https://www.drupal.org/node/2830467
|
||||
link_domain:
|
||||
type: string
|
||||
label: 'Domain of the relation'
|
||||
bc_entity_resource_permissions:
|
||||
type: boolean
|
||||
label: 'Whether the pre Drupal 8.2.x behavior of having permissions for EntityResource is enabled or not.'
|
||||
|
||||
# Method-level granularity of REST resource configuration.
|
||||
rest_resource.method:
|
||||
type: mapping
|
||||
mapping:
|
||||
HEAD:
|
||||
type: rest_request
|
||||
label: 'HEAD method settings'
|
||||
GET:
|
||||
type: rest_request
|
||||
label: 'GET method settings'
|
||||
POST:
|
||||
type: rest_request
|
||||
label: 'POST method settings'
|
||||
PUT:
|
||||
type: rest_request
|
||||
label: 'PUT method settings'
|
||||
DELETE:
|
||||
type: rest_request
|
||||
label: 'DELETE method settings'
|
||||
TRACE:
|
||||
type: rest_request
|
||||
label: 'TRACE method settings'
|
||||
OPTIONS:
|
||||
type: rest_request
|
||||
label: 'OPTIONS method settings'
|
||||
CONNECT:
|
||||
type: rest_request
|
||||
label: 'CONNECT method settings'
|
||||
PATCH:
|
||||
type: rest_request
|
||||
label: 'PATCH method settings'
|
||||
|
||||
# Resource-level granularity of REST resource configuration.
|
||||
rest_resource.resource:
|
||||
type: mapping
|
||||
mapping:
|
||||
methods:
|
||||
type: sequence
|
||||
label: 'Supported methods'
|
||||
sequence:
|
||||
type: string
|
||||
label: 'HTTP method'
|
||||
formats:
|
||||
type: sequence
|
||||
label: 'Supported formats'
|
||||
sequence:
|
||||
type: string
|
||||
label: 'Format'
|
||||
authentication:
|
||||
type: sequence
|
||||
label: 'Supported authentication providers'
|
||||
sequence:
|
||||
type: string
|
||||
label: 'Authentication provider'
|
||||
|
||||
rest_request:
|
||||
type: mapping
|
||||
mapping:
|
||||
supported_formats:
|
||||
type: sequence
|
||||
label: 'Supported format'
|
||||
sequence:
|
||||
type: string
|
||||
label: 'Format'
|
||||
supported_auth:
|
||||
type: sequence
|
||||
label: 'Supported authentication'
|
||||
sequence:
|
||||
type: string
|
||||
label: 'Authentication'
|
||||
|
||||
rest.resource.*:
|
||||
type: config_entity
|
||||
label: 'REST resource config'
|
||||
mapping:
|
||||
id:
|
||||
type: string
|
||||
label: 'REST resource config ID'
|
||||
plugin_id:
|
||||
type: string
|
||||
label: 'REST resource plugin id'
|
||||
granularity:
|
||||
type: string
|
||||
label: 'REST resource configuration granularity'
|
||||
configuration:
|
||||
type: rest_resource.[%parent.granularity]
|
||||
label: 'REST resource configuration'
|
|
@ -0,0 +1,41 @@
|
|||
# Schema for the views plugins of the REST module.
|
||||
|
||||
views.display.rest_export:
|
||||
type: views_display_path
|
||||
label: 'REST display options'
|
||||
mapping:
|
||||
auth:
|
||||
type: sequence
|
||||
label: 'Authentication'
|
||||
sequence:
|
||||
type: string
|
||||
label: 'Authentication Provider'
|
||||
|
||||
views.row.data_field:
|
||||
type: views_row
|
||||
label: 'Field row'
|
||||
mapping:
|
||||
field_options:
|
||||
type: sequence
|
||||
label: 'Options'
|
||||
sequence:
|
||||
type: mapping
|
||||
label: 'Row'
|
||||
mapping:
|
||||
alias:
|
||||
type: string
|
||||
label: 'Alias for ID'
|
||||
raw_output:
|
||||
type: boolean
|
||||
label: 'Raw output for ID'
|
||||
|
||||
views.style.serializer:
|
||||
type: views_style
|
||||
label: 'Serialized output format'
|
||||
mapping:
|
||||
formats:
|
||||
type: sequence
|
||||
label: 'Formats'
|
||||
sequence:
|
||||
type: string
|
||||
label: 'Format'
|
89
2017/web/core/modules/rest/rest.api.php
Normal file
89
2017/web/core/modules/rest/rest.api.php
Normal file
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Describes hooks provided by the RESTful Web Services module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup hooks
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Alter the resource plugin definitions.
|
||||
*
|
||||
* @param array $definitions
|
||||
* The collection of resource definitions.
|
||||
*/
|
||||
function hook_rest_resource_alter(&$definitions) {
|
||||
if (isset($definitions['entity:node'])) {
|
||||
// We want to handle REST requests regarding nodes with our own plugin
|
||||
// class.
|
||||
$definitions['entity:node']['class'] = 'Drupal\mymodule\Plugin\rest\resource\NodeResource';
|
||||
// Serialized nodes should be expanded to my specific node class.
|
||||
$definitions['entity:node']['serialization_class'] = 'Drupal\mymodule\Entity\MyNode';
|
||||
}
|
||||
// We don't want Views to show up in the array of plugins at all.
|
||||
unset($definitions['entity:view']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the REST type URI.
|
||||
*
|
||||
* @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. Use
|
||||
* hook_serialization_type_uri_alter() instead. This exists solely for BC.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2830467
|
||||
*
|
||||
* Modules may wish to alter the type URI generated for a resource based on the
|
||||
* context of the serializer/normalizer operation.
|
||||
*
|
||||
* @param string $uri
|
||||
* The URI to alter.
|
||||
* @param array $context
|
||||
* The context from the serializer/normalizer operation.
|
||||
*
|
||||
* @see \Symfony\Component\Serializer\SerializerInterface::serialize()
|
||||
* @see \Symfony\Component\Serializer\SerializerInterface::deserialize()
|
||||
* @see \Symfony\Component\Serializer\NormalizerInterface::normalize()
|
||||
* @see \Symfony\Component\Serializer\DenormalizerInterface::denormalize()
|
||||
*/
|
||||
function hook_rest_type_uri_alter(&$uri, $context = []) {
|
||||
if ($context['mymodule'] == TRUE) {
|
||||
$base = \Drupal::config('serialization.settings')->get('link_domain');
|
||||
$uri = str_replace($base, 'http://mymodule.domain', $uri);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the REST relation URI.
|
||||
*
|
||||
* @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. Use
|
||||
* hook_serialization_relation_uri_alter() instead. This exists solely for BC.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2830467
|
||||
*
|
||||
* Modules may wish to alter the relation URI generated for a resource based on
|
||||
* the context of the serializer/normalizer operation.
|
||||
*
|
||||
* @param string $uri
|
||||
* The URI to alter.
|
||||
* @param array $context
|
||||
* The context from the serializer/normalizer operation.
|
||||
*
|
||||
* @see \Symfony\Component\Serializer\SerializerInterface::serialize()
|
||||
* @see \Symfony\Component\Serializer\SerializerInterface::deserialize()
|
||||
* @see \Symfony\Component\Serializer\NormalizerInterface::normalize()
|
||||
* @see \Symfony\Component\Serializer\DenormalizerInterface::denormalize()
|
||||
*/
|
||||
function hook_rest_relation_uri_alter(&$uri, $context = []) {
|
||||
if ($context['mymodule'] == TRUE) {
|
||||
$base = \Drupal::config('serialization.settings')->get('link_domain');
|
||||
$uri = str_replace($base, 'http://mymodule.domain', $uri);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup hooks".
|
||||
*/
|
8
2017/web/core/modules/rest/rest.info.yml
Normal file
8
2017/web/core/modules/rest/rest.info.yml
Normal file
|
@ -0,0 +1,8 @@
|
|||
name: 'RESTful Web Services'
|
||||
type: module
|
||||
description: 'Exposes entities and other resources as RESTful web API'
|
||||
package: Web services
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- drupal:serialization
|
122
2017/web/core/modules/rest/rest.install
Normal file
122
2017/web/core/modules/rest/rest.install
Normal file
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Install, update and uninstall functions for the rest module.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityType;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Implements hook_requirements().
|
||||
*/
|
||||
function rest_requirements($phase) {
|
||||
$requirements = [];
|
||||
|
||||
if ($phase == 'runtime' && PHP_SAPI !== 'cli' && version_compare(PHP_VERSION, '5.6.0', '>=') && version_compare(PHP_VERSION, '7', '<') && ini_get('always_populate_raw_post_data') != -1) {
|
||||
$requirements['always_populate_raw_post_data'] = [
|
||||
'title' => t('always_populate_raw_post_data PHP setting'),
|
||||
'value' => t('Not set to -1.'),
|
||||
'severity' => REQUIREMENT_ERROR,
|
||||
'description' => t('The always_populate_raw_post_data PHP setting should be set to -1 in PHP version 5.6. Please check the <a href="https://php.net/manual/en/ini.core.php#ini.always-populate-raw-post-data">PHP manual</a> for information on how to correct this.'),
|
||||
];
|
||||
}
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Install the REST config entity type and fix old settings-based config.
|
||||
*
|
||||
* @see rest_post_update_create_rest_resource_config_entities()
|
||||
*/
|
||||
function rest_update_8201() {
|
||||
\Drupal::entityDefinitionUpdateManager()->installEntityType(new ConfigEntityType([
|
||||
'id' => 'rest_resource_config',
|
||||
'label' => new TranslatableMarkup('REST resource configuration'),
|
||||
'config_prefix' => 'resource',
|
||||
'admin_permission' => 'administer rest resources',
|
||||
'label_callback' => 'getLabelFromPlugin',
|
||||
'entity_keys' => ['id' => 'id'],
|
||||
'config_export' => [
|
||||
'id',
|
||||
'plugin_id',
|
||||
'granularity',
|
||||
'configuration',
|
||||
],
|
||||
]));
|
||||
\Drupal::state()->set('rest_update_8201_resources', \Drupal::config('rest.settings')->get('resources'));
|
||||
\Drupal::configFactory()->getEditable('rest.settings')
|
||||
->clear('resources')
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-save all views with a REST display to add new auth defaults.
|
||||
*/
|
||||
function rest_update_8202() {
|
||||
$config_factory = \Drupal::configFactory();
|
||||
foreach ($config_factory->listAll('views.view.') as $view_config_name) {
|
||||
$save = FALSE;
|
||||
$view = $config_factory->getEditable($view_config_name);
|
||||
$displays = $view->get('display');
|
||||
foreach ($displays as $display_name => &$display) {
|
||||
if ($display['display_plugin'] == 'rest_export') {
|
||||
if (!isset($display['display_options']['auth'])) {
|
||||
$display['display_options']['auth'] = [];
|
||||
$save = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($save) {
|
||||
$view->set('display', $displays);
|
||||
$view->save(TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable BC for EntityResource: continue to use permissions.
|
||||
*/
|
||||
function rest_update_8203() {
|
||||
$config_factory = \Drupal::configFactory();
|
||||
$rest_settings = $config_factory->getEditable('rest.settings');
|
||||
$rest_settings->set('bc_entity_resource_permissions', TRUE)
|
||||
->save(TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the right REST authentication method is used.
|
||||
*
|
||||
* This fixes the bug in https://www.drupal.org/node/2825204.
|
||||
*/
|
||||
function rest_update_8401() {
|
||||
$config_factory = \Drupal::configFactory();
|
||||
$auth_providers = \Drupal::service('authentication_collector')->getSortedProviders();
|
||||
$process_auth = function ($auth_option) use ($auth_providers) {
|
||||
foreach ($auth_providers as $provider_id => $provider_data) {
|
||||
// The provider belongs to the module that declares it as a service.
|
||||
if (strtok($provider_data->_serviceId, '.') === $auth_option) {
|
||||
return $provider_id;
|
||||
}
|
||||
}
|
||||
|
||||
return $auth_option;
|
||||
};
|
||||
|
||||
foreach ($config_factory->listAll('views.view.') as $view_config_name) {
|
||||
$save = FALSE;
|
||||
$view = $config_factory->getEditable($view_config_name);
|
||||
$displays = $view->get('display');
|
||||
foreach ($displays as $display_name => $display) {
|
||||
if ('rest_export' === $display['display_plugin'] && !empty($display['display_options']['auth'])) {
|
||||
$displays[$display_name]['display_options']['auth'] = array_map($process_auth, $display['display_options']['auth']);
|
||||
$save = TRUE;
|
||||
}
|
||||
}
|
||||
if ($save) {
|
||||
$view->set('display', $displays);
|
||||
$view->save(TRUE);
|
||||
}
|
||||
}
|
||||
}
|
57
2017/web/core/modules/rest/rest.module
Normal file
57
2017/web/core/modules/rest/rest.module
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* RESTful web services module.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\views\ViewEntityInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function rest_help($route_name, RouteMatchInterface $route_match) {
|
||||
switch ($route_name) {
|
||||
case 'help.page.rest':
|
||||
$output = '';
|
||||
$output .= '<h3>' . t('About') . '</h3>';
|
||||
$output .= '<p>' . 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, custom 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 enabled 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')) ? \Drupal::url('help.page', ['name' => 'field']) : '#']) . '</p>';
|
||||
$output .= '<h3>' . t('Uses') . '</h3>';
|
||||
$output .= '<dl>';
|
||||
$output .= '<dt>' . t('Installing supporting modules') . '</dt>';
|
||||
$output .= '<dd>' . t('In order to use REST on a web site, you need to install modules that provide serialization and authentication services. You can use the Core module <a href=":hal">HAL</a> for serialization and <a href=":basic_auth">HTTP Basic Authentication</a> for authentication, or install a contributed or custom module.', [':hal' => (\Drupal::moduleHandler()->moduleExists('hal')) ? \Drupal::url('help.page', ['name' => 'hal']) : '#', ':basic_auth' => (\Drupal::moduleHandler()->moduleExists('basic_auth')) ? \Drupal::url('help.page', ['name' => 'basic_auth']) : '#']) . '</dd>';
|
||||
$output .= '<dt>' . t('Enabling REST support for an entity type') . '</dt>';
|
||||
$output .= '<dd>' . 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')) ? \Drupal::url('help.page', ['name' => 'node']) : '#', ':config' => 'https://www.drupal.org/documentation/modules/rest', ':restui' => 'https://www.drupal.org/project/restui']) . '</dd>';
|
||||
$output .= '<dd>' . 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 .= '</dl>';
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_view_presave().
|
||||
*
|
||||
* @see rest_update_8401()
|
||||
*/
|
||||
function rest_view_presave(ViewEntityInterface $view) {
|
||||
// Fix the auth options on import, much like what rest_update_8401() does.
|
||||
$auth_providers = \Drupal::service('authentication_collector')->getSortedProviders();
|
||||
$process_auth = function ($auth_option) use ($auth_providers) {
|
||||
foreach ($auth_providers as $provider_id => $provider_data) {
|
||||
// The provider belongs to the module that declares it as a service.
|
||||
if (strtok($provider_data->_serviceId, '.') === $auth_option) {
|
||||
return $provider_id;
|
||||
}
|
||||
}
|
||||
|
||||
return $auth_option;
|
||||
};
|
||||
|
||||
foreach (array_keys($view->get('display')) as $display_id) {
|
||||
$display = &$view->getDisplay($display_id);
|
||||
if ($display['display_plugin'] === 'rest_export' && !empty($display['display_options']['auth'])) {
|
||||
$display['display_options']['auth'] = array_map($process_auth, $display['display_options']['auth']);
|
||||
}
|
||||
}
|
||||
}
|
5
2017/web/core/modules/rest/rest.permissions.yml
Normal file
5
2017/web/core/modules/rest/rest.permissions.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
permission_callbacks:
|
||||
- Drupal\rest\RestPermissions::permissions
|
||||
|
||||
administer rest resources:
|
||||
title: 'Administer REST resource configuration'
|
63
2017/web/core/modules/rest/rest.post_update.php
Normal file
63
2017/web/core/modules/rest/rest.post_update.php
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Post update functions for Rest.
|
||||
*/
|
||||
|
||||
use Drupal\rest\Entity\RestResourceConfig;
|
||||
use Drupal\rest\RestResourceConfigInterface;
|
||||
|
||||
/**
|
||||
* Create REST resource configuration entities.
|
||||
*
|
||||
* @see rest_update_8201()
|
||||
* @see https://www.drupal.org/node/2308745
|
||||
*/
|
||||
function rest_post_update_create_rest_resource_config_entities() {
|
||||
$resources = \Drupal::state()->get('rest_update_8201_resources', []);
|
||||
foreach ($resources as $key => $resource) {
|
||||
$resource = RestResourceConfig::create([
|
||||
'id' => str_replace(':', '.', $key),
|
||||
'granularity' => RestResourceConfigInterface::METHOD_GRANULARITY,
|
||||
'configuration' => $resource,
|
||||
]);
|
||||
$resource->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplify method-granularity REST resource config to resource-granularity.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2721595
|
||||
*/
|
||||
function rest_post_update_resource_granularity() {
|
||||
/** @var \Drupal\rest\RestResourceConfigInterface[] $resource_config_entities */
|
||||
$resource_config_entities = RestResourceConfig::loadMultiple();
|
||||
|
||||
foreach ($resource_config_entities as $resource_config_entity) {
|
||||
if ($resource_config_entity->get('granularity') === RestResourceConfigInterface::METHOD_GRANULARITY) {
|
||||
$configuration = $resource_config_entity->get('configuration');
|
||||
|
||||
$format_and_auth_configuration = [];
|
||||
foreach (array_keys($configuration) as $method) {
|
||||
$format_and_auth_configuration['format'][$method] = implode(',', $configuration[$method]['supported_formats']);
|
||||
$format_and_auth_configuration['auth'][$method] = implode(',', $configuration[$method]['supported_auth']);
|
||||
}
|
||||
|
||||
// If each method has the same formats and the same authentication
|
||||
// providers configured, convert it to 'granularity: resource', which has
|
||||
// a simpler/less verbose configuration.
|
||||
if (count(array_unique($format_and_auth_configuration['format'])) === 1 && count(array_unique($format_and_auth_configuration['auth'])) === 1) {
|
||||
$first_method = array_keys($configuration)[0];
|
||||
$resource_config_entity->set('configuration', [
|
||||
'methods' => array_keys($configuration),
|
||||
'formats' => $configuration[$first_method]['supported_formats'],
|
||||
'authentication' => $configuration[$first_method]['supported_auth'],
|
||||
]);
|
||||
$resource_config_entity->set('granularity', RestResourceConfigInterface::RESOURCE_GRANULARITY);
|
||||
$resource_config_entity->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
9
2017/web/core/modules/rest/rest.routing.yml
Normal file
9
2017/web/core/modules/rest/rest.routing.yml
Normal file
|
@ -0,0 +1,9 @@
|
|||
# @deprecated This route is deprecated, use the system.csrftoken route from the
|
||||
# system module instead.
|
||||
# @todo Remove this route in Drupal 9.0.0.
|
||||
rest.csrftoken:
|
||||
path: '/rest/session/token'
|
||||
defaults:
|
||||
_controller: '\Drupal\system\Controller\CsrfTokenController::csrfToken'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
50
2017/web/core/modules/rest/rest.services.yml
Normal file
50
2017/web/core/modules/rest/rest.services.yml
Normal file
|
@ -0,0 +1,50 @@
|
|||
services:
|
||||
plugin.manager.rest:
|
||||
class: Drupal\rest\Plugin\Type\ResourcePluginManager
|
||||
arguments: ['@container.namespaces', '@cache.discovery', '@module_handler']
|
||||
cache.rest:
|
||||
class: Drupal\Core\Cache\CacheBackendInterface
|
||||
tags:
|
||||
- { name: cache.bin }
|
||||
factory: cache_factory:get
|
||||
arguments: [rest]
|
||||
# @todo Remove this service in Drupal 9.0.0.
|
||||
access_check.rest.csrf:
|
||||
alias: access_check.header.csrf
|
||||
rest.resource_routes:
|
||||
class: Drupal\rest\Routing\ResourceRoutes
|
||||
arguments: ['@plugin.manager.rest', '@entity_type.manager', '@logger.channel.rest']
|
||||
tags:
|
||||
- { name: 'event_subscriber' }
|
||||
logger.channel.rest:
|
||||
parent: logger.channel_base
|
||||
arguments: ['rest']
|
||||
|
||||
# Event subscribers.
|
||||
rest.resource_response.subscriber:
|
||||
class: Drupal\rest\EventSubscriber\ResourceResponseSubscriber
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
arguments: ['@serializer', '@renderer', '@current_route_match']
|
||||
rest.config_subscriber:
|
||||
class: Drupal\rest\EventSubscriber\RestConfigSubscriber
|
||||
arguments: ['@router.builder']
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
rest.resource.entity.post_route.subscriber:
|
||||
class: \Drupal\rest\EventSubscriber\EntityResourcePostRouteSubscriber
|
||||
arguments: ['@entity_type.manager']
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
|
||||
# @todo Remove in Drupal 9.0.0.
|
||||
rest.path_processor_entity_resource_bc:
|
||||
class: \Drupal\rest\PathProcessor\PathProcessorEntityResourceBC
|
||||
arguments: ['@entity_type.manager']
|
||||
tags:
|
||||
- { name: path_processor_inbound }
|
||||
rest.route_processor_get_bc:
|
||||
class: \Drupal\rest\RouteProcessor\RestResourceGetRouteProcessorBC
|
||||
arguments: ['%serializer.formats%', '@router.route_provider']
|
||||
tags:
|
||||
- { name: route_processor_outbound }
|
62
2017/web/core/modules/rest/src/Annotation/RestResource.php
Normal file
62
2017/web/core/modules/rest/src/Annotation/RestResource.php
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?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.
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*/
|
||||
public $label;
|
||||
|
||||
/**
|
||||
* The serialization class to deserialize serialized data into.
|
||||
*
|
||||
* @see \Symfony\Component\Serializer\SerializerInterface's "type" parameter.
|
||||
*
|
||||
* @var string (optional)
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @see core/core.link_relation_types.yml
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public $uri_paths = [];
|
||||
|
||||
}
|
271
2017/web/core/modules/rest/src/Entity/ConfigDependencies.php
Normal file
271
2017/web/core/modules/rest/src/Entity/ConfigDependencies.php
Normal file
|
@ -0,0 +1,271 @@
|
|||
<?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;
|
||||
}
|
||||
|
||||
}
|
284
2017/web/core/modules/rest/src/Entity/RestResourceConfig.php
Normal file
284
2017/web/core/modules/rest/src/Entity/RestResourceConfig.php
Normal file
|
@ -0,0 +1,284 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Entity;
|
||||
|
||||
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 = @Translation("REST resource configuration"),
|
||||
* label_collection = @Translation("REST resource configurations"),
|
||||
* label_singular = @Translation("REST resource configuration"),
|
||||
* label_plural = @Translation("REST resource configurations"),
|
||||
* label_count = @PluralTranslation(
|
||||
* singular = "@count REST resource configuration",
|
||||
* plural = "@count REST resource configurations",
|
||||
* ),
|
||||
* config_prefix = "resource",
|
||||
* admin_permission = "administer rest resources",
|
||||
* label_callback = "getLabelFromPlugin",
|
||||
* entity_keys = {
|
||||
* "id" = "id"
|
||||
* },
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The label callback for this configuration entity.
|
||||
*
|
||||
* @return string The label.
|
||||
*/
|
||||
protected function getLabelFromPlugin() {
|
||||
$plugin_definition = $this->getResourcePluginManager()
|
||||
->getDefinition(['id' => $this->plugin_id]);
|
||||
return $plugin_definition['label'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the resource plugin manager.
|
||||
*
|
||||
* @return \Drupal\Component\Plugin\PluginManagerInterface
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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\EntityManagerInterface
|
||||
*/
|
||||
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 (substr($plugin_id, 0, 6) !== '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\Entity::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() {
|
||||
// Priority -10, to run after \Drupal\rest\Routing\ResourceRoutes, which has
|
||||
// priority 0.
|
||||
$events[RoutingEvents::DYNAMIC][] = ['onDynamicRouteEvent', -10];
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,228 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\EventSubscriber;
|
||||
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Core\Cache\CacheableResponse;
|
||||
use Drupal\Core\Cache\CacheableResponseInterface;
|
||||
use Drupal\Core\Render\RenderContext;
|
||||
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\FilterResponseEvent;
|
||||
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\FilterResponseEvent $event
|
||||
* The event to process.
|
||||
*/
|
||||
public function onResponse(FilterResponseEvent $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->getContentType();
|
||||
|
||||
// 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, e.g. DELETE routes.
|
||||
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.
|
||||
*
|
||||
* In versions of Drupal prior to 8.5, implicit bubbling of cacheability
|
||||
* metadata was allowed because there was no explicit cacheability metadata
|
||||
* bubbling API. To maintain backwards compatibility, we continue to support
|
||||
* this, but support for this will be dropped in Drupal 9.0.0. This is
|
||||
* especially useful when interacting with APIs that implicitly invoke
|
||||
* rendering (for example: generating URLs): this allows those to "leak", and
|
||||
* we collect their bubbled cacheability metadata automatically in a render
|
||||
* context.
|
||||
*
|
||||
* @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,
|
||||
* for example for the response to a DELETE request.
|
||||
*
|
||||
* @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(),
|
||||
];
|
||||
|
||||
// @deprecated In Drupal 8.5.0, will be removed before Drupal 9.0.0. Use
|
||||
// explicit cacheability metadata bubbling instead. (The wrapping call to
|
||||
// executeInRenderContext() will be removed before Drupal 9.0.0.)
|
||||
$context = new RenderContext();
|
||||
$output = $this->renderer
|
||||
->executeInRenderContext($context, function () use ($serializer, $data, $format, $serialization_context) {
|
||||
return $serializer->serialize($data, $format, $serialization_context);
|
||||
});
|
||||
if ($response instanceof CacheableResponseInterface) {
|
||||
if (!$context->isEmpty()) {
|
||||
@trigger_error('Implicit cacheability metadata bubbling (onto the global render context) in normalizers is deprecated since Drupal 8.5.0 and will be removed in Drupal 9.0.0. Use the "cacheability" serialization context instead, for explicit cacheability metadata bubbling. See https://www.drupal.org/node/2918937', E_USER_DEPRECATED);
|
||||
$response->addCacheableDependency($context->pop());
|
||||
}
|
||||
$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());
|
||||
$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() {
|
||||
// Run before \Drupal\dynamic_page_cache\EventSubscriber\DynamicPageCacheSubscriber
|
||||
// (priority 100), so that Dynamic Page Cache can cache flattened responses.
|
||||
$events[KernelEvents::RESPONSE][] = ['onResponse', 128];
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\EventSubscriber;
|
||||
|
||||
use Drupal\Core\Config\ConfigCrudEvent;
|
||||
use Drupal\Core\Config\ConfigEvents;
|
||||
use Drupal\Core\Routing\RouteBuilderInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* A subscriber triggering a route rebuild when certain configuration changes.
|
||||
*/
|
||||
class RestConfigSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* The router builder.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteBuilderInterface
|
||||
*/
|
||||
protected $routerBuilder;
|
||||
|
||||
/**
|
||||
* Constructs the RestConfigSubscriber.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteBuilderInterface $router_builder
|
||||
* The router builder service.
|
||||
*/
|
||||
public function __construct(RouteBuilderInterface $router_builder) {
|
||||
$this->routerBuilder = $router_builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Informs the router builder a rebuild is needed when necessary.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigCrudEvent $event
|
||||
* The Event to process.
|
||||
*/
|
||||
public function onSave(ConfigCrudEvent $event) {
|
||||
$saved_config = $event->getConfig();
|
||||
// @see \Drupal\rest\Plugin\rest\resource\EntityResource::permissions()
|
||||
if ($saved_config->getName() === 'rest.settings' && $event->isChanged('bc_entity_resource_permissions')) {
|
||||
$this->routerBuilder->setRebuildNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
$events[ConfigEvents::SAVE][] = ['onSave'];
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\LinkManager;
|
||||
|
||||
use Drupal\hal\LinkManager\ConfigurableLinkManagerInterface as MovedConfigurableLinkManagerInterface;
|
||||
|
||||
/**
|
||||
* @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. This has
|
||||
* been moved to the hal module. This exists solely for BC.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2830467
|
||||
*/
|
||||
interface ConfigurableLinkManagerInterface extends MovedConfigurableLinkManagerInterface {}
|
13
2017/web/core/modules/rest/src/LinkManager/LinkManager.php
Normal file
13
2017/web/core/modules/rest/src/LinkManager/LinkManager.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\LinkManager;
|
||||
|
||||
use Drupal\hal\LinkManager\LinkManager as MovedLinkManager;
|
||||
|
||||
/**
|
||||
* @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. This has
|
||||
* been moved to the hal module. This exists solely for BC.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2830467
|
||||
*/
|
||||
class LinkManager extends MovedLinkManager implements LinkManagerInterface {}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\LinkManager;
|
||||
|
||||
use Drupal\hal\LinkManager\LinkManagerBase as MovedLinkManagerBase;
|
||||
|
||||
/**
|
||||
* @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. This has
|
||||
* been moved to the hal module. This exists solely for BC.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2830467
|
||||
*/
|
||||
abstract class LinkManagerBase extends MovedLinkManagerBase {}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\LinkManager;
|
||||
|
||||
use Drupal\hal\LinkManager\LinkManagerInterface as MovedLinkManagerInterface;
|
||||
|
||||
/**
|
||||
* @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. This has
|
||||
* been moved to the hal module. This exists solely for BC.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2830467
|
||||
*/
|
||||
interface LinkManagerInterface extends MovedLinkManagerInterface {}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\LinkManager;
|
||||
|
||||
use Drupal\hal\LinkManager\RelationLinkManager as MovedLinkRelationManager;
|
||||
|
||||
/**
|
||||
* @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. This has
|
||||
* been moved to the hal module. This exists solely for BC.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2830467
|
||||
*/
|
||||
class RelationLinkManager extends MovedLinkRelationManager implements RelationLinkManagerInterface {}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\LinkManager;
|
||||
|
||||
use Drupal\hal\LinkManager\RelationLinkManagerInterface as MovedRelationLinkManagerInterface;
|
||||
|
||||
/**
|
||||
* @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. This has
|
||||
* been moved to the hal module. This exists solely for BC.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2830467
|
||||
*/
|
||||
interface RelationLinkManagerInterface extends MovedRelationLinkManagerInterface {}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\LinkManager;
|
||||
|
||||
use Drupal\hal\LinkManager\TypeLinkManager as MovedTypeLinkManager;
|
||||
|
||||
/**
|
||||
* @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. This has
|
||||
* been moved to the hal module. This exists solely for BC.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2830467
|
||||
*/
|
||||
class TypeLinkManager extends MovedTypeLinkManager implements TypeLinkManagerInterface {}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\LinkManager;
|
||||
|
||||
use Drupal\hal\LinkManager\TypeLinkManagerInterface as MovedTypeLinkManagerInterface;
|
||||
|
||||
/**
|
||||
* @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. This has
|
||||
* been moved to the hal module. This exists solely for BC.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2830467
|
||||
*/
|
||||
interface TypeLinkManagerInterface extends MovedTypeLinkManagerInterface {}
|
34
2017/web/core/modules/rest/src/ModifiedResourceResponse.php
Normal file
34
2017/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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\PathProcessor;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Path processor to maintain BC for entity REST resource URLs from Drupal 8.0.
|
||||
*/
|
||||
class PathProcessorEntityResourceBC implements InboundPathProcessorInterface {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Creates a new PathProcessorEntityResourceBC instance.
|
||||
*
|
||||
* @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 function processInbound($path, Request $request) {
|
||||
if ($request->getMethod() === 'POST' && strpos($path, '/entity/') === 0) {
|
||||
$parts = explode('/', $path);
|
||||
$entity_type_id = array_pop($parts);
|
||||
|
||||
// Until Drupal 8.3, no entity types specified a link template for the
|
||||
// 'create' link relation type. As of Drupal 8.3, all core content entity
|
||||
// types provide this link relation type. This inbound path processor
|
||||
// provides automatic backwards compatibility: it allows both the old
|
||||
// default from \Drupal\rest\Plugin\rest\resource\EntityResource, i.e.
|
||||
// "/entity/{entity_type}" and the link template specified in a particular
|
||||
// entity type. The former is rewritten to the latter
|
||||
// specific one if it exists.
|
||||
$entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
|
||||
if ($entity_type->hasLinkTemplate('create')) {
|
||||
return $entity_type->getLinkTemplate('create');
|
||||
}
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
}
|
101
2017/web/core/modules/rest/src/Plugin/Deriver/EntityDeriver.php
Normal file
101
2017/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\EntityManagerInterface;
|
||||
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 manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* Constructs an EntityDeriver object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager.
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $entity_manager) {
|
||||
$this->entityManager = $entity_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, $base_plugin_id) {
|
||||
return new static(
|
||||
$container->get('entity.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->entityManager->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;
|
||||
}
|
||||
|
||||
}
|
236
2017/web/core/modules/rest/src/Plugin/ResourceBase.php
Normal file
236
2017/web/core/modules/rest/src/Plugin/ResourceBase.php
Normal file
|
@ -0,0 +1,236 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Plugin;
|
||||
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
use Drupal\Core\Routing\BcRoute;
|
||||
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 = isset($definition['uri_paths']['canonical']) ? $definition['uri_paths']['canonical'] : '/' . strtr($this->pluginId, ':', '/') . '/{id}';
|
||||
$create_path = isset($definition['uri_paths']['create']) ? $definition['uri_paths']['create'] : '/' . strtr($this->pluginId, ':', '/');
|
||||
// BC: the REST module originally created the POST URL for a resource by
|
||||
// reading the 'https://www.drupal.org/link-relations/create' URI path from
|
||||
// the plugin annotation. For consistency with entity type definitions, that
|
||||
// then changed to reading the 'create' URI path. For any REST Resource
|
||||
// plugins that were using the old mechanism, we continue to support that.
|
||||
if (!isset($definition['uri_paths']['create']) && isset($definition['uri_paths']['https://www.drupal.org/link-relations/create'])) {
|
||||
$create_path = $definition['uri_paths']['https://www.drupal.org/link-relations/create'];
|
||||
}
|
||||
|
||||
$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);
|
||||
|
||||
// BC: the REST module originally created per-format GET routes, instead
|
||||
// of a single route. To minimize the surface of this BC layer, this uses
|
||||
// route definitions that are as empty as possible, plus an outbound route
|
||||
// processor.
|
||||
// @see \Drupal\rest\RouteProcessor\RestResourceGetRouteProcessorBC
|
||||
if ($method === 'GET' || $method === 'HEAD') {
|
||||
foreach ($this->serializerFormats as $format_name) {
|
||||
$collection->add("$route_name.$method.$format_name", (new BcRoute())->setRequirement('_format', $format_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 $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
2017/web/core/modules/rest/src/Plugin/ResourceInterface.php
Normal file
53
2017/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. Example: array('GET', 'POST', 'PATCH').
|
||||
*/
|
||||
public function availableMethods();
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Plugin\Type;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
|
||||
/**
|
||||
* 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, 'Drupal\rest\Plugin\ResourceInterface', 'Drupal\rest\Annotation\RestResource');
|
||||
|
||||
$this->setCacheBackend($cache_backend, 'rest_plugins');
|
||||
$this->alterInfo('rest_resource');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @deprecated in Drupal 8.2.0.
|
||||
* Use Drupal\rest\Plugin\Type\ResourcePluginManager::createInstance()
|
||||
* instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2874934
|
||||
*/
|
||||
public function getInstance(array $options) {
|
||||
if (isset($options['id'])) {
|
||||
return $this->createInstance($options['id']);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,462 @@
|
|||
<?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\Http\Exception\CacheableAccessDeniedHttpException;
|
||||
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\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 = @Translation("Entity"),
|
||||
* serialization_class = "Drupal\Core\Entity\Entity",
|
||||
* deriver = "Drupal\rest\Plugin\Deriver\EntityDeriver",
|
||||
* 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.
|
||||
*
|
||||
* @return \Drupal\rest\ResourceResponse
|
||||
* The response containing the entity with its accessible fields.
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
|
||||
*/
|
||||
public function get(EntityInterface $entity) {
|
||||
$entity_access = $entity->access('view', NULL, TRUE);
|
||||
if (!$entity_access->isAllowed()) {
|
||||
throw new CacheableAccessDeniedHttpException($entity_access, $entity_access->getReason() ?: $this->generateFallbackAccessDeniedMessage($entity, 'view'));
|
||||
}
|
||||
|
||||
$response = new ResourceResponse($entity, 200);
|
||||
$response->addCacheableDependency($entity);
|
||||
$response->addCacheableDependency($entity_access);
|
||||
|
||||
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->urlInfo('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');
|
||||
}
|
||||
$entity_access = $original_entity->access('update', NULL, TRUE);
|
||||
if (!$entity_access->isAllowed()) {
|
||||
throw new AccessDeniedHttpException($entity_access->getReason() ?: $this->generateFallbackAccessDeniedMessage($entity, 'update'));
|
||||
}
|
||||
|
||||
// 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) {
|
||||
$entity_access = $entity->access('delete', NULL, TRUE);
|
||||
if (!$entity_access->isAllowed()) {
|
||||
throw new AccessDeniedHttpException($entity_access->getReason() ?: $this->generateFallbackAccessDeniedMessage($entity, 'delete'));
|
||||
}
|
||||
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() {
|
||||
// @see https://www.drupal.org/node/2664780
|
||||
if ($this->configFactory->get('rest.settings')->get('bc_entity_resource_permissions')) {
|
||||
// The default Drupal 8.0.x and 8.1.x behavior.
|
||||
return parent::permissions();
|
||||
}
|
||||
else {
|
||||
// The default Drupal 8.2.x behavior.
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getBaseRoute($canonical_path, $method) {
|
||||
$route = parent::getBaseRoute($canonical_path, $method);
|
||||
$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 $key => $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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,522 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Plugin\views\display;
|
||||
|
||||
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\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 = @Translation("REST export"),
|
||||
* help = @Translation("Create a REST export resource."),
|
||||
* uses_route = TRUE,
|
||||
* admin = @Translation("REST export"),
|
||||
* 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 authentication providers' modules, keyed by provider ID.
|
||||
*
|
||||
* Authentication providers like 'cookie' and 'basic_auth' are the array
|
||||
* keys. The array values are the module names, e.g.:
|
||||
* @code
|
||||
* ['cookie' => 'user', 'basic_auth' => 'basic_auth']
|
||||
* @endcode
|
||||
*
|
||||
* @deprecated as of 8.4.x, will be removed in before Drupal 9.0.0, see
|
||||
* https://www.drupal.org/node/2825204.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $authenticationProviders;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
// For BC reasons we keep around authenticationProviders as before.
|
||||
$this->authenticationProviders = $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' => views_ui_truncate($auth, 24),
|
||||
];
|
||||
|
||||
// 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_formats = explode('|', $route->getRequirement('_format'));
|
||||
$view_route_formats = explode('|', $view_route->getRequirement('_format'));
|
||||
return $this->overrideAppliesPathAndMethod($view_path, $view_route, $route)
|
||||
&& (!$route->hasRequirement('_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();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
$dependencies = parent::calculateDependencies();
|
||||
|
||||
$dependencies += ['module' => []];
|
||||
$dependencies['module'] = array_merge($dependencies['module'], array_filter(array_map(function ($provider) {
|
||||
// During the update path the provider options might be wrong. This can
|
||||
// happen when any update function, like block_update_8300() triggers a
|
||||
// view to be saved.
|
||||
return isset($this->authenticationProviderIds[$provider])
|
||||
? $this->authenticationProviderIds[$provider]
|
||||
: NULL;
|
||||
}, $this->getOption('auth'))));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Plugin\views\row;
|
||||
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
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 = @Translation("Entity"),
|
||||
* help = @Translation("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 manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
public $entityManager;
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$this->entityManager = $entity_manager;
|
||||
$this->languageManager = $language_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.manager'), $container->get('language_manager'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render($row) {
|
||||
return $this->getEntityTranslation($row->_entity, $row);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getEntityTypeId() {
|
||||
return $this->view->getBaseEntityType()->id();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEntityManager() {
|
||||
return $this->entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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());
|
||||
}
|
||||
|
||||
}
|
192
2017/web/core/modules/rest/src/Plugin/views/row/DataFieldRow.php
Normal file
192
2017/web/core/modules/rest/src/Plugin/views/row/DataFieldRow.php
Normal file
|
@ -0,0 +1,192 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Plugin\views\row;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
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 = @Translation("Fields"),
|
||||
* help = @Translation("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' => isset($options[$id]['alias']) ? $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' => isset($options[$id]['raw_output']) ? $options[$id]['raw_output'] : '',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Form element validation handler for \Drupal\rest\Plugin\views\row\DataFieldRow::buildOptionsForm().
|
||||
*/
|
||||
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, pass this through the field advancedRender() method.
|
||||
else {
|
||||
$value = $field->advancedRender($row);
|
||||
}
|
||||
|
||||
// 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 isset($item[$key]) ? $item[$key] : NULL;
|
||||
}, $options);
|
||||
}
|
||||
|
||||
}
|
205
2017/web/core/modules/rest/src/Plugin/views/style/Serializer.php
Normal file
205
2017/web/core/modules/rest/src/Plugin/views/style/Serializer.php
Normal file
|
@ -0,0 +1,205 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Plugin\views\style;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
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 = @Translation("Serializer"),
|
||||
* help = @Translation("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);
|
||||
}
|
||||
|
||||
}
|
341
2017/web/core/modules/rest/src/RequestHandler.php
Normal file
341
2017/web/core/modules/rest/src/RequestHandler.php
Normal file
|
@ -0,0 +1,341 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest;
|
||||
|
||||
use Drupal\Component\Utility\ArgumentsResolver;
|
||||
use Drupal\Core\Cache\CacheableResponseInterface;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
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 config factory.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* The serializer.
|
||||
*
|
||||
* @var \Symfony\Component\Serializer\SerializerInterface|\Symfony\Component\Serializer\Encoder\DecoderInterface
|
||||
*/
|
||||
protected $serializer;
|
||||
|
||||
/**
|
||||
* Creates a new RequestHandler instance.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The config factory.
|
||||
* @param \Symfony\Component\Serializer\SerializerInterface|\Symfony\Component\Serializer\Encoder\DecoderInterface $serializer
|
||||
* The serializer.
|
||||
*/
|
||||
public function __construct(ConfigFactoryInterface $config_factory, SerializerInterface $serializer) {
|
||||
$this->configFactory = $config_factory;
|
||||
$this->serializer = $serializer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('config.factory'),
|
||||
$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);
|
||||
// Add global rest settings config's cache tag, for BC flags.
|
||||
// @see \Drupal\rest\Plugin\rest\resource\EntityResource::permissions()
|
||||
// @see \Drupal\rest\EventSubscriber\RestConfigSubscriber
|
||||
// @todo Remove in https://www.drupal.org/node/2893804
|
||||
$response->addCacheableDependency($this->configFactory->get('rest.settings'));
|
||||
}
|
||||
|
||||
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->getContentType();
|
||||
|
||||
$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);
|
||||
try {
|
||||
$arguments = $argument_resolver->getArguments([$resource, $method]);
|
||||
}
|
||||
catch (\RuntimeException $exception) {
|
||||
@trigger_error('Passing in arguments the legacy way is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Provide the right parameter names in the method, similar to controllers. See https://www.drupal.org/node/2894819', E_USER_DEPRECATED);
|
||||
$arguments = $this->getLegacyParameters($route_match, $unserialized, $request);
|
||||
}
|
||||
|
||||
// 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 typehint, 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)) {
|
||||
$upcasted_route_arguments['entity'] = $unserialized;
|
||||
$upcasted_route_arguments['data'] = $unserialized;
|
||||
$upcasted_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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the parameter usable without an argument resolver.
|
||||
*
|
||||
* This creates an list of parameters in a statically defined order.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The route match
|
||||
* @param mixed $unserialized
|
||||
* The unserialized data.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request.
|
||||
*
|
||||
* @deprecated in Drupal 8.4.0, will be removed before Drupal 9.0.0. Use the
|
||||
* argument resolver method instead, see ::createArgumentResolver().
|
||||
*
|
||||
* @see https://www.drupal.org/node/2894819
|
||||
*
|
||||
* @return array
|
||||
* An array of parameters.
|
||||
*/
|
||||
protected function getLegacyParameters(RouteMatchInterface $route_match, $unserialized, Request $request) {
|
||||
$route_parameters = $route_match->getParameters();
|
||||
$parameters = [];
|
||||
// Filter out all internal parameters starting with "_".
|
||||
foreach ($route_parameters as $key => $parameter) {
|
||||
if ($key{0} !== '_') {
|
||||
$parameters[] = $parameter;
|
||||
}
|
||||
}
|
||||
|
||||
return array_merge($parameters, [$unserialized, $request]);
|
||||
}
|
||||
|
||||
}
|
39
2017/web/core/modules/rest/src/ResourceResponse.php
Normal file
39
2017/web/core/modules/rest/src/ResourceResponse.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?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.
|
||||
*
|
||||
* @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
2017/web/core/modules/rest/src/ResourceResponseInterface.php
Normal file
18
2017/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();
|
||||
|
||||
}
|
24
2017/web/core/modules/rest/src/ResourceResponseTrait.php
Normal file
24
2017/web/core/modules/rest/src/ResourceResponseTrait.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
65
2017/web/core/modules/rest/src/RestPermissions.php
Normal file
65
2017/web/core/modules/rest/src/RestPermissions.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest;
|
||||
|
||||
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\EntityManagerInterface
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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();
|
||||
$permissions = array_merge($permissions, $plugin->permissions());
|
||||
}
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
51
2017/web/core/modules/rest/src/RestServiceProvider.php
Normal file
51
2017/web/core/modules/rest/src/RestServiceProvider.php
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Core\DependencyInjection\ServiceProviderInterface;
|
||||
use Drupal\rest\LinkManager\LinkManager;
|
||||
use Drupal\rest\LinkManager\RelationLinkManager;
|
||||
use Drupal\rest\LinkManager\TypeLinkManager;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* Provides BC services.
|
||||
*
|
||||
* These services are not added via rest.services.yml because the service
|
||||
* classes extend classes from the HAL module. They also have no use without
|
||||
* that module.
|
||||
*/
|
||||
class RestServiceProvider implements ServiceProviderInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function register(ContainerBuilder $container) {
|
||||
$modules = $container->getParameter(('container.modules'));
|
||||
if (isset($modules['hal'])) {
|
||||
// @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0.
|
||||
// Use hal.link_manager instead.
|
||||
// @see https://www.drupal.org/node/2830467
|
||||
$service_definition = new ChildDefinition(new Reference('hal.link_manager'));
|
||||
$service_definition->setClass(LinkManager::class);
|
||||
$container->setDefinition('rest.link_manager', $service_definition);
|
||||
|
||||
// @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0.
|
||||
// Use hal.link_manager.type instead.
|
||||
// @see https://www.drupal.org/node/2830467
|
||||
$service_definition = new ChildDefinition(new Reference('hal.link_manager.type'));
|
||||
$service_definition->setClass(TypeLinkManager::class);
|
||||
$container->setDefinition('rest.link_manager.type', $service_definition);
|
||||
|
||||
// @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0.
|
||||
// Use hal.link_manager.relation instead.
|
||||
// @see https://www.drupal.org/node/2830467
|
||||
$service_definition = new ChildDefinition(new Reference('hal.link_manager.relation'));
|
||||
$service_definition->setClass(RelationLinkManager::class);
|
||||
$container->setDefinition('rest.link_manager.relation', $service_definition);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\RouteProcessor;
|
||||
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\Core\RouteProcessor\OutboundRouteProcessorInterface;
|
||||
use Drupal\Core\Routing\RouteProviderInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Processes the BC REST routes, to ensure old route names continue to work.
|
||||
*/
|
||||
class RestResourceGetRouteProcessorBC implements OutboundRouteProcessorInterface {
|
||||
|
||||
/**
|
||||
* The available serialization formats.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $serializerFormats = [];
|
||||
|
||||
/**
|
||||
* The route provider.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteProviderInterface
|
||||
*/
|
||||
protected $routeProvider;
|
||||
|
||||
/**
|
||||
* Constructs a RestResourceGetRouteProcessorBC object.
|
||||
*
|
||||
* @param string[] $serializer_formats
|
||||
* The available serialization formats.
|
||||
* @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
|
||||
* The route provider.
|
||||
*/
|
||||
public function __construct(array $serializer_formats, RouteProviderInterface $route_provider) {
|
||||
$this->serializerFormats = $serializer_formats;
|
||||
$this->routeProvider = $route_provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processOutbound($route_name, Route $route, array &$parameters, BubbleableMetadata $bubbleable_metadata = NULL) {
|
||||
$route_name_parts = explode('.', $route_name);
|
||||
// BC: the REST module originally created per-format GET routes, instead
|
||||
// of a single route. To minimize the surface of this BC layer, this uses
|
||||
// route definitions that are as empty as possible, plus an outbound route
|
||||
// processor.
|
||||
// @see \Drupal\rest\Plugin\ResourceBase::routes()
|
||||
if ($route_name_parts[0] === 'rest' && $route_name_parts[count($route_name_parts) - 2] === 'GET' && in_array($route_name_parts[count($route_name_parts) - 1], $this->serializerFormats, TRUE)) {
|
||||
array_pop($route_name_parts);
|
||||
$redirected_route_name = implode('.', $route_name_parts);
|
||||
@trigger_error(sprintf("The '%s' route is deprecated since version 8.5.x and will be removed in 9.0.0. Use the '%s' route instead.", $route_name, $redirected_route_name), E_USER_DEPRECATED);
|
||||
static::overwriteRoute($route, $this->routeProvider->getRouteByName($redirected_route_name));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrites one route's metadata with the other's.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $target_route
|
||||
* The route whose metadata to overwrite.
|
||||
* @param \Symfony\Component\Routing\Route $source_route
|
||||
* The route whose metadata to read from.
|
||||
*
|
||||
* @see \Symfony\Component\Routing\Route
|
||||
*/
|
||||
protected static function overwriteRoute(Route $target_route, Route $source_route) {
|
||||
$target_route->setPath($source_route->getPath());
|
||||
$target_route->setDefaults($source_route->getDefaults());
|
||||
$target_route->setRequirements($source_route->getRequirements());
|
||||
$target_route->setOptions($source_route->getOptions());
|
||||
$target_route->setHost($source_route->getHost());
|
||||
$target_route->setSchemes($source_route->getSchemes());
|
||||
$target_route->setMethods($source_route->getMethods());
|
||||
}
|
||||
|
||||
}
|
157
2017/web/core/modules/rest/src/Routing/ResourceRoutes.php
Normal file
157
2017/web/core/modules/rest/src/Routing/ResourceRoutes.php
Normal file
|
@ -0,0 +1,157 @@
|
|||
<?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\EntityManagerInterface
|
||||
*/
|
||||
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.
|
||||
* @return array
|
||||
*/
|
||||
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
|
||||
// - that exist for BC
|
||||
// @see \Drupal\rest\RouteProcessor\RestResourceGetRouteProcessorBC
|
||||
if (($methods && ($method = $methods[0]) && $supported_formats = $rest_resource_config->getFormats($method)) || $route->hasOption('bc_route')) {
|
||||
$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;
|
||||
}
|
||||
|
||||
// Remove BC routes for unsupported formats.
|
||||
if ($route->getOption('bc_route') === TRUE) {
|
||||
$format_requirement = $route->getRequirement('_format');
|
||||
if ($format_requirement && !in_array($format_requirement, $rest_resource_config->getFormats($method))) {
|
||||
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'], 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() {
|
||||
$events[RoutingEvents::DYNAMIC] = 'onDynamicRouteEvent';
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
626
2017/web/core/modules/rest/src/Tests/RESTTestBase.php
Normal file
626
2017/web/core/modules/rest/src/Tests/RESTTestBase.php
Normal file
|
@ -0,0 +1,626 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Tests;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityType;
|
||||
use Drupal\node\NodeInterface;
|
||||
use Drupal\rest\RestResourceConfigInterface;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use GuzzleHttp\Cookie\FileCookieJar;
|
||||
use GuzzleHttp\Cookie\SetCookie;
|
||||
|
||||
/**
|
||||
* Test helper class that provides a REST client method to send HTTP requests.
|
||||
*
|
||||
* @deprecated in Drupal 8.3.x-dev and will be removed before Drupal 9.0.0. Use \Drupal\Tests\rest\Functional\ResourceTestBase and \Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase instead. Only retained for contributed module tests that may be using this base class.
|
||||
*/
|
||||
abstract class RESTTestBase extends WebTestBase {
|
||||
|
||||
/**
|
||||
* The REST resource config storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $resourceConfigStorage;
|
||||
|
||||
/**
|
||||
* The default serialization format to use for testing REST operations.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $defaultFormat;
|
||||
|
||||
/**
|
||||
* The default MIME type to use for testing REST operations.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $defaultMimeType;
|
||||
|
||||
/**
|
||||
* The entity type to use for testing.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $testEntityType = 'entity_test';
|
||||
|
||||
/**
|
||||
* The default authentication provider to use for testing REST operations.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultAuth;
|
||||
|
||||
|
||||
/**
|
||||
* The raw response body from http request operations.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $responseBody;
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['rest', 'entity_test'];
|
||||
|
||||
/**
|
||||
* The last response.
|
||||
*
|
||||
* @var \Psr\Http\Message\ResponseInterface
|
||||
*/
|
||||
protected $response;
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->defaultFormat = 'hal_json';
|
||||
$this->defaultMimeType = 'application/hal+json';
|
||||
$this->defaultAuth = ['cookie'];
|
||||
$this->resourceConfigStorage = $this->container->get('entity_type.manager')->getStorage('rest_resource_config');
|
||||
// Create a test content type for node testing.
|
||||
if (in_array('node', static::$modules)) {
|
||||
$this->drupalCreateContentType(['name' => 'resttest', 'type' => 'resttest']);
|
||||
}
|
||||
|
||||
$this->cookieFile = $this->publicFilesDirectory . '/cookie.jar';
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates cookies used by guzzle later.
|
||||
*
|
||||
* @return \GuzzleHttp\Cookie\CookieJarInterface
|
||||
* The used CURL options in guzzle.
|
||||
*/
|
||||
protected function cookies() {
|
||||
$cookies = [];
|
||||
|
||||
foreach ($this->cookies as $key => $cookie) {
|
||||
$cookies[$key][] = $cookie['value'];
|
||||
}
|
||||
|
||||
$request = \Drupal::request();
|
||||
$cookies = NestedArray::mergeDeep($cookies, $this->extractCookiesFromRequest($request));
|
||||
|
||||
$cookie_jar = new FileCookieJar($this->cookieFile);
|
||||
foreach ($cookies as $key => $cookie_values) {
|
||||
foreach ($cookie_values as $cookie_value) {
|
||||
// setcookie() sets the value of a cookie to be deleted, when its gonna
|
||||
// be removed.
|
||||
if ($cookie_value !== 'deleted') {
|
||||
$cookie_jar->setCookie(new SetCookie(['Name' => $key, 'Value' => $cookie_value, 'Domain' => $request->getHost()]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $cookie_jar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to issue a HTTP request with simpletest's cURL.
|
||||
*
|
||||
* @param string|\Drupal\Core\Url $url
|
||||
* A Url object or system path.
|
||||
* @param string $method
|
||||
* HTTP method, one of GET, POST, PUT or DELETE.
|
||||
* @param string $body
|
||||
* The body for POST and PUT.
|
||||
* @param string $mime_type
|
||||
* The MIME type of the transmitted content.
|
||||
* @param bool $csrf_token
|
||||
* If NULL, a CSRF token will be retrieved and used. If FALSE, omit the
|
||||
* X-CSRF-Token request header (to simulate developer error). Otherwise, the
|
||||
* passed in value will be used as the value for the X-CSRF-Token request
|
||||
* header (to simulate developer error, by sending an invalid CSRF token).
|
||||
*
|
||||
* @return string
|
||||
* The content returned from the request.
|
||||
*/
|
||||
protected function httpRequest($url, $method, $body = NULL, $mime_type = NULL, $csrf_token = NULL) {
|
||||
if (!isset($mime_type)) {
|
||||
$mime_type = $this->defaultMimeType;
|
||||
}
|
||||
if (!in_array($method, ['GET', 'HEAD', 'OPTIONS', 'TRACE'])) {
|
||||
// GET the CSRF token first for writing requests.
|
||||
$requested_token = $this->drupalGet('session/token');
|
||||
}
|
||||
|
||||
$client = \Drupal::httpClient();
|
||||
$url = $this->buildUrl($url);
|
||||
|
||||
$options = [
|
||||
'http_errors' => FALSE,
|
||||
'cookies' => $this->cookies(),
|
||||
'curl' => [
|
||||
CURLOPT_HEADERFUNCTION => [&$this, 'curlHeaderCallback'],
|
||||
],
|
||||
];
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
$options += [
|
||||
'headers' => [
|
||||
'Accept' => $mime_type,
|
||||
],
|
||||
];
|
||||
$response = $client->get($url, $options);
|
||||
break;
|
||||
|
||||
case 'HEAD':
|
||||
$response = $client->head($url, $options);
|
||||
break;
|
||||
|
||||
case 'POST':
|
||||
$options += [
|
||||
'headers' => $csrf_token !== FALSE ? [
|
||||
'Content-Type' => $mime_type,
|
||||
'X-CSRF-Token' => ($csrf_token === NULL ? $requested_token : $csrf_token),
|
||||
] : [
|
||||
'Content-Type' => $mime_type,
|
||||
],
|
||||
'body' => $body,
|
||||
];
|
||||
$response = $client->post($url, $options);
|
||||
break;
|
||||
|
||||
case 'PUT':
|
||||
$options += [
|
||||
'headers' => $csrf_token !== FALSE ? [
|
||||
'Content-Type' => $mime_type,
|
||||
'X-CSRF-Token' => ($csrf_token === NULL ? $requested_token : $csrf_token),
|
||||
] : [
|
||||
'Content-Type' => $mime_type,
|
||||
],
|
||||
'body' => $body,
|
||||
];
|
||||
$response = $client->put($url, $options);
|
||||
break;
|
||||
|
||||
case 'PATCH':
|
||||
$options += [
|
||||
'headers' => $csrf_token !== FALSE ? [
|
||||
'Content-Type' => $mime_type,
|
||||
'X-CSRF-Token' => ($csrf_token === NULL ? $requested_token : $csrf_token),
|
||||
] : [
|
||||
'Content-Type' => $mime_type,
|
||||
],
|
||||
'body' => $body,
|
||||
];
|
||||
$response = $client->patch($url, $options);
|
||||
break;
|
||||
|
||||
case 'DELETE':
|
||||
$options += [
|
||||
'headers' => $csrf_token !== FALSE ? [
|
||||
'Content-Type' => $mime_type,
|
||||
'X-CSRF-Token' => ($csrf_token === NULL ? $requested_token : $csrf_token),
|
||||
] : [],
|
||||
];
|
||||
$response = $client->delete($url, $options);
|
||||
break;
|
||||
}
|
||||
|
||||
$this->response = $response;
|
||||
$this->responseBody = (string) $response->getBody();
|
||||
$this->setRawContent($this->responseBody);
|
||||
|
||||
// Ensure that any changes to variables in the other thread are picked up.
|
||||
$this->refreshVariables();
|
||||
|
||||
$this->verbose($method . ' request to: ' . $url .
|
||||
'<hr />Code: ' . $this->response->getStatusCode() .
|
||||
(isset($options['headers']) ? '<hr />Request headers: ' . nl2br(print_r($options['headers'], TRUE)) : '') .
|
||||
(isset($options['body']) ? '<hr />Request body: ' . nl2br(print_r($options['body'], TRUE)) : '') .
|
||||
'<hr />Response headers: ' . nl2br(print_r($response->getHeaders(), TRUE)) .
|
||||
'<hr />Response body: ' . $this->responseBody);
|
||||
|
||||
return $this->responseBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function assertResponse($code, $message = '', $group = 'Browser') {
|
||||
if (!isset($this->response)) {
|
||||
return parent::assertResponse($code, $message, $group);
|
||||
}
|
||||
return $this->assertEqual($code, $this->response->getStatusCode(), $message ? $message : "HTTP response expected $code, actual {$this->response->getStatusCode()}", $group);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function drupalGetHeaders($all_requests = FALSE) {
|
||||
if (!isset($this->response)) {
|
||||
return parent::drupalGetHeaders($all_requests);
|
||||
}
|
||||
$lowercased_keys = array_map('strtolower', array_keys($this->response->getHeaders()));
|
||||
return array_map(function (array $header) {
|
||||
return implode(', ', $header);
|
||||
}, array_combine($lowercased_keys, array_values($this->response->getHeaders())));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function drupalGetHeader($name, $all_requests = FALSE) {
|
||||
if (!isset($this->response)) {
|
||||
return parent::drupalGetHeader($name, $all_requests);
|
||||
}
|
||||
if ($header = $this->response->getHeader($name)) {
|
||||
return implode(', ', $header);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates entity objects based on their types.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* The type of the entity that should be created.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface
|
||||
* The new entity object.
|
||||
*/
|
||||
protected function entityCreate($entity_type) {
|
||||
return $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type)
|
||||
->create($this->entityValues($entity_type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an array of suitable property values for an entity type.
|
||||
*
|
||||
* Required properties differ from entity type to entity type, so we keep a
|
||||
* minimum mapping here.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The ID of the type of entity that should be created.
|
||||
*
|
||||
* @return array
|
||||
* An array of values keyed by property name.
|
||||
*/
|
||||
protected function entityValues($entity_type_id) {
|
||||
switch ($entity_type_id) {
|
||||
case 'entity_test':
|
||||
return [
|
||||
'name' => $this->randomMachineName(),
|
||||
'user_id' => 1,
|
||||
'field_test_text' => [
|
||||
0 => [
|
||||
'value' => $this->randomString(),
|
||||
'format' => 'plain_text',
|
||||
],
|
||||
],
|
||||
];
|
||||
case 'config_test':
|
||||
return [
|
||||
'id' => $this->randomMachineName(),
|
||||
'label' => 'Test label',
|
||||
];
|
||||
case 'node':
|
||||
return ['title' => $this->randomString(), 'type' => 'resttest'];
|
||||
case 'node_type':
|
||||
return [
|
||||
'type' => 'article',
|
||||
'name' => $this->randomMachineName(),
|
||||
];
|
||||
case 'user':
|
||||
return ['name' => $this->randomMachineName()];
|
||||
|
||||
case 'comment':
|
||||
return [
|
||||
'subject' => $this->randomMachineName(),
|
||||
'entity_type' => 'node',
|
||||
'comment_type' => 'comment',
|
||||
'comment_body' => $this->randomString(),
|
||||
'entity_id' => 'invalid',
|
||||
'field_name' => 'comment',
|
||||
];
|
||||
case 'taxonomy_vocabulary':
|
||||
return [
|
||||
'vid' => 'tags',
|
||||
'name' => $this->randomMachineName(),
|
||||
];
|
||||
case 'block':
|
||||
// Block placements depend on themes, ensure Bartik is installed.
|
||||
$this->container->get('theme_installer')->install(['bartik']);
|
||||
return [
|
||||
'id' => strtolower($this->randomMachineName(8)),
|
||||
'plugin' => 'system_powered_by_block',
|
||||
'theme' => 'bartik',
|
||||
'region' => 'header',
|
||||
];
|
||||
default:
|
||||
if ($this->isConfigEntity($entity_type_id)) {
|
||||
return $this->configEntityValues($entity_type_id);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the REST service interface for a specific entity type.
|
||||
*
|
||||
* @param string|false $resource_type
|
||||
* The resource type that should get REST API enabled or FALSE to disable all
|
||||
* resource types.
|
||||
* @param string $method
|
||||
* The HTTP method to enable, e.g. GET, POST etc.
|
||||
* @param string|array $format
|
||||
* (Optional) The serialization format, e.g. hal_json, or a list of formats.
|
||||
* @param array $auth
|
||||
* (Optional) The list of valid authentication methods.
|
||||
*/
|
||||
protected function enableService($resource_type, $method = 'GET', $format = NULL, array $auth = []) {
|
||||
if ($resource_type) {
|
||||
// Enable REST API for this entity type.
|
||||
$resource_config_id = str_replace(':', '.', $resource_type);
|
||||
// get entity by id
|
||||
/** @var \Drupal\rest\RestResourceConfigInterface $resource_config */
|
||||
$resource_config = $this->resourceConfigStorage->load($resource_config_id);
|
||||
if (!$resource_config) {
|
||||
$resource_config = $this->resourceConfigStorage->create([
|
||||
'id' => $resource_config_id,
|
||||
'granularity' => RestResourceConfigInterface::METHOD_GRANULARITY,
|
||||
'configuration' => [],
|
||||
]);
|
||||
}
|
||||
$configuration = $resource_config->get('configuration');
|
||||
|
||||
if (is_array($format)) {
|
||||
for ($i = 0; $i < count($format); $i++) {
|
||||
$configuration[$method]['supported_formats'][] = $format[$i];
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ($format == NULL) {
|
||||
$format = $this->defaultFormat;
|
||||
}
|
||||
$configuration[$method]['supported_formats'][] = $format;
|
||||
}
|
||||
|
||||
if (!is_array($auth) || empty($auth)) {
|
||||
$auth = $this->defaultAuth;
|
||||
}
|
||||
foreach ($auth as $auth_provider) {
|
||||
$configuration[$method]['supported_auth'][] = $auth_provider;
|
||||
}
|
||||
|
||||
$resource_config->set('configuration', $configuration);
|
||||
$resource_config->save();
|
||||
}
|
||||
else {
|
||||
foreach ($this->resourceConfigStorage->loadMultiple() as $resource_config) {
|
||||
$resource_config->delete();
|
||||
}
|
||||
}
|
||||
$this->rebuildCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuilds routing caches.
|
||||
*/
|
||||
protected function rebuildCache() {
|
||||
$this->container->get('router.builder')->rebuildIfNeeded();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* This method is overridden to deal with a cURL quirk: the usage of
|
||||
* CURLOPT_CUSTOMREQUEST cannot be unset on the cURL handle, so we need to
|
||||
* override it every time it is omitted.
|
||||
*/
|
||||
protected function curlExec($curl_options, $redirect = FALSE) {
|
||||
unset($this->response);
|
||||
|
||||
if (!isset($curl_options[CURLOPT_CUSTOMREQUEST])) {
|
||||
if (!empty($curl_options[CURLOPT_HTTPGET])) {
|
||||
$curl_options[CURLOPT_CUSTOMREQUEST] = 'GET';
|
||||
}
|
||||
if (!empty($curl_options[CURLOPT_POST])) {
|
||||
$curl_options[CURLOPT_CUSTOMREQUEST] = 'POST';
|
||||
}
|
||||
}
|
||||
return parent::curlExec($curl_options, $redirect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the necessary user permissions for entity operations.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type.
|
||||
* @param string $operation
|
||||
* The operation, one of 'view', 'create', 'update' or 'delete'.
|
||||
*
|
||||
* @return array
|
||||
* The set of user permission strings.
|
||||
*/
|
||||
protected function entityPermissions($entity_type_id, $operation) {
|
||||
switch ($entity_type_id) {
|
||||
case 'entity_test':
|
||||
switch ($operation) {
|
||||
case 'view':
|
||||
return ['view test entity'];
|
||||
case 'create':
|
||||
case 'update':
|
||||
case 'delete':
|
||||
return ['administer entity_test content'];
|
||||
}
|
||||
case 'node':
|
||||
switch ($operation) {
|
||||
case 'view':
|
||||
return ['access content'];
|
||||
case 'create':
|
||||
return ['create resttest content'];
|
||||
case 'update':
|
||||
return ['edit any resttest content'];
|
||||
case 'delete':
|
||||
return ['delete any resttest content'];
|
||||
}
|
||||
|
||||
case 'comment':
|
||||
switch ($operation) {
|
||||
case 'view':
|
||||
return ['access comments'];
|
||||
|
||||
case 'create':
|
||||
return ['post comments', 'skip comment approval'];
|
||||
|
||||
case 'update':
|
||||
return ['edit own comments'];
|
||||
|
||||
case 'delete':
|
||||
return ['administer comments'];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'user':
|
||||
switch ($operation) {
|
||||
case 'view':
|
||||
return ['access user profiles'];
|
||||
|
||||
default:
|
||||
return ['administer users'];
|
||||
}
|
||||
|
||||
default:
|
||||
if ($this->isConfigEntity($entity_type_id)) {
|
||||
$entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
|
||||
if ($admin_permission = $entity_type->getAdminPermission()) {
|
||||
return [$admin_permission];
|
||||
}
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an entity based on the location URL returned in the location header.
|
||||
*
|
||||
* @param string $location_url
|
||||
* The URL returned in the Location header.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\Entity|false
|
||||
* The entity or FALSE if there is no matching entity.
|
||||
*/
|
||||
protected function loadEntityFromLocationHeader($location_url) {
|
||||
$url_parts = explode('/', $location_url);
|
||||
$id = end($url_parts);
|
||||
return $this->container->get('entity_type.manager')
|
||||
->getStorage($this->testEntityType)->load($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove node fields that can only be written by an admin user.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* The node to remove fields where non-administrative users cannot write.
|
||||
*
|
||||
* @return \Drupal\node\NodeInterface
|
||||
* The node with removed fields.
|
||||
*/
|
||||
protected function removeNodeFieldsForNonAdminUsers(NodeInterface $node) {
|
||||
$node->set('status', NULL);
|
||||
$node->set('created', NULL);
|
||||
$node->set('changed', NULL);
|
||||
$node->set('promote', NULL);
|
||||
$node->set('sticky', NULL);
|
||||
$node->set('revision_timestamp', NULL);
|
||||
$node->set('revision_log', NULL);
|
||||
$node->set('uid', NULL);
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if the HTTP request response body is identical to the expected
|
||||
* value.
|
||||
*
|
||||
* @param $expected
|
||||
* The first value to check.
|
||||
* @param $message
|
||||
* (optional) A message to display with the assertion. Do not translate
|
||||
* messages: use \Drupal\Component\Render\FormattableMarkup to embed
|
||||
* variables in the message text, not t(). If left blank, a default message
|
||||
* will be displayed.
|
||||
* @param $group
|
||||
* (optional) The group this message is in, which is displayed in a column
|
||||
* in test output. Use 'Debug' to indicate this is debugging output. Do not
|
||||
* translate this string. Defaults to 'Other'; most tests do not override
|
||||
* this default.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the assertion succeeded, FALSE otherwise.
|
||||
*/
|
||||
protected function assertResponseBody($expected, $message = '', $group = 'REST Response') {
|
||||
return $this->assertIdentical($expected, $this->responseBody, $message ? $message : strtr('Response body @expected (expected) is equal to @response (actual).', ['@expected' => var_export($expected, TRUE), '@response' => var_export($this->responseBody, TRUE)]), $group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an entity type id is for a Config Entity.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type ID to check.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the entity is a Config Entity, FALSE otherwise.
|
||||
*/
|
||||
protected function isConfigEntity($entity_type_id) {
|
||||
return \Drupal::entityTypeManager()->getDefinition($entity_type_id) instanceof ConfigEntityType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an array of suitable property values for a config entity type.
|
||||
*
|
||||
* Config entities have some common keys that need to be created. Required
|
||||
* properties differ among config entity types, so we keep a minimum mapping
|
||||
* here.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The ID of the type of entity that should be created.
|
||||
*
|
||||
* @return array
|
||||
* An array of values keyed by property name.
|
||||
*/
|
||||
protected function configEntityValues($entity_type_id) {
|
||||
$entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
|
||||
$keys = $entity_type->getKeys();
|
||||
$values = [];
|
||||
// Fill out known key values that are shared across entity types.
|
||||
foreach ($keys as $key) {
|
||||
if ($key === 'id' || $key === 'label') {
|
||||
$values[$key] = $this->randomMachineName();
|
||||
}
|
||||
}
|
||||
// Add extra values for particular entity types.
|
||||
switch ($entity_type_id) {
|
||||
case 'block':
|
||||
$values['plugin'] = 'system_powered_by_block';
|
||||
break;
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
}
|
Binary file not shown.
59
2017/web/core/modules/rest/tests/fixtures/update/drupal-8.rest-rest_update_8201.php
vendored
Normal file
59
2017/web/core/modules/rest/tests/fixtures/update/drupal-8.rest-rest_update_8201.php
vendored
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains database additions to drupal-8.bare.standard.php.gz for testing the
|
||||
* upgrade path of rest_update_8201().
|
||||
*/
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
|
||||
$connection = Database::getConnection();
|
||||
|
||||
// Set the schema version.
|
||||
$connection->insert('key_value')
|
||||
->fields([
|
||||
'collection' => 'system.schema',
|
||||
'name' => 'rest',
|
||||
'value' => 'i:8000;',
|
||||
])
|
||||
->execute();
|
||||
|
||||
// Update core.extension.
|
||||
$extensions = $connection->select('config')
|
||||
->fields('config', ['data'])
|
||||
->condition('collection', '')
|
||||
->condition('name', 'core.extension')
|
||||
->execute()
|
||||
->fetchField();
|
||||
$extensions = unserialize($extensions);
|
||||
$extensions['module']['basic_auth'] = 0;
|
||||
$extensions['module']['rest'] = 0;
|
||||
$extensions['module']['serialization'] = 0;
|
||||
$connection->update('config')
|
||||
->fields([
|
||||
'data' => serialize($extensions),
|
||||
])
|
||||
->condition('collection', '')
|
||||
->condition('name', 'core.extension')
|
||||
->execute();
|
||||
|
||||
// Install the rest configuration.
|
||||
$config = [
|
||||
'resources' => [
|
||||
'entity:node' => [
|
||||
'GET' => [
|
||||
'supported_formats' => ['json'],
|
||||
'supported_auth' => ['basic_auth'],
|
||||
],
|
||||
],
|
||||
],
|
||||
'link_domain' => NULL,
|
||||
];
|
||||
$data = $connection->insert('config')
|
||||
->fields([
|
||||
'name' => 'rest.settings',
|
||||
'data' => serialize($config),
|
||||
'collection' => '',
|
||||
])
|
||||
->execute();
|
58
2017/web/core/modules/rest/tests/fixtures/update/drupal-8.rest-rest_update_8203.php
vendored
Normal file
58
2017/web/core/modules/rest/tests/fixtures/update/drupal-8.rest-rest_update_8203.php
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains database additions to drupal-8.bare.standard.php.gz for testing the
|
||||
* upgrade path of rest_update_8203().
|
||||
*/
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
|
||||
$connection = Database::getConnection();
|
||||
|
||||
// Set the schema version.
|
||||
$connection->insert('key_value')
|
||||
->fields([
|
||||
'collection' => 'system.schema',
|
||||
'name' => 'rest',
|
||||
'value' => 'i:8000;',
|
||||
])
|
||||
->execute();
|
||||
|
||||
// Update core.extension.
|
||||
$extensions = $connection->select('config')
|
||||
->fields('config', ['data'])
|
||||
->condition('collection', '')
|
||||
->condition('name', 'core.extension')
|
||||
->execute()
|
||||
->fetchField();
|
||||
$extensions = unserialize($extensions);
|
||||
$extensions['module']['rest'] = 0;
|
||||
$extensions['module']['serialization'] = 0;
|
||||
$connection->update('config')
|
||||
->fields([
|
||||
'data' => serialize($extensions),
|
||||
])
|
||||
->condition('collection', '')
|
||||
->condition('name', 'core.extension')
|
||||
->execute();
|
||||
|
||||
// Install the rest configuration.
|
||||
$config = [
|
||||
'resources' => [
|
||||
'entity:node' => [
|
||||
'GET' => [
|
||||
'supported_formats' => ['json'],
|
||||
'supported_auth' => ['basic_auth'],
|
||||
],
|
||||
],
|
||||
],
|
||||
'link_domain' => NULL,
|
||||
];
|
||||
$data = $connection->insert('config')
|
||||
->fields([
|
||||
'name' => 'rest.settings',
|
||||
'data' => serialize($config),
|
||||
'collection' => '',
|
||||
])
|
||||
->execute();
|
63
2017/web/core/modules/rest/tests/fixtures/update/rest-export-with-authentication-correction.php
vendored
Normal file
63
2017/web/core/modules/rest/tests/fixtures/update/rest-export-with-authentication-correction.php
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Test fixture for \Drupal\Tests\rest\Functional\Update\RestExportAuthCorrectionUpdateTest.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Serialization\Yaml;
|
||||
|
||||
$connection = Database::getConnection();
|
||||
|
||||
// Set the schema version.
|
||||
$connection->insert('key_value')
|
||||
->fields([
|
||||
'collection' => 'system.schema',
|
||||
'name' => 'rest',
|
||||
'value' => 'i:8000;',
|
||||
])
|
||||
->execute();
|
||||
|
||||
// Update core.extension.
|
||||
$extensions = $connection->select('config')
|
||||
->fields('config', ['data'])
|
||||
->condition('collection', '')
|
||||
->condition('name', 'core.extension')
|
||||
->execute()
|
||||
->fetchField();
|
||||
$extensions = unserialize($extensions);
|
||||
$extensions['module']['rest'] = 0;
|
||||
$extensions['module']['serialization'] = 0;
|
||||
$extensions['module']['basic_auth'] = 0;
|
||||
$connection->update('config')
|
||||
->fields([
|
||||
'data' => serialize($extensions),
|
||||
])
|
||||
->condition('collection', '')
|
||||
->condition('name', 'core.extension')
|
||||
->execute();
|
||||
|
||||
$connection->insert('config')
|
||||
->fields([
|
||||
'name' => 'rest.settings',
|
||||
'data' => serialize([
|
||||
'link_domain' => '~',
|
||||
]),
|
||||
'collection' => '',
|
||||
])
|
||||
->execute();
|
||||
|
||||
$connection->insert('config')
|
||||
->fields([
|
||||
'name' => 'views.view.rest_export_with_authorization_correction',
|
||||
])
|
||||
->execute();
|
||||
|
||||
$connection->merge('config')
|
||||
->condition('name', 'views.view.rest_export_with_authorization_correction')
|
||||
->condition('collection', '')
|
||||
->fields([
|
||||
'data' => serialize(Yaml::decode(file_get_contents('core/modules/views/tests/modules/views_test_config/test_views/views.view.rest_export_with_authorization_correction.yml'))),
|
||||
])
|
||||
->execute();
|
65
2017/web/core/modules/rest/tests/fixtures/update/rest-export-with-authentication.php
vendored
Normal file
65
2017/web/core/modules/rest/tests/fixtures/update/rest-export-with-authentication.php
vendored
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Test fixture for \Drupal\rest\Tests\Update\RestExportAuthUpdateTest.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Serialization\Yaml;
|
||||
|
||||
$connection = Database::getConnection();
|
||||
$config = $connection;
|
||||
|
||||
// Set the schema version.
|
||||
$connection->insert('key_value')
|
||||
->fields([
|
||||
'collection' => 'system.schema',
|
||||
'name' => 'rest',
|
||||
'value' => 'i:8000;',
|
||||
])
|
||||
->execute();
|
||||
|
||||
// Update core.extension.
|
||||
$extensions = $connection->select('config')
|
||||
->fields('config', ['data'])
|
||||
->condition('collection', '')
|
||||
->condition('name', 'core.extension')
|
||||
->execute()
|
||||
->fetchField();
|
||||
$extensions = unserialize($extensions);
|
||||
$extensions['module']['rest'] = 0;
|
||||
$extensions['module']['serialization'] = 0;
|
||||
$extensions['module']['basic_auth'] = 0;
|
||||
$connection->update('config')
|
||||
->fields([
|
||||
'data' => serialize($extensions),
|
||||
])
|
||||
->condition('collection', '')
|
||||
->condition('name', 'core.extension')
|
||||
->execute();
|
||||
|
||||
$config = [
|
||||
'link_domain' => '~',
|
||||
];
|
||||
$data = $connection->insert('config')
|
||||
->fields([
|
||||
'name' => 'rest.settings',
|
||||
'data' => serialize($config),
|
||||
'collection' => '',
|
||||
])
|
||||
->execute();
|
||||
|
||||
$connection->insert('config')
|
||||
->fields([
|
||||
'name' => 'views.view.rest_export_with_authorization',
|
||||
])
|
||||
->execute();
|
||||
|
||||
$connection->merge('config')
|
||||
->condition('name', 'views.view.rest_export_with_authorization')
|
||||
->condition('collection', '')
|
||||
->fields([
|
||||
'data' => serialize(Yaml::decode(file_get_contents('core/modules/views/tests/modules/views_test_config/test_views/views.view.rest_export_with_authorization.yml'))),
|
||||
])
|
||||
->execute();
|
32
2017/web/core/modules/rest/tests/fixtures/update/rest.resource.entity.comment_2721595.yml
vendored
Normal file
32
2017/web/core/modules/rest/tests/fixtures/update/rest.resource.entity.comment_2721595.yml
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
id: entity.comment
|
||||
plugin_id: 'entity:comment'
|
||||
granularity: method
|
||||
configuration:
|
||||
GET:
|
||||
supported_formats:
|
||||
- hal_json
|
||||
# This resource has a method-specific format.
|
||||
# @see \Drupal\rest\Tests\Update\ResourceGranularityUpdateTest
|
||||
- xml
|
||||
supported_auth:
|
||||
- basic_auth
|
||||
POST:
|
||||
supported_formats:
|
||||
- hal_json
|
||||
supported_auth:
|
||||
- basic_auth
|
||||
PATCH:
|
||||
supported_formats:
|
||||
- hal_json
|
||||
supported_auth:
|
||||
- basic_auth
|
||||
DELETE:
|
||||
supported_formats:
|
||||
- hal_json
|
||||
supported_auth:
|
||||
- basic_auth
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
- basic_auth
|
||||
- hal
|
29
2017/web/core/modules/rest/tests/fixtures/update/rest.resource.entity.node_2721595.yml
vendored
Normal file
29
2017/web/core/modules/rest/tests/fixtures/update/rest.resource.entity.node_2721595.yml
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
id: entity.node
|
||||
plugin_id: 'entity:node'
|
||||
granularity: method
|
||||
configuration:
|
||||
GET:
|
||||
supported_formats:
|
||||
- hal_json
|
||||
supported_auth:
|
||||
- basic_auth
|
||||
POST:
|
||||
supported_formats:
|
||||
- hal_json
|
||||
supported_auth:
|
||||
- basic_auth
|
||||
PATCH:
|
||||
supported_formats:
|
||||
- hal_json
|
||||
supported_auth:
|
||||
- basic_auth
|
||||
DELETE:
|
||||
supported_formats:
|
||||
- hal_json
|
||||
supported_auth:
|
||||
- basic_auth
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
- basic_auth
|
||||
- hal
|
32
2017/web/core/modules/rest/tests/fixtures/update/rest.resource.entity.user_2721595.yml
vendored
Normal file
32
2017/web/core/modules/rest/tests/fixtures/update/rest.resource.entity.user_2721595.yml
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
id: entity.user
|
||||
plugin_id: 'entity:user'
|
||||
granularity: method
|
||||
configuration:
|
||||
GET:
|
||||
supported_formats:
|
||||
- hal_json
|
||||
supported_auth:
|
||||
- basic_auth
|
||||
# This resource has a method-specific authentication.
|
||||
# @see \Drupal\rest\Tests\Update\ResourceGranularityUpdateTest
|
||||
- oauth
|
||||
POST:
|
||||
supported_formats:
|
||||
- hal_json
|
||||
supported_auth:
|
||||
- basic_auth
|
||||
PATCH:
|
||||
supported_formats:
|
||||
- hal_json
|
||||
supported_auth:
|
||||
- basic_auth
|
||||
DELETE:
|
||||
supported_formats:
|
||||
- hal_json
|
||||
supported_auth:
|
||||
- basic_auth
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
- basic_auth
|
||||
- hal
|
|
@ -0,0 +1,7 @@
|
|||
name: 'Configuration test REST'
|
||||
type: module
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- drupal:config_test
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains hook implementations for testing REST module.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_entity_type_alter().
|
||||
*/
|
||||
function config_test_rest_entity_type_alter(array &$entity_types) {
|
||||
// Undo part of what config_test_entity_type_alter() did: remove this
|
||||
// config_test_no_status entity type, because it uses the same entity class as
|
||||
// the config_test entity type, which makes REST deserialization impossible.
|
||||
unset($entity_types['config_test_no_status']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_access().
|
||||
*/
|
||||
function config_test_rest_config_test_access(EntityInterface $entity, $operation, AccountInterface $account) {
|
||||
// Add permission, so that EntityResourceTestBase's scenarios can test access
|
||||
// being denied. By default, all access is always allowed for the config_test
|
||||
// config entity.
|
||||
return AccessResult::forbiddenIf(!$account->hasPermission('view config_test'))->cachePerPermissions();
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
view config_test:
|
||||
title: 'View ConfigTest entities'
|
|
@ -0,0 +1,6 @@
|
|||
name: 'REST test'
|
||||
type: module
|
||||
description: 'Provides test hooks and resources for REST module.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains hook implementations for testing REST module.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
|
||||
/**
|
||||
* Implements hook_entity_field_access().
|
||||
*
|
||||
* @see \Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase::setUp()
|
||||
*/
|
||||
function rest_test_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) {
|
||||
// @see \Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase::testPost()
|
||||
// @see \Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase::testPatch()
|
||||
if ($field_definition->getName() === 'field_rest_test') {
|
||||
switch ($operation) {
|
||||
case 'view':
|
||||
// Never ever allow this field to be viewed: this lets
|
||||
// EntityResourceTestBase::testGet() test in a "vanilla" way.
|
||||
return AccessResult::forbidden();
|
||||
case 'edit':
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
// @see \Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase::testGet()
|
||||
// @see \Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase::testPatch()
|
||||
if ($field_definition->getName() === 'field_rest_test_multivalue') {
|
||||
switch ($operation) {
|
||||
case 'view':
|
||||
// Never ever allow this field to be viewed: this lets
|
||||
// EntityResourceTestBase::testGet() test in a "vanilla" way.
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
// @see \Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase::testGet()
|
||||
// @see \Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase::testPatch()
|
||||
if ($field_definition->getName() === 'rest_test_validation') {
|
||||
switch ($operation) {
|
||||
case 'view':
|
||||
// Never ever allow this field to be viewed: this lets
|
||||
// EntityResourceTestBase::testGet() test in a "vanilla" way.
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
// No opinion.
|
||||
return AccessResult::neutral();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_base_field_info().
|
||||
*/
|
||||
function rest_test_entity_base_field_info(EntityTypeInterface $entity_type) {
|
||||
$fields = [];
|
||||
$fields['rest_test_validation'] = BaseFieldDefinition::create('string')
|
||||
->setLabel(t('REST test validation field'))
|
||||
->setDescription(t('A text field with some special validations attached used for testing purposes'))
|
||||
->addConstraint('rest_test_validation');
|
||||
|
||||
return $fields;
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
services:
|
||||
rest_test.authentication.test_auth:
|
||||
class: Drupal\rest_test\Authentication\Provider\TestAuth
|
||||
tags:
|
||||
- { name: authentication_provider, provider_id: 'rest_test_auth' }
|
||||
rest_test.authentication.test_auth_global:
|
||||
class: Drupal\rest_test\Authentication\Provider\TestAuthGlobal
|
||||
tags:
|
||||
- { name: authentication_provider, provider_id: 'rest_test_auth_global', global: TRUE }
|
||||
rest_test.page_cache_request_policy.deny_test_auth_requests:
|
||||
class: Drupal\rest_test\PageCache\RequestPolicy\DenyTestAuthRequests
|
||||
public: false
|
||||
tags:
|
||||
- { name: page_cache_request_policy }
|
||||
rest_test.encoder.foobar:
|
||||
class: Drupal\serialization\Encoder\JsonEncoder
|
||||
tags:
|
||||
- { name: encoder, format: foobar }
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest_test\Authentication\Provider;
|
||||
|
||||
use Drupal\Core\Authentication\AuthenticationProviderInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Authentication provider for testing purposes.
|
||||
*/
|
||||
class TestAuth implements AuthenticationProviderInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applies(Request $request) {
|
||||
return $request->headers->has('REST-test-auth');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function authenticate(Request $request) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest_test\Authentication\Provider;
|
||||
|
||||
use Drupal\Core\Authentication\AuthenticationProviderInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Global authentication provider for testing purposes.
|
||||
*/
|
||||
class TestAuthGlobal implements AuthenticationProviderInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applies(Request $request) {
|
||||
return $request->headers->has('REST-test-auth-global');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function authenticate(Request $request) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest_test\PageCache\RequestPolicy;
|
||||
|
||||
use Drupal\Core\PageCache\RequestPolicyInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Cache policy for pages requested with REST Test Auth.
|
||||
*
|
||||
* This policy disallows caching of requests that use the REST Test Auth
|
||||
* authentication provider for security reasons (just like basic_auth).
|
||||
* Otherwise responses for authenticated requests can get into the page cache
|
||||
* and could be delivered to unprivileged users.
|
||||
*
|
||||
* @see \Drupal\rest_test\Authentication\Provider\TestAuth
|
||||
* @see \Drupal\rest_test\Authentication\Provider\TestAuthGlobal
|
||||
* @see \Drupal\basic_auth\PageCache\DisallowBasicAuthRequests
|
||||
*/
|
||||
class DenyTestAuthRequests implements RequestPolicyInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function check(Request $request) {
|
||||
if ($request->headers->has('REST-test-auth') || $request->headers->has('REST-test-auth-global')) {
|
||||
return self::DENY;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest_test\Plugin\Validation\Constraint;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
/**
|
||||
* Adds some validations for a REST test field.
|
||||
*
|
||||
* @Constraint(
|
||||
* id = "rest_test_validation",
|
||||
* label = @Translation("REST test validation", context = "Validation")
|
||||
* )
|
||||
*
|
||||
* @see \Drupal\Core\TypedData\OptionsProviderInterface
|
||||
*/
|
||||
class RestTestConstraint extends Constraint {
|
||||
|
||||
public $message = 'REST test validation failed';
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest_test\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
|
||||
/**
|
||||
* Validator for \Drupal\rest_test\Plugin\Validation\Constraint\RestTestConstraint.
|
||||
*/
|
||||
class RestTestConstraintValidator extends ConstraintValidator {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate($value, Constraint $constraint) {
|
||||
if ($value instanceof FieldItemListInterface) {
|
||||
$value = $value->getValue();
|
||||
if (!empty($value[0]['value']) && $value[0]['value'] === 'ALWAYS_FAIL') {
|
||||
$this->context->addViolation($constraint->message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest_test\Plugin\rest\resource;
|
||||
|
||||
use Drupal\rest\Plugin\ResourceBase;
|
||||
use Drupal\rest\ResourceResponse;
|
||||
|
||||
/**
|
||||
* Class used to test that serialization_class is optional.
|
||||
*
|
||||
* @RestResource(
|
||||
* id = "serialization_test",
|
||||
* label = @Translation("Optional serialization_class"),
|
||||
* serialization_class = "",
|
||||
* uri_paths = {}
|
||||
* )
|
||||
*/
|
||||
class NoSerializationClassTestResource extends ResourceBase {
|
||||
|
||||
/**
|
||||
* Responds to a POST request.
|
||||
*
|
||||
* @param array $data
|
||||
* An array with the payload.
|
||||
*
|
||||
* @return \Drupal\rest\ResourceResponse
|
||||
*/
|
||||
public function post(array $data) {
|
||||
return new ResourceResponse($data);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
name: 'REST test views'
|
||||
type: module
|
||||
description: 'Provides default views for views REST tests.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- drupal:rest
|
||||
- drupal:views
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Test hook implementations for the REST views test module.
|
||||
*/
|
||||
|
||||
use Drupal\views\ViewExecutable;
|
||||
|
||||
/**
|
||||
* Implements hook_views_post_execute().
|
||||
*/
|
||||
function rest_test_views_views_post_execute(ViewExecutable $view) {
|
||||
// Attach a custom header to the test_data_export view.
|
||||
if ($view->id() === 'test_serializer_display_entity') {
|
||||
if ($value = \Drupal::state()->get('rest_test_views_set_header', FALSE)) {
|
||||
$view->getResponse()->headers->set('Custom-Header', $value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,277 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- node.type.article
|
||||
module:
|
||||
- node
|
||||
- rest
|
||||
- user
|
||||
id: test_excluded_field_token_display
|
||||
label: 'Test Excluded Field Token Display'
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: mini
|
||||
options:
|
||||
items_per_page: 10
|
||||
offset: 0
|
||||
id: 0
|
||||
total_pages: null
|
||||
expose:
|
||||
items_per_page: false
|
||||
items_per_page_label: 'Items per page'
|
||||
items_per_page_options: '5, 10, 25, 50'
|
||||
items_per_page_options_all: false
|
||||
items_per_page_options_all_label: '- All -'
|
||||
offset: false
|
||||
offset_label: Offset
|
||||
tags:
|
||||
previous: ‹‹
|
||||
next: ››
|
||||
style:
|
||||
type: serializer
|
||||
row:
|
||||
type: fields
|
||||
options:
|
||||
inline: { }
|
||||
separator: ''
|
||||
hide_empty: false
|
||||
default_field_elements: true
|
||||
fields:
|
||||
title:
|
||||
id: title
|
||||
table: node_field_data
|
||||
field: title
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: true
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: false
|
||||
ellipsis: false
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: string
|
||||
settings:
|
||||
link_to_entity: false
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
entity_type: node
|
||||
entity_field: title
|
||||
plugin_id: field
|
||||
nothing:
|
||||
id: nothing
|
||||
table: views
|
||||
field: nothing
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: true
|
||||
text: '{{ title }}'
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: false
|
||||
plugin_id: custom
|
||||
filters:
|
||||
status:
|
||||
value: '1'
|
||||
table: node_field_data
|
||||
field: status
|
||||
plugin_id: boolean
|
||||
entity_type: node
|
||||
entity_field: status
|
||||
id: status
|
||||
expose:
|
||||
operator: ''
|
||||
group: 1
|
||||
type:
|
||||
id: type
|
||||
table: node_field_data
|
||||
field: type
|
||||
value:
|
||||
article: article
|
||||
entity_type: node
|
||||
entity_field: type
|
||||
plugin_id: bundle
|
||||
sorts:
|
||||
nid:
|
||||
id: nid
|
||||
table: node_field_data
|
||||
field: nid
|
||||
order: DESC
|
||||
entity_type: node
|
||||
entity_field: nid
|
||||
plugin_id: standard
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
exposed: false
|
||||
expose:
|
||||
label: ''
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- request_format
|
||||
- url.query_args
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
rest_export_1:
|
||||
display_plugin: rest_export
|
||||
id: rest_export_1
|
||||
display_title: 'REST export'
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
path: rest/test/excluded-field-token
|
||||
pager:
|
||||
type: some
|
||||
options:
|
||||
items_per_page: 10
|
||||
offset: 0
|
||||
style:
|
||||
type: serializer
|
||||
options:
|
||||
formats:
|
||||
json: json
|
||||
row:
|
||||
type: data_field
|
||||
options:
|
||||
field_options:
|
||||
title:
|
||||
alias: ''
|
||||
raw_output: false
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- request_format
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
|
@ -0,0 +1,55 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- rest
|
||||
- user
|
||||
id: test_serializer_display_entity
|
||||
label: 'Test serialize display entity rows'
|
||||
module: rest
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: entity_test
|
||||
base_field: id
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: null
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
query:
|
||||
type: views_query
|
||||
exposed_form:
|
||||
type: basic
|
||||
style:
|
||||
type: serializer
|
||||
row:
|
||||
type: data_entity
|
||||
sorts:
|
||||
id:
|
||||
id: standard
|
||||
table: entity_test
|
||||
field: id
|
||||
order: DESC
|
||||
plugin_id: date
|
||||
entity_type: entity_test
|
||||
entity_field: id
|
||||
title: 'Test serialize'
|
||||
arguments: { }
|
||||
rest_export_1:
|
||||
display_plugin: rest_export
|
||||
id: rest_export_1
|
||||
display_title: serializer
|
||||
position: null
|
||||
display_options:
|
||||
defaults:
|
||||
access: false
|
||||
path: test/serialize/entity
|
|
@ -0,0 +1,47 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- entity_test
|
||||
- rest
|
||||
id: test_serializer_display_entity_translated
|
||||
label: 'Test serialize translated entity rows'
|
||||
module: rest
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: entity_test_mul_property_data
|
||||
base_field: id
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: null
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
query:
|
||||
type: views_query
|
||||
exposed_form:
|
||||
type: basic
|
||||
style:
|
||||
type: serializer
|
||||
row:
|
||||
type: data_entity
|
||||
title: 'Test serialize translated entity rows'
|
||||
rendering_language: '***LANGUAGE_entity_translation***'
|
||||
arguments: { }
|
||||
rest_export_1:
|
||||
display_plugin: rest_export
|
||||
id: rest_export_1
|
||||
display_title: serializer
|
||||
position: null
|
||||
display_options:
|
||||
defaults:
|
||||
access: false
|
||||
path: test/serialize/translated_entity
|
|
@ -0,0 +1,110 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- rest
|
||||
- user
|
||||
id: test_serializer_display_field
|
||||
label: 'Test serializer display field rows'
|
||||
module: rest
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: views_test_data
|
||||
base_field: id
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: null
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
query:
|
||||
type: views_query
|
||||
exposed_form:
|
||||
type: basic
|
||||
style:
|
||||
type: serializer
|
||||
row:
|
||||
type: data_field
|
||||
fields:
|
||||
name:
|
||||
id: name
|
||||
table: views_test_data
|
||||
field: name
|
||||
label: ''
|
||||
plugin_id: string
|
||||
nothing:
|
||||
id: nothing
|
||||
table: views
|
||||
field: nothing
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: 'Custom text'
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: true
|
||||
text: TEST
|
||||
plugin_id: custom
|
||||
created:
|
||||
id: created
|
||||
table: views_test_data
|
||||
field: created
|
||||
type: timestamp
|
||||
settings:
|
||||
date_format: medium
|
||||
custom_date_format: ''
|
||||
timezone: ''
|
||||
plugin_id: field
|
||||
sorts:
|
||||
created:
|
||||
id: created
|
||||
table: views_test_data
|
||||
field: created
|
||||
order: DESC
|
||||
plugin_id: date
|
||||
title: 'Test serialize'
|
||||
arguments: { }
|
||||
rest_export_1:
|
||||
display_plugin: rest_export
|
||||
id: rest_export_1
|
||||
display_title: serializer
|
||||
position: null
|
||||
display_options:
|
||||
defaults:
|
||||
access: false
|
||||
style: false
|
||||
row: false
|
||||
path: test/serialize/field
|
||||
access:
|
||||
type: none
|
||||
style:
|
||||
type: serializer
|
||||
row:
|
||||
type: data_field
|
||||
rest_export_2:
|
||||
display_plugin: rest_export
|
||||
id: rest_export_2
|
||||
display_title: 'serialize - access denied'
|
||||
position: null
|
||||
display_options:
|
||||
defaults:
|
||||
access: false
|
||||
style: false
|
||||
row: false
|
||||
path: test/serialize/denied
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'administer views'
|
||||
style:
|
||||
type: serializer
|
||||
row:
|
||||
type: data_field
|
|
@ -0,0 +1,172 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- field.storage.node.body
|
||||
module:
|
||||
- field
|
||||
- node
|
||||
- rest
|
||||
- rest_test_views
|
||||
- user
|
||||
id: test_serializer_node_display_field
|
||||
label: 'Test serializer display field rows for entity fields'
|
||||
module: rest_test_views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: null
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'administer views'
|
||||
cache:
|
||||
type: tag
|
||||
query:
|
||||
type: views_query
|
||||
exposed_form:
|
||||
type: basic
|
||||
style:
|
||||
type: serializer
|
||||
row:
|
||||
type: data_field
|
||||
fields:
|
||||
nid:
|
||||
id: nid
|
||||
table: node_field_data
|
||||
field: nid
|
||||
plugin_id: field
|
||||
entity_type: node
|
||||
entity_field: nid
|
||||
title:
|
||||
id: title
|
||||
table: node_field_data
|
||||
field: title
|
||||
label: Title
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
element_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
entity_type: node
|
||||
entity_field: title
|
||||
type: string
|
||||
settings:
|
||||
link_to_entity: true
|
||||
plugin_id: field
|
||||
body:
|
||||
id: body
|
||||
table: node__body
|
||||
field: body
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Body
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: text_default
|
||||
settings: { }
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
plugin_id: field
|
||||
entity_type: node
|
||||
entity_field: body
|
||||
title: 'Test serialize'
|
||||
arguments: { }
|
||||
rest_export_1:
|
||||
display_plugin: rest_export
|
||||
id: rest_export_1
|
||||
display_title: serializer
|
||||
position: null
|
||||
display_options:
|
||||
defaults:
|
||||
access: false
|
||||
style: false
|
||||
row: false
|
||||
path: test/serialize/node-field
|
||||
access:
|
||||
type: none
|
||||
style:
|
||||
type: serializer
|
||||
row:
|
||||
type: data_field
|
||||
|
||||
rest_export_2:
|
||||
display_plugin: rest_export
|
||||
id: rest_export_2
|
||||
display_title: 'REST export 2'
|
||||
position: 2
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
auth:
|
||||
basic_auth: basic_auth
|
||||
path: test/serialize/auth_with_perm
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- request_format
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags:
|
||||
- 'config:field.storage.node.body'
|
|
@ -0,0 +1,172 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- field.storage.node.body
|
||||
module:
|
||||
- field
|
||||
- node
|
||||
- rest
|
||||
- rest_test_views
|
||||
- user
|
||||
id: test_serializer_node_exposed_filter
|
||||
label: 'Test serializer display for exposed filters'
|
||||
module: rest_test_views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: null
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
query:
|
||||
type: views_query
|
||||
exposed_form:
|
||||
type: basic
|
||||
style:
|
||||
type: serializer
|
||||
row:
|
||||
type: data_field
|
||||
fields:
|
||||
nid:
|
||||
id: nid
|
||||
table: node_field_data
|
||||
field: nid
|
||||
plugin_id: field
|
||||
entity_type: node
|
||||
entity_field: nid
|
||||
body:
|
||||
id: body
|
||||
table: node__body
|
||||
field: body
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Body
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: text_default
|
||||
settings: { }
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
plugin_id: field
|
||||
entity_type: node
|
||||
entity_field: body
|
||||
filters:
|
||||
title:
|
||||
id: title
|
||||
table: node_field_data
|
||||
field: title
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: starts
|
||||
value: ''
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: title_op
|
||||
label: Title
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: title_op
|
||||
identifier: title
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '0'
|
||||
administrator: '0'
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
entity_type: node
|
||||
entity_field: title
|
||||
plugin_id: string
|
||||
title: 'Test serialize'
|
||||
arguments: { }
|
||||
rest_export_1:
|
||||
display_plugin: rest_export
|
||||
id: rest_export_1
|
||||
display_title: serializer
|
||||
position: null
|
||||
display_options:
|
||||
defaults:
|
||||
access: false
|
||||
style: false
|
||||
row: false
|
||||
path: test/serialize/node-exposed-filter
|
||||
access:
|
||||
type: none
|
||||
style:
|
||||
type: serializer
|
||||
row:
|
||||
type: data_field
|
|
@ -0,0 +1,70 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- rest
|
||||
- user
|
||||
id: test_serializer_shared_path
|
||||
label: 'Test serializer shared path'
|
||||
module: rest
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: entity_test
|
||||
base_field: id
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: null
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
query:
|
||||
type: views_query
|
||||
exposed_form:
|
||||
type: basic
|
||||
style:
|
||||
type: serializer
|
||||
row:
|
||||
type: data_entity
|
||||
sorts:
|
||||
id:
|
||||
id: standard
|
||||
table: entity_test
|
||||
field: id
|
||||
order: DESC
|
||||
plugin_id: date
|
||||
entity_type: entity_test
|
||||
entity_field: id
|
||||
title: 'Test serialize'
|
||||
arguments: { }
|
||||
rest_export_1:
|
||||
display_plugin: rest_export
|
||||
id: rest_export_1
|
||||
display_title: serializer
|
||||
position: null
|
||||
display_options:
|
||||
defaults:
|
||||
access: false
|
||||
path: test/serialize/shared
|
||||
page_1:
|
||||
display_plugin: page
|
||||
id: page_1
|
||||
display_title: page
|
||||
position: null
|
||||
display_options:
|
||||
defaults:
|
||||
access: false
|
||||
style: false
|
||||
row: false
|
||||
style:
|
||||
type: default
|
||||
row:
|
||||
type: entity:entity_test
|
||||
path: test/serialize/shared
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Trait for ResourceTestBase subclasses testing $auth=NULL, i.e. authless/anon.
|
||||
*
|
||||
* Characteristics:
|
||||
* - When no authentication provider is being used, there also cannot be any
|
||||
* particular error response for missing authentication, since by definition
|
||||
* there is not any authentication.
|
||||
* - For the same reason, there are no authentication edge cases to test.
|
||||
* - Because no authentication is required, this is vulnerable to CSRF attacks
|
||||
* by design. Hence a REST resource should probably only allow for anonymous
|
||||
* for safe (GET/HEAD) HTTP methods, and only with extreme care should unsafe
|
||||
* (POST/PATCH/DELETE) HTTP methods be allowed for a REST resource that allows
|
||||
* anonymous access.
|
||||
*/
|
||||
trait AnonResourceTestTrait {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function assertResponseWhenMissingAuthentication($method, ResponseInterface $response) {
|
||||
throw new \LogicException('When testing for anonymous users, authentication cannot be missing.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function assertAuthenticationEdgeCases($method, Url $url, array $request_options) {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Trait for ResourceTestBase subclasses testing $auth=basic_auth.
|
||||
*
|
||||
* Characteristics:
|
||||
* - Every request must send an Authorization header.
|
||||
* - When accessing a URI that requires authentication without being
|
||||
* authenticated, a 401 response must be sent.
|
||||
* - Because every request must send an authorization, there is no danger of
|
||||
* CSRF attacks.
|
||||
*
|
||||
* @see \Drupal\Tests\rest\Functional\BasicAuthResourceWithInterfaceTranslationTestTrait
|
||||
*/
|
||||
trait BasicAuthResourceTestTrait {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getAuthenticationRequestOptions($method) {
|
||||
return [
|
||||
'headers' => [
|
||||
'Authorization' => 'Basic ' . base64_encode($this->account->name->value . ':' . $this->account->passRaw),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function assertResponseWhenMissingAuthentication($method, ResponseInterface $response) {
|
||||
$expected_page_cache_header_value = $method === 'GET' ? 'MISS' : FALSE;
|
||||
// @see \Drupal\basic_auth\Authentication\Provider\BasicAuth::challengeException()
|
||||
$expected_dynamic_page_cache_header_value = $expected_page_cache_header_value;
|
||||
$this->assertResourceErrorResponse(401, 'No authentication credentials provided.', $response, ['4xx-response', 'config:system.site', 'config:user.role.anonymous', 'http_response'], ['user.roles:anonymous'], $expected_page_cache_header_value, $expected_dynamic_page_cache_header_value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function assertAuthenticationEdgeCases($method, Url $url, array $request_options) {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Trait for ResourceTestBase subclasses testing $auth=basic_auth + 'language'.
|
||||
*
|
||||
* @see \Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait
|
||||
*/
|
||||
trait BasicAuthResourceWithInterfaceTranslationTestTrait {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function assertResponseWhenMissingAuthentication($method, ResponseInterface $response) {
|
||||
// Because BasicAuth::challengeException() relies on the 'system.site'
|
||||
// configuration, and this test installs the 'language' module, all config
|
||||
// may be translated and therefore gets the 'languages:language_interface'
|
||||
// cache context.
|
||||
$expected_page_cache_header_value = $method === 'GET' ? 'MISS' : FALSE;
|
||||
$this->assertResourceErrorResponse(401, 'No authentication credentials provided.', $response, ['4xx-response', 'config:system.site', 'config:user.role.anonymous', 'http_response'], ['languages:language_interface', 'user.roles:anonymous'], $expected_page_cache_header_value, $expected_page_cache_header_value);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional;
|
||||
|
||||
/**
|
||||
* Trait for ResourceTestBase subclasses formatting expected timestamp data.
|
||||
*/
|
||||
trait BcTimestampNormalizerUnixTestTrait {
|
||||
|
||||
/**
|
||||
* Formats a UNIX timestamp.
|
||||
*
|
||||
* Depending on the 'bc_timestamp_normalizer_unix' setting. The return will be
|
||||
* an RFC3339 date string or the same timestamp that was passed in.
|
||||
*
|
||||
* @param int $timestamp
|
||||
* The timestamp value to format.
|
||||
*
|
||||
* @return string|int
|
||||
* The formatted RFC3339 date string or UNIX timestamp.
|
||||
*
|
||||
* @see \Drupal\serialization\Normalizer\TimestampItemNormalizer
|
||||
*/
|
||||
protected function formatExpectedTimestampItemValues($timestamp) {
|
||||
// If the setting is enabled, just return the timestamp as-is now.
|
||||
if ($this->config('serialization.settings')->get('bc_timestamp_normalizer_unix')) {
|
||||
return ['value' => $timestamp];
|
||||
}
|
||||
|
||||
// Otherwise, format the date string to the same that
|
||||
// \Drupal\serialization\Normalizer\TimestampItemNormalizer will produce.
|
||||
$date = new \DateTime();
|
||||
$date->setTimestamp($timestamp);
|
||||
$date->setTimezone(new \DateTimeZone('UTC'));
|
||||
|
||||
// Format is also added to the expected return values.
|
||||
return [
|
||||
'value' => $date->format(\DateTime::RFC3339),
|
||||
'format' => \DateTime::RFC3339,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use GuzzleHttp\RequestOptions;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Trait for ResourceTestBase subclasses testing $auth=cookie.
|
||||
*
|
||||
* Characteristics:
|
||||
* - After performing a valid "log in" request, the server responds with a 2xx
|
||||
* status code and a 'Set-Cookie' response header. This cookie is what
|
||||
* continues to identify the user in subsequent requests.
|
||||
* - When accessing a URI that requires authentication without being
|
||||
* authenticated, a standard 403 response must be sent.
|
||||
* - Because of the reliance on cookies, and the fact that user agents send
|
||||
* cookies with every request, this is vulnerable to CSRF attacks. To mitigate
|
||||
* this, the response for the "log in" request contains a CSRF token that must
|
||||
* be sent with every unsafe (POST/PATCH/DELETE) HTTP request.
|
||||
*/
|
||||
trait CookieResourceTestTrait {
|
||||
|
||||
/**
|
||||
* The session cookie.
|
||||
*
|
||||
* @see ::initAuthentication
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $sessionCookie;
|
||||
|
||||
/**
|
||||
* The CSRF token.
|
||||
*
|
||||
* @see ::initAuthentication
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $csrfToken;
|
||||
|
||||
/**
|
||||
* The logout token.
|
||||
*
|
||||
* @see ::initAuthentication
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $logoutToken;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function initAuthentication() {
|
||||
$user_login_url = Url::fromRoute('user.login.http')
|
||||
->setRouteParameter('_format', static::$format);
|
||||
|
||||
$request_body = [
|
||||
'name' => $this->account->name->value,
|
||||
'pass' => $this->account->passRaw,
|
||||
];
|
||||
|
||||
$request_options[RequestOptions::BODY] = $this->serializer->encode($request_body, static::$format);
|
||||
$request_options[RequestOptions::HEADERS] = [
|
||||
'Content-Type' => static::$mimeType,
|
||||
];
|
||||
$response = $this->request('POST', $user_login_url, $request_options);
|
||||
|
||||
// Parse and store the session cookie.
|
||||
$this->sessionCookie = explode(';', $response->getHeader('Set-Cookie')[0], 2)[0];
|
||||
|
||||
// Parse and store the CSRF token and logout token.
|
||||
$data = $this->serializer->decode((string) $response->getBody(), static::$format);
|
||||
$this->csrfToken = $data['csrf_token'];
|
||||
$this->logoutToken = $data['logout_token'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getAuthenticationRequestOptions($method) {
|
||||
$request_options[RequestOptions::HEADERS]['Cookie'] = $this->sessionCookie;
|
||||
// @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
|
||||
if (!in_array($method, ['HEAD', 'GET', 'OPTIONS', 'TRACE'])) {
|
||||
$request_options[RequestOptions::HEADERS]['X-CSRF-Token'] = $this->csrfToken;
|
||||
}
|
||||
return $request_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function assertResponseWhenMissingAuthentication($method, ResponseInterface $response) {
|
||||
// Requests needing cookie authentication but missing it results in a 403
|
||||
// response. The cookie authentication mechanism sets no response message.
|
||||
// Hence, effectively, this is just the 403 response that one gets as the
|
||||
// anonymous user trying to access a certain REST resource.
|
||||
// @see \Drupal\user\Authentication\Provider\Cookie
|
||||
// @todo https://www.drupal.org/node/2847623
|
||||
if ($method === 'GET') {
|
||||
$expected_cookie_403_cacheability = $this->getExpectedUnauthorizedAccessCacheability();
|
||||
// - \Drupal\Core\EventSubscriber\AnonymousUserResponseSubscriber applies
|
||||
// to cacheable anonymous responses: it updates their cacheability.
|
||||
// - A 403 response to a GET request is cacheable.
|
||||
// Therefore we must update our cacheability expectations accordingly.
|
||||
if (in_array('user.permissions', $expected_cookie_403_cacheability->getCacheContexts(), TRUE)) {
|
||||
$expected_cookie_403_cacheability->addCacheTags(['config:user.role.anonymous']);
|
||||
}
|
||||
// @todo Fix \Drupal\block\BlockAccessControlHandler::mergeCacheabilityFromConditions() in https://www.drupal.org/node/2867881
|
||||
if (static::$entityTypeId === 'block') {
|
||||
$expected_cookie_403_cacheability->setCacheTags(str_replace('user:2', 'user:0', $expected_cookie_403_cacheability->getCacheTags()));
|
||||
}
|
||||
$this->assertResourceErrorResponse(403, FALSE, $response, $expected_cookie_403_cacheability->getCacheTags(), $expected_cookie_403_cacheability->getCacheContexts(), 'MISS', 'MISS');
|
||||
}
|
||||
else {
|
||||
$this->assertResourceErrorResponse(403, FALSE, $response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function assertAuthenticationEdgeCases($method, Url $url, array $request_options) {
|
||||
// X-CSRF-Token request header is unnecessary for safe and side effect-free
|
||||
// HTTP methods. No need for additional assertions.
|
||||
// @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
|
||||
if (in_array($method, ['HEAD', 'GET', 'OPTIONS', 'TRACE'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
unset($request_options[RequestOptions::HEADERS]['X-CSRF-Token']);
|
||||
|
||||
// DX: 403 when missing X-CSRF-Token request header.
|
||||
$response = $this->request($method, $url, $request_options);
|
||||
$this->assertResourceErrorResponse(403, 'X-CSRF-Token request header is missing', $response);
|
||||
|
||||
$request_options[RequestOptions::HEADERS]['X-CSRF-Token'] = 'this-is-not-the-token-you-are-looking-for';
|
||||
|
||||
// DX: 403 when invalid X-CSRF-Token request header.
|
||||
$response = $this->request($method, $url, $request_options);
|
||||
$this->assertResourceErrorResponse(403, 'X-CSRF-Token request header is invalid', $response);
|
||||
|
||||
$request_options[RequestOptions::HEADERS]['X-CSRF-Token'] = $this->csrfToken;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Action;
|
||||
|
||||
@trigger_error('The ' . __NAMESPACE__ . '\ActionResourceTestBase is deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0. Instead, use Drupal\Tests\system\Functional\Rest\ActionResourceTestBase. See https://www.drupal.org/node/2971931.', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\Tests\system\Functional\Rest\ActionResourceTestBase as ActionResourceTestBaseReal;
|
||||
|
||||
/**
|
||||
* @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use
|
||||
* Drupal\Tests\system\Functional\Rest\ActionResourceTestBase instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2971931
|
||||
*/
|
||||
abstract class ActionResourceTestBase extends ActionResourceTestBaseReal {
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\BaseFieldOverride;
|
||||
|
||||
@trigger_error('The ' . __NAMESPACE__ . '\BaseFieldOverrideResourceTestBase is deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0. Instead, use Drupal\FunctionalTests\Rest\BaseFieldOverrideResourceTestBase. See https://www.drupal.org/node/2971931.', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\FunctionalTests\Rest\BaseFieldOverrideResourceTestBase as BaseFieldOverrideResourceTestBaseReal;
|
||||
|
||||
/**
|
||||
* @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use
|
||||
* Drupal\FunctionalTests\Rest\BaseFieldOverrideResourceTestBase instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2971931
|
||||
*/
|
||||
abstract class BaseFieldOverrideResourceTestBase extends BaseFieldOverrideResourceTestBaseReal {
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Block;
|
||||
|
||||
@trigger_error('The ' . __NAMESPACE__ . '\BlockResourceTestBase is deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0. Instead, use Drupal\Tests\block\Functional\Rest\BlockResourceTestBase. See https://www.drupal.org/node/2971931.', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\Tests\block\Functional\Rest\BlockResourceTestBase as BlockResourceTestBaseReal;
|
||||
|
||||
/**
|
||||
* @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use
|
||||
* Drupal\Tests\block\Functional\Rest\BlockResourceTestBase instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2971931
|
||||
*/
|
||||
abstract class BlockResourceTestBase extends BlockResourceTestBaseReal {
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\BlockContent;
|
||||
|
||||
@trigger_error('The ' . __NAMESPACE__ . '\BlockContentResourceTestBase is deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0. Instead, use Drupal\Tests\block_content\Functional\Rest\BlockContentResourceTestBase. See https://www.drupal.org/node/2971931.', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\Tests\block_content\Functional\Rest\BlockContentResourceTestBase as BlockContentResourceTestBaseReal;
|
||||
|
||||
/**
|
||||
* @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use
|
||||
* Drupal\Tests\block_content\Functional\Rest\BlockContentResourceTestBase instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2971931
|
||||
*/
|
||||
abstract class BlockContentResourceTestBase extends BlockContentResourceTestBaseReal {
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\BlockContentType;
|
||||
|
||||
@trigger_error('The ' . __NAMESPACE__ . '\BlockContentTypeResourceTestBase is deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0. Instead, use Drupal\Tests\block_content\Functional\Rest\BlockContentTypeResourceTestBase. See https://www.drupal.org/node/2971931.', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\Tests\block_content\Functional\Rest\BlockContentTypeResourceTestBase as BlockContentTypeResourceTestBaseReal;
|
||||
|
||||
/**
|
||||
* @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use
|
||||
* Drupal\Tests\block_content\Functional\Rest\BlockContentTypeResourceTestBase instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2971931
|
||||
*/
|
||||
abstract class BlockContentTypeResourceTestBase extends BlockContentTypeResourceTestBaseReal {
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Comment;
|
||||
|
||||
@trigger_error('The ' . __NAMESPACE__ . '\CommentResourceTestBase is deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0. Instead, use Drupal\Tests\comment\Functional\Rest\CommentResourceTestBase. See https://www.drupal.org/node/2971931.', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\Tests\comment\Functional\Rest\CommentResourceTestBase as CommentResourceTestBaseReal;
|
||||
|
||||
/**
|
||||
* @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use
|
||||
* Drupal\Tests\comment\Functional\Rest\CommentResourceTestBase instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2971931
|
||||
*/
|
||||
abstract class CommentResourceTestBase extends CommentResourceTestBaseReal {
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\CommentType;
|
||||
|
||||
@trigger_error('The ' . __NAMESPACE__ . '\CommentTypeResourceTestBase is deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0. Instead, use Drupal\Tests\comment\Functional\Rest\CommentTypeResourceTestBase. See https://www.drupal.org/node/2971931.', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\Tests\comment\Functional\Rest\CommentTypeResourceTestBase as CommentTypeResourceTestBaseReal;
|
||||
|
||||
/**
|
||||
* @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use
|
||||
* Drupal\Tests\comment\Functional\Rest\CommentTypeResourceTestBase instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2971931
|
||||
*/
|
||||
abstract class CommentTypeResourceTestBase extends CommentTypeResourceTestBaseReal {
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\ConfigTest;
|
||||
|
||||
@trigger_error('The ' . __NAMESPACE__ . '\ConfigTestResourceTestBase is deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0. Instead, use Drupal\Tests\config_test\Functional\Rest\ConfigTestResourceTestBase. See https://www.drupal.org/node/2971931.', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\Tests\config_test\Functional\Rest\ConfigTestResourceTestBase as ConfigTestResourceTestBaseReal;
|
||||
|
||||
/**
|
||||
* @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use
|
||||
* Drupal\Tests\config_test\Functional\Rest\ConfigTestResourceTestBase instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2971931
|
||||
*/
|
||||
abstract class ConfigTestResourceTestBase extends ConfigTestResourceTestBaseReal {
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\ConfigurableLanguage;
|
||||
|
||||
@trigger_error('The ' . __NAMESPACE__ . '\ConfigurableLanguageResourceTestBase is deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0. Instead, use Drupal\Tests\language\Functional\Rest\ConfigurableLanguageResourceTestBase. See https://www.drupal.org/node/2971931.', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\Tests\language\Functional\Rest\ConfigurableLanguageResourceTestBase as ConfigurableLanguageResourceTestBaseReal;
|
||||
|
||||
/**
|
||||
* @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use
|
||||
* Drupal\Tests\language\Functional\Rest\ConfigurableLanguageResourceTestBase instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2971931
|
||||
*/
|
||||
abstract class ConfigurableLanguageResourceTestBase extends ConfigurableLanguageResourceTestBaseReal {
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\ContactForm;
|
||||
|
||||
@trigger_error('The ' . __NAMESPACE__ . '\ContactFormResourceTestBase is deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0. Instead, use Drupal\Tests\contact\Functional\Rest\ContactFormResourceTestBase. See https://www.drupal.org/node/2971931.', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\Tests\contact\Functional\Rest\ContactFormResourceTestBase as ContactFormResourceTestBaseReal;
|
||||
|
||||
/**
|
||||
* @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use
|
||||
* Drupal\Tests\contact\Functional\Rest\ContactFormResourceTestBase instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2971931
|
||||
*/
|
||||
abstract class ContactFormResourceTestBase extends ContactFormResourceTestBaseReal {
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\ContentLanguageSettings;
|
||||
|
||||
@trigger_error('The ' . __NAMESPACE__ . '\ContentLanguageSettingsResourceTestBase is deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0. Instead, use Drupal\Tests\language\Functional\Rest\ContentLanguageSettingsResourceTestBase. See https://www.drupal.org/node/2971931.', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\Tests\language\Functional\Rest\ContentLanguageSettingsResourceTestBase as ContentLanguageSettingsResourceTestBaseReal;
|
||||
|
||||
/**
|
||||
* @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use
|
||||
* Drupal\Tests\language\Functional\Rest\ContentLanguageSettingsResourceTestBase instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2971931
|
||||
*/
|
||||
abstract class ContentLanguageSettingsResourceTestBase extends ContentLanguageSettingsResourceTestBaseReal {
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\DateFormat;
|
||||
|
||||
@trigger_error('The ' . __NAMESPACE__ . '\DateFormatResourceTestBase is deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0. Instead, use Drupal\FunctionalTests\Rest\DateFormatResourceTestBase. See https://www.drupal.org/node/2971931.', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\FunctionalTests\Rest\DateFormatResourceTestBase as DateFormatResourceTestBaseReal;
|
||||
|
||||
/**
|
||||
* @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use
|
||||
* Drupal\FunctionalTests\Rest\DateFormatResourceTestBase instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2971931
|
||||
*/
|
||||
abstract class DateFormatResourceTestBase extends DateFormatResourceTestBaseReal {
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Editor;
|
||||
|
||||
@trigger_error('The ' . __NAMESPACE__ . '\EditorResourceTestBase is deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0. Instead, use Drupal\Tests\editor\Functional\Rest\EditorResourceTestBase. See https://www.drupal.org/node/2971931.', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\Tests\editor\Functional\Rest\EditorResourceTestBase as EditorResourceTestBaseReal;
|
||||
|
||||
/**
|
||||
* @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use
|
||||
* Drupal\Tests\editor\Functional\Rest\EditorResourceTestBase instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2971931
|
||||
*/
|
||||
abstract class EditorResourceTestBase extends EditorResourceTestBaseReal {
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\EntityFormDisplay;
|
||||
|
||||
@trigger_error('The ' . __NAMESPACE__ . '\EntityFormDisplayResourceTestBase is deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0. Instead, use Drupal\FunctionalTests\Rest\EntityFormDisplayResourceTestBase. See https://www.drupal.org/node/2971931.', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\FunctionalTests\Rest\EntityFormDisplayResourceTestBase as EntityFormDisplayResourceTestBaseReal;
|
||||
|
||||
/**
|
||||
* @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use
|
||||
* Drupal\FunctionalTests\Rest\EntityFormDisplayResourceTestBase instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2971931
|
||||
*/
|
||||
abstract class EntityFormDisplayResourceTestBase extends EntityFormDisplayResourceTestBaseReal {
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\EntityFormMode;
|
||||
|
||||
@trigger_error('The ' . __NAMESPACE__ . '\EntityFormModeResourceTestBase is deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0. Instead, use Drupal\FunctionalTests\Rest\EntityFormModeResourceTestBase. See https://www.drupal.org/node/2971931.', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\FunctionalTests\Rest\EntityFormModeResourceTestBase as EntityFormModeResourceTestBaseReal;
|
||||
|
||||
/**
|
||||
* @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use
|
||||
* Drupal\FunctionalTests\Rest\EntityFormModeResourceTestBase instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2971931
|
||||
*/
|
||||
abstract class EntityFormModeResourceTestBase extends EntityFormModeResourceTestBaseReal {
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Checks that all core content/config entity types have REST test coverage.
|
||||
*
|
||||
* Every entity type must have test coverage for:
|
||||
* - every format in core (json + xml + hal_json)
|
||||
* - every authentication provider in core (anon, cookie, basic_auth)
|
||||
*
|
||||
* @group rest
|
||||
*/
|
||||
class EntityResourceRestTestCoverageTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Entity definitions array.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $definitions;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$all_modules = system_rebuild_module_data();
|
||||
$stable_core_modules = array_filter($all_modules, function ($module) {
|
||||
// Filter out contrib, hidden, testing, and experimental modules. We also
|
||||
// don't need to enable modules that are already enabled.
|
||||
return
|
||||
$module->origin === 'core' &&
|
||||
empty($module->info['hidden']) &&
|
||||
$module->status == FALSE &&
|
||||
$module->info['package'] !== 'Testing' &&
|
||||
$module->info['package'] !== 'Core (Experimental)';
|
||||
});
|
||||
|
||||
$this->container->get('module_installer')->install(array_keys($stable_core_modules));
|
||||
$this->rebuildContainer();
|
||||
|
||||
$this->definitions = $this->container->get('entity_type.manager')->getDefinitions();
|
||||
|
||||
// Entity types marked as "internal" are not exposed by the entity REST
|
||||
// resource plugin and hence also don't need test coverage.
|
||||
$this->definitions = array_filter($this->definitions, function (EntityTypeInterface $entity_type) {
|
||||
return !$entity_type->isInternal();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that all core content/config entity types have REST test coverage.
|
||||
*/
|
||||
public function testEntityTypeRestTestCoverage() {
|
||||
$tests = [
|
||||
// Test coverage for formats provided by the 'serialization' module.
|
||||
'serialization' => [
|
||||
'path' => '\Drupal\Tests\PROVIDER\Functional\Rest\CLASS',
|
||||
'class suffix' => [
|
||||
'JsonAnonTest',
|
||||
'JsonBasicAuthTest',
|
||||
'JsonCookieTest',
|
||||
'XmlAnonTest',
|
||||
'XmlBasicAuthTest',
|
||||
'XmlCookieTest',
|
||||
],
|
||||
],
|
||||
// Test coverage for formats provided by the 'hal' module.
|
||||
'hal' => [
|
||||
'path' => '\Drupal\Tests\PROVIDER\Functional\Hal\CLASS',
|
||||
'class suffix' => [
|
||||
'HalJsonAnonTest',
|
||||
'HalJsonBasicAuthTest',
|
||||
'HalJsonCookieTest',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$problems = [];
|
||||
foreach ($this->definitions as $entity_type_id => $info) {
|
||||
$class_name_full = $info->getClass();
|
||||
$parts = explode('\\', $class_name_full);
|
||||
$class_name = end($parts);
|
||||
$module_name = $parts[1];
|
||||
|
||||
foreach ($tests as $module => $info) {
|
||||
$path = $info['path'];
|
||||
$missing_tests = [];
|
||||
foreach ($info['class suffix'] as $postfix) {
|
||||
$class = str_replace(['PROVIDER', 'CLASS'], [$module_name, $class_name], $path . $postfix);
|
||||
$class_alternative = str_replace("\\Drupal\\Tests\\$module_name\\Functional", '\Drupal\FunctionalTests', $class);
|
||||
if (class_exists($class) || class_exists($class_alternative)) {
|
||||
continue;
|
||||
}
|
||||
$missing_tests[] = $postfix;
|
||||
}
|
||||
if (!empty($missing_tests)) {
|
||||
$missing_tests_list = implode(', ', array_map(function ($missing_test) use ($class_name) {
|
||||
return $class_name . $missing_test;
|
||||
}, $missing_tests));
|
||||
$which_normalization = $module === 'serialization' ? 'default' : $module;
|
||||
$problems[] = "$entity_type_id: $class_name ($class_name_full), $which_normalization normalization (expected tests: $missing_tests_list)";
|
||||
}
|
||||
}
|
||||
}
|
||||
$all = count($this->definitions);
|
||||
$good = $all - count($problems);
|
||||
$this->assertSame([], $problems, $this->getLlamaMessage($good, $all));
|
||||
}
|
||||
|
||||
/**
|
||||
* Message from Llama.
|
||||
*
|
||||
* @param int $g
|
||||
* A count of entities with test coverage.
|
||||
* @param int $a
|
||||
* A count of all entities.
|
||||
*
|
||||
* @return string
|
||||
* An information about progress of REST test coverage.
|
||||
*/
|
||||
protected function getLlamaMessage($g, $a) {
|
||||
return "
|
||||
☼
|
||||
________________________
|
||||
/ Hi! \\
|
||||
| It's llame to not have |
|
||||
| complete REST tests! |
|
||||
| |
|
||||
| Progress: $g/$a. |
|
||||
| ________________________/
|
||||
|/
|
||||
// o
|
||||
l'>
|
||||
ll
|
||||
llama
|
||||
|| ||
|
||||
'' ''
|
||||
";
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue