src/Access/RelationshipFieldAccess.php | 7 ++++-- src/Context/FieldResolver.php | 7 +++--- .../JsonApiDocumentTopLevelNormalizer.php | 6 ++--- src/Normalizer/RelationshipNormalizer.php | 6 +++-- src/ResourceType/ResourceTypeRepository.php | 8 ++++++- tests/src/Functional/NodeTest.php | 2 +- tests/src/Functional/ResourceResponseTestTrait.php | 5 +++-- tests/src/Functional/ResourceTestBase.php | 26 +++++++++++++++++----- 8 files changed, 47 insertions(+), 20 deletions(-) diff --git a/src/Access/RelationshipFieldAccess.php b/src/Access/RelationshipFieldAccess.php index 51a85b5..82f7622 100644 --- a/src/Access/RelationshipFieldAccess.php +++ b/src/Access/RelationshipFieldAccess.php @@ -7,6 +7,7 @@ use Drupal\Core\Access\AccessResultReasonInterface; use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Routing\Access\AccessInterface; use Drupal\Core\Session\AccountInterface; +use Drupal\jsonapi\ResourceType\ResourceType; use Drupal\jsonapi\Routing\Routes; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Route; @@ -43,10 +44,12 @@ class RelationshipFieldAccess implements AccessInterface { $field_operation = $request->isMethodCacheable() ? 'view' : 'edit'; $entity_operation = $request->isMethodCacheable() ? 'view' : 'update'; if ($resource_type = $request->get(Routes::RESOURCE_TYPE_KEY)) { + assert($resource_type instanceof ResourceType); $entity = $request->get($resource_type->getEntityTypeId()); - if ($entity instanceof FieldableEntityInterface && $entity->hasField($relationship_field_name)) { + $internal_name = $resource_type->getInternalName($relationship_field_name); + if ($entity instanceof FieldableEntityInterface && $entity->hasField($internal_name)) { $entity_access = $entity->access($entity_operation, $account, TRUE); - $field_access = $entity->get($relationship_field_name)->access($field_operation, $account, TRUE); + $field_access = $entity->get($internal_name)->access($field_operation, $account, TRUE); $access_result = $entity_access->andIf($field_access); if (!$access_result->isAllowed()) { $reason = "The current user is not allowed to {$field_operation} this relationship."; diff --git a/src/Context/FieldResolver.php b/src/Context/FieldResolver.php index 2660013..e7a7dc7 100644 --- a/src/Context/FieldResolver.php +++ b/src/Context/FieldResolver.php @@ -155,10 +155,11 @@ class FieldResolver { if (empty($path_parts)) { throw new BadRequestHttpException('Empty include path.'); } - $internal_field_name = $resource_type->getInternalName($path_parts[0]); - $relatable_resource_types = $resource_type->getRelatableResourceTypesByField($internal_field_name); + $public_field_name = $path_parts[0]; + $internal_field_name = $resource_type->getInternalName($public_field_name); + $relatable_resource_types = $resource_type->getRelatableResourceTypesByField($public_field_name); if (empty($relatable_resource_types)) { - $message = "`$internal_field_name` is not a valid relationship field name."; + $message = "`$public_field_name` is not a valid relationship field name."; if (!empty(($possible = implode(', ', array_keys($resource_type->getRelatableResourceTypes()))))) { $message .= " Possible values: $possible."; } diff --git a/src/Normalizer/JsonApiDocumentTopLevelNormalizer.php b/src/Normalizer/JsonApiDocumentTopLevelNormalizer.php index a10cf18..37ae240 100644 --- a/src/Normalizer/JsonApiDocumentTopLevelNormalizer.php +++ b/src/Normalizer/JsonApiDocumentTopLevelNormalizer.php @@ -224,7 +224,7 @@ class JsonApiDocumentTopLevelNormalizer extends NormalizerBase implements Denorm // primary resource type of individual and relationship routes and is // determined by the relationship field name. $related = $request->get('_on_relationship') ? FALSE : $request->get('related'); - $public_includes = array_map(function ($include) use ($resource_type, $related) { + $internal_includes = array_map(function ($include) use ($resource_type, $related) { $trimmed = trim($include); // If the request is a related route, prefix the path with the related // field name so that the path can be resolved from the base resource @@ -235,13 +235,13 @@ class JsonApiDocumentTopLevelNormalizer extends NormalizerBase implements Denorm }, FieldResolver::resolveInternalIncludePath($resource_type, $path_parts)); }, $includes); // Flatten the resolved possible include paths. - $public_includes = array_reduce($public_includes, 'array_merge', []); + $internal_includes = array_reduce($internal_includes, 'array_merge', []); // Build the expanded context. $context = [ 'account' => NULL, 'sparse_fieldset' => NULL, 'resource_type' => NULL, - 'include' => $public_includes, + 'include' => $internal_includes, 'expanded' => TRUE, ]; if ($request->query->get('fields')) { diff --git a/src/Normalizer/RelationshipNormalizer.php b/src/Normalizer/RelationshipNormalizer.php index e9f43e9..15c2cc8 100644 --- a/src/Normalizer/RelationshipNormalizer.php +++ b/src/Normalizer/RelationshipNormalizer.php @@ -208,11 +208,13 @@ class RelationshipNormalizer extends NormalizerBase implements DenormalizerInter $normalizer_items[] = $this->serializer->normalize($relationship_item, $format, $context); } $cardinality = $relationship->getCardinality(); + assert($context['resource_type'] instanceof ResourceType); + $resource_type = $context['resource_type']; $link_context = [ 'host_entity_id' => $relationship->getHostEntity()->uuid(), - 'field_name' => $relationship->getPropertyName(), + 'field_name' => $resource_type->getPublicName($relationship->getPropertyName()), 'link_manager' => $this->linkManager, - 'resource_type' => $context['resource_type'], + 'resource_type' => $resource_type, ]; // If this is called, access to the Relationship field is allowed. The // cacheability of the access result is carried by the Relationship value diff --git a/src/ResourceType/ResourceTypeRepository.php b/src/ResourceType/ResourceTypeRepository.php index a6eb6a4..9e6a888 100644 --- a/src/ResourceType/ResourceTypeRepository.php +++ b/src/ResourceType/ResourceTypeRepository.php @@ -299,11 +299,17 @@ class ResourceTypeRepository implements ResourceTypeRepositoryInterface { $resource_type->getBundle() ); - return array_map(function ($field_definition) { + $relatable_internal = array_map(function ($field_definition) { return $this->getRelatableResourceTypesFromFieldDefinition($field_definition); }, array_filter($field_definitions, function ($field_definition) { return $this->isReferenceFieldDefinition($field_definition); })); + + $relatable_public = []; + foreach ($relatable_internal as $internal_field_name => $value) { + $relatable_public[$resource_type->getPublicName($internal_field_name)] = $value; + } + return $relatable_public; } return []; } diff --git a/tests/src/Functional/NodeTest.php b/tests/src/Functional/NodeTest.php index 370f1d4..b1e3e0d 100644 --- a/tests/src/Functional/NodeTest.php +++ b/tests/src/Functional/NodeTest.php @@ -330,7 +330,7 @@ class NodeTest extends ResourceTestBase { */ protected static function getIncludePermissions() { return [ - 'uid.type' => ['administer users'], + 'uid.node_type' => ['administer users'], 'uid.roles' => ['administer permissions'], ]; } diff --git a/tests/src/Functional/ResourceResponseTestTrait.php b/tests/src/Functional/ResourceResponseTestTrait.php index d5e1cd3..d22f027 100644 --- a/tests/src/Functional/ResourceResponseTestTrait.php +++ b/tests/src/Functional/ResourceResponseTestTrait.php @@ -146,7 +146,8 @@ trait ResourceResponseTestTrait { $resource_data = array_reduce($include_paths, function ($data, $path) use ($request_options) { $field_names = explode('.', $path); $entity = $this->entity; - foreach ($field_names as $field_name) { + foreach ($field_names as $public_field_name) { + $field_name = $this->resourceType->getInternalName($public_field_name); $collected_responses = []; $field_access = static::entityFieldAccess($entity, $field_name, 'view', $this->account); if (!$field_access->isAllowed()) { @@ -169,7 +170,7 @@ trait ResourceResponseTestTrait { break; } } - $psr_responses = $this->getResponses([static::getRelatedLink(static::toResourceIdentifier($entity), $field_name)], $request_options); + $psr_responses = $this->getResponses([static::getRelatedLink(static::toResourceIdentifier($entity), $public_field_name)], $request_options); $collected_responses[] = static::toCollectionResourceResponse(static::toResourceResponses($psr_responses), NULL, TRUE); $entity = $entity->{$field_name}->entity; } diff --git a/tests/src/Functional/ResourceTestBase.php b/tests/src/Functional/ResourceTestBase.php index c5bb3b6..58771c7 100644 --- a/tests/src/Functional/ResourceTestBase.php +++ b/tests/src/Functional/ResourceTestBase.php @@ -72,6 +72,15 @@ abstract class ResourceTestBase extends BrowserTestBase { protected static $resourceTypeName = NULL; /** + * The JSON API resource type for the tested entity type plus bundle. + * + * Necessary for looking up public (alias) or internal (actual) field names. + * + * @var \Drupal\jsonapi\ResourceType\ResourceType + */ + protected $resourceType; + + /** * The fields that are protected against modification during PATCH requests. * * @var string[] @@ -197,6 +206,8 @@ abstract class ResourceTestBase extends BrowserTestBase { // Create an entity. $this->entityStorage = $this->container->get('entity_type.manager')->getStorage(static::$entityTypeId); $this->entity = $this->setUpFields($this->createEntity(), $this->account); + + $this->resourceType = $this->container->get('jsonapi.resource_type.repository')->getByTypeName(static::$resourceTypeName); } /** @@ -1591,7 +1602,7 @@ abstract class ResourceTestBase extends BrowserTestBase { */ protected function getExpectedGetRelationshipResponse($relationship_field_name, EntityInterface $entity = NULL) { $entity = $entity ?: $this->entity; - $access = static::entityFieldAccess($entity, $relationship_field_name, 'view', $this->account); + $access = static::entityFieldAccess($entity, $this->resourceType->getInternalName($relationship_field_name), 'view', $this->account); if (!$access->isAllowed()) { return static::getAccessDeniedResponse($this->entity, $access, $relationship_field_name, 'The current user is not allowed to view this relationship.', FALSE); } @@ -1650,8 +1661,9 @@ abstract class ResourceTestBase extends BrowserTestBase { */ protected function getExpectedGetRelationshipDocumentData($relationship_field_name, EntityInterface $entity = NULL) { $entity = $entity ?: $this->entity; + $internal_field_name = $this->resourceType->getInternalName($relationship_field_name); /* @var \Drupal\Core\Field\FieldItemListInterface $field */ - $field = $entity->{$relationship_field_name}; + $field = $entity->{$internal_field_name}; $is_multiple = $field->getFieldDefinition()->getFieldStorageDefinition()->getCardinality() !== 1; if ($field->isEmpty()) { return $is_multiple ? [] : NULL; @@ -1695,7 +1707,8 @@ abstract class ResourceTestBase extends BrowserTestBase { $base_resource_identifier = static::toResourceIdentifier($entity); $expected_related_responses = []; foreach ($relationship_field_names as $relationship_field_name) { - $access = static::entityFieldAccess($entity, $relationship_field_name, 'view', $this->account); + $internal_name = $this->resourceType->getInternalName($relationship_field_name); + $access = static::entityFieldAccess($entity, $internal_name, 'view', $this->account); if (!$access->isAllowed()) { $detail = 'The current user is not allowed to view this relationship.'; if ($access instanceof AccessResultReasonInterface && ($reason = $access->getReason())) { @@ -2674,7 +2687,7 @@ abstract class ResourceTestBase extends BrowserTestBase { } /** - * Gets a list of relationship field names for the resource type under test. + * Gets a list of public relationship names for the resource type under test. * * @param \Drupal\Core\Entity\EntityInterface|null $entity * (optional) The entity for which to get relationship field names. @@ -2691,7 +2704,7 @@ abstract class ResourceTestBase extends BrowserTestBase { return array_reduce($fields, function ($field_names, $field) { /* @var \Drupal\Core\Field\FieldItemListInterface $field */ if (static::isReferenceFieldDefinition($field->getFieldDefinition())) { - $field_names[] = $field->getName(); + $field_names[] = $this->resourceType->getPublicName($field->getName()); } return $field_names; }, []); @@ -2770,7 +2783,8 @@ abstract class ResourceTestBase extends BrowserTestBase { /* $paths = []; */ foreach ($relationship_field_names as $field_name) { $next = ($path) ? "$path.$field_name" : $field_name; - if ($target_entity = $entity->{$field_name}->entity) { + $internal_field_name = $this->resourceType->getInternalName($field_name); + if ($target_entity = $entity->{$internal_field_name}->entity) { $deep = $get_nested_relationship_field_names($target_entity, $depth - 1, $next); $paths = array_merge($paths, $deep); }