From da5ea0aeda0780158c0f39b98dd00c8ba53d4c6e Mon Sep 17 00:00:00 2001 From: Lee Rowlands <lee.rowlands@previousnext.com.au> Date: Mon, 3 Jul 2023 16:40:34 +1000 Subject: [PATCH] Issue #3057545 by acbramley, hchonov, bbrala, bradjones1, larowlan, yogeshmpawar, Leon Kessler, gease, joachim, gabesullice, kfritsche, jibran, Wim Leers, Berdir, smustgrave, alexpott, catch: ResourceTypeRepository wrongly assumes that all entity reference fields have the setting "target_type" --- .../Field/FieldType/EntityReferenceItem.php | 18 ++++- .../FieldType/EntityReferenceItemBase.php | 16 +++++ .../EntityReferenceItemInterface.php | 29 +++++++++ core/modules/jsonapi/src/IncludeResolver.php | 34 ++++++++-- .../ResourceType/ResourceTypeRepository.php | 59 +++++++++++------ .../jsonapi_test_reference_types.info.yml | 3 + .../jsonapi_test_reference_types.module | 24 +++++++ .../FieldType/DeprecatedReferenceItem.php | 65 +++++++++++++++++++ .../jsonapi/tests/src/Functional/NodeTest.php | 22 +++++++ .../ResourceType/RelatedResourceTypesTest.php | 11 ++++ 10 files changed, 252 insertions(+), 29 deletions(-) create mode 100644 core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItemBase.php create mode 100644 core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItemInterface.php create mode 100644 core/modules/jsonapi/tests/modules/jsonapi_test_reference_types/jsonapi_test_reference_types.info.yml create mode 100644 core/modules/jsonapi/tests/modules/jsonapi_test_reference_types/jsonapi_test_reference_types.module create mode 100644 core/modules/jsonapi/tests/modules/jsonapi_test_reference_types/src/Plugin/Field/FieldType/DeprecatedReferenceItem.php diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php index c9b2623233f6..7e5b99511e71 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php @@ -13,7 +13,6 @@ use Drupal\Core\Entity\TypedData\EntityDataDefinition; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldException; -use Drupal\Core\Field\FieldItemBase; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\Field\PreconfiguredFieldUiOptionsInterface; use Drupal\Core\Form\FormStateInterface; @@ -42,7 +41,7 @@ * list_class = "\Drupal\Core\Field\EntityReferenceFieldItemList", * ) */ -class EntityReferenceItem extends FieldItemBase implements OptionsProviderInterface, PreconfiguredFieldUiOptionsInterface { +class EntityReferenceItem extends EntityReferenceItemBase implements OptionsProviderInterface, PreconfiguredFieldUiOptionsInterface { /** * {@inheritdoc} @@ -778,4 +777,19 @@ public static function getPreconfiguredOptions() { return $options; } + /** + * {@inheritdoc} + */ + public static function getReferenceableBundles(FieldDefinitionInterface $field_definition): array { + $settings = $field_definition->getSettings(); + $target_type_id = $settings['target_type']; + $handler_settings = $settings['handler_settings']; + + $has_target_bundles = isset($handler_settings['target_bundles']) && !empty($handler_settings['target_bundles']); + $target_bundles = $has_target_bundles + ? $handler_settings['target_bundles'] + : array_keys(\Drupal::service('entity_type.bundle.info')->getBundleInfo($target_type_id)); + return [$target_type_id => $target_bundles]; + } + } diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItemBase.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItemBase.php new file mode 100644 index 000000000000..2178d47bb06e --- /dev/null +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItemBase.php @@ -0,0 +1,16 @@ +<?php + +namespace Drupal\Core\Field\Plugin\Field\FieldType; + +use Drupal\Core\Field\FieldItemBase; + +/** + * Base class for field items referencing other entities. + * + * Any field type that is an entity reference should extend from this class in + * order to remain backwards compatible with any changes added in the future + * to EntityReferenceItemInterface. + */ +abstract class EntityReferenceItemBase extends FieldItemBase implements EntityReferenceItemInterface { + +} diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItemInterface.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItemInterface.php new file mode 100644 index 000000000000..624b71ec9e65 --- /dev/null +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItemInterface.php @@ -0,0 +1,29 @@ +<?php + +namespace Drupal\Core\Field\Plugin\Field\FieldType; + +use Drupal\Core\Field\FieldDefinitionInterface; + +/** + * Interface definition for field items referencing other entities. + * + * Field items should extend \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItemBase. + */ +interface EntityReferenceItemInterface { + + /** + * Returns the referenceable entity types and bundles. + * + * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition + * The field definition for which to retrieve the referenceable entity + * types and bundles. + * + * @return array + * An array of referenceable bundles where the array is keyed by the entity + * type ID, with values an array of bundle names. (It is a single-value + * array with the entity type ID if the entity type does not implement + * bundles.) + */ + public static function getReferenceableBundles(FieldDefinitionInterface $field_definition): array; + +} diff --git a/core/modules/jsonapi/src/IncludeResolver.php b/core/modules/jsonapi/src/IncludeResolver.php index 2aad83386f94..b7d47d557621 100644 --- a/core/modules/jsonapi/src/IncludeResolver.php +++ b/core/modules/jsonapi/src/IncludeResolver.php @@ -6,7 +6,8 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Field\FieldItemListInterface; -use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem; +use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItemInterface; +use Drupal\Core\TypedData\DataReferenceDefinitionInterface; use Drupal\jsonapi\Access\EntityAccessChecker; use Drupal\jsonapi\Context\FieldResolver; use Drupal\jsonapi\Exception\EntityAccessDeniedHttpException; @@ -137,11 +138,32 @@ protected function resolveIncludeTree(array $include_tree, Data $data, Data $inc $includes = IncludedData::merge($includes, new IncludedData([$exception])); continue; } - $target_type = $field_list->getFieldDefinition()->getFieldStorageDefinition()->getSetting('target_type'); - assert(!empty($target_type)); - foreach ($field_list as $field_item) { - assert($field_item instanceof EntityReferenceItem); - $references[$target_type][] = $field_item->get($field_item::mainPropertyName())->getValue(); + if (is_subclass_of($field_list->getItemDefinition()->getClass(), EntityReferenceItemInterface::class)) { + foreach ($field_list as $field_item) { + if (!($field_item->getDataDefinition()->getPropertyDefinition('entity') instanceof DataReferenceDefinitionInterface)) { + continue; + } + + if (!($field_item->entity instanceof EntityInterface)) { + continue; + } + + // Support entity reference fields that don't have the referenced + // target type stored in settings. + $references[$field_item->entity->getEntityTypeId()][] = $field_item->get($field_item::mainPropertyName())->getValue(); + } + } + else { + @trigger_error( + sprintf('Entity reference field items not implementing %s is deprecated in drupal:10.2.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3279140', EntityReferenceItemInterface::class), + E_USER_DEPRECATED + ); + $target_type = $field_list->getFieldDefinition()->getFieldStorageDefinition()->getSetting('target_type'); + if (!empty($target_type)) { + foreach ($field_list as $field_item) { + $references[$target_type][] = $field_item->get($field_item::mainPropertyName())->getValue(); + } + } } } foreach ($references as $target_type => $ids) { diff --git a/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php b/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php index 2223956a6518..57555f066554 100644 --- a/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php +++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php @@ -15,6 +15,7 @@ use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Installer\InstallerKernel; +use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItemInterface; use Drupal\Core\TypedData\DataReferenceTargetDefinition; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException; @@ -439,29 +440,45 @@ protected function calculateRelatableResourceTypes(ResourceType $resource_type, protected function getRelatableResourceTypesFromFieldDefinition(FieldDefinitionInterface $field_definition, array $resource_types) { $item_definition = $field_definition->getItemDefinition(); $entity_type_id = $item_definition->getSetting('target_type'); - $handler_settings = $item_definition->getSetting('handler_settings'); - $target_bundles = empty($handler_settings['target_bundles']) ? $this->getAllBundlesForEntityType($entity_type_id) : $handler_settings['target_bundles']; $relatable_resource_types = []; + $item_class = $item_definition->getClass(); + if (is_subclass_of($item_class, EntityReferenceItemInterface::class)) { + $target_type_bundles = $item_class::getReferenceableBundles($field_definition); + } + else { + @trigger_error( + sprintf('Entity reference field items not implementing %s is deprecated in drupal:10.2.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3279140', EntityReferenceItemInterface::class), + E_USER_DEPRECATED + ); + $handler_settings = $item_definition->getSetting('handler_settings'); - foreach ($target_bundles as $target_bundle) { - if ($resource_type = static::lookupResourceType($resource_types, $entity_type_id, $target_bundle)) { - $relatable_resource_types[] = $resource_type; - } - // Do not warn during the site installation since system integrity - // is not guaranteed in this period and the warnings may pop up falsy, - // adding confusion to the process. - elseif (!InstallerKernel::installationAttempted()) { - trigger_error( - sprintf( - 'The "%s" at "%s:%s" references the "%s:%s" entity type that does not exist. Please take action.', - $field_definition->getName(), - $field_definition->getTargetEntityTypeId(), - $field_definition->getTargetBundle(), - $entity_type_id, - $target_bundle - ), - E_USER_WARNING - ); + $has_target_bundles = isset($handler_settings['target_bundles']) && !empty($handler_settings['target_bundles']); + $target_bundles = $has_target_bundles ? $handler_settings['target_bundles'] : $this->getAllBundlesForEntityType($entity_type_id); + $target_type_bundles = [$entity_type_id => $target_bundles]; + } + + foreach ($target_type_bundles as $entity_type_id => $target_bundles) { + foreach ($target_bundles as $target_bundle) { + if ($resource_type = static::lookupResourceType($resource_types, $entity_type_id, $target_bundle)) { + $relatable_resource_types[] = $resource_type; + continue; + } + // Do not warn during site installation since system integrity + // is not guaranteed during this period and may cause confusing and + // unnecessary warnings. + if (!InstallerKernel::installationAttempted()) { + trigger_error( + sprintf( + 'The "%s" at "%s:%s" references the "%s:%s" entity type that does not exist. Please take action.', + $field_definition->getName(), + $field_definition->getTargetEntityTypeId(), + $field_definition->getTargetBundle(), + $entity_type_id, + $target_bundle + ), + E_USER_WARNING + ); + } } } diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_reference_types/jsonapi_test_reference_types.info.yml b/core/modules/jsonapi/tests/modules/jsonapi_test_reference_types/jsonapi_test_reference_types.info.yml new file mode 100644 index 000000000000..c87903c1da50 --- /dev/null +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_reference_types/jsonapi_test_reference_types.info.yml @@ -0,0 +1,3 @@ +name: 'JSON API test deprecated reference field types' +type: module +package: Testing diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_reference_types/jsonapi_test_reference_types.module b/core/modules/jsonapi/tests/modules/jsonapi_test_reference_types/jsonapi_test_reference_types.module new file mode 100644 index 000000000000..df448396837b --- /dev/null +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_reference_types/jsonapi_test_reference_types.module @@ -0,0 +1,24 @@ +<?php + +/** + * @file + * Contains hook implementations for the jsonapi_test_reference_types module. + */ + +use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Field\BaseFieldDefinition; + +/** + * Implements hook_entity_base_field_info(). + */ +function jsonapi_test_reference_types_entity_base_field_info(EntityTypeInterface $entity_type) { + // Add a field of the deprecated reference type to nodes. + if ($entity_type->id() === 'node') { + $fields = []; + $fields['deprecated_reference'] = BaseFieldDefinition::create('jsonapi_test_deprecated_reference') + ->setLabel(t('Reference')) + ->setDescription(t('Deprecated reference field.')); + + return $fields; + } +} diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_reference_types/src/Plugin/Field/FieldType/DeprecatedReferenceItem.php b/core/modules/jsonapi/tests/modules/jsonapi_test_reference_types/src/Plugin/Field/FieldType/DeprecatedReferenceItem.php new file mode 100644 index 000000000000..7067bc5bd869 --- /dev/null +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_reference_types/src/Plugin/Field/FieldType/DeprecatedReferenceItem.php @@ -0,0 +1,65 @@ +<?php + +namespace Drupal\jsonapi_test_reference_types\Plugin\Field\FieldType; + +use Drupal\Core\Field\FieldItemBase; +use Drupal\Core\Field\FieldStorageDefinitionInterface; +use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\Core\TypedData\DataReferenceTargetDefinition; + +/** + * Entity reference field type which doesn't implement the standard interface. + * + * This is to test the handling of deprecated fields which do not implement + * \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItemInterface. + * + * @see https://www.drupal.org/node/3279140 + * @see \Drupal\Tests\jsonapi\Kernel\ResourceType\RelatedResourceTypesTest::testGetRelatableResourceTypesFromFieldDefinitionEntityReferenceFieldDeprecated() + * + * @todo Remove this in Drupal 11 https://www.drupal.org/project/drupal/issues/3353314. + * + * @FieldType( + * id = "jsonapi_test_deprecated_reference", + * ) + */ +class DeprecatedReferenceItem extends FieldItemBase { + + /** + * {@inheritdoc} + */ + public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) { + $properties['target_id'] = DataReferenceTargetDefinition::create('integer') + ->setLabel(new TranslatableMarkup('Target ID')) + ->setSetting('unsigned', TRUE); + + return $properties; + } + + /** + * {@inheritdoc} + */ + public static function schema(FieldStorageDefinitionInterface $field_definition) { + $schema = [ + 'columns' => [ + 'target_id' => [ + 'description' => 'The ID of the target entity.', + 'type' => 'int', + 'unsigned' => TRUE, + ], + ], + 'indexes' => [ + 'target_id' => ['target_id'], + ], + ]; + + return $schema; + } + + /** + * {@inheritdoc} + */ + public static function mainPropertyName() { + return 'target_id'; + } + +} diff --git a/core/modules/jsonapi/tests/src/Functional/NodeTest.php b/core/modules/jsonapi/tests/src/Functional/NodeTest.php index ec45b2d0b4ef..9c9f5b16c607 100644 --- a/core/modules/jsonapi/tests/src/Functional/NodeTest.php +++ b/core/modules/jsonapi/tests/src/Functional/NodeTest.php @@ -526,4 +526,26 @@ public function testCollectionFilterAccess() { $this->assertContains('user.node_grants:view', explode(' ', $response->getHeader('X-Drupal-Cache-Contexts')[0])); } + /** + * Tests deprecated entity reference items. + * + * @group legacy + */ + public function testDeprecatedEntityReferenceFieldItem(): void { + \Drupal::service('module_installer')->install(['jsonapi_test_reference_types']); + + $this->setUpAuthorization('GET'); + // @todo Remove line below in favor of commented line in https://www.drupal.org/project/drupal/issues/2878463. + $url = Url::fromRoute(sprintf('jsonapi.%s.individual', static::$resourceTypeName), ['entity' => $this->entity->uuid()]); + // $url = $this->entity->toUrl('jsonapi'); + $query = ['include' => 'deprecated_reference']; + $url->setOption('query', $query); + $request_options = []; + $request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json'; + $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions()); + + $this->expectDeprecation('Entity reference field items not implementing Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItemInterface is deprecated in drupal:10.2.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3279140'); + $this->request('GET', $url, $request_options); + } + } diff --git a/core/modules/jsonapi/tests/src/Kernel/ResourceType/RelatedResourceTypesTest.php b/core/modules/jsonapi/tests/src/Kernel/ResourceType/RelatedResourceTypesTest.php index cc7b0dd858ec..5ab0627e02de 100644 --- a/core/modules/jsonapi/tests/src/Kernel/ResourceType/RelatedResourceTypesTest.php +++ b/core/modules/jsonapi/tests/src/Kernel/ResourceType/RelatedResourceTypesTest.php @@ -211,4 +211,15 @@ public function testGetRelatableResourceTypesFromFieldDefinition() { } } + /** + * Test the deprecation error on entity reference fields. + * + * @group legacy + */ + public function testGetRelatableResourceTypesFromFieldDefinitionEntityReferenceFieldDeprecated(): void { + \Drupal::service('module_installer')->install(['jsonapi_test_reference_types']); + $this->expectDeprecation('Entity reference field items not implementing Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItemInterface is deprecated in drupal:10.2.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3279140'); + $this->resourceTypeRepository->all(); + } + } -- GitLab