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"
parent
0733716c75
commit
da5ea0aeda
|
@ -13,7 +13,6 @@ use Drupal\Core\Entity\FieldableEntityInterface;
|
|||
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 @@ use Drupal\Core\Validation\Plugin\Validation\Constraint\AllowedValuesConstraint;
|
|||
* 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 @@ class EntityReferenceItem extends FieldItemBase implements OptionsProviderInterf
|
|||
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];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -6,7 +6,8 @@ use Drupal\Core\Access\AccessResult;
|
|||
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 @@ class IncludeResolver {
|
|||
$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) {
|
||||
|
|
|
@ -15,6 +15,7 @@ use Drupal\Core\Entity\EntityTypeManagerInterface;
|
|||
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 @@ class ResourceTypeRepository implements ResourceTypeRepositoryInterface {
|
|||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
name: 'JSON API test deprecated reference field types'
|
||||
type: module
|
||||
package: Testing
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
}
|
||||
|
||||
}
|
|
@ -526,4 +526,26 @@ class NodeTest extends ResourceTestBase {
|
|||
$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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -211,4 +211,15 @@ class RelatedResourceTypesTest extends JsonapiKernelTestBase {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue