View source
<?php
class SearchApiSolrService extends SearchApiAbstractService {
const SOLR_DATE_FORMAT = 'Y-m-d\\TH:i:s\\Z';
protected $connection_class = 'SearchApiSolrConnection';
protected $solr;
protected $fieldNames = array();
protected $fields;
protected $commitScheduled = FALSE;
protected $request_handler = NULL;
public function configurationForm(array $form, array &$form_state) {
if ($this->options) {
$form['server_description'] = array(
'#type' => 'item',
'#title' => t('Solr server URI'),
'#description' => $this
->getServerLink(),
);
}
$options = $this->options + array(
'scheme' => 'http',
'host' => 'localhost',
'port' => '8983',
'path' => '/solr',
'http_user' => '',
'http_pass' => '',
'excerpt' => FALSE,
'retrieve_data' => FALSE,
'highlight_data' => FALSE,
'skip_schema_check' => FALSE,
'log_query' => FALSE,
'log_response' => FALSE,
'commits_disabled' => FALSE,
'solr_version' => '',
'http_method' => 'AUTO',
'clean_ids' => $this->options ? FALSE : TRUE,
'site_hash' => $this->options ? FALSE : TRUE,
'autocorrect_spell' => TRUE,
'autocorrect_suggest_words' => TRUE,
'highlight_prefix' => '',
'highlight_suffix' => '',
);
if (!$options['clean_ids']) {
if (module_exists('advanced_help')) {
$variables['@url'] = url('help/search_api_solr/README.txt');
}
else {
$variables['@url'] = url(drupal_get_path('module', 'search_api_solr') . '/README.txt');
}
$description = t('Change Solr field names to be more compatible with advanced features. Doing this leads to re-indexing of all indexes on this server. See <a href="@url">README.txt</a> for details.', $variables);
$form['clean_ids_form'] = array(
'#type' => 'fieldset',
'#title' => t('Clean field identifiers'),
'#description' => $description,
'#collapsible' => TRUE,
);
$form['clean_ids_form']['submit'] = array(
'#type' => 'submit',
'#value' => t('Switch to clean field identifiers'),
'#submit' => array(
'_search_api_solr_switch_to_clean_ids',
),
);
}
$form['clean_ids'] = array(
'#type' => 'value',
'#value' => $options['clean_ids'],
);
if (!$options['site_hash']) {
$description = t('If you want to index content from multiple sites on a single Solr server, you should enable the multi-site compatibility here. Note, however, that this will completely clear all search indexes (from this site) lying on this server. All content will have to be re-indexed.');
$form['site_hash_form'] = array(
'#type' => 'fieldset',
'#title' => t('Multi-site compatibility'),
'#description' => $description,
'#collapsible' => TRUE,
);
$form['site_hash_form']['submit'] = array(
'#type' => 'submit',
'#value' => t('Turn on multi-site compatibility and clear all indexes'),
'#submit' => array(
'_search_api_solr_switch_to_site_hash',
),
);
}
$form['site_hash'] = array(
'#type' => 'value',
'#value' => $options['site_hash'],
);
$form['scheme'] = array(
'#type' => 'select',
'#title' => t('HTTP protocol'),
'#description' => t('The HTTP protocol to use for sending queries.'),
'#default_value' => $options['scheme'],
'#options' => array(
'http' => 'http',
'https' => 'https',
),
);
$form['host'] = array(
'#type' => 'textfield',
'#title' => t('Solr host'),
'#description' => t('The host name or IP of your Solr server, e.g. <code>localhost</code> or <code>www.example.com</code>.'),
'#default_value' => $options['host'],
'#required' => TRUE,
);
$form['port'] = array(
'#type' => 'textfield',
'#title' => t('Solr port'),
'#description' => t('The Jetty example server is at port 8983, while Tomcat uses 8080 by default.'),
'#default_value' => $options['port'],
'#required' => TRUE,
);
$form['path'] = array(
'#type' => 'textfield',
'#title' => t('Solr path'),
'#description' => t('The path that identifies the Solr instance to use on the server. (For Solr versions 4 and above, this should include the name of the core to use.)'),
'#default_value' => $options['path'],
);
$form['http'] = array(
'#type' => 'fieldset',
'#title' => t('Basic HTTP authentication'),
'#description' => t('If your Solr server is protected by basic HTTP authentication, enter the login data here.'),
'#collapsible' => TRUE,
'#collapsed' => empty($options['http_user']),
);
$form['http']['http_user'] = array(
'#type' => 'textfield',
'#title' => t('Username'),
'#default_value' => $options['http_user'],
'#prefix' => '<input type="text" style="display:none" /><input type="password" style="display:none" />',
);
$form['http']['http_pass'] = array(
'#type' => 'password',
'#title' => t('Password'),
'#description' => t('If this field is left blank and the HTTP username is filled out, the current password will not be changed.'),
);
$form['advanced'] = array(
'#type' => 'fieldset',
'#title' => t('Advanced'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['advanced']['excerpt'] = array(
'#type' => 'checkbox',
'#title' => t('Return an excerpt for all results'),
'#description' => 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' => $options['excerpt'],
);
$form['advanced']['highlight_prefix'] = array(
'#type' => 'textfield',
'#title' => t('Highlight prefix'),
'#default_value' => $options['highlight_prefix'],
'#description' => t('Change the prefix if using Solr to perform highlighting (not highlight processor). If empty, defaults to %tag', array(
'%tag' => '<strong>',
)),
'#states' => array(
'invisible' => array(
array(
':input[name="options[form][advanced][excerpt]"]' => array(
'checked' => FALSE,
),
),
),
),
);
$form['advanced']['highlight_suffix'] = array(
'#type' => 'textfield',
'#title' => t('Highlight suffix'),
'#default_value' => $options['highlight_suffix'],
'#description' => t('Change the suffix if using Solr to perform highlighting (not highlight processor). If empty, defaults to %tag', array(
'%tag' => '</strong>',
)),
'#states' => array(
'invisible' => array(
array(
':input[name="options[form][advanced][excerpt]"]' => array(
'checked' => FALSE,
),
),
),
),
);
$form['advanced']['retrieve_data'] = array(
'#type' => 'checkbox',
'#title' => t('Retrieve result data from Solr'),
'#description' => 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' => $options['retrieve_data'],
);
$form['advanced']['highlight_data'] = array(
'#type' => 'checkbox',
'#title' => t('Highlight retrieved data'),
'#description' => t('When retrieving result data from the Solr server, try to highlight the search terms in the returned fulltext fields. <strong>Note:</strong> Do not use the "Highlighting" processor for the index together with this option – use one or the other.'),
'#default_value' => $options['highlight_data'],
);
$form['advanced']['highlight_data']['#states']['invisible'][':input[name="options[form][advanced][retrieve_data]"]']['checked'] = FALSE;
$form['advanced']['skip_schema_check'] = array(
'#type' => 'checkbox',
'#title' => t('Skip schema verification'),
'#description' => t('Skip the automatic check for schema-compatibility. 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' => $options['skip_schema_check'],
);
$form['advanced']['solr_version'] = array(
'#type' => 'select',
'#title' => t('Solr version override'),
'#description' => t('Specify the Solr version manually in case it cannot be retrieved automatically. The version can be found in the Solr admin interface under "Solr Specification Version" or "solr-spec".'),
'#options' => array(
'' => t('Determine automatically'),
'3' => '3.x',
'4' => '4.x',
'5' => '5.x',
),
'#default_value' => $options['solr_version'],
);
$form['advanced']['http_method'] = array(
'#type' => 'select',
'#title' => t('HTTP method'),
'#description' => t('The HTTP method to use for sending queries. GET will often fail with larger queries, while POST should not be cached. AUTO will use GET when possible, and POST for queries that are too large.'),
'#default_value' => $options['http_method'],
'#options' => array(
'AUTO' => t('AUTO'),
'POST' => 'POST',
'GET' => 'GET',
),
);
$form['advanced']['log_query'] = array(
'#type' => 'checkbox',
'#title' => t('Log search requests'),
'#description' => t('Log all outgoing Solr search requests.'),
'#default_value' => $options['log_query'],
);
$form['advanced']['log_response'] = array(
'#type' => 'checkbox',
'#title' => t('Log search results'),
'#description' => t('Log all search result responses received from Solr. NOTE: This may slow down your site since all response data (including possible retrieved data) will be saved in the Drupal log.'),
'#default_value' => $options['log_response'],
);
$form['advanced']['commits_disabled'] = array(
'#type' => 'checkbox',
'#title' => t('Disable explicit committing'),
'#description' => t('Do not send any commit commands to the Solr server.'),
'#default_value' => $options['commits_disabled'],
);
if (module_exists('search_api_autocomplete')) {
$form['advanced']['autocomplete'] = array(
'#type' => 'fieldset',
'#title' => t('Autocomplete'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['advanced']['autocomplete']['autocorrect_spell'] = array(
'#type' => 'checkbox',
'#title' => t('Use spellcheck for autocomplete suggestions'),
'#description' => t('If activated, spellcheck suggestions ("Did you mean") will be included in the autocomplete suggestions. Since the used dictionary contains words from all indexes, this might lead to leaking of sensitive data, depending on your setup.'),
'#default_value' => $options['autocorrect_spell'],
);
$form['advanced']['autocomplete']['autocorrect_suggest_words'] = array(
'#type' => 'checkbox',
'#title' => t('Suggest additional words'),
'#description' => t('If activated and the user enters a complete word, Solr will suggest additional words the user wants to search, which are often found (not searched!) together. This has been known to lead to strange results in some configurations – if you see inappropriate additional-word suggestions, you might want to deactivate this option.'),
'#default_value' => $options['autocorrect_suggest_words'],
);
}
return $form;
}
public function configurationFormValidate(array $form, array &$values, array &$form_state) {
if (isset($values['port']) && (!is_numeric($values['port']) || $values['port'] < 0 || $values['port'] > 65535)) {
form_error($form['port'], t('The port has to be an integer between 0 and 65535.'));
}
}
public function configurationFormSubmit(array $form, array &$values, array &$form_state) {
$values += $values['http'];
$values += $values['advanced'];
$values += !empty($values['autocomplete']) ? $values['autocomplete'] : array();
unset($values['http'], $values['advanced'], $values['autocomplete']);
$values['highlight_data'] &= $values['retrieve_data'];
if ($values['http_pass'] === '' && isset($this->options['http_user']) && $values['http_user'] === $this->options['http_user']) {
$values['http_pass'] = $this->options['http_pass'];
}
parent::configurationFormSubmit($form, $values, $form_state);
}
public function supportsFeature($feature) {
$supported = drupal_map_assoc(array(
'search_api_autocomplete',
'search_api_between',
'search_api_facets',
'search_api_facets_operator_or',
'search_api_grouping',
'search_api_mlt',
'search_api_multi',
'search_api_service_extra',
'search_api_spellcheck',
'search_api_data_type_location',
'search_api_data_type_geohash',
'search_api_random_sort',
));
if (isset($supported[$feature])) {
return TRUE;
}
if (substr($feature, 0, 21) != 'search_api_data_type_') {
return FALSE;
}
$type = substr($feature, 21);
$type = search_api_get_data_type_info($type);
return $type && !empty($type['prefix']);
}
public function viewSettings() {
return '';
}
public function getExtraInformation() {
$info = array();
$info[] = array(
'label' => t('Solr server URI'),
'info' => $this
->getServerLink(),
);
if ($this->options['http_user']) {
$vars = array(
'@user' => $this->options['http_user'],
'@pass' => str_repeat('*', strlen($this->options['http_pass'])),
);
$http = t('Username: @user; Password: @pass', $vars);
$info[] = array(
'label' => t('Basic HTTP authentication'),
'info' => $http,
);
}
if ($this->server->enabled) {
$ping = $this
->ping();
if ($ping) {
$msg = t('The Solr server could be reached (latency: @millisecs ms).', array(
'@millisecs' => $ping * 1000,
));
}
else {
$msg = t('The Solr server could not be reached. Further data is therefore unavailable.');
}
$info[] = array(
'label' => t('Connection'),
'info' => $msg,
'status' => $ping ? 'ok' : 'error',
);
if ($ping) {
try {
$this
->connect();
$this->solr
->clearCache();
$data = $this->solr
->getLuke();
if (isset($data->index->numDocs)) {
$stats_summary = $this->solr
->getStatsSummary();
$pending_msg = $stats_summary['@pending_docs'] ? t('(@pending_docs sent but not yet processed)', $stats_summary) : '';
$index_msg = $stats_summary['@index_size'] ? t('(@index_size on disk)', $stats_summary) : '';
$indexed_message = t('@num items !pending !index_msg', array(
'@num' => $data->index->numDocs,
'!pending' => $pending_msg,
'!index_msg' => $index_msg,
));
$info[] = array(
'label' => t('Indexed'),
'info' => $indexed_message,
);
if (!empty($stats_summary['@deletes_total'])) {
$info[] = array(
'label' => t('Pending Deletions'),
'info' => $stats_summary['@deletes_total'],
);
}
$info[] = array(
'label' => t('Delay'),
'info' => t('@autocommit_time before updates are processed.', $stats_summary),
);
$status = 'ok';
if (empty($this->options['skip_schema_check'])) {
if (substr($stats_summary['@schema_version'], 0, 10) == 'search-api') {
drupal_set_message(t('Your schema.xml version is too old. Please replace all configuration files with the ones packaged with this module and re-index you data.'), 'error');
$status = 'error';
}
elseif (substr($stats_summary['@schema_version'], 0, 9) != 'drupal-4.') {
$variables['@url'] = url('https://www.drupal.org/node/1999310');
$message = t('You are using an incompatible schema.xml configuration file. Please follow the instructions in <a href="@url">the handbook</a> for setting up Solr.', $variables);
drupal_set_message($message, 'error');
$status = 'error';
}
}
$info[] = array(
'label' => t('Schema'),
'info' => $stats_summary['@schema_version'],
'status' => $status,
);
if (!empty($stats_summary['@core_name'])) {
$info[] = array(
'label' => t('Solr Core Name'),
'info' => $stats_summary['@core_name'],
);
}
}
} catch (SearchApiException $e) {
$info[] = array(
'label' => t('Additional information'),
'info' => t('An error occurred while trying to retrieve additional information from the Solr server: @msg.', array(
'@msg' => $e
->getMessage(),
)),
'status' => 'error',
);
}
}
}
return $info;
}
public function getServerLink() {
if (!$this->options) {
return '';
}
$host = $this->options['host'];
if ($host == 'localhost' && !empty($_SERVER['SERVER_NAME'])) {
$host = $_SERVER['SERVER_NAME'];
}
$url = $this->options['scheme'] . '://' . $host . ':' . $this->options['port'] . $this->options['path'];
return l($url, $url);
}
protected function connect() {
if (!$this->solr) {
$connection_class = $this
->getConnectionClass();
if (!class_exists($connection_class)) {
throw new SearchApiException(t('Invalid class @class set as Solr connection class.', array(
'@class' => $connection_class,
)));
}
$options = $this->options + array(
'server' => $this->server->machine_name,
);
$this->solr = new $connection_class($options);
if (!$this->solr instanceof SearchApiSolrConnectionInterface) {
$this->solr = NULL;
throw new SearchApiException(t('Invalid class @class set as Solr connection class.', array(
'@class' => $connection_class,
)));
}
}
}
public function addIndex(SearchApiIndex $index) {
if (module_exists('search_api_multi') && module_exists('search_api_views')) {
views_invalidate_cache();
}
}
public function fieldsUpdated(SearchApiIndex $index) {
if (module_exists('search_api_multi') && module_exists('search_api_views')) {
views_invalidate_cache();
}
$old_fields = isset($index->original->options['fields']) ? $index->original->options['fields'] : array();
$new_fields = isset($index->options['fields']) ? $index->options['fields'] : array();
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
->getFieldNames($index->original, TRUE);
$new_field_names = $this
->getFieldNames($index, TRUE);
return $old_field_names != $new_field_names;
}
public function removeIndex($index) {
if (module_exists('search_api_multi') && module_exists('search_api_views')) {
views_invalidate_cache();
}
parent::removeIndex($index);
}
public function indexItems(SearchApiIndex $index, array $items) {
$documents = array();
$ret = array();
$index_id = $this
->getIndexId($index->machine_name);
$fields = $this
->getFieldNames($index);
$languages = language_list();
$base_urls = array();
foreach ($items as $id => $item) {
$doc = new SearchApiSolrDocument();
$doc
->setField('id', $this
->createId($index_id, $id));
$doc
->setField('index_id', $index_id);
$doc
->setField('item_id', $id);
if (!empty($this->options['site_hash'])) {
$doc
->setField('hash', search_api_solr_site_hash());
$lang = $item['search_api_language']['value'];
if (empty($base_urls[$lang])) {
$url_options = array(
'absolute' => TRUE,
);
if (isset($languages[$lang])) {
$url_options['language'] = $languages[$lang];
}
$base_urls[$lang] = url(NULL, $url_options);
}
$doc
->setField('site', $base_urls[$lang]);
}
$text_content = array();
foreach ($item as $key => $field) {
if (!isset($fields[$key])) {
$type = search_api_get_item_type_info($index->item_type);
$vars = array(
'@field' => $key,
'@type' => $type ? $type['name'] : $index->item_type,
'@id' => $id,
);
watchdog('search_api_solr', 'Error while indexing: Unknown field @field set for @type with ID @id.', $vars, WATCHDOG_WARNING);
$doc = NULL;
break;
}
$text_content[] = $this
->addIndexField($doc, $fields[$key], $field['value'], $field['type']);
}
$doc
->setField('content', implode("\n\n", array_filter($text_content)));
if ($doc) {
$documents[] = $doc;
$ret[] = $id;
}
}
drupal_alter('search_api_solr_documents', $documents, $index, $items);
$this
->alterSolrDocuments($documents, $index, $items);
if (!$documents) {
return array();
}
try {
$this
->connect();
$this->solr
->addDocuments($documents);
if (!empty($index->options['index_directly'])) {
$this
->scheduleCommit();
}
return $ret;
} catch (SearchApiException $e) {
watchdog_exception('search_api_solr', $e, "%type while indexing: !message in %function (line %line of %file).");
}
return array();
}
public function createId($index_id, $item_id) {
$site_hash = !empty($this->options['site_hash']) ? search_api_solr_site_hash() . '-' : '';
return "{$site_hash}{$index_id}-{$item_id}";
}
public function getFieldNames(SearchApiIndex $index, $reset = FALSE) {
if (!isset($this->fieldNames[$index->machine_name]) || $reset) {
$ret = array(
'search_api_id' => 'item_id',
'search_api_relevance' => 'score',
'search_api_random' => 'random',
);
$fields = isset($index->options['fields']) ? $index->options['fields'] : array();
foreach ($fields as $key => $field) {
$type = $field['type'];
if (isset($field['real_type'])) {
$custom_type = search_api_extract_inner_type($field['real_type']);
if ($this
->supportsFeature('search_api_data_type_' . $custom_type)) {
$type = $field['real_type'];
}
}
$inner_type = search_api_extract_inner_type($type);
$type_info = search_api_solr_get_data_type_info($inner_type);
$pref = isset($type_info['prefix']) ? $type_info['prefix'] : '';
if (empty($type_info['always multiValued'])) {
$pref .= $type == $inner_type ? 's' : 'm';
}
if (!empty($this->options['clean_ids'])) {
$name = $pref . '_' . str_replace(':', '$', $key);
}
else {
$name = $pref . '_' . $key;
}
$ret[$key] = $name;
}
drupal_alter('search_api_solr_field_mapping', $index, $ret);
$this->fieldNames[$index->machine_name] = $ret;
}
return $this->fieldNames[$index->machine_name];
}
protected function addIndexField(SearchApiSolrDocument $doc, $key, $value, $type, $multi_valued = FALSE) {
$text_content = '';
if (!isset($value)) {
return $text_content;
}
if (search_api_is_list_type($type)) {
$type = substr($type, 5, -1);
foreach ($value as $v) {
$text_content .= $this
->addIndexField($doc, $key, $v, $type, TRUE) . "\n\n";
}
return trim($text_content);
}
switch ($type) {
case 'tokens':
foreach ($value as $v) {
$text_content .= $v['value'] . ' ';
$doc
->addField($key, $v['value']);
}
return trim($text_content);
case 'boolean':
$value = $value ? 'true' : 'false';
break;
case 'date':
$value = is_numeric($value) ? (int) $value : strtotime($value);
if ($value === FALSE) {
return $text_content;
}
$value = format_date($value, 'custom', self::SOLR_DATE_FORMAT, 'UTC');
break;
case 'integer':
$value = (int) $value;
break;
case 'decimal':
$value = (double) $value;
break;
}
if ($multi_valued) {
$doc
->addField($key, $value);
}
else {
$doc
->setField($key, $value);
}
if (search_api_is_text_type($type)) {
$text_content = $value;
}
return $text_content;
}
protected function alterSolrDocuments(array &$documents, SearchApiIndex $index, array $items) {
}
public function deleteItems($ids = 'all', SearchApiIndex $index = NULL) {
$this
->connect();
if (is_array($ids)) {
$index_id = $this
->getIndexId($index->machine_name);
$solr_ids = array();
foreach ($ids as $id) {
$solr_ids[] = $this
->createId($index_id, $id);
}
$this->solr
->deleteByMultipleIds($solr_ids);
}
else {
$query = array();
if ($index) {
$index_id = $this
->getIndexId($index->machine_name);
$index_id = call_user_func(array(
$this
->getConnectionClass(),
'phrase',
), $index_id);
$query[] = "index_id:{$index_id}";
}
if (!empty($this->options['site_hash'])) {
$query[] = 'hash:' . search_api_solr_site_hash();
}
if ($ids != 'all') {
$query[] = $query ? "({$ids})" : $ids;
}
$this->solr
->deleteByQuery($query ? implode(' AND ', $query) : '*:*');
}
$this
->scheduleCommit();
}
public function search(SearchApiQueryInterface $query) {
$time_method_called = microtime(TRUE);
$this->request_handler = NULL;
$index = $query
->getIndex();
$index_id = $this
->getIndexId($index->machine_name);
$fields = $this
->getFieldNames($index);
$this
->connect();
$version = $this->solr
->getSolrVersion();
$keys = $query
->getKeys();
if (is_array($keys)) {
$keys = $this
->flattenKeys($keys);
}
$options = $query
->getOptions();
$search_fields = $this
->getQueryFields($query);
$index_fields = $index
->getFields() + array(
'search_api_relevance' => array(
'type' => 'decimal',
'indexed' => TRUE,
),
'search_api_id' => array(
'type' => 'integer',
'indexed' => TRUE,
),
);
$qf = array();
foreach ($search_fields as $f) {
$boost = isset($index_fields[$f]['boost']) ? '^' . $index_fields[$f]['boost'] : '';
$qf[] = $fields[$f] . $boost;
}
$filter = $query
->getFilter();
$fq = $this
->createFilterQueries($filter, $fields, $index->options['fields']);
$fq[] = 'index_id:' . call_user_func(array(
$this
->getConnectionClass(),
'phrase',
), $index_id);
if (!empty($this->options['site_hash'])) {
$fq[] = 'hash:' . search_api_solr_site_hash();
}
$sort = array();
foreach ($query
->getSort() as $field => $order) {
$f = $fields[$field];
if (substr($f, 0, 3) == 'ss_') {
$f = 'sort_' . substr($f, 3);
}
if ($field == 'search_api_random') {
$params = $query
->getOption('search_api_random_sort', array());
$seed = !empty($params['seed']) ? $params['seed'] : mt_rand();
$f = 'random_' . $seed;
}
$order = strtolower($order);
$sort[$field] = "{$f} {$order}";
}
$facets = $query
->getOption('search_api_facets', array());
$facet_params = $this
->getFacetParams($facets, $fields, $fq);
$highlight_params = $this
->getHighlightParams($query);
$mlt = $query
->getOption('search_api_mlt');
if ($mlt) {
$mlt_params['qt'] = 'mlt';
$mlt_fl = array();
$mlt_no_numeric_fields = FALSE;
if ($version == 4) {
$system_info = $this->solr
->getSystemInfo();
$mlt_no_numeric_fields = !isset($system_info->lucene->{'solr-spec-version'}) || version_compare($system_info->lucene->{'solr-spec-version'}, '4.6.0', '<');
}
foreach ($mlt['fields'] as $f) {
if ($fields[$f][0] === 'd' || $mlt_no_numeric_fields && in_array($fields[$f][0], array(
'i',
'f',
))) {
continue;
}
$mlt_fl[] = $fields[$f];
if (isset($index->options['fields'][$f]['type']) && !search_api_is_text_type($index->options['fields'][$f]['type'])) {
$mlt_params['f.' . $fields[$f] . '.mlt.minwl'] = 0;
}
}
$mlt_params['mlt.fl'] = implode(',', $mlt_fl);
$id = $this
->createId($index_id, $mlt['id']);
$id = call_user_func(array(
$this
->getConnectionClass(),
'phrase',
), $id);
$keys = 'id:' . $id;
if ($version >= 5) {
$facet_params = array();
}
}
if ($spatials = $query
->getOption('search_api_location')) {
foreach ($spatials as $i => $spatial) {
if (!isset($spatial['field'])) {
continue;
}
unset($radius);
$field = $fields[$spatial['field']];
$escaped_field = call_user_func(array(
$this
->getConnectionClass(),
'escapeFieldName',
), $field);
if (isset($spatial['bbox'])) {
if ($version >= 4) {
$bbox = $spatial['bbox'];
$fq[] = $escaped_field . ':[' . (double) $bbox['bottom'] . ',' . (double) $bbox['left'] . ' TO ' . (double) $bbox['top'] . ',' . (double) $bbox['right'] . ']';
}
else {
$warnings[] = t('Filtering by a bounding box is not supported in Solr versions below 4.');
}
}
if (!isset($spatial['lat']) || !isset($spatial['lon'])) {
continue;
}
$point = (double) $spatial['lat'] . ',' . (double) $spatial['lon'];
if (isset($spatial['radius'])) {
$radius = (double) $spatial['radius'];
}
$spatial_method = 'geofilt';
if (isset($spatial['method']) && in_array($spatial['method'], array(
'geofilt',
'bbox',
))) {
$spatial_method = $spatial['method'];
}
foreach ($fq as $key => $value) {
$preg_field = preg_quote($escaped_field, '/');
if (preg_match('/^(?:\\{!tag[^}]+\\})?' . $preg_field . ':\\["?(\\*|\\d+(?:\\.\\d+)?)"? TO "?(\\*|\\d+(?:\\.\\d+)?)"?\\]$/', $value, $m)) {
unset($fq[$key]);
if ($m[1] && is_numeric($m[1])) {
$min_radius = isset($min_radius) ? max($min_radius, $m[1]) : $m[1];
}
if (is_numeric($m[2])) {
$radius = isset($radius) ? min($radius, $m[2]) : $m[2];
}
}
}
if (isset($min_radius) && strpos($field, ':') === FALSE) {
$upper = isset($radius) ? " u={$radius}" : '';
$fq[] = "{!frange l={$min_radius}{$upper}}geodist({$field},{$point})";
}
elseif (isset($radius)) {
$fq[] = "{!{$spatial_method} pt={$point} sfield={$field} d={$radius}}";
}
if (isset($sort[$spatial['field']]) && substr($sort[$spatial['field']], 0, strlen($field)) === $field) {
if (strpos($field, ':') === FALSE) {
$sort[$spatial['field']] = str_replace($field, "geodist({$field},{$point})", $sort[$spatial['field']]);
}
else {
$link = l(t('edit server'), 'admin/config/search/search_api/server/' . $this->server->machine_name . '/edit');
watchdog('search_api_solr', 'Location sort on field @field had to be ignored because unclean field identifiers are used.', array(
'@field' => $spatial['field'],
), WATCHDOG_WARNING, $link);
}
}
if (!empty($spatial['distance']) && $version >= 4) {
if (strpos($field, ':') === FALSE) {
$location_fields[] = '_' . $field . '_distance_:geodist(' . $field . ',' . $point . ')';
}
else {
$link = l(t('edit server'), 'admin/config/search/search_api/server/' . $this->server->machine_name . '/edit');
watchdog('search_api_solr', "Location distance information can't be added because unclean field identifiers are used.", array(), WATCHDOG_WARNING, $link);
}
}
if (!empty($facets)) {
if (!empty($facet_params['facet.field'])) {
$facet_params['facet.field'] = array_diff($facet_params['facet.field'], array(
$field,
));
}
foreach ($facets as $delta => $facet) {
if ($facet['field'] != $spatial['field']) {
continue;
}
$steps = $facet['limit'] > 0 ? $facet['limit'] : 5;
$step = (isset($radius) ? $radius : 100) / $steps;
for ($k = $steps - 1; $k > 0; --$k) {
$distance = $step * $k;
$key = "spatial-{$delta}-{$distance}";
$facet_params['facet.query'][] = "{!{$spatial_method} pt={$point} sfield={$field} d={$distance} key={$key}}";
}
foreach (array(
'limit',
'mincount',
'missing',
) as $setting) {
unset($facet_params["f.{$field}.facet.{$setting}"]);
}
}
}
}
}
foreach ($sort as $field => $sort_param) {
if (substr($sort_param, 0, 3) === 'loc') {
unset($sort[$field]);
}
}
$grouping = $query
->getOption('search_api_grouping');
if (!empty($grouping['use_grouping'])) {
$group_params['group'] = 'true';
$group_params['group.ngroups'] = 'true';
if (!empty($grouping['truncate'])) {
$group_params['group.truncate'] = 'true';
}
if (!empty($grouping['group_facet'])) {
$group_params['group.facet'] = 'true';
}
foreach ($grouping['fields'] as $collapse_field) {
$type = $index_fields[$collapse_field]['type'];
if ($version < 4) {
if (search_api_is_list_type($type) || !search_api_is_text_type($type, array(
'string',
'boolean',
'uri',
))) {
$warnings[] = t('Grouping is not supported for field @field. ' . 'Only single-valued fields of type "String", "Boolean" or "URI" are supported.', array(
'@field' => $index_fields[$collapse_field]['name'],
));
continue;
}
}
else {
if (search_api_is_list_type($type) || search_api_is_text_type($type)) {
$warnings[] = t('Grouping is not supported for field @field. ' . 'Only single-valued fields not indexed as "Fulltext" are supported.', array(
'@field' => $index_fields[$collapse_field]['name'],
));
continue;
}
}
$group_params['group.field'][] = $fields[$collapse_field];
}
if (empty($group_params['group.field'])) {
unset($group_params);
}
else {
if (!empty($grouping['group_sort'])) {
foreach ($grouping['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);
}
if ($group_sort_field == 'search_api_random') {
$params = $query
->getOption('search_api_random_sort', array());
$seed = !empty($params['seed']) ? $params['seed'] : mt_rand();
$f = 'random_' . $seed;
}
$order = strtolower($order);
$group_params['group.sort'][] = $f . ' ' . $order;
}
}
if (!empty($group_params['group.sort'])) {
$group_params['group.sort'] = implode(', ', $group_params['group.sort']);
}
}
if (!empty($grouping['group_limit']) && $grouping['group_limit'] != 1) {
$group_params['group.limit'] = $grouping['group_limit'];
}
}
}
if (!$keys) {
$keys = NULL;
}
$params = array(
'fl' => 'item_id,score',
'qf' => $qf,
'fq' => $fq,
);
if (isset($options['offset'])) {
$params['start'] = $options['offset'];
}
$params['rows'] = isset($options['limit']) ? $options['limit'] : 1000000;
if ($sort) {
$params['sort'] = implode(', ', $sort);
}
if (!empty($facet_params['facet.field'])) {
$params += $facet_params;
}
if (!empty($highlight_params)) {
$params += $highlight_params;
}
if (!empty($options['search_api_spellcheck'])) {
$params['spellcheck'] = 'true';
}
if (!empty($mlt_params['mlt.fl'])) {
$params += $mlt_params;
}
if (!empty($group_params)) {
$params += $group_params;
}
if (!empty($this->options['retrieve_data'])) {
$params['fl'] = '*,score';
}
if (!empty($location_fields)) {
$params['fl'] .= ',' . implode(',', $location_fields);
}
$http_method = !empty($this->options['http_method']) ? $this->options['http_method'] : 'AUTO';
$call_args = array(
'query' => &$keys,
'params' => &$params,
'http_method' => &$http_method,
);
if ($this->request_handler) {
$this
->setRequestHandler($this->request_handler, $call_args);
}
try {
$time_processing_done = microtime(TRUE);
drupal_alter('search_api_solr_query', $call_args, $query);
$this
->preQuery($call_args, $query);
$response = $this->solr
->search($keys, $params, $http_method);
$time_query_done = microtime(TRUE);
$results = $this
->extractResults($query, $response);
if (!empty($warnings)) {
$results['warnings'] = isset($results['warnings']) ? array_merge($warnings, $results['warnings']) : $warnings;
}
if ($facets = $this
->extractFacets($query, $response)) {
$results['search_api_facets'] = $facets;
}
drupal_alter('search_api_solr_search_results', $results, $query, $response);
$this
->postQuery($results, $query, $response);
$time_end = microtime(TRUE);
$results['performance'] = array(
'complete' => $time_end - $time_method_called,
'preprocessing' => $time_processing_done - $time_method_called,
'execution' => $time_query_done - $time_processing_done,
'postprocessing' => $time_end - $time_query_done,
);
return $results;
} catch (SearchApiException $e) {
throw new SearchApiException(t('An error occurred while trying to search with Solr: @msg.', array(
'@msg' => $e
->getMessage(),
)));
}
}
protected function extractResults(SearchApiQueryInterface $query, $response) {
$index = $query
->getIndex();
$fields = $this
->getFieldNames($index);
$field_options = $index->options['fields'];
$version = $this->solr
->getSolrVersion();
$results = array();
$results['results'] = array();
$results['search_api_solr_response'] = $response;
if (!isset($response->response) && !isset($response->grouped)) {
$results['result count'] = 0;
return $results;
}
$grouping = $query
->getOption('search_api_grouping');
if (!empty($grouping['use_grouping']) && !empty($response->grouped)) {
$docs = array();
$results['result count'] = 0;
foreach ($grouping['fields'] as $field) {
if (!empty($response->grouped->{$fields[$field]})) {
$results['result count'] += $response->grouped->{$fields[$field]}->ngroups;
foreach ($response->grouped->{$fields[$field]}->groups as $group) {
foreach ($group->doclist->docs as $doc) {
$docs[] = $doc;
}
}
}
}
}
else {
$results['result count'] = $response->response->numFound;
$docs = $response->response->docs;
}
$spatials = $query
->getOption('search_api_location');
foreach ($docs as $doc) {
$result = array(
'id' => NULL,
'score' => NULL,
'fields' => array(),
);
foreach ($fields as $search_api_property => $solr_property) {
if (isset($doc->{$solr_property})) {
$value = $doc->{$solr_property};
$first_value = $value;
while (is_array($first_value)) {
$first_value = reset($first_value);
}
if (isset($field_options[$search_api_property]['type']) && search_api_extract_inner_type($field_options[$search_api_property]['type']) === 'date' && preg_match('/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$/', $first_value)) {
$value = is_array($value) ? array_map('strtotime', $value) : strtotime($value);
}
$result['fields'][$search_api_property] = $value;
}
}
$result['id'] = $result['fields']['search_api_id'];
$result['score'] = $result['fields']['search_api_relevance'];
if ($spatials) {
foreach ($spatials as $spatial) {
if (isset($spatial['field']) && !empty($spatial['distance'])) {
if ($version >= 4) {
$doc_field = '_' . $fields[$spatial['field']] . '_distance_';
if (!empty($doc->{$doc_field})) {
$results['search_api_location'][$spatial['field']][$result['id']]['distance'] = $doc->{$doc_field};
}
}
}
}
}
$index_id = $this
->getIndexId($index->machine_name);
$solr_id = $this
->createId($index_id, $result['id']);
$excerpt = $this
->getExcerpt($response, $solr_id, $result['fields'], $fields, $this
->getQueryFields($query));
if ($excerpt) {
$result['excerpt'] = $excerpt;
}
if ($result['id']) {
$results['results'][$result['id']] = $result;
}
}
if (module_exists('search_api_spellcheck') && $query
->getOption('search_api_spellcheck')) {
$results['search_api_spellcheck'] = new SearchApiSpellcheckSolr($response);
}
return $results;
}
protected function getExcerpt($response, $id, array &$fields, array $field_mapping, array $fulltext_fields) {
if (!isset($response->highlighting->{$id})) {
return FALSE;
}
$output = '';
$highlighting = $response->highlighting->{$id};
$highlight_fields = !empty($this->options['highlight_data']);
$create_excerpt = !empty($this->options['excerpt']);
if (!$highlight_fields && !$create_excerpt) {
return FALSE;
}
$excerpt_parts = array();
$field_mapping = array_flip($field_mapping);
$fulltext_fields = drupal_map_assoc($fulltext_fields);
foreach ($highlighting as $solr_property => $values) {
$values = (array) $values;
if (empty($field_mapping[$solr_property])) {
continue;
}
$search_api_property = $field_mapping[$solr_property];
if (isset($fulltext_fields[$search_api_property])) {
$excerpt_parts = array_merge($excerpt_parts, $values);
}
if (!$highlight_fields) {
continue;
}
$values = $this
->sanitizeHighlightValue($values, $search_api_property);
$orig_values = preg_replace('#\\[(/?)HIGHLIGHT\\]#', '', $values);
$field_values = array();
if (!empty($fields[$search_api_property])) {
$field_values = $this
->sanitizeHighlightValue($fields[$search_api_property]);
}
foreach ($field_values as $delta => $field_value) {
foreach ($orig_values as $num => $item) {
if ($item === $field_value) {
$field_values[$delta] = $this
->formatHighlighting($values[$num]);
$change = TRUE;
continue 2;
}
}
}
if (!empty($change)) {
$fields[$search_api_property] = array(
'#value' => $field_values,
'#sanitize_callback' => FALSE,
);
}
}
if ($create_excerpt && $excerpt_parts) {
$excerpt = array();
$excerpt_length = 0;
foreach ($excerpt_parts as $value) {
$value = strip_tags($value);
foreach ($this
->extractHighlightingSnippets($value) as $snippet) {
$excerpt[] = $snippet;
$excerpt_length += drupal_strlen($snippet);
if (count($excerpt) >= 3 || $excerpt_length >= 300) {
break 2;
}
}
}
if ($excerpt) {
$output = implode(' … ', $excerpt) . ' …';
}
}
return $output;
}
protected function sanitizeHighlightValue($value, $field_id = NULL) {
if (is_array($value)) {
foreach ($value as $i => $nested_value) {
$value[$i] = $this
->sanitizeHighlightValue($nested_value, $field_id);
}
return $value;
}
return check_plain(strip_tags($value));
}
protected function formatHighlighting($snippet) {
$search = array(
'[HIGHLIGHT]',
'[/HIGHLIGHT]',
);
return str_replace($search, $this
->getHighlightingPrefixSuffix(), $snippet);
}
protected function getHighlightingPrefixSuffix() {
$prefix = '<strong>';
$suffix = '</strong>';
if (!empty($this->options['highlight_prefix'])) {
$prefix = $this->options['highlight_prefix'];
}
if (!empty($this->options['highlight_suffix'])) {
$suffix = $this->options['highlight_suffix'];
}
return array(
$prefix,
$suffix,
);
}
protected function extractHighlightingSnippets($value) {
$parts = preg_split('#\\[/?HIGHLIGHT\\]#', $value);
$num_parts = count($parts);
if ($num_parts < 3) {
return array();
}
$snippets = array();
$snippet = '';
$combined_length = 0;
foreach ($parts as $i => $part) {
if ($i % 2 === 1) {
$snippet .= '[HIGHLIGHT]' . $part . '[/HIGHLIGHT]';
continue;
}
if ($snippet && drupal_strlen($part) < 60) {
$snippet .= $part;
continue;
}
if ($snippet) {
$space = strpos($part, ' ', 25);
if ($space === FALSE) {
$space = 30;
}
$snippet .= ' ' . substr($part, 0, $space);
$combined_length += drupal_strlen($snippet);
$snippets[] = $this
->sanitizeAndFormatExcerptSnippet($snippet);
$snippet = '';
if (count($snippets) >= 3 || $combined_length >= 300) {
break;
}
$part = substr($part, $space);
}
if ($num_parts <= $i + 1) {
break;
}
$length = drupal_strlen($part);
if ($length > 30) {
$space = strrpos(substr($part, 0, -25), ' ');
if ($space === FALSE) {
$space = $length - 30;
}
$part = substr($part, $space + 1);
}
$snippet = $part;
}
if ($snippet) {
$snippets[] = $this
->sanitizeAndFormatExcerptSnippet($snippet);
}
return $snippets;
}
protected function extractFacets(SearchApiQueryInterface $query, $response) {
$facets = array();
if (!isset($response->facet_counts)) {
return $facets;
}
$index = $query
->getIndex();
$fields = $this
->getFieldNames($index);
$extract_facets = $query
->getOption('search_api_facets', array());
$facet_fields = array();
$facet_queries = array();
if (isset($response->facet_counts->facet_fields)) {
$facet_fields = $response->facet_counts->facet_fields;
}
if (isset($response->facet_counts->facet_queries)) {
$facet_queries = $response->facet_counts->facet_queries;
}
$empty_key = key((array) json_decode('{"":5}'));
foreach ($extract_facets as $delta => $info) {
$field = $fields[$info['field']];
if (!empty($info['solr_facet_query'])) {
if (!empty($facet_queries)) {
foreach ($facet_queries as $term => $count) {
$term = preg_replace('/^{[^}]+}/', '', $term);
if (strpos($term, $field . ':') === 0) {
$term = substr($term, strlen($field) + 1);
if (!preg_match('/^(?:[(\\[][^ ]+ .*[)\\]]|".*"|!)$/', $term)) {
$term = "\"{$term}\"";
}
$facets[$delta][] = array(
'filter' => $term,
'count' => $count,
'solr_facet_query' => TRUE,
);
}
}
}
}
elseif (!empty($facet_fields->{$field})) {
$min_count = $info['min_count'];
$terms = $facet_fields->{$field};
if ($info['missing']) {
if (isset($terms->{$empty_key})) {
if ($terms->{$empty_key} < $min_count) {
unset($terms->{$empty_key});
}
else {
$terms = (array) $terms;
arsort($terms);
if ($info['limit'] > 0 && count($terms) > $info['limit']) {
array_pop($terms);
}
}
}
}
elseif (isset($terms->{$empty_key})) {
$terms = clone $terms;
unset($terms->{$empty_key});
}
$type = isset($index->options['fields'][$info['field']]['type']) ? search_api_extract_inner_type($index->options['fields'][$info['field']]['type']) : 'string';
foreach ($terms as $term => $count) {
if ($count >= $min_count) {
if ($term === $empty_key) {
$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]);
}
}
}
if ($facet_queries && $query
->getOption('search_api_location')) {
foreach ($facet_queries as $key => $count) {
if (!preg_match('/^spatial-(.*)-(\\d+(?:\\.\\d+)?)$/', $key, $m)) {
continue;
}
if (empty($extract_facets[$m[1]])) {
continue;
}
$facet = $extract_facets[$m[1]];
if ($count >= $facet['min_count']) {
$facets[$m[1]][] = array(
'filter' => "[* {$m[2]}]",
'count' => $count,
);
}
}
}
return $facets;
}
protected function flattenKeys(array $keys) {
$k = array();
$or = $keys['#conjunction'] == 'OR';
$neg = !empty($keys['#negation']);
foreach (element_children($keys) as $i) {
$key = $keys[$i];
if (!$key) {
continue;
}
if (is_array($key)) {
$subkeys = $this
->flattenKeys($key);
if ($subkeys) {
$nested_expressions = TRUE;
if ($or && $neg) {
$subkeys = "({$subkeys})";
}
$k[] = $subkeys;
}
}
else {
$key = trim($key);
$key = call_user_func(array(
$this
->getConnectionClass(),
'phrase',
), $key);
$k[] = $key;
}
}
if (!$k) {
return '';
}
if (count($k) == 1 && empty($nested_expressions)) {
$k = reset($k);
return $neg ? "*:* AND -{$k}" : $k;
}
if ($or) {
if ($neg) {
return '*:* AND -' . implode(' AND -', $k);
}
return '((' . implode(') OR (', $k) . '))';
}
$k = implode(' AND ', $k);
return $neg ? "*:* AND -({$k})" : $k;
}
protected function createFilterQueries(SearchApiQueryFilterInterface $filter, array $solr_fields, array $fields) {
$or = $filter
->getConjunction() == 'OR';
$fq = array();
$prefix = '';
foreach ($filter
->getFilters() as $f) {
if (is_array($f)) {
if (!isset($fields[$f[0]])) {
throw new SearchApiException(t('Filter term on unknown or unindexed field @field.', array(
'@field' => $f[0],
)));
}
if ($f[1] !== '') {
$fq[] = $this
->createFilterQuery($solr_fields[$f[0]], $f[1], $f[2], $fields[$f[0]]);
}
}
elseif ($f instanceof SearchApiQueryFilterInterface) {
$q = $this
->createFilterQueries($f, $solr_fields, $fields);
if ($filter
->getConjunction() != $f
->getConjunction() && count($q) > 1) {
$fq[] = '((' . implode(') ' . $f
->getConjunction() . ' (', $q) . '))';
}
else {
$fq = array_merge($fq, $q);
}
}
}
if (method_exists($filter, 'getTags')) {
foreach ($filter
->getTags() as $tag) {
$prefix = "{!tag={$tag}}";
break;
}
}
if ($or && count($fq) > 1) {
$fq = array(
'((' . implode(') OR (', $fq) . '))',
);
}
if ($prefix) {
foreach ($fq as $i => $filter) {
$fq[$i] = $prefix . $filter;
}
}
return $fq;
}
protected function createFilterQuery($field, $value, $operator, $field_info) {
$field = call_user_func(array(
$this
->getConnectionClass(),
'escapeFieldName',
), $field);
if (isset($field_info['real_type']) && $field_info['real_type'] == 'location') {
if ($value === NULL) {
$field .= '_0___tdouble';
}
}
if ($value === NULL) {
return ($operator == '=' ? '*:* AND -' : '') . "{$field}:[* TO *]";
}
$type = search_api_extract_inner_type($field_info['type']);
if (!is_array($value)) {
$value = $this
->formatFilterValue($value, $type);
}
else {
foreach ($value as &$val) {
$val = $this
->formatFilterValue($val, $type);
}
unset($val);
}
switch (strtoupper($operator)) {
case '<>':
return "*:* AND -({$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}:[{$value[0]} TO {$value[1]}]";
case 'NOT BETWEEN':
return "*:* AND -{$field}:[{$value[0]} TO {$value[1]}]";
default:
return "{$field}:{$value}";
}
}
protected function formatFilterValue($value, $type) {
switch ($type) {
case 'boolean':
$value = $value ? 'true' : 'false';
break;
case 'date':
$value = is_numeric($value) ? (int) $value : strtotime($value);
if ($value === FALSE) {
return 0;
}
$value = format_date($value, 'custom', self::SOLR_DATE_FORMAT, 'UTC');
break;
case 'text':
return '(' . call_user_func(array(
$this
->getConnectionClass(),
'escape',
), $value) . ')';
}
return call_user_func(array(
$this
->getConnectionClass(),
'phrase',
), $value);
}
protected function getFacetParams(array $facets, array $fields, array &$fq = array()) {
if (!$facets) {
return array();
}
$facet_params['facet'] = 'true';
$facet_params['facet.sort'] = 'count';
$facet_params['facet.limit'] = 10;
$facet_params['facet.mincount'] = 1;
$facet_params['facet.missing'] = 'false';
foreach ($facets as $info) {
if (empty($fields[$info['field']])) {
continue;
}
$field = $fields[$info['field']];
if (isset($info['operator']) && $info['operator'] === 'or') {
$tag = 'facet:' . $info['field'];
if (!empty($info['solr_facet_query'])) {
foreach ($info['solr_facet_query'] as $expression) {
$facet_params['facet.query'][] = "{!ex={$tag}}{$field}:{$expression}";
}
}
else {
$facet_params['facet.field'][] = "{!ex={$tag}}{$field}";
}
}
elseif (!empty($info['solr_facet_query'])) {
foreach ($info['solr_facet_query'] as $expression) {
$facet_params['facet.query'][] = "{$field}:{$expression}";
}
}
else {
$facet_params['facet.field'][] = $field;
}
if ($info['limit'] != 10) {
$facet_params["f.{$field}.facet.limit"] = $info['limit'] ? $info['limit'] : -1;
}
if ($info['min_count'] != 1) {
$facet_params["f.{$field}.facet.mincount"] = $info['min_count'];
}
if ($info['missing']) {
$facet_params["f.{$field}.facet.missing"] = 'true';
}
}
return $facet_params;
}
protected function getHighlightParams($query) {
$highlight_params = array();
if (!empty($this->options['excerpt']) || !empty($this->options['highlight_data'])) {
$highlight_params['hl'] = 'true';
$highlight_params['hl.fl'] = variable_get('search_api_solr_highlight_prefix', 'tm_') . '*';
$highlight_params['hl.simple.pre'] = '[HIGHLIGHT]';
$highlight_params['hl.simple.post'] = '[/HIGHLIGHT]';
$highlight_params['hl.snippets'] = 1;
$highlight_params['hl.fragsize'] = 0;
}
return $highlight_params;
}
protected function setRequestHandler($handler, array &$call_args) {
if ($handler == 'pinkPony') {
$call_args['params']['qt'] = $handler;
return TRUE;
}
return FALSE;
}
protected function preQuery(array &$call_args, SearchApiQueryInterface $query) {
}
protected function postQuery(array &$results, SearchApiQueryInterface $query, $response) {
}
public function getAutocompleteSuggestions(SearchApiQueryInterface $query, SearchApiAutocompleteSearch $search, $incomplete_key, $user_input) {
$suggestions = array();
$this->request_handler = NULL;
$incomp = drupal_strtolower($incomplete_key);
$index = $query
->getIndex();
$fields = $this
->getFieldNames($index);
$complete = $query
->getOriginalKeys();
$keys = $query
->getKeys();
if (is_array($keys)) {
$keys_array = array();
while ($keys) {
reset($keys);
if (!element_child(key($keys))) {
array_shift($keys);
continue;
}
$key = array_shift($keys);
if (is_array($key)) {
$keys = array_merge($keys, $key);
}
else {
$keys_array[$key] = $key;
}
}
$keys = $this
->flattenKeys($query
->getKeys());
}
else {
$keys_array = drupal_map_assoc(preg_split('/[-\\s():{}\\[\\]\\\\"]+/', $keys, -1, PREG_SPLIT_NO_EMPTY));
}
if (!$keys) {
$keys = NULL;
}
$search_fields = $this
->getQueryFields($query);
$qf = array();
foreach ($search_fields as $f) {
$qf[] = $fields[$f];
}
$fq = $this
->createFilterQueries($query
->getFilter(), $fields, $index->options['fields']);
$index_id = $this
->getIndexId($index->machine_name);
$fq[] = 'index_id:' . call_user_func(array(
$this
->getConnectionClass(),
'phrase',
), $index_id);
if (!empty($this->options['site_hash'])) {
$fq[] = 'hash:' . search_api_solr_site_hash();
}
$facet_fields = array();
foreach ($search_fields as $f) {
$facet_fields[] = $fields[$f];
}
$limit = $query
->getOption('limit', 10);
$params = array(
'qf' => $qf,
'fq' => $fq,
'rows' => 0,
'facet' => 'true',
'facet.field' => $facet_fields,
'facet.prefix' => $incomp,
'facet.limit' => $limit * 5,
'facet.mincount' => 1,
'spellcheck' => !isset($this->options['autocorrect_spell']) || $this->options['autocorrect_spell'] ? 'true' : 'false',
'spellcheck.count' => 1,
);
$http_method = !empty($this->options['http_method']) ? $this->options['http_method'] : 'AUTO';
$call_args = array(
'query' => &$keys,
'params' => &$params,
'http_method' => &$http_method,
);
if ($this->request_handler) {
$this
->setRequestHandler($this->request_handler, $call_args);
}
$second_pass = !isset($this->options['autocorrect_suggest_words']) || $this->options['autocorrect_suggest_words'];
$alter_data = array(
'search' => $search,
'query' => $query,
'incomplete_key' => $incomplete_key,
'user_input' => $user_input,
);
for ($i = 0; $i < ($second_pass ? 2 : 1); ++$i) {
try {
$this
->connect();
drupal_alter('search_api_solr_query', $call_args, $query);
$this
->preQuery($call_args, $query);
$response = $this->solr
->search($keys, $params, $http_method);
$alter_data['responses'][] = $response;
if (!empty($response->spellcheck->suggestions)) {
$replace = array();
foreach ($response->spellcheck->suggestions as $word => $data) {
$replace[$word] = $data->suggestion[0];
}
$corrected = str_ireplace(array_keys($replace), array_values($replace), $user_input);
if ($corrected != $user_input) {
array_unshift($suggestions, array(
'prefix' => t('Did you mean') . ':',
'user_input' => $corrected,
));
}
}
$matches = array();
if (isset($response->facet_counts->facet_fields)) {
foreach ($response->facet_counts->facet_fields as $terms) {
foreach ($terms as $term => $count) {
if (isset($matches[$term])) {
$matches[$term] = max($matches[$term], $count);
}
else {
$matches[$term] = $count;
}
}
}
if ($matches) {
foreach ($matches as $term => $count) {
if (strlen($term) < 3 || isset($keys_array[$term])) {
unset($matches[$term]);
}
}
$result_count = $response->response->numFound;
$max_occurrences = $result_count * variable_get('search_api_solr_autocomplete_max_occurrences', 0.9);
if (($max_occurrences >= 1 || $i > 0) && $max_occurrences < $result_count) {
foreach ($matches as $match => $count) {
if ($count > $max_occurrences) {
unset($matches[$match]);
}
}
}
arsort($matches);
$additional_matches = array_slice($matches, $limit - count($suggestions), NULL, TRUE);
$matches = array_slice($matches, 0, $limit, TRUE);
$incomp_length = strlen($incomp);
foreach ($matches as $term => $count) {
if (drupal_strtolower(substr($term, 0, $incomp_length)) == $incomp) {
$suggestions[] = array(
'suggestion_suffix' => substr($term, $incomp_length),
'term' => $term,
'results' => $count,
);
}
else {
$suggestions[] = array(
'suggestion_suffix' => ' ' . $term,
'term' => $term,
'results' => $count,
);
}
}
}
}
} catch (SearchApiException $e) {
watchdog_exception('search_api_solr', $e, "%type during autocomplete Solr query: !message in %function (line %line of %file).", array(), WATCHDOG_WARNING);
}
if (count($suggestions) >= $limit) {
break;
}
unset($params['facet.prefix']);
$keys = trim($keys . ' ' . $incomplete_key);
}
drupal_alter('search_api_solr_autocomplete_suggestions', $suggestions, $alter_data);
return $suggestions;
}
public function queryMultiple(array $options = array()) {
return search_api_multi_query($options);
}
public function searchMultiple(SearchApiMultiQueryInterface $query) {
$time_method_called = microtime(TRUE);
$solr_fields = array(
'search_api_id' => 'item_id',
'search_api_relevance' => 'score',
'search_api_multi_index' => 'index_id',
);
$fields = array(
'search_api_multi_index' => array(
'type' => 'string',
),
);
foreach ($query
->getIndexes() as $index) {
if (empty($index->options['fields'])) {
continue;
}
$prefix = $this
->getIndexId($index->machine_name) . ':';
foreach ($this
->getFieldNames($index) as $field => $key) {
if (!isset($solr_fields[$field])) {
$solr_fields[$prefix . $field] = $key;
}
}
foreach ($index->options['fields'] as $field => $info) {
$fields[$prefix . $field] = $info;
}
}
$keys = $query
->getKeys();
if (is_array($keys)) {
$keys = $this
->flattenKeys($keys);
}
$search_fields = $query
->getFields();
$qf = array();
foreach ($search_fields as $f) {
$boost = isset($fields[$f]['boost']) ? '^' . $fields[$f]['boost'] : '';
$qf[] = $solr_fields[$f] . $boost;
}
$filter = $query
->getFilter();
$fq = $this
->createFilterQueries($filter, $solr_fields, $fields);
$index_filter = array();
$indexes = array();
foreach ($query
->getIndexes() as $index) {
$index_id = $this
->getIndexId($index->machine_name);
$indexes[$index_id] = $index;
$index_filter[] = 'index_id:' . call_user_func(array(
$this
->getConnectionClass(),
'phrase',
), $index_id);
}
$fq[] = implode(' OR ', $index_filter);
if (!empty($this->options['site_hash'])) {
$fq[] = 'hash:' . search_api_solr_site_hash();
}
$sort = array();
foreach ($query
->getSort() as $f => $order) {
$f = $solr_fields[$f];
if (substr($f, 0, 3) == 'ss_') {
$f = 'sort_' . substr($f, 3);
}
$order = strtolower($order);
$sort[] = "{$f} {$order}";
}
$facets = $query
->getOption('search_api_facets') ? $query
->getOption('search_api_facets') : array();
$facet_params = $this
->getFacetParams($facets, $solr_fields, $fq);
$highlight_params = $this
->getHighlightParams($query);
if (!$keys) {
$keys = NULL;
}
$options = $query
->getOptions();
$params = array(
'fl' => 'item_id,index_id,score',
'qf' => $qf,
'fq' => $fq,
);
if (isset($options['offset'])) {
$params['start'] = $options['offset'];
}
if (isset($options['limit'])) {
$params['rows'] = $options['limit'];
}
if ($sort) {
$params['sort'] = implode(', ', $sort);
}
if (!empty($facet_params['facet.field'])) {
$params += $facet_params;
}
if (!empty($highlight_params)) {
$params += $highlight_params;
}
if (!empty($this->options['retrieve_data'])) {
$params['fl'] = '*,score';
}
$http_method = !empty($this->options['http_method']) ? $this->options['http_method'] : 'AUTO';
$time_processing_done = microtime(TRUE);
$this
->connect();
$call_args = array(
'query' => &$keys,
'params' => &$params,
'http_method' => &$http_method,
);
drupal_alter('search_api_solr_multi_query', $call_args, $query);
$response = $this->solr
->search($keys, $params, $http_method);
$time_query_done = microtime(TRUE);
$results = array();
$results['result count'] = $response->response->numFound;
$results['results'] = array();
$fulltext_fields_by_index = array();
foreach ($search_fields as $field) {
list($index_id, $field) = explode(':', $field, 2);
$fulltext_fields_by_index[$index_id][] = $field;
}
foreach ($response->response->docs as $id => $doc) {
$index_id = $doc->index_id;
if (isset($indexes[$index_id])) {
$index = $indexes[$index_id];
}
else {
$index = new SearchApiIndex(array(
'machine_name' => $index_id,
));
}
$fields = $this
->getFieldNames($index);
$field_options = $index->options['fields'];
$result = array(
'id' => NULL,
'index_id' => $index_id,
'score' => NULL,
'fields' => array(),
);
$solr_id = $this
->createId($index_id, $doc->item_id);
foreach ($fields as $search_api_property => $solr_property) {
if (isset($doc->{$solr_property})) {
$value = $doc->{$solr_property};
$first_value = $value;
while (is_array($first_value)) {
$first_value = reset($first_value);
}
if (isset($field_options[$search_api_property]['type']) && search_api_extract_inner_type($field_options[$search_api_property]['type']) === 'date' && preg_match('/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$/', $first_value)) {
$value = is_array($value) ? array_map('strtotime', $value) : strtotime($value);
}
$result['fields'][$search_api_property] = $value;
}
}
$fulltext_fields = isset($fulltext_fields_by_index[$index_id]) ? $fulltext_fields_by_index[$index_id] : array();
$excerpt = $this
->getExcerpt($response, $solr_id, $result['fields'], $fields, $fulltext_fields);
if ($excerpt) {
$result['excerpt'] = $excerpt;
}
$result['id'] = $result['fields']['search_api_id'];
$result['score'] = $result['fields']['search_api_relevance'];
$results['results'][$id] = $result;
}
if (isset($response->facet_counts->facet_fields)) {
$results['search_api_facets'] = array();
$facet_fields = $response->facet_counts->facet_fields;
$empty_key = key((array) json_decode('{"":5}'));
foreach ($facets as $delta => $info) {
$field = $solr_fields[$info['field']];
if (!empty($facet_fields->{$field})) {
$min_count = $info['min_count'];
$terms = $facet_fields->{$field};
if ($info['missing']) {
if (isset($terms->{$empty_key})) {
if ($terms->{$empty_key} < $min_count) {
unset($terms->{$empty_key});
}
else {
$terms = (array) $terms;
arsort($terms);
if ($info['limit'] > 0 && count($terms) > $info['limit']) {
array_pop($terms);
}
}
}
}
elseif (isset($terms->{$empty_key})) {
$terms = clone $terms;
unset($terms->{$empty_key});
}
$type = isset($fields[$info['field']]['type']) ? search_api_extract_inner_type($fields[$info['field']]['type']) : 'string';
foreach ($terms as $term => $count) {
if ($count >= $min_count) {
if ($term === $empty_key) {
$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) {
$results['search_api_facets'][$delta][] = array(
'filter' => $term,
'count' => $count,
);
}
}
}
if (empty($results['search_api_facets'][$delta])) {
unset($results['search_api_facets'][$delta]);
}
}
}
}
drupal_alter('search_api_solr_multi_search_results', $results, $query, $response);
$time_end = microtime(TRUE);
$results['performance'] = array(
'complete' => $time_end - $time_method_called,
'preprocessing' => $time_processing_done - $time_method_called,
'execution' => $time_query_done - $time_processing_done,
'postprocessing' => $time_end - $time_query_done,
);
return $results;
}
public function ping() {
$this
->connect();
return $this->solr
->ping();
}
public function commit() {
if (!empty($this->options['commits_disabled'])) {
return;
}
try {
$this
->connect();
return $this->solr
->commit(FALSE);
} catch (SearchApiException $e) {
watchdog_exception('search_api_solr', $e, '%type while trying to commit on server @server: !message in %function (line %line of %file).', array(
'@server' => $this->server->machine_name,
), WATCHDOG_WARNING);
}
}
public function scheduleCommit() {
if (!$this->commitScheduled) {
$this->commitScheduled = TRUE;
drupal_register_shutdown_function(array(
$this,
'commit',
));
}
}
public function getConnectionClass() {
return variable_get('search_api_solr_connection_class', $this->connection_class);
}
public function setConnectionClass($class) {
$this->connection_class = $class;
$this->solr = NULL;
}
public function getSolrConnection() {
$this
->connect();
return $this->solr;
}
public function getFields($num_terms = 0) {
$this
->connect();
return $this->solr
->getFields($num_terms);
}
public function getFile($file = NULL) {
$this
->connect();
$file_servlet_name = constant($this
->getConnectionClass() . '::FILE_SERVLET');
$params['contentType'] = 'text/xml;charset=utf-8';
if ($file) {
$params['file'] = $file;
}
return $this->solr
->makeServletRequest($file_servlet_name, $params);
}
protected function getIndexId($machine_name) {
$id = variable_get('search_api_solr_index_prefix_' . $machine_name, '') . $machine_name;
$id = variable_get('search_api_solr_index_prefix', '') . $id;
return $id;
}
protected function getQueryFields(SearchApiQueryInterface $query) {
$fulltext_fields = $query
->getFields();
$index_fields = $query
->getIndex()
->getFulltextFields();
return $fulltext_fields === NULL ? $index_fields : array_intersect($fulltext_fields, $index_fields);
}
protected function sanitizeAndFormatExcerptSnippet($snippet) {
$snippet = check_plain($snippet);
$snippet = $this
->formatHighlighting($snippet);
$snippet = trim($snippet, "\0../:;=?..@[..`");
return $snippet;
}
}