class SearchApiElasticsearchBackend in Elasticsearch Connector 8.6
Same name and namespace in other branches
- 8.7 src/Plugin/search_api/backend/SearchApiElasticsearchBackend.php \Drupal\elasticsearch_connector\Plugin\search_api\backend\SearchApiElasticsearchBackend
- 8 src/Plugin/search_api/backend/SearchApiElasticsearchBackend.php \Drupal\elasticsearch_connector\Plugin\search_api\backend\SearchApiElasticsearchBackend
- 8.2 src/Plugin/search_api/backend/SearchApiElasticsearchBackend.php \Drupal\elasticsearch_connector\Plugin\search_api\backend\SearchApiElasticsearchBackend
- 8.5 src/Plugin/search_api/backend/SearchApiElasticsearchBackend.php \Drupal\elasticsearch_connector\Plugin\search_api\backend\SearchApiElasticsearchBackend
Elasticsearch Search API Backend definition.
TODO: Check for dependencies and remove them in order to properly test the code.
Plugin annotation
@SearchApiBackend(
id = "elasticsearch",
label = @Translation("Elasticsearch"),
description = @Translation("Index items using an Elasticsearch server.")
)
Hierarchy
- class \Drupal\Component\Plugin\PluginBase implements DerivativeInspectionInterface, PluginInspectionInterface
- class \Drupal\Core\Plugin\PluginBase uses DependencySerializationTrait, MessengerTrait, StringTranslationTrait
- class \Drupal\search_api\Plugin\HideablePluginBase implements HideablePluginInterface
- class \Drupal\search_api\Plugin\ConfigurablePluginBase implements ConfigurablePluginInterface uses PluginDependencyTrait
- class \Drupal\search_api\Backend\BackendPluginBase implements BackendInterface uses LoggerTrait
- class \Drupal\elasticsearch_connector\Plugin\search_api\backend\SearchApiElasticsearchBackend implements PluginFormInterface, SearchApiElasticsearchBackendInterface uses PluginFormTrait
- class \Drupal\search_api\Backend\BackendPluginBase implements BackendInterface uses LoggerTrait
- class \Drupal\search_api\Plugin\ConfigurablePluginBase implements ConfigurablePluginInterface uses PluginDependencyTrait
- class \Drupal\search_api\Plugin\HideablePluginBase implements HideablePluginInterface
- class \Drupal\Core\Plugin\PluginBase uses DependencySerializationTrait, MessengerTrait, StringTranslationTrait
Expanded class hierarchy of SearchApiElasticsearchBackend
1 file declares its use of SearchApiElasticsearchBackend
- ExcludeSourceFields.php in src/
Plugin/ search_api/ processor/ ExcludeSourceFields.php
File
- src/
Plugin/ search_api/ backend/ SearchApiElasticsearchBackend.php, line 48
Namespace
Drupal\elasticsearch_connector\Plugin\search_api\backendView source
class SearchApiElasticsearchBackend extends BackendPluginBase implements PluginFormInterface, SearchApiElasticsearchBackendInterface {
use PluginFormTrait;
/**
* Set a large integer to be the size for a "Hard limit" value of "No limit".
*/
const FACET_NO_LIMIT_SIZE = 10000;
/**
* Auto fuzziness setting.
*
* Auto fuzziness in Elasticsearch means we don't specify a specific
* Levenshtein distance, falling back to auto behavior. Fuzziness, including
* auto fuzziness, is defined in the Elasticsearch documentation here:
*
* @see https://www.elastic.co/guide/en/elasticsearch/reference/5.6/common-options.html#fuzziness
*/
const FUZZINESS_AUTO = 'auto';
/**
* Elasticsearch settings.
*
* @var \Drupal\Core\Config\Config
*/
protected $elasticsearchSettings;
/**
* Cluster id.
*
* @var int
*/
protected $clusterId;
/**
* Cluster object.
*
* @var \Drupal\elasticsearch_connector\Entity\Cluster
*/
protected $cluster;
/**
* Elasticsearch client.
*
* @var \nodespark\DESConnector\ClientInterface
*/
protected $client;
/**
* Form builder service.
*
* @var \Drupal\Core\Form\FormBuilderInterface
*/
protected $formBuilder;
/**
* Module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Client manager service.
*
* @var \Drupal\elasticsearch_connector\ElasticSearch\ClientManagerInterface
*/
protected $clientManager;
/**
* The cluster manager service.
*
* @var \Drupal\elasticsearch_connector\ClusterManager
*/
protected $clusterManager;
/**
* The entity type manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Logger.
*
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* Elasticsearch index factory.
*
* @var \Drupal\elasticsearch_connector\ElasticSearch\Parameters\Factory\IndexFactory
*/
protected $indexFactory;
/**
* SearchApiElasticsearchBackend constructor.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param array $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Form\FormBuilderInterface $form_builder
* Form builder service.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* Module handler service.
* @param \Drupal\elasticsearch_connector\ElasticSearch\ClientManagerInterface $client_manager
* Client manager service.
* @param \Drupal\Core\Config\Config $elasticsearch_settings
* Elasticsearch settings object.
* @param \Psr\Log\LoggerInterface $logger
* Logger.
* @param \Drupal\elasticsearch_connector\ClusterManager $cluster_manager
* The cluster manager service.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager service.
* @param \Drupal\elasticsearch_connector\ElasticSearch\Parameters\Factory\IndexFactory $indexFactory
* Index factory.
*
* @throws \Drupal\search_api\SearchApiException
*/
public function __construct(array $configuration, $plugin_id, array $plugin_definition, FormBuilderInterface $form_builder, ModuleHandlerInterface $module_handler, ClientManagerInterface $client_manager, Config $elasticsearch_settings, LoggerInterface $logger, ClusterManager $cluster_manager, EntityTypeManagerInterface $entity_type_manager, IndexFactory $indexFactory) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->formBuilder = $form_builder;
$this->moduleHandler = $module_handler;
$this->clientManager = $client_manager;
$this->logger = $logger;
$this->elasticsearchSettings = $elasticsearch_settings;
$this->clusterManager = $cluster_manager;
$this->entityTypeManager = $entity_type_manager;
$this->indexFactory = $indexFactory;
if (empty($this->configuration['cluster_settings']['cluster'])) {
$this->configuration['cluster_settings']['cluster'] = $this->clusterManager
->getDefaultCluster();
}
$this->cluster = $this->entityTypeManager
->getStorage('elasticsearch_cluster')
->load($this->configuration['cluster_settings']['cluster']);
if (!isset($this->cluster)) {
throw new SearchApiException($this
->t('Cannot load the Elasticsearch cluster for your index.'));
}
$this->client = $this->clientManager
->getClientForCluster($this->cluster);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($configuration, $plugin_id, $plugin_definition, $container
->get('form_builder'), $container
->get('module_handler'), $container
->get('elasticsearch_connector.client_manager'), $container
->get('config.factory')
->get('elasticsearch.settings'), $container
->get('logger.factory')
->get('elasticconnector_sapi'), $container
->get('elasticsearch_connector.cluster_manager'), $container
->get('entity_type.manager'), $container
->get('elasticsearch_connector.index_factory'));
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'cluster_settings' => [
'cluster' => '',
],
'scheme' => 'http',
'host' => 'localhost',
'port' => '9200',
'path' => '',
'excerpt' => FALSE,
'retrieve_data' => FALSE,
'highlight_data' => FALSE,
'http_method' => 'AUTO',
'autocorrect_spell' => TRUE,
'autocorrect_suggest_words' => TRUE,
'fuzziness' => self::FUZZINESS_AUTO,
];
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
if (!$this->server
->isNew()) {
$server_link = $this->cluster
->getSafeUrl();
// Editing this server.
$form['server_description'] = [
'#type' => 'item',
'#title' => $this
->t('Elasticsearch Cluster'),
'#description' => Link::fromTextAndUrl($server_link, Url::fromUri($server_link)),
];
}
$form['cluster_settings'] = [
'#type' => 'fieldset',
'#title' => t('Elasticsearch settings'),
];
// We are not displaying disabled clusters.
$clusters = $this->clusterManager
->loadAllClusters(FALSE);
$options = [];
foreach ($clusters as $key => $cluster) {
$options[$key] = $cluster->cluster_id;
}
$options[$this->clusterManager
->getDefaultCluster()] = t('Default cluster: @name', [
'@name' => $this->clusterManager
->getDefaultCluster(),
]);
$form['cluster_settings']['cluster'] = [
'#type' => 'select',
'#title' => t('Cluster'),
'#required' => TRUE,
'#options' => $options,
'#default_value' => $this->configuration['cluster_settings']['cluster'] ? $this->configuration['cluster_settings']['cluster'] : '',
'#description' => t('Select the cluster you want to handle the connections.'),
];
$fuzziness_options = [
'' => $this
->t('- Disabled -'),
self::FUZZINESS_AUTO => self::FUZZINESS_AUTO,
];
$fuzziness_options += array_combine(range(0, 5), range(0, 5));
$form['fuzziness'] = [
'#type' => 'select',
'#title' => t('Fuzziness'),
'#required' => TRUE,
'#options' => $fuzziness_options,
'#default_value' => $this->configuration['fuzziness'],
'#description' => $this
->t('Some queries and APIs support parameters to allow inexact fuzzy matching, using the fuzziness parameter. See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/5.6/common-options.html#fuzziness">Fuzziness</a> for more information.'),
];
return $form;
}
/**
* {@inheritdoc}
*
* TODO: implement 'search_api_multi',
* TODO: implement 'search_api_service_extra',
* TODO: implement 'search_api_spellcheck',
* TODO: implement 'search_api_data_type_location',
* TODO: implement 'search_api_data_type_geohash',
*/
public function getSupportedFeatures() {
// First, check the features we always support.
return [
'search_api_autocomplete',
'search_api_facets',
'search_api_facets_operator_or',
'search_api_grouping',
'search_api_mlt',
];
}
/**
* {@inheritdoc}
*/
public function getSupportedDataTypes() {
$data_types = [
'object',
'location',
];
// Allow other modules to intercept and define what types they want to
// support.
$this->moduleHandler
->alter('elasticsearch_connector_supported_data_types', $data_types);
return $data_types;
}
/**
* {@inheritdoc}
*/
public function viewSettings() {
$info = [];
$server_link = $this->cluster
->getSafeUrl();
$info[] = [
'label' => $this
->t('Elasticsearch server URI'),
'info' => Link::fromTextAndUrl($server_link, Url::fromUri($server_link)),
];
if ($this->server
->status()) {
// If the server is enabled, check whether Elasticsearch can be reached.
$ping = $this->client
->isClusterOk();
if ($ping) {
$msg = $this
->t('The Elasticsearch server could be reached');
}
else {
$msg = $this
->t('The Elasticsearch server could not be reached. Further data is therefore unavailable.');
}
$info[] = [
'label' => $this
->t('Connection'),
'info' => $msg,
'status' => $ping ? 'ok' : 'error',
];
}
return $info;
}
/**
* Get the configured cluster; if the cluster is blank, use the default.
*
* @return string
* The name of the configured cluster.
*/
public function getCluster() {
return $this->configuration['cluster_settings']['cluster'];
}
/**
* Get the configured fuzziness value.
*
* @return string
* The configured fuzziness value.
*/
public function getFuzziness() {
return $this->configuration['fuzziness'];
}
/**
* {@inheritdoc}
*/
public function addIndex(IndexInterface $index) {
$this
->updateIndex($index);
}
/**
* {@inheritdoc}
*/
public function updateIndex(IndexInterface $index) {
try {
if (!$this->client
->indices()
->exists($this->indexFactory
->index($index))) {
$response = $this->client
->indices()
->create($this->indexFactory
->create($index));
if (!$this->client
->CheckResponseAck($response)) {
drupal_set_message($this
->t('The elasticsearch client was not able to create index'), 'error');
}
}
// Update mapping.
$this
->fieldsUpdated($index);
} catch (ElasticsearchException $e) {
drupal_set_message($e
->getMessage(), 'error');
}
}
/**
* Create or re-create the given index's field mapping.
*
* @param \Drupal\search_api\IndexInterface $index
* Index to update fields for.
*
* @return bool
* TRUE on success, FALSE otherwise.
*/
public function fieldsUpdated(IndexInterface $index) {
$params = $this->indexFactory
->index($index, TRUE);
try {
if ($this->client
->indices()
->existsType($params)) {
$current_mapping = $this->client
->indices()
->getMapping($params);
if (!empty($current_mapping)) {
try {
// If the mapping exits, delete it to be able to re-create it.
$this->client
->indices()
->deleteMapping($params);
} catch (ElasticsearchException $e) {
// If the mapping exits, delete the index and recreate it.
// In Elasticsearch 2.3 it is not possible to delete a mapping,
// so don't use $this->client->indices()->deleteMapping as doing so
// will throw an exception.
$this
->removeIndex($index);
$this
->addIndex($index);
}
}
}
$response = $this->client
->indices()
->putMapping($this->indexFactory
->mapping($index));
if (!$this->client
->CheckResponseAck($response)) {
drupal_set_message(t('Cannot create the mapping of the fields!'), 'error');
}
} catch (ElasticsearchException $e) {
drupal_set_message($e
->getMessage(), 'error');
return FALSE;
}
return TRUE;
}
/**
* {@inheritdoc}
*/
public function removeIndex($index) {
$params = $this->indexFactory
->index($index);
try {
if ($this->client
->indices()
->exists($params)) {
$this->client
->indices()
->delete($params);
}
} catch (ElasticsearchException $e) {
drupal_set_message($e
->getMessage(), 'error');
}
}
/**
* {@inheritdoc}
*/
public function indexItems(IndexInterface $index, array $items) {
$elastic_type_exists = $this
->doesTypeExists($index);
if (empty($elastic_type_exists) || empty($items)) {
return array();
}
try {
$response = $this->client
->bulk($this->indexFactory
->bulkIndex($index, $items));
// If there were any errors, log them and throw an exception.
if (!empty($response['errors'])) {
foreach ($response['items'] as $item) {
if (!empty($item['index']['status']) && $item['index']['status'] == '400') {
$this->logger
->error('%reason. %caused_by for id: %id', [
'%reason' => $item['index']['error']['reason'],
'%caused_by' => $item['index']['error']['caused_by']['reason'],
'%id' => $item['index']['_id'],
]);
}
}
throw new SearchApiException($this
->t('An error occurred during indexing. Check your watchdog for more information.'));
}
} catch (ElasticsearchException $e) {
drupal_set_message($e
->getMessage(), 'error');
}
return array_keys($items);
}
/**
* {@inheritdoc}
*/
public function deleteAllIndexItems(IndexInterface $index, $datasource_id = NULL) {
$this
->removeIndex($index);
$this
->addIndex($index);
}
/**
* {@inheritdoc}
*/
public function deleteItems(IndexInterface $index = NULL, array $ids) {
if (!count($ids)) {
return;
}
try {
$this->client
->bulk($this->indexFactory
->bulkDelete($index, $ids));
} catch (ElasticsearchException $e) {
drupal_set_message($e
->getMessage(), 'error');
}
}
/**
* Implements SearchApiAutocompleteInterface::getAutocompleteSuggestions().
*
* Note that the interface is not directly implemented to avoid a dependency
* on search_api_autocomplete module.
*/
public function getAutocompleteSuggestions(QueryInterface $query, SearchInterface $search, $incomplete_key, $user_input) {
try {
$fields = $this
->getQueryFulltextFields($query);
if (count($fields) > 1) {
throw new \LogicException('Elasticsearch requires a single fulltext field for use with autocompletion! Please adjust your configuration.');
}
$query
->setOption('autocomplete', $incomplete_key);
$query
->setOption('autocomplete_field', reset($fields));
// Disable facets so it does not collide with autocompletion results.
$query
->setOption('search_api_facets', FALSE);
$result = $this
->search($query);
$query
->postExecute($result);
// Parse suggestions out of the response.
$suggestions = [];
$factory = new SuggestionFactory($user_input);
$response = $result
->getExtraData('elasticsearch_response');
if (isset($response['aggregations']['autocomplete']['buckets'])) {
$suffix_start = strlen($user_input);
$buckets = $response['aggregations']['autocomplete']['buckets'];
foreach ($buckets as $bucket) {
$suggestions[] = $factory
->createFromSuggestionSuffix(substr($bucket['key'], $suffix_start), $bucket['doc_count']);
}
}
return $suggestions;
} catch (\Exception $e) {
$this->logger
->error($e
->getMessage());
return array();
}
}
/**
* {@inheritdoc}
*/
public function search(QueryInterface $query) {
// Results.
$search_result = $query
->getResults();
// Get index.
$index = $query
->getIndex();
$params = $this->indexFactory
->index($index, TRUE);
// Check Elasticsearch index.
if (!$this->client
->indices()
->existsType($params)) {
return $search_result;
}
// Add the facets to the request.
if ($query
->getOption('search_api_facets')) {
$this
->addFacets($query);
}
// Build Elasticsearch query.
$params = SearchFactory::search($query);
// Note that this requires fielddata option to be enabled.
// @see ::getAutocompleteSuggestions()
// @see \Drupal\elasticsearch_connector\ElasticSearch\Parameters\Factory\IndexFactory::mapping()
if ($incomplete_key = $query
->getOption('autocomplete')) {
// Autocomplete suggestions are determined using a term aggregation (like
// facets), but filtered to only include facets with the right prefix.
// As the search facet field is analyzed, facets are tokenized terms and
// all in lower case. To match that, we need convert the our filter to
// lower case also.
$incomplete_key = strtolower($incomplete_key);
// Note that we cannot use the elasticsearch client aggregations API as
// it does not support the "include" parameter.
$params['body']['aggs']['autocomplete']['terms'] = [
'field' => $query
->getOption('autocomplete_field'),
'include' => $incomplete_key . '.*',
];
}
try {
// Allow modules to alter the Elasticsearch query.
$this
->preQuery($query);
// Do search.
$response = $this->client
->search($params)
->getRawResponse();
$results = SearchFactory::parseResult($query, $response);
// Handle the facets result when enabled.
if ($query
->getOption('search_api_facets')) {
$this
->parseFacets($results, $query);
}
// Allow modules to alter the Elasticsearch Results.
$this
->postQuery($results, $query, $response);
return $results;
} catch (\Exception $e) {
watchdog_exception('Elasticsearch API', $e);
return $search_result;
}
}
/**
* {@inheritdoc}
*/
public function isAvailable() {
return $this->client
->isClusterOk();
}
/**
* Fill the aggregation array of the request.
*
* @param \Drupal\search_api\Query\QueryInterface $query
* Search API query.
*/
protected function addFacets(QueryInterface $query) {
foreach ($query
->getOption('search_api_facets') as $key => $facet) {
$facet += [
'type' => NULL,
];
$object = NULL;
// @todo Add more options.
switch ($facet['type']) {
case 'stats':
$object = new Stats($key, $key);
break;
default:
$object = new Terms($key, $key);
// Limit the number of facets in the result according the to facet
// setting. A limit of 0 means no limit. Elasticsearch doesn't have a
// way to set no limit, so we set a large integer in that case.
$size = $facet['limit'] ? $facet['limit'] : self::FACET_NO_LIMIT_SIZE;
$object
->setSize($size);
// Set global scope for facets with 'OR' operator.
if ($facet['operator'] == 'or') {
$object
->setGlobalScope(TRUE);
}
}
if (!empty($object)) {
$this->client
->aggregations()
->setAggregation($object);
}
}
}
/**
* Parse the result set and add the facet values.
*
* @param \Drupal\search_api\Query\ResultSet $results
* Result set, all items matched in a search.
* @param \Drupal\search_api\Query\QueryInterface $query
* Search API query object.
*/
protected function parseFacets(ResultSet $results, QueryInterface $query) {
$response = $results
->getExtraData('elasticsearch_response');
$facets = $query
->getOption('search_api_facets');
// Create an empty array that will be attached to the result object.
$attach = [];
foreach ($facets as $key => $facet) {
$terms = [];
// Handle 'and' operator.
if ($facet['operator'] == 'and' || $facet['operator'] == 'or' && !isset($response['aggregations'][$key . '_global'])) {
if (!empty($facet['type']) && $facet['type'] == 'stats') {
$terms = $response['aggregations'][$key];
}
else {
$buckets = $response['aggregations'][$key]['buckets'];
array_walk($buckets, function ($value) use (&$terms) {
$terms[] = [
'count' => $value['doc_count'],
'filter' => '"' . $value['key'] . '"',
];
});
}
}
elseif ($facet['operator'] == 'or') {
if (!empty($facet['type']) && $facet['type'] == 'stats') {
$terms = $response['aggregations'][$key . '_global'];
}
else {
$buckets = $response['aggregations'][$key . '_global'][$key]['buckets'];
array_walk($buckets, function ($value) use (&$terms) {
$terms[] = [
'count' => $value['doc_count'],
'filter' => '"' . $value['key'] . '"',
];
});
}
}
$attach[$key] = $terms;
}
$results
->setExtraData('search_api_facets', $attach);
}
/**
* Helper function, check if the given index and type exists.
*
* @param \Drupal\search_api\IndexInterface $index
* Index object.
*
* @return bool
* TRUE if the given index exists in Elasticsearch, otherwise FALSE.
*/
protected function doesTypeExists(IndexInterface $index) {
$params = $this->indexFactory
->index($index, TRUE);
try {
return $this->client
->indices()
->existsType($params);
} catch (ElasticsearchException $e) {
drupal_set_message($e
->getMessage(), 'error');
return FALSE;
}
}
/**
* Prefixes an index ID as configured.
*
* The resulting ID will be a concatenation of the following strings:
* - If set, the "elasticsearch.settings.index_prefix" configuration.
* - If set, the index-specific "elasticsearch.settings.index_prefix_INDEX"
* configuration.
* - The index's machine name.
*
* @param string $machine_name
* The index's machine name.
*
* @return string
* The prefixed machine name.
*/
protected function getIndexId($machine_name) {
// Prepend per-index prefix.
$id = $this->elasticsearchSettings
->get('index_prefix_' . $machine_name) . $machine_name;
// Prepend environment prefix.
$id = $this->elasticsearchSettings
->get('index_prefix') . $id;
return $id;
}
/**
* Helper function. Return date gap from two dates or timestamps.
*
* @param mixed $min
* Start date or timestamp.
* @param mixed $max
* End date or timestamp.
* @param bool $timestamp
* TRUE if the first two params are timestamps, FALSE otherwise. In the case
* of FALSE, it's assumed the first two arguments are strings and they are
* converted to timestamps using strtotime().
*
* @return string
* One of 'NONE', 'YEAR', 'MONTH', or 'DAY' depending on the difference
*
* @see facetapi_get_timestamp_gap()
*/
protected static function getDateGap($min, $max, $timestamp = TRUE) {
if ($timestamp !== TRUE) {
$min = strtotime($min);
$max = strtotime($max);
}
if (empty($min) || empty($max)) {
return 'DAY';
}
$diff = $max - $min;
switch (TRUE) {
case $diff > 86400 * 365:
return 'NONE';
case $diff > 86400 * gmdate('t', $min):
return 'YEAR';
case $diff > 86400:
return 'MONTH';
default:
return 'DAY';
}
}
/**
* Helper function build facets in search.
*
* @param array $params
* Array of parameters to be sent in the body of a _search endpoint
* Elasticsearch request.
* @param \Drupal\search_api\Query\QueryInterface $query
* Search API query object.
*/
protected function addSearchFacets(array &$params, QueryInterface $query) {
// Search API facets.
$facets = $query
->getOption('search_api_facets');
$index_fields = $this
->getIndexFields($query);
if (!empty($facets)) {
// Loop through facets.
foreach ($facets as $facet_id => $facet_info) {
$field_id = $facet_info['field'];
$facet = [
$field_id => [],
];
// Skip if not recognized as a known field.
if (!isset($index_fields[$field_id])) {
continue;
}
// TODO: missing function reference.
$field_type = search_api_extract_inner_type($index_fields[$field_id]['type']);
// TODO: handle different types (GeoDistance and so on). See the
// supportedFeatures todo.
if ($field_type === 'date') {
$facet_type = 'date_histogram';
$facet[$field_id] = $this
->createDateFieldFacet($field_id, $facet);
}
else {
$facet_type = 'terms';
$facet[$field_id][$facet_type]['all_terms'] = (bool) $facet_info['missing'];
}
// Add the facet.
if (!empty($facet[$field_id])) {
// Add facet options.
$facet_info['facet_type'] = $facet_type;
$facet[$field_id] = $this
->addFacetOptions($facet[$field_id], $query, $facet_info);
}
$params['body']['facets'][$field_id] = $facet[$field_id];
}
}
}
/**
* Helper function that adds options and returns facet.
*
* @param array $facet
* @param QueryInterface $query
* @param string $facet_info
*
* @return array
*/
protected function addFacetOptions(array &$facet, QueryInterface $query, $facet_info) {
$facet_limit = $this
->getFacetLimit($facet_info);
$facet_search_filter = $this
->getFacetSearchFilter($query, $facet_info);
// Set the field.
$facet[$facet_info['facet_type']]['field'] = $facet_info['field'];
// OR facet. We remove filters affecting the associated field.
// TODO: distinguish between normal filters and facet filters.
// See http://drupal.org/node/1390598.
// Filter the facet.
if (!empty($facet_search_filter)) {
$facet['facet_filter'] = $facet_search_filter;
}
// Limit the number of returned entries.
if ($facet_limit > 0 && $facet_info['facet_type'] == 'terms') {
$facet[$facet_info['facet_type']]['size'] = $facet_limit;
}
return $facet;
}
/**
* Helper function return Facet filter.
*
* @param QueryInterface $query
* @param array $facet_info
*
* @return array|null|string
*/
protected function getFacetSearchFilter(QueryInterface $query, array $facet_info) {
$index_fields = $this
->getIndexFields($query);
if (isset($facet_info['operator']) && Unicode::strtolower($facet_info['operator']) == 'or') {
$facet_search_filter = $this
->parseConditionGroup($query
->getConditionGroup(), $index_fields, $facet_info['field']);
if (!empty($facet_search_filter)) {
$facet_search_filter = $facet_search_filter[0];
}
}
else {
$facet_search_filter = $this
->parseConditionGroup($query
->getConditionGroup(), $index_fields);
if (!empty($facet_search_filter)) {
$facet_search_filter = $facet_search_filter[0];
}
}
return $facet_search_filter;
}
/**
* Helper function create Facet for date field type.
*
* @param mixed $facet_id
* @param array $facet
*
* @return array.
*/
protected function createDateFieldFacet($facet_id, array $facet) {
$result = $facet[$facet_id];
$date_interval = $this
->getDateFacetInterval($facet_id);
$result['date_histogram']['interval'] = $date_interval;
// TODO: Check the timezone cause this hardcoded way doesn't seem right.
$result['date_histogram']['time_zone'] = 'UTC';
// Use factor 1000 as we store dates as seconds from epoch
// not milliseconds.
$result['date_histogram']['factor'] = 1000;
return $result;
}
/**
* Helper function that return facet limits.
*
* @param array $facet_info
*
* @return int|null
*/
protected function getFacetLimit(array $facet_info) {
// If no limit (-1) is selected, use the server facet limit option.
$facet_limit = !empty($facet_info['limit']) ? $facet_info['limit'] : -1;
if ($facet_limit < 0) {
$facet_limit = $this
->getOption('facet_limit', 10);
}
return $facet_limit;
}
/**
* Helper function which add params to date facets.
*
* @param mixed $facet_id
*
* @return string
*/
protected function getDateFacetInterval($facet_id) {
// Active search corresponding to this index.
$searcher = key(facetapi_get_active_searchers());
// Get the FacetApiAdapter for this searcher.
$adapter = isset($searcher) ? facetapi_adapter_load($searcher) : NULL;
// Get the date granularity.
$date_gap = $this
->getDateGranularity($adapter, $facet_id);
switch ($date_gap) {
// Already a selected YEAR, we want the months.
case 'YEAR':
$date_interval = 'month';
break;
// Already a selected MONTH, we want the days.
case 'MONTH':
$date_interval = 'day';
break;
// Already a selected DAY, we want the hours and so on.
case 'DAY':
$date_interval = 'hour';
break;
// By default we return result counts by year.
default:
$date_interval = 'year';
}
return $date_interval;
}
/**
* Helper function to return date gap.
*
* @param $adapter
* @param $facet_id
*
* @return mixed|string
*/
public function getDateGranularity($adapter, $facet_id) {
// Date gaps.
$gap_weight = [
'YEAR' => 2,
'MONTH' => 1,
'DAY' => 0,
];
$gaps = [];
$date_gap = 'YEAR';
// Get the date granularity.
if (isset($adapter)) {
// Get the current date gap from the active date filters.
$active_items = $adapter
->getActiveItems([
'name' => $facet_id,
]);
if (!empty($active_items)) {
foreach ($active_items as $active_item) {
$value = $active_item['value'];
if (strpos($value, ' TO ') > 0) {
list($date_min, $date_max) = explode(' TO ', str_replace([
'[',
']',
], '', $value), 2);
$gap = self::getDateGap($date_min, $date_max, FALSE);
if (isset($gap_weight[$gap])) {
$gaps[] = $gap_weight[$gap];
}
}
}
if (!empty($gaps)) {
// Minimum gap.
$date_gap = array_search(min($gaps), $gap_weight);
}
}
}
return $date_gap;
}
/**
* Helper function that parse facets.
*
* @param array $response
* @param QueryInterface $query
*
* @return array
*/
protected function parseSearchFacets(array $response, QueryInterface $query) {
$result = [];
$index_fields = $this
->getIndexFields($query);
$facets = $query
->getOption('search_api_facets');
if (!empty($facets) && isset($response['facets'])) {
foreach ($response['facets'] as $facet_id => $facet_data) {
if (isset($facets[$facet_id])) {
$facet_info = $facets[$facet_id];
$facet_min_count = $facet_info['min_count'];
$field_id = $facet_info['field'];
$field_type = search_api_extract_inner_type($index_fields[$field_id]['type']);
// TODO: handle different types (GeoDistance and so on).
if ($field_type === 'date') {
foreach ($facet_data['entries'] as $entry) {
if ($entry['count'] >= $facet_min_count) {
// Divide time by 1000 as we want seconds from epoch
// not milliseconds.
$result[$facet_id][] = [
'count' => $entry['count'],
'filter' => '"' . $entry['time'] / 1000 . '"',
];
}
}
}
else {
foreach ($facet_data['terms'] as $term) {
if ($term['count'] >= $facet_min_count) {
$result[$facet_id][] = [
'count' => $term['count'],
'filter' => '"' . $term['term'] . '"',
];
}
}
}
}
}
}
return $result;
}
/**
* Allow custom changes before sending a search query to Elasticsearch.
*
* This allows subclasses to apply custom changes before the query is sent to
* Elasticsearch.
*
* @param \Drupal\search_api\Query\QueryInterface $query
* The \Drupal\search_api\Query\Query object representing the executed
* search query.
*/
protected function preQuery(QueryInterface $query) {
}
/**
* Allow custom changes before search results are returned for subclasses.
*
* @param \Drupal\search_api\Query\ResultSetInterface $results
* The results array that will be returned for the search.
* @param \Drupal\search_api\Query\QueryInterface $query
* The \Drupal\search_api\Query\Query object representing the executed
* search query.
* @param object $response
* The response object returned by Elasticsearch.
*/
protected function postQuery(ResultSetInterface $results, QueryInterface $query, $response) {
}
/* TODO: Implement the settings update feature. */
/**
* {@inheritdoc}
*/
public function supportsDataType($type) {
$data_types = $this
->getSupportedDataTypes();
return in_array($type, $data_types);
}
/**
* Implements __sleep()
*
* Prevents closure serialization error on search_api server add form
*/
public function __sleep() {
return array();
}
}
Members
Name![]() |
Modifiers | Type | Description | Overrides |
---|---|---|---|---|
BackendPluginBase:: |
protected | property | The fields helper. | |
BackendPluginBase:: |
protected | property |
The messenger. Overrides MessengerTrait:: |
|
BackendPluginBase:: |
protected | property | The server this backend is configured for. | |
BackendPluginBase:: |
protected | property | The backend's server's ID. | |
BackendPluginBase:: |
public | function |
Provides information on additional fields made available by the backend. Overrides BackendSpecificInterface:: |
|
BackendPluginBase:: |
public | function |
Limits the processors displayed in the UI for indexes on this server. Overrides BackendSpecificInterface:: |
1 |
BackendPluginBase:: |
public | function | Retrieves the fields helper. | |
BackendPluginBase:: |
public | function | Retrieves the messenger. | |
BackendPluginBase:: |
protected | function | Retrieves the effective fulltext fields from the query. | |
BackendPluginBase:: |
public | function |
Retrieves the server entity for this backend. Overrides BackendInterface:: |
|
BackendPluginBase:: |
protected | function | Creates dummy field objects for the "magic" fields present for every index. | 1 |
BackendPluginBase:: |
public | function |
Reacts to the server's creation. Overrides BackendInterface:: |
1 |
BackendPluginBase:: |
public | function |
Notifies the backend that its configuration was updated. Overrides BackendInterface:: |
2 |
BackendPluginBase:: |
public | function |
Notifies the backend that the server is about to be deleted. Overrides BackendInterface:: |
1 |
BackendPluginBase:: |
public | function |
Notifies the backend that its configuration is about to be updated. Overrides BackendInterface:: |
1 |
BackendPluginBase:: |
public | function |
Sets the configuration for this plugin instance. Overrides ConfigurablePluginBase:: |
|
BackendPluginBase:: |
public | function | Sets the fields helper. | |
BackendPluginBase:: |
public | function |
Sets the messenger. Overrides MessengerTrait:: |
|
BackendPluginBase:: |
public | function |
Sets the server entity for this backend. Overrides BackendInterface:: |
|
BackendPluginBase:: |
protected | function | Verifies that the given condition operator is valid for this backend. | |
BackendPluginBase:: |
public | function |
Implements the magic __wakeup() method. Overrides DependencySerializationTrait:: |
1 |
ConfigurablePluginBase:: |
public | function |
Calculates dependencies for the configured plugin. Overrides DependentPluginInterface:: |
6 |
ConfigurablePluginBase:: |
protected | function | Calculates and adds dependencies of a specific plugin instance. | |
ConfigurablePluginBase:: |
public | function |
Gets this plugin's configuration. Overrides ConfigurableInterface:: |
|
ConfigurablePluginBase:: |
public | function |
Returns the plugin's description. Overrides ConfigurablePluginInterface:: |
|
ConfigurablePluginBase:: |
protected | function | Calculates and returns dependencies of a specific plugin instance. | |
ConfigurablePluginBase:: |
public | function |
Returns the label for use on the administration pages. Overrides ConfigurablePluginInterface:: |
|
ConfigurablePluginBase:: |
protected | function | Wraps the module handler. | |
ConfigurablePluginBase:: |
public | function |
Informs the plugin that some of its dependencies are being removed. Overrides ConfigurablePluginInterface:: |
5 |
ConfigurablePluginBase:: |
protected | function | Wraps the theme handler. | |
DependencySerializationTrait:: |
protected | property | An array of entity type IDs keyed by the property name of their storages. | |
DependencySerializationTrait:: |
protected | property | An array of service IDs keyed by property name used for serialization. | |
DependencyTrait:: |
protected | property | The object's dependencies. | |
DependencyTrait:: |
protected | function | Adds multiple dependencies. | |
DependencyTrait:: |
protected | function | Adds a dependency. | |
HideablePluginBase:: |
public | function |
Determines whether this plugin should be hidden in the UI. Overrides HideablePluginInterface:: |
1 |
LoggerTrait:: |
public | function | Retrieves the logger. | |
LoggerTrait:: |
protected | function | Logs an exception. | |
LoggerTrait:: |
public | function | Sets the logger. | |
MessengerTrait:: |
public | function | Gets the messenger. | 29 |
PluginBase:: |
protected | property | Configuration information passed into the plugin. | 1 |
PluginBase:: |
protected | property | The plugin implementation definition. | 1 |
PluginBase:: |
protected | property | The plugin_id. | |
PluginBase:: |
constant | A string which is used to separate base plugin IDs from the derivative ID. | ||
PluginBase:: |
public | function |
Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface:: |
|
PluginBase:: |
public | function |
Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface:: |
|
PluginBase:: |
public | function |
Gets the definition of the plugin implementation. Overrides PluginInspectionInterface:: |
3 |
PluginBase:: |
public | function |
Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface:: |
|
PluginBase:: |
public | function | Determines if the plugin is configurable. | |
PluginDependencyTrait:: |
protected | function | Calculates and adds dependencies of a specific plugin instance. Aliased as: traitCalculatePluginDependencies | 1 |
PluginDependencyTrait:: |
protected | function | Calculates and returns dependencies of a specific plugin instance. Aliased as: traitGetPluginDependencies | |
PluginDependencyTrait:: |
protected | function | Wraps the module handler. Aliased as: traitModuleHandler | 1 |
PluginDependencyTrait:: |
protected | function | Wraps the theme handler. Aliased as: traitThemeHandler | 1 |
PluginFormTrait:: |
public | function | Form submission handler. | 7 |
PluginFormTrait:: |
public | function | Form validation handler. | 2 |
SearchApiElasticsearchBackend:: |
protected | property | Elasticsearch client. | |
SearchApiElasticsearchBackend:: |
protected | property | Client manager service. | |
SearchApiElasticsearchBackend:: |
protected | property | Cluster object. | |
SearchApiElasticsearchBackend:: |
protected | property | Cluster id. | |
SearchApiElasticsearchBackend:: |
protected | property | The cluster manager service. | |
SearchApiElasticsearchBackend:: |
protected | property | Elasticsearch settings. | |
SearchApiElasticsearchBackend:: |
protected | property | The entity type manager service. | |
SearchApiElasticsearchBackend:: |
protected | property | Form builder service. | |
SearchApiElasticsearchBackend:: |
protected | property | Elasticsearch index factory. | |
SearchApiElasticsearchBackend:: |
protected | property |
Logger. Overrides LoggerTrait:: |
|
SearchApiElasticsearchBackend:: |
protected | property | Module handler service. | |
SearchApiElasticsearchBackend:: |
protected | function | Helper function that adds options and returns facet. | |
SearchApiElasticsearchBackend:: |
protected | function | Fill the aggregation array of the request. | |
SearchApiElasticsearchBackend:: |
public | function |
Adds a new index to this server. Overrides BackendPluginBase:: |
|
SearchApiElasticsearchBackend:: |
protected | function | Helper function build facets in search. | |
SearchApiElasticsearchBackend:: |
public | function |
Form constructor. Overrides PluginFormInterface:: |
|
SearchApiElasticsearchBackend:: |
public static | function |
Creates an instance of the plugin. Overrides BackendPluginBase:: |
|
SearchApiElasticsearchBackend:: |
protected | function | Helper function create Facet for date field type. | |
SearchApiElasticsearchBackend:: |
public | function |
Gets default configuration for this plugin. Overrides ConfigurablePluginBase:: |
|
SearchApiElasticsearchBackend:: |
public | function |
Deletes all the items from the index. Overrides BackendSpecificInterface:: |
|
SearchApiElasticsearchBackend:: |
public | function |
Deletes the specified items from the index. Overrides BackendSpecificInterface:: |
|
SearchApiElasticsearchBackend:: |
protected | function | Helper function, check if the given index and type exists. | |
SearchApiElasticsearchBackend:: |
constant | Set a large integer to be the size for a "Hard limit" value of "No limit". | ||
SearchApiElasticsearchBackend:: |
public | function | Create or re-create the given index's field mapping. | |
SearchApiElasticsearchBackend:: |
constant | Auto fuzziness setting. | ||
SearchApiElasticsearchBackend:: |
public | function | Implements SearchApiAutocompleteInterface::getAutocompleteSuggestions(). | |
SearchApiElasticsearchBackend:: |
public | function | Get the configured cluster; if the cluster is blank, use the default. | |
SearchApiElasticsearchBackend:: |
protected | function | Helper function which add params to date facets. | |
SearchApiElasticsearchBackend:: |
protected static | function | Helper function. Return date gap from two dates or timestamps. | |
SearchApiElasticsearchBackend:: |
public | function | Helper function to return date gap. | |
SearchApiElasticsearchBackend:: |
protected | function | Helper function that return facet limits. | |
SearchApiElasticsearchBackend:: |
protected | function | Helper function return Facet filter. | |
SearchApiElasticsearchBackend:: |
public | function | Get the configured fuzziness value. | |
SearchApiElasticsearchBackend:: |
protected | function | Prefixes an index ID as configured. | |
SearchApiElasticsearchBackend:: |
public | function |
Get a list of supported data types. Overrides SearchApiElasticsearchBackendInterface:: |
|
SearchApiElasticsearchBackend:: |
public | function |
TODO: implement 'search_api_multi',
TODO: implement 'search_api_service_extra',
TODO: implement 'search_api_spellcheck',
TODO: implement 'search_api_data_type_location',
TODO: implement… Overrides BackendPluginBase:: |
|
SearchApiElasticsearchBackend:: |
public | function |
Indexes the specified items. Overrides BackendSpecificInterface:: |
|
SearchApiElasticsearchBackend:: |
public | function |
Returns a boolean with the availability of the backend. Overrides BackendPluginBase:: |
|
SearchApiElasticsearchBackend:: |
protected | function | Parse the result set and add the facet values. | |
SearchApiElasticsearchBackend:: |
protected | function | Helper function that parse facets. | |
SearchApiElasticsearchBackend:: |
protected | function | Allow custom changes before search results are returned for subclasses. | |
SearchApiElasticsearchBackend:: |
protected | function | Allow custom changes before sending a search query to Elasticsearch. | |
SearchApiElasticsearchBackend:: |
public | function |
Removes an index from this server. Overrides BackendPluginBase:: |
|
SearchApiElasticsearchBackend:: |
public | function |
Executes a search on this server. Overrides BackendSpecificInterface:: |
|
SearchApiElasticsearchBackend:: |
public | function |
Determines whether the backend supports a given add-on data type. Overrides BackendPluginBase:: |
|
SearchApiElasticsearchBackend:: |
public | function |
Notifies the server that an index attached to it has been changed. Overrides BackendPluginBase:: |
|
SearchApiElasticsearchBackend:: |
public | function |
Returns additional, backend-specific information about this server. Overrides BackendPluginBase:: |
|
SearchApiElasticsearchBackend:: |
public | function |
SearchApiElasticsearchBackend constructor. Overrides BackendPluginBase:: |
|
SearchApiElasticsearchBackend:: |
public | function |
Implements __sleep() Overrides BackendPluginBase:: |
|
StringTranslationTrait:: |
protected | property | The string translation service. | 1 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. |