View source
<?php
namespace Drupal\search_api_solr\Plugin\search_api\backend;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Config\Config;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Form\SubformState;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Plugin\PluginDependencyTrait;
use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\Core\TypedData\ComplexDataDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\Url;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
use Drupal\search_api\Item\Field;
use Drupal\search_api\Item\FieldInterface;
use Drupal\search_api\Item\ItemInterface;
use Drupal\search_api\Plugin\PluginFormTrait;
use Drupal\search_api\Plugin\search_api\data_type\value\TextValue;
use Drupal\search_api\Query\ConditionInterface;
use Drupal\search_api\SearchApiException;
use Drupal\search_api\IndexInterface;
use Drupal\search_api\Query\ConditionGroupInterface;
use Drupal\search_api\Query\QueryInterface;
use Drupal\search_api\Backend\BackendPluginBase;
use Drupal\search_api\Query\ResultSetInterface;
use Drupal\search_api\Utility\DataTypeHelperInterface;
use Drupal\search_api\Utility\FieldsHelperInterface;
use Drupal\search_api\Utility\Utility as SearchApiUtility;
use Drupal\search_api_autocomplete\SearchInterface;
use Drupal\search_api_autocomplete\Suggestion;
use Drupal\search_api_autocomplete\Suggestion\SuggestionFactory;
use Drupal\search_api_solr\SearchApiSolrException;
use Drupal\search_api_solr\SolrBackendInterface;
use Drupal\search_api_solr\SolrConnector\SolrConnectorPluginManager;
use Drupal\search_api_solr\Utility\Utility as SearchApiSolrUtility;
use Solarium\Core\Client\Response;
use Solarium\Core\Query\QueryInterface as SolariumQueryInterface;
use Solarium\Core\Query\Result\ResultInterface;
use Solarium\Exception\ExceptionInterface;
use Solarium\QueryType\Update\Query\Query as UpdateQuery;
use Solarium\QueryType\Select\Query\Query;
use Solarium\QueryType\Select\Result\Result;
use Solarium\QueryType\Suggester\Query as SuggesterQuery;
use Solarium\QueryType\Suggester\Result\Result as SuggesterResult;
use Solarium\QueryType\Update\Query\Document;
use Symfony\Component\DependencyInjection\ContainerInterface;
define('SEARCH_API_SOLR_MIN_SCHEMA_VERSION', 4);
define('SEARCH_API_ID_FIELD_NAME', 'ss_search_api_id');
class SearchApiSolrBackend extends BackendPluginBase implements SolrBackendInterface, PluginFormInterface {
use PluginDependencyTrait;
use PluginFormTrait {
submitConfigurationForm as traitSubmitConfigurationForm;
}
protected $fieldNames = array();
protected $moduleHandler;
protected $searchApiSolrSettings;
protected $languageManager;
protected $solrConnectorPluginManager;
protected $solrConnector;
protected $fieldsHelper;
protected $dataTypeHelper;
public function __construct(array $configuration, $plugin_id, array $plugin_definition, ModuleHandlerInterface $module_handler, Config $search_api_solr_settings, LanguageManagerInterface $language_manager, SolrConnectorPluginManager $solr_connector_plugin_manager, FieldsHelperInterface $fields_helper, DataTypeHelperInterface $dataTypeHelper) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->moduleHandler = $module_handler;
$this->searchApiSolrSettings = $search_api_solr_settings;
$this->languageManager = $language_manager;
$this->solrConnectorPluginManager = $solr_connector_plugin_manager;
$this->fieldsHelper = $fields_helper;
$this->dataTypeHelper = $dataTypeHelper;
}
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($configuration, $plugin_id, $plugin_definition, $container
->get('module_handler'), $container
->get('config.factory')
->get('search_api_solr.settings'), $container
->get('language_manager'), $container
->get('plugin.manager.search_api_solr.connector'), $container
->get('search_api.fields_helper'), $container
->get('search_api.data_type_helper'));
}
public function defaultConfiguration() {
return array(
'excerpt' => FALSE,
'retrieve_data' => FALSE,
'highlight_data' => FALSE,
'skip_schema_check' => FALSE,
'site_hash' => FALSE,
'suggest_suffix' => TRUE,
'suggest_corrections' => TRUE,
'suggest_words' => FALSE,
'connector' => NULL,
'connector_config' => [],
);
}
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
if (!$this->server
->isNew()) {
$form['server_description'] = array(
'#type' => 'item',
'#title' => $this
->t('Solr server URI'),
'#description' => $this
->getSolrConnector()
->getServerLink(),
);
}
$solr_connector_options = $this
->getSolrConnectorOptions();
$form['connector'] = array(
'#type' => 'radios',
'#title' => $this
->t('Solr Connector'),
'#description' => $this
->t('Choose a connector to use for this Solr server.'),
'#options' => $solr_connector_options,
'#default_value' => $this->configuration['connector'],
'#required' => TRUE,
'#ajax' => array(
'callback' => [
get_class($this),
'buildAjaxSolrConnectorConfigForm',
],
'wrapper' => 'search-api-solr-connector-config-form',
'method' => 'replace',
'effect' => 'fade',
),
);
$this
->buildConnectorConfigForm($form, $form_state);
$form['advanced'] = array(
'#type' => 'fieldset',
'#title' => $this
->t('Advanced'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['advanced']['retrieve_data'] = array(
'#type' => 'checkbox',
'#title' => $this
->t('Retrieve result data from Solr'),
'#description' => $this
->t('When checked, result data will be retrieved directly from the Solr server. This might make item loads unnecessary. Only indexed fields can be retrieved. Note also that the returned field data might not always be correct, due to preprocessing and caching issues.'),
'#default_value' => $this->configuration['retrieve_data'],
);
$form['advanced']['highlight_data'] = array(
'#type' => 'checkbox',
'#title' => $this
->t('Highlight retrieved data'),
'#description' => $this
->t('When retrieving result data from the Solr server, try to highlight the search terms in the returned fulltext fields.'),
'#default_value' => $this->configuration['highlight_data'],
);
$form['advanced']['excerpt'] = array(
'#type' => 'checkbox',
'#title' => $this
->t('Return an excerpt for all results'),
'#description' => $this
->t("If search keywords are given, use Solr's capabilities to create a highlighted search excerpt for each result. Whether the excerpts will actually be displayed depends on the settings of the search, though."),
'#default_value' => $this->configuration['excerpt'],
);
$form['advanced']['skip_schema_check'] = array(
'#type' => 'checkbox',
'#title' => $this
->t('Skip schema verification'),
'#description' => $this
->t('Skip the automatic check for schema-compatibillity. Use this override if you are seeing an error-message about an incompatible schema.xml configuration file, and you are sure the configuration is compatible.'),
'#default_value' => $this->configuration['skip_schema_check'],
);
$form['advanced']['highlight_data']['#states']['invisible'][':input[name="backend_config[advanced][retrieve_data]"]']['checked'] = FALSE;
if ($this->moduleHandler
->moduleExists('search_api_autocomplete')) {
$form['autocomplete'] = array(
'#type' => 'details',
'#title' => $this
->t('Autocomplete settings'),
'#description' => $this
->t('These settings allow you to configure how suggestions are computed when autocompletion is used. If you are seeing many inappropriate suggestions you might want to deactivate the corresponding suggestion type. You can also deactivate one method to speed up the generation of suggestions.'),
);
$form['autocomplete']['suggest_suffix'] = array(
'#type' => 'checkbox',
'#title' => $this
->t('Suggest word endings'),
'#description' => $this
->t('Suggest endings for the currently entered word.'),
'#default_value' => $this->configuration['suggest_suffix'],
);
$form['autocomplete']['suggest_corrections'] = array(
'#type' => 'checkbox',
'#title' => $this
->t('Suggest corrected words'),
'#description' => $this
->t('Suggest corrections for the currently entered words.'),
'#default_value' => $this->configuration['suggest_corrections'],
);
$form['autocomplete']['suggest_words'] = array(
'#type' => 'checkbox',
'#title' => $this
->t('Suggest additional words'),
'#description' => $this
->t('Suggest additional words the user might want to search for.'),
'#default_value' => $this->configuration['suggest_words'],
'#disabled' => TRUE,
);
}
$form['multisite'] = array(
'#type' => 'fieldset',
'#title' => $this
->t('Multi-site compatibility'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#description' => $this
->t("By default a single Solr backend based Search API server is able to index the data of multiple Drupal sites. But this is an expert-only and dangerous feature that mainly exists for backward compatibility. If you really index multiple sites in one index and don't activate 'Retrieve results for this site only' below you have to ensure that you enable 'Retrieve result data from Solr'! Otherwise it could lead to any kind of errors!"),
);
$description = $this
->t("Automatically filter all searches to only retrieve results from this Drupal site. The default and intended behavior is to display results from all sites. WARNING: Enabling this filter might break features like autocomplete, spell checking or suggesters!");
$form['multisite']['site_hash'] = array(
'#type' => 'checkbox',
'#title' => $this
->t('Retrieve results for this site only'),
'#description' => $description,
'#default_value' => $this->configuration['site_hash'],
);
return $form;
}
protected function getSolrConnectorOptions() {
$options = [];
foreach ($this->solrConnectorPluginManager
->getDefinitions() as $plugin_id => $plugin_definition) {
$options[$plugin_id] = Html::escape($plugin_definition['label']);
}
return $options;
}
public function buildConnectorConfigForm(array &$form, FormStateInterface $form_state) {
$form['connector_config'] = [];
$connector_id = $this->configuration['connector'];
if ($connector_id) {
$connector = $this->solrConnectorPluginManager
->createInstance($connector_id, $this->configuration['connector_config']);
if ($connector instanceof PluginFormInterface) {
$form_state
->set('connector', $connector_id);
if ($form_state
->isRebuilding()) {
$this
->messenger()
->addWarning('Please configure the selected Solr connector.');
}
$connector_form_state = SubformState::createForSubform($form['connector_config'], $form, $form_state);
$form['connector_config'] = $connector
->buildConfigurationForm($form['connector_config'], $connector_form_state);
$form['connector_config']['#type'] = 'details';
$form['connector_config']['#title'] = $this
->t('Configure %plugin Solr connector', array(
'%plugin' => $connector
->label(),
));
$form['connector_config']['#description'] = $connector
->getDescription();
$form['connector_config']['#open'] = TRUE;
}
}
$form['connector_config'] += [
'#type' => 'container',
];
$form['connector_config']['#attributes'] = [
'id' => 'search-api-solr-connector-config-form',
];
$form['connector_config']['#tree'] = TRUE;
}
public static function buildAjaxSolrConnectorConfigForm(array $form, FormStateInterface $form_state) {
return $form['backend_config']['connector_config'];
}
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
if ($form_state
->getValue('connector') != $form_state
->get('connector')) {
$new_connector = $this->solrConnectorPluginManager
->createInstance($form_state
->getValue('connector'));
if ($new_connector instanceof PluginFormInterface) {
$form_state
->setRebuild();
}
else {
$form_state
->setError($form['connector'], $this
->t('The connector could not be activated.'));
}
}
else {
$this->configuration['connector'] = $form_state
->get('connector');
$connector = $this
->getSolrConnector();
if ($connector instanceof PluginFormInterface) {
$connector_form_state = SubformState::createForSubform($form['connector_config'], $form, $form_state);
$connector
->validateConfigurationForm($form['connector_config'], $connector_form_state);
}
else {
$form_state
->setError($form['connector'], $this
->t('The connector could not be activated.'));
}
}
}
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->configuration['connector'] = $form_state
->get('connector');
$connector = $this
->getSolrConnector();
if ($connector instanceof PluginFormInterface) {
$connector_form_state = SubformState::createForSubform($form['connector_config'], $form, $form_state);
$connector
->submitConfigurationForm($form['connector_config'], $connector_form_state);
}
$values = $form_state
->getValues();
$values += $values['advanced'];
$values += $values['multisite'];
if (!empty($values['autocomplete'])) {
$values += $values['autocomplete'];
}
else {
$defaults = $this
->defaultConfiguration();
$values['suggest_suffix'] = $defaults['suggest_suffix'];
$values['suggest_corrections'] = $defaults['suggest_corrections'];
$values['suggest_words'] = $defaults['suggest_words'];
}
$values['highlight_data'] &= $values['retrieve_data'];
foreach ($values as $key => $value) {
$form_state
->setValue($key, $value);
}
$form_state
->unsetValue('advanced');
$form_state
->unsetValue('multisite');
$form_state
->unsetValue('autocomplete');
$form_state
->unsetValue('server_description');
$this
->traitSubmitConfigurationForm($form, $form_state);
\Drupal::state()
->delete('search_api_solr.endpoint.data');
}
public function getSolrConnector() {
if (!$this->solrConnector) {
if (!($this->solrConnector = $this->solrConnectorPluginManager
->createInstance($this->configuration['connector'], $this->configuration['connector_config']))) {
throw new SearchApiException("The Solr Connector with ID '{$this->configuration}['connector']' could not be retrieved.");
}
}
return $this->solrConnector;
}
public function isAvailable() {
$conn = $this
->getSolrConnector();
return $conn
->pingCore() !== FALSE;
}
public function getSupportedFeatures() {
return [
'search_api_autocomplete',
'search_api_facets',
'search_api_facets_operator_or',
'search_api_mlt',
'search_api_random_sort',
'search_api_data_type_location',
'search_api_grouping',
];
}
public function supportsDataType($type) {
return in_array($type, [
'location',
'rpt',
'solr_string_ngram',
'solr_string_storage',
'solr_text_ngram',
'solr_text_omit_norms',
'solr_text_phonetic',
'solr_text_unstemmed',
'solr_text_wstoken',
]);
}
public function viewSettings() {
$connector = $this
->getSolrConnector();
$info[] = [
'label' => $this
->t('Solr connector plugin'),
'info' => $connector
->label(),
];
$info[] = [
'label' => $this
->t('Solr server URI'),
'info' => $connector
->getServerLink(),
];
$info[] = [
'label' => $this
->t('Solr core URI'),
'info' => $connector
->getCoreLink(),
];
$info = array_merge($info, $connector
->viewSettings());
if ($this->server
->status()) {
$ping_server = $connector
->pingServer();
if ($ping_server) {
$msg = $this
->t('The Solr server could be reached.');
}
else {
$msg = $this
->t('The Solr server could not be reached or is protected by your service provider.');
}
$info[] = [
'label' => $this
->t('Server Connection'),
'info' => $msg,
'status' => $ping_server ? 'ok' : 'error',
];
$ping = $connector
->pingCore();
if ($ping) {
$msg = $this
->t('The Solr core could be accessed (latency: @millisecs ms).', [
'@millisecs' => $ping * 1000,
]);
}
else {
$msg = $this
->t('The Solr core could not be accessed. Further data is therefore unavailable.');
}
$info[] = [
'label' => $this
->t('Core Connection'),
'info' => $msg,
'status' => $ping ? 'ok' : 'error',
];
$version = $connector
->getSolrVersion();
$info[] = [
'label' => $this
->t('Configured Solr Version'),
'info' => $version,
'status' => version_compare($version, '0.0.0', '>') ? 'ok' : 'error',
];
if ($ping_server || $ping) {
$info[] = [
'label' => $this
->t('Detected Solr Version'),
'info' => $connector
->getSolrVersion(TRUE),
'status' => 'ok',
];
try {
$data = $connector
->getLuke();
if (isset($data['index']['numDocs'])) {
$stats_summary = $connector
->getStatsSummary();
$pending_msg = $stats_summary['@pending_docs'] ? $this
->t('(@pending_docs sent but not yet processed)', $stats_summary) : '';
$index_msg = $stats_summary['@index_size'] ? $this
->t('(@index_size on disk)', $stats_summary) : '';
$indexed_message = $this
->t('@num items @pending @index_msg', array(
'@num' => $data['index']['numDocs'],
'@pending' => $pending_msg,
'@index_msg' => $index_msg,
));
$info[] = [
'label' => $this
->t('Indexed'),
'info' => $indexed_message,
];
if (!empty($stats_summary['@deletes_total'])) {
$info[] = [
'label' => $this
->t('Pending Deletions'),
'info' => $stats_summary['@deletes_total'],
];
}
$info[] = [
'label' => $this
->t('Delay'),
'info' => $this
->t('@autocommit_time before updates are processed.', $stats_summary),
];
$status = 'ok';
if (empty($this->configuration['skip_schema_check'])) {
if (substr($stats_summary['@schema_version'], 0, 10) == 'search-api') {
$this
->messenger()
->addError('Your schema.xml version is too old. Please replace all configuration files with the ones packaged with this module and re-index you data.');
$status = 'error';
}
elseif (!preg_match('/drupal-[' . SEARCH_API_SOLR_MIN_SCHEMA_VERSION . '-9]\\./', $stats_summary['@schema_version'])) {
$variables['@url'] = Url::fromUri('internal:/' . drupal_get_path('module', 'search_api_solr') . '/INSTALL.txt')
->toString();
$message = $this
->t('You are using an incompatible schema.xml configuration file. Please follow the instructions in the <a href="@url">INSTALL.txt</a> file for setting up Solr.', $variables);
$this
->messenger()
->addError($message);
$status = 'error';
}
}
$info[] = [
'label' => $this
->t('Schema'),
'info' => $stats_summary['@schema_version'],
'status' => $status,
];
if (!empty($stats_summary['@core_name'])) {
$info[] = [
'label' => $this
->t('Solr Core Name'),
'info' => $stats_summary['@core_name'],
];
}
}
} catch (SearchApiException $e) {
$info[] = [
'label' => $this
->t('Additional information'),
'info' => $this
->t('An error occurred while trying to retrieve additional information from the Solr server: %msg', [
'%msg' => $e
->getMessage(),
]),
'status' => 'error',
];
}
}
}
if ($this->moduleHandler
->moduleExists('search_api_autocomplete')) {
$autocomplete_modes = [];
if ($this->configuration['suggest_suffix']) {
$autocomplete_modes[] = $this
->t('Suggest word endings');
}
if ($this->configuration['suggest_corrections']) {
$autocomplete_modes[] = $this
->t('Suggest corrected words');
}
if ($this->configuration['suggest_words']) {
$autocomplete_modes[] = $this
->t('Suggest additional words');
}
$info[] = [
'label' => $this
->t('Autocomplete suggestions'),
'info' => !empty($autocomplete_modes) ? implode('; ', $autocomplete_modes) : $this
->t('none'),
];
}
return $info;
}
public function updateIndex(IndexInterface $index) {
if ($this
->indexFieldsUpdated($index)) {
$index
->reindex();
}
}
protected function indexFieldsUpdated(IndexInterface $index) {
if (!isset($index->original)) {
return TRUE;
}
$original = $index->original;
$old_fields = $original
->getFields();
$new_fields = $index
->getFields();
if (!$old_fields && !$new_fields) {
return FALSE;
}
if (array_diff_key($old_fields, $new_fields) || array_diff_key($new_fields, $old_fields)) {
return TRUE;
}
$old_field_names = $this
->getSolrFieldNames($original, TRUE);
$new_field_names = $this
->getSolrFieldNames($index, TRUE);
return $old_field_names != $new_field_names;
}
public function removeIndex($index) {
if (is_object($index) && !$index
->isReadOnly()) {
$this
->deleteAllIndexItems($index);
}
}
public function indexItems(IndexInterface $index, array $items) {
$connector = $this
->getSolrConnector();
$update_query = $connector
->getUpdateQuery();
$documents = $this
->getDocuments($index, $items, $update_query);
if (!$documents) {
return [];
}
try {
$update_query
->addDocuments($documents);
$connector
->update($update_query);
$ret = [];
foreach ($documents as $document) {
$ret[] = $document
->getFields()[SEARCH_API_ID_FIELD_NAME];
}
return $ret;
} catch (\Exception $e) {
watchdog_exception('search_api_solr', $e, "%type while indexing: @message in %function (line %line of %file).");
throw new SearchApiException($e
->getMessage(), $e
->getCode(), $e);
}
}
public function getDocument(IndexInterface $index, ItemInterface $item) {
$documents = $this
->getDocuments($index, [
$item
->getId() => $item,
]);
return reset($documents);
}
public function getDocuments(IndexInterface $index, array $items, UpdateQuery $update_query = NULL) {
$connector = $this
->getSolrConnector();
$schema_version = $connector
->getSchemaVersion();
$documents = array();
$index_id = $this
->getIndexId($index
->id());
$field_names = $this
->getSolrFieldNames($index);
$languages = $this->languageManager
->getLanguages();
$base_urls = array();
if (!$update_query) {
$update_query = $connector
->getUpdateQuery();
}
foreach ($items as $id => $item) {
$doc = $update_query
->createDocument();
$doc
->setField('id', $this
->createId($index_id, $id));
$doc
->setField('index_id', $index_id);
if ($boost = $item
->getBoost()) {
$doc
->setBoost($boost);
}
$doc
->setField('hash', SearchApiSolrUtility::getSiteHash());
$lang = $item
->getLanguage();
if (empty($base_urls[$lang])) {
$url_options = array(
'absolute' => TRUE,
);
if (isset($languages[$lang])) {
$url_options['language'] = $languages[$lang];
}
$base_urls[$lang] = Url::fromRoute('<front>', array(), $url_options)
->toString(TRUE)
->getGeneratedUrl();
}
$doc
->setField('site', $base_urls[$lang]);
$item_fields = $item
->getFields();
$item_fields += $special_fields = $this
->getSpecialFields($index, $item);
foreach ($item_fields as $name => $field) {
if (!isset($field_names[$name])) {
$vars = array(
'%field' => $name,
'@id' => $id,
);
\Drupal::logger('search_api_solr')
->warning('Error while indexing: Unknown field %field on the item with ID @id.', $vars);
$doc = NULL;
break;
}
$this
->addIndexField($doc, $field_names[$name], $field
->getValues(), $field
->getType());
if (!array_key_exists($name, $special_fields) && version_compare($schema_version, '4.4', '>=')) {
$values = $field
->getValues();
$first_value = reset($values);
if ($first_value) {
if ($first_value instanceof TextValue && mb_strlen($first_value
->getText()) > 32) {
$first_value = new TextValue(Unicode::truncate($first_value
->getText(), 32));
}
if (strpos($field_names[$name], 't') === 0 || strpos($field_names[$name], 's') === 0) {
$this
->addIndexField($doc, 'sort_' . $name, [
$first_value,
], $field
->getType());
}
elseif (preg_match('/^([a-z]+)m(_.*)/', $field_names[$name], $matches)) {
$values = $field
->getValues();
$this
->addIndexField($doc, $matches[1] . 's' . $matches[2], [
$first_value,
], $field
->getType());
}
}
}
}
if ($doc) {
$documents[] = $doc;
}
}
$this->moduleHandler
->alter('search_api_solr_documents', $documents, $index, $items);
$this
->alterSolrDocuments($documents, $index, $items);
return $documents;
}
public function deleteItems(IndexInterface $index, array $ids) {
try {
$index_id = $this
->getIndexId($index
->id());
$solr_ids = array();
foreach ($ids as $id) {
$solr_ids[] = $this
->createId($index_id, $id);
}
$connector = $this
->getSolrConnector();
$update_query = $connector
->getUpdateQuery();
$update_query
->addDeleteByIds($solr_ids);
$connector
->update($update_query);
} catch (ExceptionInterface $e) {
throw new SearchApiSolrException($e
->getMessage(), $e
->getCode(), $e);
}
}
public function deleteAllIndexItems(IndexInterface $index, $datasource_id = NULL) {
$connector = $this
->getSolrConnector();
$query_helper = $connector
->getQueryHelper();
$query = '+index_id:' . $this
->getIndexId($query_helper
->escapePhrase($index
->id()));
$query .= ' +hash:' . $query_helper
->escapePhrase(SearchApiSolrUtility::getSiteHash());
if ($datasource_id) {
$query .= ' +' . $this
->getSolrFieldNames($index)['search_api_datasource'] . ':' . $query_helper
->escapePhrase($datasource_id);
}
$update_query = $connector
->getUpdateQuery();
$update_query
->addDeleteQuery($query);
$connector
->update($update_query);
}
public function search(QueryInterface $query) {
$mlt_options = $query
->getOption('search_api_mlt');
if (!empty($mlt_options)) {
$query
->addTag('mlt');
}
$this
->alterSearchApiQuery($query);
$index = $query
->getIndex();
$index_id = $this
->getIndexId($index
->id());
$field_names = $this
->getSolrFieldNames($index);
$connector = $this
->getSolrConnector();
$solarium_query = NULL;
$index_fields = $index
->getFields();
$index_fields += $this
->getSpecialFields($index);
if ($query
->hasTag('mlt')) {
$solarium_query = $this
->getMoreLikeThisQuery($query, $index_id, $index_fields, $field_names);
}
else {
$solarium_query = $connector
->getSelectQuery();
$keys = $query
->getKeys();
if (is_array($keys)) {
$keys = $this
->flattenKeys($keys);
}
if (!empty($keys)) {
$solarium_query
->setQuery($keys);
}
$search_fields = $this
->getQueryFulltextFields($query);
$query_fields = [];
$query_fields_boosted = [];
foreach ($search_fields as $search_field) {
$query_fields[] = $field_names[$search_field];
$field = $index_fields[$search_field];
$boost = $field
->getBoost() ? '^' . $field
->getBoost() : '';
$query_fields_boosted[] = $field_names[$search_field] . $boost;
}
$solarium_query
->getEDisMax()
->setQueryFields(implode(' ', $query_fields_boosted));
$this
->setHighlighting($solarium_query, $query, $query_fields);
}
$options = $query
->getOptions();
$filter_queries = $this
->getFilterQueries($query, $field_names, $index_fields, $options);
foreach ($filter_queries as $id => $filter_query) {
$solarium_query
->createFilterQuery('filters_' . $id)
->setQuery($filter_query['query'])
->addTags($filter_query['tags']);
}
$query_helper = $connector
->getQueryHelper($solarium_query);
$solarium_query
->createFilterQuery('index_id')
->setQuery('index_id:' . $query_helper
->escapePhrase($index_id));
if ($this->configuration['site_hash']) {
$site_hash = $query_helper
->escapePhrase(SearchApiSolrUtility::getSiteHash());
$solarium_query
->createFilterQuery('site_hash')
->setQuery('hash:' . $site_hash);
}
if (!empty($this->configuration['retrieve_data'])) {
$solarium_query
->setFields([
'*',
'score',
]);
}
else {
$returned_fields = [
$field_names['search_api_id'],
$field_names['search_api_language'],
$field_names['search_api_relevance'],
];
if (!$this->configuration['site_hash']) {
$returned_fields[] = 'hash';
}
$solarium_query
->setFields($returned_fields);
}
$this
->setSorts($solarium_query, $query, $field_names);
$this
->setFacets($query, $solarium_query, $field_names);
if (isset($options['search_api_location'])) {
$this
->setSpatial($solarium_query, $options['search_api_location'], $field_names);
}
if (isset($options['search_api_rpt'])) {
if (version_compare($connector
->getSolrVersion(), 5.1, '>=')) {
$this
->setRpt($solarium_query, $options['search_api_rpt'], $field_names);
}
else {
\Drupal::logger('search_api_solr')
->error('Rpt data type feature is only supported by Solr version 5.1 or higher.');
}
}
$grouping_options = $query
->getOption('search_api_grouping');
if (!empty($grouping_options['use_grouping'])) {
$this
->setGrouping($solarium_query, $query, $grouping_options, $index_fields, $field_names);
}
if (isset($options['offset'])) {
$solarium_query
->setStart($options['offset']);
}
$rows = isset($options['limit']) ? $options['limit'] : 1000000;
$solarium_query
->setRows($rows);
if (!empty($options['search_api_spellcheck'])) {
$solarium_query
->getSpellcheck();
}
foreach ($options as $option => $value) {
if (strpos($option, 'solr_param_') === 0) {
$solarium_query
->addParam(substr($option, 11), $value);
}
}
$this
->applySearchWorkarounds($solarium_query, $query);
try {
$this->moduleHandler
->alter('search_api_solr_query', $solarium_query, $query);
$this
->preQuery($solarium_query, $query);
$response = $connector
->search($solarium_query);
$body = $response
->getBody();
if (200 != $response
->getStatusCode()) {
throw new SearchApiSolrException(strip_tags($body), $response
->getStatusCode());
}
$this
->alterSolrResponseBody($body, $query);
$response = new Response($body, $response
->getHeaders());
$result = $connector
->createSearchResult($solarium_query, $response);
$results = $this
->extractResults($query, $result);
if (!empty($warnings)) {
foreach ($warnings as $warning) {
$results
->addWarning($warning);
}
}
if ($result instanceof Result) {
if ($facets = $this
->extractFacets($query, $result, $field_names)) {
$results
->setExtraData('search_api_facets', $facets);
}
}
$this->moduleHandler
->alter('search_api_solr_search_results', $results, $query, $result);
$this
->postQuery($results, $query, $result);
} catch (\Exception $e) {
throw new SearchApiSolrException($this
->t('An error occurred while trying to search with Solr: @msg.', array(
'@msg' => $e
->getMessage(),
)), $e
->getCode(), $e);
}
}
protected function applySearchWorkarounds(Query $solarium_query, QueryInterface $query) {
if ($query
->hasTag('server_index_status')) {
return;
}
$connector = $this
->getSolrConnector();
$schema_version = $connector
->getSchemaVersion();
$solr_version = $connector
->getSolrVersion();
if (version_compare($schema_version, '4.4', '<')) {
$params = $solarium_query
->getParams();
if (!isset($params['q.op'])) {
$solarium_query
->addParam('q.op', 'OR');
}
}
}
protected function createId($index_id, $item_id) {
return SearchApiSolrUtility::getSiteHash() . "-{$index_id}-{$item_id}";
}
public function getSolrFieldNames(IndexInterface $index, $reset = FALSE) {
if (!isset($this->fieldNames[$index
->id()]) || $reset) {
$ret = array(
'search_api_relevance' => 'score',
'search_api_random' => 'random',
);
$fields = $index
->getFields();
$fields += $this
->getSpecialFields($index);
foreach ($fields as $key => $field) {
if (empty($ret[$key])) {
$type = $field
->getType();
$type_info = SearchApiSolrUtility::getDataTypeInfo($type);
$pref = isset($type_info['prefix']) ? $type_info['prefix'] : '';
if ($this->fieldsHelper
->isFieldIdReserved($key)) {
$pref .= 's';
}
else {
if ($field
->getDataDefinition()
->isList() || $this
->isHierarchicalField($field)) {
$pref .= 'm';
}
else {
try {
$datasource = $field
->getDatasource();
if (!$datasource) {
throw new SearchApiException();
}
else {
$pref .= $this
->getPropertyPathCardinality($field
->getPropertyPath(), $datasource
->getPropertyDefinitions()) != 1 ? 'm' : 's';
}
} catch (SearchApiException $e) {
$pref .= 'm';
}
}
}
$name = $pref . '_' . $key;
$ret[$key] = SearchApiSolrUtility::encodeSolrName($name);
if ($type == 'location') {
$ret[$key . '__distance'] = SearchApiSolrUtility::encodeSolrName($name . '__distance');
}
}
}
$this->moduleHandler
->alter('search_api_solr_field_mapping', $index, $ret);
$this->fieldNames[$index
->id()] = $ret;
}
return $this->fieldNames[$index
->id()];
}
protected function getPropertyPathCardinality($property_path, array $properties, $cardinality = 1) {
[
$key,
$nested_path,
] = SearchApiUtility::splitPropertyPath($property_path, FALSE);
if (isset($properties[$key])) {
$property = $properties[$key];
if ($property instanceof FieldDefinitionInterface) {
$storage = $property
->getFieldStorageDefinition();
if ($storage instanceof FieldStorageDefinitionInterface) {
if ($storage
->getCardinality() == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
return FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED;
}
$cardinality *= $storage
->getCardinality();
}
}
if (isset($nested_path)) {
$property = $this->fieldsHelper
->getInnerProperty($property);
if ($property instanceof ComplexDataDefinitionInterface) {
$cardinality = $this
->getPropertyPathCardinality($nested_path, $this->fieldsHelper
->getNestedProperties($property), $cardinality);
}
}
}
return $cardinality;
}
protected function isHierarchicalField(FieldInterface $field) {
$definition = $field
->getDataDefinition();
if ($definition instanceof ComplexDataDefinitionInterface) {
$properties = $this->fieldsHelper
->getNestedProperties($definition);
$properties[''] = $definition;
foreach ($properties as $property) {
$property = $this->fieldsHelper
->getInnerProperty($property);
if ($property instanceof EntityDataDefinitionInterface) {
if ($this
->hasHierarchicalProperties($property)) {
return TRUE;
}
}
}
}
return FALSE;
}
protected function hasHierarchicalProperties(EntityDataDefinitionInterface $property) {
$entity_type_id = $property
->getEntityTypeId();
foreach ($this->fieldsHelper
->getNestedProperties($property) as $name_2 => $property_2) {
$property_2 = $this->fieldsHelper
->getInnerProperty($property_2);
if ($property_2 instanceof EntityDataDefinitionInterface) {
if ($property_2
->getEntityTypeId() == $entity_type_id) {
return TRUE;
}
}
elseif ($property_2 instanceof ComplexDataDefinitionInterface) {
foreach ($property_2
->getPropertyDefinitions() as $property_3) {
$property_3 = $this->fieldsHelper
->getInnerProperty($property_3);
if ($property_3 instanceof EntityDataDefinitionInterface) {
if ($property_3
->getEntityTypeId() == $entity_type_id) {
return TRUE;
}
}
}
}
}
return FALSE;
}
protected function addIndexField(Document $doc, $key, array $values, $type) {
if (!isset($values)) {
return;
}
foreach ($values as $value) {
switch ($type) {
case 'boolean':
$value = $value ? 'true' : 'false';
break;
case 'date':
$value = $this
->formatDate($value);
if ($value === FALSE) {
return;
}
break;
case 'integer':
$value = (int) $value;
break;
case 'decimal':
$value = (double) $value;
break;
case 'text':
$value = $value
->toText();
break;
case 'string':
default:
}
$doc
->addField($key, $value);
}
}
protected function alterSolrDocuments(array &$documents, IndexInterface $index, array $items) {
}
protected function extractResults(QueryInterface $query, ResultInterface $result) {
$index = $query
->getIndex();
$backend_config = $index
->getServerInstance()
->getBackendConfig();
$field_names = $this
->getSolrFieldNames($index);
$fields = $index
->getFields(TRUE);
$site_hash = SearchApiSolrUtility::getSiteHash();
$id_field = $field_names['search_api_id'];
$score_field = $field_names['search_api_relevance'];
$language_field = $field_names['search_api_language'];
$result_set = $query
->getResults();
$result_set
->setExtraData('search_api_solr_response', $result
->getData());
$is_grouping = $result instanceof Result && $result
->getGrouping();
if (!$result
->getResponse() && !$is_grouping) {
$result_set
->setResultCount(0);
return $result_set;
}
$grouping = $query
->getOption('search_api_grouping');
if (!empty($grouping['use_grouping'])) {
$docs = [];
$resultCount = 0;
if ($result_set
->hasExtraData('search_api_solr_response')) {
$response = $result_set
->getExtraData('search_api_solr_response');
foreach ($grouping['fields'] as $field) {
$solr_field_name = $field_names[$field];
if (!empty($response['grouped'][$solr_field_name])) {
$resultCount = count($response['grouped'][$solr_field_name]);
foreach ($response['grouped'][$solr_field_name]['groups'] as $group) {
foreach ($group['doclist']['docs'] as $doc) {
$docs[] = $doc;
}
}
}
}
$result_set
->setResultCount($resultCount);
if (count($grouping['fields']) == 1) {
$field = reset($grouping['fields']);
$solr_field_name = $field_names[$field];
if (isset($response['grouped'][$solr_field_name]['ngroups'])) {
$result_set
->setResultCount($response['grouped'][$solr_field_name]['ngroups']);
}
}
}
}
else {
$result_set
->setResultCount($result
->getNumFound());
$docs = $result
->getDocuments();
}
foreach ($docs as $doc) {
if (is_array($doc)) {
$doc_fields = $doc;
}
else {
$doc_fields = $doc
->getFields();
}
if (empty($doc_fields[$id_field])) {
throw new SearchApiSolrException(sprintf('The result does not contain the essential ID field "%s".', $id_field));
}
$item_id = $doc_fields[$id_field];
if (!$this->configuration['site_hash'] && $doc_fields['hash'] != $site_hash) {
$item_id = $doc_fields['hash'] . '--' . $item_id;
}
$result_item = $this->fieldsHelper
->createItem($index, $item_id);
$result_item
->setExtraData('search_api_solr_document', $doc);
$result_item
->setLanguage($doc_fields[$language_field]);
if (isset($doc_fields[$score_field])) {
$result_item
->setScore($doc_fields[$score_field]);
unset($doc_fields[$score_field]);
}
unset($doc_fields[$id_field]);
foreach ($field_names as $search_api_property => $solr_property) {
if (isset($doc_fields[$solr_property]) && isset($fields[$search_api_property])) {
$doc_field = is_array($doc_fields[$solr_property]) ? $doc_fields[$solr_property] : [
$doc_fields[$solr_property],
];
$field = clone $fields[$search_api_property];
foreach ($doc_field as &$value) {
switch ($field
->getType()) {
case 'date':
if (preg_match('/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$/', $value)) {
$value = strtotime($value);
}
break;
case 'text':
$value = new TextValue($value);
}
}
$field
->setValues($doc_field);
$result_item
->setField($search_api_property, $field);
}
}
$solr_id = $this
->createId($index
->id(), $result_item
->getId());
if ($excerpt = $this
->getExcerpt($result
->getData(), $solr_id, $result_item, $field_names)) {
$result_item
->setExcerpt($excerpt);
}
$result_set
->addResultItem($result_item);
}
return $result_set;
}
protected function extractFacets(QueryInterface $query, Result $resultset, array $field_names) {
if (!$resultset
->getFacetSet()) {
return [];
}
$facets = [];
$index = $query
->getIndex();
$fields = $index
->getFields();
$extract_facets = $query
->getOption('search_api_facets', []);
if ($facet_fields = $resultset
->getFacetSet()
->getFacets()) {
foreach ($extract_facets as $delta => $info) {
$field = $field_names[$info['field']];
if (!empty($facet_fields[$field])) {
$min_count = $info['min_count'];
$terms = $facet_fields[$field]
->getValues();
if ($info['missing']) {
if (isset($terms[''])) {
if ($terms[''] < $min_count) {
unset($terms['']);
}
else {
arsort($terms);
if ($info['limit'] > 0 && count($terms) > $info['limit']) {
array_pop($terms);
}
}
}
}
elseif (isset($terms[''])) {
unset($terms['']);
}
$type = isset($fields[$info['field']]) ? $fields[$info['field']]
->getType() : 'string';
foreach ($terms as $term => $count) {
if ($count >= $min_count) {
if ($term === '') {
$term = '!';
}
elseif ($type == 'boolean') {
if ($term == 'true') {
$term = '"1"';
}
elseif ($term == 'false') {
$term = '"0"';
}
}
elseif ($type == 'date') {
$term = $term ? '"' . strtotime($term) . '"' : NULL;
}
else {
$term = "\"{$term}\"";
}
if ($term) {
$facets[$delta][] = array(
'filter' => $term,
'count' => $count,
);
}
}
}
if (empty($facets[$delta])) {
unset($facets[$delta]);
}
}
}
}
$result_data = $resultset
->getData();
if (isset($result_data['facet_counts']['facet_queries'])) {
if ($spatials = $query
->getOption('search_api_location')) {
foreach ($result_data['facet_counts']['facet_queries'] as $key => $count) {
if (!preg_match('/^spatial-(.*)-(\\d+(?:\\.\\d+)?)-(\\d+(?:\\.\\d+)?)$/', $key, $matches)) {
continue;
}
if (empty($extract_facets[$matches[1]])) {
continue;
}
$facet = $extract_facets[$matches[1]];
if ($count >= $facet['min_count']) {
$facets[$matches[1]][] = [
'filter' => "[{$matches[2]} {$matches[3]}]",
'count' => $count,
];
}
}
}
}
if (isset($result_data['facet_counts']['facet_heatmaps'])) {
if ($spatials = $query
->getOption('search_api_rpt')) {
foreach ($result_data['facet_counts']['facet_heatmaps'] as $key => $value) {
if (!preg_match('/^rpts_(.*)$/', $key, $matches)) {
continue;
}
if (empty($extract_facets[$matches[1]])) {
continue;
}
$heatmaps = array_slice($value, 15);
array_walk_recursive($heatmaps, function ($heatmaps) use (&$heatmap) {
$heatmap[] = $heatmaps;
});
$count = array_sum($heatmap);
$facets[$matches[1]][] = [
'filter' => $value,
'count' => $count,
];
}
}
}
return $facets;
}
protected function addLanguageConditions(ConditionGroupInterface $condition_group, QueryInterface $query) {
$languages = $query
->getLanguages();
if ($languages !== NULL) {
$condition_group
->addCondition('search_api_language', $languages, 'IN');
}
}
protected function getFilterQueries(QueryInterface $query, array $solr_fields, array $index_fields, array &$options) {
$condition_group = $query
->getConditionGroup();
$this
->addLanguageConditions($condition_group, $query);
return $this
->createFilterQueries($condition_group, $solr_fields, $index_fields, $options);
}
protected function createFilterQueries(ConditionGroupInterface $condition_group, array $solr_fields, array $index_fields, array &$options) {
$fq = [];
$conditions = $condition_group
->getConditions();
foreach ($conditions as $condition) {
if ($condition instanceof ConditionInterface) {
$field = $condition
->getField();
if (!isset($index_fields[$field])) {
throw new SearchApiException($this
->t('Filter term on unknown or unindexed field @field.', array(
'@field' => $field,
)));
}
$value = $condition
->getValue();
$filter_query = $this
->createFilterQuery($solr_fields[$field], $value, $condition
->getOperator(), $index_fields[$field], $options);
if ($filter_query) {
$fq[] = [
'query' => $this
->createFilterQuery($solr_fields[$field], $value, $condition
->getOperator(), $index_fields[$field], $options),
'tags' => $condition_group
->getTags(),
];
}
}
else {
$nested_fqs = $this
->createFilterQueries($condition, $solr_fields, $index_fields, $options);
$fq = array_merge($fq, $this
->reduceFilterQueries($nested_fqs, $condition));
}
}
return $fq;
}
protected function reduceFilterQueries(array $filter_queries, ConditionGroupInterface $condition_group, $last = FALSE) {
$fq = [];
if (count($filter_queries) > 1) {
$queries = [];
$tags = [];
$pre = $condition_group
->getConjunction() == 'OR' ? '' : '+';
foreach ($filter_queries as $nested_fq) {
if (strpos($nested_fq['query'], '-') !== 0) {
$queries[] = $pre . $nested_fq['query'];
}
elseif (!$pre) {
$queries[] = '(' . $nested_fq['query'] . ')';
}
else {
$queries[] = $nested_fq['query'];
}
$tags += $nested_fq['tags'];
}
$fq[] = [
'query' => (!$last ? '(' : '') . implode(' ', $queries) . (!$last ? ')' : ''),
'tags' => array_unique($tags + $condition_group
->getTags()),
];
}
elseif (!empty($filter_queries)) {
$fq[] = [
'query' => $filter_queries[0]['query'],
'tags' => array_unique($filter_queries[0]['tags'] + $condition_group
->getTags()),
];
}
return $fq;
}
protected function createFilterQuery($field, $value, $operator, FieldInterface $index_field, array &$options) {
if (!is_array($value)) {
$value = [
$value,
];
}
foreach ($value as &$v) {
if (!is_null($v) || !in_array($operator, [
'=',
'<>',
'IN',
'NOT IN',
])) {
$v = trim($v);
$v = $this
->formatFilterValue($v, $index_field
->getType());
}
}
unset($v);
if (1 == count($value)) {
$value = array_shift($value);
switch ($operator) {
case 'IN':
$operator = '=';
break;
case 'NOT IN':
$operator = '<>';
break;
}
}
if (!is_null($value) && isset($options['search_api_location'])) {
foreach ($options['search_api_location'] as &$spatial) {
if (!empty($spatial['field']) && $index_field
->getFieldIdentifier() == $spatial['field']) {
$spatial['filter_query_conditions'] = [
'field' => $field,
'value' => $value,
'operator' => $operator,
];
return NULL;
}
}
}
switch ($operator) {
case '<>':
if (is_null($value)) {
return "{$field}:[* TO *]";
}
else {
return "(*:* -{$field}:{$value})";
}
case '<':
return "{$field}:{* TO {$value}}";
case '<=':
return "{$field}:[* TO {$value}]";
case '>=':
return "{$field}:[{$value} TO *]";
case '>':
return "{$field}:{{$value} TO *}";
case 'BETWEEN':
return "{$field}:[" . array_shift($value) . ' TO ' . array_shift($value) . ']';
case 'NOT BETWEEN':
return "(*:* -{$field}:[" . array_shift($value) . ' TO ' . array_shift($value) . '])';
case 'IN':
$parts = [];
$null = FALSE;
foreach ($value as $v) {
if (is_null($v)) {
$null = TRUE;
}
else {
$parts[] = "{$field}:{$v}";
}
}
if ($null) {
return "(*:* -{$field}:[* TO *])";
}
return '(' . implode(" ", $parts) . ')';
case 'NOT IN':
$parts = [];
$null = FALSE;
foreach ($value as $v) {
if (is_null($v)) {
$null = TRUE;
}
else {
$parts[] = "-{$field}:{$v}";
}
}
return '(' . ($null ? "{$field}:[* TO *]" : '*:*') . ' ' . implode(" ", $parts) . ')';
case '=':
default:
if (is_null($value)) {
return "(*:* -{$field}:[* TO *])";
}
else {
return "{$field}:{$value}";
}
}
}
protected function createLocationFilterQuery(&$spatial) {
$spatial_method = isset($spatial['method']) && in_array($spatial['method'], [
'geofilt',
'bbox',
]) ? $spatial['method'] : 'geofilt';
$value = $spatial['filter_query_conditions']['value'];
switch ($spatial['filter_query_conditions']['operator']) {
case '<':
case '<=':
$spatial['radius'] = $value;
return '{!' . $spatial_method . '}';
case '>':
case '>=':
$spatial['min_radius'] = $value;
return "{!frange l={$value}}geodist()";
case 'BETWEEN':
$spatial['min_radius'] = array_shift($value);
$spatial['radius'] = array_shift($value);
return '{!frange l=' . $spatial['min_radius'] . ' u=' . $spatial['radius'] . '}geodist()';
case '=':
case '<>':
case 'NOT BETWEEN':
case 'IN':
case 'NOT IN':
default:
throw new SearchApiSolrException('Unsupported operator for location queries');
}
}
protected function formatFilterValue($value, $type) {
switch ($type) {
case 'boolean':
$value = $value ? 'true' : 'false';
break;
case 'date':
$value = $this
->formatDate($value);
if ($value === FALSE) {
return 0;
}
break;
case 'location':
return (double) $value;
}
return $this
->getSolrConnector()
->getQueryHelper()
->escapePhrase($value);
}
protected function formatDate($input) {
$input = is_numeric($input) ? (int) $input : new \DateTime($input, timezone_open(DateTimeItemInterface::STORAGE_TIMEZONE));
return $this
->getSolrConnector()
->getQueryHelper()
->formatDate($input);
}
protected function setFacets(QueryInterface $query, Query $solarium_query, array $field_names) {
$facets = $query
->getOption('search_api_facets', []);
if (empty($facets)) {
return;
}
$facet_set = $solarium_query
->getFacetSet();
$facet_set
->setSort('count');
$facet_set
->setLimit(10);
$facet_set
->setMinCount(1);
$facet_set
->setMissing(FALSE);
foreach ($facets as $info) {
if (empty($field_names[$info['field']])) {
continue;
}
$field = $field_names[$info['field']];
$facet_field = $facet_set
->createFacetField($field)
->setField($field);
if (isset($info['operator']) && strtolower($info['operator']) === 'or') {
$facet_field
->getLocalParameters()
->clearExcludes()
->addExcludes([
'facet:' . $info['field'],
]);
}
if ($info['limit'] != 10) {
$limit = $info['limit'] ? $info['limit'] : -1;
$facet_field
->setLimit($limit);
}
if ($info['min_count'] != 1) {
$facet_field
->setMinCount($info['min_count']);
}
if ($info['missing']) {
$facet_field
->setMissing(TRUE);
}
else {
$facet_field
->setMissing(FALSE);
}
}
}
protected function alterSearchApiQuery(QueryInterface $query) {
}
protected function preQuery(SolariumQueryInterface $solarium_query, QueryInterface $query) {
}
protected function postQuery(ResultSetInterface $results, QueryInterface $query, $response) {
}
protected function alterSolrResponseBody(&$body, QueryInterface $query) {
}
public function getAutocompleteSuggestions(QueryInterface $query, SearchInterface $search, $incomplete_key, $user_input) {
$suggestions = [];
$factory = NULL;
if (class_exists(SuggestionFactory::class)) {
$factory = new SuggestionFactory($user_input);
}
if ($this->configuration['suggest_suffix'] || $this->configuration['suggest_corrections'] || $this->configuration['suggest_words']) {
$connector = $this
->getSolrConnector();
$solr_version = $connector
->getSolrVersion();
if (version_compare($solr_version, '6.5', '=')) {
\Drupal::logger('search_api_solr')
->error('Solr 6.5.x contains a bug that breaks the autocomplete feature. Downgrade to 6.4.x or upgrade to 6.6.x.');
return [];
}
$solarium_query = $connector
->getTermsQuery();
$schema_version = $connector
->getSchemaVersion();
if (version_compare($schema_version, '5.4', '>=')) {
$solarium_query
->setHandler('autocomplete');
}
else {
$solarium_query
->setHandler('terms');
}
try {
$fl = [];
if (version_compare($schema_version, '5.4', '>=')) {
$fl = $this
->getAutocompleteFields($query, $search);
}
else {
$fl[] = 'spell';
}
$incomplete_key = mb_strtolower($incomplete_key);
$user_input = mb_strtolower($user_input);
$solarium_query
->setFields($fl);
$solarium_query
->setPrefix($incomplete_key);
$solarium_query
->setLimit(10);
if ($this->configuration['suggest_corrections']) {
$solarium_query
->addParam('q', $user_input);
$solarium_query
->addParam('spellcheck', 'true');
$solarium_query
->addParam('spellcheck.count', 1);
}
$terms_result = $connector
->execute($solarium_query);
$autocomplete_terms = [];
foreach ($terms_result as $terms) {
foreach ($terms as $term => $count) {
if ($term != $incomplete_key) {
$autocomplete_terms[$term] = $count;
}
}
}
if ($this->configuration['suggest_suffix']) {
foreach ($autocomplete_terms as $term => $count) {
$suggestion_suffix = mb_substr($term, mb_strlen($incomplete_key));
if ($factory) {
$suggestions[] = $factory
->createFromSuggestionSuffix($suggestion_suffix, $count);
}
else {
$suggestions[] = Suggestion::fromSuggestionSuffix($suggestion_suffix, $count, $user_input);
}
}
}
if ($this->configuration['suggest_corrections']) {
if (version_compare($schema_version, '5.4', '<')) {
$solarium_query
->setHandler('select');
$terms_result = $connector
->execute($solarium_query);
}
$suggester_result = new SuggesterResult(new SuggesterQuery(), $terms_result
->getResponse());
$suggestion_data = $suggester_result
->getData();
if (isset($suggestion_data['spellcheck']['suggestions'])) {
$suggestion_string = '';
foreach ($suggestion_data['spellcheck']['suggestions'] as $key => $sg) {
if (isset($suggestion_data['spellcheck']['suggestions'][$key + 1]) && is_array($suggestion_data['spellcheck']['suggestions'][$key + 1])) {
$correction = array_pop($suggestion_data['spellcheck']['suggestions'][$key + 1]);
$suggestion_string .= implode(' ', $correction) . ' ';
}
}
if (isset($suggestion_suffix) && (string) ($user_input . $suggestion_suffix) == trim($suggestion_string)) {
return $suggestions;
}
if ($factory) {
$suggestions[] = $factory
->createFromSuggestedKeys(trim($suggestion_string));
}
else {
$suggestions[] = Suggestion::fromSuggestedKeys(trim($suggestion_string), $user_input);
}
}
}
} catch (SearchApiException $e) {
watchdog_exception('search_api_solr', $e);
return [];
}
}
return $suggestions;
}
protected function getAutocompleteFields(QueryInterface $query, SearchInterface $search) {
$fl = [];
$solr_field_names = $this
->getSolrFieldNames($query
->getIndex());
$fulltext_fields = $search
->getOption('fields') ? $search
->getOption('fields') : $this
->getQueryFulltextFields($query);
foreach ($fulltext_fields as $fulltext_field) {
$fl[] = 'terms_' . $solr_field_names[$fulltext_field];
}
return $fl;
}
public function setConfiguration(array $configuration) {
parent::setConfiguration($configuration);
$this->solrConnector = NULL;
}
protected function getIndexId($machine_name) {
$id = $this->searchApiSolrSettings
->get('index_prefix_' . $machine_name) . $machine_name;
$id = $this->searchApiSolrSettings
->get('index_prefix') . $id;
return $id;
}
public function calculateDependencies() {
$this
->calculatePluginDependencies($this
->getSolrConnector());
return $this->dependencies;
}
protected function getExcerpt($data, $solr_id, ItemInterface $item, array $field_mapping) {
if (!isset($data['highlighting'][$solr_id])) {
return FALSE;
}
$output = '';
if (!empty($this->configuration['excerpt']) && !empty($data['highlighting'][$solr_id]['spell'])) {
foreach ($data['highlighting'][$solr_id]['spell'] as $snippet) {
$snippet = strip_tags($snippet);
$snippet = preg_replace('/^.*>|<.*$/', '', $snippet);
$snippet = SearchApiSolrUtility::formatHighlighting($snippet);
$snippet = trim($snippet, "\0../:;=?..@[..`");
$output .= $snippet . ' … ';
}
}
if (!empty($this->configuration['highlight_data'])) {
$item_fields = $item
->getFields();
foreach ($field_mapping as $search_api_property => $solr_property) {
if (!empty($data['highlighting'][$solr_id][$solr_property])) {
$snippets = [];
foreach ($data['highlighting'][$solr_id][$solr_property] as $value) {
$snippets[] = [
'raw' => preg_replace('#\\[(/?)HIGHLIGHT\\]#', '', $value),
'replace' => SearchApiSolrUtility::formatHighlighting($value),
];
}
if ($snippets) {
$values = $item_fields[$search_api_property]
->getValues();
foreach ($values as $value) {
foreach ($snippets as $snippet) {
if ($value instanceof TextValue) {
if ($value
->getText() === $snippet['raw']) {
$value
->setText($snippet['replace']);
}
}
else {
if ($value == $snippet['raw']) {
$value = $snippet['replace'];
}
}
}
}
$item_fields[$search_api_property]
->setValues($values);
}
}
}
}
return $output;
}
protected function flattenKeys(array $keys) {
$k = [];
$pre = '+';
if (isset($keys['#conjunction']) && $keys['#conjunction'] == 'OR') {
$pre = '';
}
$neg = empty($keys['#negation']) ? '' : '-';
foreach ($keys as $key_nr => $key) {
if (is_string($key_nr) && $key_nr[0] === '#' || !$key) {
continue;
}
if (is_array($key)) {
$subkeys = $this
->flattenKeys($key);
if ($subkeys) {
$nested_expressions = TRUE;
$k[] = "({$subkeys})";
}
}
else {
$k[] = $this
->getSolrConnector()
->getQueryHelper()
->escapePhrase(trim($key));
}
}
if (!$k) {
return '';
}
if (count($k) == 1 && empty($nested_expressions)) {
return $neg . reset($k);
}
return $neg . '(' . $pre . implode(' ' . $pre, $k) . ')';
}
protected function setHighlighting(Query $solarium_query, QueryInterface $query, $highlighted_fields = []) {
$excerpt = !empty($this->configuration['excerpt']);
$highlight = !empty($this->configuration['highlight_data']);
if ($highlight || $excerpt) {
$highlighter = \Drupal::config('search_api_solr.standard_highlighter');
$hl = $solarium_query
->getHighlighting();
$hl
->setSimplePrefix('[HIGHLIGHT]');
$hl
->setSimplePostfix('[/HIGHLIGHT]');
if ($highlighter
->get('maxAnalyzedChars') != $highlighter
->getOriginal('maxAnalyzedChars')) {
$hl
->setMaxAnalyzedChars($highlighter
->get('maxAnalyzedChars'));
}
if ($highlighter
->get('fragmenter') != $highlighter
->getOriginal('fragmenter')) {
$hl
->setFragmenter($highlighter
->get('fragmenter'));
}
if ($highlighter
->get('usePhraseHighlighter') != $highlighter
->getOriginal('usePhraseHighlighter')) {
$hl
->setUsePhraseHighlighter($highlighter
->get('usePhraseHighlighter'));
}
if ($highlighter
->get('highlightMultiTerm') != $highlighter
->getOriginal('highlightMultiTerm')) {
$hl
->setHighlightMultiTerm($highlighter
->get('highlightMultiTerm'));
}
if ($highlighter
->get('preserveMulti') != $highlighter
->getOriginal('preserveMulti')) {
$hl
->setPreserveMulti($highlighter
->get('preserveMulti'));
}
if ($highlighter
->get('regex.slop') != $highlighter
->getOriginal('regex.slop')) {
$hl
->setRegexSlop($highlighter
->get('regex.slop'));
}
if ($highlighter
->get('regex.pattern') != $highlighter
->getOriginal('regex.pattern')) {
$hl
->setRegexPattern($highlighter
->get('regex.pattern'));
}
if ($highlighter
->get('regex.maxAnalyzedChars') != $highlighter
->getOriginal('regex.maxAnalyzedChars')) {
$hl
->setRegexMaxAnalyzedChars($highlighter
->get('regex.maxAnalyzedChars'));
}
if ($excerpt) {
$excerpt_field = $hl
->getField('spell');
$excerpt_field
->setSnippets($highlighter
->get('excerpt.snippets'));
$excerpt_field
->setFragSize($highlighter
->get('excerpt.fragsize'));
$excerpt_field
->setMergeContiguous($highlighter
->get('excerpt.mergeContiguous'));
}
if ($highlight && !empty($highlighted_fields)) {
foreach ($highlighted_fields as $highlighted_field) {
$hl
->addField($highlighted_field);
}
$hl
->setSnippets(1);
$hl
->setFragSize(0);
$hl
->setMergeContiguous($highlighter
->get('highlight.mergeContiguous'));
$hl
->setRequireFieldMatch($highlighter
->get('highlight.requireFieldMatch'));
}
}
}
protected function getMoreLikeThisQuery(QueryInterface $query, $index_id, $index_fields = [], $fields = []) {
$connector = $this
->getSolrConnector();
$solarium_query = $connector
->getMoreLikeThisQuery();
$query_helper = $connector
->getQueryHelper($solarium_query);
$mlt_options = $query
->getOption('search_api_mlt');
$language_ids = $query
->getLanguages();
if (empty($language_ids)) {
$language_ids[] = \Drupal::languageManager()
->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)
->getId();
$query
->setLanguages($language_ids);
}
$ids = [];
foreach ($query
->getIndex()
->getDatasources() as $datasource) {
if ($entity_type_id = $datasource
->getEntityTypeId()) {
$entity = \Drupal::entityTypeManager()
->getStorage($entity_type_id)
->load($mlt_options['id']);
if ($entity instanceof ContentEntityInterface) {
$translated = FALSE;
if ($entity
->isTranslatable()) {
foreach ($language_ids as $language_id) {
if ($entity
->hasTranslation($language_id)) {
$ids[] = SearchApiUtility::createCombinedId($datasource
->getPluginId(), $datasource
->getItemId($entity
->getTranslation($language_id)
->getTypedData()));
$translated = TRUE;
}
}
}
if (!$translated) {
$ids[] = SearchApiUtility::createCombinedId($datasource
->getPluginId(), $datasource
->getItemId($entity
->getTypedData()));
}
}
else {
$ids[] = $mlt_options['id'];
}
}
}
if (!empty($ids)) {
array_walk($ids, function (&$id, $key) use ($index_id, $query_helper) {
$id = $this
->createId($index_id, $id);
$id = $query_helper
->escapePhrase($id);
});
$solarium_query
->setQuery('id:' . implode(' id:', $ids));
}
$mlt_fl = [];
foreach ($mlt_options['fields'] as $mlt_field) {
$version = $this
->getSolrConnector()
->getSolrVersion();
if ($fields[$mlt_field][0] === 'd' || version_compare($version, '4', '==') && in_array($fields[$mlt_field][0], array(
'i',
'f',
))) {
continue;
}
$mlt_fl[] = $fields[$mlt_field];
if (isset($index_fields[$mlt_field]) && !$this->dataTypeHelper
->isTextType($index_fields[$mlt_field]
->getType())) {
$solarium_query
->addParam('f.' . $fields[$mlt_field] . '.mlt.minwl', 0);
}
}
$solarium_query
->setMltFields($mlt_fl);
$solarium_query
->setMinimumDocumentFrequency(1);
$solarium_query
->setMinimumTermFrequency(1);
return $solarium_query;
}
protected function setSpatial(Query $solarium_query, array $spatial_options, $field_names = []) {
if (count($spatial_options) > 1) {
throw new SearchApiSolrException('Only one spatial search can be handled per query.');
}
$spatial = reset($spatial_options);
$solr_field = $field_names[$spatial['field']];
$distance_field = $spatial['field'] . '__distance';
$solr_distance_field = $field_names[$distance_field];
$spatial['lat'] = (double) $spatial['lat'];
$spatial['lon'] = (double) $spatial['lon'];
$spatial['radius'] = isset($spatial['radius']) ? (double) $spatial['radius'] : 0.0;
$spatial['min_radius'] = isset($spatial['min_radius']) ? (double) $spatial['min_radius'] : 0.0;
if (!isset($spatial['filter_query_conditions'])) {
$spatial['filter_query_conditions'] = [];
}
$spatial['filter_query_conditions'] += [
'field' => $solr_field,
'value' => $spatial['radius'],
'operator' => '<',
];
$solarium_query
->addField($solr_distance_field . ':geodist()');
$spatial_query = $solarium_query
->getSpatial();
$spatial_query
->setDistance($spatial['radius']);
$spatial_query
->setField($solr_field);
$spatial_query
->setPoint($spatial['lat'] . ',' . $spatial['lon']);
$solarium_query
->createFilterQuery($solr_field)
->setQuery($this
->createLocationFilterQuery($spatial));
$sorts = $solarium_query
->getSorts();
if (isset($sorts[$solr_distance_field])) {
$new_sorts = [];
foreach ($sorts as $key => $order) {
if ($key == $solr_distance_field) {
$new_sorts['geodist()'] = $order;
}
else {
$new_sorts[$key] = $order;
}
}
$solarium_query
->clearSorts();
$solarium_query
->setSorts($new_sorts);
}
$facet_set = $solarium_query
->getFacetSet();
if (!empty($facet_set)) {
$facets = $facet_set
->getFacets();
foreach ($facets as $delta => $facet) {
$facet_options = $facet
->getOptions();
if ($facet_options['field'] != $solr_distance_field) {
continue;
}
$facet_set
->removeFacet($delta);
$limit = $facet
->getLimit();
$steps = $limit > 0 ? $limit : 5;
$step = ($spatial['radius'] - $spatial['min_radius']) / $steps;
for ($i = 0; $i < $steps; $i++) {
$distance_min = $spatial['min_radius'] + $step * $i;
$distance_max = $distance_min + $step - 1;
$key = "spatial-{$distance_field}-{$distance_min}-{$distance_max}";
$facet_set
->createFacetQuery($key . ' frange l=' . $distance_min . ' u=' . $distance_max)
->setQuery('geodist()');
}
}
}
}
protected function setRpt(Query $solarium_query, array $rpt_options, $field_names = array()) {
if (count($rpt_options) > 1) {
throw new SearchApiSolrException('Only one spatial search can be handled per query.');
}
$rpt = reset($rpt_options);
$solr_field = $field_names[$rpt['field']];
$rpt['geom'] = isset($rpt['geom']) ? $rpt['geom'] : '["-180 -90" TO "180 90"]';
$solarium_query
->createFilterQuery($solr_field)
->setQuery($solr_field . ':' . $rpt['geom']);
$solarium_query
->addParam('facet', 'on');
$solarium_query
->addParam('facet.heatmap', $solr_field);
$solarium_query
->addParam('facet.heatmap.geom', $rpt['geom']);
$solarium_query
->addParam('facet.heatmap.format', $rpt['format']);
$solarium_query
->addParam('facet.heatmap.maxCells', $rpt['maxCells']);
$solarium_query
->addParam('facet.heatmap.gridLevel', $rpt['gridLevel']);
}
protected function setSorts(Query $solarium_query, QueryInterface $query, $field_names = []) {
$new_schema_version = version_compare($this
->getSolrConnector()
->getSchemaVersion(), '4.4', '>=');
foreach ($query
->getSorts() as $field => $order) {
$f = '';
if (strpos($field, 'search_api_') === 0) {
if ($field == 'search_api_random') {
$params = $query
->getOption('search_api_random_sort', []);
$seed = !empty($params['seed']) ? $params['seed'] : mt_rand();
$f = $field_names[$field] . '_' . $seed;
}
}
elseif ($new_schema_version) {
if (strpos($field_names[$field], 't') === 0 || strpos($field_names[$field], 's') === 0) {
$f = 'sort_' . $field;
}
elseif (preg_match('/^([a-z]+)m(_.*)/', $field_names[$field], $matches)) {
$f = $matches[1] . 's' . $matches[2];
}
}
if (!$f) {
$f = $field_names[$field];
}
$solarium_query
->addSort($f, strtolower($order));
}
}
protected function setGrouping(Query $solarium_query, QueryInterface $query, $grouping_options = [], $index_fields = [], $field_names = []) {
if (!empty($grouping_options['use_grouping'])) {
$group_fields = [];
foreach ($grouping_options['fields'] as $collapse_field) {
$first_name = $field_names[$collapse_field];
$field = $index_fields[$collapse_field];
$type = $field
->getType();
if ($this->dataTypeHelper
->isTextType($type)) {
$this
->getLogger()
->error('Grouping is not supported for field @field. Only single-valued fields not indexed as "Fulltext" are supported.', [
'@field' => $index_fields[$collapse_field]['name'],
]);
}
else {
$group_fields[] = $first_name;
}
}
if (!empty($group_fields)) {
$grouping_component = $solarium_query
->getGrouping();
$grouping_component
->setFields($group_fields)
->setNumberOfGroups(TRUE)
->setTruncate(!empty($grouping_options['truncate']))
->setFacet(!empty($grouping_options['group_facet']));
if (!empty($grouping_options['group_limit']) && $grouping_options['group_limit'] != 1) {
$grouping_component
->setLimit($grouping_options['group_limit']);
}
if (!empty($grouping_options['group_sort'])) {
$sorts = [];
foreach ($grouping_options['group_sort'] as $group_sort_field => $order) {
if (isset($fields[$group_sort_field])) {
$f = $fields[$group_sort_field];
if (substr($f, 0, 3) === 'ss_') {
$f = 'sort_' . substr($f, 3);
}
$sorts[] = $f . ' ' . strtolower($order);
}
}
$grouping_component
->setSort(implode(', ', $sorts));
}
}
}
}
public function extractContentFromFile($filepath) {
$connector = $this
->getSolrConnector();
$filename = basename($filepath);
$query = $connector
->getExtractQuery();
$query
->setExtractOnly(TRUE);
$query
->setFile($filepath);
$result = $connector
->extract($query);
return $connector
->getContentFromExtractResult($result, $filename);
}
public function getBackendDefinedFields(IndexInterface $index) {
$location_distance_fields = [];
foreach ($index
->getFields() as $field) {
if ($field
->getType() == 'location') {
$distance_field_name = $field
->getFieldIdentifier() . '__distance';
$property_path_name = $field
->getPropertyPath() . '__distance';
$distance_field = new Field($index, $distance_field_name);
$distance_field
->setLabel($field
->getLabel() . ' (distance)');
$distance_field
->setDataDefinition(DataDefinition::create('decimal'));
$distance_field
->setType('decimal');
$distance_field
->setDatasourceId($field
->getDatasourceId());
$distance_field
->setPropertyPath($property_path_name);
$location_distance_fields[$distance_field_name] = $distance_field;
}
}
return $location_distance_fields;
}
public function __sleep() {
$properties = array_flip(parent::__sleep());
unset($properties['solrConnector']);
return array_keys($properties);
}
}