Move into nested docroot
This commit is contained in:
parent
83a0d3a149
commit
c8b70abde9
13405 changed files with 0 additions and 0 deletions
11
web/core/modules/rest/config/install/rest.settings.yml
Normal file
11
web/core/modules/rest/config/install/rest.settings.yml
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Set the domain for REST type and relation links.
|
||||
# If left blank, the site's domain will be used.
|
||||
link_domain: ~
|
||||
|
||||
# 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
|
84
web/core/modules/rest/config/schema/rest.schema.yml
Normal file
84
web/core/modules/rest/config/schema/rest.schema.yml
Normal file
|
@ -0,0 +1,84 @@
|
|||
# Schema for the configuration files of the REST module.
|
||||
rest.settings:
|
||||
type: config_object
|
||||
label: 'REST settings'
|
||||
mapping:
|
||||
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:
|
||||
GET:
|
||||
type: rest_request
|
||||
label: 'GET method settings'
|
||||
POST:
|
||||
type: rest_request
|
||||
label: 'POST method settings'
|
||||
PATCH:
|
||||
type: rest_request
|
||||
label: 'PATCH method settings'
|
||||
DELETE:
|
||||
type: rest_request
|
||||
label: 'DELETE 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'
|
41
web/core/modules/rest/config/schema/rest.views.schema.yml
Normal file
41
web/core/modules/rest/config/schema/rest.views.schema.yml
Normal file
|
@ -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'
|
80
web/core/modules/rest/rest.api.php
Normal file
80
web/core/modules/rest/rest.api.php
Normal file
|
@ -0,0 +1,80 @@
|
|||
<?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.
|
||||
*
|
||||
* 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 = array()) {
|
||||
if ($context['mymodule'] == TRUE) {
|
||||
$base = \Drupal::config('rest.settings')->get('link_domain');
|
||||
$uri = str_replace($base, 'http://mymodule.domain', $uri);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Alter the REST relation URI.
|
||||
*
|
||||
* 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 = array()) {
|
||||
if ($context['mymodule'] == TRUE) {
|
||||
$base = \Drupal::config('rest.settings')->get('link_domain');
|
||||
$uri = str_replace($base, 'http://mymodule.domain', $uri);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup hooks".
|
||||
*/
|
8
web/core/modules/rest/rest.info.yml
Normal file
8
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:
|
||||
- serialization
|
96
web/core/modules/rest/rest.install
Normal file
96
web/core/modules/rest/rest.install
Normal file
|
@ -0,0 +1,96 @@
|
|||
<?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 = array();
|
||||
|
||||
if (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'] = array(
|
||||
'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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @defgroup updates-8.1.x-to-8.2.x
|
||||
* @{
|
||||
* Update functions from 8.1.x to 8.2.x.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "defgroup updates-8.1.x-to-8.2.x".
|
||||
*/
|
29
web/core/modules/rest/rest.module
Normal file
29
web/core/modules/rest/rest.module
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* RESTful web services module.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
||||
/**
|
||||
* 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 entities (see the <a href=":field">Field module help page</a> for more information about entities) such as content, users, taxonomy terms, etc.; 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>.', array(':rest' => 'https://www.drupal.org/documentation/modules/rest', ':field' => (\Drupal::moduleHandler()->moduleExists('field')) ? \Drupal::url('help.page', array('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.', array(':hal' => (\Drupal::moduleHandler()->moduleExists('hal')) ? \Drupal::url('help.page', array('name' => 'hal')) : '#', ':basic_auth' => (\Drupal::moduleHandler()->moduleExists('basic_auth')) ? \Drupal::url('help.page', array('name' => 'basic_auth')) : '#')) . '</dd>';
|
||||
$output .= '<dt>' . t('Enabling REST support for an entity type') . '</dt>';
|
||||
$output .= '<dd>' . t('REST support for content items of the Node module is enabled by default, and support for other types of content entities can be enabled. To enable support, you can use a <a href=":config">process based on configuration editing</a> or the contributed <a href=":restui">Rest UI module</a>.', array(':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;
|
||||
}
|
||||
}
|
5
web/core/modules/rest/rest.permissions.yml
Normal file
5
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'
|
73
web/core/modules/rest/rest.post_update.php
Normal file
73
web/core/modules/rest/rest.post_update.php
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Post update functions for Rest.
|
||||
*/
|
||||
|
||||
use Drupal\rest\Entity\RestResourceConfig;
|
||||
use Drupal\rest\RestResourceConfigInterface;
|
||||
|
||||
/**
|
||||
* @addtogroup updates-8.1.x-to-8.2.x
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup updates-8.1.x-to-8.2.x".
|
||||
*/
|
9
web/core/modules/rest/rest.routing.yml
Normal file
9
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'
|
30
web/core/modules/rest/rest.services.yml
Normal file
30
web/core/modules/rest/rest.services.yml
Normal file
|
@ -0,0 +1,30 @@
|
|||
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.link_manager:
|
||||
class: Drupal\rest\LinkManager\LinkManager
|
||||
arguments: ['@rest.link_manager.type', '@rest.link_manager.relation']
|
||||
rest.link_manager.type:
|
||||
class: Drupal\rest\LinkManager\TypeLinkManager
|
||||
arguments: ['@cache.default', '@module_handler', '@config.factory', '@request_stack', '@entity_type.bundle.info']
|
||||
rest.link_manager.relation:
|
||||
class: Drupal\rest\LinkManager\RelationLinkManager
|
||||
arguments: ['@cache.default', '@entity.manager', '@module_handler', '@config.factory', '@request_stack']
|
||||
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']
|
48
web/core/modules/rest/src/Annotation/RestResource.php
Normal file
48
web/core/modules/rest/src/Annotation/RestResource.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?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 resource plugin ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The human-readable name of the resource plugin.
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*/
|
||||
public $label;
|
||||
|
||||
/**
|
||||
* The serialization class to deserialize serialized data into.
|
||||
*
|
||||
* @var string (optional)
|
||||
*/
|
||||
public $serialization_class;
|
||||
|
||||
}
|
271
web/core/modules/rest/src/Entity/ConfigDependencies.php
Normal file
271
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;
|
||||
}
|
||||
|
||||
}
|
258
web/core/modules/rest/src/Entity/RestResourceConfig.php
Normal file
258
web/core/modules/rest/src/Entity/RestResourceConfig.php
Normal file
|
@ -0,0 +1,258 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Entity;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityBase;
|
||||
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"),
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\LinkManager;
|
||||
|
||||
/**
|
||||
* Defines an interface for a link manager with a configurable domain.
|
||||
*/
|
||||
interface ConfigurableLinkManagerInterface {
|
||||
|
||||
/**
|
||||
* Sets the link domain used in constructing link URIs.
|
||||
*
|
||||
* @param string $domain
|
||||
* The link domain to use for constructing link URIs.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setLinkDomain($domain);
|
||||
|
||||
}
|
71
web/core/modules/rest/src/LinkManager/LinkManager.php
Normal file
71
web/core/modules/rest/src/LinkManager/LinkManager.php
Normal file
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\LinkManager;
|
||||
|
||||
class LinkManager implements LinkManagerInterface {
|
||||
|
||||
/**
|
||||
* The type link manager.
|
||||
*
|
||||
* @var \Drupal\rest\LinkManager\TypeLinkManagerInterface
|
||||
*/
|
||||
protected $typeLinkManager;
|
||||
|
||||
/**
|
||||
* The relation link manager.
|
||||
*
|
||||
* @var \Drupal\rest\LinkManager\RelationLinkManagerInterface
|
||||
*/
|
||||
protected $relationLinkManager;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \Drupal\rest\LinkManager\TypeLinkManagerInterface $type_link_manager
|
||||
* Manager for handling bundle URIs.
|
||||
* @param \Drupal\rest\LinkManager\RelationLinkManagerInterface $relation_link_manager
|
||||
* Manager for handling bundle URIs.
|
||||
*/
|
||||
public function __construct(TypeLinkManagerInterface $type_link_manager, RelationLinkManagerInterface $relation_link_manager) {
|
||||
$this->typeLinkManager = $type_link_manager;
|
||||
$this->relationLinkManager = $relation_link_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTypeUri($entity_type, $bundle, $context = array()) {
|
||||
return $this->typeLinkManager->getTypeUri($entity_type, $bundle, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTypeInternalIds($type_uri, $context = array()) {
|
||||
return $this->typeLinkManager->getTypeInternalIds($type_uri, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRelationUri($entity_type, $bundle, $field_name, $context = array()) {
|
||||
return $this->relationLinkManager->getRelationUri($entity_type, $bundle, $field_name, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRelationInternalIds($relation_uri) {
|
||||
return $this->relationLinkManager->getRelationInternalIds($relation_uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setLinkDomain($domain) {
|
||||
$this->relationLinkManager->setLinkDomain($domain);
|
||||
$this->typeLinkManager->setLinkDomain($domain);
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
58
web/core/modules/rest/src/LinkManager/LinkManagerBase.php
Normal file
58
web/core/modules/rest/src/LinkManager/LinkManagerBase.php
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\LinkManager;
|
||||
|
||||
/**
|
||||
* Defines an abstract base-class for REST link manager objects.
|
||||
*/
|
||||
abstract class LinkManagerBase {
|
||||
|
||||
/**
|
||||
* Link domain used for type links URIs.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $linkDomain;
|
||||
|
||||
/**
|
||||
* Config factory service.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* The request stack.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\RequestStack
|
||||
*/
|
||||
protected $requestStack;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setLinkDomain($domain) {
|
||||
$this->linkDomain = rtrim($domain, '/');
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the link domain.
|
||||
*
|
||||
* @return string
|
||||
* The link domain.
|
||||
*/
|
||||
protected function getLinkDomain() {
|
||||
if (empty($this->linkDomain)) {
|
||||
if ($domain = $this->configFactory->get('rest.settings')->get('link_domain')) {
|
||||
$this->linkDomain = rtrim($domain, '/');
|
||||
}
|
||||
else {
|
||||
$request = $this->requestStack->getCurrentRequest();
|
||||
$this->linkDomain = $request->getSchemeAndHttpHost() . $request->getBasePath();
|
||||
}
|
||||
}
|
||||
return $this->linkDomain;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\LinkManager;
|
||||
|
||||
/**
|
||||
* Interface implemented by link managers.
|
||||
*
|
||||
* There are no explicit methods on the manager interface. Instead link managers
|
||||
* broker the interactions of the different components, and therefore must
|
||||
* implement each component interface, which is enforced by this interface
|
||||
* extending all of the component ones.
|
||||
*
|
||||
* While a link manager may directly implement these interface methods with
|
||||
* custom logic, it is expected to be more common for plugin managers to proxy
|
||||
* the method invocations to the respective components.
|
||||
*/
|
||||
interface LinkManagerInterface extends TypeLinkManagerInterface, RelationLinkManagerInterface {
|
||||
}
|
142
web/core/modules/rest/src/LinkManager/RelationLinkManager.php
Normal file
142
web/core/modules/rest/src/LinkManager/RelationLinkManager.php
Normal file
|
@ -0,0 +1,142 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\LinkManager;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Entity\ContentEntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
class RelationLinkManager extends LinkManagerBase implements RelationLinkManagerInterface {
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Cache\CacheBackendInterface;
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* Entity manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* Module handler service.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
|
||||
* The cache of relation URIs and their associated Typed Data IDs.
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler service.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The config factory service.
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
|
||||
* The request stack.
|
||||
*/
|
||||
public function __construct(CacheBackendInterface $cache, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, ConfigFactoryInterface $config_factory, RequestStack $request_stack) {
|
||||
$this->cache = $cache;
|
||||
$this->entityManager = $entity_manager;
|
||||
$this->configFactory = $config_factory;
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->requestStack = $request_stack;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRelationUri($entity_type, $bundle, $field_name, $context = array()) {
|
||||
// Per the interface documention of this method, the returned URI may
|
||||
// optionally also serve as the URL of a documentation page about this
|
||||
// field. However, the REST module does not currently implement such
|
||||
// a documentation page. Therefore, we return a URI assembled relative to
|
||||
// the site's base URL, which is sufficient to uniquely identify the site's
|
||||
// entity type + bundle + field for use in hypermedia formats, but we do
|
||||
// not take into account unclean URLs, language prefixing, or anything else
|
||||
// that would be required for Drupal to be able to respond with content
|
||||
// at this URL. If a module is installed that adds such content, but
|
||||
// requires this URL to be different (e.g., include a language prefix),
|
||||
// then the module must also override the RelationLinkManager class/service
|
||||
// to return the desired URL.
|
||||
$uri = $this->getLinkDomain() . "/rest/relation/$entity_type/$bundle/$field_name";
|
||||
$this->moduleHandler->alter('rest_relation_uri', $uri, $context);
|
||||
return $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRelationInternalIds($relation_uri, $context = array()) {
|
||||
$relations = $this->getRelations($context);
|
||||
if (isset($relations[$relation_uri])) {
|
||||
return $relations[$relation_uri];
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array of relation links.
|
||||
*
|
||||
* Any field can be handled as a relation simply by changing how it is
|
||||
* normalized. Therefore, there is no prior knowledge that can be used here
|
||||
* to determine which fields to assign relation URIs. Instead, each field,
|
||||
* even primitives, are given a relation URI. It is up to the caller to
|
||||
* determine which URIs to use.
|
||||
*
|
||||
* @param array $context
|
||||
* Context from the normalizer/serializer operation.
|
||||
*
|
||||
* @return array
|
||||
* An array of typed data ids (entity_type, bundle, and field name) keyed
|
||||
* by corresponding relation URI.
|
||||
*/
|
||||
protected function getRelations($context = array()) {
|
||||
$cid = 'rest:links:relations';
|
||||
$cache = $this->cache->get($cid);
|
||||
if (!$cache) {
|
||||
$this->writeCache($context);
|
||||
$cache = $this->cache->get($cid);
|
||||
}
|
||||
return $cache->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the cache of relation links.
|
||||
*
|
||||
* @param array $context
|
||||
* Context from the normalizer/serializer operation.
|
||||
*/
|
||||
protected function writeCache($context = array()) {
|
||||
$data = array();
|
||||
|
||||
foreach ($this->entityManager->getDefinitions() as $entity_type) {
|
||||
if ($entity_type instanceof ContentEntityTypeInterface) {
|
||||
foreach ($this->entityManager->getBundleInfo($entity_type->id()) as $bundle => $bundle_info) {
|
||||
foreach ($this->entityManager->getFieldDefinitions($entity_type->id(), $bundle) as $field_definition) {
|
||||
$relation_uri = $this->getRelationUri($entity_type->id(), $bundle, $field_definition->getName(), $context);
|
||||
$data[$relation_uri] = array(
|
||||
'entity_type' => $entity_type,
|
||||
'bundle' => $bundle,
|
||||
'field_name' => $field_definition->getName(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// These URIs only change when field info changes, so cache it permanently
|
||||
// and only clear it when the fields cache is cleared.
|
||||
$this->cache->set('rest:links:relations', $data, Cache::PERMANENT, array('entity_field_info'));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\LinkManager;
|
||||
|
||||
interface RelationLinkManagerInterface extends ConfigurableLinkManagerInterface {
|
||||
|
||||
/**
|
||||
* Gets the URI that corresponds to a field.
|
||||
*
|
||||
* When using hypermedia formats, this URI can be used to indicate which
|
||||
* field the data represents. Documentation about this field can also be
|
||||
* provided at this URI.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* The bundle's entity type.
|
||||
* @param string $bundle
|
||||
* The bundle name.
|
||||
* @param string $field_name
|
||||
* The field name.
|
||||
* @param array $context
|
||||
* (optional) Optional serializer/normalizer context.
|
||||
*
|
||||
* @return string
|
||||
* The corresponding URI for the field.
|
||||
*/
|
||||
public function getRelationUri($entity_type, $bundle, $field_name, $context = array());
|
||||
|
||||
/**
|
||||
* Translates a REST URI into internal IDs.
|
||||
*
|
||||
* @param string $relation_uri
|
||||
* Relation URI to transform into internal IDs
|
||||
*
|
||||
* @return array
|
||||
* Array with keys 'entity_type', 'bundle' and 'field_name'.
|
||||
*/
|
||||
public function getRelationInternalIds($relation_uri);
|
||||
|
||||
}
|
148
web/core/modules/rest/src/LinkManager/TypeLinkManager.php
Normal file
148
web/core/modules/rest/src/LinkManager/TypeLinkManager.php
Normal file
|
@ -0,0 +1,148 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\LinkManager;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
class TypeLinkManager extends LinkManagerBase implements TypeLinkManagerInterface {
|
||||
|
||||
/**
|
||||
* Injected cache backend.
|
||||
*
|
||||
* @var \Drupal\Core\Cache\CacheBackendInterface;
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* Module handler service.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The bundle info service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
|
||||
*/
|
||||
protected $bundleInfoService;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
|
||||
* The injected cache backend for caching type URIs.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler service.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The config factory service.
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
|
||||
* The request stack.
|
||||
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info_service
|
||||
* The bundle info service.
|
||||
*/
|
||||
public function __construct(CacheBackendInterface $cache, ModuleHandlerInterface $module_handler, ConfigFactoryInterface $config_factory, RequestStack $request_stack, EntityTypeBundleInfoInterface $bundle_info_service) {
|
||||
$this->cache = $cache;
|
||||
$this->configFactory = $config_factory;
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->requestStack = $request_stack;
|
||||
$this->bundleInfoService = $bundle_info_service;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTypeUri($entity_type, $bundle, $context = array()) {
|
||||
// Per the interface documention of this method, the returned URI may
|
||||
// optionally also serve as the URL of a documentation page about this
|
||||
// bundle. However, the REST module does not currently implement such
|
||||
// a documentation page. Therefore, we return a URI assembled relative to
|
||||
// the site's base URL, which is sufficient to uniquely identify the site's
|
||||
// entity type and bundle for use in hypermedia formats, but we do not
|
||||
// take into account unclean URLs, language prefixing, or anything else
|
||||
// that would be required for Drupal to be able to respond with content
|
||||
// at this URL. If a module is installed that adds such content, but
|
||||
// requires this URL to be different (e.g., include a language prefix),
|
||||
// then the module must also override the TypeLinkManager class/service to
|
||||
// return the desired URL.
|
||||
$uri = $this->getLinkDomain() . "/rest/type/$entity_type/$bundle";
|
||||
$this->moduleHandler->alter('rest_type_uri', $uri, $context);
|
||||
return $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTypeInternalIds($type_uri, $context = array()) {
|
||||
$types = $this->getTypes($context);
|
||||
if (isset($types[$type_uri])) {
|
||||
return $types[$type_uri];
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array of type links.
|
||||
*
|
||||
* @param array $context
|
||||
* Context from the normalizer/serializer operation.
|
||||
*
|
||||
* @return array
|
||||
* An array of typed data ids (entity_type and bundle) keyed by
|
||||
* corresponding type URI.
|
||||
*/
|
||||
protected function getTypes($context = array()) {
|
||||
$cid = 'rest:links:types';
|
||||
$cache = $this->cache->get($cid);
|
||||
if (!$cache) {
|
||||
$data = $this->writeCache($context);
|
||||
}
|
||||
else {
|
||||
$data = $cache->data;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the cache of type links.
|
||||
*
|
||||
* @param array $context
|
||||
* Context from the normalizer/serializer operation.
|
||||
*
|
||||
* @return array
|
||||
* An array of typed data ids (entity_type and bundle) keyed by
|
||||
* corresponding type URI.
|
||||
*/
|
||||
protected function writeCache($context = array()) {
|
||||
$data = array();
|
||||
|
||||
// Type URIs correspond to bundles. Iterate through the bundles to get the
|
||||
// URI and data for them.
|
||||
$entity_types = \Drupal::entityManager()->getDefinitions();
|
||||
foreach ($this->bundleInfoService->getAllBundleInfo() as $entity_type_id => $bundles) {
|
||||
// Only content entities are supported currently.
|
||||
// @todo Consider supporting config entities.
|
||||
if ($entity_types[$entity_type_id]->isSubclassOf('\Drupal\Core\Config\Entity\ConfigEntityInterface')) {
|
||||
continue;
|
||||
}
|
||||
foreach ($bundles as $bundle => $bundle_info) {
|
||||
// Get a type URI for the bundle.
|
||||
$bundle_uri = $this->getTypeUri($entity_type_id, $bundle, $context);
|
||||
$data[$bundle_uri] = array(
|
||||
'entity_type' => $entity_type_id,
|
||||
'bundle' => $bundle,
|
||||
);
|
||||
}
|
||||
}
|
||||
// These URIs only change when entity info changes, so cache it permanently
|
||||
// and only clear it when entity_info is cleared.
|
||||
$this->cache->set('rest:links:types', $data, Cache::PERMANENT, array('entity_types'));
|
||||
return $data;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\LinkManager;
|
||||
|
||||
interface TypeLinkManagerInterface extends ConfigurableLinkManagerInterface {
|
||||
|
||||
/**
|
||||
* Gets the URI that corresponds to a bundle.
|
||||
*
|
||||
* When using hypermedia formats, this URI can be used to indicate which
|
||||
* bundle the data represents. Documentation about required and optional
|
||||
* fields can also be provided at this URI.
|
||||
*
|
||||
* @param $entity_type
|
||||
* The bundle's entity type.
|
||||
* @param $bundle
|
||||
* The bundle name.
|
||||
* @param array $context
|
||||
* (optional) Optional serializer/normalizer context.
|
||||
*
|
||||
* @return string
|
||||
* The corresponding URI for the bundle.
|
||||
*/
|
||||
public function getTypeUri($entity_type, $bundle, $context = array());
|
||||
|
||||
/**
|
||||
* Get a bundle's Typed Data IDs based on a URI.
|
||||
*
|
||||
* @param string $type_uri
|
||||
* The type URI.
|
||||
* @param array $context
|
||||
* Context from the normalizer/serializer operation.
|
||||
*
|
||||
* @return array | boolean
|
||||
* If the URI matches a bundle, returns an array containing entity_type and
|
||||
* bundle. Otherwise, returns false.
|
||||
*/
|
||||
public function getTypeInternalIds($type_uri, $context = array());
|
||||
|
||||
}
|
34
web/core/modules/rest/src/ModifiedResourceResponse.php
Normal file
34
web/core/modules/rest/src/ModifiedResourceResponse.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* A response that does not contain cacheability metadata.
|
||||
*
|
||||
* Used when resources are modified by a request: responses to unsafe requests
|
||||
* (POST/PATCH/DELETE) can never be cached.
|
||||
*
|
||||
* @see \Drupal\rest\ResourceResponse
|
||||
*/
|
||||
class ModifiedResourceResponse extends Response implements ResourceResponseInterface {
|
||||
|
||||
use ResourceResponseTrait;
|
||||
|
||||
/**
|
||||
* Constructor for ModifiedResourceResponse objects.
|
||||
*
|
||||
* @param mixed $data
|
||||
* Response data that should be serialized.
|
||||
* @param int $status
|
||||
* The response status code.
|
||||
* @param array $headers
|
||||
* An array of response headers.
|
||||
*/
|
||||
public function __construct($data = NULL, $status = 200, $headers = []) {
|
||||
$this->responseData = $data;
|
||||
parent::__construct('', $status, $headers);
|
||||
}
|
||||
|
||||
}
|
97
web/core/modules/rest/src/Plugin/Deriver/EntityDeriver.php
Normal file
97
web/core/modules/rest/src/Plugin/Deriver/EntityDeriver.php
Normal file
|
@ -0,0 +1,97 @@
|
|||
<?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) {
|
||||
$this->derivatives[$entity_type_id] = array(
|
||||
'id' => 'entity:' . $entity_type_id,
|
||||
'entity_type' => $entity_type_id,
|
||||
'serialization_class' => $entity_type->getClass(),
|
||||
'label' => $entity_type->getLabel(),
|
||||
);
|
||||
|
||||
$default_uris = array(
|
||||
'canonical' => "/entity/$entity_type_id/" . '{' . $entity_type_id . '}',
|
||||
'https://www.drupal.org/link-relations/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;
|
||||
}
|
||||
|
||||
}
|
242
web/core/modules/rest/src/Plugin/ResourceBase.php
Normal file
242
web/core/modules/rest/src/Plugin/ResourceBase.php
Normal file
|
@ -0,0 +1,242 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Plugin;
|
||||
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Common base class for resource plugins.
|
||||
*
|
||||
* Note that this base class' implementation of the permissions() method
|
||||
* generates a permission for every method for a resource. If your resource
|
||||
* already has its own access control mechanism, you should opt out from this
|
||||
* default permissions() method by overriding it.
|
||||
*
|
||||
* @see \Drupal\rest\Annotation\RestResource
|
||||
* @see \Drupal\rest\Plugin\Type\ResourcePluginManager
|
||||
* @see \Drupal\rest\Plugin\ResourceInterface
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup third_party
|
||||
*/
|
||||
abstract class ResourceBase extends PluginBase implements ContainerFactoryPluginInterface, ResourceInterface {
|
||||
|
||||
/**
|
||||
* The available serialization formats.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $serializerFormats = array();
|
||||
|
||||
/**
|
||||
* 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 = array();
|
||||
$definition = $this->getPluginDefinition();
|
||||
foreach ($this->availableMethods() as $method) {
|
||||
$lowered_method = strtolower($method);
|
||||
$permissions["restful $lowered_method $this->pluginId"] = array(
|
||||
'title' => $this->t('Access @method on %label resource', array('@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']['https://www.drupal.org/link-relations/create']) ? $definition['uri_paths']['https://www.drupal.org/link-relations/create'] : '/' . strtr($this->pluginId, ':', '/');
|
||||
|
||||
$route_name = strtr($this->pluginId, ':', '.');
|
||||
|
||||
$methods = $this->availableMethods();
|
||||
foreach ($methods as $method) {
|
||||
$route = $this->getBaseRoute($canonical_path, $method);
|
||||
|
||||
switch ($method) {
|
||||
case 'POST':
|
||||
$route->setPath($create_path);
|
||||
// Restrict the incoming HTTP Content-type header to the known
|
||||
// serialization formats.
|
||||
$route->addRequirements(array('_content_type_format' => implode('|', $this->serializerFormats)));
|
||||
$collection->add("$route_name.$method", $route);
|
||||
break;
|
||||
|
||||
case 'PATCH':
|
||||
// Restrict the incoming HTTP Content-type header to the known
|
||||
// serialization formats.
|
||||
$route->addRequirements(array('_content_type_format' => implode('|', $this->serializerFormats)));
|
||||
$collection->add("$route_name.$method", $route);
|
||||
break;
|
||||
|
||||
case 'GET':
|
||||
case 'HEAD':
|
||||
// Restrict GET and HEAD requests to the media type specified in the
|
||||
// HTTP Accept headers.
|
||||
foreach ($this->serializerFormats as $format_name) {
|
||||
// Expose one route per available format.
|
||||
$format_route = clone $route;
|
||||
$format_route->addRequirements(array('_format' => $format_name));
|
||||
$collection->add("$route_name.$method.$format_name", $format_route);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$collection->add("$route_name.$method", $route);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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 array(
|
||||
'HEAD',
|
||||
'GET',
|
||||
'POST',
|
||||
'PUT',
|
||||
'DELETE',
|
||||
'TRACE',
|
||||
'OPTIONS',
|
||||
'CONNECT',
|
||||
'PATCH',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function availableMethods() {
|
||||
$methods = $this->requestMethods();
|
||||
$available = array();
|
||||
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, array(
|
||||
'_controller' => 'Drupal\rest\RequestHandler::handle',
|
||||
),
|
||||
$this->getBaseRouteRequirements($method),
|
||||
array(),
|
||||
'',
|
||||
array(),
|
||||
// The HTTP method is a requirement for this route.
|
||||
array($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
web/core/modules/rest/src/Plugin/ResourceInterface.php
Normal file
53
web/core/modules/rest/src/Plugin/ResourceInterface.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
|
||||
/**
|
||||
* Specifies the publicly available methods of a resource plugin.
|
||||
*
|
||||
* @see \Drupal\rest\Annotation\RestResource
|
||||
* @see \Drupal\rest\Plugin\Type\ResourcePluginManager
|
||||
* @see \Drupal\rest\Plugin\ResourceBase
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup third_party
|
||||
*/
|
||||
interface ResourceInterface extends PluginInspectionInterface {
|
||||
|
||||
/**
|
||||
* Returns a collection of routes with URL path information for the resource.
|
||||
*
|
||||
* This method determines where a resource is reachable, what path
|
||||
* replacements are used, the required HTTP method for the operation etc.
|
||||
*
|
||||
* @return \Symfony\Component\Routing\RouteCollection
|
||||
* A collection of routes that should be registered for this resource.
|
||||
*/
|
||||
public function routes();
|
||||
|
||||
/**
|
||||
* Provides an array of permissions suitable for .permissions.yml files.
|
||||
*
|
||||
* A resource plugin can define a set of user permissions that are used on the
|
||||
* routes for this resource or for other purposes.
|
||||
*
|
||||
* It is not required for a resource plugin to specify permissions: if they
|
||||
* have their own access control mechanism, they can use that, and return the
|
||||
* empty array.
|
||||
*
|
||||
* @return array
|
||||
* The permission array.
|
||||
*/
|
||||
public function permissions();
|
||||
|
||||
/**
|
||||
* Returns the available HTTP request methods on this plugin.
|
||||
*
|
||||
* @return array
|
||||
* The list of supported methods. Example: array('GET', 'POST', 'PATCH').
|
||||
*/
|
||||
public function availableMethods();
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?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.
|
||||
*/
|
||||
public function getInstance(array $options){
|
||||
if (isset($options['id'])) {
|
||||
return $this->createInstance($options['id']);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,374 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Plugin\rest\resource;
|
||||
|
||||
use Drupal\Component\Plugin\DependentPluginInterface;
|
||||
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\rest\Plugin\ResourceBase;
|
||||
use Drupal\rest\ResourceResponse;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\rest\ModifiedResourceResponse;
|
||||
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}",
|
||||
* "https://www.drupal.org/link-relations/create" = "/entity/{entity_type}"
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class EntityResource extends ResourceBase implements DependentPluginInterface {
|
||||
|
||||
/**
|
||||
* The entity type targeted by this resource.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeInterface
|
||||
*/
|
||||
protected $entityType;
|
||||
|
||||
/**
|
||||
* The config factory.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, $serializer_formats, LoggerInterface $logger, ConfigFactoryInterface $config_factory) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);
|
||||
$this->entityType = $entity_type_manager->getDefinition($plugin_definition['entity_type']);
|
||||
$this->configFactory = $config_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.');
|
||||
}
|
||||
|
||||
if (!$entity->access('create')) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
$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');
|
||||
}
|
||||
|
||||
// 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'");
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the received data before saving.
|
||||
$this->validate($entity);
|
||||
try {
|
||||
$entity->save();
|
||||
$this->logger->notice('Created entity %type with ID %id.', array('%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.
|
||||
$url = $entity->urlInfo('canonical', ['absolute' => TRUE])->toString(TRUE);
|
||||
$response = new ModifiedResourceResponse($entity, 201, ['Location' => $url->getGeneratedUrl()]);
|
||||
return $response;
|
||||
}
|
||||
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');
|
||||
}
|
||||
if (!$original_entity->access('update')) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
// Overwrite the received properties.
|
||||
$entity_keys = $entity->getEntityType()->getKeys();
|
||||
foreach ($entity->_restSubmittedFields as $field_name) {
|
||||
$field = $entity->get($field_name);
|
||||
|
||||
// Entity key fields need special treatment: together they uniquely
|
||||
// identify the entity. Therefore it does not make sense to modify any of
|
||||
// them. However, rather than throwing an error, we just ignore them as
|
||||
// long as their specified values match their current values.
|
||||
if (in_array($field_name, $entity_keys, TRUE)) {
|
||||
// Unchanged values for entity keys don't need access checking.
|
||||
if ($original_entity->get($field_name)->getValue() === $entity->get($field_name)->getValue()) {
|
||||
continue;
|
||||
}
|
||||
// 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.
|
||||
elseif (isset($entity_keys['langcode']) && $field_name === $entity_keys['langcode'] && $field->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$original_entity->get($field_name)->access('edit')) {
|
||||
throw new AccessDeniedHttpException("Access denied on updating field '$field_name'.");
|
||||
}
|
||||
$original_entity->set($field_name, $field->getValue());
|
||||
}
|
||||
|
||||
// Validate the received data before saving.
|
||||
$this->validate($original_entity);
|
||||
try {
|
||||
$original_entity->save();
|
||||
$this->logger->notice('Updated entity %type with ID %id.', array('%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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
if (!$entity->access('delete')) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
try {
|
||||
$entity->delete();
|
||||
$this->logger->notice('Deleted entity %type with ID %id.', array('%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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the whole entity does not violate any validation constraints.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity object.
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
|
||||
* If validation errors are found.
|
||||
*/
|
||||
protected function validate(EntityInterface $entity) {
|
||||
// @todo Remove when https://www.drupal.org/node/2164373 is committed.
|
||||
if (!$entity instanceof FieldableEntityInterface) {
|
||||
return;
|
||||
}
|
||||
$violations = $entity->validate();
|
||||
|
||||
// Remove violations of inaccessible fields as they cannot stem from our
|
||||
// changes.
|
||||
$violations->filterByFieldAccess();
|
||||
|
||||
if (count($violations) > 0) {
|
||||
$message = "Unprocessable Entity: validation failed.\n";
|
||||
foreach ($violations as $violation) {
|
||||
$message .= $violation->getPropertyPath() . ': ' . $violation->getMessage() . "\n";
|
||||
}
|
||||
// Instead of returning a generic 400 response we use the more specific
|
||||
// 422 Unprocessable Entity code from RFC 4918. That way clients can
|
||||
// distinguish between general syntax errors in bad serializations (code
|
||||
// 400) and semantic errors in well-formed requests (code 422).
|
||||
throw new HttpException(422, $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') ?: array();
|
||||
$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()]];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
447
web/core/modules/rest/src/Plugin/views/display/RestExport.php
Normal file
447
web/core/modules/rest/src/Plugin/views/display/RestExport.php
Normal file
|
@ -0,0 +1,447 @@
|
|||
<?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\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;
|
||||
|
||||
/**
|
||||
* The renderer.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* The collector of authentication providers.
|
||||
*
|
||||
* @var \Drupal\Core\Authentication\AuthenticationCollectorInterface
|
||||
*/
|
||||
protected $authenticationCollector;
|
||||
|
||||
/**
|
||||
* The authentication providers, keyed by ID.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $authenticationProviders;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, RouteProviderInterface $route_provider, StateInterface $state, RendererInterface $renderer, array $authentication_providers) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $route_provider, $state);
|
||||
|
||||
$this->renderer = $renderer;
|
||||
$this->authenticationProviders = $authentication_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')
|
||||
|
||||
);
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function initDisplay(ViewExecutable $view, array &$display, array &$options = NULL) {
|
||||
parent::initDisplay($view, $display, $options);
|
||||
|
||||
$request_content_type = $this->view->getRequest()->getRequestFormat();
|
||||
// Only use the requested content type if it's not 'html'. If it is then
|
||||
// default to 'json' to aid debugging.
|
||||
// @todo Remove the need for this when we have better content negotiation.
|
||||
if ($request_content_type != 'html') {
|
||||
$this->setContentType($request_content_type);
|
||||
}
|
||||
// If the requested content type is 'html' and the default 'json' is not
|
||||
// selected as a format option in the view display, fallback to the first
|
||||
// format in the array.
|
||||
elseif (!empty($options['style']['options']['formats']) && !isset($options['style']['options']['formats'][$this->getContentType()])) {
|
||||
$this->setContentType(reset($options['style']['options']['formats']));
|
||||
}
|
||||
|
||||
$this->setMimeType($this->view->getRequest()->getMimeType($this->contentType));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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->authenticationProviders, $this->authenticationProviders);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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'] = array(
|
||||
'title' => $this->t('Path settings'),
|
||||
'column' => 'second',
|
||||
'build' => array(
|
||||
'#weight' => -10,
|
||||
),
|
||||
);
|
||||
|
||||
$options['path']['category'] = 'path';
|
||||
$options['path']['title'] = $this->t('Path');
|
||||
$options['auth'] = array(
|
||||
'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'] = array(
|
||||
'#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 appropiate 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']);
|
||||
|
||||
// Format as a string using pipes as a delimiter.
|
||||
if ($formats = $style_plugin->getFormats()) {
|
||||
// Allow a REST Export View to be returned with an HTML-only accept
|
||||
// format. That allows browsers or other non-compliant systems to access
|
||||
// the view, as it is unlikely to have a conflicting HTML representation
|
||||
// anyway.
|
||||
$route->setRequirement('_format', implode('|', $formats + ['html']));
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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 = array();
|
||||
$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']);
|
||||
}
|
||||
elseif ($this->view->getRequest()->getFormat($this->view->element['#content_type']) !== 'html') {
|
||||
// This display plugin is primarily 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
|
||||
// (checked above), 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::applyDisplayCachablityMetadata($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' => []];
|
||||
$modules = array_map(function ($authentication_provider) {
|
||||
return $this->authenticationProviders[$authentication_provider];
|
||||
}, $this->getOption('auth'));
|
||||
$dependencies['module'] = array_merge($dependencies['module'], $modules);
|
||||
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
}
|
118
web/core/modules/rest/src/Plugin/views/row/DataEntityRow.php
Normal file
118
web/core/modules/rest/src/Plugin/views/row/DataEntityRow.php
Normal file
|
@ -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
web/core/modules/rest/src/Plugin/views/row/DataFieldRow.php
Normal file
192
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 = array();
|
||||
|
||||
/**
|
||||
* Stores an array of options to determine if the raw field output is used.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $rawOutputOptions = array();
|
||||
|
||||
/**
|
||||
* {@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'] = array('default' => array());
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
parent::buildOptionsForm($form, $form_state);
|
||||
|
||||
$form['field_options'] = array(
|
||||
'#type' => 'table',
|
||||
'#header' => array($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'] = array(
|
||||
'#markup' => $id,
|
||||
);
|
||||
$form['field_options'][$id]['alias'] = array(
|
||||
'#title' => $this->t('Alias for @id', array('@id' => $id)),
|
||||
'#title_display' => 'invisible',
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => isset($options[$id]['alias']) ? $options[$id]['alias'] : '',
|
||||
'#element_validate' => array(array($this, 'validateAliasName')),
|
||||
);
|
||||
$form['field_options'][$id]['raw_output'] = array(
|
||||
'#title' => $this->t('Raw output for @id', array('@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(array('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 = array();
|
||||
|
||||
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
web/core/modules/rest/src/Plugin/views/style/Serializer.php
Normal file
205
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 = array();
|
||||
|
||||
/**
|
||||
* 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'] = array('default' => array());
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
parent::buildOptionsForm($form, $form_state);
|
||||
|
||||
$form['formats'] = array(
|
||||
'#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(array('style_options', 'formats'));
|
||||
$form_state->setValue(array('style_options', 'formats'), array_filter($formats));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
$rows = array();
|
||||
// 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);
|
||||
}
|
||||
|
||||
}
|
247
web/core/modules/rest/src/RequestHandler.php
Normal file
247
web/core/modules/rest/src/RequestHandler.php
Normal file
|
@ -0,0 +1,247 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Cache\CacheableResponseInterface;
|
||||
use Drupal\Core\Render\RenderContext;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
|
||||
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
|
||||
use Symfony\Component\Serializer\SerializerInterface;
|
||||
|
||||
/**
|
||||
* Acts as intermediate request forwarder for resource plugins.
|
||||
*/
|
||||
class RequestHandler implements ContainerAwareInterface, ContainerInjectionInterface {
|
||||
|
||||
use ContainerAwareTrait;
|
||||
|
||||
/**
|
||||
* The resource configuration storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $resourceStorage;
|
||||
|
||||
/**
|
||||
* Creates a new RequestHandler instance.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $entity_storage
|
||||
* The resource configuration storage.
|
||||
*/
|
||||
public function __construct(EntityStorageInterface $entity_storage) {
|
||||
$this->resourceStorage = $entity_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static($container->get('entity_type.manager')->getStorage('rest_resource_config'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a web API request.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The route match.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The HTTP request object.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
* The response object.
|
||||
*/
|
||||
public function handle(RouteMatchInterface $route_match, Request $request) {
|
||||
$method = strtolower($request->getMethod());
|
||||
|
||||
// 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 do the same as what the UrlMatcher does: map HEAD
|
||||
// requests to the logic for GET. This also guarantees response headers for
|
||||
// HEAD requests are identical to those for GET requests, because we just
|
||||
// return a GET response. 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()
|
||||
if ($method === 'head') {
|
||||
$method = 'get';
|
||||
}
|
||||
|
||||
$resource_config_id = $route_match->getRouteObject()->getDefault('_rest_resource_config');
|
||||
/** @var \Drupal\rest\RestResourceConfigInterface $resource_config */
|
||||
$resource_config = $this->resourceStorage->load($resource_config_id);
|
||||
$resource = $resource_config->getResourcePlugin();
|
||||
|
||||
// Deserialize incoming data if available.
|
||||
/** @var \Symfony\Component\Serializer\SerializerInterface $serializer */
|
||||
$serializer = $this->container->get('serializer');
|
||||
$received = $request->getContent();
|
||||
$unserialized = NULL;
|
||||
if (!empty($received)) {
|
||||
$format = $request->getContentType();
|
||||
|
||||
// Only allow serialization formats that are explicitly configured. If no
|
||||
// formats are configured allow all and hope that the serializer knows the
|
||||
// format. If the serializer cannot handle it an exception will be thrown
|
||||
// that bubbles up to the client.
|
||||
$request_method = $request->getMethod();
|
||||
if (in_array($format, $resource_config->getFormats($request_method))) {
|
||||
$definition = $resource->getPluginDefinition();
|
||||
try {
|
||||
if (!empty($definition['serialization_class'])) {
|
||||
$unserialized = $serializer->deserialize($received, $definition['serialization_class'], $format, array('request_method' => $method));
|
||||
}
|
||||
// If the plugin does not specify a serialization class just decode
|
||||
// the received data.
|
||||
else {
|
||||
$unserialized = $serializer->decode($received, $format, array('request_method' => $method));
|
||||
}
|
||||
}
|
||||
catch (UnexpectedValueException $e) {
|
||||
$error['error'] = $e->getMessage();
|
||||
$content = $serializer->serialize($error, $format);
|
||||
return new Response($content, 400, array('Content-Type' => $request->getMimeType($format)));
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new UnsupportedMediaTypeHttpException();
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the request parameters that should be passed to the resource
|
||||
// plugin.
|
||||
$route_parameters = $route_match->getParameters();
|
||||
$parameters = array();
|
||||
// Filter out all internal parameters starting with "_".
|
||||
foreach ($route_parameters as $key => $parameter) {
|
||||
if ($key{0} !== '_') {
|
||||
$parameters[] = $parameter;
|
||||
}
|
||||
}
|
||||
|
||||
// Invoke the operation on the resource plugin.
|
||||
$format = $this->getResponseFormat($route_match, $request);
|
||||
$response = call_user_func_array(array($resource, $method), array_merge($parameters, array($unserialized, $request)));
|
||||
|
||||
return $response instanceof ResourceResponseInterface ?
|
||||
$this->renderResponse($request, $response, $serializer, $format, $resource_config) :
|
||||
$response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the format to respond in.
|
||||
*
|
||||
* Respects the requested format if one is specified. However, it is common to
|
||||
* forget to specify a request 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.
|
||||
*/
|
||||
protected function getResponseFormat(RouteMatchInterface $route_match, Request $request) {
|
||||
$route = $route_match->getRouteObject();
|
||||
$acceptable_request_formats = $route->hasRequirement('_format') ? explode('|', $route->getRequirement('_format')) : [];
|
||||
$acceptable_content_type_formats = $route->hasRequirement('_content_type_format') ? explode('|', $route->getRequirement('_content_type_format')) : [];
|
||||
$acceptable_formats = $request->isMethodSafe() ? $acceptable_request_formats : $acceptable_content_type_formats;
|
||||
|
||||
$requested_format = $request->getRequestFormat();
|
||||
$content_type_format = $request->getContentType();
|
||||
|
||||
// If an acceptable format is requested, then use that. Otherwise, including
|
||||
// and particularly when the client forgot to specify a format, then use
|
||||
// heuristics to select the format that is most likely expected.
|
||||
if (in_array($requested_format, $acceptable_formats)) {
|
||||
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.
|
||||
elseif (!empty($request->getContent()) && in_array($content_type_format, $acceptable_content_type_formats)) {
|
||||
return $content_type_format;
|
||||
}
|
||||
// Otherwise, use the first acceptable format.
|
||||
elseif (!empty($acceptable_formats)) {
|
||||
return $acceptable_formats[0];
|
||||
}
|
||||
// Sometimes, there are no acceptable formats, e.g. DELETE routes.
|
||||
else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a resource response.
|
||||
*
|
||||
* Serialization can invoke rendering (e.g., generating URLs), but the
|
||||
* serialization API does not provide a mechanism to collect the
|
||||
* bubbleable metadata associated with that (e.g., language and other
|
||||
* contexts), so instead, allow those to "leak" and collect them here 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.
|
||||
* @param \Drupal\rest\RestResourceConfigInterface $resource_config
|
||||
* The resource config.
|
||||
*
|
||||
* @return \Drupal\rest\ResourceResponse
|
||||
* The altered response.
|
||||
*
|
||||
* @todo Add test coverage for language negotiation contexts in
|
||||
* https://www.drupal.org/node/2135829.
|
||||
*/
|
||||
protected function renderResponse(Request $request, ResourceResponseInterface $response, SerializerInterface $serializer, $format, RestResourceConfigInterface $resource_config) {
|
||||
$data = $response->getResponseData();
|
||||
|
||||
if ($response instanceof CacheableResponseInterface) {
|
||||
// Add rest config's cache tags.
|
||||
$response->addCacheableDependency($resource_config);
|
||||
}
|
||||
|
||||
// If there is data to send, serialize and set it as the response body.
|
||||
if ($data !== NULL) {
|
||||
if ($response instanceof CacheableResponseInterface) {
|
||||
$context = new RenderContext();
|
||||
$output = $this->container->get('renderer')
|
||||
->executeInRenderContext($context, function () use ($serializer, $data, $format) {
|
||||
return $serializer->serialize($data, $format);
|
||||
});
|
||||
|
||||
if (!$context->isEmpty()) {
|
||||
$response->addCacheableDependency($context->pop());
|
||||
}
|
||||
}
|
||||
else {
|
||||
$output = $serializer->serialize($data, $format);
|
||||
}
|
||||
|
||||
$response->setContent($output);
|
||||
$response->headers->set('Content-Type', $request->getMimeType($format));
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
}
|
39
web/core/modules/rest/src/ResourceResponse.php
Normal file
39
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 = array()) {
|
||||
$this->responseData = $data;
|
||||
parent::__construct('', $status, $headers);
|
||||
}
|
||||
|
||||
}
|
18
web/core/modules/rest/src/ResourceResponseInterface.php
Normal file
18
web/core/modules/rest/src/ResourceResponseInterface.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest;
|
||||
|
||||
/**
|
||||
* Defines a common interface for resource responses.
|
||||
*/
|
||||
interface ResourceResponseInterface {
|
||||
|
||||
/**
|
||||
* Returns response data that should be serialized.
|
||||
*
|
||||
* @return mixed
|
||||
* Response data that should be serialized.
|
||||
*/
|
||||
public function getResponseData();
|
||||
|
||||
}
|
25
web/core/modules/rest/src/ResourceResponseTrait.php
Normal file
25
web/core/modules/rest/src/ResourceResponseTrait.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?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
web/core/modules/rest/src/RestPermissions.php
Normal file
65
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;
|
||||
}
|
||||
|
||||
}
|
61
web/core/modules/rest/src/RestResourceConfigInterface.php
Normal file
61
web/core/modules/rest/src/RestResourceConfigInterface.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
||||
use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
|
||||
|
||||
/**
|
||||
* Defines a configuration entity to store enabled REST resources.
|
||||
*/
|
||||
interface RestResourceConfigInterface extends ConfigEntityInterface, EntityWithPluginCollectionInterface {
|
||||
|
||||
/**
|
||||
* Granularity value for per-method configuration.
|
||||
*/
|
||||
const METHOD_GRANULARITY = 'method';
|
||||
|
||||
/**
|
||||
* Granularity value for per-resource configuration.
|
||||
*/
|
||||
const RESOURCE_GRANULARITY = 'resource';
|
||||
|
||||
/**
|
||||
* Retrieves the REST resource plugin.
|
||||
*
|
||||
* @return \Drupal\rest\Plugin\ResourceInterface
|
||||
* The resource plugin
|
||||
*/
|
||||
public function getResourcePlugin();
|
||||
|
||||
/**
|
||||
* Retrieves a list of supported HTTP methods.
|
||||
*
|
||||
* @return string[]
|
||||
* A list of supported HTTP methods.
|
||||
*/
|
||||
public function getMethods();
|
||||
|
||||
/**
|
||||
* Retrieves a list of supported authentication providers.
|
||||
*
|
||||
* @param string $method
|
||||
* The request method e.g GET or POST.
|
||||
*
|
||||
* @return string[]
|
||||
* A list of supported authentication provider IDs.
|
||||
*/
|
||||
public function getAuthenticationProviders($method);
|
||||
|
||||
/**
|
||||
* Retrieves a list of supported response formats.
|
||||
*
|
||||
* @param string $method
|
||||
* The request method e.g GET or POST.
|
||||
*
|
||||
* @return string[]
|
||||
* A list of supported format IDs.
|
||||
*/
|
||||
public function getFormats($method);
|
||||
|
||||
}
|
126
web/core/modules/rest/src/Routing/ResourceRoutes.php
Normal file
126
web/core/modules/rest/src/Routing/ResourceRoutes.php
Normal file
|
@ -0,0 +1,126 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Routing;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Routing\RouteSubscriberBase;
|
||||
use Drupal\rest\Plugin\Type\ResourcePluginManager;
|
||||
use Drupal\rest\RestResourceConfigInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Subscriber for REST-style routes.
|
||||
*/
|
||||
class ResourceRoutes extends RouteSubscriberBase {
|
||||
|
||||
/**
|
||||
* 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 \Symfony\Component\Routing\RouteCollection $collection
|
||||
* The route collection for adding routes.
|
||||
* @return array
|
||||
*/
|
||||
protected function alterRoutes(RouteCollection $collection) {
|
||||
// Iterate over all enabled REST resource configs.
|
||||
/** @var \Drupal\rest\RestResourceConfigInterface[] $resource_configs */
|
||||
$resource_configs = $this->resourceConfigStorage->loadMultiple();
|
||||
// Iterate over all enabled resource plugins.
|
||||
foreach ($resource_configs as $resource_config) {
|
||||
$resource_routes = $this->getRoutesForResourceConfig($resource_config);
|
||||
$collection->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 where the method is enabled in the configuration.
|
||||
if ($methods && ($method = $methods[0]) && $supported_formats = $rest_resource_config->getFormats($method)) {
|
||||
$route->setRequirement('_csrf_request_header_token', 'TRUE');
|
||||
|
||||
// Check that authentication providers are defined.
|
||||
if (empty($rest_resource_config->getAuthenticationProviders($method))) {
|
||||
$this->logger->error('At least one authentication provider must be defined for resource @id', array(':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', array(':id' => $rest_resource_config->id()));
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the route has a format requirement, then verify that the
|
||||
// resource has it.
|
||||
$format_requirement = $route->getRequirement('_format');
|
||||
if ($format_requirement && !in_array($format_requirement, $rest_resource_config->getFormats($method))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The configuration seems legit at this point, so we set the
|
||||
// authentication provider and add the route.
|
||||
$route->setOption('_auth', $rest_resource_config->getAuthenticationProviders($method));
|
||||
$route->setDefault('_rest_resource_config', $rest_resource_config->id());
|
||||
$collection->add("rest.$name", $route);
|
||||
}
|
||||
|
||||
}
|
||||
return $collection;
|
||||
}
|
||||
|
||||
}
|
553
web/core/modules/rest/src/Tests/RESTTestBase.php
Normal file
553
web/core/modules/rest/src/Tests/RESTTestBase.php
Normal file
|
@ -0,0 +1,553 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Tests;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityType;
|
||||
use Drupal\node\NodeInterface;
|
||||
use Drupal\rest\RestResourceConfigInterface;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* 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 = array('rest', 'entity_test');
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->defaultFormat = 'hal_json';
|
||||
$this->defaultMimeType = 'application/hal+json';
|
||||
$this->defaultAuth = array('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(array('name' => 'resttest', 'type' => 'resttest'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, array('GET', 'HEAD', 'OPTIONS', 'TRACE'))) {
|
||||
// GET the CSRF token first for writing requests.
|
||||
$requested_token = $this->drupalGet('session/token');
|
||||
}
|
||||
|
||||
$url = $this->buildUrl($url);
|
||||
|
||||
$curl_options = array();
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
// Set query if there are additional GET parameters.
|
||||
$curl_options = array(
|
||||
CURLOPT_HTTPGET => TRUE,
|
||||
CURLOPT_CUSTOMREQUEST => 'GET',
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_NOBODY => FALSE,
|
||||
CURLOPT_HTTPHEADER => array('Accept: ' . $mime_type),
|
||||
);
|
||||
break;
|
||||
|
||||
case 'HEAD':
|
||||
$curl_options = array(
|
||||
CURLOPT_HTTPGET => FALSE,
|
||||
CURLOPT_CUSTOMREQUEST => 'HEAD',
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_NOBODY => TRUE,
|
||||
CURLOPT_HTTPHEADER => array('Accept: ' . $mime_type),
|
||||
);
|
||||
break;
|
||||
|
||||
case 'POST':
|
||||
$curl_options = array(
|
||||
CURLOPT_HTTPGET => FALSE,
|
||||
CURLOPT_POST => TRUE,
|
||||
CURLOPT_POSTFIELDS => $body,
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_NOBODY => FALSE,
|
||||
CURLOPT_HTTPHEADER => $csrf_token !== FALSE ? array(
|
||||
'Content-Type: ' . $mime_type,
|
||||
'X-CSRF-Token: ' . ($csrf_token === NULL ? $requested_token : $csrf_token),
|
||||
) : array(
|
||||
'Content-Type: ' . $mime_type,
|
||||
),
|
||||
);
|
||||
break;
|
||||
|
||||
case 'PUT':
|
||||
$curl_options = array(
|
||||
CURLOPT_HTTPGET => FALSE,
|
||||
CURLOPT_CUSTOMREQUEST => 'PUT',
|
||||
CURLOPT_POSTFIELDS => $body,
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_NOBODY => FALSE,
|
||||
CURLOPT_HTTPHEADER => $csrf_token !== FALSE ? array(
|
||||
'Content-Type: ' . $mime_type,
|
||||
'X-CSRF-Token: ' . ($csrf_token === NULL ? $requested_token : $csrf_token),
|
||||
) : array(
|
||||
'Content-Type: ' . $mime_type,
|
||||
),
|
||||
);
|
||||
break;
|
||||
|
||||
case 'PATCH':
|
||||
$curl_options = array(
|
||||
CURLOPT_HTTPGET => FALSE,
|
||||
CURLOPT_CUSTOMREQUEST => 'PATCH',
|
||||
CURLOPT_POSTFIELDS => $body,
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_NOBODY => FALSE,
|
||||
CURLOPT_HTTPHEADER => $csrf_token !== FALSE ? array(
|
||||
'Content-Type: ' . $mime_type,
|
||||
'X-CSRF-Token: ' . ($csrf_token === NULL ? $requested_token : $csrf_token),
|
||||
) : array(
|
||||
'Content-Type: ' . $mime_type,
|
||||
),
|
||||
);
|
||||
break;
|
||||
|
||||
case 'DELETE':
|
||||
$curl_options = array(
|
||||
CURLOPT_HTTPGET => FALSE,
|
||||
CURLOPT_CUSTOMREQUEST => 'DELETE',
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_NOBODY => FALSE,
|
||||
CURLOPT_HTTPHEADER => $csrf_token !== FALSE ? array(
|
||||
'X-CSRF-Token: ' . ($csrf_token === NULL ? $requested_token : $csrf_token),
|
||||
) : array(),
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
if ($mime_type === 'none') {
|
||||
unset($curl_options[CURLOPT_HTTPHEADER]['Content-Type']);
|
||||
}
|
||||
|
||||
$this->responseBody = $this->curlExec($curl_options);
|
||||
|
||||
// Ensure that any changes to variables in the other thread are picked up.
|
||||
$this->refreshVariables();
|
||||
|
||||
$headers = $this->drupalGetHeaders();
|
||||
|
||||
$this->verbose($method . ' request to: ' . $url .
|
||||
'<hr />Code: ' . curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE) .
|
||||
(isset($curl_options[CURLOPT_HTTPHEADER]) ? '<hr />Request headers: ' . nl2br(print_r($curl_options[CURLOPT_HTTPHEADER], TRUE)) : '' ) .
|
||||
(isset($curl_options[CURLOPT_POSTFIELDS]) ? '<hr />Request body: ' . nl2br(print_r($curl_options[CURLOPT_POSTFIELDS], TRUE)) : '' ) .
|
||||
'<hr />Response headers: ' . nl2br(print_r($headers, TRUE)) .
|
||||
'<hr />Response body: ' . $this->responseBody);
|
||||
|
||||
return $this->responseBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 array(
|
||||
'name' => $this->randomMachineName(),
|
||||
'user_id' => 1,
|
||||
'field_test_text' => array(0 => array(
|
||||
'value' => $this->randomString(),
|
||||
'format' => 'plain_text',
|
||||
)),
|
||||
);
|
||||
case 'config_test':
|
||||
return [
|
||||
'id' => $this->randomMachineName(),
|
||||
'label' => 'Test label',
|
||||
];
|
||||
case 'node':
|
||||
return array('title' => $this->randomString(), 'type' => 'resttest');
|
||||
case 'node_type':
|
||||
return array(
|
||||
'type' => 'article',
|
||||
'name' => $this->randomMachineName(),
|
||||
);
|
||||
case 'user':
|
||||
return array('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(),
|
||||
];
|
||||
default:
|
||||
if ($this->isConfigEntity($entity_type_id)) {
|
||||
return $this->configEntityValues($entity_type_id);
|
||||
}
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
// Rebuild routing cache, so that the REST API paths are available.
|
||||
$this->container->get('router.builder')->rebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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) {
|
||||
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 array('view test entity');
|
||||
case 'create':
|
||||
case 'update':
|
||||
case 'delete':
|
||||
return array('administer entity_test content');
|
||||
}
|
||||
case 'node':
|
||||
switch ($operation) {
|
||||
case 'view':
|
||||
return array('access content');
|
||||
case 'create':
|
||||
return array('create resttest content');
|
||||
case 'update':
|
||||
return array('edit any resttest content');
|
||||
case 'delete':
|
||||
return array('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\Utility\SafeMarkup::format() 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).', array('@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;
|
||||
}
|
||||
|
||||
}
|
130
web/core/modules/rest/src/Tests/ResourceTest.php
Normal file
130
web/core/modules/rest/src/Tests/ResourceTest.php
Normal file
|
@ -0,0 +1,130 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Tests;
|
||||
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\rest\RestResourceConfigInterface;
|
||||
use Drupal\user\Entity\Role;
|
||||
use Drupal\user\RoleInterface;
|
||||
|
||||
/**
|
||||
* Tests the structure of a REST resource.
|
||||
*
|
||||
* @group rest
|
||||
*/
|
||||
class ResourceTest extends RESTTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('hal', 'rest', 'entity_test', 'rest_test');
|
||||
|
||||
/**
|
||||
* The entity.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
// Create an entity programmatic.
|
||||
$this->entity = $this->entityCreate('entity_test');
|
||||
$this->entity->save();
|
||||
|
||||
Role::load(AccountInterface::ANONYMOUS_ROLE)
|
||||
->grantPermission('view test entity')
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a resource without formats cannot be enabled.
|
||||
*/
|
||||
public function testFormats() {
|
||||
$this->resourceConfigStorage->create([
|
||||
'id' => 'entity.entity_test',
|
||||
'granularity' => RestResourceConfigInterface::METHOD_GRANULARITY,
|
||||
'configuration' => [
|
||||
'GET' => [
|
||||
'supported_auth' => [
|
||||
'basic_auth',
|
||||
],
|
||||
],
|
||||
],
|
||||
])->save();
|
||||
|
||||
// Verify that accessing the resource returns 406.
|
||||
$response = $this->httpRequest($this->entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET');
|
||||
// \Drupal\Core\Routing\RequestFormatRouteFilter considers the canonical,
|
||||
// non-REST route a match, but a lower quality one: no format restrictions
|
||||
// means there's always a match and hence when there is no matching REST
|
||||
// route, the non-REST route is used, but can't render into
|
||||
// application/hal+json, so it returns a 406.
|
||||
$this->assertResponse('406', 'HTTP response code is 406 when the resource does not define formats, because it falls back to the canonical, non-REST route.');
|
||||
$this->curlClose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a resource without authentication cannot be enabled.
|
||||
*/
|
||||
public function testAuthentication() {
|
||||
$this->resourceConfigStorage->create([
|
||||
'id' => 'entity.entity_test',
|
||||
'granularity' => RestResourceConfigInterface::METHOD_GRANULARITY,
|
||||
'configuration' => [
|
||||
'GET' => [
|
||||
'supported_formats' => [
|
||||
'hal_json',
|
||||
],
|
||||
],
|
||||
],
|
||||
])->save();
|
||||
|
||||
// Verify that accessing the resource returns 401.
|
||||
$response = $this->httpRequest($this->entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET');
|
||||
// \Drupal\Core\Routing\RequestFormatRouteFilter considers the canonical,
|
||||
// non-REST route a match, but a lower quality one: no format restrictions
|
||||
// means there's always a match and hence when there is no matching REST
|
||||
// route, the non-REST route is used, but can't render into
|
||||
// application/hal+json, so it returns a 406.
|
||||
$this->assertResponse('406', 'HTTP response code is 406 when the resource does not define formats, because it falls back to the canonical, non-REST route.');
|
||||
$this->curlClose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that serialization_class is optional.
|
||||
*/
|
||||
public function testSerializationClassIsOptional() {
|
||||
$this->enableService('serialization_test', 'POST', 'json');
|
||||
|
||||
Role::load(RoleInterface::ANONYMOUS_ID)
|
||||
->grantPermission('restful post serialization_test')
|
||||
->save();
|
||||
|
||||
$serialized = $this->container->get('serializer')->serialize(['foo', 'bar'], 'json');
|
||||
$this->httpRequest('serialization_test', 'POST', $serialized, 'application/json');
|
||||
$this->assertResponse(200);
|
||||
$this->assertResponseBody('["foo","bar"]');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that resource URI paths are formatted properly.
|
||||
*/
|
||||
public function testUriPaths() {
|
||||
$this->enableService('entity:entity_test');
|
||||
/** @var \Drupal\rest\Plugin\Type\ResourcePluginManager $manager */
|
||||
$manager = \Drupal::service('plugin.manager.rest');
|
||||
|
||||
foreach ($manager->getDefinitions() as $resource => $definition) {
|
||||
foreach ($definition['uri_paths'] as $key => $uri_path) {
|
||||
$this->assertFalse(strpos($uri_path, '//'), 'The resource URI path does not have duplicate slashes.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Tests\Update;
|
||||
|
||||
use Drupal\system\Tests\Update\UpdatePathTestBase;
|
||||
|
||||
/**
|
||||
* Tests that existing sites continue to use permissions for EntityResource.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2664780
|
||||
*
|
||||
* @group rest
|
||||
*/
|
||||
class EntityResourcePermissionsUpdateTest extends UpdatePathTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['rest', 'serialization'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setDatabaseDumpFiles() {
|
||||
$this->databaseDumpFiles = [
|
||||
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
|
||||
__DIR__ . '/../../../../rest/tests/fixtures/update/drupal-8.rest-rest_update_8203.php',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests rest_update_8203().
|
||||
*/
|
||||
public function testBcEntityResourcePermissionSettingAdded() {
|
||||
$permission_handler = $this->container->get('user.permissions');
|
||||
|
||||
$is_rest_resource_permission = function ($permission) {
|
||||
return $permission['provider'] === 'rest' && (string) $permission['title'] !== 'Administer REST resource configuration';
|
||||
};
|
||||
|
||||
// Make sure we have the expected values before the update.
|
||||
$rest_settings = $this->config('rest.settings');
|
||||
$this->assertFalse(array_key_exists('bc_entity_resource_permissions', $rest_settings->getRawData()));
|
||||
$this->assertEqual([], array_filter($permission_handler->getPermissions(), $is_rest_resource_permission));
|
||||
|
||||
$this->runUpdates();
|
||||
|
||||
// Make sure we have the expected values after the update.
|
||||
$rest_settings = $this->config('rest.settings');
|
||||
$this->assertTrue(array_key_exists('bc_entity_resource_permissions', $rest_settings->getRawData()));
|
||||
$this->assertTrue($rest_settings->get('bc_entity_resource_permissions'));
|
||||
$rest_permissions = array_keys(array_filter($permission_handler->getPermissions(), $is_rest_resource_permission));
|
||||
$this->assertEqual(['restful delete entity:node', 'restful get entity:node', 'restful patch entity:node', 'restful post entity:node'], $rest_permissions);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Tests\Update;
|
||||
|
||||
use Drupal\system\Tests\Update\UpdatePathTestBase;
|
||||
|
||||
/**
|
||||
* Tests method-granularity REST config is simplified to resource-granularity.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2721595
|
||||
* @see rest_post_update_resource_granularity()
|
||||
*
|
||||
* @group rest
|
||||
*/
|
||||
class ResourceGranularityUpdateTest extends UpdatePathTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['rest', 'serialization'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setDatabaseDumpFiles() {
|
||||
$this->databaseDumpFiles = [
|
||||
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
|
||||
__DIR__ . '/../../../../rest/tests/fixtures/update/drupal-8.rest-rest_post_update_resource_granularity.php',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests rest_post_update_simplify_resource_granularity().
|
||||
*/
|
||||
public function testMethodGranularityConvertedToResourceGranularity() {
|
||||
/** @var \Drupal\Core\Entity\EntityStorageInterface $resource_config_storage */
|
||||
$resource_config_storage = $this->container->get('entity_type.manager')->getStorage('rest_resource_config');
|
||||
|
||||
// Make sure we have the expected values before the update.
|
||||
$resource_config_entities = $resource_config_storage->loadMultiple();
|
||||
$this->assertIdentical(['entity.comment', 'entity.node', 'entity.user'], array_keys($resource_config_entities));
|
||||
$this->assertIdentical('method', $resource_config_entities['entity.node']->get('granularity'));
|
||||
$this->assertIdentical('method', $resource_config_entities['entity.comment']->get('granularity'));
|
||||
$this->assertIdentical('method', $resource_config_entities['entity.user']->get('granularity'));
|
||||
|
||||
// Read the existing 'entity:comment' and 'entity:user' resource
|
||||
// configuration so we can verify it after the update.
|
||||
$comment_resource_configuration = $resource_config_entities['entity.comment']->get('configuration');
|
||||
$user_resource_configuration = $resource_config_entities['entity.user']->get('configuration');
|
||||
|
||||
$this->runUpdates();
|
||||
|
||||
// Make sure we have the expected values after the update.
|
||||
$resource_config_entities = $resource_config_storage->loadMultiple();
|
||||
$this->assertIdentical(['entity.comment', 'entity.node', 'entity.user'], array_keys($resource_config_entities));
|
||||
// 'entity:node' should be updated.
|
||||
$this->assertIdentical('resource', $resource_config_entities['entity.node']->get('granularity'));
|
||||
$this->assertidentical($resource_config_entities['entity.node']->get('configuration'), [
|
||||
'methods' => ['GET', 'POST', 'PATCH', 'DELETE'],
|
||||
'formats' => ['hal_json'],
|
||||
'authentication' => ['basic_auth'],
|
||||
]);
|
||||
// 'entity:comment' should be unchanged.
|
||||
$this->assertIdentical('method', $resource_config_entities['entity.comment']->get('granularity'));
|
||||
$this->assertIdentical($comment_resource_configuration, $resource_config_entities['entity.comment']->get('configuration'));
|
||||
// 'entity:user' should be unchanged.
|
||||
$this->assertIdentical('method', $resource_config_entities['entity.user']->get('granularity'));
|
||||
$this->assertIdentical($user_resource_configuration, $resource_config_entities['entity.user']->get('configuration'));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Tests\Update;
|
||||
|
||||
use Drupal\rest\RestResourceConfigInterface;
|
||||
use Drupal\system\Tests\Update\UpdatePathTestBase;
|
||||
|
||||
/**
|
||||
* Tests that rest.settings is converted to rest_resource_config entities.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2308745
|
||||
* @see rest_update_8201()
|
||||
* @see rest_post_update_create_rest_resource_config_entities()
|
||||
*
|
||||
* @group rest
|
||||
*/
|
||||
class RestConfigurationEntitiesUpdateTest extends UpdatePathTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['rest', 'serialization'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setDatabaseDumpFiles() {
|
||||
$this->databaseDumpFiles = [
|
||||
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
|
||||
__DIR__ . '/../../../../rest/tests/fixtures/update/drupal-8.rest-rest_update_8201.php',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests rest_update_8201().
|
||||
*/
|
||||
public function testResourcesConvertedToConfigEntities() {
|
||||
/** @var \Drupal\Core\Entity\EntityStorageInterface $resource_config_storage */
|
||||
$resource_config_storage = $this->container->get('entity_type.manager')->getStorage('rest_resource_config');
|
||||
|
||||
// Make sure we have the expected values before the update.
|
||||
$rest_settings = $this->config('rest.settings');
|
||||
$this->assertTrue(array_key_exists('resources', $rest_settings->getRawData()));
|
||||
$this->assertTrue(array_key_exists('entity:node', $rest_settings->getRawData()['resources']));
|
||||
$resource_config_entities = $resource_config_storage->loadMultiple();
|
||||
$this->assertIdentical([], array_keys($resource_config_entities));
|
||||
|
||||
$this->runUpdates();
|
||||
|
||||
// Make sure we have the expected values after the update.
|
||||
$rest_settings = $this->config('rest.settings');
|
||||
$this->assertFalse(array_key_exists('resources', $rest_settings->getRawData()));
|
||||
$resource_config_entities = $resource_config_storage->loadMultiple();
|
||||
$this->assertIdentical(['entity.node'], array_keys($resource_config_entities));
|
||||
$node_resource_config_entity = $resource_config_entities['entity.node'];
|
||||
$this->assertIdentical(RestResourceConfigInterface::RESOURCE_GRANULARITY, $node_resource_config_entity->get('granularity'));
|
||||
$this->assertIdentical([
|
||||
'methods' => ['GET'],
|
||||
'formats' => ['json'],
|
||||
'authentication' => ['basic_auth'],
|
||||
], $node_resource_config_entity->get('configuration'));
|
||||
$this->assertIdentical(['module' => ['basic_auth', 'node', 'serialization']], $node_resource_config_entity->getDependencies());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Tests\Update;
|
||||
|
||||
use Drupal\system\Tests\Update\UpdatePathTestBase;
|
||||
|
||||
/**
|
||||
* Ensures that update hook is run properly for REST Export config.
|
||||
*
|
||||
* @group Update
|
||||
*/
|
||||
class RestExportAuthUpdateTest extends UpdatePathTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setDatabaseDumpFiles() {
|
||||
$this->databaseDumpFiles = [
|
||||
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
|
||||
__DIR__ . '/../../../tests/fixtures/update/rest-export-with-authentication.php',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that update hook is run for rest module.
|
||||
*/
|
||||
public function testUpdate() {
|
||||
$this->runUpdates();
|
||||
|
||||
// Get particular view.
|
||||
$view = \Drupal::entityTypeManager()->getStorage('view')->load('rest_export_with_authorization');
|
||||
$displays = $view->get('display');
|
||||
$this->assertIdentical($displays['rest_export_1']['display_options']['auth']['basic_auth'], 'basic_auth', 'Basic authentication is set as authentication method.');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Tests\Views;
|
||||
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\views\Tests\ViewTestBase;
|
||||
use Drupal\views\Tests\ViewTestData;
|
||||
use Drupal\views\Views;
|
||||
|
||||
/**
|
||||
* Tests the display of an excluded field that is used as a token.
|
||||
*
|
||||
* @group rest
|
||||
* @see \Drupal\rest\Plugin\views\display\RestExport
|
||||
* @see \Drupal\rest\Plugin\views\row\DataFieldRow
|
||||
*/
|
||||
class ExcludedFieldTokenTest extends ViewTestBase {
|
||||
|
||||
/**
|
||||
* @var \Drupal\views\ViewExecutable
|
||||
*/
|
||||
protected $view;
|
||||
|
||||
/**
|
||||
* The views that are used by this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $testViews = ['test_excluded_field_token_display'];
|
||||
|
||||
/**
|
||||
* The modules that need to be installed for this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = [
|
||||
'entity_test',
|
||||
'rest_test_views',
|
||||
'node',
|
||||
'field',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
ViewTestData::createTestViews(get_class($this), ['rest_test_views']);
|
||||
|
||||
// Create some test content.
|
||||
for ($i = 1; $i <= 10; $i++) {
|
||||
Node::create([
|
||||
'type' => 'article',
|
||||
'title' => 'Article test ' . $i,
|
||||
])->save();
|
||||
}
|
||||
|
||||
$this->enableViewsTestModule();
|
||||
|
||||
$this->view = Views::getView('test_excluded_field_token_display');
|
||||
$this->view->setDisplay('rest_export_1');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the display of an excluded title field when used as a token.
|
||||
*/
|
||||
public function testExcludedTitleTokenDisplay() {
|
||||
$actual_json = $this->drupalGetWithFormat($this->view->getPath(), 'json');
|
||||
$this->assertResponse(200);
|
||||
|
||||
$expected = [
|
||||
['nothing' => 'Article test 10'],
|
||||
['nothing' => 'Article test 9'],
|
||||
['nothing' => 'Article test 8'],
|
||||
['nothing' => 'Article test 7'],
|
||||
['nothing' => 'Article test 6'],
|
||||
['nothing' => 'Article test 5'],
|
||||
['nothing' => 'Article test 4'],
|
||||
['nothing' => 'Article test 3'],
|
||||
['nothing' => 'Article test 2'],
|
||||
['nothing' => 'Article test 1'],
|
||||
];
|
||||
$this->assertIdentical($actual_json, json_encode($expected));
|
||||
}
|
||||
|
||||
|
||||
}
|
838
web/core/modules/rest/src/Tests/Views/StyleSerializerTest.php
Normal file
838
web/core/modules/rest/src/Tests/Views/StyleSerializerTest.php
Normal file
|
@ -0,0 +1,838 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Tests\Views;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
|
||||
use Drupal\views\Entity\View;
|
||||
use Drupal\views\Plugin\views\display\DisplayPluginBase;
|
||||
use Drupal\views\Views;
|
||||
use Drupal\views\Tests\Plugin\PluginTestBase;
|
||||
use Drupal\views\Tests\ViewTestData;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Tests the serializer style plugin.
|
||||
*
|
||||
* @group rest
|
||||
* @see \Drupal\rest\Plugin\views\display\RestExport
|
||||
* @see \Drupal\rest\Plugin\views\style\Serializer
|
||||
* @see \Drupal\rest\Plugin\views\row\DataEntityRow
|
||||
* @see \Drupal\rest\Plugin\views\row\DataFieldRow
|
||||
*/
|
||||
class StyleSerializerTest extends PluginTestBase {
|
||||
|
||||
use AssertPageCacheContextsAndTagsTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $dumpHeaders = TRUE;
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('views_ui', 'entity_test', 'hal', 'rest_test_views', 'node', 'text', 'field', 'language', 'basic_auth');
|
||||
|
||||
/**
|
||||
* Views used by this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $testViews = array('test_serializer_display_field', 'test_serializer_display_entity', 'test_serializer_display_entity_translated', 'test_serializer_node_display_field', 'test_serializer_node_exposed_filter');
|
||||
|
||||
/**
|
||||
* A user with administrative privileges to look at test entity and configure views.
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
ViewTestData::createTestViews(get_class($this), array('rest_test_views'));
|
||||
|
||||
$this->adminUser = $this->drupalCreateUser(array('administer views', 'administer entity_test content', 'access user profiles', 'view test entity'));
|
||||
|
||||
// Save some entity_test entities.
|
||||
for ($i = 1; $i <= 10; $i++) {
|
||||
EntityTest::create(array('name' => 'test_' . $i, 'user_id' => $this->adminUser->id()))->save();
|
||||
}
|
||||
|
||||
$this->enableViewsTestModule();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the auth options restricts access to a REST views display.
|
||||
*/
|
||||
public function testRestViewsAuthentication() {
|
||||
// Assume the view is hidden behind a permission.
|
||||
$this->drupalGetWithFormat('test/serialize/auth_with_perm', 'json');
|
||||
$this->assertResponse(401);
|
||||
|
||||
// Not even logging in would make it possible to see the view, because then
|
||||
// we are denied based on authentication method (cookie).
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalGetWithFormat('test/serialize/auth_with_perm', 'json');
|
||||
$this->assertResponse(403);
|
||||
$this->drupalLogout();
|
||||
|
||||
// But if we use the basic auth authentication strategy, we should be able
|
||||
// to see the page.
|
||||
$url = $this->buildUrl('test/serialize/auth_with_perm');
|
||||
$response = \Drupal::httpClient()->get($url, [
|
||||
'auth' => [$this->adminUser->getUsername(), $this->adminUser->pass_raw],
|
||||
]);
|
||||
|
||||
// Ensure that any changes to variables in the other thread are picked up.
|
||||
$this->refreshVariables();
|
||||
|
||||
$headers = $response->getHeaders();
|
||||
$this->verbose('GET request to: ' . $url .
|
||||
'<hr />Code: ' . curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE) .
|
||||
'<hr />Response headers: ' . nl2br(print_r($headers, TRUE)) .
|
||||
'<hr />Response body: ' . (string) $response->getBody());
|
||||
$this->assertResponse(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the behavior of the Serializer callback paths and row plugins.
|
||||
*/
|
||||
public function testSerializerResponses() {
|
||||
// Test the serialize callback.
|
||||
$view = Views::getView('test_serializer_display_field');
|
||||
$view->initDisplay();
|
||||
$this->executeView($view);
|
||||
|
||||
$actual_json = $this->drupalGetWithFormat('test/serialize/field', 'json');
|
||||
$this->assertResponse(200);
|
||||
$this->assertCacheTags($view->getCacheTags());
|
||||
$this->assertCacheContexts(['languages:language_interface', 'theme', 'request_format']);
|
||||
// @todo Due to https://www.drupal.org/node/2352009 we can't yet test the
|
||||
// propagation of cache max-age.
|
||||
|
||||
// Test the http Content-type.
|
||||
$headers = $this->drupalGetHeaders();
|
||||
$this->assertEqual($headers['content-type'], 'application/json', 'The header Content-type is correct.');
|
||||
|
||||
$expected = array();
|
||||
foreach ($view->result as $row) {
|
||||
$expected_row = array();
|
||||
foreach ($view->field as $id => $field) {
|
||||
$expected_row[$id] = $field->render($row);
|
||||
}
|
||||
$expected[] = $expected_row;
|
||||
}
|
||||
|
||||
$this->assertIdentical($actual_json, json_encode($expected), 'The expected JSON output was found.');
|
||||
|
||||
|
||||
// Test that the rendered output and the preview output are the same.
|
||||
$view->destroy();
|
||||
$view->setDisplay('rest_export_1');
|
||||
// Mock the request content type by setting it on the display handler.
|
||||
$view->display_handler->setContentType('json');
|
||||
$output = $view->preview();
|
||||
$this->assertIdentical($actual_json, (string) drupal_render_root($output), 'The expected JSON preview output was found.');
|
||||
|
||||
// Test a 403 callback.
|
||||
$this->drupalGet('test/serialize/denied');
|
||||
$this->assertResponse(403);
|
||||
|
||||
// Test the entity rows.
|
||||
$view = Views::getView('test_serializer_display_entity');
|
||||
$view->initDisplay();
|
||||
$this->executeView($view);
|
||||
|
||||
// Get the serializer service.
|
||||
$serializer = $this->container->get('serializer');
|
||||
|
||||
$entities = array();
|
||||
foreach ($view->result as $row) {
|
||||
$entities[] = $row->_entity;
|
||||
}
|
||||
|
||||
$expected = $serializer->serialize($entities, 'json');
|
||||
|
||||
$actual_json = $this->drupalGetWithFormat('test/serialize/entity', 'json');
|
||||
$this->assertResponse(200);
|
||||
$this->assertIdentical($actual_json, $expected, 'The expected JSON output was found.');
|
||||
$expected_cache_tags = $view->getCacheTags();
|
||||
$expected_cache_tags[] = 'entity_test_list';
|
||||
/** @var \Drupal\Core\Entity\EntityInterface $entity */
|
||||
foreach ($entities as $entity) {
|
||||
$expected_cache_tags = Cache::mergeTags($expected_cache_tags, $entity->getCacheTags());
|
||||
}
|
||||
$this->assertCacheTags($expected_cache_tags);
|
||||
$this->assertCacheContexts(['languages:language_interface', 'theme', 'entity_test_view_grants', 'request_format']);
|
||||
|
||||
$expected = $serializer->serialize($entities, 'hal_json');
|
||||
$actual_json = $this->drupalGetWithFormat('test/serialize/entity', 'hal_json');
|
||||
$this->assertIdentical($actual_json, $expected, 'The expected HAL output was found.');
|
||||
$this->assertCacheTags($expected_cache_tags);
|
||||
|
||||
// Change the default format to xml.
|
||||
$view->setDisplay('rest_export_1');
|
||||
$view->getDisplay()->setOption('style', array(
|
||||
'type' => 'serializer',
|
||||
'options' => array(
|
||||
'uses_fields' => FALSE,
|
||||
'formats' => array(
|
||||
'xml' => 'xml',
|
||||
),
|
||||
),
|
||||
));
|
||||
$view->save();
|
||||
$expected = $serializer->serialize($entities, 'xml');
|
||||
$actual_xml = $this->drupalGet('test/serialize/entity');
|
||||
$this->assertIdentical($actual_xml, $expected, 'The expected XML output was found.');
|
||||
$this->assertCacheContexts(['languages:language_interface', 'theme', 'entity_test_view_grants', 'request_format']);
|
||||
|
||||
// Allow multiple formats.
|
||||
$view->setDisplay('rest_export_1');
|
||||
$view->getDisplay()->setOption('style', array(
|
||||
'type' => 'serializer',
|
||||
'options' => array(
|
||||
'uses_fields' => FALSE,
|
||||
'formats' => array(
|
||||
'xml' => 'xml',
|
||||
'json' => 'json',
|
||||
),
|
||||
),
|
||||
));
|
||||
$view->save();
|
||||
$expected = $serializer->serialize($entities, 'json');
|
||||
$actual_json = $this->drupalGetWithFormat('test/serialize/entity', 'json');
|
||||
$this->assertIdentical($actual_json, $expected, 'The expected JSON output was found.');
|
||||
$expected = $serializer->serialize($entities, 'xml');
|
||||
$actual_xml = $this->drupalGetWithFormat('test/serialize/entity', 'xml');
|
||||
$this->assertIdentical($actual_xml, $expected, 'The expected XML output was found.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies site maintenance mode functionality.
|
||||
*/
|
||||
protected function testSiteMaintenance() {
|
||||
$view = Views::getView('test_serializer_display_field');
|
||||
$view->initDisplay();
|
||||
$this->executeView($view);
|
||||
|
||||
// Set the site to maintenance mode.
|
||||
$this->container->get('state')->set('system.maintenance_mode', TRUE);
|
||||
|
||||
$this->drupalGetWithFormat('test/serialize/entity', 'json');
|
||||
// Verify that the endpoint is unavailable for anonymous users.
|
||||
$this->assertResponse(503);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a request on the request stack with a specified format.
|
||||
*
|
||||
* @param string $format
|
||||
* The new request format.
|
||||
*/
|
||||
protected function addRequestWithFormat($format) {
|
||||
$request = \Drupal::request();
|
||||
$request = clone $request;
|
||||
$request->setRequestFormat($format);
|
||||
|
||||
\Drupal::requestStack()->push($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests REST export with views render caching enabled.
|
||||
*/
|
||||
public function testRestRenderCaching() {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
/** @var \Drupal\Core\Render\RenderCacheInterface $render_cache */
|
||||
$render_cache = \Drupal::service('render_cache');
|
||||
|
||||
// Enable render caching for the views.
|
||||
/** @var \Drupal\views\ViewEntityInterface $storage */
|
||||
$storage = View::load('test_serializer_display_entity');
|
||||
$options = &$storage->getDisplay('default');
|
||||
$options['display_options']['cache'] = [
|
||||
'type' => 'tag',
|
||||
];
|
||||
$storage->save();
|
||||
|
||||
$original = DisplayPluginBase::buildBasicRenderable('test_serializer_display_entity', 'rest_export_1');
|
||||
|
||||
// Ensure that there is no corresponding render cache item yet.
|
||||
$original['#cache'] += ['contexts' => []];
|
||||
$original['#cache']['contexts'] = Cache::mergeContexts($original['#cache']['contexts'], $this->container->getParameter('renderer.config')['required_cache_contexts']);
|
||||
|
||||
$cache_tags = [
|
||||
'config:views.view.test_serializer_display_entity',
|
||||
'entity_test:1',
|
||||
'entity_test:10',
|
||||
'entity_test:2',
|
||||
'entity_test:3',
|
||||
'entity_test:4',
|
||||
'entity_test:5',
|
||||
'entity_test:6',
|
||||
'entity_test:7',
|
||||
'entity_test:8',
|
||||
'entity_test:9',
|
||||
'entity_test_list'
|
||||
];
|
||||
$cache_contexts = [
|
||||
'entity_test_view_grants',
|
||||
'languages:language_interface',
|
||||
'theme',
|
||||
'request_format',
|
||||
];
|
||||
|
||||
$this->assertFalse($render_cache->get($original));
|
||||
|
||||
// Request the page, once in XML and once in JSON to ensure that the caching
|
||||
// varies by it.
|
||||
$result1 = $this->drupalGetJSON('test/serialize/entity');
|
||||
$this->addRequestWithFormat('json');
|
||||
$this->assertHeader('content-type', 'application/json');
|
||||
$this->assertCacheContexts($cache_contexts);
|
||||
$this->assertCacheTags($cache_tags);
|
||||
$this->assertTrue($render_cache->get($original));
|
||||
|
||||
$result_xml = $this->drupalGetWithFormat('test/serialize/entity', 'xml');
|
||||
$this->addRequestWithFormat('xml');
|
||||
$this->assertHeader('content-type', 'text/xml; charset=UTF-8');
|
||||
$this->assertCacheContexts($cache_contexts);
|
||||
$this->assertCacheTags($cache_tags);
|
||||
$this->assertTrue($render_cache->get($original));
|
||||
|
||||
// Ensure that the XML output is different from the JSON one.
|
||||
$this->assertNotEqual($result1, $result_xml);
|
||||
|
||||
// Ensure that the cached page works.
|
||||
$result2 = $this->drupalGetJSON('test/serialize/entity');
|
||||
$this->addRequestWithFormat('json');
|
||||
$this->assertHeader('content-type', 'application/json');
|
||||
$this->assertEqual($result2, $result1);
|
||||
$this->assertCacheContexts($cache_contexts);
|
||||
$this->assertCacheTags($cache_tags);
|
||||
$this->assertTrue($render_cache->get($original));
|
||||
|
||||
// Create a new entity and ensure that the cache tags are taken over.
|
||||
EntityTest::create(['name' => 'test_11', 'user_id' => $this->adminUser->id()])->save();
|
||||
$result3 = $this->drupalGetJSON('test/serialize/entity');
|
||||
$this->addRequestWithFormat('json');
|
||||
$this->assertHeader('content-type', 'application/json');
|
||||
$this->assertNotEqual($result3, $result2);
|
||||
|
||||
// Add the new entity cache tag and remove the first one, because we just
|
||||
// show 10 items in total.
|
||||
$cache_tags[] = 'entity_test:11';
|
||||
unset($cache_tags[array_search('entity_test:1', $cache_tags)]);
|
||||
|
||||
$this->assertCacheContexts($cache_contexts);
|
||||
$this->assertCacheTags($cache_tags);
|
||||
$this->assertTrue($render_cache->get($original));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the response format configuration.
|
||||
*/
|
||||
public function testResponseFormatConfiguration() {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
$style_options = 'admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/style_options';
|
||||
|
||||
// Select only 'xml' as an accepted format.
|
||||
$this->drupalPostForm($style_options, array('style_options[formats][xml]' => 'xml'), t('Apply'));
|
||||
$this->drupalPostForm(NULL, array(), t('Save'));
|
||||
|
||||
// Should return a 406.
|
||||
$this->drupalGetWithFormat('test/serialize/field', 'json');
|
||||
$this->assertHeader('content-type', 'application/json');
|
||||
$this->assertResponse(406, 'A 406 response was returned when JSON was requested.');
|
||||
// Should return a 200.
|
||||
$this->drupalGetWithFormat('test/serialize/field', 'xml');
|
||||
$this->assertHeader('content-type', 'text/xml; charset=UTF-8');
|
||||
$this->assertResponse(200, 'A 200 response was returned when XML was requested.');
|
||||
|
||||
// Add 'json' as an accepted format, so we have multiple.
|
||||
$this->drupalPostForm($style_options, array('style_options[formats][json]' => 'json'), t('Apply'));
|
||||
$this->drupalPostForm(NULL, array(), t('Save'));
|
||||
|
||||
// Should return a 200.
|
||||
// @todo This should be fixed when we have better content negotiation.
|
||||
$this->drupalGet('test/serialize/field');
|
||||
$this->assertHeader('content-type', 'application/json');
|
||||
$this->assertResponse(200, 'A 200 response was returned when any format was requested.');
|
||||
|
||||
// Should return a 200. Emulates a sample Firefox header.
|
||||
$this->drupalGet('test/serialize/field', array(), array('Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'));
|
||||
$this->assertHeader('content-type', 'application/json');
|
||||
$this->assertResponse(200, 'A 200 response was returned when a browser accept header was requested.');
|
||||
|
||||
// Should return a 200.
|
||||
$this->drupalGetWithFormat('test/serialize/field', 'json');
|
||||
$this->assertHeader('content-type', 'application/json');
|
||||
$this->assertResponse(200, 'A 200 response was returned when JSON was requested.');
|
||||
$headers = $this->drupalGetHeaders();
|
||||
$this->assertEqual($headers['content-type'], 'application/json', 'The header Content-type is correct.');
|
||||
// Should return a 200.
|
||||
$this->drupalGetWithFormat('test/serialize/field', 'xml');
|
||||
$this->assertHeader('content-type', 'text/xml; charset=UTF-8');
|
||||
$this->assertResponse(200, 'A 200 response was returned when XML was requested');
|
||||
$headers = $this->drupalGetHeaders();
|
||||
$this->assertTrue(strpos($headers['content-type'], 'text/xml') !== FALSE, 'The header Content-type is correct.');
|
||||
// Should return a 406.
|
||||
$this->drupalGetWithFormat('test/serialize/field', 'html');
|
||||
// We want to show the first format by default, see
|
||||
// \Drupal\rest\Plugin\views\style\Serializer::render.
|
||||
$this->assertHeader('content-type', 'application/json');
|
||||
$this->assertResponse(200, 'A 200 response was returned when HTML was requested.');
|
||||
|
||||
// Now configure now format, so all of them should be allowed.
|
||||
$this->drupalPostForm($style_options, array('style_options[formats][json]' => '0', 'style_options[formats][xml]' => '0'), t('Apply'));
|
||||
|
||||
// Should return a 200.
|
||||
$this->drupalGetWithFormat('test/serialize/field', 'json');
|
||||
$this->assertHeader('content-type', 'application/json');
|
||||
$this->assertResponse(200, 'A 200 response was returned when JSON was requested.');
|
||||
// Should return a 200.
|
||||
$this->drupalGetWithFormat('test/serialize/field', 'xml');
|
||||
$this->assertHeader('content-type', 'text/xml; charset=UTF-8');
|
||||
$this->assertResponse(200, 'A 200 response was returned when XML was requested');
|
||||
// Should return a 200.
|
||||
$this->drupalGetWithFormat('test/serialize/field', 'html');
|
||||
// We want to show the first format by default, see
|
||||
// \Drupal\rest\Plugin\views\style\Serializer::render.
|
||||
$this->assertHeader('content-type', 'application/json');
|
||||
$this->assertResponse(200, 'A 200 response was returned when HTML was requested.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the field ID alias functionality of the DataFieldRow plugin.
|
||||
*/
|
||||
public function testUIFieldAlias() {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// Test the UI settings for adding field ID aliases.
|
||||
$this->drupalGet('admin/structure/views/view/test_serializer_display_field/edit/rest_export_1');
|
||||
$row_options = 'admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/row_options';
|
||||
$this->assertLinkByHref($row_options);
|
||||
|
||||
// Test an empty string for an alias, this should not be used. This also
|
||||
// tests that the form can be submitted with no aliases.
|
||||
$this->drupalPostForm($row_options, array('row_options[field_options][name][alias]' => ''), t('Apply'));
|
||||
$this->drupalPostForm(NULL, array(), t('Save'));
|
||||
|
||||
$view = Views::getView('test_serializer_display_field');
|
||||
$view->setDisplay('rest_export_1');
|
||||
$this->executeView($view);
|
||||
|
||||
$expected = array();
|
||||
foreach ($view->result as $row) {
|
||||
$expected_row = array();
|
||||
foreach ($view->field as $id => $field) {
|
||||
$expected_row[$id] = $field->render($row);
|
||||
}
|
||||
$expected[] = $expected_row;
|
||||
}
|
||||
|
||||
$this->assertIdentical($this->drupalGetJSON('test/serialize/field'), $this->castSafeStrings($expected));
|
||||
|
||||
// Test a random aliases for fields, they should be replaced.
|
||||
$alias_map = array(
|
||||
'name' => $this->randomMachineName(),
|
||||
// Use # to produce an invalid character for the validation.
|
||||
'nothing' => '#' . $this->randomMachineName(),
|
||||
'created' => 'created',
|
||||
);
|
||||
|
||||
$edit = array('row_options[field_options][name][alias]' => $alias_map['name'], 'row_options[field_options][nothing][alias]' => $alias_map['nothing']);
|
||||
$this->drupalPostForm($row_options, $edit, t('Apply'));
|
||||
$this->assertText(t('The machine-readable name must contain only letters, numbers, dashes and underscores.'));
|
||||
|
||||
// Change the map alias value to a valid one.
|
||||
$alias_map['nothing'] = $this->randomMachineName();
|
||||
|
||||
$edit = array('row_options[field_options][name][alias]' => $alias_map['name'], 'row_options[field_options][nothing][alias]' => $alias_map['nothing']);
|
||||
$this->drupalPostForm($row_options, $edit, t('Apply'));
|
||||
|
||||
$this->drupalPostForm(NULL, array(), t('Save'));
|
||||
|
||||
$view = Views::getView('test_serializer_display_field');
|
||||
$view->setDisplay('rest_export_1');
|
||||
$this->executeView($view);
|
||||
|
||||
$expected = array();
|
||||
foreach ($view->result as $row) {
|
||||
$expected_row = array();
|
||||
foreach ($view->field as $id => $field) {
|
||||
$expected_row[$alias_map[$id]] = $field->render($row);
|
||||
}
|
||||
$expected[] = $expected_row;
|
||||
}
|
||||
|
||||
$this->assertIdentical($this->drupalGetJSON('test/serialize/field'), $this->castSafeStrings($expected));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the raw output options for row field rendering.
|
||||
*/
|
||||
public function testFieldRawOutput() {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// Test the UI settings for adding field ID aliases.
|
||||
$this->drupalGet('admin/structure/views/view/test_serializer_display_field/edit/rest_export_1');
|
||||
$row_options = 'admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/row_options';
|
||||
$this->assertLinkByHref($row_options);
|
||||
|
||||
// Test an empty string for an alias, this should not be used. This also
|
||||
// tests that the form can be submitted with no aliases.
|
||||
$values = array(
|
||||
'row_options[field_options][created][raw_output]' => '1',
|
||||
'row_options[field_options][name][raw_output]' => '1',
|
||||
);
|
||||
$this->drupalPostForm($row_options, $values, t('Apply'));
|
||||
$this->drupalPostForm(NULL, array(), t('Save'));
|
||||
|
||||
$view = Views::getView('test_serializer_display_field');
|
||||
$view->setDisplay('rest_export_1');
|
||||
$this->executeView($view);
|
||||
|
||||
$storage = $this->container->get('entity_type.manager')->getStorage('entity_test');
|
||||
|
||||
// Update the name for each to include a script tag.
|
||||
foreach ($storage->loadMultiple() as $entity_test) {
|
||||
$name = $entity_test->name->value;
|
||||
$entity_test->set('name', "<script>$name</script>");
|
||||
$entity_test->save();
|
||||
}
|
||||
|
||||
// Just test the raw 'created' value against each row.
|
||||
foreach ($this->drupalGetJSON('test/serialize/field') as $index => $values) {
|
||||
$this->assertIdentical($values['created'], $view->result[$index]->views_test_data_created, 'Expected raw created value found.');
|
||||
$this->assertIdentical($values['name'], $view->result[$index]->views_test_data_name, 'Expected raw name value found.');
|
||||
}
|
||||
|
||||
// Test result with an excluded field.
|
||||
$view->setDisplay('rest_export_1');
|
||||
$view->displayHandlers->get('rest_export_1')->overrideOption('fields', [
|
||||
'name' => [
|
||||
'id' => 'name',
|
||||
'table' => 'views_test_data',
|
||||
'field' => 'name',
|
||||
'relationship' => 'none',
|
||||
],
|
||||
'created' => [
|
||||
'id' => 'created',
|
||||
'exclude' => TRUE,
|
||||
'table' => 'views_test_data',
|
||||
'field' => 'created',
|
||||
'relationship' => 'none',
|
||||
],
|
||||
]);
|
||||
$view->save();
|
||||
$this->executeView($view);
|
||||
foreach ($this->drupalGetJSON('test/serialize/field') as $index => $values) {
|
||||
$this->assertTrue(!isset($values['created']), 'Excluded value not found.');
|
||||
}
|
||||
// Test that the excluded field is not shown in the row options.
|
||||
$this->drupalGet('admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/row_options');
|
||||
$this->assertNoText('created');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the live preview output for json output.
|
||||
*/
|
||||
public function testLivePreview() {
|
||||
// We set up a request so it looks like an request in the live preview.
|
||||
$request = new Request();
|
||||
$request->query->add([MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax']);
|
||||
/** @var \Symfony\Component\HttpFoundation\RequestStack $request_stack */
|
||||
$request_stack = \Drupal::service('request_stack');
|
||||
$request_stack->push($request);
|
||||
|
||||
$view = Views::getView('test_serializer_display_entity');
|
||||
$view->setDisplay('rest_export_1');
|
||||
$this->executeView($view);
|
||||
|
||||
// Get the serializer service.
|
||||
$serializer = $this->container->get('serializer');
|
||||
|
||||
$entities = array();
|
||||
foreach ($view->result as $row) {
|
||||
$entities[] = $row->_entity;
|
||||
}
|
||||
|
||||
$expected = $serializer->serialize($entities, 'json');
|
||||
|
||||
$view->live_preview = TRUE;
|
||||
|
||||
$build = $view->preview();
|
||||
$rendered_json = $build['#plain_text'];
|
||||
$this->assertTrue(!isset($build['#markup']) && $rendered_json == $expected, 'Ensure the previewed json is escaped.');
|
||||
$view->destroy();
|
||||
|
||||
$expected = $serializer->serialize($entities, 'xml');
|
||||
|
||||
// Change the request format to xml.
|
||||
$view->setDisplay('rest_export_1');
|
||||
$view->getDisplay()->setOption('style', array(
|
||||
'type' => 'serializer',
|
||||
'options' => array(
|
||||
'uses_fields' => FALSE,
|
||||
'formats' => array(
|
||||
'xml' => 'xml',
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
$this->executeView($view);
|
||||
$build = $view->preview();
|
||||
$rendered_xml = $build['#plain_text'];
|
||||
$this->assertEqual($rendered_xml, $expected, 'Ensure we preview xml when we change the request format.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the views interface for REST export displays.
|
||||
*/
|
||||
public function testSerializerViewsUI() {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
// Click the "Update preview button".
|
||||
$this->drupalPostForm('admin/structure/views/view/test_serializer_display_field/edit/rest_export_1', $edit = array(), t('Update preview'));
|
||||
$this->assertResponse(200);
|
||||
// Check if we receive the expected result.
|
||||
$result = $this->xpath('//div[@id="views-live-preview"]/pre');
|
||||
$this->assertIdentical($this->drupalGet('test/serialize/field'), (string) $result[0], 'The expected JSON preview output was found.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the field row style using fieldapi fields.
|
||||
*/
|
||||
public function testFieldapiField() {
|
||||
$this->drupalCreateContentType(array('type' => 'page'));
|
||||
$node = $this->drupalCreateNode();
|
||||
|
||||
$result = $this->drupalGetJSON('test/serialize/node-field');
|
||||
$this->assertEqual($result[0]['nid'], $node->id());
|
||||
$this->assertEqual($result[0]['body'], $node->body->processed);
|
||||
|
||||
// Make sure that serialized fields are not exposed to XSS.
|
||||
$node = $this->drupalCreateNode();
|
||||
$node->body = [
|
||||
'value' => '<script type="text/javascript">alert("node-body");</script>' . $this->randomMachineName(32),
|
||||
'format' => filter_default_format(),
|
||||
];
|
||||
$node->save();
|
||||
$result = $this->drupalGetJSON('test/serialize/node-field');
|
||||
$this->assertEqual($result[1]['nid'], $node->id());
|
||||
$this->assertTrue(strpos($this->getRawContent(), "<script") === FALSE, "No script tag is present in the raw page contents.");
|
||||
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// Add an alias and make the output raw.
|
||||
$row_options = 'admin/structure/views/nojs/display/test_serializer_node_display_field/rest_export_1/row_options';
|
||||
|
||||
// Test an empty string for an alias, this should not be used. This also
|
||||
// tests that the form can be submitted with no aliases.
|
||||
$this->drupalPostForm($row_options, ['row_options[field_options][title][raw_output]' => '1'], t('Apply'));
|
||||
$this->drupalPostForm(NULL, [], t('Save'));
|
||||
|
||||
$view = Views::getView('test_serializer_node_display_field');
|
||||
$view->setDisplay('rest_export_1');
|
||||
$this->executeView($view);
|
||||
|
||||
// Test the raw 'created' value against each row.
|
||||
foreach ($this->drupalGetJSON('test/serialize/node-field') as $index => $values) {
|
||||
$this->assertIdentical($values['title'], $view->result[$index]->_entity->title->value, 'Expected raw title value found.');
|
||||
}
|
||||
|
||||
// Test that multiple raw body fields are shown.
|
||||
// Make the body field unlimited cardinatlity.
|
||||
$storage_definition = $node->getFieldDefinition('body')->getFieldStorageDefinition();
|
||||
$storage_definition->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
|
||||
$storage_definition->save();
|
||||
|
||||
$this->drupalPostForm($row_options, ['row_options[field_options][body][raw_output]' => '1'], t('Apply'));
|
||||
$this->drupalPostForm(NULL, [], t('Save'));
|
||||
|
||||
$node = $this->drupalCreateNode();
|
||||
|
||||
$body = [
|
||||
'value' => '<script type="text/javascript">alert("node-body");</script>' . $this->randomMachineName(32),
|
||||
'format' => filter_default_format(),
|
||||
];
|
||||
// Add two body items.
|
||||
$node->body = [$body, $body];
|
||||
$node->save();
|
||||
|
||||
$view = Views::getView('test_serializer_node_display_field');
|
||||
$view->setDisplay('rest_export_1');
|
||||
$this->executeView($view);
|
||||
|
||||
$result = $this->drupalGetJSON('test/serialize/node-field');
|
||||
$this->assertEqual(count($result[2]['body']), $node->body->count(), 'Expected count of values');
|
||||
$this->assertEqual($result[2]['body'], array_map(function($item) { return $item['value']; }, $node->body->getValue()), 'Expected raw body values found.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the "Grouped rows" functionality.
|
||||
*/
|
||||
public function testGroupRows() {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = $this->container->get('renderer');
|
||||
$this->drupalCreateContentType(['type' => 'page']);
|
||||
// Create a text field with cardinality set to unlimited.
|
||||
$field_name = 'field_group_rows';
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'node',
|
||||
'type' => 'string',
|
||||
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
|
||||
]);
|
||||
$field_storage->save();
|
||||
// Create an instance of the text field on the content type.
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'page',
|
||||
]);
|
||||
$field->save();
|
||||
$grouped_field_values = ['a', 'b', 'c'];
|
||||
$edit = [
|
||||
'title' => $this->randomMachineName(),
|
||||
$field_name => $grouped_field_values,
|
||||
];
|
||||
$this->drupalCreateNode($edit);
|
||||
$view = Views::getView('test_serializer_node_display_field');
|
||||
$view->setDisplay('rest_export_1');
|
||||
// Override the view's fields to include the field_group_rows field, set the
|
||||
// group_rows setting to true.
|
||||
$fields = [
|
||||
$field_name => [
|
||||
'id' => $field_name,
|
||||
'table' => 'node__' . $field_name,
|
||||
'field' => $field_name,
|
||||
'type' => 'string',
|
||||
'group_rows' => TRUE,
|
||||
],
|
||||
];
|
||||
$view->displayHandlers->get('default')->overrideOption('fields', $fields);
|
||||
$build = $view->preview();
|
||||
// Get the serializer service.
|
||||
$serializer = $this->container->get('serializer');
|
||||
// Check if the field_group_rows field is grouped.
|
||||
$expected = [];
|
||||
$expected[] = [$field_name => implode(', ', $grouped_field_values)];
|
||||
$this->assertEqual($serializer->serialize($expected, 'json'), (string) $renderer->renderRoot($build));
|
||||
// Set the group rows setting to false.
|
||||
$view = Views::getView('test_serializer_node_display_field');
|
||||
$view->setDisplay('rest_export_1');
|
||||
$fields[$field_name]['group_rows'] = FALSE;
|
||||
$view->displayHandlers->get('default')->overrideOption('fields', $fields);
|
||||
$build = $view->preview();
|
||||
// Check if the field_group_rows field is ungrouped and displayed per row.
|
||||
$expected = [];
|
||||
foreach ($grouped_field_values as $grouped_field_value) {
|
||||
$expected[] = [$field_name => $grouped_field_value];
|
||||
}
|
||||
$this->assertEqual($serializer->serialize($expected, 'json'), (string) $renderer->renderRoot($build));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the exposed filter works.
|
||||
*
|
||||
* There is an exposed filter on the title field which takes a title query
|
||||
* parameter. This is set to filter nodes by those whose title starts with
|
||||
* the value provided.
|
||||
*/
|
||||
public function testRestViewExposedFilter() {
|
||||
$this->drupalCreateContentType(array('type' => 'page'));
|
||||
$node0 = $this->drupalCreateNode(array('title' => 'Node 1'));
|
||||
$node1 = $this->drupalCreateNode(array('title' => 'Node 11'));
|
||||
$node2 = $this->drupalCreateNode(array('title' => 'Node 111'));
|
||||
|
||||
// Test that no filter brings back all three nodes.
|
||||
$result = $this->drupalGetJSON('test/serialize/node-exposed-filter');
|
||||
|
||||
$expected = array(
|
||||
0 => array(
|
||||
'nid' => $node0->id(),
|
||||
'body' => $node0->body->processed,
|
||||
),
|
||||
1 => array(
|
||||
'nid' => $node1->id(),
|
||||
'body' => $node1->body->processed,
|
||||
),
|
||||
2 => array(
|
||||
'nid' => $node2->id(),
|
||||
'body' => $node2->body->processed,
|
||||
),
|
||||
);
|
||||
|
||||
$this->assertEqual($result, $expected, 'Querying a view with no exposed filter returns all nodes.');
|
||||
|
||||
// Test that title starts with 'Node 11' query finds 2 of the 3 nodes.
|
||||
$result = $this->drupalGetJSON('test/serialize/node-exposed-filter', ['query' => ['title' => 'Node 11']]);
|
||||
|
||||
$expected = array(
|
||||
0 => array(
|
||||
'nid' => $node1->id(),
|
||||
'body' => $node1->body->processed,
|
||||
),
|
||||
1 => array(
|
||||
'nid' => $node2->id(),
|
||||
'body' => $node2->body->processed,
|
||||
),
|
||||
);
|
||||
|
||||
$cache_contexts = [
|
||||
'languages:language_content',
|
||||
'languages:language_interface',
|
||||
'theme',
|
||||
'request_format',
|
||||
'user.node_grants:view',
|
||||
'url',
|
||||
];
|
||||
|
||||
$this->assertEqual($result, $expected, 'Querying a view with a starts with exposed filter on the title returns nodes whose title starts with value provided.');
|
||||
$this->assertCacheContexts($cache_contexts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test multilingual entity rows.
|
||||
*/
|
||||
public function testMulEntityRows() {
|
||||
// Create some languages.
|
||||
ConfigurableLanguage::createFromLangcode('l1')->save();
|
||||
ConfigurableLanguage::createFromLangcode('l2')->save();
|
||||
|
||||
// Create an entity with no translations.
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('entity_test_mul');
|
||||
$storage->create(['langcode' => 'l1', 'name' => 'mul-none'])->save();
|
||||
|
||||
// Create some entities with translations.
|
||||
$entity = $storage->create(['langcode' => 'l1', 'name' => 'mul-l1-orig']);
|
||||
$entity->save();
|
||||
$entity->addTranslation('l2', ['name' => 'mul-l1-l2'])->save();
|
||||
$entity = $storage->create(['langcode' => 'l2', 'name' => 'mul-l2-orig']);
|
||||
$entity->save();
|
||||
$entity->addTranslation('l1', ['name' => 'mul-l2-l1'])->save();
|
||||
|
||||
// Get the names of the output.
|
||||
$json = $this->drupalGetWithFormat('test/serialize/translated_entity', 'json');
|
||||
$decoded = $this->container->get('serializer')->decode($json, 'hal_json');
|
||||
$names = [];
|
||||
foreach ($decoded as $item) {
|
||||
$names[] = $item['name'][0]['value'];
|
||||
}
|
||||
sort($names);
|
||||
|
||||
// Check that the names are correct.
|
||||
$expected = ['mul-l1-l2', 'mul-l1-orig', 'mul-l2-l1', 'mul-l2-orig', 'mul-none'];
|
||||
$this->assertIdentical($names, $expected, 'The translated content was found in the JSON.');
|
||||
}
|
||||
|
||||
}
|
BIN
web/core/modules/rest/tests/fixtures/update/drupal-8.rest-rest_post_update_resource_granularity.php
vendored
Normal file
BIN
web/core/modules/rest/tests/fixtures/update/drupal-8.rest-rest_post_update_resource_granularity.php
vendored
Normal file
Binary file not shown.
59
web/core/modules/rest/tests/fixtures/update/drupal-8.rest-rest_update_8201.php
vendored
Normal file
59
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
web/core/modules/rest/tests/fixtures/update/drupal-8.rest-rest_update_8203.php
vendored
Normal file
58
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();
|
65
web/core/modules/rest/tests/fixtures/update/rest-export-with-authentication.php
vendored
Normal file
65
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
web/core/modules/rest/tests/fixtures/update/rest.resource.entity.comment_2721595.yml
vendored
Normal file
32
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
web/core/modules/rest/tests/fixtures/update/rest.resource.entity.node_2721595.yml
vendored
Normal file
29
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
web/core/modules/rest/tests/fixtures/update/rest.resource.entity.user_2721595.yml
vendored
Normal file
32
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:
|
||||
- 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,8 @@
|
|||
name: 'REST test'
|
||||
type: module
|
||||
description: 'Provides test hooks and resources for REST module.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- rest
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains hook implementations for testing REST module.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
|
||||
/**
|
||||
* Implements hook_rest_type_uri_alter().
|
||||
*/
|
||||
function rest_test_rest_type_uri_alter(&$uri, $context = array()) {
|
||||
if (!empty($context['rest_test'])) {
|
||||
$uri = 'rest_test_type';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_rest_relation_uri_alter().
|
||||
*/
|
||||
function rest_test_rest_relation_uri_alter(&$uri, $context = array()) {
|
||||
if (!empty($context['rest_test'])) {
|
||||
$uri = 'rest_test_relation';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_field_access().
|
||||
*
|
||||
* @see \Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase::setUp()
|
||||
* @see \Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase::testPost()
|
||||
*/
|
||||
function rest_test_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// No opinion.
|
||||
return AccessResult::neutral();
|
||||
}
|
|
@ -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:
|
||||
- rest
|
||||
- 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,36 @@
|
|||
<?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(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,43 @@
|
|||
<?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.
|
||||
*/
|
||||
trait BasicAuthResourceTestTrait {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getAuthenticationRequestOptions($method) {
|
||||
return [
|
||||
'headers' => [
|
||||
'Authorization' => 'Basic ' . base64_encode($this->account->name->value . ':' . $this->account->passRaw),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function assertResponseWhenMissingAuthentication(ResponseInterface $response) {
|
||||
$this->assertResourceErrorResponse(401, 'No authentication credentials provided.', $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function assertAuthenticationEdgeCases($method, Url $url, array $request_options) {}
|
||||
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
<?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() {
|
||||
// @todo Remove hardcoded use of the 'json' format, and use static::$format
|
||||
// + static::$mimeType instead in https://www.drupal.org/node/2820888.
|
||||
$user_login_url = Url::fromRoute('user.login.http')
|
||||
->setRouteParameter('_format', 'json');
|
||||
|
||||
$request_body = [
|
||||
'name' => $this->account->name->value,
|
||||
'pass' => $this->account->passRaw,
|
||||
];
|
||||
|
||||
$request_options[RequestOptions::BODY] = $this->serializer->encode($request_body, 'json');
|
||||
$request_options[RequestOptions::HEADERS]['Accept'] = 'application/json';
|
||||
$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(ResponseInterface $response) {
|
||||
$this->assertResourceErrorResponse(403, '', $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,29 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Block;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class BlockJsonAnonTest extends BlockResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Block;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class BlockJsonBasicAuthTest extends BlockResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
|
||||
use JsonBasicAuthWorkaroundFor2805281Trait {
|
||||
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Block;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class BlockJsonCookieTest extends BlockResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Block;
|
||||
|
||||
use Drupal\block\Entity\Block;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
|
||||
|
||||
abstract class BlockResourceTestBase extends EntityResourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['block'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $entityTypeId = 'block';
|
||||
|
||||
/**
|
||||
* @var \Drupal\block\BlockInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
$this->entity->setVisibilityConfig('user_role', [])->save();
|
||||
break;
|
||||
case 'POST':
|
||||
$this->grantPermissionsToTestedRole(['administer blocks']);
|
||||
break;
|
||||
case 'PATCH':
|
||||
$this->grantPermissionsToTestedRole(['administer blocks']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
$block = Block::create([
|
||||
'plugin' => 'llama_block',
|
||||
'region' => 'header',
|
||||
'id' => 'llama',
|
||||
'theme' => 'classy',
|
||||
]);
|
||||
// All blocks can be viewed by the anonymous user by default. An interesting
|
||||
// side effect of this is that any anonymous user is also able to read the
|
||||
// corresponding block config entity via REST, even if an authentication
|
||||
// provider is configured for the block config entity REST resource! In
|
||||
// other words: Block entities do not distinguish between 'view' as in
|
||||
// "render on a page" and 'view' as in "read the configuration".
|
||||
// This prevents that.
|
||||
// @todo Fix this in https://www.drupal.org/node/2820315.
|
||||
$block->setVisibilityConfig('user_role', [
|
||||
'id' => 'user_role',
|
||||
'roles' => ['non-existing-role' => 'non-existing-role'],
|
||||
'negate' => FALSE,
|
||||
'context_mapping' => [
|
||||
'user' => '@user.current_user_context:current_user',
|
||||
],
|
||||
]);
|
||||
$block->save();
|
||||
|
||||
return $block;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
$normalization = [
|
||||
'uuid' => $this->entity->uuid(),
|
||||
'id' => 'llama',
|
||||
'weight' => NULL,
|
||||
'langcode' => 'en',
|
||||
'status' => TRUE,
|
||||
'dependencies' => [
|
||||
'theme' => [
|
||||
'classy',
|
||||
],
|
||||
],
|
||||
'theme' => 'classy',
|
||||
'region' => 'header',
|
||||
'provider' => NULL,
|
||||
'plugin' => 'llama_block',
|
||||
'settings' => [
|
||||
'id' => 'broken',
|
||||
'label' => '',
|
||||
'provider' => 'core',
|
||||
'label_display' => 'visible',
|
||||
],
|
||||
'visibility' => [],
|
||||
];
|
||||
|
||||
return $normalization;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
// @todo Update in https://www.drupal.org/node/2300677.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCacheContexts() {
|
||||
// @see ::createEntity()
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCacheTags() {
|
||||
// Because the 'user.permissions' cache context is missing, the cache tag
|
||||
// for the anonymous user role is never added automatically.
|
||||
return array_filter(parent::getExpectedCacheTags(), function ($tag) {
|
||||
return $tag !== 'config:user.role.anonymous';
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Comment;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class CommentJsonAnonTest extends CommentResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Anononymous users cannot edit their own comments.
|
||||
*
|
||||
* @see \Drupal\comment\CommentAccessControlHandler::checkAccess
|
||||
*
|
||||
* Therefore we grant them the 'administer comments' permission for the
|
||||
* purpose of this test.
|
||||
*
|
||||
* @see ::setUpAuthorization
|
||||
*/
|
||||
protected static $patchProtectedFieldNames = [
|
||||
'pid',
|
||||
'entity_id',
|
||||
'changed',
|
||||
'thread',
|
||||
'entity_type',
|
||||
'field_name',
|
||||
];
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Comment;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class CommentJsonBasicAuthTest extends CommentResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
|
||||
use JsonBasicAuthWorkaroundFor2805281Trait {
|
||||
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Comment;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class CommentJsonCookieTest extends CommentResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
}
|
|
@ -0,0 +1,309 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Comment;
|
||||
|
||||
use Drupal\comment\Entity\Comment;
|
||||
use Drupal\comment\Entity\CommentType;
|
||||
use Drupal\comment\Tests\CommentTestTrait;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
|
||||
use Drupal\user\Entity\User;
|
||||
use GuzzleHttp\RequestOptions;
|
||||
|
||||
abstract class CommentResourceTestBase extends EntityResourceTestBase {
|
||||
|
||||
use CommentTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['comment', 'entity_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $entityTypeId = 'comment';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $patchProtectedFieldNames = [
|
||||
'pid',
|
||||
'entity_id',
|
||||
'uid',
|
||||
'name',
|
||||
'homepage',
|
||||
'created',
|
||||
'changed',
|
||||
'status',
|
||||
'thread',
|
||||
'entity_type',
|
||||
'field_name',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var \Drupal\comment\CommentInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
$this->grantPermissionsToTestedRole(['access comments', 'view test entity']);
|
||||
break;
|
||||
case 'POST':
|
||||
$this->grantPermissionsToTestedRole(['post comments']);
|
||||
break;
|
||||
case 'PATCH':
|
||||
// Anononymous users are not ever allowed to edit their own comments. To
|
||||
// be able to test PATCHing comments as the anonymous user, the more
|
||||
// permissive 'administer comments' permission must be granted.
|
||||
// @see \Drupal\comment\CommentAccessControlHandler::checkAccess
|
||||
if (static::$auth) {
|
||||
$this->grantPermissionsToTestedRole(['edit own comments']);
|
||||
}
|
||||
else {
|
||||
$this->grantPermissionsToTestedRole(['administer comments']);
|
||||
}
|
||||
break;
|
||||
case 'DELETE':
|
||||
$this->grantPermissionsToTestedRole(['administer comments']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
// Create a "bar" bundle for the "entity_test" entity type and create.
|
||||
$bundle = 'bar';
|
||||
entity_test_create_bundle($bundle, NULL, 'entity_test');
|
||||
|
||||
// Create a comment field on this bundle.
|
||||
$this->addDefaultCommentField('entity_test', 'bar', 'comment');
|
||||
|
||||
// Create a "Camelids" test entity that the comment will be assigned to.
|
||||
$commented_entity = EntityTest::create(array(
|
||||
'name' => 'Camelids',
|
||||
'type' => 'bar',
|
||||
));
|
||||
$commented_entity->save();
|
||||
|
||||
// Create a "Llama" comment.
|
||||
$comment = Comment::create([
|
||||
'comment_body' => [
|
||||
'value' => 'The name "llama" was adopted by European settlers from native Peruvians.',
|
||||
'format' => 'plain_text',
|
||||
],
|
||||
'entity_id' => $commented_entity->id(),
|
||||
'entity_type' => 'entity_test',
|
||||
'field_name' => 'comment',
|
||||
]);
|
||||
$comment->setSubject('Llama')
|
||||
->setOwnerId(static::$auth ? $this->account->id() : 0)
|
||||
->setPublished(TRUE)
|
||||
->setCreatedTime(123456789)
|
||||
->setChangedTime(123456789);
|
||||
$comment->save();
|
||||
|
||||
return $comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
$author = User::load($this->entity->getOwnerId());
|
||||
return [
|
||||
'cid' => [
|
||||
['value' => 1],
|
||||
],
|
||||
'uuid' => [
|
||||
['value' => $this->entity->uuid()],
|
||||
],
|
||||
'langcode' => [
|
||||
[
|
||||
'value' => 'en',
|
||||
],
|
||||
],
|
||||
'comment_type' => [
|
||||
[
|
||||
'target_id' => 'comment',
|
||||
'target_type' => 'comment_type',
|
||||
'target_uuid' => CommentType::load('comment')->uuid(),
|
||||
],
|
||||
],
|
||||
'subject' => [
|
||||
[
|
||||
'value' => 'Llama',
|
||||
],
|
||||
],
|
||||
'status' => [
|
||||
[
|
||||
'value' => 1,
|
||||
],
|
||||
],
|
||||
'created' => [
|
||||
[
|
||||
'value' => '123456789',
|
||||
],
|
||||
],
|
||||
'changed' => [
|
||||
[
|
||||
'value' => '123456789',
|
||||
],
|
||||
],
|
||||
'default_langcode' => [
|
||||
[
|
||||
'value' => TRUE,
|
||||
],
|
||||
],
|
||||
'uid' => [
|
||||
[
|
||||
'target_id' => $author->id(),
|
||||
'target_type' => 'user',
|
||||
'target_uuid' => $author->uuid(),
|
||||
'url' => base_path() . 'user/' . $author->id(),
|
||||
],
|
||||
],
|
||||
'pid' => [],
|
||||
'entity_type' => [
|
||||
[
|
||||
'value' => 'entity_test',
|
||||
],
|
||||
],
|
||||
'entity_id' => [
|
||||
[
|
||||
'target_id' => '1',
|
||||
'target_type' => 'entity_test',
|
||||
'target_uuid' => EntityTest::load(1)->uuid(),
|
||||
'url' => base_path() . 'entity_test/1',
|
||||
],
|
||||
],
|
||||
'field_name' => [
|
||||
[
|
||||
'value' => 'comment',
|
||||
],
|
||||
],
|
||||
'name' => [],
|
||||
'homepage' => [],
|
||||
'thread' => [
|
||||
[
|
||||
'value' => '01/',
|
||||
],
|
||||
],
|
||||
'comment_body' => [
|
||||
[
|
||||
'value' => 'The name "llama" was adopted by European settlers from native Peruvians.',
|
||||
'format' => 'plain_text',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
return [
|
||||
'comment_type' => [
|
||||
[
|
||||
'target_id' => 'comment',
|
||||
],
|
||||
],
|
||||
'entity_type' => [
|
||||
[
|
||||
'value' => 'entity_test',
|
||||
],
|
||||
],
|
||||
'entity_id' => [
|
||||
[
|
||||
'target_id' => EntityTest::load(1)->id(),
|
||||
],
|
||||
],
|
||||
'field_name' => [
|
||||
[
|
||||
'value' => 'comment',
|
||||
],
|
||||
],
|
||||
'subject' => [
|
||||
[
|
||||
'value' => 'Dramallama',
|
||||
],
|
||||
],
|
||||
'comment_body' => [
|
||||
[
|
||||
'value' => 'Llamas are awesome.',
|
||||
'format' => 'plain_text',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPatchEntity() {
|
||||
return array_diff_key($this->getNormalizedPostEntity(), ['entity_type' => TRUE, 'entity_id' => TRUE, 'field_name' => TRUE]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests POSTing a comment without critical base fields.
|
||||
*
|
||||
* testPost() is testing with the most minimal normalization possible: the one
|
||||
* returned by ::getNormalizedPostEntity().
|
||||
*
|
||||
* But Comment entities have some very special edge cases:
|
||||
* - base fields that are not marked as required in
|
||||
* \Drupal\comment\Entity\Comment::baseFieldDefinitions() yet in fact are
|
||||
* required.
|
||||
* - base fields that are marked as required, but yet can still result in
|
||||
* validation errors other than "missing required field".
|
||||
*/
|
||||
public function testPostDxWithoutCriticalBaseFields() {
|
||||
$this->initAuthentication();
|
||||
$this->provisionEntityResource();
|
||||
$this->setUpAuthorization('POST');
|
||||
|
||||
$url = $this->getPostUrl()->setOption('query', ['_format' => static::$format]);
|
||||
$request_options = [];
|
||||
$request_options[RequestOptions::HEADERS]['Accept'] = static::$mimeType;
|
||||
$request_options[RequestOptions::HEADERS]['Content-Type'] = static::$mimeType;
|
||||
$request_options = array_merge_recursive($request_options, $this->getAuthenticationRequestOptions('POST'));
|
||||
|
||||
// DX: 422 when missing 'entity_type' field.
|
||||
$request_options[RequestOptions::BODY] = $this->serializer->encode(array_diff_key($this->getNormalizedPostEntity(), ['entity_type' => TRUE]), static::$format);
|
||||
$response = $this->request('POST', $url, $request_options);
|
||||
// @todo Uncomment, remove next line in https://www.drupal.org/node/2820364.
|
||||
$this->assertResourceErrorResponse(500, 'A fatal error occurred: Internal Server Error', $response);
|
||||
//$this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nentity_type: This value should not be null.\n", $response);
|
||||
|
||||
// DX: 422 when missing 'entity_id' field.
|
||||
$request_options[RequestOptions::BODY] = $this->serializer->encode(array_diff_key($this->getNormalizedPostEntity(), ['entity_id' => TRUE]), static::$format);
|
||||
// @todo Remove the try/catch in favor of the two commented lines in
|
||||
// https://www.drupal.org/node/2820364.
|
||||
try {
|
||||
$response = $this->request('POST', $url, $request_options);
|
||||
// This happens on DrupalCI.
|
||||
//$this->assertSame(500, $response->getStatusCode());
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
// This happens on Wim's local machine.
|
||||
//$this->assertSame("Error: Call to a member function get() on null\nDrupal\\comment\\Plugin\\Validation\\Constraint\\CommentNameConstraintValidator->getAnonymousContactDetailsSetting()() (Line: 96)\n", $e->getMessage());
|
||||
}
|
||||
//$response = $this->request('POST', $url, $request_options);
|
||||
//$this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nentity_type: This value should not be null.\n", $response);
|
||||
|
||||
// DX: 422 when missing 'entity_type' field.
|
||||
$request_options[RequestOptions::BODY] = $this->serializer->encode(array_diff_key($this->getNormalizedPostEntity(), ['field_name' => TRUE]), static::$format);
|
||||
$response = $this->request('POST', $url, $request_options);
|
||||
// @todo Uncomment, remove next line in https://www.drupal.org/node/2820364.
|
||||
$this->assertResourceErrorResponse(500, 'A fatal error occurred: Field is unknown.', $response);
|
||||
//$this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nfield_name: This value should not be null.\n", $response);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\ConfigTest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class ConfigTestJsonAnonTest extends ConfigTestResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\ConfigTest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class ConfigTestJsonBasicAuthTest extends ConfigTestResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
|
||||
use JsonBasicAuthWorkaroundFor2805281Trait {
|
||||
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\ConfigTest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class ConfigTestJsonCookieTest extends ConfigTestResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\ConfigTest;
|
||||
|
||||
use Drupal\config_test\Entity\ConfigTest;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
|
||||
|
||||
abstract class ConfigTestResourceTestBase extends EntityResourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['config_test', 'config_test_rest'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $entityTypeId = 'config_test';
|
||||
|
||||
/**
|
||||
* @var \Drupal\config_test\ConfigTestInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
$this->grantPermissionsToTestedRole(['view config_test']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
$config_test = ConfigTest::create([
|
||||
'id' => 'llama',
|
||||
'label' => 'Llama',
|
||||
]);
|
||||
$config_test->save();
|
||||
|
||||
return $config_test;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
$normalization = [
|
||||
'uuid' => $this->entity->uuid(),
|
||||
'id' => 'llama',
|
||||
'weight' => 0,
|
||||
'langcode' => 'en',
|
||||
'status' => TRUE,
|
||||
'dependencies' => [],
|
||||
'label' => 'Llama',
|
||||
'style' => NULL,
|
||||
'size' => NULL,
|
||||
'size_value' => NULL,
|
||||
'protected_property' => NULL,
|
||||
];
|
||||
|
||||
return $normalization;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
// @todo Update in https://www.drupal.org/node/2300677.
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\EntityTest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class EntityTestJsonAnonTest extends EntityTestResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\EntityTest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class EntityTestJsonBasicAuthTest extends EntityTestResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
|
||||
use JsonBasicAuthWorkaroundFor2805281Trait {
|
||||
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\EntityTest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class EntityTestJsonCookieTest extends EntityTestResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\EntityTest;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
|
||||
use Drupal\user\Entity\User;
|
||||
|
||||
abstract class EntityTestResourceTestBase extends EntityResourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['entity_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $entityTypeId = 'entity_test';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $patchProtectedFieldNames = [];
|
||||
|
||||
/**
|
||||
* @var \Drupal\entity_test\Entity\EntityTest
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
$this->grantPermissionsToTestedRole(['view test entity']);
|
||||
break;
|
||||
case 'POST':
|
||||
$this->grantPermissionsToTestedRole(['create entity_test entity_test_with_bundle entities']);
|
||||
break;
|
||||
case 'PATCH':
|
||||
case 'DELETE':
|
||||
$this->grantPermissionsToTestedRole(['administer entity_test content']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
$entity_test = EntityTest::create([
|
||||
'name' => 'Llama',
|
||||
'type' => 'entity_test',
|
||||
]);
|
||||
$entity_test->setOwnerId(0);
|
||||
$entity_test->save();
|
||||
|
||||
return $entity_test;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
$author = User::load(0);
|
||||
$normalization = [
|
||||
'uuid' => [
|
||||
[
|
||||
'value' => $this->entity->uuid()
|
||||
]
|
||||
],
|
||||
'id' => [
|
||||
[
|
||||
'value' => '1',
|
||||
],
|
||||
],
|
||||
'langcode' => [
|
||||
[
|
||||
'value' => 'en',
|
||||
],
|
||||
],
|
||||
'type' => [
|
||||
[
|
||||
'value' => 'entity_test',
|
||||
]
|
||||
],
|
||||
'name' => [
|
||||
[
|
||||
'value' => 'Llama',
|
||||
]
|
||||
],
|
||||
'created' => [
|
||||
[
|
||||
'value' => $this->entity->get('created')->value,
|
||||
]
|
||||
],
|
||||
'user_id' => [
|
||||
[
|
||||
'target_id' => $author->id(),
|
||||
'target_type' => 'user',
|
||||
'target_uuid' => $author->uuid(),
|
||||
'url' => $author->toUrl()->toString(),
|
||||
]
|
||||
],
|
||||
'field_test_text' => [],
|
||||
];
|
||||
|
||||
return $normalization;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
return [
|
||||
'type' => 'entity_test',
|
||||
'name' => [
|
||||
[
|
||||
'value' => 'Dramallama',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Node;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class NodeJsonAnonTest extends NodeResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Node;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class NodeJsonBasicAuthTest extends NodeResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
|
||||
use JsonBasicAuthWorkaroundFor2805281Trait {
|
||||
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Node;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class NodeJsonCookieTest extends NodeResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
}
|
|
@ -0,0 +1,196 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Node;
|
||||
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
|
||||
use Drupal\user\Entity\User;
|
||||
|
||||
abstract class NodeResourceTestBase extends EntityResourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['node'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $entityTypeId = 'node';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $patchProtectedFieldNames = [
|
||||
'uid',
|
||||
'created',
|
||||
'changed',
|
||||
'promote',
|
||||
'sticky',
|
||||
'revision_timestamp',
|
||||
'revision_uid',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var \Drupal\node\NodeInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
$this->grantPermissionsToTestedRole(['access content']);
|
||||
break;
|
||||
case 'POST':
|
||||
$this->grantPermissionsToTestedRole(['access content', 'create camelids content']);
|
||||
break;
|
||||
case 'PATCH':
|
||||
$this->grantPermissionsToTestedRole(['access content', 'edit any camelids content']);
|
||||
break;
|
||||
case 'DELETE':
|
||||
$this->grantPermissionsToTestedRole(['access content', 'delete any camelids content']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
if (!NodeType::load('camelids')) {
|
||||
// Create a "Camelids" node type.
|
||||
NodeType::create([
|
||||
'name' => 'Camelids',
|
||||
'type' => 'camelids',
|
||||
])->save();
|
||||
}
|
||||
|
||||
// Create a "Llama" node.
|
||||
$node = Node::create(['type' => 'camelids']);
|
||||
$node->setTitle('Llama')
|
||||
->setOwnerId(static::$auth ? $this->account->id() : 0)
|
||||
->setPublished(TRUE)
|
||||
->setCreatedTime(123456789)
|
||||
->setChangedTime(123456789)
|
||||
->setRevisionCreationTime(123456789)
|
||||
->save();
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
$author = User::load($this->entity->getOwnerId());
|
||||
return [
|
||||
'nid' => [
|
||||
['value' => 1],
|
||||
],
|
||||
'uuid' => [
|
||||
['value' => $this->entity->uuid()],
|
||||
],
|
||||
'vid' => [
|
||||
['value' => 1],
|
||||
],
|
||||
'langcode' => [
|
||||
[
|
||||
'value' => 'en',
|
||||
],
|
||||
],
|
||||
'type' => [
|
||||
[
|
||||
'target_id' => 'camelids',
|
||||
'target_type' => 'node_type',
|
||||
'target_uuid' => NodeType::load('camelids')->uuid(),
|
||||
],
|
||||
],
|
||||
'title' => [
|
||||
[
|
||||
'value' => 'Llama',
|
||||
],
|
||||
],
|
||||
'status' => [
|
||||
[
|
||||
'value' => 1,
|
||||
],
|
||||
],
|
||||
'created' => [
|
||||
[
|
||||
'value' => '123456789',
|
||||
],
|
||||
],
|
||||
'changed' => [
|
||||
[
|
||||
'value' => '123456789',
|
||||
],
|
||||
],
|
||||
'promote' => [
|
||||
[
|
||||
'value' => 1,
|
||||
],
|
||||
],
|
||||
'sticky' => [
|
||||
[
|
||||
'value' => '0',
|
||||
],
|
||||
],
|
||||
'revision_timestamp' => [
|
||||
[
|
||||
'value' => '123456789',
|
||||
],
|
||||
],
|
||||
'revision_translation_affected' => [
|
||||
[
|
||||
'value' => TRUE,
|
||||
],
|
||||
],
|
||||
'default_langcode' => [
|
||||
[
|
||||
'value' => TRUE,
|
||||
],
|
||||
],
|
||||
'uid' => [
|
||||
[
|
||||
'target_id' => $author->id(),
|
||||
'target_type' => 'user',
|
||||
'target_uuid' => $author->uuid(),
|
||||
'url' => base_path() . 'user/' . $author->id(),
|
||||
],
|
||||
],
|
||||
'revision_uid' => [
|
||||
[
|
||||
'target_id' => $author->id(),
|
||||
'target_type' => 'user',
|
||||
'target_uuid' => $author->uuid(),
|
||||
'url' => base_path() . 'user/' . $author->id(),
|
||||
],
|
||||
],
|
||||
'revision_log' => [
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
return [
|
||||
'type' => [
|
||||
[
|
||||
'target_id' => 'camelids',
|
||||
],
|
||||
],
|
||||
'title' => [
|
||||
[
|
||||
'value' => 'Dramallama',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Role;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class RoleJsonAnonTest extends RoleResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Role;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class RoleJsonBasicAuthTest extends RoleResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function assertResponseWhenMissingAuthentication(ResponseInterface $response) {
|
||||
$this->assertSame(401, $response->getStatusCode());
|
||||
$this->assertSame('{"message":"A fatal error occurred: No authentication credentials provided."}', (string) $response->getBody());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Role;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class RoleJsonCookieTest extends RoleResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Role;
|
||||
|
||||
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
|
||||
use Drupal\user\Entity\Role;
|
||||
|
||||
abstract class RoleResourceTestBase extends EntityResourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $entityTypeId = 'user_role';
|
||||
|
||||
/**
|
||||
* @var \Drupal\user\RoleInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
$this->grantPermissionsToTestedRole(['administer permissions']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
$role = Role::create([
|
||||
'id' => 'llama',
|
||||
'name' => $this->randomString(),
|
||||
]);
|
||||
$role->save();
|
||||
|
||||
return $role;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
return [
|
||||
'uuid' => $this->entity->uuid(),
|
||||
'weight' => 2,
|
||||
'langcode' => 'en',
|
||||
'status' => TRUE,
|
||||
'dependencies' => [],
|
||||
'id' => 'llama',
|
||||
'label' => NULL,
|
||||
'is_admin' => NULL,
|
||||
'permissions' => [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
// @todo Update in https://www.drupal.org/node/2300677.
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Term;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class TermJsonAnonTest extends TermResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Term;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class TermJsonBasicAuthTest extends TermResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
|
||||
use JsonBasicAuthWorkaroundFor2805281Trait {
|
||||
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Term;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class TermJsonCookieTest extends TermResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue