From e68662396df6c920a286a18c4509aaa112bf9bb8 Mon Sep 17 00:00:00 2001
From: effulgentsia <alex.bronstein@acquia.com>
Date: Mon, 14 Oct 2019 12:49:15 -0700
Subject: [PATCH] Issue #3085035 by gabesullice, Wim Leers: Add a public API
 for aliasing and disabling JSON:API resource type fields

---
 .../ResourceType/ResourceTypeBuildEvent.php   | 62 ++++++++++++++++++-
 .../src/ResourceType/ResourceTypeField.php    | 10 +++
 .../ResourceType/ResourceTypeRelationship.php | 10 +++
 .../ResourceType/ResourceTypeRepository.php   |  6 +-
 .../ResourceTypeBuildEventSubscriber.php      | 44 ++++++++++++-
 .../ResourceTypeRepositoryTest.php            | 40 ++++++++++++
 6 files changed, 166 insertions(+), 6 deletions(-)

diff --git a/core/modules/jsonapi/src/ResourceType/ResourceTypeBuildEvent.php b/core/modules/jsonapi/src/ResourceType/ResourceTypeBuildEvent.php
index cb9d6dbf1e23..55e8fe1b38c8 100644
--- a/core/modules/jsonapi/src/ResourceType/ResourceTypeBuildEvent.php
+++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeBuildEvent.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\jsonapi\ResourceType;
 
+use Drupal\Component\Assertion\Inspector;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Symfony\Component\EventDispatcher\Event;
 
@@ -20,6 +21,13 @@ class ResourceTypeBuildEvent extends Event {
    */
   protected $resourceTypeName;
 
+  /**
+   * The fields of the resource type to be built.
+   *
+   * @var \Drupal\jsonapi\ResourceType\ResourceTypeField[]
+   */
+  protected $fields;
+
   /**
    * Whether the JSON:API resource type to be built should be disabled.
    *
@@ -35,9 +43,13 @@ class ResourceTypeBuildEvent extends Event {
    *
    * @param string $resource_type_name
    *   A JSON:API resource type name.
+   * @param \Drupal\jsonapi\ResourceType\ResourceTypeField[] $fields
+   *   The fields of the resource type to be built.
    */
-  protected function __construct($resource_type_name) {
+  protected function __construct($resource_type_name, array $fields) {
+    assert(Inspector::assertAllObjects($fields, ResourceTypeField::class));
     $this->resourceTypeName = $resource_type_name;
+    $this->fields = $fields;
   }
 
   /**
@@ -48,12 +60,14 @@ protected function __construct($resource_type_name) {
    * @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) {
-    return new static(sprintf('%s--%s', $entity_type->id(), $bundle));
+  public static function createFromEntityTypeAndBundle(EntityTypeInterface $entity_type, $bundle, array $fields) {
+    return new static(sprintf('%s--%s', $entity_type->id(), $bundle), $fields);
   }
 
   /**
@@ -83,4 +97,46 @@ 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/ResourceTypeField.php b/core/modules/jsonapi/src/ResourceType/ResourceTypeField.php
index 76e35dbcc0c8..987f4963843e 100644
--- a/core/modules/jsonapi/src/ResourceType/ResourceTypeField.php
+++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeField.php
@@ -95,6 +95,16 @@ public function withPublicName($public_name) {
     return new static($this->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 e62203ca2fe7..8e782a527c4b 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 b12baeec54fc..724264732f4b 100644
--- a/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php
+++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php
@@ -151,10 +151,12 @@ 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);
+      $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(),
@@ -164,7 +166,7 @@ protected function createResourceType(EntityTypeInterface $entity_type, $bundle)
       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/src/EventSubscriber/ResourceTypeBuildEventSubscriber.php b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_building/src/EventSubscriber/ResourceTypeBuildEventSubscriber.php
index 252461032c4a..af4f1a267a99 100644
--- 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
@@ -17,7 +17,13 @@ class ResourceTypeBuildEventSubscriber implements EventSubscriberInterface {
    * {@inheritdoc}
    */
   public static function getSubscribedEvents() {
-    return [ResourceTypeBuildEvents::BUILD => 'disableResourceType'];
+    return [
+      ResourceTypeBuildEvents::BUILD => [
+        ['disableResourceType'],
+        ['aliasResourceTypeFields'],
+        ['disableResourceTypeFields'],
+      ],
+    ];
   }
 
   /**
@@ -33,4 +39,40 @@ public function disableResourceType(ResourceTypeBuildEvent $event) {
     }
   }
 
+  /**
+   * 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 b7ecced323df..79081e9a35c0 100644
--- a/core/modules/jsonapi/tests/src/Kernel/ResourceType/ResourceTypeRepositoryTest.php
+++ b/core/modules/jsonapi/tests/src/Kernel/ResourceType/ResourceTypeRepositoryTest.php
@@ -167,4 +167,44 @@ public function testResourceTypeDisabling() {
     $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'));
+  }
+
 }
-- 
GitLab