diff --git a/facets.module b/facets.module index 66e8412..0841aa6 100644 --- a/facets.module +++ b/facets.module @@ -383,23 +383,3 @@ 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/modules/facets_exposed_filters/facets_exposed_filters.info.yml b/modules/facets_exposed_filters/facets_exposed_filters.info.yml new file mode 100644 index 0000000..db9ce5f --- /dev/null +++ b/modules/facets_exposed_filters/facets_exposed_filters.info.yml @@ -0,0 +1,11 @@ +name: 'Facets exposed filters (Experimental)' +type: module +description: 'Render facets in exposed filters.' +core_version_requirement: ^9.2 || ^10.1 +package: Search +configure: entity.facets_facet.collection +dependencies: + - search_api:search_api + - facets:facets + - drupal:views + - better_exposed_filters:better_exposed_filters diff --git a/modules/facets_exposed_filters/facets_exposed_filters.module b/modules/facets_exposed_filters/facets_exposed_filters.module new file mode 100644 index 0000000..2c02efb --- /dev/null +++ b/modules/facets_exposed_filters/facets_exposed_filters.module @@ -0,0 +1,46 @@ +id()]['facets'] = [ + 'title' => t('Facets'), + 'help' => t('Displays facets in a filter or area.'), + 'filter' => [ + 'id' => 'facets_filter', + ], + 'area' => [ + 'id' => 'facets_area', + ], + ]; + } +} + +function facets_exposed_filters_form_facets_facet_edit_form_alter(&$form, FormStateInterface $form_state) { + $facet = \Drupal::routeMatch()->getParameter('facets_facet'); + $facetsource_config = $facet->getFacetSourceConfig(); + + // Only alter form when views_exposed_filters url processor is active. + if (substr($facetsource_config->getUrlProcessorName(), 0, 21) === 'views_exposed_filters') { + // Hide processors who do not apply when using views_exposed_filters. + $form['widget'] = ['#type' => 'hidden', '#value' => 'views_exposed_filter']; + + $form["facet_settings"]["only_visible_when_facet_source_is_visible"]['#access'] = FALSE; + $form["facet_settings"]["only_visible_when_facet_source_is_visible"]['#value'] = FALSE; + + $form["facet_settings"]["url_alias"]['#access'] = FALSE; + $form["facet_settings"]["show_title"]['#access'] = FALSE; + $form["facet_settings"]["show_only_one_result"]['#access'] = FALSE; + $form["facet_settings"]["show_only_one_result"]['#value'] = FALSE; + + $form["facet_settings"]["empty_behavior"]['#access'] = FALSE; + unset($form["facet_settings"]["show_only_one_result"]); + } +} + diff --git a/modules/facets_exposed_filters/src/Plugin/facets/url_processor/ViewsExposedFilters.php b/modules/facets_exposed_filters/src/Plugin/facets/url_processor/ViewsExposedFilters.php new file mode 100644 index 0000000..6ba8c54 --- /dev/null +++ b/modules/facets_exposed_filters/src/Plugin/facets/url_processor/ViewsExposedFilters.php @@ -0,0 +1,122 @@ +initializeActiveFilters(); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('request_stack')->getCurrentRequest(), + $container->get('entity_type.manager'), + ); + } + + /** + * {@inheritdoc} + */ + public function buildUrls(FacetInterface $facet, array $results) { + // No results are found for this facet, so don't try to create urls. + if (empty($results)) { + return []; + } + + $display_id = $this->configuration['facet']->getFacetSource()->pluginDefinition["display_id"]; + $parts = explode(':', $display_id); + $views_info = explode('__', $parts[1]); + $views_name = $views_info[0]; + $views_display = $views_info[1]; + + // We dont build real urls here. Views will handle this part for us. However + // our code still depends on the url being set, so we set all results to use + // the views display url. + $url = Url::fromRoute('view.' . $views_name . '.' . $views_display); + foreach ($results as $key => $result) { + $results[$key]->setUrl($url); + } + + return $results; + } + + /** + * Initializes the active filters from the request query. + * + * Get all the filters that are active by checking the request query and store + * them in activeFilters which is an array where key is the facet id and value + * is an array of raw values. + */ + protected function initializeActiveFilters() { + $url_parameters = $this->request->query; + $active_filters = []; + $display_id = $this->configuration['facet']->getFacetSource()->pluginDefinition["display_id"]; + $parts = explode(':', $display_id); + $views_info = explode('__', $parts[1]); + $views_name = $views_info[0]; + $views_display = $views_info[1]; + $view = View::load($views_name); + $display = $view->getDisplay($views_display); + if (!isset($display["display_options"]["filters"])) { + $display = $view->getDisplay('default'); + } + foreach ($display["display_options"]["filters"] as $filter) { + if ($filter["plugin_id"] == 'facets_filter') { + $facet_id = $filter['facet']; + $identifier = $filter["expose"]["identifier"]; + $query_params = $url_parameters->all(); + if (isset($query_params[$identifier]) && $query_params[$identifier]) { + if ($filter["expose"]["multiple"]) { + $active_filters[$facet_id] = $url_parameters->all()[$identifier]; + } + else { + if ($url_parameters->all()[$identifier] != 'All') { + $active_filters[$facet_id][] = $url_parameters->all()[$identifier]; + } + } + } + } + } + $this->activeFilters = $active_filters; + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + return ['url.query_args']; + } + +} diff --git a/modules/facets_exposed_filters/src/Plugin/facets/widget/ViewsExposedFilterWidget.php b/modules/facets_exposed_filters/src/Plugin/facets/widget/ViewsExposedFilterWidget.php new file mode 100644 index 0000000..3196206 --- /dev/null +++ b/modules/facets_exposed_filters/src/Plugin/facets/widget/ViewsExposedFilterWidget.php @@ -0,0 +1,49 @@ +getResults(); + return $this->buildOneLevel($results); + } + + /** + * Builds one level from results. + * + * @param \Drupal\facets\Result\ResultInterface[] $results + * A list of results. + * + * @return array + * Generated build. + */ + protected function buildOneLevel(array $results): array { + $items = []; + + foreach ($results as $result) { + $label = $result->getDisplayValue(); + if ($this->getConfiguration()['show_numbers'] && $result->getCount() !== FALSE) { + $label .= ' ('. $result->getCount() .')'; + } + $items[$result->getRawValue()] = $label; + } + + return $items; + } + +} diff --git a/modules/facets_exposed_filters/src/Plugin/views/FacetsViewsPluginTrait.php b/modules/facets_exposed_filters/src/Plugin/views/FacetsViewsPluginTrait.php new file mode 100644 index 0000000..0241793 --- /dev/null +++ b/modules/facets_exposed_filters/src/Plugin/views/FacetsViewsPluginTrait.php @@ -0,0 +1,78 @@ +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['facet'] = [ + '#title' => 'Facet', + '#options' => $options, + '#type' => 'radios', + '#required' => TRUE, + '#default_value' => isset($this->options['facet']) ? $this->options['facet'] : NULL, + ]; + } + + /** + * Gets the facets to render. + * + * @return array + * The facet blocks to be output, in render array format. + */ + public function facetsViewsGetFacets() { + $build = []; + + if (!$this->options['facet']) { + return $build; + } + + /** @var \Drupal\facets\Entity\Facet $facet */ + $facet = $this->facetStorage->load($this->options['facet']); + $facet_build = $this->facetManager->build($facet); + + if (!isset($facet_build[0]) || !$facet_build[0]) { + return $build; + } + + $options = []; + // Empty behavior is not supported. Ensure we have actual results. + if (isset($facet_build[0]) && !isset($facet_build[0]["#type"])) { + $build = [ + '#type' => 'select', + '#options' => $facet_build[0], + '#multiple' => $this->options["expose"]["multiple"], + ]; + } + + return $build; + } + +} diff --git a/src/Plugin/views/area/FacetsArea.php b/modules/facets_exposed_filters/src/Plugin/views/area/FacetsArea.php similarity index 95% rename from src/Plugin/views/area/FacetsArea.php rename to modules/facets_exposed_filters/src/Plugin/views/area/FacetsArea.php index f3e7f59..fc5ed95 100644 --- a/src/Plugin/views/area/FacetsArea.php +++ b/modules/facets_exposed_filters/src/Plugin/views/area/FacetsArea.php @@ -1,12 +1,12 @@ TRUE]; $options['expose']['contains']['identifier'] = ['default' => 'facet_' . $random->name()]; - $options['facets']['default'] = []; + $options['expose']['contains']['multiple'] = ['default' => TRUE]; + $options['facet']['default'] = NULL; $options['label_display']['default'] = BlockPluginInterface::BLOCK_LABEL_VISIBLE; return $options; } @@ -93,7 +93,7 @@ class FacetsFilter extends FilterPluginBase { * {@inheritdoc} */ public function buildOptionsForm(&$form, FormStateInterface $form_state) { - HandlerBase::buildOptionsForm($form, $form_state); + parent::buildOptionsForm($form, $form_state); $this->facetsViewsBuildOptionsForm($form, $form_state); } @@ -101,13 +101,22 @@ class FacetsFilter extends FilterPluginBase { * {@inheritdoc} */ public function adminSummary() { - return implode(', ', array_filter($this->options['facets'])); + return $this->options['facet']; } /** * {@inheritdoc} */ public function valueForm(&$form, FormStateInterface $form_state) { + + // Dirty check if we are in views UI. Return no values as it removes + // settings from the view when editing settings for an existing facet + // filter. + if(isset($form["admin_label"])) { + $form['value'] = []; + return; + } + static $is_processing = NULL; if ($is_processing) { diff --git a/src/Plugin/better_exposed_filters/filter/Facets.php b/src/Plugin/better_exposed_filters/filter/Facets.php deleted file mode 100644 index d642956..0000000 --- a/src/Plugin/better_exposed_filters/filter/Facets.php +++ /dev/null @@ -1,81 +0,0 @@ -get('facets_not_built') ?? FALSE) { - return; - } - - $field_id = $this->getExposedFilterFieldId(); - if (!empty($form[$field_id]) && !empty($form[$field_id]['#content'])) { - $metadata = new CacheableMetadata(); - if (isset($form['#cache'])) { - $metadata = CacheableMetadata::createFromRenderArray($form); - } - - foreach ($form[$field_id]['#content'] as &$facet_content) { - /** @var \Drupal\facets\FacetInterface $facet */ - $facet = $facet_content['content'][0]['#facet'] ?? NULL; - if (!$facet) { - continue; - } - - switch ($facet->getWidgetInstance()->getPluginId()) { - case 'array': - // Not supported here. - break; - - case 'checkbox': - case 'dropdown': - case 'links': - default: - parent::exposedFormAlter($form, $form_state); - - /** @var \Drupal\views\Plugin\views\filter\FilterPluginBase $filter */ - $filter = $this->handler; - - if ($filter->view->ajaxEnabled() || $filter->view->display_handler->ajaxEnabled()) { - array_walk_recursive( $facet_content, function(&$value, $key) { - if ($key === 'data-drupal-facet-ajax') { - $value = '1'; - } - }); - } - - $metadata = $metadata->merge(CacheableMetadata::createFromRenderArray($facet_content)); - - break; - } - } - unset($facet_content); - - $metadata->applyTo($form); - } - } - - public static function isApplicable($filter = NULL, array $filter_options = []) { - $is_applicable = parent::isApplicable($filter, $filter_options); - if (is_a($filter, 'Drupal\facets\Plugin\views\filter\FacetsFilter')) { - $is_applicable = TRUE; - } - return $is_applicable; - } -} diff --git a/src/Plugin/views/FacetsViewsPluginTrait.php b/src/Plugin/views/FacetsViewsPluginTrait.php deleted file mode 100644 index df61f33..0000000 --- a/src/Plugin/views/FacetsViewsPluginTrait.php +++ /dev/null @@ -1,111 +0,0 @@ -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'] : [], - ]; - - $form['label_display'] = [ - '#type' => 'checkbox', - '#title' => $this->t('Display block title'), - '#default_value' => ($this->options['label_display'] === BlockPluginInterface::BLOCK_LABEL_VISIBLE), - '#return_value' => BlockPluginInterface::BLOCK_LABEL_VISIBLE, - ]; - } - - /** - * 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)) { - $item = [ - '#theme' => 'block', - '#configuration' => [ - 'provider' => 'facets', - 'label' => $facet->label(), - 'label_display' => ($this->options['label_display'] === BlockPluginInterface::BLOCK_LABEL_VISIBLE), - ], - '#id' => $facet->id(), - '#plugin_id' => 'facet_block:' . $facet->id(), - '#base_plugin_id' => 'facet_block', - '#derivative_plugin_id' => $facet->id(), - '#weight' => $facet->getWeight(), - 'content' => $facet_build, - ]; - - $metadata = CacheableMetadata::createFromObject($facet); - foreach (array_keys($this->view->getExposedInput()) as $input_key) { - if ($input_key) { - // Fulltext searches anf other exposed filters might influence a - // facet. - $metadata->addCacheContexts(['facets_filter:' . $input_key]); - } - } - - $metadata->applyTo($item); - if ($metadata->getCacheMaxAge() != 0) { - // Try to cache the rendered facet. - $item['#cache']['keys'] = ['facet', $facet->id()]; - } - - $items[] = $item; - } - } - - if (!empty($items)) { - $build = [ - '#theme' => 'facets_views_plugin', - '#content' => $items, - ]; - } - - return $build; - } - -}