diff -u b/src/Context/FieldResolver.php b/src/Context/FieldResolver.php --- b/src/Context/FieldResolver.php +++ b/src/Context/FieldResolver.php @@ -5,6 +5,7 @@ use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Field\FieldStorageDefinitionInterface; +use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\TypedData\DataReferenceTargetDefinition; use Drupal\jsonapi\ResourceType\ResourceType; @@ -99,48 +100,41 @@ // works for the time being. $parts = explode('.', $external_field_name); $reference_breadcrumbs = []; + /* @var \Drupal\jsonapi\ResourceType\ResourceType[] $resource_types */ $resource_types = [$resource_type]; - while ($field_name = array_shift($parts)) { - $field_name = $this->getInternalName($field_name, $resource_types); - $bundle_field_definitions = array_filter($this->fieldManager->getFieldDefinitions($entity_type_id, $bundle), function ($element) { - return ($element instanceof BaseFieldDefinition) ? $element : FALSE; - }); - $definitions = $bundle_field_definitions - + $this->fieldManager->getFieldStorageDefinitions($entity_type_id) - // We also need the base field definitions in case there are - // relationships coming from computed fields. - + $this->fieldManager->getBaseFieldDefinitions($entity_type_id); - if (!$definitions) { - throw new BadRequestHttpException(sprintf( - 'Invalid nested filtering. There is no entity type "%s".', - $entity_type_id - )); - } - if (empty($definitions[$field_name])) { + while ($part = array_shift($parts)) { + $field_name = $this->getInternalName($part, $resource_types); + + // Get all possible referenced field definitions. + $definitions = $this->getFieldDefinitions($resource_types, $field_name); + + // If there are no definitions, then the field does not exist. + if (empty($definitions)) { throw new BadRequestHttpException(sprintf( - 'Invalid nested filtering. Invalid entity reference "%s".', - $field_name + 'Invalid nested filtering. The field `%s`, given in the path `%s`, does not exist.', + $part, + $external_field_name )); } + + // Get all of the possibly referenced resource types. + $resource_types = $this->getReferencedResourceTypes($definitions); + + // Keep a trail of entity reference field names. array_push($reference_breadcrumbs, $field_name); - // Update the resource type with the referenced type. - $resource_types = $this->collectResourceTypesForReference($definitions[$field_name]); - // Update the entity type with the referenced type. - $entity_type_id = $definitions[$field_name]->getSetting('target_type'); + // $field_name may not be a reference field. In that case we should treat // the rest of the parts as complex fields. - if (empty($entity_type_id)) { - // This is the path from the initial entity type to the entity type that - // contains $field_name. This path is a set of entity references. - $entity_path = implode('.entity.', $reference_breadcrumbs); - // This is the path from the final entity type to the selected field - // column. + if (empty($resource_types)) { + // This is the remaining path that must be for a complex field. $field_path = implode('.', $parts); - - return implode('.', array_filter([$entity_path, $field_path])); + // Reconstruct the full patch to the final reference field. + $entity_path = implode('.entity.', $reference_breadcrumbs); + return (empty($field_path)) ? $entity_path : $entity_path . '.' . $field_path; } } + // Reconstruct the full patch to the final reference field. return implode('.entity.', $reference_breadcrumbs); } @@ -165,4 +159,41 @@ /** + * Get all the definitions from the given resources by their field name. + * + * @param \Drupal\jsonapi\ResourceType\ResourceType[] $resource_types + * The resource types on which the reference field might exist. + * @param string $field_name + * The field for which to retrieve target resources. + * + * @return \Drupal\Core\Field\FieldDefinitionInterface + * The found field definitions. + */ + protected function getFieldDefinitions(array $resource_types, $field_name) { + return array_reduce($resource_types, function ($all, $resource_type) use ($field_name) { + /* @var \Drupal\jsonapi\ResourceType\ResourceType $resource_type */ + $entity_type = $resource_type->getEntityTypeId(); + $bundle = $resource_type->getBundle(); + $definitions = $this->fieldManager->getFieldDefinitions($entity_type, $bundle); + return (isset($definitions[$field_name])) ? array_merge($all, [$definitions[$field_name]]) : $all; + }, []); + } + + /** + * Get the possibly referenced ResourceTypes with list of field definitions. + * + * @param \Drupal\Core\Field\FieldDefinitionInterface $definitions + * The resource types on which the reference field might exist. + * + * @return \Drupal\jsonapi\ResourceType\ResourceType[] + * The possible target resources. + */ + protected function getReferencedResourceTypes(array $definitions) { + return array_reduce($definitions, function ($all, $definition) { + $resource_types = $this->collectResourceTypesForReference($definition); + return array_merge($all, $resource_types); + }, []); + } + + /** * Build a list of resource types depending on which bundles are referenced. * @@ -172,23 +203,27 @@ * @return \Drupal\jsonapi\ResourceType\ResourceType[] * The list of resource types. */ - protected function collectResourceTypesForReference(FieldStorageDefinitionInterface $field_definition) { - // Check if the field is a flavor of an Entity Reference field. - $main_property_definition = $field_definition->getPropertyDefinition( - $field_definition->getMainPropertyName() - ); - if (!$main_property_definition instanceof DataReferenceTargetDefinition) { + protected function collectResourceTypesForReference(FieldDefinitionInterface $field_definition) { + if ($field_definition->isList()) { + $field_definition = $field_definition->getItemDefinition(); + } + $field_data_type = $field_definition->getDataType(); + // Does the type end with '_reference'? Probably an entity reference. + if (strpos($field_data_type, '_reference') !== strlen($field_data_type) - strlen('_reference')) { return []; } $entity_type_id = $field_definition->getSetting('target_type'); $handler_settings = $field_definition->getSetting('handler_settings'); - if (empty($handler_settings['target_bundles'])) { + if (!isset($handler_settings['target_bundles']) || empty($handler_settings['target_bundles'])) { // If target bundles is NULL it means ALL of the bundles in the entity ID - // are referenceable. + // can be referenced. $bundle_info = $this->entityTypeBundleInfo ->getBundleInfo($entity_type_id); $target_bundles = array_keys($bundle_info); } + else { + $target_bundles = $handler_settings['target_bundles']; + } return array_map(function ($bundle) use ($entity_type_id) { return $this->resourceTypeRepository->get($entity_type_id, $bundle); }, $target_bundles); diff -u b/tests/src/Functional/comment/JsonApiFunctionalCommentTestBase.php b/tests/src/Functional/comment/JsonApiFunctionalCommentTestBase.php --- b/tests/src/Functional/comment/JsonApiFunctionalCommentTestBase.php +++ b/tests/src/Functional/comment/JsonApiFunctionalCommentTestBase.php @@ -98,6 +98,7 @@ FieldConfig::create([ 'field_storage' => $fieldStorageTagsComment, 'label' => $this->randomMachineName(), + 'entity_type' => 'taxonomy_term', 'bundle' => $this->vocabulary->id(), ])->save(); only in patch2: unchanged: --- a/jsonapi.services.yml +++ b/jsonapi.services.yml @@ -132,7 +132,7 @@ services: # Event subscribers. jsonapi.resource_response.subscriber: class: Drupal\jsonapi\EventSubscriber\ResourceResponseSubscriber - arguments: ['@serializer', '@renderer', '@logger.channel.jsonapi'] + arguments: ['@serializer', '@renderer', '@logger.channel.jsonapi', '@module_handler', '@app.root'] calls: - [setValidator, []] - [setSchemaFactory, ['@?schemata.schema_factory']] # This is only injected when the service is available. only in patch2: unchanged: --- a/tests/src/Kernel/Context/FieldResolverTest.php +++ b/tests/src/Kernel/Context/FieldResolverTest.php @@ -152,14 +152,11 @@ class FieldResolverTest extends JsonapiKernelTestBase { $this->assertEquals('field_test1', $this->sut->resolveInternal($entity_type_id, $bundle, 'field_test1')); $this->assertEquals('field_test2', $this->sut->resolveInternal($entity_type_id, $bundle, 'field_test2')); - $this->assertEquals('field_test3', $this->sut->resolveInternal($entity_type_id, $bundle, 'field_test3')); $this->assertEquals('field_test_ref1.entity.field_test1', $this->sut->resolveInternal($entity_type_id, $bundle, 'field_test_ref1.field_test1')); $this->assertEquals('field_test_ref1.entity.field_test2', $this->sut->resolveInternal($entity_type_id, $bundle, 'field_test_ref1.field_test2')); $this->assertEquals('field_test_ref2.entity.field_test1', $this->sut->resolveInternal($entity_type_id, $bundle, 'field_test_ref2.field_test1')); $this->assertEquals('field_test_ref2.entity.field_test2', $this->sut->resolveInternal($entity_type_id, $bundle, 'field_test_ref2.field_test2')); - $this->assertEquals('field_test_ref3.entity.field_test1', $this->sut->resolveInternal($entity_type_id, $bundle, 'field_test_ref3.field_test1')); - $this->assertEquals('field_test_ref3.entity.field_test2', $this->sut->resolveInternal($entity_type_id, $bundle, 'field_test_ref3.field_test2')); $this->assertEquals('field_test_ref1.entity.field_test_text', $this->sut->resolveInternal($entity_type_id, $bundle, 'field_test_ref1.field_test_text')); $this->assertEquals('field_test_ref1.entity.field_test_text.value', $this->sut->resolveInternal($entity_type_id, $bundle, 'field_test_ref1.field_test_text.value')); @@ -167,6 +164,11 @@ class FieldResolverTest extends JsonapiKernelTestBase { $this->assertEquals('field_test_ref2.entity.field_test_text', $this->sut->resolveInternal($entity_type_id, $bundle, 'field_test_ref2.field_test_text')); $this->assertEquals('field_test_ref2.entity.field_test_text.value', $this->sut->resolveInternal($entity_type_id, $bundle, 'field_test_ref2.field_test_text.value')); $this->assertEquals('field_test_ref2.entity.field_test_text.format', $this->sut->resolveInternal($entity_type_id, $bundle, 'field_test_ref2.field_test_text.format')); + + /*$this->assertEquals('field_test3', $this->sut->resolveInternal($entity_type_id, $bundle, 'field_test3')); + $this->assertEquals('field_test_ref3.entity.field_test1', $this->sut->resolveInternal($entity_type_id, $bundle, 'field_test_ref3.field_test1')); + $this->assertEquals('field_test_ref3.entity.field_test2', $this->sut->resolveInternal($entity_type_id, $bundle, 'field_test_ref3.field_test2')); + */ } /**