elasticsearch_search_api-1.0.x-dev/modules/elasticsearch_search_api_example/src/Search/ExampleElasticSearchParamsBuilder.php

modules/elasticsearch_search_api_example/src/Search/ExampleElasticSearchParamsBuilder.php
<?php

namespace Drupal\elasticsearch_search_api_example\Search;

use Drupal\elasticsearch_search_api\Search\ElasticSearchParamsBuilder;
use Drupal\elasticsearch_search_api\Search\Facet\Control\CompositeFacetControlInterface;
use Drupal\elasticsearch_search_api\Search\Facet\Control\FacetControlInterface;
use Drupal\elasticsearch_search_api\Search\Facet\FacetCollection;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\elasticsearch_search_api\Search\FacetedKeywordSearchAction;
use Drupal\elasticsearch_search_api\Search\FacetedSearchActionInterface;
use Drupal\elasticsearch_search_api\Search\IndexFactoryAdapter;
use Drupal\elasticsearch_search_api\Utility\UtilityHelper;
use Drupal\search_api\Entity\Index;
use Drupal\search_api\IndexInterface;
use Drupal\search_api\Item\FieldInterface;

/**
 * Builds parameters to pass with an ElasticSearch search request.
 */
class ExampleElasticSearchParamsBuilder extends ElasticSearchParamsBuilder {

  /**
   * The default boost used for ngram fields. Can be overridden.
   */
  const NGRAM_FIELD_DEFAULT_BOOST = 0.1;

  /**
   * Set up exceptions for ngram fields that need a specific boost.
   *
   * Format:
   *   [
   *    'field_paragraph_title' => 0.5,
   *   ]
   */
  const NGRAM_FIELD_BOOST_EXCEPTIONS = [];

  /**
   * Index id.
   *
   * @var \Drupal\search_api\Entity\Index
   */
  protected $index;

  /**
   * The lang code of the current language.
   *
   * @var string
   */
  protected $langcode;

  /**
   * Index factory adapter.
   *
   * @var \Drupal\elasticsearch_search_api\Search\IndexFactoryAdapter
   */
  protected $indexFactoryAdapter;

  /**
   * ElasticSearchParamsBuilder constructor.
   */
  public function __construct(IndexInterface $index, LanguageManagerInterface $languageManager, IndexFactoryAdapter $indexFactoryAdapter) {
    $this->index = $index;
    $this->langcode = $languageManager->getCurrentLanguage()->getId();
    $this->indexFactoryAdapter = $indexFactoryAdapter;
  }

  /**
   * {@inheritdoc}
   */
  public function build(FacetedSearchActionInterface $searchAction): array {
    if ($searchAction instanceof FacetedKeywordSearchAction && $keyword = $searchAction->getKeyword()) {
      $fragment_size = 300;

      $params = [
        'body' => [
          '_source' => FALSE,
          'from' => $searchAction->getFrom(),
          'size' => $searchAction->getSize(),
          'query' => $this->buildMultiMatchQueries($keyword),
          'highlight' => [
            'number_of_fragments' => 1,
            'fragment_size' => is_numeric($fragment_size) ? $fragment_size : 300,
            'fields' => [
              "*" => [
                "require_field_match" => FALSE,
              ],
            ],
            'pre_tags' => '<strong>',
            'post_tags' => '</strong>',
          ],
        ],
      ];
    }
    else {
      $params = [
        'body' => [
          '_source' => FALSE,
          'from' => $searchAction->getFrom(),
          'size' => $searchAction->getSize(),
        ],
      ];
    }

    $chosenFacetValues = $searchAction->getChosenFacetValues();

    if (count($searchAction->getAvailableFacets())) {
      $params['body']['aggs'] = $this->buildAggregations($searchAction, $chosenFacetValues);
    }

    $post_filter = $this->buildFacetFilters($chosenFacetValues);
    $params['body']['post_filter'] = [
      'bool' => [
        'must' => $post_filter,
      ],
    ];

    $index = $this->getIndexName($this->index);
    $params['index'] = $index;

    return $params;
  }

  /**
   * Get standard filters for the search query.
   *
   * Returns a list of standard filters, such as language, published state,
   * and content type.
   *
   * @return array
   *   Standard filters.
   */
  protected function getStandardFilters() {
    return [
      [
        'term' => [
          'langcode' => $this->langcode,
        ],
      ],
      [
        'term' => [
          'status' => 1,
        ],
      ],
    ];
  }

  /**
   * Builds a filter for a given set of facet values.
   *
   * @param \Drupal\elasticsearch_search_api\Search\Facet\FacetCollection $facet_values
   *   Facet values.
   *
   * @return array
   *   Array to be used as an ElasticSearch filter.
   */
  protected function buildFacetFilters(FacetCollection $facet_values): array {
    $post_filter = [];
    /** @var \Drupal\elasticsearch_search_api\Search\Facet\FacetValueInterface[] $selected_values */
    foreach ($facet_values as $facet => $selected_values) {
      $facetControlService = \Drupal::service('elasticsearch_search_api.facet_control.' . $facet);
      if ($facetControlService instanceof CompositeFacetControlInterface) {
        $facet_post_filter = $facetControlService->buildFacetFilter($selected_values);
      }
      elseif ($facetControlService instanceof FacetControlInterface) {
        $facet_post_filter = [];
        foreach ($selected_values as $selected_value) {
          $facet_post_filter[] = [
            'term' => [
              $facetControlService->getFieldName() => $selected_value->value(),
            ],
          ];
        }
      }

      if (count($facet_post_filter) > 1) {
        $post_filter[] = [
          'bool' => [
            'should' => $facet_post_filter,
          ],
        ];
      }
      elseif (count($facet_post_filter) === 1) {
        $post_filter[] = reset($facet_post_filter);
      }
    }

    return $post_filter;
  }

  /**
   * Build aggregations for an elastic query.
   *
   * @param \Drupal\elasticsearch_search_api\Search\FacetedSearchActionInterface $searchAction
   *   The search action to get available facets from.
   * @param \Drupal\elasticsearch_search_api\Search\Facet\FacetCollection $chosenFacetValues
   *   Chosen facet values.
   *
   * @return array
   *   List of aggregations.
   */
  protected function buildAggregations(FacetedSearchActionInterface $searchAction, FacetCollection $chosenFacetValues) {
    $aggregations = [];

    foreach ($searchAction->getAvailableFacets() as $facet) {
      /** @var \Drupal\elasticsearch_search_api\Search\Facet\Control\FacetControlInterface $facetControlService */
      $facetControlService = \Drupal::service('elasticsearch_search_api.facet_control.' . $facet);
      if (!$facetControlService->addToAggregations()) {
        continue;
      }

      $aggregation_facet_values = $chosenFacetValues->without($facet);
      // Use a sub-aggregation & apply the the filter of all other facets on it.
      if (!$aggregation_facet_values->isEmpty()) {
        $agg_filter = $this->buildFacetFilters($aggregation_facet_values);

        $aggregations[$facet] = [
          'filter' => ['bool' => ['must' => $agg_filter]],
          'aggs' => [
            'filtered' => [
              'terms' => [
                'field' => $facetControlService->getFieldName(),
                'size' => 999,
              ],
            ],
          ],
        ];
      }
      else {
        $aggregations[$facet] = [
          'terms' => [
            'field' => $facetControlService->getFieldName(),
            'size' => 999,
          ],
        ];
      }
    }

    return $aggregations;
  }

  /**
   * Dynamically generate a list of fields to add to the query.
   *
   * This list will include a basic field query
   * and optionally include an ngram query.
   * Every list item will contain boosting.
   *
   * @return array
   *   An array of fields.
   */
  protected function buildMultimatchFields($include_ngram = TRUE) {
    $multi_match_fields = [];
    $configured_fields = $this->index->getFulltextFields();
    foreach ($configured_fields as $field_name) {
      $field = $this->index->getField($field_name);
      $multi_match_fields[] = $this->buildBasicFieldMatch($field);
      if ($include_ngram) {
        $multi_match_fields[] = $this->buildNgramFieldMatch($field);
      }
    }

    return $multi_match_fields;
  }

  /**
   * Build a part of the multimatch query.
   *
   * Format: field_paragraph_title^5
   *
   * @param \Drupal\search_api\Item\FieldInterface $field
   *   A configured search api field.
   *
   * @return string
   *   Field identifier string with boosting value.
   */
  protected function buildBasicFieldMatch(FieldInterface $field) {
    return "{$field->getFieldIdentifier()}^{$field->getBoost()}";
  }

  /**
   * Build a part of the multimatch query.
   *
   * Format: field_paragraph_title.ngram^5
   *
   * By default, the constant NGRAM_FIELD_DEFAULT_BOOST value
   * will be used to boost ngram fields. This can be overridden
   * using the NGRAM_FIELD_BOOST_EXCEPTIONS constant, to provide
   * field specific ngram boosting.
   *
   * @param \Drupal\search_api\Item\FieldInterface $field
   *   A configured search api field.
   *
   * @return string
   *   Field name as a string concatenated with ngram boosting value.
   */
  protected function buildNgramFieldMatch(FieldInterface $field) {
    $field_name = $field->getFieldIdentifier();

    $ngram_boost = static::NGRAM_FIELD_DEFAULT_BOOST;
    if (array_key_exists($field_name, static::NGRAM_FIELD_BOOST_EXCEPTIONS)) {
      $ngram_boost = static::NGRAM_FIELD_BOOST_EXCEPTIONS[$field_name];
    }

    return "{$field_name}.ngram^{$ngram_boost}";
  }

  /**
   * Build the multi match queries.
   *
   * @return array
   *   The multi match query in array format.
   */
  protected function buildMultiMatchQueries($keyword) {
    $quoted_phrase = UtilityHelper::extractQuotedString($keyword);
    $words_in_keyword = str_word_count($keyword);
    $query = [];

    if (!empty($keyword)) {
      $query['function_score']['query']['bool']['should'][] = [
        'bool' => [
          'must' => [
            'multi_match' => [
              'query' => $keyword,
              'fields' => $this->buildMultimatchFields(),
              'type' => 'most_fields',
            ],
          ],
        ],
      ];

      if ($words_in_keyword > 1) {
        $query['function_score']['functions'][] = [
          'filter' => [
            'multi_match' => [
              'query' => $keyword,
              'fields' => $this->buildMultimatchFields(FALSE),
              'minimum_should_match' => $words_in_keyword,
              'type' => 'cross_fields',
            ],
          ],
          'weight' => 100,
        ];
      }
    }

    foreach ($quoted_phrase as $phrase) {
      $query['function_score']['query']['bool']['should'][] = [
        'bool' => [
          'must' => [
            'multi_match' => [
              'query' => $phrase,
              'fields' => $this->buildMultimatchFields(),
              'type' => 'phrase',
            ],
          ],
        ],
      ];
    }

    $query['function_score']['boost'] = 1;
    $query['function_score']['boost_mode'] = "multiply";

    return $query;
  }

  /**
   * Get the name of the index.
   *
   * @param \Drupal\search_api\Entity\Index $index
   *   The index.
   *
   * @return string
   *   The index name.
   */
  protected function getIndexName(Index $index) {
    return $this->indexFactoryAdapter->getIndexName($index);
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc