jsonapi_search_api-8.x-1.x-dev/modules/jsonapi_search_api_facets/src/EventSubscriber/SearchApiQueryPreExecute.php
modules/jsonapi_search_api_facets/src/EventSubscriber/SearchApiQueryPreExecute.php
<?php
namespace Drupal\jsonapi_search_api_facets\EventSubscriber;
use Drupal\facets\FacetManager\DefaultFacetManager;
use Drupal\search_api\Event\QueryPreExecuteEvent;
use Drupal\search_api\Event\SearchApiEvents;
use Drupal\search_api\Query\ConditionGroupInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Adds Facets support to a Search API Query.
*/
class SearchApiQueryPreExecute implements EventSubscriberInterface {
/**
* The facet manager.
*
* @var \Drupal\facets\FacetManager\DefaultFacetManager
*/
protected $facetManager;
/**
* Constructs a new event subscriber.
*
* @param \Drupal\facets\FacetManager\DefaultFacetManager $facet_manager
* The facet manager.
*/
public function __construct(DefaultFacetManager $facet_manager) {
$this->facetManager = $facet_manager;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
$events[SearchApiEvents::QUERY_PRE_EXECUTE][] = ['includeFacetsInQuery'];
return $events;
}
/**
* Alter the Search API query to include Facets if enabled.
*
* @param \Drupal\search_api\Event\QueryPreExecuteEvent $event
* The event being emitted by Search API.
*/
public function includeFacetsInQuery(QueryPreExecuteEvent $event) {
$query = $event->getQuery();
$search_id = $query->getSearchId();
if (strpos($search_id, 'jsonapi_search_api:') === 0 && $query->getIndex()->getServerInstance()->supportsFeature('search_api_facets')) {
$facet_source_id = strtr('jsonapi_search_api_facets:!index', [
'!index' => $query->getIndex()->id(),
]);
$this->facetManager->alterQuery($query, $facet_source_id);
$facets = $this->facetManager->getFacetsByFacetSourceId($facet_source_id);
$facet_tags = [];
$aliased_facets = [];
foreach ($facets as $facet) {
if ($facet->getUrlAlias() !== $facet->getFieldIdentifier()) {
$aliased_facets[] = $facet->getUrlAlias();
}
if ($facet->getQueryOperator() === 'or') {
$facet_field = $facet->getFieldIdentifier();
$facet_tag = strtr('facet:!field', ['!field' => $facet_field]);
$facet_tags[$facet_field] = $facet_tag;
}
}
// When using the OR operator for facets a filter is created to
// support tagging and excluding filters. The base JSON:API search will
// add the initial filter from the URL parameters which will stomp over
// the one that is added via facets.
// Only way to alter these conditions is modifying by reference.
// @see: https://www.drupal.org/project/search_api/issues/2910522#comment-12270738.
$conditions = $query->getConditionGroup();
$this->removeAliasConditions($conditions, $aliased_facets);
$this->tagConditionsWithFacetTags($conditions, $facet_tags);
}
}
/**
* Removes alias conditions from the query.
*
* The index resource adds all filter conditions to the Search API query,
* which includes the facet URL alias. Remove them to prevent errors for
* non-existent fields.
*
* @param \Drupal\search_api\Query\ConditionGroupInterface $condition_group
* The condition group.
* @param array $aliases
* The facet aliases.
*/
protected function removeAliasConditions(ConditionGroupInterface $condition_group, array $aliases) {
$conditions = &$condition_group->getConditions();
foreach ($conditions as $key => $condition) {
if ($condition instanceof ConditionGroupInterface) {
$this->removeAliasConditions($condition, $aliases);
}
else {
if (in_array($condition->getField(), $aliases, TRUE)) {
unset($conditions[$key]);
}
}
}
}
/**
* Handles tagging conditionGroups with appropriate facet tags as needed.
*
* @param \Drupal\search_api\Query\ConditionInterface|\Drupal\search_api\Query\ConditionGroupInterface $condition_or_group
* The ConditionGroup or Condition being evaluated.
* @param array $facet_tags
* The facet tags that may need to be applied to the condition.
*
* @return void|string
* Void if the Condition is being modified, the tag to be added otherwise.
*/
protected function tagConditionsWithFacetTags($condition_or_group, array $facet_tags) {
if ($condition_or_group instanceof ConditionGroupInterface) {
foreach ($condition_or_group->getConditions() as $condition) {
$tags_to_add = $this->tagConditionsWithFacetTags($condition, $facet_tags);
if ($tags_to_add) {
$tags = &$condition_or_group->getTags();
$tags[$tags_to_add] = $tags_to_add;
}
}
}
else {
$field = $condition_or_group->getField();
if (isset($facet_tags[$field])) {
return $facet_tags[$field];
}
}
}
}
