diff --git a/facets.install b/facets.install
index 8a5b4e6..794d5c8 100644
--- a/facets.install
+++ b/facets.install
@@ -8,6 +8,7 @@
use Drupal\facets\Entity\Facet;
use Drupal\facets\Entity\FacetSource;
use Drupal\block\Entity\Block;
+use Drupal\facets\Plugin\facets\facet_source\SearchApiDisplay;
/**
* Implements hook_update_dependencies().
@@ -174,17 +175,8 @@ function facets_update_8005() {
* Update facet blocks configuration with a block id used for AJAX support.
*/
function facets_update_8006() {
- $query = \Drupal::entityQuery('block')
- ->condition('plugin', 'facet_block', 'STARTS_WITH')
- ->execute();
-
- foreach ($query as $block_id) {
- $block = Block::load($block_id);
- $configuration = $block->get('settings');
- $configuration['block_id'] = $block_id;
- $block->set('settings', $configuration);
- $block->save();
- }
+ // Empty update hook, we do not support this anymore.
+ // @see https://www.drupal.org/project/facets/issues/3073444
}
/**
@@ -216,3 +208,37 @@ function facets_update_8009() {
$facet->save();
}
}
+
+/**
+ * Enable facet block caching for the views with "Search API tag or time" cache.
+ */
+function facets_update_8010() {
+ $facet_storage = \Drupal::entityTypeManager()->getStorage('facets_facet');
+ $processed_views = [];
+ /** @var \Drupal\facets\FacetInterface $facet */
+ foreach ($facet_storage->loadMultiple() as $facet) {
+ if (
+ ($source = $facet->getFacetSource())
+ && $source instanceof SearchApiDisplay
+ && ($view_executable = $source->getViewsDisplay())
+ && !in_array($view_executable->id(), $processed_views)
+ && ($cache_plugin = $view_executable->getDisplay()->getPlugin('cache'))
+ && in_array(
+ $cache_plugin->getPluginId(),
+ ['search_api_tag', 'search_api_time']
+ )
+ ) {
+ $view_executable->save();
+ $processed_views[] = $view_executable->id();
+ }
+ }
+ if (!empty($processed_views)) {
+ return t(
+ 'Facet caching was enabled for the following views: %views.',
+ ['%views' => implode(', ', $processed_views)]
+ );
+ }
+ else {
+ return t('There are no views with search API cache plugins and facets in the same time, so nothing has been updated.');
+ }
+}
diff --git a/facets.module b/facets.module
index bc7d27e..e619677 100644
--- a/facets.module
+++ b/facets.module
@@ -14,6 +14,7 @@ use Drupal\Core\Url;
use Drupal\facets\Entity\Facet;
use Drupal\facets\Entity\FacetSource;
use Drupal\facets\FacetInterface;
+use Drupal\search_api\Entity\Index;
use Drupal\views\Entity\View;
use Drupal\Core\Entity\EntityInterface;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
@@ -66,6 +67,11 @@ function facets_theme($existing, $type, $theme, $path) {
'context' => [],
],
],
+ 'facets_views_plugin' => [
+ 'variables' => [
+ 'content' => [],
+ ],
+ ],
];
}
@@ -365,3 +371,23 @@ function facets_theme_suggestions_facets_result_item(array $variables) {
}
return $suggestions;
}
+
+/**
+ * Implements hook_views_data_alter().
+ */
+function facets_views_data_alter(array &$data) {
+
+ /** @var \Drupal\search_api\IndexInterface $index */
+ foreach (Index::loadMultiple() as $index) {
+ $data['search_api_index_' . $index->id()]['facets'] = [
+ 'title' => t('Facets'),
+ 'help' => t('Displays facets in a filter or area.'),
+ 'filter' => [
+ 'id' => 'facets_filter',
+ ],
+ 'area' => [
+ 'id' => 'facets_area',
+ ],
+ ];
+ }
+}
diff --git a/facets.routing.yml b/facets.routing.yml
index 9f26911..1e897e7 100644
--- a/facets.routing.yml
+++ b/facets.routing.yml
@@ -48,10 +48,3 @@ entity.facets_facet_source.edit_form:
_title: 'Edit facet source configuration'
requirements:
_entity_create_access: 'facets_facet'
-
-facets.block.ajax:
- path: '/facets-block-ajax'
- defaults:
- _controller: '\Drupal\facets\Controller\FacetBlockAjaxController::ajaxFacetBlockView'
- requirements:
- _access: 'TRUE'
diff --git a/facets.services.yml b/facets.services.yml
index 103f94a..7c31490 100644
--- a/facets.services.yml
+++ b/facets.services.yml
@@ -24,6 +24,7 @@ services:
- '@plugin.manager.facets.facet_source'
- '@plugin.manager.facets.processor'
- '@entity_type.manager'
+ - '@current_route_match'
facets.utility.date_handler:
class: Drupal\facets\Utility\FacetsDateHandler
arguments:
@@ -38,6 +39,10 @@ services:
arguments: ['@plugin.manager.block']
tags:
- { name: event_subscriber }
+ facets.route_alter:
+ class: \Drupal\facets\EventSubscriber\RouteAlterSubscriber
+ tags:
+ - { name: event_subscriber }
facets.search_api_subscriber:
class: Drupal\facets\EventSubscriber\SearchApiSubscriber
arguments: ['@facets.manager']
diff --git a/js/base-widget.js b/js/base-widget.js
index 8ca3e6c..3af9e8b 100644
--- a/js/base-widget.js
+++ b/js/base-widget.js
@@ -24,7 +24,7 @@
* .once('my-custom-widget-on-change')
* .on('change', function () {
* // In this example $(this).val() will provide needed URL.
- * $(this).trigger('facets_filter', [ $(this).val() ]);
+ * $(this).trigger('facets_filter', [ $(this).val(), false ]);
* });
*
* The facets module will trigger "facets_filtering" before filter is
diff --git a/modules/facets_summary/facets_summary.module b/modules/facets_summary/facets_summary.module
index 6720862..d2505eb 100644
--- a/modules/facets_summary/facets_summary.module
+++ b/modules/facets_summary/facets_summary.module
@@ -5,6 +5,8 @@
* Hook implementations for the facets summary module.
*/
+use Drupal\search_api\Entity\Index;
+
/**
* Implements hook_theme().
*/
@@ -98,3 +100,23 @@ function facets_summary_theme_suggestions_facets_summary_item_list(array $variab
function facets_summary_preprocess_facets_summary_item_list(array &$variables) {
template_preprocess_item_list($variables);
}
+
+/**
+ * Implements hook_views_data_alter().
+ */
+function facets_summary_views_data_alter(array &$data) {
+
+ /** @var \Drupal\search_api\IndexInterface $index */
+ foreach (Index::loadMultiple() as $index) {
+ $data['search_api_index_' . $index->id()]['facets_summary'] = [
+ 'title' => t('Facets summary'),
+ 'help' => t('Displays facets summary in a filter or area'),
+ 'filter' => [
+ 'id' => 'facets_summary_filter',
+ ],
+ 'area' => [
+ 'id' => 'facets_summary_area',
+ ],
+ ];
+ }
+}
diff --git a/modules/facets_summary/src/Form/FacetsSummarySettingsForm.php b/modules/facets_summary/src/Form/FacetsSummarySettingsForm.php
index 2b19def..d61295e 100644
--- a/modules/facets_summary/src/Form/FacetsSummarySettingsForm.php
+++ b/modules/facets_summary/src/Form/FacetsSummarySettingsForm.php
@@ -245,9 +245,12 @@ class FacetsSummarySettingsForm extends EntityForm {
if (isset($facet_source) && $facet_source instanceof SearchApiFacetSourceInterface) {
$view = $facet_source->getViewsDisplay();
if ($view !== NULL) {
- $view->display_handler->overrideOption('cache', ['type' => 'none']);
- $view->save();
- $this->messenger()->addMessage($this->t('Caching of view %view has been disabled.', ['%view' => $view->storage->label()]));
+ $views_cache_type = $view->display_handler->getOption('cache')['type'];
+ if ($views_cache_type !== 'none' && strpos($views_cache_type, 'search_api_') === FALSE) {
+ $view->display_handler->overrideOption('cache', ['type' => 'none']);
+ $view->save();
+ \Drupal::messenger()->addMessage($this->t('Caching of view %view has been disabled.', ['%view' => $view->storage->label()]));
+ }
}
}
diff --git a/modules/facets_summary/src/Plugin/Block/FacetsSummaryBlock.php b/modules/facets_summary/src/Plugin/Block/FacetsSummaryBlock.php
index e805c5e..9990aa1 100644
--- a/modules/facets_summary/src/Plugin/Block/FacetsSummaryBlock.php
+++ b/modules/facets_summary/src/Plugin/Block/FacetsSummaryBlock.php
@@ -5,7 +5,6 @@ namespace Drupal\facets_summary\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Cache\UncacheableDependencyTrait;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
-use Drupal\Core\Url;
use Drupal\facets_summary\Entity\FacetsSummary;
use Drupal\facets_summary\FacetsSummaryBlockInterface;
use Drupal\facets_summary\FacetsSummaryManager\DefaultFacetsSummaryManager;
@@ -102,18 +101,6 @@ class FacetsSummaryBlock extends BlockBase implements FacetsSummaryBlockInterfac
];
}
- /** @var \Drupal\views\ViewExecutable $view */
- if ($view = $facets_summary->getFacetSource()->getViewsDisplay()) {
- $build['#attached']['drupalSettings']['facets_views_ajax'] = [
- 'facets_summary_ajax' => [
- 'facets_summary_id' => $facets_summary->id(),
- 'view_id' => $view->id(),
- 'current_display_id' => $view->current_display,
- 'ajax_path' => Url::fromRoute('views.ajax')->toString(),
- ],
- ];
- }
-
return $build;
}
diff --git a/modules/facets_summary/src/Plugin/facets_summary/processor/HideWhenNotRenderedProcessor.php b/modules/facets_summary/src/Plugin/facets_summary/processor/HideWhenNotRenderedProcessor.php
index 2f41f67..4780b3c 100644
--- a/modules/facets_summary/src/Plugin/facets_summary/processor/HideWhenNotRenderedProcessor.php
+++ b/modules/facets_summary/src/Plugin/facets_summary/processor/HideWhenNotRenderedProcessor.php
@@ -19,7 +19,44 @@ use Drupal\facets_summary\Processor\ProcessorPluginBase;
* }
* )
*/
-class HideWhenNotRenderedProcessor extends ProcessorPluginBase implements BuildProcessorInterface {
+class HideWhenNotRenderedProcessor extends ProcessorPluginBase implements BuildProcessorInterface, ContainerFactoryPluginInterface {
+
+ /**
+ * The current route match.
+ *
+ * @var \Drupal\Core\Routing\RouteMatchInterface
+ */
+ protected $routeMatch;
+
+ /**
+ * Constructs a new object.
+ *
+ * @param array $configuration
+ * A configuration array containing information about the plugin instance.
+ * @param string $plugin_id
+ * The plugin_id for the plugin instance.
+ * @param mixed $plugin_definition
+ * The plugin implementation definition.
+ * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+ * The current route match.
+ */
+ public function __construct(array $configuration, $plugin_id, $plugin_definition, RouteMatchInterface $route_match = NULL) {
+ parent::__construct($configuration, $plugin_id, $plugin_definition);
+
+ $this->routeMatch = $route_match;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $container->get('current_route_match')
+ );
+ }
/**
* {@inheritdoc}
diff --git a/modules/facets_summary/src/Plugin/views/FacetsSummaryViewsPluginTrait.php b/modules/facets_summary/src/Plugin/views/FacetsSummaryViewsPluginTrait.php
new file mode 100644
index 0000000..8f58b36
--- /dev/null
+++ b/modules/facets_summary/src/Plugin/views/FacetsSummaryViewsPluginTrait.php
@@ -0,0 +1,93 @@
+facetSummaryStorage->loadMultiple();
+
+ $format = 'search_api:views_%s__%s__%s';
+ $source = sprintf($format, $this->view->getDisplay()->getPluginId(), $this->view->id(), $this->view->current_display);
+ foreach ($facets_summaries as $facets_summary) {
+ if ($facets_summary->getFacetSourceId() === $source) {
+ $options[$facets_summary->id()] = $facets_summary->label();
+ }
+ }
+
+ $form['facet_summary'] = [
+ '#title' => 'Facet summary',
+ '#options' => $options,
+ '#type' => 'radios',
+ '#required' => TRUE,
+ '#default_value' => isset($this->options['facet_summary']) ? $this->options['facet_summary'] : [],
+ ];
+ }
+
+ /**
+ * Gets the facets summary to render.
+ *
+ * @return array
+ * A summary of the facets being used.
+ */
+ public function FacetsViewsGetFacetSummary() {
+ $build = [];
+
+ /** @var \Drupal\facets_summary\Entity\FacetsSummary $summary */
+ $summary = $this->facetSummaryStorage->load($this->options['facet_summary']);
+ if ($summary) {
+ $facet_summary = $this->facetSummaryManager->build($summary);
+ if (!empty($facet_summary)) {
+ $summary_build = [
+ '#theme' => 'block',
+ '#configuration' => [
+ 'provider' => 'facets_summary',
+ 'label' => $summary->label(),
+ 'label_display' => TRUE,
+ ],
+ '#id' => $summary->id(),
+ '#plugin_id' => 'facet_summary_block:' . $summary->id(),
+ '#base_plugin_id' => 'facet_block',
+ '#derivative_plugin_id' => $summary->id(),
+ '#weight' => 0,
+ '#cache' => [
+ 'contexts' => [],
+ 'tags' => [],
+ 'max-age' => 0,
+ ],
+ 'content' => $facet_summary,
+ ];
+ }
+ }
+
+ if (!empty($summary_build)) {
+ $build = [
+ '#theme' => 'facets_views_plugin',
+ '#content' => $summary_build,
+ ];
+
+ if ($this->view->getDisplay()->ajaxEnabled()) {
+ $build['#attached']['library'][] = 'facets/drupal.facets.views-ajax';
+ }
+ }
+
+ return $build;
+ }
+
+}
diff --git a/modules/facets_summary/src/Plugin/views/area/FacetsSummaryArea.php b/modules/facets_summary/src/Plugin/views/area/FacetsSummaryArea.php
new file mode 100644
index 0000000..0773387
--- /dev/null
+++ b/modules/facets_summary/src/Plugin/views/area/FacetsSummaryArea.php
@@ -0,0 +1,103 @@
+facetSummaryManager = $facet_summary_manager;
+ $this->facetSummaryStorage = $facet_storage;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $container->get('facets_summary.manager'),
+ $container->get('entity_type.manager')->getStorage('facets_summary')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function defineOptions() {
+ $options = parent::defineOptions();
+ // Set the default to TRUE so it shows on empty pages by default.
+ $options['empty']['default'] = TRUE;
+ $options['facet_summary'] = ['default' => ''];
+ return $options;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
+ $this->FacetsSummaryiewsBuildOptionsForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function adminSummary() {
+ return '';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function render($empty = FALSE) {
+ return $this->FacetsViewsGetFacetSummary();
+ }
+
+}
diff --git a/modules/facets_summary/src/Plugin/views/filter/FacetsSummaryFilter.php b/modules/facets_summary/src/Plugin/views/filter/FacetsSummaryFilter.php
new file mode 100644
index 0000000..a7fbb23
--- /dev/null
+++ b/modules/facets_summary/src/Plugin/views/filter/FacetsSummaryFilter.php
@@ -0,0 +1,143 @@
+facetSummaryManager = $facet_summary_manager;
+ $this->facetSummaryStorage = $facet_storage;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $container->get('facets_summary.manager'),
+ $container->get('entity_type.manager')->getStorage('facets_summary')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function defineOptions() {
+ $random = new Random();
+ $options = parent::defineOptions();
+ $options['exposed'] = ['default' => TRUE];
+ $options['expose']['contains']['identifier'] = ['default' => 'facet_summary_' . $random->name()];
+ $options['facet_summary']['default'] = '';
+ return $options;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
+ $this->FacetsSummaryiewsBuildOptionsForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function adminSummary() {
+ return '';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function valueForm(&$form, FormStateInterface $form_state) {
+ static $is_processing = NULL;
+
+ if ($is_processing) {
+ $form['value'] = [];
+ return;
+ }
+
+ $is_processing = TRUE;
+ $form['value'] = $this->FacetsViewsGetFacetSummary();
+ $is_processing = FALSE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function acceptExposedInput($input) {
+ return FALSE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateExposeForm($form, FormStateInterface $form_state) {}
+
+ /**
+ * {@inheritdoc}
+ */
+ public function canGroup() {
+ return FALSE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function query() {}
+
+}
diff --git a/src/Controller/FacetsViewsAjaxController.php b/src/Controller/FacetsViewsAjaxController.php
new file mode 100644
index 0000000..3f60abe
--- /dev/null
+++ b/src/Controller/FacetsViewsAjaxController.php
@@ -0,0 +1,25 @@
+facetsRemoveQueryParams($request);
+ $response = parent::ajaxView($request);
+ return $this->addExposedBlockToResponse($response);
+ }
+
+}
diff --git a/src/Controller/FacetsViewsAjaxGetController.php b/src/Controller/FacetsViewsAjaxGetController.php
new file mode 100644
index 0000000..756ff39
--- /dev/null
+++ b/src/Controller/FacetsViewsAjaxGetController.php
@@ -0,0 +1,25 @@
+facetsRemoveQueryParams($request);
+ $response = parent::ajaxView($request);
+ return $this->addExposedBlockToResponse($response);
+ }
+
+}
diff --git a/src/Entity/Facet.php b/src/Entity/Facet.php
index f615f35..6dd4b2e 100644
--- a/src/Entity/Facet.php
+++ b/src/Entity/Facet.php
@@ -1063,6 +1063,13 @@ class Facet extends ConfigEntityBase implements FacetInterface {
if (!$update) {
self::clearBlockCache();
}
+ // @todo move into condition above when this issue
+ // https://www.drupal.org/project/search_api/issues/3197050 will be fixed.
+ // Right now it clears the search api results and views cache by re-saving a
+ // views view config.
+ if ($source = $this->getFacetSource()) {
+ $this->getFacetSource()->registerFacet($this);
+ }
}
/**
diff --git a/src/EventSubscriber/RouteAlterSubscriber.php b/src/EventSubscriber/RouteAlterSubscriber.php
new file mode 100644
index 0000000..54cae76
--- /dev/null
+++ b/src/EventSubscriber/RouteAlterSubscriber.php
@@ -0,0 +1,39 @@
+getRouteCollection();
+ if ($route = $collection->get('views.ajax')) {
+ $controller = $route->getDefault('_controller');
+ // Views AJAX get support.
+ if ($controller == '\Drupal\views_ajax_get\Controller\ViewsAjaxController::ajaxView') {
+ $route->setDefault('_controller', '\Drupal\facets\Controller\FacetsViewsAjaxGetController::ajaxView');
+ }
+ else {
+ $route->setDefault('_controller', '\Drupal\facets\Controller\FacetsViewsAjaxController::ajaxView');
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getSubscribedEvents() {
+ $events[RoutingEvents::ALTER][] = ['onRouteAlter', -100];
+ return $events;
+ }
+
+}
diff --git a/src/FacetManager/DefaultFacetManager.php b/src/FacetManager/DefaultFacetManager.php
index b3e93df..f2e5fc7 100644
--- a/src/FacetManager/DefaultFacetManager.php
+++ b/src/FacetManager/DefaultFacetManager.php
@@ -2,7 +2,11 @@
namespace Drupal\facets\FacetManager;
+use Drupal\Core\Cache\CacheableDependencyInterface;
+use Drupal\Core\Cache\CacheableMetadata;
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Routing\CurrentRouteMatch;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\facets\Exception\InvalidProcessorException;
use Drupal\facets\FacetInterface;
@@ -78,23 +82,34 @@ class DefaultFacetManager {
*/
protected $builtFacets = [];
+ /**
+ * @var \Drupal\Core\Routing\CurrentRouteMatch
+ */
+ private $routeMatch;
+
/**
* Constructs a new instance of the DefaultFacetManager.
*
- * @param \Drupal\facets\QueryType\QueryTypePluginManager $query_type_plugin_manager
+ * @param \Drupal\facets\QueryType\QueryTypePluginManager $query_type_plugin_manager
* The query type plugin manager.
- * @param \Drupal\facets\FacetSource\FacetSourcePluginManager $facet_source_manager
+ * @param \Drupal\facets\FacetSource\FacetSourcePluginManager $facet_source_manager
* The facet source plugin manager.
- * @param \Drupal\facets\Processor\ProcessorPluginManager $processor_plugin_manager
+ * @param \Drupal\facets\Processor\ProcessorPluginManager $processor_plugin_manager
* The processor plugin manager.
- * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+ * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type plugin manager.
+ * @param \Drupal\Core\Routing\CurrentRouteMatch $route_match
+ * The current route match.
+ *
+ * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+ * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
- public function __construct(QueryTypePluginManager $query_type_plugin_manager, FacetSourcePluginManager $facet_source_manager, ProcessorPluginManager $processor_plugin_manager, EntityTypeManagerInterface $entity_type_manager) {
+ public function __construct(QueryTypePluginManager $query_type_plugin_manager, FacetSourcePluginManager $facet_source_manager, ProcessorPluginManager $processor_plugin_manager, EntityTypeManagerInterface $entity_type_manager, CurrentRouteMatch $route_match) {
$this->queryTypePluginManager = $query_type_plugin_manager;
$this->facetSourcePluginManager = $facet_source_manager;
$this->processorPluginManager = $processor_plugin_manager;
$this->facetStorage = $entity_type_manager->getStorage('facets_facet');
+ $this->routeMatch = $route_match;
}
/**
@@ -103,17 +118,21 @@ class DefaultFacetManager {
* This method is called by the implementing module to initialize the facet
* display process.
*
- * @param mixed $query
+ * @param mixed $query
* The backend's native query object.
- * @param string $facetsource_id
+ * @param string $facetsource_id
* The facet source ID to process.
+ *
+ * @throws \Drupal\Component\Plugin\Exception\PluginException
*/
public function alterQuery(&$query, $facetsource_id) {
+ $query_is_cacheable = $query instanceof RefinableCacheableDependencyInterface;
/** @var \Drupal\facets\FacetInterface[] $facets */
$facets = $this->getFacetsByFacetSourceId($facetsource_id);
- foreach ($facets as $facet) {
+ foreach ($facets as $facet) {
$processors = $facet->getProcessors();
+
if (isset($processors['dependent_processor'])) {
$conditions = $processors['dependent_processor']->getConfiguration();
@@ -126,7 +145,6 @@ class DefaultFacetManager {
}
foreach ($enabled_conditions as $facet_id => $condition_settings) {
-
if (!isset($facets[$facet_id]) || !$processors['dependent_processor']->isConditionMet($condition_settings, $facets[$facet_id])) {
// The conditions are not met anymore, remove the active items.
$facet->setActiveItems([]);
@@ -156,6 +174,10 @@ class DefaultFacetManager {
]
);
$query_type_plugin->execute();
+ // Merge cache medata that gathered from facet and its processors.
+ if ($query_is_cacheable) {
+ $query->addCacheableDependency($facet);
+ }
}
}
@@ -172,11 +194,12 @@ class DefaultFacetManager {
/**
* Returns currently rendered facets filtered by facetsource ID.
*
- * @param string $facetsource_id
+ * @param string $facetsource_id
* The facetsource ID to filter by.
*
* @return \Drupal\facets\FacetInterface[]
* An array of enabled facets.
+ * @throws \Drupal\facets\Exception\InvalidProcessorException
*/
public function getFacetsByFacetSourceId($facetsource_id) {
// Immediately initialize the facets.
@@ -235,7 +258,11 @@ class DefaultFacetManager {
throw new InvalidProcessorException("The processor {$processor->getPluginDefinition()['id']} has a post_query definition but doesn't implement the required PostQueryProcessor interface");
}
$post_query_processor->postQuery($facet);
+ if ($post_query_processor instanceof CacheableDependencyInterface) {
+ $facet->addCacheableDependency($post_query_processor);
+ }
}
+
$this->processedFacets[$facetsource_id][$facet->id()] = $facet;
}
}
@@ -263,6 +290,9 @@ class DefaultFacetManager {
throw new InvalidProcessorException("The processor {$processor->getPluginDefinition()['id']} has a pre_query definition but doesn't implement the required PreQueryProcessorInterface interface");
}
$pre_query_processor->preQuery($facet);
+ if ($processor instanceof CacheableDependencyInterface) {
+ $facet->addCacheableDependency($processor);
+ }
}
}
}
@@ -311,12 +341,18 @@ class DefaultFacetManager {
throw new InvalidProcessorException("The processor {$processor->getPluginDefinition()['id']} has a build definition but doesn't implement the required BuildProcessorInterface interface");
}
$results = $processor->build($facet, $results);
+ if ($processor instanceof CacheableDependencyInterface) {
+ $facet->addCacheableDependency($processor);
+ }
}
// Trigger sort stage.
$active_sort_processors = [];
foreach ($facet->getProcessorsByStage(ProcessorInterface::STAGE_SORT) as $processor) {
$active_sort_processors[] = $processor;
+ if ($processor instanceof CacheableDependencyInterface) {
+ $facet->addCacheableDependency($processor);
+ }
}
// Sort the actual results if we have enabled sort processors.
@@ -369,7 +405,7 @@ class DefaultFacetManager {
/** @var \Drupal\facets\Widget\WidgetPluginInterface $widget */
$widget = $facet->getWidgetInstance();
$build = $widget->build($facet);
-
+ CacheableMetadata::createFromObject($facet)->applyTo($build);
// No results behavior handling. Return a custom text or false depending on
// settings.
if (empty($facet->getResults())) {
@@ -392,10 +428,9 @@ class DefaultFacetManager {
];
}
else {
- // If the facet has no results, but it is being rendered trough ajax we
- // should render a container (that is empty). This is because the
- // javascript needs to be able to find a div to replace with the new
- // content.
+ // If the facet has no results, but it is being rendered trough AJAX it
+ // should render an empty container. This is because the JavaScript
+ // needs to be able to find a div to replace with the new content.
return [
[
0 => $build,
@@ -415,8 +450,10 @@ class DefaultFacetManager {
/**
* Updates all facets of a given facet source with the raw results.
*
- * @param string $facetsource_id
+ * @param string $facetsource_id
* The facet source ID of the currently processed facet.
+ *
+ * @throws \Drupal\facets\Exception\InvalidProcessorException|\Drupal\Component\Plugin\Exception\PluginException
*/
public function updateResults($facetsource_id) {
$facets = $this->getFacetsByFacetSourceId($facetsource_id);
@@ -438,11 +475,12 @@ class DefaultFacetManager {
* Keep in mind that if you want to have the facet's build processor executed,
* call returnBuiltFacet() instead.
*
- * @param \Drupal\facets\FacetInterface $facet
+ * @param \Drupal\facets\FacetInterface $facet
* The facet to process.
*
* @return \Drupal\facets\FacetInterface|null
* The updated facet if it exists, NULL otherwise.
+ * @throws \Drupal\facets\Exception\InvalidProcessorException
*/
public function returnProcessedFacet(FacetInterface $facet) {
$this->processFacets($facet->getFacetSourceId());
@@ -452,11 +490,12 @@ class DefaultFacetManager {
/**
* Returns one of the built facets.
*
- * @param \Drupal\facets\FacetInterface $facet
+ * @param \Drupal\facets\FacetInterface $facet
* The facet to process.
*
* @return \Drupal\facets\FacetInterface
* The built facet.
+ * @throws \Drupal\facets\Exception\InvalidProcessorException
*/
public function returnBuiltFacet(FacetInterface $facet) {
return $this->processBuild($facet);
diff --git a/src/FacetSource/FacetSourcePluginBase.php b/src/FacetSource/FacetSourcePluginBase.php
index 9a470f8..4141dd9 100644
--- a/src/FacetSource/FacetSourcePluginBase.php
+++ b/src/FacetSource/FacetSourcePluginBase.php
@@ -2,6 +2,7 @@
namespace Drupal\facets\FacetSource;
+use Drupal\Core\Cache\UncacheableDependencyTrait;
use Drupal\Core\Plugin\PluginBase;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -24,6 +25,7 @@ use Drupal\facets\QueryType\QueryTypePluginManager;
* @see plugin_api
*/
abstract class FacetSourcePluginBase extends PluginBase implements FacetSourcePluginInterface, ContainerFactoryPluginInterface {
+ use UncacheableDependencyTrait;
/**
* The plugin manager.
@@ -153,4 +155,10 @@ abstract class FacetSourcePluginBase extends PluginBase implements FacetSourcePl
$this->facet->setFieldIdentifier($field_identifier);
}
+ /**
+ * {@inheritdoc}
+ */
+ public function registerFacet(FacetInterface $facet) {
+ }
+
}
diff --git a/src/FacetSource/FacetSourcePluginInterface.php b/src/FacetSource/FacetSourcePluginInterface.php
index 1b97101..d8eca29 100644
--- a/src/FacetSource/FacetSourcePluginInterface.php
+++ b/src/FacetSource/FacetSourcePluginInterface.php
@@ -3,6 +3,7 @@
namespace Drupal\facets\FacetSource;
use Drupal\Component\Plugin\DependentPluginInterface;
+use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\facets\FacetInterface;
@@ -15,7 +16,7 @@ use Drupal\facets\FacetInterface;
*
* @see plugin_api
*/
-interface FacetSourcePluginInterface extends PluginFormInterface, DependentPluginInterface {
+interface FacetSourcePluginInterface extends PluginFormInterface, DependentPluginInterface, CacheableDependencyInterface {
/**
* Fills the facet entities with results from the facet source.
@@ -115,4 +116,15 @@ interface FacetSourcePluginInterface extends PluginFormInterface, DependentPlugi
*/
public function buildFacet();
+ /**
+ * Register newly added facet within its source.
+ *
+ * Add facet cache tags and contexts into the facet source, to make sure that
+ * it will be invalidated whenever facet preferences will change.
+ *
+ * @param \Drupal\facets\FacetInterface $facet
+ * Facet that being inserted or updated.
+ */
+ public function registerFacet(FacetInterface $facet);
+
}
diff --git a/src/FacetsViewsAjaxTrait.php b/src/FacetsViewsAjaxTrait.php
new file mode 100644
index 0000000..d27f100
--- /dev/null
+++ b/src/FacetsViewsAjaxTrait.php
@@ -0,0 +1,75 @@
+getMethod() === Request::METHOD_POST) {
+ foreach (['f', 'page'] as $key) {
+ if ($request->query->has($key)) {
+ $request->query->remove($key);
+ }
+ }
+ }
+ }
+
+ /**
+ * Adds the exposed form to the response if necessary.
+ *
+ * @param mixed $response
+ * The AJAX response. This is not type hinted because this can either be
+ * a ViewsAjaxResponse or a CacheableViewsAjaxResponse.
+ *
+ * @return mixed
+ * An empty array, the output from Views, or the response object.
+ */
+ public function addExposedBlockToResponse($response) {
+ $view = $response->getView();
+ $display = $view->getDisplay();
+ if ($display->getOption('exposed_block') && $display->usesExposedFormInBlock()) {
+
+ $context = new RenderContext();
+ $exposed_block = $this->renderer->executeInRenderContext($context, function () use ($view) {
+ $output = $view->display_handler->viewExposedFormBlocks();
+ if (is_array($output) && !empty($output)) {
+ return $output;
+ }
+
+ return [];
+ });
+
+ if (!$context->isEmpty() && !empty($exposed_block)) {
+ $bubbleable_metadata = $context->pop();
+ BubbleableMetadata::createFromRenderArray($exposed_block)
+ ->merge($bubbleable_metadata)
+ ->applyTo($exposed_block);
+ }
+
+ // Replace exposed block.
+ $selector = 'views-exposed-form-' . strtr($view->id(), '_', '-') . '-' . strtr($view->current_display, '_', '-');
+ $response->addCommand(new ReplaceCommand("#" . $selector, $exposed_block));
+ }
+
+ return $response;
+ }
+
+}
diff --git a/src/Form/FacetSettingsForm.php b/src/Form/FacetSettingsForm.php
index 3000147..4632292 100644
--- a/src/Form/FacetSettingsForm.php
+++ b/src/Form/FacetSettingsForm.php
@@ -305,9 +305,10 @@ class FacetSettingsForm extends EntityForm {
if ($view->display_handler instanceof Block) {
$facet->setOnlyVisibleWhenFacetSourceIsVisible(FALSE);
}
- $view->display_handler->overrideOption('cache', ['type' => 'none']);
- $view->save();
- $this->messenger()->addMessage($this->t('Caching of view %view has been disabled.', ['%view' => $view->storage->label()]));
+ $views_cache_type = $view->display_handler->getOption('cache')['type'];
+ if ($views_cache_type !== 'none') {
+ $this->messenger()->addMessage($this->t('You may experience issues, because %view use cache. In case you will try to turn set cache plugin to none.', ['%view' => $view->storage->label()]));
+ }
}
}
diff --git a/src/Plugin/Block/FacetBlock.php b/src/Plugin/Block/FacetBlock.php
index ac3b076..5ad94d4 100644
--- a/src/Plugin/Block/FacetBlock.php
+++ b/src/Plugin/Block/FacetBlock.php
@@ -3,9 +3,10 @@
namespace Drupal\facets\Plugin\Block;
use Drupal\Core\Block\BlockBase;
+use Drupal\Core\Cache\CacheableDependencyInterface;
+use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
-use Drupal\Core\Form\FormStateInterface;
use Drupal\facets\FacetManager\DefaultFacetManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -105,54 +106,85 @@ class FacetBlock extends BlockBase implements ContainerFactoryPluginInterface {
else {
$build['#attributes']['class'][] = 'facet-inactive';
}
-
- // Add classes needed for ajax.
- if (!empty($build['#use_ajax'])) {
- $build['#attributes']['class'][] = 'block-facets-ajax';
- // The configuration block id isn't always set in the configuration.
- if (isset($this->configuration['block_id'])) {
- $build['#attributes']['class'][] = 'js-facet-block-id-' . $this->configuration['block_id'];
- }
- else {
- $build['#attributes']['class'][] = 'js-facet-block-id-' . $this->pluginId;
- }
- }
}
return $build;
}
+ /**
+ * Get cache metadata from the facet source plugin.
+ *
+ * @return \Drupal\Core\Cache\CacheableMetadata
+ * Facet block cache metadata.
+ */
+ protected function getCacheMetadata() {
+ $facet = $this->facetStorage->load($this->getDerivativeId());
+ $metadata = (new CacheableMetadata())
+ ->setCacheTags($facet->getCacheTags())
+ ->setCacheContexts($facet->getCacheContexts());
+ if (
+ ($source = $facet->getFacetSource())
+ && $source instanceof CacheableDependencyInterface
+ ) {
+ $metadata = $metadata->merge(
+ (new CacheableMetadata())
+ ->setCacheTags($source->getCacheTags())
+ ->setCacheContexts($source->getCacheContexts())
+ )->setCacheMaxAge($source->getCacheMaxAge());
+ }
+ else {
+ // A facet block cannot be cached, because it must always match the
+ // current search results, and Search API gets those search results from a
+ // data source that can be external to Drupal. Therefore it is impossible
+ // to guarantee that the search results are in sync with the data managed
+ // by Drupal. Consequently, it is not possible to cache the search results
+ // at all. If the search results cannot be cached, then neither can the
+ // facets, because they must always match.
+ // Fortunately, facet blocks are rendered using a lazy builder (like all
+ // blocks in Drupal), which means their rendering can be deferred (unlike
+ // the search results, which are the main content of the page, and
+ // deferring their rendering would mean sending an empty page to the
+ // user). This means that facet blocks can be rendered and sent *after*
+ // the initial page was loaded, by installing the BigPipe (big_pipe)
+ // module.
+ //
+ // When BigPipe is enabled, the search results will appear first, and then
+ // each facet block will appear one-by-one, in DOM order.
+ // See https://www.drupal.org/project/big_pipe.
+ //
+ // In a future version of Facet API, this could be refined, but due to the
+ // reliance on external data sources, it will be very difficult if not
+ // impossible to improve this significantly.
+ //
+ // Note: when using Drupal core's Search module instead of the contributed
+ // Search API module, the above limitations do not apply, but for now it's
+ // not considered worth the effort to optimize this just for Drupal core's
+ // Search.
+ $metadata->setCacheMaxAge(0);
+ }
+
+ return $metadata;
+ }
+
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
- // A facet block cannot be cached, because it must always match the current
- // search results, and Search API gets those search results from a data
- // source that can be external to Drupal. Therefore it is impossible to
- // guarantee that the search results are in sync with the data managed by
- // Drupal. Consequently, it is not possible to cache the search results at
- // all. If the search results cannot be cached, then neither can the facets,
- // because they must always match.
- // Fortunately, facet blocks are rendered using a lazy builder (like all
- // blocks in Drupal), which means their rendering can be deferred (unlike
- // the search results, which are the main content of the page, and deferring
- // their rendering would mean sending an empty page to the user). This means
- // that facet blocks can be rendered and sent *after* the initial page was
- // loaded, by installing the BigPipe (big_pipe) module.
- //
- // When BigPipe is enabled, the search results will appear first, and then
- // each facet block will appear one-by-one, in DOM order.
- // See https://www.drupal.org/project/big_pipe.
- //
- // In a future version of Facet API, this could be refined, but due to the
- // reliance on external data sources, it will be very difficult if not
- // impossible to improve this significantly.
- //
- // Note: when using Drupal core's Search module instead of the contributed
- // Search API module, the above limitations do not apply, but for now it is
- // not considered worth the effort to optimize this just for Drupal core's
- // Search.
- return 0;
+ return $this->getCacheMetadata()->getCacheMaxAge();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCacheTags() {
+ return $this->getCacheMetadata()->getCacheTags();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCacheContexts() {
+ return $this->getCacheMetadata()->getCacheContexts();
}
/**
diff --git a/src/Plugin/facets/facet_source/SearchApiDisplay.php b/src/Plugin/facets/facet_source/SearchApiDisplay.php
index 57911d5..4cd3a30 100644
--- a/src/Plugin/facets/facet_source/SearchApiDisplay.php
+++ b/src/Plugin/facets/facet_source/SearchApiDisplay.php
@@ -3,6 +3,7 @@
namespace Drupal\facets\Plugin\facets\facet_source;
use Drupal\Component\Plugin\DependentPluginInterface;
+use Drupal\Core\Cache\Cache;
use Drupal\Core\Extension\ModuleHandler;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
@@ -146,6 +147,45 @@ class SearchApiDisplay extends FacetSourcePluginBase implements SearchApiFacetSo
return $this->getDisplay()->getPath();
}
+ /**
+ * Helper function to get arguments for views contextual filters.
+ *
+ * @return array
+ * Values of contextual filters.
+ */
+ private function extractArgumentsForViewDisplay(): array {
+ $argumentValues = [];
+ // For AJAX requests we cannot take the value the same way as for non-AJAX
+ // requests because route is identified as Drupal AJAX and views arguments
+ // are removed by Views.
+ if ($this->request->isXmlHttpRequest()) {
+ $argumentValues = explode('/', $_REQUEST['view_args']);
+ }
+ else {
+ $display = $this->getViewsDisplay()->getDisplay();
+
+ // Display plugin which have a path, i.e. pages.
+ // @see \Drupal\views\Plugin\views\display\PathPluginBase
+ if ($display->hasPath()) {
+ $viewUrlParameters = $display->getUrl()->getRouteParameters();
+ if (!empty($viewUrlParameters)) {
+ $parameters = [];
+ foreach ($viewUrlParameters as $viewUrlParameter => $validator) {
+ $parameters[] = $this->request->attributes->has($viewUrlParameter) ? $this->request->attributes->get($viewUrlParameter) : NULL;
+ }
+
+ // Add view parameters as arguments only if at least one of them
+ // resolved to a value, otherwise let views handle the defaults.
+ if (!empty(array_filter($parameters))) {
+ $argumentValues = array_merge($argumentValues, $parameters);
+ }
+ }
+ }
+ // @todo Support other plugin types.
+ }
+ return $argumentValues;
+ }
+
/**
* {@inheritdoc}
*/
@@ -164,6 +204,8 @@ class SearchApiDisplay extends FacetSourcePluginBase implements SearchApiFacetSo
if ($results === NULL && isset($display_definition['view_id'])) {
$view = Views::getView($display_definition['view_id']);
$view->setDisplay($display_definition['view_display']);
+ $view->setArguments($this->extractArgumentsForViewDisplay());
+ $view->preExecute();
$view->execute();
$results = $this->searchApiQueryHelper->getResults($search_id);
}
@@ -387,21 +429,6 @@ class SearchApiDisplay extends FacetSourcePluginBase implements SearchApiFacetSo
if ($view === NULL) {
return $build;
}
-
- // Add JS for Views with Ajax Enabled.
- if ($view->display_handler->ajaxEnabled()) {
- $js_settings = [
- 'view_id' => $view->id(),
- 'current_display_id' => $view->current_display,
- 'view_base_path' => ltrim($view->getPath() ?? '', '/'),
- 'ajax_path' => Url::fromRoute('views.ajax')->toString(),
- ];
- $build['#attached']['library'][] = 'facets/drupal.facets.views-ajax';
- $build['#attached']['drupalSettings']['facets_views_ajax'] = [
- $this->facet->id() => $js_settings,
- ];
- $build['#use_ajax'] = TRUE;
- }
return $build;
}
@@ -418,4 +445,46 @@ class SearchApiDisplay extends FacetSourcePluginBase implements SearchApiFacetSo
}
}
+ /**
+ * {@inheritdoc}
+ */
+ public function getCacheContexts() {
+ return $this->getViewsDisplay()
+ ->getDisplay()
+ ->getCacheMetadata()
+ ->getCacheContexts();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCacheTags() {
+ $view_display = $this->getViewsDisplay()->getDisplay();
+ return Cache::mergeTags(
+ $view_display->getCacheMetadata()->getCacheTags(),
+ $view_display->getPlugin('cache')->getCacheTags()
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCacheMaxAge() {
+ $view_display = $this->getViewsDisplay()->getDisplay();
+ return Cache::mergeMaxAges(
+ $view_display->getCacheMetadata()->getCacheMaxAge(),
+ $view_display->getPlugin('cache')->getCacheMaxAge()
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function registerFacet(FacetInterface $facet) {
+ // Alter views view cache metadata.
+ // @see \Drupal\search_api\Plugin\views\cache\SearchApiCachePluginTrait::generateResultsKey()
+ // @see \Drupal\views\Plugin\views\cache\CachePluginBase::alterCacheMetadata()
+ $this->getViewsDisplay()->save();
+ }
+
}
diff --git a/src/Plugin/facets/hierarchy/Taxonomy.php b/src/Plugin/facets/hierarchy/Taxonomy.php
index 129c0e0..cc04ee2 100644
--- a/src/Plugin/facets/hierarchy/Taxonomy.php
+++ b/src/Plugin/facets/hierarchy/Taxonomy.php
@@ -2,6 +2,7 @@
namespace Drupal\facets\Plugin\facets\hierarchy;
+use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\facets\Hierarchy\HierarchyPluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -197,4 +198,11 @@ class Taxonomy extends HierarchyPluginBase {
return $this->termParents[$tid] = reset($parents)->id();
}
+ /**
+ * {@inheritdoc}
+ */
+ public function getCacheTags() {
+ return Cache::mergeTags(parent::getCacheTags(), ['taxonomy_term:list']);
+ }
+
}
diff --git a/src/Plugin/facets/processor/CombineFacetProcessor.php b/src/Plugin/facets/processor/CombineFacetProcessor.php
index 40603eb..a51dbbf 100644
--- a/src/Plugin/facets/processor/CombineFacetProcessor.php
+++ b/src/Plugin/facets/processor/CombineFacetProcessor.php
@@ -148,7 +148,6 @@ class CombineFacetProcessor extends ProcessorPluginBase implements BuildProcesso
/** @var \Drupal\facets\Entity\Facet $current_facet */
$current_facet = $this->facetStorage->load($facet_id);
$current_facet = $this->facetsManager->returnBuiltFacet($current_facet);
-
switch ($settings['mode']) {
case 'union':
$results = $keyed_results + $current_facet->getResultsKeyedByRawValue();
@@ -162,6 +161,8 @@ class CombineFacetProcessor extends ProcessorPluginBase implements BuildProcesso
$results = array_intersect_key($keyed_results, $current_facet->getResultsKeyedByRawValue());
break;
}
+ // Pass build processor information into current facet.
+ $facet->addCacheableDependency($current_facet);
}
return $results;
diff --git a/src/Plugin/facets/processor/TermWeightWidgetOrderProcessor.php b/src/Plugin/facets/processor/TermWeightWidgetOrderProcessor.php
index 2993893..5168ea6 100644
--- a/src/Plugin/facets/processor/TermWeightWidgetOrderProcessor.php
+++ b/src/Plugin/facets/processor/TermWeightWidgetOrderProcessor.php
@@ -2,6 +2,7 @@
namespace Drupal\facets\Plugin\facets\processor;
+use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\TypedData\ComplexDataDefinitionInterface;
@@ -126,4 +127,11 @@ class TermWeightWidgetOrderProcessor extends SortProcessorPluginBase implements
return FALSE;
}
+ /**
+ * {@inheritdoc}
+ */
+ public function getCacheTags() {
+ return Cache::mergeTags(parent::getCacheTags(), ['taxonomy_term:list']);
+ }
+
}
diff --git a/src/Plugin/facets/processor/TranslateEntityAggregatedFieldProcessor.php b/src/Plugin/facets/processor/TranslateEntityAggregatedFieldProcessor.php
index efc4c18..09cbd6c 100644
--- a/src/Plugin/facets/processor/TranslateEntityAggregatedFieldProcessor.php
+++ b/src/Plugin/facets/processor/TranslateEntityAggregatedFieldProcessor.php
@@ -167,7 +167,7 @@ class TranslateEntityAggregatedFieldProcessor extends ProcessorPluginBase implem
if ($entity instanceof TranslatableInterface && $entity->hasTranslation($language_interface->getId())) {
$entity = $entity->getTranslation($language_interface->getId());
}
-
+ $facet->addCacheableDependency($entity);
// Overwrite the result's display value.
$results[$i]->setDisplayValue($entity->label());
}
diff --git a/src/Plugin/facets/processor/TranslateEntityProcessor.php b/src/Plugin/facets/processor/TranslateEntityProcessor.php
index dff609f..5786703 100644
--- a/src/Plugin/facets/processor/TranslateEntityProcessor.php
+++ b/src/Plugin/facets/processor/TranslateEntityProcessor.php
@@ -128,7 +128,7 @@ class TranslateEntityProcessor extends ProcessorPluginBase implements BuildProce
if ($entity instanceof TranslatableInterface && $entity->hasTranslation($language_interface->getId())) {
$entity = $entity->getTranslation($language_interface->getId());
}
-
+ $facet->addCacheableDependency($entity);
// Overwrite the result's display value.
$results[$i]->setDisplayValue($entity->label());
}
diff --git a/src/Plugin/facets/processor/UidToUserNameCallbackProcessor.php b/src/Plugin/facets/processor/UidToUserNameCallbackProcessor.php
index 254037d..0eaf4c5 100644
--- a/src/Plugin/facets/processor/UidToUserNameCallbackProcessor.php
+++ b/src/Plugin/facets/processor/UidToUserNameCallbackProcessor.php
@@ -34,6 +34,7 @@ class UidToUserNameCallbackProcessor extends ProcessorPluginBase implements Buil
/** @var \Drupal\user\Entity\User $user */
if (($user = User::load($result->getRawValue())) !== NULL) {
$result->setDisplayValue($user->getDisplayName());
+ $facet->addCacheableDependency($user);
$usernames[] = $result;
}
}
diff --git a/src/Plugin/facets/processor/UrlProcessorHandler.php b/src/Plugin/facets/processor/UrlProcessorHandler.php
index 92f8655..54aceda 100644
--- a/src/Plugin/facets/processor/UrlProcessorHandler.php
+++ b/src/Plugin/facets/processor/UrlProcessorHandler.php
@@ -2,6 +2,7 @@
namespace Drupal\facets\Plugin\facets\processor;
+use Drupal\Core\Cache\Cache;
use Drupal\facets\Exception\InvalidProcessorException;
use Drupal\facets\FacetInterface;
use Drupal\facets\Processor\BuildProcessorInterface;
@@ -82,4 +83,14 @@ class UrlProcessorHandler extends ProcessorPluginBase implements BuildProcessorI
$this->processor->setActiveItems($facet);
}
+ /**
+ * {@inheritdoc}
+ */
+ public function getCacheContexts() {
+ return Cache::mergeContexts(
+ parent::getCacheContexts(),
+ ['url.path', 'url.query_args']
+ );
+ }
+
}
diff --git a/src/Plugin/views/FacetsViewsPluginTrait.php b/src/Plugin/views/FacetsViewsPluginTrait.php
new file mode 100644
index 0000000..4566d1b
--- /dev/null
+++ b/src/Plugin/views/FacetsViewsPluginTrait.php
@@ -0,0 +1,96 @@
+facetStorage->loadMultiple();
+
+ $format = 'search_api:views_%s__%s__%s';
+ $source = sprintf($format, $this->view->getDisplay()->getPluginId(), $this->view->id(), $this->view->current_display);
+ foreach ($facets as $facet) {
+ if ($facet->getFacetSourceId() === $source) {
+ $options[$facet->id()] = $facet->label();
+ }
+ }
+
+ $form['facets'] = [
+ '#title' => 'Facets',
+ '#options' => $options,
+ '#type' => 'checkboxes',
+ '#required' => TRUE,
+ '#default_value' => isset($this->options['facets']) ? $this->options['facets'] : [],
+ ];
+ }
+
+ /**
+ * Gets the facets to render.
+ *
+ * @return array
+ * The facet blocks to be output, in render array format.
+ */
+ public function FacetsViewsGetFacets() {
+ $build = [];
+
+ /** @var \Drupal\facets\Entity\Facet[] $facets */
+ $items = [];
+ $facets = $this->facetStorage->loadMultiple(array_filter($this->options['facets']));
+ foreach ($facets as $facet) {
+ $facet_build = $this->facetManager->build($facet);
+ if (!empty($facet_build)) {
+ $facet_source = $facet->getFacetSource();
+ $facet_build += $facet_source->buildFacet();
+ $items[] = [
+ '#theme' => 'block',
+ '#configuration' => [
+ 'provider' => 'facets',
+ 'label' => $facet->label(),
+ 'label_display' => TRUE,
+ ],
+ '#id' => $facet->id(),
+ '#plugin_id' => 'facet_block:' . $facet->id(),
+ '#base_plugin_id' => 'facet_block',
+ '#derivative_plugin_id' => $facet->id(),
+ '#weight' => $facet->getWeight(),
+ '#cache' => [
+ 'contexts' => [],
+ 'tags' => [],
+ 'max-age' => 0,
+ ],
+ 'content' => $facet_build,
+ ];
+ }
+ }
+
+ if (!empty($items)) {
+ $build = [
+ '#theme' => 'facets_views_plugin',
+ '#content' => $items,
+ ];
+
+ if ($this->view->getDisplay()->ajaxEnabled()) {
+ $build['#attached']['library'][] = 'facets/drupal.facets.views-ajax';
+ }
+ }
+
+ return $build;
+ }
+
+}
diff --git a/src/Plugin/views/area/FacetsArea.php b/src/Plugin/views/area/FacetsArea.php
new file mode 100644
index 0000000..026d181
--- /dev/null
+++ b/src/Plugin/views/area/FacetsArea.php
@@ -0,0 +1,103 @@
+facetManager = $facet_manager;
+ $this->facetStorage = $facet_storage;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $container->get('facets.manager'),
+ $container->get('entity_type.manager')->getStorage('facets_facet')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function defineOptions() {
+ $options = parent::defineOptions();
+ // Set the default to TRUE so it shows on empty pages by default.
+ $options['empty']['default'] = TRUE;
+ $options['facets'] = ['default' => []];
+ return $options;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
+ $this->FacetsViewsBuildOptionsForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function adminSummary() {
+ return implode(', ', array_filter($this->options['facets']));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function render($empty = FALSE) {
+ return $this->FacetsViewsGetFacets();
+ }
+
+}
diff --git a/src/Plugin/views/filter/FacetsFilter.php b/src/Plugin/views/filter/FacetsFilter.php
new file mode 100644
index 0000000..4993ec0
--- /dev/null
+++ b/src/Plugin/views/filter/FacetsFilter.php
@@ -0,0 +1,143 @@
+facetManager = $facet_manager;
+ $this->facetStorage = $facet_storage;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $container->get('facets.manager'),
+ $container->get('entity_type.manager')->getStorage('facets_facet')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function defineOptions() {
+ $random = new Random();
+ $options = parent::defineOptions();
+ $options['exposed'] = ['default' => TRUE];
+ $options['expose']['contains']['identifier'] = ['default' => 'facet_' . $random->name()];
+ $options['facets']['default'] = [];
+ return $options;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
+ $this->FacetsViewsBuildOptionsForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function adminSummary() {
+ return implode(', ', array_filter($this->options['facets']));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function valueForm(&$form, FormStateInterface $form_state) {
+ static $is_processing = NULL;
+
+ if ($is_processing) {
+ $form['value'] = [];
+ return;
+ }
+
+ $is_processing = TRUE;
+ $form['value'] = $this->FacetsViewsGetFacets();
+ $is_processing = FALSE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function acceptExposedInput($input) {
+ return FALSE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateExposeForm($form, FormStateInterface $form_state) {}
+
+ /**
+ * {@inheritdoc}
+ */
+ public function canGroup() {
+ return FALSE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function query() {}
+
+}
diff --git a/src/Processor/ProcessorPluginBase.php b/src/Processor/ProcessorPluginBase.php
index 5b3f3e5..1d0727e 100644
--- a/src/Processor/ProcessorPluginBase.php
+++ b/src/Processor/ProcessorPluginBase.php
@@ -2,6 +2,8 @@
namespace Drupal\facets\Processor;
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Entity\DependencyTrait;
use Drupal\Core\Plugin\PluginBase;
@@ -10,7 +12,7 @@ use Drupal\facets\FacetInterface;
/**
* A base class for plugins that implements most of the boilerplate.
*/
-class ProcessorPluginBase extends PluginBase implements ProcessorInterface {
+class ProcessorPluginBase extends PluginBase implements ProcessorInterface, CacheableDependencyInterface {
use DependencyTrait;
@@ -116,4 +118,25 @@ class ProcessorPluginBase extends PluginBase implements ProcessorInterface {
return NULL;
}
+ /**
+ * {@inheritdoc}
+ */
+ public function getCacheContexts() {
+ return [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCacheTags() {
+ return [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCacheMaxAge() {
+ return Cache::PERMANENT;
+ }
+
}
diff --git a/src/Widget/WidgetPluginBase.php b/src/Widget/WidgetPluginBase.php
index 2cbfb1f..7bbda4b 100644
--- a/src/Widget/WidgetPluginBase.php
+++ b/src/Widget/WidgetPluginBase.php
@@ -81,12 +81,6 @@ abstract class WidgetPluginBase extends PluginBase implements WidgetPluginInterf
'class' => [$facet->getActiveItems() ? 'facet-active' : 'facet-inactive'],
],
'#context' => !empty($widget['type']) ? ['list_style' => $widget['type']] : [],
- '#cache' => [
- 'contexts' => [
- 'url.path',
- 'url.query_args',
- ],
- ],
];
}
diff --git a/templates/facets-views-plugin.html.twig b/templates/facets-views-plugin.html.twig
new file mode 100644
index 0000000..ce4f5d0
--- /dev/null
+++ b/templates/facets-views-plugin.html.twig
@@ -0,0 +1,3 @@
+
+ {{ content }}
+
diff --git a/tests/facets_processors_collection/facets_processors_collection.info.yml b/tests/facets_processors_collection/facets_processors_collection.info.yml
new file mode 100644
index 0000000..03bf078
--- /dev/null
+++ b/tests/facets_processors_collection/facets_processors_collection.info.yml
@@ -0,0 +1,8 @@
+name: 'Facets processors collection'
+type: module
+description: 'Contains collection of test facet processors'
+package: 'Testing'
+hidden: true
+core_version_requirement: ^9.2 || ^10.0
+dependencies:
+ - facets:facets
diff --git a/tests/facets_processors_collection/facets_processors_collection.module b/tests/facets_processors_collection/facets_processors_collection.module
new file mode 100644
index 0000000..1073ff9
--- /dev/null
+++ b/tests/facets_processors_collection/facets_processors_collection.module
@@ -0,0 +1,20 @@
+get('facets_processors_collection_alter_string_query_handler', FALSE)
+ ) {
+ $query_types['string'] = 'search_api_string_cached';
+ }
+}
diff --git a/tests/facets_processors_collection/facets_processors_collection.services.yml b/tests/facets_processors_collection/facets_processors_collection.services.yml
new file mode 100644
index 0000000..4e9e133
--- /dev/null
+++ b/tests/facets_processors_collection/facets_processors_collection.services.yml
@@ -0,0 +1,28 @@
+services:
+ cache_context.fpc_build:
+ class: Drupal\facets_processors_collection\Cache\FpcCacheContext
+ argumets:
+ type: build
+ tags:
+ - { name: cache.context }
+
+ cache_context.fpc_sort:
+ class: Drupal\facets_processors_collection\Cache\FpcCacheContext
+ argumets:
+ type: sort
+ tags:
+ - { name: cache.context }
+
+ cache_context.fpc_post_query:
+ class: Drupal\facets_processors_collection\Cache\FpcCacheContext
+ argumets:
+ type: post_query
+ tags:
+ - { name: cache.context }
+
+ cache_context.fpc_query_type_plugin:
+ class: Drupal\facets_processors_collection\Cache\FpcCacheContext
+ argumets:
+ type: query_type_plugin
+ tags:
+ - { name: cache.context }
diff --git a/tests/facets_processors_collection/src/Cache/FpcCacheContext.php b/tests/facets_processors_collection/src/Cache/FpcCacheContext.php
new file mode 100644
index 0000000..0076003
--- /dev/null
+++ b/tests/facets_processors_collection/src/Cache/FpcCacheContext.php
@@ -0,0 +1,86 @@
+type = $type;
+ }
+
+ /**
+ * Get all allowed context types.
+ *
+ * @return array
+ * Array of context types: all processor stages + query_type plugin.
+ */
+ protected static function getAllowedTypes() {
+ return array_merge(static::$processorStages, [static::QUERY_PLUGIN]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getLabel() {
+ return t(
+ 'FPC: cache context, cab be one of the following: %stages.',
+ ['%stages' => implode(', ', static::getAllowedTypes())]
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getContext() {
+ return 'fpc_' . $this->type;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCacheableMetadata() {
+ return new CacheableMetadata();
+ }
+
+}
diff --git a/tests/facets_processors_collection/src/Plugin/facets/processor/FpcBuildProcessor.php b/tests/facets_processors_collection/src/Plugin/facets/processor/FpcBuildProcessor.php
new file mode 100644
index 0000000..a7bd65a
--- /dev/null
+++ b/tests/facets_processors_collection/src/Plugin/facets/processor/FpcBuildProcessor.php
@@ -0,0 +1,59 @@
+setDisplayValue('Test ' . $result->getDisplayValue());
+ }
+ // An example cache tag that can be added from the ::build().
+ $facet->addCacheTags(['fpc:added_within_build_method']);
+
+ return $results;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCacheTags() {
+ return Cache::mergeTags(parent::getCacheTags(), ['fpc:build_processor']);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCacheContexts() {
+ return Cache::mergeContexts(parent::getCacheContexts(), ['fpc_build']);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getQueryType() {
+ return 'search_api_string_cached';
+ }
+
+}
diff --git a/tests/facets_processors_collection/src/Plugin/facets/processor/FpcPostQueryProcessor.php b/tests/facets_processors_collection/src/Plugin/facets/processor/FpcPostQueryProcessor.php
new file mode 100644
index 0000000..ba2a61c
--- /dev/null
+++ b/tests/facets_processors_collection/src/Plugin/facets/processor/FpcPostQueryProcessor.php
@@ -0,0 +1,45 @@
+addCacheTags(['fpc:added_within_postQuery_method']);
+ }
+
+}
diff --git a/tests/facets_processors_collection/src/Plugin/facets/processor/FpcSortProcessor.php b/tests/facets_processors_collection/src/Plugin/facets/processor/FpcSortProcessor.php
new file mode 100644
index 0000000..45a10cd
--- /dev/null
+++ b/tests/facets_processors_collection/src/Plugin/facets/processor/FpcSortProcessor.php
@@ -0,0 +1,53 @@
+disables cache"),
+ * stages = {
+ * "sort" = 50
+ * }
+ * )
+ */
+class FpcSortRandomProcessor extends FpcSortProcessor {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function sortResults(Result $a, Result $b) {
+ return random_int(-1, 1);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCacheMaxAge() {
+ // As sorting should be random, we can't cache results.
+ return 0;
+ }
+
+}
diff --git a/tests/facets_processors_collection/src/Plugin/facets/query_type/CacheableQueryTypePlugin.php b/tests/facets_processors_collection/src/Plugin/facets/query_type/CacheableQueryTypePlugin.php
new file mode 100644
index 0000000..c5c948b
--- /dev/null
+++ b/tests/facets_processors_collection/src/Plugin/facets/query_type/CacheableQueryTypePlugin.php
@@ -0,0 +1,27 @@
+query->addCacheTags(['fpc:query_plugin_type_plugin']);
+ $this->query->addCacheContexts(['fpc_query_type_plugin']);
+ }
+
+}
diff --git a/tests/facets_search_api_dependency/config/install/views.view.search_api_test_view.yml b/tests/facets_search_api_dependency/config/install/views.view.search_api_test_view.yml
index d13feac..0b617b0 100644
--- a/tests/facets_search_api_dependency/config/install/views.view.search_api_test_view.yml
+++ b/tests/facets_search_api_dependency/config/install/views.view.search_api_test_view.yml
@@ -1,76 +1,34 @@
-base_field: search_api_id
-base_table: search_api_index_database_search_index
-core: 8.x
-description: ''
+langcode: en
status: true
+dependencies:
+ config:
+ - search_api.index.database_search_index
+ module:
+ - search_api
+id: search_api_test_view
+label: 'Search API Test Fulltext search view'
+module: views
+description: ''
+tag: ''
+base_table: search_api_index_database_search_index
+base_field: search_api_id
display:
default:
- display_plugin: default
id: default
display_title: Master
+ display_plugin: default
position: 0
display_options:
- access:
- type: none
- options: { }
- cache:
- type: none
- options: { }
- query:
- type: search_api_query
- options:
- skip_access: true
- exposed_form:
- type: basic
- options:
- submit_button: Search
- reset_button: false
- reset_button_label: Reset
- exposed_sorts_label: 'Sort by'
- expose_sort_order: true
- sort_asc_label: Asc
- sort_desc_label: Desc
- pager:
- type: full
- options:
- items_per_page: 10
- offset: 0
- id: 0
- total_pages: null
- expose:
- items_per_page: false
- items_per_page_label: 'Items per page'
- items_per_page_options: '5, 10, 20, 40, 60'
- items_per_page_options_all: false
- items_per_page_options_all_label: '- All -'
- offset: false
- offset_label: Offset
- tags:
- previous: '‹ previous'
- next: 'next ›'
- first: '« first'
- last: 'last »'
- quantity: 9
- style:
- type: default
- row:
- type: search_api
- options:
- view_modes:
- bundle:
- 'article': default
- 'page': default
- datasource:
- 'entity:entity_test': default
+ title: 'Fulltext test index'
fields:
search_api_id:
+ id: search_api_id
table: search_api_index_database_search_index
field: search_api_id
- id: search_api_id
- plugin_id: numeric
relationship: none
group_type: group
admin_label: ''
+ plugin_id: numeric
label: 'Entity ID'
exclude: false
alter:
@@ -117,9 +75,81 @@ display:
decimal: .
separator: ','
format_plural: false
- format_plural_string: "1\x03@count"
+ format_plural_string: !!binary MQNAY291bnQ=
prefix: ''
suffix: ''
+ pager:
+ type: full
+ options:
+ offset: 0
+ items_per_page: 10
+ total_pages: null
+ id: 0
+ tags:
+ next: 'next ›'
+ previous: '‹ previous'
+ first: '« first'
+ last: 'last »'
+ expose:
+ items_per_page: false
+ items_per_page_label: 'Items per page'
+ items_per_page_options: '5, 10, 20, 40, 60'
+ items_per_page_options_all: false
+ items_per_page_options_all_label: '- All -'
+ offset: false
+ offset_label: Offset
+ quantity: 9
+ exposed_form:
+ type: basic
+ options:
+ submit_button: Search
+ reset_button: false
+ reset_button_label: Reset
+ exposed_sorts_label: 'Sort by'
+ expose_sort_order: true
+ sort_asc_label: Asc
+ sort_desc_label: Desc
+ access:
+ type: none
+ options: { }
+ cache:
+ type: none
+ options: { }
+ empty: { }
+ sorts:
+ search_api_id:
+ id: search_api_id
+ table: search_api_index_database_search_index
+ field: search_api_id
+ relationship: none
+ group_type: group
+ admin_label: ''
+ plugin_id: search_api
+ order: ASC
+ expose:
+ label: ''
+ field_identifier: search_api_id
+ exposed: false
+ arguments:
+ search_api_datasource:
+ id: search_api_datasource
+ table: search_api_index_database_search_index
+ field: search_api_datasource
+ plugin_id: search_api
+ break_phrase: true
+ type:
+ id: type
+ table: search_api_index_database_search_index
+ field: type
+ plugin_id: search_api
+ break_phrase: false
+ not: true
+ keywords:
+ id: keywords
+ table: search_api_index_database_search_index
+ field: keywords
+ plugin_id: search_api
+ break_phrase: true
filters:
search_api_fulltext:
id: search_api_fulltext
@@ -128,6 +158,7 @@ display:
relationship: none
group_type: group
admin_label: ''
+ plugin_id: search_api_fulltext
operator: and
value: ''
group: 1
@@ -138,6 +169,8 @@ display:
description: ''
use_operator: true
operator: search_api_fulltext_op
+ operator_limit_selection: false
+ operator_list: { }
identifier: search_api_fulltext
required: false
remember: false
@@ -160,14 +193,13 @@ display:
group_items: { }
min_length: 3
fields: { }
- plugin_id: search_api_fulltext
id:
- plugin_id: search_api_numeric
id: id
table: search_api_index_database_search_index
field: id
relationship: none
admin_label: ''
+ plugin_id: search_api_numeric
operator: '='
group: 1
exposed: true
@@ -177,6 +209,8 @@ display:
description: ''
use_operator: true
operator: id_op
+ operator_limit_selection: false
+ operator_list: { }
identifier: id
required: false
remember: false
@@ -187,12 +221,12 @@ display:
administrator: '0'
is_grouped: false
created:
- plugin_id: search_api_date
id: created
table: search_api_index_database_search_index
field: created
relationship: none
admin_label: ''
+ plugin_id: search_api_date
operator: '='
group: 1
exposed: true
@@ -202,6 +236,8 @@ display:
description: ''
use_operator: true
operator: created_op
+ operator_limit_selection: false
+ operator_list: { }
identifier: created
required: false
remember: false
@@ -212,12 +248,12 @@ display:
administrator: '0'
is_grouped: false
keywords:
- plugin_id: search_api_string
id: keywords
table: search_api_index_database_search_index
field: keywords
relationship: none
admin_label: ''
+ plugin_id: search_api_string
operator: '='
group: 1
exposed: true
@@ -227,6 +263,8 @@ display:
description: ''
use_operator: true
operator: keywords_op
+ operator_limit_selection: false
+ operator_list: { }
identifier: keywords
required: false
remember: false
@@ -237,13 +275,13 @@ display:
administrator: '0'
is_grouped: false
search_api_language:
- plugin_id: search_api_language
id: search_api_language
table: search_api_index_database_search_index
field: search_api_language
relationship: none
admin_label: ''
- operator: 'in'
+ plugin_id: search_api_language
+ operator: in
group: 1
exposed: true
expose:
@@ -252,6 +290,8 @@ display:
description: ''
use_operator: true
operator: language_op
+ operator_limit_selection: false
+ operator_list: { }
identifier: language
required: false
remember: false
@@ -261,20 +301,22 @@ display:
anonymous: '0'
administrator: '0'
is_grouped: false
- sorts:
- search_api_id:
- id: search_api_id
- table: search_api_index_database_search_index
- field: search_api_id
- relationship: none
- group_type: group
- admin_label: ''
- order: ASC
- exposed: false
- expose:
- label: ''
- plugin_id: search_api
- title: 'Fulltext test index'
+ style:
+ type: default
+ row:
+ type: search_api
+ options:
+ view_modes:
+ bundle:
+ article: default
+ page: default
+ datasource:
+ 'entity:entity_test': default
+ query:
+ type: search_api_query
+ options:
+ skip_access: true
+ relationships: { }
header:
result:
id: result
@@ -283,54 +325,117 @@ display:
relationship: none
group_type: group
admin_label: ''
- content: 'Displaying @total search results'
plugin_id: result
+ content: 'Displaying @total search results'
footer: { }
- empty: { }
- relationships: { }
- arguments:
- search_api_datasource:
- plugin_id: search_api
- id: search_api_datasource
- table: search_api_index_database_search_index
- field: search_api_datasource
- break_phrase: true
- type:
- plugin_id: search_api
- id: type
- table: search_api_index_database_search_index
- field: type
- break_phrase: false
- not: true
- keywords:
- plugin_id: search_api
- id: keywords
- table: search_api_index_database_search_index
- field: keywords
- break_phrase: true
+ display_extenders: { }
+ cache_metadata:
+ max-age: -1
+ contexts:
+ - 'languages:language_interface'
+ - url
+ - url.query_args
+ tags:
+ - 'config:search_api.index.database_search_index'
+ block_1:
+ id: block_1
+ display_title: Block
+ display_plugin: block
+ position: 3
+ display_options:
+ defaults:
+ use_ajax: false
+ use_ajax: true
+ display_extenders: { }
+ cache_metadata:
+ max-age: -1
+ contexts:
+ - 'languages:language_interface'
+ - url
+ - url.query_args
+ tags:
+ - 'config:search_api.index.database_search_index'
+ block_1_sapi_tag:
+ id: block_1_sapi_tag
+ display_title: 'Block Search API cache tag'
+ display_plugin: block
+ position: 4
+ display_options:
+ cache:
+ type: search_api_tag
+ options: { }
+ defaults:
+ cache: false
+ use_ajax: false
+ use_ajax: true
+ display_extenders: { }
+ cache_metadata:
+ max-age: -1
+ contexts:
+ - 'languages:language_interface'
+ - url
+ - url.query_args
+ tags:
+ - 'config:search_api.index.database_search_index'
page_1:
- display_plugin: page
id: page_1
display_title: Page
+ display_plugin: page
position: 1
display_options:
+ display_extenders: { }
path: search-api-test-fulltext
- block_1:
- display_plugin: block
- id: block_1
- display_title: Block
+ cache_metadata:
+ max-age: -1
+ contexts:
+ - 'languages:language_interface'
+ - url
+ - url.query_args
+ tags:
+ - 'config:search_api.index.database_search_index'
+ page_2_sapi_tag:
+ id: page_2_sapi_tag
+ display_title: 'Page Search API cache tag'
+ display_plugin: page
position: 2
display_options:
+ cache:
+ type: search_api_tag
+ options: { }
+ defaults:
+ cache: false
display_extenders: { }
+ path: search-api-test-fulltext-cache-tag
+ cache_metadata:
+ max-age: -1
+ contexts:
+ - 'languages:language_interface'
+ - url
+ - url.query_args
+ tags:
+ - 'config:search_api.index.database_search_index'
+ page_2_sapi_time:
+ id: page_2_sapi_time
+ display_title: 'Page Search API cache time'
+ display_plugin: page
+ position: 2
+ display_options:
+ cache:
+ type: search_api_time
+ options:
+ results_lifespan: 21600
+ results_lifespan_custom: 0
+ output_lifespan: 518400
+ output_lifespan_custom: 0
defaults:
- use_ajax: false
- use_ajax: true
-label: 'Search API Test Fulltext search view'
-module: views
-id: search_api_test_view
-tag: ''
-langcode: en
-dependencies:
- module:
- - search_api
- - facets_search_api_dependency
+ cache: false
+ display_extenders: { }
+ path: search-api-test-fulltext-cache-time
+ cache_metadata:
+ max-age: -1
+ contexts:
+ - 'languages:language_interface'
+ - url
+ - url.query_args
+ tags:
+ - 'config:search_api.index.database_search_index'
diff --git a/tests/src/Functional/BreadcrumbIntegrationTest.php b/tests/src/Functional/BreadcrumbIntegrationTest.php
index 7aa9b48..47d29c7 100644
--- a/tests/src/Functional/BreadcrumbIntegrationTest.php
+++ b/tests/src/Functional/BreadcrumbIntegrationTest.php
@@ -121,7 +121,7 @@ class BreadcrumbIntegrationTest extends FacetsTestBase {
*/
protected function editFacetConfig(array $config = []) {
$this->drupalGet('admin/config/search/facets');
- $this->clickLink('Configure', 1);
+ $this->clickLink('Configure', 2);
$default_config = [
'filter_key' => 'f',
'url_processor' => 'query_string',
diff --git a/tests/src/Functional/HierarchicalFacetIntegrationTest.php b/tests/src/Functional/HierarchicalFacetIntegrationTest.php
index a3052cd..19536b6 100644
--- a/tests/src/Functional/HierarchicalFacetIntegrationTest.php
+++ b/tests/src/Functional/HierarchicalFacetIntegrationTest.php
@@ -383,7 +383,7 @@ class HierarchicalFacetIntegrationTest extends FacetsTestBase {
*/
public function testHierarchyBreadcrumb() {
$this->drupalGet('admin/config/search/facets');
- $this->clickLink('Configure', 1);
+ $this->clickLink('Configure', 2);
$default_config = [
'filter_key' => 'f',
'url_processor' => 'query_string',
diff --git a/tests/src/Functional/IntegrationCacheTest.php b/tests/src/Functional/IntegrationCacheTest.php
new file mode 100644
index 0000000..2bd61a6
--- /dev/null
+++ b/tests/src/Functional/IntegrationCacheTest.php
@@ -0,0 +1,766 @@
+enableWebsiteCache();
+ $this->setUpExampleStructure();
+ $this->insertExampleContent();
+ $this->assertEquals(5, $this->indexItems($this->indexId), '5 items were indexed.');
+
+ $this->facetStorage = $this->container->get('entity_type.manager')
+ ->getStorage('facets_facet');
+ $this->entityTestStorage = \Drupal::entityTypeManager()
+ ->getStorage('entity_test_mulrev_changed');
+ }
+
+ /**
+ * Tests various operations via the Facets' admin UI.
+ *
+ * Cached implementation of testBlockView integration test.
+ *
+ * @see \Drupal\Tests\facets\Functional\IntegrationTest::testBlockView()
+ */
+ public function testFramework() {
+ $facet_id = 'test_facet_name';
+ $this->drupalGet(static::VIEW_URL);
+ // By default, the view should show all entities.
+ $this->assertSession()->pageTextContains('Displaying 5 search results');
+
+ $this->createFacet('Test Facet name', $facet_id, 'type', static::VIEW_DISPLAY);
+
+ // Verify that the facet results are correct.
+ $this->drupalGet(static::VIEW_URL);
+ $this->assertSession()->pageTextContains('item');
+ $this->assertSession()->pageTextContains('article');
+
+ // Verify that facet blocks appear as expected.
+ $this->assertFacetBlocksAppear();
+
+ // Verify that the facet only shows when the facet source is visible, it
+ // should not show up on the user page.
+ $this->drupalGet('');
+ $this->assertNoFacetBlocksAppear();
+
+ // Do not show the block on empty behaviors.
+ $this->clearIndex();
+ $this->drupalGet(static::VIEW_URL);
+
+ // Verify that no facet blocks appear. Empty behavior "None" is selected by
+ // default.
+ $this->assertNoFacetBlocksAppear();
+
+ // Verify that the "empty_text" appears as expected.
+ $settings = [
+ 'behavior' => 'text',
+ 'text' => 'No results found for this block!',
+ ];
+ $facet = $this->getFacetById($facet_id);
+ $facet->setEmptyBehavior($settings);
+ $this->facetStorage->save($facet);
+
+ $this->drupalGet(static::VIEW_URL);
+ $this->assertSession()->responseContains('block-test-facet-name');
+ $this->assertSession()
+ ->responseContains('No results found for this block!');
+ }
+
+ /**
+ * Tests that a block view also works.
+ *
+ * Cached implementation of testBlockView integration test.
+ *
+ * @see \Drupal\Tests\facets\Functional\IntegrationTest::testBlockView()
+ */
+ public function testBlockView() {
+ $webAssert = $this->assertSession();
+ $this->createFacet(
+ 'Block view facet',
+ 'block_view_facet',
+ 'type',
+ 'block_1_sapi_tag',
+ 'views_block__search_api_test_view'
+ );
+
+ // Place the views block in the footer of all pages.
+ $block_settings = [
+ 'region' => 'sidebar_first',
+ 'id' => 'view_block',
+ ];
+ $this->drupalPlaceBlock('views_block:search_api_test_view-block_1_sapi_tag', $block_settings);
+
+ // By default, the view should show all entities.
+ $this->drupalGet('');
+ $webAssert->pageTextContains('Fulltext test index');
+ $webAssert->pageTextContains('Displaying 5 search results');
+ $webAssert->pageTextContains('item');
+ $webAssert->pageTextContains('article');
+
+ // Click the item link, and test that filtering of results actually works.
+ $this->clickLink('item');
+ $webAssert->pageTextContains('Displaying 3 search results');
+ }
+
+ /**
+ * Tests that an url alias works correctly.
+ *
+ * Cached implementation of testUrlAlias integration test.
+ *
+ * @see \Drupal\Tests\facets\Functional\IntegrationTest::testUrlAlias()
+ */
+ public function testUrlAlias() {
+ $facet_id = 'ab_facet';
+ $this->createFacet('ab Facet', $facet_id, 'type', static::VIEW_DISPLAY);
+
+ $this->drupalGet(static::VIEW_URL);
+ $this->assertFacetLabel('item');
+ $this->assertFacetLabel('article');
+
+ $this->clickLink('item');
+ $url = Url::fromUserInput('/' . static::VIEW_URL, ['query' => ['f' => ['ab_facet:item']]]);
+ $this->assertSession()->addressEquals($url);
+
+ $this->updateFacet($facet_id, ['url_alias' => 'llama']);
+
+ $this->drupalGet(static::VIEW_URL);
+ $this->assertFacetLabel('item');
+ $this->assertFacetLabel('article');
+
+ $this->clickLink('item');
+ $url = Url::fromUserInput('/' . static::VIEW_URL, ['query' => ['f' => ['llama:item']]]);
+ $this->assertSession()->addressEquals($url);
+ }
+
+ /**
+ * Tests facet dependencies.
+ *
+ * Cached implementation of testFacetDependencies integration test.
+ *
+ * @see \Drupal\Tests\facets\Functional\IntegrationTest::testFacetDependencies()
+ */
+ public function testFacetDependencies() {
+ $facet_name = "DependableFacet";
+ $facet_id = 'dependablefacet';
+
+ $depending_facet_name = "DependingFacet";
+ $depending_facet_id = "dependingfacet";
+
+ $this->createFacet($facet_name, $facet_id, 'type', static::VIEW_DISPLAY);
+ $this->createFacet($depending_facet_name, $depending_facet_id, 'keywords', static::VIEW_DISPLAY);
+
+ // Go to the view and test that both facets are shown. Item and article
+ // come from the DependableFacet, orange and grape come from DependingFacet.
+ $this->drupalGet(static::VIEW_URL);
+ $this->assertFacetLabel('grape');
+ $this->assertFacetLabel('orange');
+ $this->assertFacetLabel('item');
+ $this->assertFacetLabel('article');
+ $this->assertFacetBlocksAppear();
+
+ // Change the visiblity settings of the DependingFacet.
+ $facet = $this->getFacetById($depending_facet_id);
+ $processor = [
+ 'processor_id' => 'dependent_processor',
+ 'weights' => ['build' => 5],
+ 'settings' => [
+ $facet_id => [
+ 'enable' => TRUE,
+ 'condition' => 'values',
+ 'values' => 'item',
+ 'negate' => FALSE,
+ ],
+ ],
+ ];
+ $facet->addProcessor($processor);
+ $this->facetStorage->save($facet);
+
+ // Go to the view and test that only the types are shown.
+ $this->drupalGet(static::VIEW_URL);
+ $this->assertSession()->linkNotExists('grape');
+ $this->assertSession()->linkNotExists('orange');
+ $this->assertFacetLabel('item');
+ $this->assertFacetLabel('article');
+
+ // Click on the item, and test that this shows the keywords.
+ $this->clickLink('item');
+ $this->assertFacetLabel('grape');
+ $this->assertFacetLabel('orange');
+
+ // Go back to the view, click on article and test that the keywords are
+ // hidden.
+ $this->drupalGet(static::VIEW_URL);
+ $this->clickLink('article');
+ $this->assertSession()->linkNotExists('grape');
+ $this->assertSession()->linkNotExists('orange');
+
+ // Change the visibility settings to negate the previous settings.
+ $processor['settings'][$facet_id]['negate'] = TRUE;
+ $facet->addProcessor($processor);
+ $this->facetStorage->save($facet);
+
+ // Go to the view and test only the type facet is shown.
+ $this->drupalGet(static::VIEW_URL);
+ $this->assertFacetLabel('item');
+ $this->assertFacetLabel('article');
+ $this->assertFacetLabel('grape');
+ $this->assertFacetLabel('orange');
+
+ // Click on the article, and test that this shows the keywords.
+ $this->clickLink('article');
+ $this->assertFacetLabel('grape');
+ $this->assertFacetLabel('orange');
+
+ // Go back to the view, click on item and test that the keywords are
+ // hidden.
+ $this->drupalGet(static::VIEW_URL);
+ $this->clickLink('item');
+ $this->assertSession()->linkNotExists('grape');
+ $this->assertSession()->linkNotExists('orange');
+
+ // Disable negation again.
+ $processor['settings'][$facet_id]['negate'] = FALSE;
+ $facet->addProcessor($processor);
+ $this->facetStorage->save($facet);
+
+ $this->drupalGet(static::VIEW_URL);
+ $this->assertSession()->pageTextContains('Displaying 5 search results');
+ $this->assertSession()->linkNotExists('grape');
+ $this->clickLink('item');
+ $this->assertSession()->pageTextContains('Displaying 3 search results');
+ $this->assertSession()->linkExists('grape');
+ $this->clickLink('grape');
+ $this->assertSession()->pageTextContains('Displaying 1 search results');
+ // Disable item again, and the grape should not be reflected in the search
+ // result anymore.
+ $this->clickLink('item');
+ $this->assertSession()->pageTextContains('Displaying 5 search results');
+ }
+
+ /**
+ * Tests the facet's and/or functionality.
+ *
+ * Cached implementation of testAndOrFacet integration test.
+ *
+ * @see \Drupal\Tests\facets\Functional\IntegrationTest::testAndOrFacet()
+ */
+ public function testAndOrFacet() {
+ $facet_id = 'test_facet';
+
+ $this->createFacet('test & facet', $facet_id, 'type', static::VIEW_DISPLAY);
+ $this->updateFacet($facet_id, ['query_operator' => 'and']);
+
+ $this->drupalGet(static::VIEW_URL);
+ $this->assertFacetLabel('item');
+ $this->assertFacetLabel('article');
+
+ $this->clickLink('item');
+ $this->checkFacetIsActive('item');
+ $this->assertSession()->linkNotExists('article');
+
+ $this->updateFacet($facet_id, ['query_operator' => 'or']);
+
+ $this->drupalGet(static::VIEW_URL);
+ $this->assertFacetLabel('item');
+ $this->assertFacetLabel('article');
+
+ $this->clickLink('item (3)');
+ $this->checkFacetIsActive('item');
+ $this->assertFacetLabel('article (2)');
+ }
+
+ /**
+ * Tests the facet's exclude functionality.
+ *
+ * Cached implementation of testExcludeFacet integration test.
+ *
+ * @see \Drupal\Tests\facets\Functional\IntegrationTest::testExcludeFacet()
+ */
+ public function testExcludeFacet() {
+ $facet_id = 'test_facet';
+ $this->createFacet('test & facet', $facet_id, 'type', static::VIEW_DISPLAY);
+ $this->updateFacet($facet_id, ['exclude' => TRUE]);
+
+ $this->drupalGet(static::VIEW_URL);
+ $this->assertSession()->pageTextContains('foo bar baz');
+ $this->assertSession()->pageTextContains('foo baz');
+ $this->assertFacetLabel('item');
+
+ $this->clickLink('item');
+ $this->checkFacetIsActive('item');
+ $this->assertSession()->pageTextContains('foo baz');
+ $this->assertSession()->pageTextContains('bar baz');
+ $this->assertSession()->pageTextNotContains('foo bar baz');
+
+ $this->updateFacet($facet_id, ['exclude' => FALSE]);
+
+ $this->drupalGet(static::VIEW_URL);
+ $this->assertSession()->pageTextContains('foo bar baz');
+ $this->assertSession()->pageTextContains('foo baz');
+ $this->assertFacetLabel('item');
+
+ $this->clickLink('item');
+ $this->checkFacetIsActive('item');
+ $this->assertSession()->pageTextContains('foo bar baz');
+ $this->assertSession()->pageTextContains('foo test');
+ $this->assertSession()->pageTextContains('bar');
+ $this->assertSession()->pageTextNotContains('foo baz');
+ }
+
+ /**
+ * Tests the facet's exclude functionality for a date field.
+ *
+ * Cached implementation of testExcludeFacetDate integration test.
+ *
+ * @see \Drupal\Tests\facets\Functional\IntegrationTest::testExcludeFacetDate()
+ */
+ public function testExcludeFacetDate() {
+ $facet_id = $field_name = 'created';
+
+ $this->entityTestStorage->create([
+ 'name' => 'foo new',
+ 'body' => 'test test',
+ 'type' => 'item',
+ 'keywords' => ['orange'],
+ 'category' => 'item_category',
+ $field_name => 1490000000,
+ ])->save();
+
+ $this->entityTestStorage->create([
+ 'name' => 'foo old',
+ 'body' => 'test test',
+ 'type' => 'item',
+ 'keywords' => ['orange'],
+ 'category' => 'item_category',
+ $field_name => 1460000000,
+ ])->save();
+
+ $this->assertEquals(2, $this->indexItems($this->indexId), '2 items were indexed.');
+
+ $this->createFacet('Created', $facet_id, $field_name, static::VIEW_DISPLAY);
+ $facet = $this->getFacetById($facet_id);
+ $facet->addProcessor([
+ 'processor_id' => 'date_item',
+ 'weights' => ['build' => 35],
+ 'settings' => [
+ 'date_display' => 'actual_date',
+ 'granularity' => SearchApiDate::FACETAPI_DATE_MONTH,
+ 'hierarchy' => FALSE,
+ 'date_format' => '',
+ ],
+ ]);
+ $this->facetStorage->save($facet);
+
+ $this->drupalGet(static::VIEW_URL);
+ $this->assertSession()->pageTextContains('foo old');
+ $this->assertSession()->pageTextContains('foo new');
+ $this->clickLink('March 2017');
+ $this->checkFacetIsActive('March 2017');
+ $this->assertSession()->pageTextContains('foo new');
+ $this->assertSession()->pageTextNotContains('foo old');
+
+ $this->updateFacet($facet->id(), ['exclude' => TRUE]);
+
+ $this->drupalGet(static::VIEW_URL);
+ $this->clickLink('March 2017');
+ $this->checkFacetIsActive('March 2017');
+ $this->assertSession()->pageTextContains('foo old');
+ $this->assertSession()->pageTextNotContains('foo new');
+ }
+
+ /**
+ * Tests allow only one active item.
+ *
+ * Cached implementation of testAllowOneActiveItem integration test.
+ *
+ * @see \Drupal\Tests\facets\Functional\IntegrationTest::testAllowOneActiveItem()
+ */
+ public function testAllowOneActiveItem() {
+ $this->createFacet('Spotted wood owl', 'spotted_wood_owl', 'keywords', static::VIEW_DISPLAY);
+
+ $facet = $this->getFacetById('spotted_wood_owl');
+ $facet->setShowOnlyOneResult(TRUE);
+ $this->facetStorage->save($facet);
+
+ $this->drupalGet(static::VIEW_URL);
+ $this->assertSession()->pageTextContains('Displaying 5 search results');
+ $this->assertFacetLabel('grape');
+ $this->assertFacetLabel('orange');
+
+ $this->clickLink('grape');
+ $this->assertSession()->pageTextContains('Displaying 3 search results');
+ $this->checkFacetIsActive('grape');
+ $this->assertFacetLabel('orange');
+
+ $this->clickLink('orange');
+ $this->assertSession()->pageTextContains('Displaying 3 search results');
+ $this->assertFacetLabel('grape');
+ $this->checkFacetIsActive('orange');
+ }
+
+ /**
+ * Tests calculations of facet count.
+ *
+ * Cached implementation of testFacetCountCalculations integration test.
+ *
+ * @see \Drupal\Tests\facets\Functional\IntegrationTest::testFacetCountCalculations()
+ */
+ public function testFacetCountCalculations() {
+ $this->createFacet('Type', 'type', 'type', static::VIEW_DISPLAY);
+ $this->createFacet('Keywords', 'keywords', 'keywords', static::VIEW_DISPLAY);
+ foreach (['type', 'keywords'] as $facet_id) {
+ $this->updateFacet($facet_id, ['query_operator' => 'and']);
+ }
+
+ $this->drupalGet(static::VIEW_URL);
+ $this->assertSession()->pageTextContains('Displaying 5 search results');
+ $this->assertFacetLabel('article (2)');
+ $this->assertFacetLabel('grape (3)');
+
+ // Make sure that after clicking on article, which has only 2 entities,
+ // there are only 2 items left in the results for other facets as well.
+ // In this case, that means we can't have 3 entities tagged with grape. Both
+ // remaining entities are tagged with grape and strawberry.
+ $this->clickPartialLink('article');
+ $this->assertSession()->pageTextNotContains('(3)');
+ $this->assertFacetLabel('grape (2)');
+ $this->assertFacetLabel('strawberry (2)');
+
+ $this->drupalGet(static::VIEW_URL);
+ $this->assertSession()->pageTextContains('Displaying 5 search results');
+ $this->assertFacetLabel('article (2)');
+ $this->assertFacetLabel('grape (3)');
+
+ // Make sure that after clicking on grape, which has only 3 entities, there
+ // are only 3 items left in the results for other facets as well. In this
+ // case, that means 2 entities of type article and 1 item.
+ $this->clickPartialLink('grape');
+ $this->assertSession()->pageTextContains('Displaying 3 search results');
+ $this->assertFacetLabel('article (2)');
+ $this->assertFacetLabel('item (1)');
+ }
+
+ /**
+ * Tests the hard limit setting.
+ *
+ * Cached implementation of testHardLimit integration test.
+ *
+ * @see \Drupal\Tests\facets\Functional\IntegrationTest::testHardLimit()
+ */
+ public function testHardLimit() {
+ $this->createFacet('Owl', 'owl', 'keywords', static::VIEW_DISPLAY);
+ $facet = $this->getFacetById('owl');
+ $facet->addProcessor([
+ 'processor_id' => 'active_widget_order',
+ 'weights' => ['sort' => 20],
+ 'settings' => [],
+ ]);
+ $facet->addProcessor([
+ 'processor_id' => 'display_value_widget_order',
+ 'weights' => ['build' => 40],
+ 'settings' => [],
+ ]);
+ $this->facetStorage->save($facet);
+
+ $this->drupalGet(static::VIEW_URL);
+ $this->assertSession()->pageTextContains('Displaying 5 search results');
+ $this->assertFacetLabel('grape (3)');
+ $this->assertFacetLabel('orange (3)');
+ $this->assertFacetLabel('apple (2)');
+ $this->assertFacetLabel('banana (1)');
+ $this->assertFacetLabel('strawberry (2)');
+
+ $this->updateFacet($facet->id(), ['hard_limit' => 3]);
+
+ $this->drupalGet(static::VIEW_URL);
+ // We're still testing for 5 search results here, the hard limit only limits
+ // the facets, not the search results.
+ $this->assertSession()->pageTextContains('Displaying 5 search results');
+ $this->assertFacetLabel('grape (3)');
+ $this->assertFacetLabel('orange (3)');
+ $this->assertFacetLabel('apple (2)');
+ $this->assertSession()->pageTextNotContains('banana (0)');
+ $this->assertSession()->pageTextNotContains('strawberry (0)');
+ }
+
+ /**
+ * Test minimum amount of items.
+ *
+ * Cached implementation of testMinimumAmount integration test.
+ *
+ * @see \Drupal\Tests\facets\Functional\IntegrationTest::testMinimumAmount()
+ */
+ public function testMinimumAmount() {
+ $this->createFacet('Elf owl', 'elf_owl', 'type', static::VIEW_DISPLAY);
+ $this->updateFacet('elf_owl', ['min_count' => 1]);
+
+ // See that both article and item are showing.
+ $this->drupalGet(static::VIEW_URL);
+ $this->assertSession()->pageTextContains('Displaying 5 search results');
+ $this->assertFacetLabel('article (2)');
+ $this->assertFacetLabel('item (3)');
+
+ $this->updateFacet('elf_owl', ['min_count' => 3]);
+
+ // See that article is now hidden, item should still be showing.
+ $this->drupalGet(static::VIEW_URL);
+ $this->assertSession()->pageTextContains('Displaying 5 search results');
+ $this->assertSession()->pageTextNotContains('article');
+ $this->assertFacetLabel('item (3)');
+ }
+
+ /**
+ * Tests the visibility of facet source.
+ *
+ * Cached implementation of testFacetSourceVisibility integration test.
+ *
+ * @see \Drupal\Tests\facets\Functional\IntegrationTest::testFacetSourceVisibility()
+ */
+ public function testFacetSourceVisibility() {
+ $this->createFacet('Vicuña', 'vicuna', 'type', static::VIEW_DISPLAY);
+ // Facet appears only on the search page for which it was created.
+ $this->drupalGet(static::VIEW_URL);
+ $this->assertFacetBlocksAppear();
+ $this->drupalGet('');
+ $this->assertNoFacetBlocksAppear();
+
+ $facet = $this->getFacetById('vicuna');
+ $facet->setOnlyVisibleWhenFacetSourceIsVisible(FALSE);
+ $this->facetStorage->save($facet);
+
+ // Test that the facet source is visible on the search page and user/2 page.
+ $this->drupalGet(static::VIEW_URL);
+ $this->assertFacetBlocksAppear();
+ $this->drupalGet('');
+ $this->assertFacetBlocksAppear();
+ }
+
+ /**
+ * Tests behavior with multiple enabled facets and their interaction.
+ *
+ * Cached implementation of testMultipleFacets integration test.
+ *
+ * @see \Drupal\Tests\facets\Functional\IntegrationTest::testMultipleFacets()
+ */
+ public function testMultipleFacets() {
+ // Create 2 facets.
+ $this->createFacet('Snow Owl', 'snow_owl', 'type', static::VIEW_DISPLAY);
+ $this->createFacet('Forest Owl', 'forest_owl', 'category', static::VIEW_DISPLAY);
+
+ foreach (['snow_owl', 'forest_owl'] as $facet_id) {
+ $this->updateFacet($facet_id, ['min_count' => 0]);
+ }
+
+ // Go to the view and check the default behavior.
+ $this->drupalGet(static::VIEW_URL);
+ $this->assertSession()->pageTextContains('Displaying 5 search results');
+ $this->assertFacetLabel('item (3)');
+ $this->assertFacetLabel('article (2)');
+ $this->assertFacetLabel('item_category (2)');
+ $this->assertFacetLabel('article_category (2)');
+
+ // Start filtering.
+ $this->clickPartialLink('item_category');
+ $this->assertSession()->pageTextContains('Displaying 2 search results');
+ $this->checkFacetIsActive('item_category');
+ $this->assertFacetLabel('item (2)');
+
+ // Go back to the overview and start another filter, from the second facet
+ // block this time.
+ $this->drupalGet(static::VIEW_URL);
+ $this->assertSession()->pageTextContains('Displaying 5 search results');
+ $this->clickPartialLink('article (2)');
+ $this->assertSession()->pageTextContains('Displaying 2 search results');
+ $this->checkFacetIsActive('article');
+ $this->assertFacetLabel('article_category (2)');
+ $this->assertFacetLabel('item_category (0)');
+ }
+
+ /**
+ * Tests that the configuration for showing a title works.
+ *
+ * Cached implementation of testShowTitle integration test.
+ *
+ * @see \Drupal\Tests\facets\Functional\IntegrationTest::testShowTitle()
+ */
+ public function testShowTitle() {
+ $this->createFacet('Llama', 'llama', 'type', static::VIEW_DISPLAY);
+ $this->drupalGet(static::VIEW_URL);
+ $this->assertSession()->pageTextNotContains('Llama');
+
+ $this->updateFacet('llama', ['show_title' => TRUE]);
+
+ $this->drupalGet(static::VIEW_URL);
+ $this->assertSession()->responseContains('Llama
');
+ $this->assertSession()->pageTextContains('Llama');
+ }
+
+ /**
+ * Test facet blocks cache max ages.
+ */
+ public function testCacheMaxAges() {
+ $block_view_builder = \Drupal::entityTypeManager()->getViewBuilder('block');
+
+ $display_map = [
+ 'page_1' => 0,
+ static::VIEW_DISPLAY => Cache::PERMANENT,
+ 'page_2_sapi_time' => 518400,
+ ];
+
+ foreach ($display_map as $display => $expected_block_max_age) {
+ $facet_id = 'test_facet_' . $display;
+ $this->createFacet('Test Facet name', $facet_id, 'type', $display);
+ $build = $block_view_builder->view($this->blocks[$facet_id]);
+ $this->assertEquals(
+ $expected_block_max_age,
+ CacheableMetadata::createFromRenderArray($build)->getCacheMaxAge(),
+ );
+ }
+ }
+
+ /**
+ * Test facet blocks cache invalidation.
+ *
+ * Test covers search page with a facets and standalone facet block on FP.
+ */
+ public function testFacetBlockCacheNewContentIndexing() {
+ $this->createFacet('Test Facet name', 'test_facet_name', 'type', static::VIEW_DISPLAY);
+
+ $facet = $this->getFacetById('test_facet_name');
+ $facet->setOnlyVisibleWhenFacetSourceIsVisible(FALSE);
+ $this->facetStorage->save($facet);
+
+ foreach (['', static::VIEW_URL] as $url) {
+ $this->drupalGet($url);
+ $this->assertFacetLabel('article (2)');
+ $this->assertFacetLabel('item (3)');
+ }
+
+ $this->entityTestStorage->create([
+ 'name' => 'foo jiz baz',
+ 'body' => 'test test and a bit more test',
+ 'type' => 'item',
+ 'keywords' => ['orange', 'black'],
+ 'category' => 'item_category',
+ ])->save();
+
+ // Entity was added but not indexed yet, so facet state should remain the
+ // same.
+ foreach (['', static::VIEW_URL] as $url) {
+ $this->drupalGet($url);
+ $this->assertFacetLabel('article (2)');
+ $this->assertFacetLabel('item (3)');
+ }
+
+ // Index 1 remaining item and check that count has been updated.
+ $this->assertEquals(1, $this->indexItems($this->indexId), '1 item was indexed.');
+ foreach (['', static::VIEW_URL] as $url) {
+ $this->drupalGet($url);
+ $this->assertFacetLabel('article (2)');
+ $this->assertFacetLabel('item (4)');
+ }
+ }
+
+ /**
+ * Enable website page caching, set 1 day max age.
+ */
+ protected function enableWebsiteCache() {
+ $max_age = 86400;
+ $this->config('system.performance')
+ ->set('cache.page.max_age', $max_age)
+ ->save();
+ $this->drupalGet(static::VIEW_URL);
+ $this->assertSession()
+ ->responseHeaderContains('Cache-Control', 'max-age=' . $max_age);
+ }
+
+ /**
+ * Get facet entity by ids.
+ *
+ * @param string $id
+ * Facet id.
+ *
+ * @return \Drupal\facets\FacetInterface
+ * Loaded facet object.
+ */
+ protected function getFacetById(string $id): FacetInterface {
+ return $this->facetStorage->load($id);
+ }
+
+ /**
+ * Update facet tith with given values.
+ *
+ * @param string $id
+ * The facet entity ID.
+ * @param array $settings
+ * Array with values keyed by property names.
+ *
+ * @return \Drupal\facets\FacetInterface
+ * An updated facet entity.
+ */
+ protected function updateFacet(string $id, array $settings): FacetInterface {
+ $facet = $this->getFacetById($id);
+ foreach ($settings as $name => $value) {
+ $facet->set($name, $value);
+ }
+ $this->facetStorage->save($facet);
+
+ return $facet;
+ }
+
+}
diff --git a/tests/src/Functional/IntegrationTest.php b/tests/src/Functional/IntegrationTest.php
index 5997420..73d9f8a 100644
--- a/tests/src/Functional/IntegrationTest.php
+++ b/tests/src/Functional/IntegrationTest.php
@@ -202,7 +202,7 @@ class IntegrationTest extends FacetsTestBase {
$this->assertFacetLabel('article');
$this->clickLink('item');
- $url = Url::fromUserInput('/search-api-test-fulltext', ['query' => ['f[0]' => 'ab_facet:item']]);
+ $url = Url::fromUserInput('/search-api-test-fulltext', ['query' => ['f' => ['ab_facet:item']]]);
$this->assertSession()->addressEquals($url);
$this->drupalGet($facet_edit_page);
@@ -213,7 +213,7 @@ class IntegrationTest extends FacetsTestBase {
$this->assertFacetLabel('article');
$this->clickLink('item');
- $url = Url::fromUserInput('/search-api-test-fulltext', ['query' => ['f[0]' => 'llama:item']]);
+ $url = Url::fromUserInput('/search-api-test-fulltext', ['query' => ['f' => ['llama:item']]]);
$this->assertSession()->addressEquals($url);
}
@@ -873,29 +873,34 @@ class IntegrationTest extends FacetsTestBase {
* Check that the disabling of the cache works.
*/
public function testViewsCacheDisable() {
- // Load the view, verify cache settings.
- $view = Views::getView('search_api_test_view');
- $view->setDisplay('page_1');
- $current_cache = $view->display_handler->getOption('cache');
- $this->assertEquals('none', $current_cache['type']);
- $view->display_handler->setOption('cache', ['type' => 'tag']);
- $view->save();
- $current_cache = $view->display_handler->getOption('cache');
- $this->assertEquals('tag', $current_cache['type']);
-
- // Create a facet and check for the cache disabled message.
- $id = "western_screech_owl";
- $name = "Western screech owl";
- $this->createFacet($name, $id);
- $this->drupalGet('admin/config/search/facets/' . $id . '/settings');
- $this->submitForm([], 'Save');
- $this->assertSession()->pageTextContains('Caching of view Search API Test Fulltext search view has been disabled.');
-
- // Check the view's cache settings again to see if they've been updated.
- $view = Views::getView('search_api_test_view');
- $view->setDisplay('page_1');
- $current_cache = $view->display_handler->getOption('cache');
- $this->assertEquals('none', $current_cache['type']);
+ $caches = [
+ // Tag cache plugin should be replaced by none, as it's not supported.
+ 'page_1' => 'none',
+ // Search API cache plugin shouldn't be changed.
+ 'page_2_sapi_tag' => 'search_api_tag',
+ 'page_2_sapi_time' => 'search_api_time',
+ ];
+ foreach ($caches as $display_id => $expected_cache_plugin) {
+ // Create a facet and check for the cache disabled message.
+ $id = 'western_screech_owl_' . $display_id;
+ $name = 'Western screech owl';
+ $this->createFacet($name, $id, 'type', $display_id);
+ $this->drupalGet('admin/config/search/facets/' . $id . '/settings');
+ $this->submitForm([], 'Save');
+ $warning = 'You may experience issues, because Search API Test Fulltext search view use cache. In case you will try to turn set cache plugin to none.';
+ if ($display_id === 'page_1') {
+ // Make sure that user will get a warning about source cache plugin.
+ $this->assertSession()->pageTextNotContains($warning);
+ }
+ else {
+ $this->assertSession()->pageTextContains($warning);
+ }
+ // Check the view's cache settings again to see if they've been updated.
+ $view = Views::getView('search_api_test_view');
+ $view->setDisplay($display_id);
+ $current_cache = $view->display_handler->getOption('cache');
+ $this->assertEquals($expected_cache_plugin, $current_cache['type']);
+ }
}
/**
diff --git a/tests/src/Functional/UrlIntegrationTest.php b/tests/src/Functional/UrlIntegrationTest.php
index b8afb95..027ae86 100644
--- a/tests/src/Functional/UrlIntegrationTest.php
+++ b/tests/src/Functional/UrlIntegrationTest.php
@@ -66,7 +66,7 @@ class UrlIntegrationTest extends FacetsTestBase {
// Go to the only enabled facet source's config and change the filter key.
$this->drupalGet('admin/config/search/facets');
- $this->clickLink('Configure', 1);
+ $this->clickLink('Configure', 2);
$edit = [
'filter_key' => 'y',
@@ -90,7 +90,7 @@ class UrlIntegrationTest extends FacetsTestBase {
// Go to the only enabled facet source's config and change the url
// processor.
$this->drupalGet('admin/config/search/facets');
- $this->clickLink('Configure', 1);
+ $this->clickLink('Configure', 2);
$edit = [
'filter_key' => 'y',
diff --git a/tests/src/Kernel/Entity/FacetFacetSourceTest.php b/tests/src/Kernel/Entity/FacetFacetSourceTest.php
index e29daf4..c01a545 100644
--- a/tests/src/Kernel/Entity/FacetFacetSourceTest.php
+++ b/tests/src/Kernel/Entity/FacetFacetSourceTest.php
@@ -31,7 +31,6 @@ class FacetFacetSourceTest extends EntityKernelTestBase {
'search_api_db',
'search_api_test_db',
'search_api_test_example_content',
- 'search_api_test_views',
'views',
'rest',
'serialization',
@@ -60,7 +59,7 @@ class FacetFacetSourceTest extends EntityKernelTestBase {
'search_api_test_db',
]);
- $this->installConfig('search_api_test_views');
+ $this->installConfig('facets_search_api_dependency');
}
/**
diff --git a/tests/src/Kernel/FacetManager/DefaultFacetManagerTest.php b/tests/src/Kernel/FacetManager/DefaultFacetManagerTest.php
index a840b44..6574282 100644
--- a/tests/src/Kernel/FacetManager/DefaultFacetManagerTest.php
+++ b/tests/src/Kernel/FacetManager/DefaultFacetManagerTest.php
@@ -2,16 +2,17 @@
namespace Drupal\Tests\facets\Kernel\FacetManager;
-use Drupal\facets\Entity\Facet;
-use Drupal\KernelTests\KernelTestBase;
+use Drupal\Core\Cache\Cache;
+use Drupal\facets\FacetInterface;
+use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
/**
* Provides the DefaultFacetManager test.
*
* @group facets
- * @coversDefaultClass Drupal\facets\FacetManager\DefaultFacetManager
+ * @coversDefaultClass \Drupal\facets\FacetManager\DefaultFacetManager
*/
-class DefaultFacetManagerTest extends KernelTestBase {
+class DefaultFacetManagerTest extends EntityKernelTestBase {
/**
* {@inheritdoc}
@@ -19,16 +20,59 @@ class DefaultFacetManagerTest extends KernelTestBase {
protected static $modules = [
'facets',
'search_api',
+ 'search_api_db',
+ 'search_api_test_db',
+ 'facets_processors_collection',
+ 'facets_search_api_dependency',
'system',
'user',
+ 'views',
+ 'rest',
+ 'serialization',
];
+ /**
+ * Facets entity storage.
+ *
+ * @var \Drupal\Core\Config\Entity\ConfigEntityStorage
+ */
+ protected $facetStorage;
+
+ /**
+ * An instance of the "facets.manager" service.
+ *
+ * @var \Drupal\facets\FacetManager\DefaultFacetManager
+ */
+ protected $facetManager;
+
/**
* {@inheritdoc}
*/
public function setUp(): void {
parent::setUp();
+
$this->installEntitySchema('facets_facet');
+ $this->installEntitySchema('entity_test_mulrev_changed');
+ $this->installEntitySchema('search_api_task');
+
+ $state_service = \Drupal::state();
+ $state_service->set('search_api_use_tracking_batch', FALSE);
+ // @see facets_processors_collection_facets_search_api_query_type_mapping_alter().
+ $state_service->set('facets_processors_collection_alter_string_query_handler', TRUE);
+
+ // Set tracking page size so tracking will work properly.
+ \Drupal::configFactory()
+ ->getEditable('search_api.settings')
+ ->set('tracking_page_size', 100)
+ ->save();
+
+ $this->installConfig([
+ 'search_api_test_db',
+ 'facets_search_api_dependency',
+ ]);
+
+ $this->facetStorage = $this->entityTypeManager->getStorage('facets_facet');
+ $this->facetManager = $this->container->get('facets.manager');
}
/**
@@ -38,16 +82,15 @@ class DefaultFacetManagerTest extends KernelTestBase {
*/
public function testGetEnabledFacets() {
/** @var \Drupal\facets\FacetManager\DefaultFacetManager $dfm */
- $dfm = \Drupal::service('facets.manager');
- $returnValue = $dfm->getEnabledFacets();
+ $returnValue = $this->facetManager->getEnabledFacets();
$this->assertEmpty($returnValue);
// Create a facet.
- $entity = $this->createAndSaveFacet('test_facet');
+ $entity = $this->createAndSaveFacet('Mercury', 'planets');
- $returnValue = $dfm->getEnabledFacets();
+ $returnValue = $this->facetManager->getEnabledFacets();
$this->assertNotEmpty($returnValue);
- $this->assertSame($entity->id(), $returnValue['test_facet']->id());
+ $this->assertSame($entity->id(), $returnValue['Mercury']->id());
}
/**
@@ -56,73 +99,213 @@ class DefaultFacetManagerTest extends KernelTestBase {
* @covers ::getFacetsByFacetSourceId
*/
public function testGetFacetsByFacetSourceId() {
- /** @var \Drupal\facets\FacetManager\DefaultFacetManager $dfm */
- $dfm = \Drupal::service('facets.manager');
- $this->assertEmpty($dfm->getFacetsByFacetSourceId('planets'));
+ $this->assertEmpty($this->facetManager->getFacetsByFacetSourceId('planets'));
// Create 2 different facets with a unique facet source id.
- $entity = $this->createAndSaveFacet('Jupiter');
- $entity->setFacetSourceId('planets');
- $entity->save();
- $entity = $this->createAndSaveFacet('Pluto');
- $entity->setFacetSourceId('former_planets');
- $entity->save();
+ $this->createAndSaveFacet('Jupiter', 'planets');
+ $this->createAndSaveFacet('Pluto', 'former_planets');
- $planetFacets = $dfm->getFacetsByFacetSourceId('planets');
+ $planetFacets = $this->facetManager->getFacetsByFacetSourceId('planets');
$this->assertNotEmpty($planetFacets);
$this->assertCount(1, $planetFacets);
$this->assertSame('Jupiter', $planetFacets['Jupiter']->id());
- $formerPlanetFacets = $dfm->getFacetsByFacetSourceId('former_planets');
+ $formerPlanetFacets = $this->facetManager->getFacetsByFacetSourceId('former_planets');
$this->assertNotEmpty($formerPlanetFacets);
$this->assertCount(1, $formerPlanetFacets);
$this->assertSame('Pluto', $formerPlanetFacets['Pluto']->id());
// Make pluto a planet again.
+ $entity = $this->facetStorage->load('Pluto');
$entity->setFacetSourceId('planets');
- $entity->save();
+ $this->facetStorage->save($entity);
// Test that we now hit the static cache.
- $planetFacets = $dfm->getFacetsByFacetSourceId('planets');
+ $planetFacets = $this->facetManager->getFacetsByFacetSourceId('planets');
$this->assertNotEmpty($planetFacets);
$this->assertCount(1, $planetFacets);
// Change the 'facets' property on the manager to public, so we can
// overwrite it here. This is because otherwise we run into the static
// caches.
- $facetsProperty = new \ReflectionProperty($dfm, 'facets');
- $facetsProperty->setAccessible(TRUE);
- $facetsProperty->setValue($dfm, []);
+ $this->resetFacetsManagerStaticCache();
// Now that the static cache is reset, test that we have 2 planets.
- $planetFacets = $dfm->getFacetsByFacetSourceId('planets');
+ $planetFacets = $this->facetManager->getFacetsByFacetSourceId('planets');
$this->assertNotEmpty($planetFacets);
$this->assertCount(2, $planetFacets);
$this->assertSame('Jupiter', $planetFacets['Jupiter']->id());
$this->assertSame('Pluto', $planetFacets['Pluto']->id());
}
+ /**
+ * Tests the cachebillity data passed into search query.
+ */
+ public function testAlterQueryCacheabilityMetadata() {
+ $view = $this->entityTypeManager
+ ->getStorage('view')
+ ->load('search_api_test_view')
+ ->getExecutable();
+ $view->setDisplay('page_1');
+
+ $query = $view->getQuery()->getSearchApiQuery();
+
+ // Create facets for a SAPI view display.
+ $facet_source = 'search_api:views_page__search_api_test_view__page_1';
+ $expected_tags = array_merge(
+ $query->getCacheTags(),
+ $this->createAndSaveFacet('Mars', $facet_source)->getCacheTags(),
+ $this->createAndSaveFacet('Neptune', $facet_source)->getCacheTags(),
+ ['fpc:query_plugin_type_plugin']
+ );
+ $this->resetFacetsManagerStaticCache();
+ $expected_contexts = array_merge(
+ $query->getCacheContexts(),
+ ['url.path', 'url.query_args', 'fpc_query_type_plugin']
+ );
+
+ // Make sure that query cachebillity will include facets cache tags e.g.
+ // view results will depends on the facet configuration.
+ $this->facetManager->alterQuery($query, $facet_source);
+ $this->assertCacheabillityArrays($expected_contexts, $query->getCacheContexts());
+ $this->assertCacheabillityArrays($expected_tags, $query->getCacheTags());
+ }
+
+ /**
+ * Tests the cachebillity data passed into search query.
+ */
+ public function testBuildCacheabilityMetadata() {
+ $expected_metadata = [
+ 'contexts' => [
+ // Facet API uses Request query params to populate active facets values.
+ 'url.path',
+ 'url.query_args',
+ // Added by build fpc_post_query_processor process plugin.
+ 'fpc_post_query',
+ // Added by build fpc_build_processor process plugin.
+ 'fpc_build',
+ // Added by build fpc_sort_processor process plugin.
+ 'fpc_sort',
+ ],
+ 'tags' => [
+ // Facet controls query and look & feel of the facet results, so it's
+ // config should be present as a cache dependency.
+ 'config:facets.facet.mars',
+ // Added by build fpc_post_query_processor process plugin.
+ 'fpc:added_within_postQuery_method',
+ 'fpc:post_query_processor',
+ // Added by build fpc_build_processor process plugin.
+ 'fpc:added_within_build_method',
+ 'fpc:build_processor',
+ // Added by build fpc_sort_processor process plugin.
+ 'fpc:sort_processor',
+ ],
+ 'max-age' => Cache::PERMANENT,
+ ];
+
+ $facet = $this->createAndSaveFacet(
+ 'mars',
+ 'search_api:views_page__search_api_test_view__page_1'
+ );
+
+ $cacheable_processors = [
+ 'fpc_post_query_processor',
+ 'fpc_build_processor',
+ 'fpc_sort_processor',
+ ];
+
+ foreach ($cacheable_processors as $processor) {
+ $facet->addProcessor([
+ 'processor_id' => $processor,
+ 'weights' => [],
+ 'settings' => [],
+ ]);
+ }
+
+ $facet->setOnlyVisibleWhenFacetSourceIsVisible(FALSE);
+ $this->facetStorage->save($facet);
+ // Make sure that new processor is taken into consideration while facet
+ // building process.
+ $this->resetFacetsManagerStaticCache();
+ $build = $this->facetManager->build($facet);
+ $this->assertCacheabillityArrays($expected_metadata, $build[0][0]['#cache']);
+
+ $facet->removeProcessor('fpc_sort_processor');
+ // Test that un-cacheable plugin kills the cache.
+ $facet->addProcessor([
+ 'processor_id' => 'fpc_sort_random_processor',
+ 'settings' => [],
+ 'weights' => [],
+ ]);
+
+ $this->facetStorage->save($facet);
+ $this->resetFacetsManagerStaticCache();
+ $build = $this->facetManager->build($facet);
+
+ $expected_metadata['max-age'] = 0;
+ $this->assertCacheabillityArrays($expected_metadata, $build[0][0]['#cache']);
+ }
+
/**
* Create and save a facet, for usage in test-scenario's.
*
* @param string $id
* The id.
+ * @param string $source
+ * The source id.
*
* @return \Drupal\facets\FacetInterface
* The newly created facet.
*/
- protected function createAndSaveFacet($id) {
- // Create a facet.
- $entity = Facet::create([
+ protected function createAndSaveFacet(string $id, string $source): FacetInterface {
+ $facet = $this->facetStorage->create([
'id' => $id,
'name' => 'Test facet',
]);
- $entity->setWidget('links');
- $entity->setEmptyBehavior(['behavior' => 'none']);
- $entity->setFacetSourceId('fluffy');
- $entity->save();
+ $facet->setWidget('links');
+ $facet->setFieldIdentifier('type');
+ $facet->setEmptyBehavior(['behavior' => 'none']);
+ $facet->setFacetSourceId($source);
- return $entity;
+ $facet->addProcessor([
+ 'processor_id' => 'url_processor_handler',
+ 'settings' => [],
+ 'weights' => [],
+ ]);
+ $this->facetStorage->save($facet);
+
+ return $facet;
+ }
+
+ /**
+ * Reset static facets.manager static cache.
+ *
+ * @todo discuss whether or not this should be done automatically when facet
+ * gets inserted/updated or deleted.
+ */
+ protected function resetFacetsManagerStaticCache() {
+ foreach (['builtFacets', 'facets', 'processedFacets'] as $prop) {
+ $facetsProperty = new \ReflectionProperty($this->facetManager, $prop);
+ $facetsProperty->setAccessible(TRUE);
+ $facetsProperty->setValue($this->facetManager, []);
+ $facetsProperty->setAccessible(FALSE);
+ }
+ }
+
+ /**
+ * Assert that actual cachebillity matches expected one.
+ */
+ public function assertCacheabillityArrays($expected, $actual, string $message = ''): void {
+ foreach ([&$expected, &$actual] as &$array) {
+ foreach ($array as &$value) {
+ if (is_array($value)) {
+ sort($value);
+ }
+ }
+ }
+ sort($expected);
+ sort($actual);
+ $this->assertEquals($expected, $actual, $message);
}
}