Commit 85d755a9 authored by alexpott's avatar alexpott

Issue #2664780 by Wim Leers, tedbow, dawehner, klausi: Remove REST's resource-...

Issue #2664780 by Wim Leers, tedbow, dawehner, klausi: Remove REST's resource- and verb-specific permissions for EntityResource, but provide BC and document why it's necessary for other resources
parent 9bc59ded
......@@ -53,13 +53,13 @@ public function testWatchdog() {
$response = $this->httpRequest(Url::fromRoute('rest.dblog.GET.' . $this->defaultFormat, ['id' => 9999, '_format' => $this->defaultFormat]), 'GET');
$this->assertResponse(404);
$decoded = Json::decode($response);
$this->assertEqual($decoded['error'], 'Log entry with ID 9999 was not found', 'Response message is correct.');
$this->assertEqual($decoded['message'], 'Log entry with ID 9999 was not found', 'Response message is correct.');
// Make a bad request (a true malformed request would never be a route match).
$response = $this->httpRequest(Url::fromRoute('rest.dblog.GET.' . $this->defaultFormat, ['id' => 0, '_format' => $this->defaultFormat]), 'GET');
$this->assertResponse(400);
$decoded = Json::decode($response);
$this->assertEqual($decoded['error'], 'No log entry ID was provided', 'Response message is correct.');
$this->assertEqual($decoded['message'], 'No log entry ID was provided', 'Response message is correct.');
}
}
......@@ -8,7 +8,6 @@
use Drupal\entity_test\Entity\EntityTest;
use Drupal\simpletest\WebTestBase;
use Drupal\Core\Cache\Cache;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
/**
......@@ -136,10 +135,6 @@ function testQueryParameterFormatRequests() {
$node = $this->drupalCreateNode(['type' => 'article']);
$node_uri = $node->urlInfo();
$node_url_with_hal_json_format = $node->urlInfo('canonical')->setRouteParameter('_format', 'hal_json');
/** @var \Drupal\user\RoleInterface $role */
$role = Role::load('anonymous');
$role->grantPermission('restful get entity:node');
$role->save();
$this->drupalGet($node_uri);
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
......
# 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
......@@ -6,6 +6,9 @@ rest.settings:
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:
......
......@@ -65,6 +65,16 @@ function rest_update_8202() {
}
}
/**
* 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".
*/
......@@ -12,6 +12,11 @@
/**
* 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
......@@ -179,7 +184,7 @@ public function availableMethods() {
}
/**
* Setups the base route for all HTTP methods.
* Gets the base route for a particular method.
*
* @param string $canonical_path
* The canonical path for the resource.
......@@ -190,20 +195,48 @@ public function availableMethods() {
* The created base route.
*/
protected function getBaseRoute($canonical_path, $method) {
$lower_method = strtolower($method);
$route = new Route($canonical_path, array(
return new Route($canonical_path, array(
'_controller' => 'Drupal\rest\RequestHandler::handle',
), array(
'_permission' => "restful $lower_method $this->pluginId",
),
$this->getBaseRouteRequirements($method),
array(),
'',
array(),
// The HTTP method is a requirement for this route.
array($method)
);
return $route;
}
/**
* 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;
}
}
......@@ -33,6 +33,10 @@ public function routes();
* 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.
*/
......
......@@ -6,6 +6,7 @@
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;
......@@ -42,6 +43,13 @@ class EntityResource extends ResourceBase implements DependentPluginInterface {
*/
protected $entityType;
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* Constructs a Drupal\rest\Plugin\rest\resource\EntityResource object.
*
......@@ -57,10 +65,13 @@ class EntityResource extends ResourceBase implements DependentPluginInterface {
* The available serialization formats.
* @param \Psr\Log\LoggerInterface $logger
* A logger instance.
* @param \Drupal\Core\Config\ConfigFactoryInterface
* The config factory.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, $serializer_formats, LoggerInterface $logger) {
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;
}
/**
......@@ -73,7 +84,8 @@ public static function create(ContainerInterface $container, array $configuratio
$plugin_definition,
$container->get('entity_type.manager'),
$container->getParameter('serializer.formats'),
$container->get('logger.factory')->get('rest')
$container->get('logger.factory')->get('rest'),
$container->get('config.factory')
);
}
......@@ -297,6 +309,21 @@ protected function validate(EntityInterface $entity) {
}
}
/**
* {@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}
*/
......
......@@ -12,7 +12,6 @@
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
use Symfony\Component\Serializer\SerializerInterface;
......@@ -131,17 +130,7 @@ public function handle(RouteMatchInterface $route_match, Request $request) {
// Invoke the operation on the resource plugin.
$format = $this->getResponseFormat($route_match, $request);
try {
$response = call_user_func_array(array($resource, $method), array_merge($parameters, array($unserialized, $request)));
}
catch (HttpException $e) {
$error['error'] = $e->getMessage();
$content = $serializer->serialize($error, $format);
// Add the default content type, but only if the headers from the
// exception have not specified it already.
$headers = $e->getHeaders() + array('Content-Type' => $request->getMimeType($format));
return new Response($content, $e->getStatusCode(), $headers);
}
$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) :
......
......@@ -43,7 +43,6 @@ public function testRead() {
// resources via the REST API, but the request is authenticated
// with session cookies.
$permissions = $this->entityPermissions($entity_type, 'view');
$permissions[] = 'restful get entity:' . $entity_type;
$account = $this->drupalCreateUser($permissions);
$this->drupalLogin($account);
......
......@@ -5,6 +5,7 @@
use Drupal\comment\Tests\CommentTestTrait;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Url;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\node\Entity\Node;
use Drupal\user\Entity\User;
......@@ -49,8 +50,6 @@ public function testCreateResourceRestApiNotEnabled() {
// Get the necessary user permissions to create the current entity type.
$permissions = $this->entityPermissions($entity_type, 'create');
// POST method must be allowed for the current entity type.
$permissions[] = 'restful post entity:' . $entity_type;
// Create the user.
$account = $this->drupalCreateUser($permissions);
......@@ -77,7 +76,11 @@ public function testCreateResourceRestApiNotEnabled() {
/**
* Ensure that an entity cannot be created without the restful permission.
*/
public function testCreateWithoutPermission() {
public function testCreateWithoutPermissionIfBcFlagIsOn() {
$rest_settings = $this->config('rest.settings');
$rest_settings->set('bc_entity_resource_permissions', TRUE)
->save(TRUE);
$entity_type = 'entity_test';
// Enables the REST service for 'entity_test' entity type.
$this->enableService('entity:' . $entity_type, 'POST');
......@@ -96,6 +99,14 @@ public function testCreateWithoutPermission() {
$this->httpRequest('entity/' . $entity_type, 'POST', $serialized, $this->defaultMimeType);
$this->assertResponse(403);
$this->assertFalse(EntityTest::loadMultiple(), 'No entity has been created in the database.');
// Create a user with the 'restful post entity:entity_test permission and
// try again. This time, we should be able to create an entity.
$permissions[] = 'restful post entity:' . $entity_type;
$account = $this->drupalCreateUser($permissions);
$this->drupalLogin($account);
$this->httpRequest('entity/' . $entity_type, 'POST', $serialized, $this->defaultMimeType);
$this->assertResponse(201);
}
/**
......@@ -331,8 +342,6 @@ public function createAccountPerEntity($entity_type) {
$accounts = array();
// Get the necessary user permissions for the current $entity_type creation.
$permissions = $this->entityPermissions($entity_type, 'create');
// POST method must be allowed for the current entity type.
$permissions[] = 'restful post entity:' . $entity_type;
// Create user without administrative permissions.
$accounts[] = $this->drupalCreateUser($permissions);
// Add administrative permissions for nodes and users.
......@@ -440,14 +449,14 @@ public function assertCreateEntityInvalidSerialized(EntityInterface $entity, $en
$entity->set('uuid', $this->randomMachineName(129));
$invalid_serialized = $this->serializer->serialize($entity, $this->defaultFormat, $context);
$response = $this->httpRequest('entity/' . $entity_type, 'POST', $invalid_serialized, $this->defaultMimeType);
$response = $this->httpRequest(Url::fromRoute("rest.entity.$entity_type.POST")->setRouteParameter('_format', $this->defaultFormat), 'POST', $invalid_serialized, $this->defaultMimeType);
// Unprocessable Entity as response.
$this->assertResponse(422);
// Verify that the text of the response is correct.
$error = Json::decode($response);
$this->assertEqual($error['error'], "Unprocessable Entity: validation failed.\nuuid.0.value: <em class=\"placeholder\">UUID</em>: may not be longer than 128 characters.\n");
$this->assertEqual($error['message'], "Unprocessable Entity: validation failed.\nuuid.0.value: <em class=\"placeholder\">UUID</em>: may not be longer than 128 characters.\n");
}
/**
......
......@@ -43,7 +43,6 @@ protected function setUp() {
// Create a user account that has the required permissions to create
// resources via the REST API.
$permissions = $this->entityPermissions($this->testEntityType, 'create');
$permissions[] = 'restful post entity:' . $this->testEntityType;
$this->account = $this->drupalCreateUser($permissions);
// Serialize an entity to a string to use in the content body of the POST
......
......@@ -31,7 +31,6 @@ public function testDelete() {
// Create a user account that has the required permissions to delete
// resources via the REST API.
$permissions = $this->entityPermissions($entity_type, 'delete');
$permissions[] = 'restful delete entity:' . $entity_type;
$account = $this->drupalCreateUser($permissions);
$this->drupalLogin($account);
......
......@@ -32,7 +32,6 @@ class NodeTest extends RESTTestBase {
protected function enableNodeConfiguration($method, $operation) {
$this->enableService('entity:node', $method);
$permissions = $this->entityPermissions('node', $operation);
$permissions[] = 'restful ' . strtolower($method) . ' entity:node';
$account = $this->drupalCreateUser($permissions);
$this->drupalLogin($account);
}
......
......@@ -48,10 +48,6 @@ public function testConfigChangePageCache() {
$this->enableService('entity:entity_test', 'POST');
$permissions = [
'administer entity_test content',
'restful post entity:entity_test',
'restful get entity:entity_test',
'restful patch entity:entity_test',
'restful delete entity:entity_test',
];
$account = $this->drupalCreateUser($permissions);
......
......@@ -48,7 +48,6 @@ public function testRead() {
// Create a user account that has the required permissions to read
// resources via the REST API.
$permissions = $this->entityPermissions($entity_type, 'view');
$permissions[] = 'restful get entity:' . $entity_type;
$account = $this->drupalCreateUser($permissions);
$this->drupalLogin($account);
......@@ -123,12 +122,6 @@ public function testRead() {
$data = Json::decode($response);
$this->assertFalse(isset($data['field_test_text']), 'Field access protected field is not visible in the response.');
}
// Try to read an entity without proper permissions.
$this->drupalLogout();
$response = $this->httpRequest($this->getReadUrl($entity), 'GET');
$this->assertResponse(403);
$this->assertIdentical('{"message":""}', $response);
}
// Try to read a resource, the user entity, which is not REST API enabled.
$account = $this->drupalCreateUser();
......@@ -155,7 +148,6 @@ public function testResourceStructure() {
// Create a user account that has the required permissions to read
// resources via the REST API.
$permissions = $this->entityPermissions('node', 'view');
$permissions[] = 'restful get entity:node';
$account = $this->drupalCreateUser($permissions);
$this->drupalLogin($account);
......
<?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);
}
}
......@@ -46,7 +46,6 @@ public function testPatchUpdate() {
// Create a user account that has the required permissions to create
// resources via the REST API.
$permissions = $this->entityPermissions($entity_type, 'update');
$permissions[] = 'restful patch entity:' . $entity_type;
$account = $this->drupalCreateUser($permissions);
$this->drupalLogin($account);
......@@ -176,10 +175,10 @@ public function testPatchUpdate() {
// Send a UUID that is too long.
$entity->set('uuid', $this->randomMachineName(129));
$invalid_serialized = $serializer->serialize($entity, $this->defaultFormat, $context);
$response = $this->httpRequest($entity->urlInfo(), 'PATCH', $invalid_serialized, $this->defaultMimeType);
$response = $this->httpRequest($entity->toUrl()->setRouteParameter('_format', $this->defaultFormat), 'PATCH', $invalid_serialized, $this->defaultMimeType);
$this->assertResponse(422);
$error = Json::decode($response);
$this->assertEqual($error['error'], "Unprocessable Entity: validation failed.\nuuid.0.value: <em class=\"placeholder\">UUID</em>: may not be longer than 128 characters.\n");
$this->assertEqual($error['message'], "Unprocessable Entity: validation failed.\nuuid.0.value: <em class=\"placeholder\">UUID</em>: may not be longer than 128 characters.\n");
// Try to update an entity without proper permissions.
$this->drupalLogout();
......@@ -202,7 +201,6 @@ public function testUpdateUser() {
// Enables the REST service for 'user' entity type.
$this->enableService('entity:' . $entity_type, 'PATCH');
$permissions = $this->entityPermissions($entity_type, 'update');
$permissions[] = 'restful patch entity:' . $entity_type;
$account = $this->drupalCreateUser($permissions);
$account->set('mail', 'old-email@example.com');
$this->drupalLogin($account);
......@@ -216,18 +214,18 @@ public function testUpdateUser() {
$context = ['account' => $account];
$normalized = $serializer->normalize($account, $this->defaultFormat, $context);
$serialized = $serializer->serialize($normalized, $this->defaultFormat, $context);
$response = $this->httpRequest($account->urlInfo(), 'PATCH', $serialized, $this->defaultMimeType);
$response = $this->httpRequest($account->toUrl()->setRouteParameter('_format', $this->defaultFormat), 'PATCH', $serialized, $this->defaultMimeType);
$this->assertResponse(422);
$error = Json::decode($response);
$this->assertEqual($error['error'], "Unprocessable Entity: validation failed.\nmail: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Email</em>.\n");
$this->assertEqual($error['message'], "Unprocessable Entity: validation failed.\nmail: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Email</em>.\n");
// Try and send the new email with a password.
$normalized['pass'][0]['existing'] = 'wrong';
$serialized = $serializer->serialize($normalized, $this->defaultFormat, $context);
$response = $this->httpRequest($account->urlInfo(), 'PATCH', $serialized, $this->defaultMimeType);
$response = $this->httpRequest($account->toUrl()->setRouteParameter('_format', $this->defaultFormat), 'PATCH', $serialized, $this->defaultMimeType);
$this->assertResponse(422);
$error = Json::decode($response);
$this->assertEqual($error['error'], "Unprocessable Entity: validation failed.\nmail: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Email</em>.\n");
$this->assertEqual($error['message'], "Unprocessable Entity: validation failed.\nmail: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Email</em>.\n");
// Try again with the password.
$normalized['pass'][0]['existing'] = $account->pass_raw;
......@@ -240,10 +238,10 @@ public function testUpdateUser() {
$normalized = $serializer->normalize($account, $this->defaultFormat, $context);
$normalized['pass'][0]['value'] = $new_password;
$serialized = $serializer->serialize($normalized, $this->defaultFormat, $context);
$response = $this->httpRequest($account->urlInfo(), 'PATCH', $serialized, $this->defaultMimeType);
$response = $this->httpRequest($account->toUrl()->setRouteParameter('_format', $this->defaultFormat), 'PATCH', $serialized, $this->defaultMimeType);
$this->assertResponse(422);
$error = Json::decode($response);
$this->assertEqual($error['error'], "Unprocessable Entity: validation failed.\npass: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Password</em>.\n");
$this->assertEqual($error['message'], "Unprocessable Entity: validation failed.\npass: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Password</em>.\n");
// Try again with the password.
$normalized['pass'][0]['existing'] = $account->pass_raw;
......@@ -264,7 +262,6 @@ public function testUpdateComment() {
// Enables the REST service for 'comment' entity type.
$this->enableService('entity:' . $entity_type, 'PATCH', ['hal_json', 'json']);
$permissions = $this->entityPermissions($entity_type, 'update');
$permissions[] = 'restful patch entity:' . $entity_type;
$account = $this->drupalCreateUser($permissions);
$account->set('mail', 'old-email@example.com');
$this->drupalLogin($account);
......@@ -336,7 +333,7 @@ public function testUpdateComment() {
protected function patchEntity(EntityInterface $entity, array $read_only_fields, AccountInterface $account, $format, $mime_type) {
$serializer = $this->container->get('serializer');
$url = $entity->toUrl();
$url = $entity->toUrl()->setRouteParameter('_format', $this->defaultFormat);
$context = ['account' => $account];
// Certain fields are always read-only, others this user simply is not
// allowed to modify. For all of them, ensure they are not serialized, else
......@@ -359,7 +356,7 @@ protected function patchEntity(EntityInterface $entity, array $read_only_fields,
$this->httpRequest($url, 'PATCH', $serialized, $mime_type);
$this->assertResponse(403);
$this->assertResponseBody('{"error":"Access denied on updating field \'' . $field . '\'."}');
$this->assertResponseBody('{"message":"Access denied on updating field \\u0027' . $field . '\\u0027."}');
if ($format === 'hal_json') {
// We've just tried with this read-only field, now unset it.
......
<?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;',
])
->fields([
'collection' => 'system.schema',
'name' => 'serialization',
'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'] = 8000;
$extensions['module']['serialization'] = 8000;
$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' => '~',
];
$data = $connection->insert('config')
->fields([
'name' => 'rest.settings',
'data' => serialize($config),
'collection' => ''
])
->execute();
......@@ -26,7 +26,6 @@ protected function setUp() {
$this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
$permissions = $this->entityPermissions('node', 'view');
$permissions[] = 'restful get entity:node';
$account = $this->drupalCreateUser($permissions);
$this->drupalLogin($account);
}
......
......@@ -39,7 +39,7 @@ public function __construct(ViewExecutable $view, LanguageManagerInterface $lang
/**
* {@inheritdoc}
*/
public function getLangcode(ResultRow $row) {
public function getLangcode(ResultRow $row, $relationship = 'none') {
return $this->langcode;
}
......
......@@ -12,8 +12,10 @@ class DefaultLanguageRenderer extends EntityTranslationRendererBase {
/**
* {@inheritdoc}
*/
public function getLangcode(ResultRow $row) {
return $row->_entity->getUntranslated()->language()->getId();
public function getLangcode(ResultRow $row, $relationship = 'none') {
if ($entity = $this->getEntity($row, $relationship)) {
return $entity->getUntranslated()->language()->getId();
}
}
}
......@@ -200,7 +200,8 @@ protected function buildFields(array $values) {
$field = $this->view->field[current($field_ids)];
foreach ($values as $result_row) {
if ($entity = $field->getEntity($result_row)) {
$entities_by_bundles[$entity->bundle()][$result_row->index] = $this->getEntityTranslation($entity, $result_row);
$relationship = isset($field->options['relationship']) ? $field->options['relationship'] : 'none';
$entities_by_bundles[$entity->bundle()][$result_row->index] = $this->getEntityTranslation($entity, $result_row, $relationship);
}
}
......
......@@ -62,18 +62,20 @@ protected function getEntityTranslationRenderer() {
* The entity object the field value being processed is attached to.
* @param \Drupal\views\ResultRow $row
* The result row the field value being processed belongs to.
* @param string $relationship
* The relationship to be used, or 'none' by default.
*
* @return \Drupal\Core\Entity\FieldableEntityInterface
* The entity translation object for the specified row.
*/
public function getEntityTranslation(EntityInterface $entity, ResultRow $row) {
public function getEntityTranslation(EntityInterface $entity, ResultRow $row, $relationship = 'none') {
// We assume the same language should be used for all entity fields
// belonging to a single row, even if they are attached to different entity
// types. Below we apply language fallback to ensure a valid value is always
// picked.
$translation = $entity;
if ($entity instanceof TranslatableInterface && count($entity->getTranslationLanguages()) > 1) {
$langcode = $this->getEntityTranslationRenderer()->getLangcode($row);
$langcode = $this->getEntityTranslationRenderer()->getLangcode($row, $relationship);
$translation = $this->getEntityManager()->getTranslationFromContext($entity, $langcode);
}
return $translation;
......
......@@ -15,11 +15,13 @@ abstract class EntityTranslationRendererBase extends RendererBase {
*
* @param \Drupal\views\ResultRow $row
* The result row.
* @param string $relationship
* The relationship to be used, or 'none' by default.
*
* @return string
* A language code.
*/
abstract public function getLangcode(ResultRow $row);
abstract public function getLangcode(ResultRow $row, $relationship = 'none');