diff --git a/src/Entity/Query/Query.php b/src/Entity/Query/Query.php index 50e3a9832..e005612f4 100644 --- a/src/Entity/Query/Query.php +++ b/src/Entity/Query/Query.php @@ -86,26 +86,51 @@ public function execute() { // result because this function gets called. $all_records = $this->getFromStorage(); - // @todo Proper entity query support that is aligned with the implementation - // in \Drupal\Core\Entity\Query\Sql\Query::prepare() can be only added - // if the following Entity API module issue is solved. + // Be consistent with \Drupal\Core\Entity\Query\Sql\Query::prepare(). + // Add and fire special entity query tags. + // @todo This fix can be only merged after the fix in the following issue + // is available in a tagged release of entity.module. // https://www.drupal.org/project/entity/issues/3332956 + // The minimum required entity.module version also MUST be bumped as part + // of this fix. // (Having a fix for a similar Group module issue is a nice to have, // https://www.drupal.org/project/group/issues/3332963.) + $this->addTag('entity_query'); + $this->addTag('entity_query_' . $this->entityTypeId); + if ($this->accessCheck) { + // We do not just add a tag but ensure that only those Apigee entities + // are returned that the entity access API grants view access. + // (Storage level filtering is not available or way too limited.) + $this->addTag($this->entityTypeId . '_access'); + // Read meta-data from query, if provided. if (!$account = $this->getMetaData('account')) { - // @todo DI dependency. $account = \Drupal::currentUser(); } + $cacheability = CacheableMetadata::createFromRenderArray([]); - $all_records = array_filter($all_records, static function (EntityInterface $entity) use ($cacheability, $account) { + $viewable_entity_ids = array_reduce($all_records, static function (array $carry, EntityInterface $entity) use ($cacheability, $account) { // Bubble up cacheability information even from a revoked access result. $result = $entity->access('view', $account, TRUE); $cacheability->addCacheableDependency($result); - return $result->isAllowed(); - }); - // @todo DI dependencies. + if ($result->isAllowed()) { + $carry[] = $entity->id(); + } + return $carry; + }, []); + + // We deliberately add conditions to the original entity query instead + // of pre-filtering all records because query conditions are visible + // in hook_query_TAG_alter() implementations for downstream developers. + if (empty($viewable_entity_ids)) { + // Add an always false condition. A persisted entity's primary id + // cannot be null. + $this->condition->notExists($this->entityType->getKey('id')); + } + else { + $this->condition->condition($this->entityType->getKey('id'), $viewable_entity_ids, 'IN'); + } /** @var \Symfony\Component\HttpFoundation\Request $request */ $request = \Drupal::requestStack()->getCurrentRequest(); $renderer = \Drupal::service('renderer'); @@ -116,6 +141,12 @@ public function execute() { } } + $hooks = ['query']; + foreach ($this->alterTags as $tag => $value) { + $hooks[] = 'query_' . $tag; + } + \Drupal::moduleHandler()->alter($hooks, $this); + $filter = $this->condition->compile($this); $result = array_filter($all_records, $filter);