View source
<?php
namespace Drupal\search_api_solr\Plugin\search_api\backend;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\search_api\Backend\BackendPluginBase;
use Drupal\search_api_autocomplete\SearchInterface;
use Drupal\search_api_solr\SearchApiSolrException;
use Drupal\search_api\IndexInterface;
use Drupal\search_api\Query\QueryInterface;
use Drupal\search_api\Query\ResultSetInterface;
use Drupal\search_api_solr\Solarium\Autocomplete\Query as AutocompleteQuery;
use Drupal\search_api_solr\SolrMultilingualBackendInterface;
use Drupal\search_api_solr\Utility\Utility;
use Solarium\Component\ComponentAwareQueryInterface;
use Solarium\Core\Query\QueryInterface as SolariumQueryInterface;
use Solarium\QueryType\Select\Query\Query;
use Solarium\Component\ResponseParser\FacetSet;
define('SEARCH_API_LANGUAGE_FIELD_NAME', 'search_api_language');
abstract class AbstractSearchApiSolrMultilingualBackend extends SearchApiSolrBackend implements SolrMultilingualBackendInterface {
protected $origKeys = FALSE;
protected abstract function createSolrDynamicField($solr_field_name, $solr_field_type_name);
protected abstract function createSolrMultilingualFieldType($solr_field_type_name);
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$form['multilingual'] = [
'#type' => 'fieldset',
'#title' => $this
->t('Multilingual'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
];
$form['multilingual']['sasm_limit_search_page_to_content_language'] = [
'#type' => 'checkbox',
'#title' => $this
->t('Limit to current content language.'),
'#description' => $this
->t('Limit all search results for custom queries or search pages not managed by Views to current content language if no language is specified in the query.'),
'#default_value' => isset($this->configuration['sasm_limit_search_page_to_content_language']) ? $this->configuration['sasm_limit_search_page_to_content_language'] : FALSE,
];
$form['multilingual']['sasm_search_page_include_language_independent'] = [
'#type' => 'checkbox',
'#title' => $this
->t('Include language independent content in search results.'),
'#description' => $this
->t('This option will include content without a language assigned in the results of custom queries or search pages not managed by Views. For example, if you search for English content, but have an article with languague of "undefined", you will see those results as well. If you disable this option, you will only see content that matches the language.'),
'#default_value' => isset($this->configuration['sasm_search_page_include_language_independent']) ? $this->configuration['sasm_search_page_include_language_independent'] : FALSE,
];
$form['multilingual']['sasm_language_unspecific_fallback_on_schema_issues'] = [
'#type' => 'checkbox',
'#title' => $this
->t('Use language fallbacks.'),
'#description' => $this
->t('This option is suitable for two use-cases. First, if you have languages like "de" and "de-at", both could be handled by a shared configuration for "de". Second, new languages will be handled by language-unspecific fallback configuration until the schema gets updated on your Solr server.'),
'#default_value' => isset($this->configuration['sasm_language_unspecific_fallback_on_schema_issues']) ? $this->configuration['sasm_language_unspecific_fallback_on_schema_issues'] : TRUE,
];
return $form;
}
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
parent::validateConfigurationForm($form, $form_state);
}
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$values = $form_state
->getValues();
foreach ($values['multilingual'] as $key => $value) {
$form_state
->setValue($key, $value);
}
$form_state
->unsetValue('multilingual');
parent::submitConfigurationForm($form, $form_state);
}
protected function alterSearchApiQuery(QueryInterface $query) {
if ($query
->hasTag('server_index_status') || $query
->hasTag('mlt')) {
return;
}
parent::alterSearchApiQuery($query);
$languages = $query
->getLanguages();
if (empty($languages)) {
if (!$query
->hasTag('views') && $this->configuration['sasm_limit_search_page_to_content_language']) {
$languages[] = \Drupal::languageManager()
->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)
->getId();
}
else {
$languages = array_keys(\Drupal::languageManager()
->getLanguages());
}
}
if ($this->configuration['sasm_search_page_include_language_independent']) {
$languages[] = LanguageInterface::LANGCODE_NOT_SPECIFIED;
$languages[] = LanguageInterface::LANGCODE_NOT_APPLICABLE;
}
$query
->setLanguages($languages);
}
protected function preQuery(SolariumQueryInterface $solarium_query, QueryInterface $query) {
if ($query
->hasTag('server_index_status')) {
return;
}
parent::preQuery($solarium_query, $query);
$language_ids = $query
->getLanguages();
if (!empty($language_ids)) {
$index = $query
->getIndex();
$fulltext_fields = $this
->getQueryFulltextFields($query);
$field_names = $this
->getSolrFieldNames($index);
$language_specific_fields = [];
foreach ($language_ids as $language_id) {
foreach ($fulltext_fields as $fulltext_field) {
$field_name = $field_names[$fulltext_field];
$language_specific_fields[$language_id][$field_name] = Utility::encodeSolrName(Utility::getLanguageSpecificSolrDynamicFieldNameForSolrDynamicFieldName($field_name, $language_id));
}
}
$components = $solarium_query
->getComponents();
if (isset($components[ComponentAwareQueryInterface::COMPONENT_HIGHLIGHTING])) {
$hl = $solarium_query
->getHighlighting();
$highlighted_fields = $hl
->getFields();
foreach ($field_names as $field_name) {
if (isset($highlighted_fields[$field_name])) {
$exchanged = FALSE;
foreach ($language_ids as $language_id) {
if (isset($language_specific_fields[$language_id][$field_name])) {
$language_specific_field = $language_specific_fields[$language_id][$field_name];
$highlighted_field = $hl
->getField($language_specific_field);
$highlighted_field
->setOptions($highlighted_fields[$field_name]
->getOptions());
$highlighted_field
->setName($language_specific_field);
$exchanged = TRUE;
}
}
if ($exchanged) {
$hl
->removeField($field_name);
}
}
}
}
if (!empty($this->configuration['retrieve_data'])) {
if ($fields_to_be_retrieved = $query
->getOption('search_api_retrieved_field_values', [])) {
$language_specific_fields_to_retrieve = [];
$fields_to_retrieve = $solarium_query
->getFields();
foreach ($fields_to_retrieve as $key => $field_name) {
$language_specific_name = FALSE;
foreach ($language_ids as $language_id) {
if (isset($language_specific_fields[$language_id][$field_name])) {
$language_specific_fields_to_retrieve[] = $language_specific_fields[$language_id][$field_name];
$language_specific_name = TRUE;
}
}
if ($language_specific_name) {
unset($fields_to_retrieve[$key]);
}
}
if ($language_specific_fields_to_retrieve) {
$solarium_query
->setFields(array_merge($language_specific_fields_to_retrieve, $fields_to_retrieve));
}
}
}
if ($query
->hasTag('mlt')) {
$mlt_fields = [];
foreach ($language_ids as $language_id) {
foreach ($solarium_query
->getMltFields() as $mlt_field) {
if (isset($language_specific_fields[$language_id][$mlt_field])) {
$mlt_fields[] = $language_specific_fields[$language_id][$mlt_field];
}
else {
$mlt_fields[$mlt_field] = $mlt_field;
}
}
}
$solarium_query
->setMltFields($mlt_fields);
}
elseif ($keys = $query
->getKeys()) {
$edismax = $solarium_query
->getEDisMax();
if ($solr_fields = $edismax
->getQueryFields()) {
$parse_mode_id = $query
->getParseMode()
->getPluginId();
if ('terms' == $parse_mode_id) {
$parse_mode_id = 'phrase';
}
$new_keys = [];
foreach ($language_ids as $language_id) {
$new_solr_fields = $solr_fields;
foreach ($fulltext_fields as $fulltext_field) {
$field_name = $field_names[$fulltext_field];
$new_solr_fields = str_replace($field_name, $language_specific_fields[$language_id][$field_name], $new_solr_fields);
}
if ($new_solr_fields == $solr_fields) {
return;
}
$flat_keys = $this
->flattenKeys($keys, explode(' ', $new_solr_fields), $parse_mode_id);
if (strpos($flat_keys, '(') !== 0 && strpos($flat_keys, '+(') !== 0 && strpos($flat_keys, '-(') !== 0) {
$flat_keys = '(' . $flat_keys . ')';
}
$new_keys[] = $flat_keys;
}
if (count($new_keys) > 1) {
$new_keys['#conjunction'] = 'OR';
}
$new_keys['#escaped'] = TRUE;
$this->origKeys = $query
->getOriginalKeys();
$query
->keys($new_keys);
$edismax
->setQueryFields([]);
}
}
}
}
protected function getFilterQueries(QueryInterface $query, array $solr_fields, array $index_fields, array &$options) {
$condition_group = $query
->getConditionGroup();
$conditions = $condition_group
->getConditions();
if (empty($conditions) || empty($query
->getLanguages())) {
return parent::getFilterQueries($query, $solr_fields, $index_fields, $options);
}
$fq = [];
foreach ($conditions as $condition) {
$language_fqs = [];
foreach ($query
->getLanguages() as $langcode) {
$language_specific_condition_group = $query
->createConditionGroup();
$language_specific_condition_group
->addCondition(SEARCH_API_LANGUAGE_FIELD_NAME, $langcode);
$language_specific_conditions =& $language_specific_condition_group
->getConditions();
$language_specific_conditions[] = $condition;
$language_fqs = array_merge($language_fqs, $this
->reduceFilterQueries($this
->createFilterQueries($language_specific_condition_group, $this
->getLanguageSpecificSolrFieldNames($langcode, $solr_fields, reset($index_fields)
->getIndex()), $index_fields, $options, $query), $condition_group));
}
$language_aware_condition_group = $query
->createConditionGroup('OR');
$fq = array_merge($fq, $this
->reduceFilterQueries($language_fqs, $language_aware_condition_group, TRUE));
}
return $fq;
}
protected function getLanguageSpecificSolrFieldNames($lancgcode, array $solr_fields, IndexInterface $index) {
foreach ($index
->getFulltextFields() as $fulltext_field) {
$solr_fields[$fulltext_field] = Utility::encodeSolrName(Utility::getLanguageSpecificSolrDynamicFieldNameForSolrDynamicFieldName($solr_fields[$fulltext_field], $lancgcode));
}
return $solr_fields;
}
protected function alterSolrResponseBody(&$body, QueryInterface $query) {
$data = json_decode($body);
$index = $query
->getIndex();
$field_names = $this
->getSolrFieldNames($index, TRUE);
$doc_languages = [];
if (isset($data->response)) {
foreach ($data->response->docs as $doc) {
$language_id = $doc_languages[$this
->createId($this
->getIndexId($index), $doc->{$field_names['search_api_id']})] = $doc->{$field_names[SEARCH_API_LANGUAGE_FIELD_NAME]};
foreach (array_keys(get_object_vars($doc)) as $language_specific_field_name) {
$field_name = Utility::getSolrDynamicFieldNameForLanguageSpecificSolrDynamicFieldName($language_specific_field_name);
if ($field_name != $language_specific_field_name) {
if (Utility::getLanguageIdFromLanguageSpecificSolrDynamicFieldName($language_specific_field_name) == $language_id) {
$doc->{$field_name} = $doc->{$language_specific_field_name};
unset($doc->{$language_specific_field_name});
}
}
}
}
}
if (isset($data->highlighting)) {
foreach ($data->highlighting as $solr_id => &$item) {
foreach (array_keys(get_object_vars($item)) as $language_specific_field_name) {
$field_name = Utility::getSolrDynamicFieldNameForLanguageSpecificSolrDynamicFieldName($language_specific_field_name);
if ($field_name != $language_specific_field_name) {
if (Utility::getLanguageIdFromLanguageSpecificSolrDynamicFieldName($language_specific_field_name) == $doc_languages[$solr_id]) {
$item->{$field_name} = $item->{$language_specific_field_name};
unset($item->{$language_specific_field_name});
}
}
}
}
}
if (isset($data->facet_counts)) {
$facet_set_helper = new FacetSet();
foreach (get_object_vars($data->facet_counts->facet_fields) as $language_specific_field_name => $facet_terms) {
$field_name = Utility::getSolrDynamicFieldNameForLanguageSpecificSolrDynamicFieldName($language_specific_field_name);
if ($field_name != $language_specific_field_name) {
if (isset($data->facet_counts->facet_fields->{$field_name})) {
$key_value = $facet_set_helper
->convertToKeyValueArray($data->facet_counts->facet_fields->{$field_name}) + $facet_set_helper
->convertToKeyValueArray($facet_terms);
$facet_terms = [];
foreach ($key_value as $key => $value) {
$facet_terms[] = $key;
$facet_terms[] = $value;
}
}
$data->facet_counts->facet_fields->{$field_name} = $facet_terms;
}
}
}
$body = json_encode($data);
}
protected function postQuery(ResultSetInterface $results, QueryInterface $query, $response) {
if ($this->origKeys) {
$query
->keys($this->origKeys);
}
parent::postQuery($results, $query, $response);
}
protected function alterSolrDocuments(array &$documents, IndexInterface $index, array $items) {
parent::alterSolrDocuments($documents, $index, $items);
$fulltext_fields = $index
->getFulltextFields();
$multiple_field_names = $this
->getSolrFieldNames($index);
$field_names = $this
->getSolrFieldNames($index, TRUE);
$fulltext_field_names = array_filter(array_flip($multiple_field_names) + array_flip($field_names), function ($value) use ($fulltext_fields) {
return in_array($value, $fulltext_fields);
});
$field_name_map_per_language = [];
foreach ($documents as $document) {
$fields = $document
->getFields();
$language_id = $fields[$field_names[SEARCH_API_LANGUAGE_FIELD_NAME]];
foreach ($fields as $monolingual_solr_field_name => $field_value) {
if (array_key_exists($monolingual_solr_field_name, $fulltext_field_names)) {
$multilingual_solr_field_name = Utility::getLanguageSpecificSolrDynamicFieldNameForSolrDynamicFieldName($monolingual_solr_field_name, $language_id);
$field_name_map_per_language[$language_id][$monolingual_solr_field_name] = Utility::encodeSolrName($multilingual_solr_field_name);
}
}
}
foreach ($field_name_map_per_language as $language_id => $map) {
$solr_field_type_name = Utility::encodeSolrName('text' . '_' . $language_id);
if (!$this
->isPartOfSchema('fieldTypes', $solr_field_type_name) && !$this
->createSolrMultilingualFieldType($solr_field_type_name) && !$this
->hasLanguageUndefinedFallback()) {
throw new SearchApiSolrException('Missing field type ' . $solr_field_type_name . ' in schema.');
}
foreach ([
'ts',
'tm',
] as $prefix) {
$multilingual_solr_field_name = Utility::encodeSolrName(Utility::getLanguageSpecificSolrDynamicFieldPrefix($prefix, $language_id)) . '*';
if (!$this
->isPartOfSchema('dynamicFields', $multilingual_solr_field_name) && !$this
->createSolrDynamicField($multilingual_solr_field_name, $solr_field_type_name) && !$this
->hasLanguageUndefinedFallback()) {
throw new SearchApiSolrException('Missing dynamic field ' . $multilingual_solr_field_name . ' in schema.');
}
}
}
foreach ($documents as $document) {
$fields = $document
->getFields();
foreach ($field_name_map_per_language as $language_id => $map) {
if ($fields[$field_names[SEARCH_API_LANGUAGE_FIELD_NAME]] == $language_id) {
foreach ($fields as $monolingual_solr_field_name => $value) {
if (isset($map[$monolingual_solr_field_name])) {
if ('twm_suggest' != $monolingual_solr_field_name) {
$document
->addField($map[$monolingual_solr_field_name], $value, $document
->getFieldBoost($monolingual_solr_field_name));
$document
->removeField($monolingual_solr_field_name);
}
}
}
}
}
}
}
protected function isPartOfSchema($kind, $name) {
static $previous_calls;
$state_key = 'sasm.' . $this
->getServer()
->id() . '.schema_parts';
$state = \Drupal::state();
$schema_parts = $state
->get($state_key);
if (!is_array($schema_parts) || empty($schema_parts[$kind]) || !in_array($name, $schema_parts[$kind]) && !isset($previous_calls[$kind])) {
$response = $this
->getSolrConnector()
->coreRestGet('schema/' . strtolower($kind));
if (empty($response[$kind])) {
throw new SearchApiSolrException('Missing information about ' . $kind . ' in response to REST request.');
}
$schema_parts[$kind] = [];
foreach ($response[$kind] as $row) {
$schema_parts[$kind][] = $row['name'];
}
$state
->set($state_key, $schema_parts);
$previous_calls[$kind] = TRUE;
}
return in_array($name, $schema_parts[$kind]);
}
public function getSchemaLanguageStatistics() {
$available = $this
->getSolrConnector()
->pingCore();
$stats = [];
foreach (\Drupal::languageManager()
->getLanguages() as $language) {
$solr_field_type_name = Utility::encodeSolrName('text' . '_' . $language
->getId());
$stats[$language
->getId()] = $available ? $this
->isPartOfSchema('fieldTypes', $solr_field_type_name) : FALSE;
}
return $stats;
}
public function hasLanguageUndefinedFallback() {
return isset($this->configuration['sasm_language_unspecific_fallback_on_schema_issues']) ? $this->configuration['sasm_language_unspecific_fallback_on_schema_issues'] : FALSE;
}
protected function setFacets(QueryInterface $query, Query $solarium_query, array $field_names) {
parent::setFacets($query, $solarium_query, $field_names);
if ($languages = $query
->getLanguages()) {
foreach ($languages as $language) {
$language_specific_field_names = $this
->getLanguageSpecificSolrFieldNames($language, $field_names, $query
->getIndex());
parent::setFacets($query, $solarium_query, array_diff_assoc($language_specific_field_names, $field_names));
}
}
}
protected function getAutocompleteFields(QueryInterface $query) {
$fl = [];
$solr_field_names = $this
->getSolrFieldNames($query
->getIndex());
foreach ($query
->getLanguages() as $langcode) {
$fulltext_fields = BackendPluginBase::getQueryFulltextFields($query);
$language_specific_fulltext_fields = $this
->getLanguageSpecificSolrFieldNames($langcode, $solr_field_names, $query
->getIndex());
foreach ($fulltext_fields as $fulltext_field) {
$fl[] = $language_specific_fulltext_fields[$fulltext_field];
}
}
return array_unique($fl);
}
protected function setAutocompleteSuggesterQuery(QueryInterface $query, AutocompleteQuery $solarium_query, $user_input, $options = []) {
if (isset($options['context_filter_tags']) && in_array('drupal/langcode:multilingual', $options['context_filter_tags'])) {
$langcodes = $query
->getLanguages();
if (count($langcodes) == 1) {
$langcode = reset($langcodes);
$options['context_filter_tags'] = str_replace('drupal/langcode:multilingual', 'drupal/langcode:' . $langcode, $options['context_filter_tags']);
$options['dictionary'] = $langcode;
}
else {
foreach ($options['context_filter_tags'] as $key => $tag) {
if ('drupal/langcode:multilingual' == $tag) {
unset($options['context_filter_tags'][$key]);
break;
}
}
}
}
parent::setAutocompleteSuggesterQuery($query, $solarium_query, $user_input, $options);
}
}