diff --git a/src/Datasource/DatasourceInterface.php b/src/Datasource/DatasourceInterface.php index 76dbfe3f..20fa7b14 100644 --- a/src/Datasource/DatasourceInterface.php +++ b/src/Datasource/DatasourceInterface.php @@ -257,4 +257,20 @@ interface DatasourceInterface extends IndexPluginInterface { */ public function getFieldDependencies(array $fields); + /** + * Get item cacheability. + * + * @param \Drupal\Core\TypedData\ComplexDataInterface $item + * + * @return \Drupal\Core\Cache\CacheableMetadata + */ + public function getItemCacheability(ComplexDataInterface $item); + + /** + * Get list cacheability. + * + * @return \Drupal\Core\Cache\CacheableMetadata + */ + public function getListCacheability(); + } diff --git a/src/Item/Item.php b/src/Item/Item.php index c92764e7..1a4e559e 100644 --- a/src/Item/Item.php +++ b/src/Item/Item.php @@ -3,6 +3,7 @@ namespace Drupal\search_api\Item; use Drupal\Core\Access\AccessResult; +use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Session\AccountInterface; use Drupal\Core\TypedData\ComplexDataInterface; use Drupal\search_api\Datasource\DatasourceInterface; @@ -415,6 +416,7 @@ class Item implements \IteratorAggregate, ItemInterface { * {@inheritdoc} */ public function checkAccessAsObject(AccountInterface $account = NULL) { + // @fixme Statically cache this by account. try { return $this->getDatasource() ->checkItemAccessAsObject($this->getOriginalObject(), $account); @@ -424,6 +426,23 @@ class Item implements \IteratorAggregate, ItemInterface { } } + /** + * {@inheritdoc} + */ + public function getCacheability(AccountInterface $account = NULL, $includeListCacheability = TRUE) { + // @fixme Statically cache this by account. + $cacheability = new CacheableMetadata(); + $access = $this->checkAccessAsObject($account); + $cacheability->addCacheableDependency($access); + if ($access->isAllowed()) { + $cacheability->addCacheableDependency($this->getDatasource()->getItemCacheability($this->getOriginalObject())); + } + if ($includeListCacheability) { + $cacheability->addCacheableDependency($this->getDatasource()->getListCacheability()); + } + return $cacheability; + } + /** * {@inheritdoc} */ diff --git a/src/Item/ItemInterface.php b/src/Item/ItemInterface.php index 288f7266..f0a1f20e 100644 --- a/src/Item/ItemInterface.php +++ b/src/Item/ItemInterface.php @@ -315,4 +315,14 @@ interface ItemInterface extends \Traversable { */ public function checkAccessAsObject(AccountInterface $account = NULL); + /** + * Get cacheability. + * + * @param \Drupal\Core\Session\AccountInterface|NULL $account + * @param bool $includeListCacheability + * + * @return \Drupal\Core\Cache\CacheableMetadata + */ + public function getCacheability(AccountInterface $account = NULL, $includeListCacheability = TRUE); + } diff --git a/src/Plugin/search_api/datasource/ContentEntity.php b/src/Plugin/search_api/datasource/ContentEntity.php index d3ae1200..120b497f 100644 --- a/src/Plugin/search_api/datasource/ContentEntity.php +++ b/src/Plugin/search_api/datasource/ContentEntity.php @@ -3,6 +3,7 @@ namespace Drupal\search_api\Plugin\search_api\datasource; use Drupal\Core\Access\AccessResult; +use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityDisplayRepositoryInterface; @@ -1044,4 +1045,27 @@ class ContentEntity extends DatasourcePluginBase implements EntityDatasourceInte return $valid_ids; } + /** + * {@inheritdoc} + */ + public function getItemCacheability(ComplexDataInterface $item) { + $cacheability = new CacheableMetadata(); + $entity = $this->getEntity($item); + if ($entity) { + $cacheability->addCacheableDependency($entity); + } + return $cacheability; + } + + /** + * {@inheritdoc} + */ + public function getListCacheability() { + $cacheability = new CacheableMetadata(); + $entityType = $this->getEntityType(); + $cacheability->addCacheTags($entityType->getListCacheTags()); + $cacheability->addCacheContexts($entityType->getListCacheContexts()); + return $cacheability; + } + } diff --git a/src/Plugin/views/query/SearchApiQuery.php b/src/Plugin/views/query/SearchApiQuery.php index 88eee242..ebe0d66a 100644 --- a/src/Plugin/views/query/SearchApiQuery.php +++ b/src/Plugin/views/query/SearchApiQuery.php @@ -4,6 +4,7 @@ namespace Drupal\search_api\Plugin\views\query; use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Cache\CacheableDependencyInterface; +use Drupal\Core\Cache\RefinableCacheableDependencyInterface; use Drupal\Core\Database\Query\ConditionInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; @@ -120,6 +121,16 @@ class SearchApiQuery extends QueryPluginBase { */ protected $messenger; + /** + * The cacheability of the result. + * + * Set in ::addResults, so only available after ::execute. + * Used in ::getCacheFoo() + * + * @var RefinableCacheableDependencyInterface + */ + protected $cacheability; + /** * {@inheritdoc} */ @@ -638,13 +649,7 @@ class SearchApiQuery extends QueryPluginBase { // avoid them being individually loaded inside checkAccess(). $result_set->preLoadResultItems(); foreach ($results as $item_id => $result) { - $access_result = $result->checkAccessAsObject($account); - if ($access_result instanceof CacheableDependencyInterface) { - foreach ($access_result->getCacheContexts() as $context) { - $view->addCacheContext($context); - } - } - if (!$access_result->isAllowed()) { + if (!$result->checkAccessAsObject($account)->isAllowed()) { unset($results[$item_id]); } } @@ -691,9 +696,37 @@ class SearchApiQuery extends QueryPluginBase { $values['index'] = $count++; $view->result[] = new ResultRow($values); + + // We drop the result set, so save its cacheability. + $this->cacheability = $result_set->getCacheability(); } } + /** + * @inheritDoc + */ + public function getCacheMaxAge() { + // The parent implementation is of no use for us. + return $this->cacheability->getCacheMaxAge(); + } + + /** + * @inheritDoc + */ + public function getCacheContexts() { + // The parent implementation is of no use for us. + return $this->cacheability->getCacheContexts(); + } + + /** + * @inheritDoc + */ + public function getCacheTags() { + // The parent implementation is of no use for us. + return $this->cacheability->getCacheTags(); + } + + /** * Retrieves the conditions placed on this query. * diff --git a/src/Query/ResultSet.php b/src/Query/ResultSet.php index 781a9dfe..e0746078 100644 --- a/src/Query/ResultSet.php +++ b/src/Query/ResultSet.php @@ -2,6 +2,8 @@ namespace Drupal\search_api\Query; +use Drupal\Core\Cache\CacheableMetadata; +use Drupal\Core\Session\AccountInterface; use Drupal\search_api\Item\ItemInterface; use Drupal\search_api\SearchApiException; @@ -259,4 +261,15 @@ class ResultSet implements \IteratorAggregate, ResultSetInterface { return $out; } + /** + * @inheritDoc + */ + public function getCacheability(AccountInterface $account = NULL, $includeListCacheability = TRUE) { + $cacheability = new CacheableMetadata(); + foreach ($this->getResultItems() as $item) { + $cacheability = $cacheability->merge($item->getCacheability($account, $includeListCacheability)); + } + return $cacheability; + } + } diff --git a/src/Query/ResultSetInterface.php b/src/Query/ResultSetInterface.php index a32a46ee..71590380 100644 --- a/src/Query/ResultSetInterface.php +++ b/src/Query/ResultSetInterface.php @@ -2,6 +2,7 @@ namespace Drupal\search_api\Query; +use Drupal\Core\Session\AccountInterface; use Drupal\search_api\Item\ItemInterface; /** @@ -197,4 +198,14 @@ interface ResultSetInterface extends \Traversable { */ public function getCloneForQuery(QueryInterface $query); + /** + * Get cacheability. + * + * @param \Drupal\Core\Session\AccountInterface|NULL $account + * @param bool $includeListCacheability + * + * @return \Drupal\Core\Cache\CacheableMetadata + */ + public function getCacheability(AccountInterface $account = NULL, $includeListCacheability = TRUE); + }