Commit 6749853b authored by alexpott's avatar alexpott
Browse files

Issue #2631774 by Wim Leers, marthinal, dawehner, tedbow, tyler.frankenstein,...

Issue #2631774 by Wim Leers, marthinal, dawehner, tedbow, tyler.frankenstein, gabesullice, valthebald: Impossible to update Comment entity with REST (HTTP PATCH): bundle field not allowed to be updated, but EntityNormalizer::denormalize() requires it
parent 95481848
......@@ -233,9 +233,12 @@ function rdf_comment_storage_load($comments) {
// to optimize performance for websites that implement an entity cache.
$created_mapping = rdf_get_mapping('comment', $comment->bundle())
->getPreparedFieldMapping('created');
/** @var \Drupal\comment\CommentInterface $comment*/
$comment->rdf_data['date'] = rdf_rdfa_attributes($created_mapping, $comment->get('created')->first()->toArray());
$entity = $comment->getCommentedEntity();
$comment->rdf_data['entity_uri'] = $entity->url();
// The current function is a storage level hook, so avoid to bubble
// bubbleable metadata, because it can be outside of a render context.
$comment->rdf_data['entity_uri'] = $entity->toUrl()->toString(TRUE)->getGeneratedUrl();
if ($comment->hasParentComment()) {
$comment->rdf_data['pid_uri'] = $comment->getParentComment()->url();
}
......
......@@ -146,13 +146,24 @@ public function patch(EntityInterface $original_entity, EntityInterface $entity
}
// Overwrite the received properties.
$langcode_key = $entity->getEntityType()->getKey('langcode');
$entity_keys = $entity->getEntityType()->getKeys();
foreach ($entity->_restSubmittedFields as $field_name) {
$field = $entity->get($field_name);
// It is not possible to set the language to NULL as it is automatically
// re-initialized. As it must not be empty, skip it if it is.
if ($field_name == $langcode_key && $field->isEmpty()) {
continue;
// 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')) {
......
......@@ -250,8 +250,8 @@ protected function entityValues($entity_type) {
* resource types.
* @param string $method
* The HTTP method to enable, e.g. GET, POST etc.
* @param string $format
* (Optional) The serialization format, e.g. hal_json.
* @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.
*/
......@@ -261,10 +261,15 @@ protected function enableService($resource_type, $method = 'GET', $format = NULL
$settings = array();
if ($resource_type) {
if ($format == NULL) {
$format = $this->defaultFormat;
if (is_array($format)) {
$settings[$resource_type][$method]['supported_formats'] = $format;
}
else {
if ($format == NULL) {
$format = $this->defaultFormat;
}
$settings[$resource_type][$method]['supported_formats'][] = $format;
}
$settings[$resource_type][$method]['supported_formats'][] = $format;
if ($auth == NULL) {
$auth = $this->defaultAuth;
......
......@@ -2,7 +2,12 @@
namespace Drupal\rest\Tests;
use Drupal\comment\Entity\Comment;
use Drupal\comment\Tests\CommentTestTrait;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\entity_test\Entity\EntityTest;
/**
* Tests the update of resources.
......@@ -11,12 +16,22 @@
*/
class UpdateTest extends RESTTestBase {
use CommentTestTrait;
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('hal', 'rest', 'entity_test');
public static $modules = ['hal', 'rest', 'entity_test', 'comment'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->addDefaultCommentField('entity_test', 'entity_test');
}
/**
* Tests several valid and invalid partial update requests on test entities.
......@@ -220,7 +235,129 @@ public function testUpdateUser() {
// Verify that we can log in with the new password.
$account->pass_raw = $new_password;
$this->drupalLogin($account);
}
/**
* Test patching a comment using both HAL+JSON and JSON.
*/
public function testUpdateComment() {
$entity_type = 'comment';
// 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);
// Create & save an entity to comment on, plus a comment.
$entity_test = EntityTest::create();
$entity_test->save();
$entity_values = $this->entityValues($entity_type);
$entity_values['entity_id'] = $entity_test->id();
$entity_values['uid'] = $account->id();
$comment = Comment::create($entity_values);
$comment->save();
$this->pass('Test case 1: PATCH comment using HAL+JSON.');
$comment->setSubject('Initial subject')->save();
$read_only_fields = [
'name',
'created',
'changed',
'status',
'thread',
'entity_type',
'field_name',
'entity_id',
'uid',
];
$this->assertNotEqual('Updated subject', $comment->getSubject());
$comment->setSubject('Updated subject');
$this->patchEntity($comment, $read_only_fields, $account, 'hal_json', 'application/hal+json');
$comment = Comment::load($comment->id());
$this->assertEqual('Updated subject', $comment->getSubject());
$this->pass('Test case 1: PATCH comment using JSON.');
$comment->setSubject('Initial subject')->save();
$read_only_fields = [
'pid', // Extra compared to HAL+JSON.
'entity_id',
'uid',
'name',
'homepage', // Extra compared to HAL+JSON.
'created',
'changed',
'status',
'thread',
'entity_type',
'field_name',
];
$this->assertNotEqual('Updated subject', $comment->getSubject());
$comment->setSubject('Updated subject');
$this->patchEntity($comment, $read_only_fields, $account, 'json', 'application/json');
$comment = Comment::load($comment->id());
$this->assertEqual('Updated subject', $comment->getSubject());
}
/**
* Patches an existing entity using the passed in (modified) entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The updated entity to send.
* @param string[] $read_only_fields
* Names of the fields that are read-only, in validation order.
* @param \Drupal\Core\Session\AccountInterface $account
* The account to use for serialization.
* @param string $format
* A serialization format.
* @param string $mime_type
* The MIME type corresponding to the specified serialization format.
*/
protected function patchEntity(EntityInterface $entity, array $read_only_fields, AccountInterface $account, $format, $mime_type) {
$serializer = $this->container->get('serializer');
$url = $entity->toUrl();
$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
// we'll get a 403 plus an error message.
for ($i = 0; $i < count($read_only_fields); $i++) {
$field = $read_only_fields[$i];
$normalized = $serializer->normalize($entity, $format, $context);
if ($format !== 'hal_json') {
// The default normalizer always keeps fields, even if they are unset
// here because they should be omitted during a PATCH request. Therefore
// manually strip them
// @see \Drupal\Core\Entity\ContentEntityBase::__unset()
// @see \Drupal\serialization\Normalizer\EntityNormalizer::normalize()
// @see \Drupal\hal\Normalizer\ContentEntityNormalizer::normalize()
$read_only_fields_so_far = array_slice($read_only_fields, 0, $i);
$normalized = array_diff_key($normalized, array_flip($read_only_fields_so_far));
}
$serialized = $serializer->serialize($normalized, $format, $context);
$this->httpRequest($url, 'PATCH', $serialized, $mime_type);
$this->assertResponse(403);
$this->assertResponseBody('{"error":"Access denied on updating field \'' . $field . '\'."}');
if ($format === 'hal_json') {
// We've just tried with this read-only field, now unset it.
$entity->set($field, NULL);
}
}
// Finally, with all read-only fields unset, the request should succeed.
$normalized = $serializer->normalize($entity, $format, $context);
if ($format !== 'hal_json') {
$normalized = array_diff_key($normalized, array_combine($read_only_fields, $read_only_fields));
}
$serialized = $serializer->serialize($normalized, $format, $context);
$this->httpRequest($url, 'PATCH', $serialized, $mime_type);
$this->assertResponse(204);
$this->assertResponseBody('');
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment