diff --git a/core/modules/jsonapi/src/IncludeResolver.php b/core/modules/jsonapi/src/IncludeResolver.php index e68c9b218ebf41ee8eefb5aea40ce64624a476f7..ddf75ad0b9a910f0e6c79283d4a300ad607db8cb 100644 --- a/core/modules/jsonapi/src/IncludeResolver.php +++ b/core/modules/jsonapi/src/IncludeResolver.php @@ -106,16 +106,18 @@ protected function resolveIncludeTree(array $include_tree, Data $data, Data $inc // Some objects in the collection may be LabelOnlyResourceObjects or // EntityAccessDeniedHttpException objects. assert($resource_object instanceof ResourceIdentifierInterface); + $public_field_name = $resource_object->getResourceType()->getPublicName($field_name); + if ($resource_object instanceof LabelOnlyResourceObject) { $message = "The current user is not allowed to view this relationship."; - $exception = new EntityAccessDeniedHttpException($resource_object->getEntity(), AccessResult::forbidden("The user only has authorization for the 'view label' operation."), '', $message, $field_name); + $exception = new EntityAccessDeniedHttpException($resource_object->getEntity(), AccessResult::forbidden("The user only has authorization for the 'view label' operation."), '', $message, $public_field_name); $includes = IncludedData::merge($includes, new IncludedData([$exception])); continue; } elseif (!$resource_object instanceof ResourceObject) { continue; } - $public_field_name = $resource_object->getResourceType()->getPublicName($field_name); + // Not all entities in $entity_collection will be of the same bundle and // may not have all of the same fields. Therefore, calling // $resource_object->get($a_missing_field_name) will result in an diff --git a/core/modules/jsonapi/tests/src/Kernel/Normalizer/JsonApiDocumentTopLevelNormalizerTest.php b/core/modules/jsonapi/tests/src/Kernel/Normalizer/JsonApiDocumentTopLevelNormalizerTest.php index 6fa0ff0bb2b778b964ccacf6798a0904ed4dd993..14e73c44fd357cd334d85d69cce9074e3f0d30e8 100644 --- a/core/modules/jsonapi/tests/src/Kernel/Normalizer/JsonApiDocumentTopLevelNormalizerTest.php +++ b/core/modules/jsonapi/tests/src/Kernel/Normalizer/JsonApiDocumentTopLevelNormalizerTest.php @@ -9,6 +9,7 @@ use Drupal\Core\Url; use Drupal\file\Entity\File; use Drupal\jsonapi\JsonApiResource\ErrorCollection; +use Drupal\jsonapi\JsonApiResource\LabelOnlyResourceObject; use Drupal\jsonapi\JsonApiResource\LinkCollection; use Drupal\jsonapi\JsonApiResource\NullIncludedData; use Drupal\jsonapi\JsonApiResource\ResourceObject; @@ -54,6 +55,7 @@ class JsonApiDocumentTopLevelNormalizerTest extends JsonapiKernelTestBase { 'file', 'image', 'jsonapi_test_normalizers_kernel', + 'jsonapi_test_resource_type_building', ]; /** @@ -77,6 +79,13 @@ class JsonApiDocumentTopLevelNormalizerTest extends JsonapiKernelTestBase { */ protected $includeResolver; + /** + * The JSON:API resource type repository under test. + * + * @var \Drupal\jsonapi\ResourceType\ResourceTypeRepository + */ + protected $resourceTypeRepository; + /** * {@inheritdoc} */ @@ -178,6 +187,7 @@ protected function setUp(): void { ])->save(); $this->includeResolver = $this->container->get('jsonapi.include_resolver'); + $this->resourceTypeRepository = $this->container->get('jsonapi.resource_type.repository'); } /** @@ -408,6 +418,70 @@ public function testNormalizeException() { ], $normalized['errors'][0]['links']); } + /** + * Test the message and exceptions thrown when we are requesting additional + * field values for Label only resource. + */ + public function testAliasFieldRouteException() { + $this->assertSame('uid', $this->resourceTypeRepository->getByTypeName('node--article')->getPublicName('uid')); + $this->assertSame('roles', $this->resourceTypeRepository->getByTypeName('user--user')->getPublicName('roles')); + $resource_type_field_aliases = [ + 'node--article' => [ + 'uid' => 'author', + ], + 'user--user' => [ + 'roles' => 'user_roles', + ], + ]; + \Drupal::state()->set('jsonapi_test_resource_type_builder.resource_type_field_aliases', $resource_type_field_aliases); + Cache::invalidateTags(['jsonapi_resource_types']); + $this->assertSame('author', $this->resourceTypeRepository->getByTypeName('node--article')->getPublicName('uid')); + $this->assertSame('user_roles', $this->resourceTypeRepository->getByTypeName('user--user')->getPublicName('roles')); + + // Create the request to fetch the articles and fetch included user. + list($request, $resource_type) = $this->generateProphecies('node', 'article'); + $user = User::load($this->node->getOwnerId()); + + $resource_object = ResourceObject::createFromEntity($resource_type, $this->node); + list($request, $user_resource_type) = $this->generateProphecies('user', 'user'); + $resource_object_user = LabelOnlyResourceObject::createFromEntity($user_resource_type, $user); + $includes = $this->includeResolver->resolve($resource_object_user, 'user_roles'); + + /** @var \Drupal\jsonapi\Normalizer\Value\CacheableNormalization $jsonapi_doc_object */ + $jsonapi_doc_object = $this + ->getNormalizer() + ->normalize( + new JsonApiDocumentTopLevel(new ResourceObjectData([$resource_object, $resource_object_user], 2), $includes, new LinkCollection([])), + 'api_json', + [ + 'resource_type' => $resource_type, + 'account' => NULL, + 'sparse_fieldset' => [ + 'node--article' => [ + 'title', + 'node_type', + 'uid', + ], + 'user--user' => [ + 'user_roles', + ], + ], + 'include' => [ + 'user_roles', + ], + ], + )->getNormalization(); + $this->assertNotEmpty($jsonapi_doc_object['meta']['omitted']); + foreach ($jsonapi_doc_object['meta']['omitted']['links'] as $key => $link) { + if (strpos($key, 'item--') === 0) { + // Ensure that resource link contains url with the alias field. + $resource_link = Url::fromUri('internal:/jsonapi/user/user/' . $user->uuid() . '/user_roles')->setAbsolute()->toString(TRUE); + $this->assertEquals($resource_link->getGeneratedUrl(), $link['href']); + $this->assertEquals("The current user is not allowed to view this relationship. The user only has authorization for the 'view label' operation.", $link['meta']['detail']); + } + } + } + /** * @covers ::normalize */