From 320c889216b8cf473a8d8487e616cc5f30d76ecd Mon Sep 17 00:00:00 2001
From: Kristiaan Van den Eynde <kristiaan@factorial.io>
Date: Thu, 25 Jun 2020 10:55:00 +0200
Subject: [PATCH] Issue #3134363 by kristiaanvandeneynde: Support a catch-all
 QueryAccessEvent for modules that may want to alter all entity queries

---
 src/QueryAccess/QueryAccessEvent.php          | 26 +++++++++++++++++--
 src/QueryAccess/QueryAccessHandlerBase.php    |  3 ++-
 .../EventSubscriber/QueryAccessSubscriber.php | 26 +++++++++++++++++++
 .../QueryAccess/QueryAccessEventTest.php      | 17 ++++++++++++
 4 files changed, 69 insertions(+), 3 deletions(-)

diff --git a/src/QueryAccess/QueryAccessEvent.php b/src/QueryAccess/QueryAccessEvent.php
index 199bcfe..792922f 100644
--- a/src/QueryAccess/QueryAccessEvent.php
+++ b/src/QueryAccess/QueryAccessEvent.php
@@ -10,7 +10,9 @@ use Symfony\Component\EventDispatcher\Event;
  *
  * Allows modules to modify access conditions before they're applied to a query.
  *
- * The event ID is dynamic: entity.query_access.$entity_type_id
+ * The event ID is both generic and dynamic:
+ * - entity.query_access
+ * - entity.query_access.$entity_type_id
  */
 class QueryAccessEvent extends Event {
 
@@ -35,6 +37,13 @@ class QueryAccessEvent extends Event {
    */
   protected $account;
 
+  /**
+   * The ID of entity type the query is for.
+   *
+   * @var string
+   */
+  protected $entityTypeId;
+
   /**
    * Constructs a new QueryAccessEvent.
    *
@@ -44,11 +53,14 @@ class QueryAccessEvent extends Event {
    *   The operation. Usually one of "view", "update", "duplicate", or "delete".
    * @param \Drupal\Core\Session\AccountInterface $account
    *   The user for which to restrict access.
+   * @param string $entity_type_id
+   *   The ID of entity type the query is for.
    */
-  public function __construct(ConditionGroup $conditions, $operation, AccountInterface $account) {
+  public function __construct(ConditionGroup $conditions, $operation, AccountInterface $account, $entity_type_id) {
     $this->conditions = $conditions;
     $this->operation = $operation;
     $this->account = $account;
+    $this->entityTypeId = $entity_type_id;
   }
 
   /**
@@ -89,4 +101,14 @@ class QueryAccessEvent extends Event {
     return $this->account;
   }
 
+  /**
+   * Gets the the entity type ID.
+   *
+   * @return string
+   *   The entity type ID.
+   */
+  public function getEntityTypeId() {
+    return $this->entityTypeId;
+  }
+
 }
diff --git a/src/QueryAccess/QueryAccessHandlerBase.php b/src/QueryAccess/QueryAccessHandlerBase.php
index 345b3fa..ec96e9e 100644
--- a/src/QueryAccess/QueryAccessHandlerBase.php
+++ b/src/QueryAccess/QueryAccessHandlerBase.php
@@ -87,7 +87,8 @@ abstract class QueryAccessHandlerBase implements EntityHandlerInterface, QueryAc
     $conditions = $this->buildConditions($operation, $account);
 
     // Allow other modules to modify the conditions before they are used.
-    $event = new QueryAccessEvent($conditions, $operation, $account);
+    $event = new QueryAccessEvent($conditions, $operation, $account, $entity_type_id);
+    $this->eventDispatcher->dispatch("entity.query_access", $event);
     $this->eventDispatcher->dispatch("entity.query_access.{$entity_type_id}", $event);
 
     return $conditions;
diff --git a/tests/modules/entity_module_test/src/EventSubscriber/QueryAccessSubscriber.php b/tests/modules/entity_module_test/src/EventSubscriber/QueryAccessSubscriber.php
index 5f7ffb6..a6cef32 100644
--- a/tests/modules/entity_module_test/src/EventSubscriber/QueryAccessSubscriber.php
+++ b/tests/modules/entity_module_test/src/EventSubscriber/QueryAccessSubscriber.php
@@ -13,10 +13,36 @@ class QueryAccessSubscriber implements EventSubscriberInterface {
    */
   public static function getSubscribedEvents() {
     return [
+      'entity.query_access' => 'onGenericQueryAccess',
       'entity.query_access.entity_test_enhanced' => 'onQueryAccess',
     ];
   }
 
+  /**
+   * Modifies the access conditions based on the entity type.
+   *
+   * This is just a convenient example for testing the catch-all event. A real
+   * subscriber would probably extend the conditions based on the third party
+   * settings it set on the entity type(s).
+   *
+   * @param \Drupal\entity\QueryAccess\QueryAccessEvent $event
+   *   The event.
+   */
+  public function onGenericQueryAccess(QueryAccessEvent $event) {
+    $conditions = $event->getConditions();
+    $email = $event->getAccount()->getEmail();
+    if ($event->getEntityTypeId() == 'entity_test_enhanced_with_owner') {
+      // Disallow access to entity_test_enhanced_with_owner for the user with
+      // email address user9000@example.com. Anyone else has access.
+      if ($email == 'user9000@example.com') {
+        $conditions->alwaysFalse();
+      }
+      elseif ($email == 'user9001@example.com') {
+        $conditions->alwaysFalse(FALSE);
+      }
+    }
+  }
+
   /**
    * Modifies the access conditions based on the current user.
    *
diff --git a/tests/src/Kernel/QueryAccess/QueryAccessEventTest.php b/tests/src/Kernel/QueryAccess/QueryAccessEventTest.php
index 8ecef3b..d5a045c 100644
--- a/tests/src/Kernel/QueryAccess/QueryAccessEventTest.php
+++ b/tests/src/Kernel/QueryAccess/QueryAccessEventTest.php
@@ -43,6 +43,23 @@ class QueryAccessEventTest extends EntityKernelTestBase {
     $this->handler = QueryAccessHandler::createInstance($this->container, $entity_type);
   }
 
+  /**
+   * Tests the generic event.
+   */
+  public function testGenericEvent() {
+    $entity_type_manager = $this->container->get('entity_type.manager');
+    $entity_type = $entity_type_manager->getDefinition('entity_test_enhanced_with_owner');
+    $handler = QueryAccessHandler::createInstance($this->container, $entity_type);
+
+    $first_user = $this->createUser(['mail' => 'user9000@example.com']);
+    $conditions = $handler->getConditions('view', $first_user);
+    $this->assertTrue($conditions->isAlwaysFalse());
+
+    $second_user = $this->createUser(['mail' => 'user9001@example.com']);
+    $conditions = $handler->getConditions('view', $second_user);
+    $this->assertFalse($conditions->isAlwaysFalse());
+  }
+
   /**
    * Tests the event.
    */
-- 
2.17.1

