diff --git a/core/modules/jsonapi/jsonapi.services.yml b/core/modules/jsonapi/jsonapi.services.yml index 319b3c1927..b830a1baa5 100644 --- a/core/modules/jsonapi/jsonapi.services.yml +++ b/core/modules/jsonapi/jsonapi.services.yml @@ -82,7 +82,7 @@ services: - { name: jsonapi_encoder, format: 'api_json' } jsonapi.resource_type.repository: class: Drupal\jsonapi\ResourceType\ResourceTypeRepository - arguments: ['@entity_type.manager', '@entity_type.bundle.info', '@entity_field.manager', '@cache.jsonapi_resource_types'] + arguments: ['@entity_type.manager', '@entity_type.bundle.info', '@entity_field.manager', '@cache.jsonapi_resource_types', '@event_dispatcher'] jsonapi.route_enhancer: class: Drupal\jsonapi\Routing\RouteEnhancer tags: diff --git a/core/modules/jsonapi/src/ResourceType/ResourceTypeBuildEvent.php b/core/modules/jsonapi/src/ResourceType/ResourceTypeBuildEvent.php new file mode 100644 index 0000000000..55e8fe1b38 --- /dev/null +++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeBuildEvent.php @@ -0,0 +1,142 @@ +resourceTypeName = $resource_type_name; + $this->fields = $fields; + } + + /** + * Creates a new ResourceTypeBuildEvent. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * An entity type for the resource type to be built. + * @param string $bundle + * A bundle name for the resource type to be built. If the entity type does + * not have bundles, the entity type ID. + * @param \Drupal\jsonapi\ResourceType\ResourceTypeField[] $fields + * The fields of the resource type to be built. + * + * @return \Drupal\jsonapi\ResourceType\ResourceTypeBuildEvent + * A new event. + */ + public static function createFromEntityTypeAndBundle(EntityTypeInterface $entity_type, $bundle, array $fields) { + return new static(sprintf('%s--%s', $entity_type->id(), $bundle), $fields); + } + + /** + * Gets current resource type name of the resource type to be built. + * + * @return string + * The resource type name. + */ + public function getResourceTypeName() { + return $this->resourceTypeName; + } + + /** + * Disables the resource type to be built. + */ + public function disableResourceType() { + $this->disabled = TRUE; + } + + /** + * Whether the resource type to be built should be disabled. + * + * @return bool + * TRUE if the resource type should be disabled, FALSE otherwise. + */ + public function resourceTypeShouldBeDisabled() { + return $this->disabled; + } + + /** + * Gets the current fields of the resource type to be built. + * + * @return \Drupal\jsonapi\ResourceType\ResourceTypeField[] + * The current fields of the resource type to be built. + */ + public function getFields() { + return $this->fields; + } + + /** + * Sets the public name of the given field on the resource type to be built. + * + * @param \Drupal\jsonapi\ResourceType\ResourceTypeField $field + * The field for which to set a public name. + * @param string $public_field_name + * The public field name to set. + */ + public function setPublicFieldName(ResourceTypeField $field, $public_field_name) { + foreach ($this->fields as $index => $value) { + if ($field === $value) { + $this->fields[$index] = $value->withPublicName($public_field_name); + return; + } + } + } + + /** + * Disables the given field on the resource type to be built. + * + * @param \Drupal\jsonapi\ResourceType\ResourceTypeField $field + * The field for which to set a public name. + */ + public function disableField(ResourceTypeField $field) { + foreach ($this->fields as $index => $value) { + if ($field === $value) { + $this->fields[$index] = $value->disabled(); + return; + } + } + } + +} diff --git a/core/modules/jsonapi/src/ResourceType/ResourceTypeBuildEvents.php b/core/modules/jsonapi/src/ResourceType/ResourceTypeBuildEvents.php new file mode 100644 index 0000000000..abfd066e3f --- /dev/null +++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeBuildEvents.php @@ -0,0 +1,18 @@ +internalName, $public_name, $this->enabled, $this->hasOne); } + /** + * Gets a new instance of the field that is disabled. + * + * @return static + * A new instance of the field that is disabled. + */ + public function disabled() { + return new static($this->internalName, $this->publicName, FALSE, $this->hasOne); + } + /** * Whether the field is enabled. * diff --git a/core/modules/jsonapi/src/ResourceType/ResourceTypeRelationship.php b/core/modules/jsonapi/src/ResourceType/ResourceTypeRelationship.php index e62203ca2f..8e782a527c 100644 --- a/core/modules/jsonapi/src/ResourceType/ResourceTypeRelationship.php +++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeRelationship.php @@ -60,4 +60,14 @@ public function withPublicName($public_name) { : $relationship; } + /** + * {@inheritdoc} + */ + public function disabled() { + $relationship = parent::disabled(); + return isset($this->relatableResourceTypes) + ? $relationship->withRelatableResourceTypes($this->relatableResourceTypes) + : $relationship; + } + } diff --git a/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php b/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php index b70ecee04d..724264732f 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\TypedData\DataReferenceTargetDefinition; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException; /** @@ -67,6 +68,13 @@ class ResourceTypeRepository implements ResourceTypeRepositoryInterface { */ protected $cache; + /** + * The event dispatcher. + * + * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface + */ + protected $eventDispatcher; + /** * Cache tags used for caching the repository. * @@ -93,12 +101,15 @@ class ResourceTypeRepository implements ResourceTypeRepositoryInterface { * The entity field manager. * @param \Drupal\Core\Cache\CacheBackendInterface $cache * The cache backend. + * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher + * The event dispatcher. */ - public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_bundle_info, EntityFieldManagerInterface $entity_field_manager, CacheBackendInterface $cache) { + public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_bundle_info, EntityFieldManagerInterface $entity_field_manager, CacheBackendInterface $cache, EventDispatcherInterface $dispatcher) { $this->entityTypeManager = $entity_type_manager; $this->entityTypeBundleInfo = $entity_bundle_info; $this->entityFieldManager = $entity_field_manager; $this->cache = $cache; + $this->eventDispatcher = $dispatcher; } /** @@ -139,15 +150,23 @@ public function all() { */ protected function createResourceType(EntityTypeInterface $entity_type, $bundle) { $raw_fields = $this->getAllFieldNames($entity_type, $bundle); + $internalize_resource_type = $entity_type->isInternal(); + $fields = static::getFields($raw_fields, $entity_type, $bundle); + if (!$internalize_resource_type) { + $event = ResourceTypeBuildEvent::createFromEntityTypeAndBundle($entity_type, $bundle, $fields); + $this->eventDispatcher->dispatch(ResourceTypeBuildEvents::BUILD, $event); + $internalize_resource_type = $event->resourceTypeShouldBeDisabled(); + $fields = $event->getFields(); + } return new ResourceType( $entity_type->id(), $bundle, $entity_type->getClass(), - $entity_type->isInternal(), + $internalize_resource_type, static::isLocatableResourceType($entity_type, $bundle), static::isMutableResourceType($entity_type, $bundle), static::isVersionableResourceType($entity_type), - static::getFields($raw_fields, $entity_type, $bundle) + $fields ); } diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_building/jsonapi_test_resource_type_building.info.yml b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_building/jsonapi_test_resource_type_building.info.yml new file mode 100644 index 0000000000..43d9a83818 --- /dev/null +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_building/jsonapi_test_resource_type_building.info.yml @@ -0,0 +1,4 @@ +name: 'JSON:API test resource type building API' +type: module +package: Testing +core: 8.x diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_building/jsonapi_test_resource_type_building.services.yml b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_building/jsonapi_test_resource_type_building.services.yml new file mode 100644 index 0000000000..77dccb48bf --- /dev/null +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_building/jsonapi_test_resource_type_building.services.yml @@ -0,0 +1,5 @@ +services: + jsonapi_test_resource_type_building.build_subscriber: + class: Drupal\jsonapi_test_resource_type_building\EventSubscriber\ResourceTypeBuildEventSubscriber + tags: + - { name: event_subscriber } diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_building/src/EventSubscriber/ResourceTypeBuildEventSubscriber.php b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_building/src/EventSubscriber/ResourceTypeBuildEventSubscriber.php new file mode 100644 index 0000000000..af4f1a267a --- /dev/null +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_building/src/EventSubscriber/ResourceTypeBuildEventSubscriber.php @@ -0,0 +1,78 @@ + [ + ['disableResourceType'], + ['aliasResourceTypeFields'], + ['disableResourceTypeFields'], + ], + ]; + } + + /** + * Disables any resource types that have been disabled by a test. + * + * @param \Drupal\jsonapi\ResourceType\ResourceTypeBuildEvent $event + * The build event. + */ + public function disableResourceType(ResourceTypeBuildEvent $event) { + $disabled_resource_types = \Drupal::state()->get('jsonapi_test_resource_type_builder.disabled_resource_types', []); + if (in_array($event->getResourceTypeName(), $disabled_resource_types, TRUE)) { + $event->disableResourceType(); + } + } + + /** + * Aliases any resource type fields that have been aliased by a test. + * + * @param \Drupal\jsonapi\ResourceType\ResourceTypeBuildEvent $event + * The build event. + */ + public function aliasResourceTypeFields(ResourceTypeBuildEvent $event) { + $aliases = \Drupal::state()->get('jsonapi_test_resource_type_builder.resource_type_field_aliases', []); + $resource_type_name = $event->getResourceTypeName(); + if (in_array($resource_type_name, array_keys($aliases), TRUE)) { + foreach ($event->getFields() as $field) { + if (isset($aliases[$resource_type_name][$field->getInternalName()])) { + $event->setPublicFieldName($field, $aliases[$resource_type_name][$field->getInternalName()]); + } + } + } + } + + /** + * Disables any resource type fields that have been aliased by a test. + * + * @param \Drupal\jsonapi\ResourceType\ResourceTypeBuildEvent $event + * The build event. + */ + public function disableResourceTypeFields(ResourceTypeBuildEvent $event) { + $aliases = \Drupal::state()->get('jsonapi_test_resource_type_builder.disabled_resource_type_fields', []); + $resource_type_name = $event->getResourceTypeName(); + if (in_array($resource_type_name, array_keys($aliases), TRUE)) { + foreach ($event->getFields() as $field) { + if (isset($aliases[$resource_type_name][$field->getInternalName()]) && $aliases[$resource_type_name][$field->getInternalName()] === TRUE) { + $event->disableField($field); + } + } + } + } + +} diff --git a/core/modules/jsonapi/tests/src/Kernel/ResourceType/ResourceTypeRepositoryTest.php b/core/modules/jsonapi/tests/src/Kernel/ResourceType/ResourceTypeRepositoryTest.php index b8c09f0f35..79081e9a35 100644 --- a/core/modules/jsonapi/tests/src/Kernel/ResourceType/ResourceTypeRepositoryTest.php +++ b/core/modules/jsonapi/tests/src/Kernel/ResourceType/ResourceTypeRepositoryTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\jsonapi\Kernel\ResourceType; +use Drupal\Core\Cache\Cache; use Drupal\jsonapi\ResourceType\ResourceType; use Drupal\node\Entity\NodeType; use Drupal\Tests\jsonapi\Kernel\JsonapiKernelTestBase; @@ -23,6 +24,7 @@ class ResourceTypeRepositoryTest extends JsonapiKernelTestBase { 'serialization', 'system', 'user', + 'jsonapi_test_resource_type_building', ]; /** @@ -147,4 +149,62 @@ public function getFieldMappingProvider() { ]; } + /** + * Tests that resource types can be disabled by a build subscriber. + */ + public function testResourceTypeDisabling() { + $this->assertFalse($this->resourceTypeRepository->getByTypeName('node--article')->isInternal()); + $this->assertFalse($this->resourceTypeRepository->getByTypeName('node--page')->isInternal()); + $this->assertFalse($this->resourceTypeRepository->getByTypeName('user--user')->isInternal()); + $disabled_resource_types = [ + 'node--page', + 'user--user', + ]; + \Drupal::state()->set('jsonapi_test_resource_type_builder.disabled_resource_types', $disabled_resource_types); + Cache::invalidateTags(['jsonapi_resource_types']); + $this->assertFalse($this->resourceTypeRepository->getByTypeName('node--article')->isInternal()); + $this->assertTrue($this->resourceTypeRepository->getByTypeName('node--page')->isInternal()); + $this->assertTrue($this->resourceTypeRepository->getByTypeName('user--user')->isInternal()); + } + + /** + * Tests that resource type fields can be aliased per resource type. + */ + public function testResourceTypeFieldAliasing() { + $this->assertSame($this->resourceTypeRepository->getByTypeName('node--article')->getPublicName('uid'), 'uid'); + $this->assertSame($this->resourceTypeRepository->getByTypeName('node--page')->getPublicName('uid'), 'uid'); + $resource_type_field_aliases = [ + 'node--article' => [ + 'uid' => 'author', + ], + 'node--page' => [ + 'uid' => 'owner', + ], + ]; + \Drupal::state()->set('jsonapi_test_resource_type_builder.resource_type_field_aliases', $resource_type_field_aliases); + Cache::invalidateTags(['jsonapi_resource_types']); + $this->assertSame($this->resourceTypeRepository->getByTypeName('node--article')->getPublicName('uid'), 'author'); + $this->assertSame($this->resourceTypeRepository->getByTypeName('node--page')->getPublicName('uid'), 'owner'); + } + + /** + * Tests that resource type fields can be disabled per resource type. + */ + public function testResourceTypeFieldDisabling() { + $this->assertTrue($this->resourceTypeRepository->getByTypeName('node--article')->isFieldEnabled('uid')); + $this->assertTrue($this->resourceTypeRepository->getByTypeName('node--page')->isFieldEnabled('uid')); + $disabled_resource_type_fields = [ + 'node--article' => [ + 'uid' => TRUE, + ], + 'node--page' => [ + 'uid' => FALSE, + ], + ]; + \Drupal::state()->set('jsonapi_test_resource_type_builder.disabled_resource_type_fields', $disabled_resource_type_fields); + Cache::invalidateTags(['jsonapi_resource_types']); + $this->assertFalse($this->resourceTypeRepository->getByTypeName('node--article')->isFieldEnabled('uid')); + $this->assertTrue($this->resourceTypeRepository->getByTypeName('node--page')->isFieldEnabled('uid')); + } + }