diff --git a/src/Entity/Query/Query.php b/src/Entity/Query/Query.php index d8ac26ab..b5afd67f 100644 --- a/src/Entity/Query/Query.php +++ b/src/Entity/Query/Query.php @@ -19,6 +19,7 @@ namespace Drupal\apigee_edge\Entity\Query; +use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; @@ -84,9 +85,69 @@ public function execute() { // Basically, DeveloperAppQuery already applies a condition on the returned // result because this function gets called. $all_records = $this->getFromStorage(); - $filter = $this->condition->compile($this); + // 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. + $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')) { + $account = \Drupal::currentUser(); + } + + $cacheability = CacheableMetadata::createFromRenderArray([]); + $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); + 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'); + if ($request->isMethodCacheable() && $renderer->hasRenderContext()) { + $build = []; + $cacheability->applyTo($build); + $renderer->render($build); + } + } + + $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); + if ($this->count) { return count($result); }