updated to 7.x-1.4

This commit is contained in:
Bachir Soussi Chiadmi
2014-02-07 10:05:53 +01:00
parent 62b7436183
commit d7303905a0
18 changed files with 596 additions and 290 deletions

View File

@@ -79,7 +79,7 @@ class SearchApiSolrService extends SearchApiAbstractService {
'excerpt' => FALSE,
'retrieve_data' => FALSE,
'highlight_data' => FALSE,
'http_method' => 'POST',
'http_method' => 'AUTO',
// Default to TRUE for new servers, but to FALSE for existing ones.
'clean_ids' => $this->options ? FALSE : TRUE,
'autocorrect_spell' => TRUE,
@@ -197,9 +197,10 @@ class SearchApiSolrService extends SearchApiAbstractService {
$form['advanced']['http_method'] = array(
'#type' => 'select',
'#title' => t('HTTP method'),
'#description' => t('The HTTP method to use for sending queries. Usually, POST will work fine in all cases.'),
'#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',
),
@@ -265,55 +266,157 @@ class SearchApiSolrService extends SearchApiAbstractService {
}
/**
* Overrides SearchApiAbstractService::supportsFeature().
* {@inheritdoc}
*/
public function supportsFeature($feature) {
// Search API features.
$supported = array(
// First, check the features we always support.
$supported = drupal_map_assoc(array(
'search_api_autocomplete',
'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',
);
// Custom data types.
foreach (search_api_solr_get_dynamic_field_info() as $type => $info) {
$supported[] = 'search_api_data_type_' . $type;
));
if (isset($supported[$feature])) {
return TRUE;
}
$supported = drupal_map_assoc($supported);
return isset($supported[$feature]);
// If it is a custom data type, maybe we support it automatically via
// search_api_solr_hook_search_api_data_type_info().
if (substr($feature, 0, 21) != 'search_api_data_type_') {
return FALSE;
}
$type = substr($feature, 21);
$type = search_api_get_data_type_info($type);
// We only support it if the "prefix" key is set.
return $type && !empty($type['prefix']);
}
/**
* Overrides SearchApiAbstractService::viewSettings().
*
* Returns an empty string since information is instead added via
* getExtraInformation().
*/
public function viewSettings() {
$output = '';
$options = $this->options;
return '';
}
$output .= "<dl>\n <dt>";
$output .= t('Solr server URI');
$output .= "</dt>\n <dd>";
$output .= $this->getServerLink();
$output .= '</dd>';
if ($options['http_user']) {
$output .= "\n <dt>";
$output .= t('Basic HTTP authentication');
$output .= "</dt>\n <dd>";
$output .= t('Username: @user', array('@user' => $options['http_user']));
$output .= "</dd>\n <dd>";
$output .= t('Password: @pass', array('@pass' => str_repeat('*', strlen($options['http_pass']))));
$output .= '</dd>';
/**
* {@inheritdoc}
*/
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,
);
}
$output .= "\n</dl>";
return $output;
if ($this->server->enabled) {
// If the server is enabled, check whether Solr can be reached.
$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 {
// If Solr can be reached, provide more information. This isn't done
// often (only when an admin views the server details), so we clear the
// cache to get the current data.
$this->connect();
$this->solr->clearCache();
$data = $this->solr->getLuke();
if (isset($data->index->numDocs)) {
// Collect the stats
$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 (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(drupal_get_path('module', 'search_api_solr') . '/INSTALL.txt');
$message = 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);
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;
}
/**
@@ -377,12 +480,12 @@ class SearchApiSolrService extends SearchApiAbstractService {
$id = is_object($index) ? $index->machine_name : $index;
// Only delete the index's data if the index isn't read-only.
if (!is_object($index) || empty($index->read_only)) {
$this->connect();
try {
$this->connect();
$this->solr->deleteByQuery("index_id:" . $id);
$this->solr->deleteByQuery("index_id:" . $this->getIndexId($id));
}
catch (Exception $e) {
watchdog_exception('search_api_solr', $e, "%type while deleting an index's data: !message in %function (line %line of %file).");
throw new SearchApiException($e->getMessage());
}
}
}
@@ -393,7 +496,7 @@ class SearchApiSolrService extends SearchApiAbstractService {
public function indexItems(SearchApiIndex $index, array $items) {
$documents = array();
$ret = array();
$index_id = $index->machine_name;
$index_id = $this->getIndexId($index->machine_name);
$fields = $this->getFieldNames($index);
foreach ($items as $id => $item) {
@@ -481,10 +584,10 @@ class SearchApiSolrService extends SearchApiAbstractService {
}
$inner_type = search_api_extract_inner_type($type);
$type_info = search_api_solr_get_dynamic_field_info($inner_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';
$pref .= ($type == $inner_type) ? 's' : 'm';
}
if (!empty($this->options['clean_ids'])) {
$name = $pref . '_' . str_replace(':', '$', $key);
@@ -586,33 +689,28 @@ class SearchApiSolrService extends SearchApiAbstractService {
* called in this fashion.
*/
public function deleteItems($ids = 'all', SearchApiIndex $index = NULL) {
try {
$this->connect();
if ($index) {
$index_id = $index->machine_name;
if (is_array($ids)) {
$solr_ids = array();
foreach ($ids as $id) {
$solr_ids[] = $this->createId($index_id, $id);
}
$this->solr->deleteByMultipleIds($solr_ids);
}
elseif ($ids == 'all') {
$this->solr->deleteByQuery("index_id:" . $index_id);
}
else {
$this->solr->deleteByQuery("index_id:" . $index_id . ' (' . $ids . ')');
$this->connect();
if ($index) {
$index_id = $this->getIndexId($index->machine_name);
if (is_array($ids)) {
$solr_ids = array();
foreach ($ids as $id) {
$solr_ids[] = $this->createId($index_id, $id);
}
$this->solr->deleteByMultipleIds($solr_ids);
}
elseif ($ids == 'all') {
$this->solr->deleteByQuery("index_id:" . $index_id);
}
else {
$q = $ids == 'all' ? '*:*' : $ids;
$this->solr->deleteByQuery($q);
$this->solr->deleteByQuery("index_id:" . $index_id . ' (' . $ids . ')');
}
$this->scheduleCommit();
}
catch(SearchApiException $e) {
watchdog_exception('search_api_solr', $e, '%type while deleting items from server @server: !message in %function (line %line of %file).', array('@server' => $this->server->name));
else {
$q = $ids == 'all' ? '*:*' : $ids;
$this->solr->deleteByQuery($q);
}
$this->scheduleCommit();
}
/**
@@ -624,6 +722,7 @@ class SearchApiSolrService extends SearchApiAbstractService {
$this->request_handler = NULL;
// Get field information.
$index = $query->getIndex();
$index_id = $this->getIndexId($index->machine_name);
$fields = $this->getFieldNames($index);
// Get Solr connection.
$this->connect();
@@ -650,7 +749,7 @@ class SearchApiSolrService extends SearchApiAbstractService {
// Extract filters.
$filter = $query->getFilter();
$fq = $this->createFilterQueries($filter, $fields, $index->options['fields']);
$fq[] = 'index_id:' . $index->machine_name;
$fq[] = 'index_id:' . $index_id;
// Extract sort.
$sort = array();
@@ -690,7 +789,7 @@ class SearchApiSolrService extends SearchApiAbstractService {
}
}
$mlt_params['mlt.fl'] = implode(',', $mlt_fl);
$id = $this->createId($index->machine_name, $mlt['id']);
$id = $this->createId($index_id, $mlt['id']);
$id = call_user_func(array($this->connection_class, 'phrase'), $id);
$keys = 'id:' . $id;
}
@@ -884,7 +983,7 @@ class SearchApiSolrService extends SearchApiAbstractService {
$params['fl'] = '*,score';
}
// Retrieve http method from server options.
$http_method = !empty($this->options['http_method']) ? $this->options['http_method'] : 'POST';
$http_method = !empty($this->options['http_method']) ? $this->options['http_method'] : 'AUTO';
$call_args = array(
'query' => &$keys,
@@ -1020,7 +1119,8 @@ class SearchApiSolrService extends SearchApiAbstractService {
$result['id'] = $result['fields']['search_api_id'];
$result['score'] = $result['fields']['search_api_relevance'];
$solr_id = $this->createId($index->machine_name, $result['id']);
$index_id = $this->getIndexId($index->machine_name);
$solr_id = $this->createId($index_id, $result['id']);
$excerpt = $this->getExcerpt($response, $solr_id, $result['fields'], $fields);
if ($excerpt) {
$result['excerpt'] = $excerpt;
@@ -1248,7 +1348,7 @@ class SearchApiSolrService extends SearchApiAbstractService {
// #conjunction | #negation | return value
// ----------------------------------------------------------------
// AND | FALSE | A B C
// AND | TRUE | -(A B C)
// AND | TRUE | -(A AND B AND C)
// OR | FALSE | ((A) OR (B) OR (C))
// OR | TRUE | -A -B -C
@@ -1264,7 +1364,7 @@ class SearchApiSolrService extends SearchApiAbstractService {
}
return '((' . implode(') OR (', $k) . '))';
}
$k = implode(' ', $k);
$k = implode($neg ? ' AND ' : ' ', $k);
return $neg ? "*:* AND -($k)" : $k;
}
@@ -1565,7 +1665,7 @@ class SearchApiSolrService extends SearchApiAbstractService {
// Extract filters
$fq = $this->createFilterQueries($query->getFilter(), $fields, $index->options['fields']);
$fq[] = 'index_id:' . $index->machine_name;
$fq[] = 'index_id:' . $this->getIndexId($index->machine_name);
// Autocomplete magic
$facet_fields = array();
@@ -1588,7 +1688,7 @@ class SearchApiSolrService extends SearchApiAbstractService {
'spellcheck.count' => 1,
);
// Retrieve http method from server options.
$http_method = !empty($this->options['http_method']) ? $this->options['http_method'] : 'POST';
$http_method = !empty($this->options['http_method']) ? $this->options['http_method'] : 'AUTO';
$call_args = array(
'query' => &$keys,
@@ -1728,11 +1828,11 @@ class SearchApiSolrService extends SearchApiAbstractService {
'type' => 'string',
),
);
foreach ($query->getIndexes() as $index_id => $index) {
foreach ($query->getIndexes() as $index) {
if (empty($index->options['fields'])) {
continue;
}
$prefix = $index_id . ':';
$prefix = $this->getIndexId($index->machine_name) . ':';
foreach ($this->getFieldNames($index) as $field => $key) {
if (!isset($solr_fields[$field])) {
$solr_fields[$prefix . $field] = $key;
@@ -1762,7 +1862,8 @@ class SearchApiSolrService extends SearchApiAbstractService {
// Restrict search to searched indexes.
$index_filter = array();
foreach ($query->getIndexes() as $index_id => $index) {
foreach ($query->getIndexes() as $index) {
$index_id = $this->getIndexId($index->machine_name);
$index_filter[] = 'index_id:' . call_user_func(array($this->connection_class, 'phrase'), $index_id);
}
$fq[] = implode(' OR ', $index_filter);
@@ -1814,7 +1915,7 @@ class SearchApiSolrService extends SearchApiAbstractService {
}
// Retrieve http method from server options.
$http_method = !empty($this->options['http_method']) ? $this->options['http_method'] : 'POST';
$http_method = !empty($this->options['http_method']) ? $this->options['http_method'] : 'AUTO';
// Send search request
$time_processing_done = microtime(TRUE);
@@ -1879,7 +1980,7 @@ class SearchApiSolrService extends SearchApiAbstractService {
$terms = clone $terms;
unset($terms->_empty_);
}
$type = isset($index->options['fields'][$info['field']]['type']) ? search_api_extract_inner_type($index->options['fields'][$info['field']]['type']) : 'string';
$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_') {
@@ -2041,4 +2142,25 @@ class SearchApiSolrService extends SearchApiAbstractService {
return $this->solr->makeServletRequest($file_servlet_name, $params);
}
/**
* Prefixes an index ID as configured.
*
* The resulting ID will be a concatenation of the following strings:
* - If set, the "search_api_solr_index_prefix" variable.
* - If set, the index-specific "search_api_solr_index_prefix_INDEX" variable.
* - The index's machine name.
*
* @param string $machine_name
* The index's machine name.
*
* @return string
* The prefixed machine name.
*/
protected function getIndexId($machine_name) {
// Prepend per-index prefix.
$id = variable_get('search_api_solr_index_prefix_' . $machine_name, '') . $machine_name;
// Prepend environment prefix.
$id = variable_get('search_api_solr_index_prefix', '') . $id;
return $id;
}
}

View File

@@ -58,10 +58,11 @@
*/
/**
* Starting point for the Solr API. Represents a Solr server resource and has
* methods for pinging, adding, deleting, committing, optimizing and searching.
* Represents a Solr server resource.
*
* Contains methods for pinging, adding, deleting, committing, optimizing and
* searching.
*/
class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
/**
@@ -133,13 +134,6 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
*/
protected $update_url;
/**
* The HTTP method to use for search requests.
*
* @var string
*/
protected $method;
/**
* HTTP Basic Authentication header to set for requests to the Solr server.
*
@@ -208,8 +202,6 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
* information to add basic HTTP authentication to all requests to the
* Solr server. Not set by default.
* - http_pass: See "http_user".
* - http_method: The HTTP method to use for searches. Can be either "GET"
* or "POST". Defaults to "POST".
*/
public function __construct(array $options) {
$options += array(
@@ -219,16 +211,12 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
'path' => 'solr',
'http_user' => NULL,
'http_pass' => NULL,
'http_method' => 'POST',
);
$this->options = $options;
$path = '/' . trim($options['path'], '/') . '/';
$this->base_url = $options['scheme'] . '://' . $options['host'] . ':' . $options['port'] . $path;
// Make sure we always have a valid method set, default to POST.
$this->method = $options['http_method'] == 'GET' ? 'GET' : 'POST';
// Set HTTP Basic Authentication parameter, if login data was set.
if (strlen($options['http_user']) && strlen($options['http_pass'])) {
$this->http_auth = 'Basic ' . base64_encode($options['http_user'] . ':' . $options['http_pass']);
@@ -429,7 +417,7 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
}
$response = $this->sendRawGet($url);
$this->stats = simplexml_load_string($response->data);
if ($this->env_id) {
if ($cid) {
cache_set($cid, $response->data, 'cache_search_api_solr');
}
}
@@ -813,15 +801,15 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
*
* Will be synchronous unless $waitSearcher is set to FALSE.
*
* @param $type
* @param string $type
* Either "commit" or "optimize".
* @param $waitSearcher
* @param bool $waitSearcher
* (optional) Wait until a new searcher is opened and registered as the main
* query searcher, making the changes visible. Defaults to true.
* @param $timeout
* @param int $timeout
* Seconds to wait until timing out with an exception. Defaults to an hour.
*
* @return
* @return object
* A response object.
*
* @throws SearchApiException
@@ -845,7 +833,20 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
}
/**
* Like PHP's built in http_build_query(), but uses rawurlencode() and no [] for repeated params.
* Generates an URL-encoded query string.
*
* Works like PHP's built in http_build_query() (or drupal_http_build_query())
* but uses rawurlencode() and no [] for repeated params, to be compatible
* with the Java-based servers Solr runs on.
*
*
* @param array $query
* The query parameters which should be set.
* @param string $parent
* Internal use only.
*
* @return string
* A query string to append (after "?") to a URL.
*/
protected function httpBuildQuery(array $query, $parent = '') {
$params = array();
@@ -870,7 +871,7 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
}
/**
* Implements SearchApiSolrConnectionInterface::search().
* {@inheritdoc}
*/
public function search($query = NULL, array $params = array(), $method = 'GET') {
// Always use JSON. See
@@ -887,15 +888,18 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
// PHP's built-in http_build_query() doesn't give us the format Solr wants.
$queryString = $this->httpBuildQuery($params);
if ($this->method == 'GET') {
if ($method == 'GET' || $method == 'AUTO') {
$searchUrl = $this->constructUrl(self::SEARCH_SERVLET, array(), $queryString);
return $this->sendRawGet($searchUrl);
}
else if ($this->method == 'POST') {
$searchUrl = $this->constructUrl(self::SEARCH_SERVLET);
$options['data'] = $queryString;
$options['headers']['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
return $this->sendRawPost($searchUrl, $options);
if ($method == 'GET' || strlen($searchUrl) <= variable_get('search_api_solr_http_get_max_length', 4000)) {
return $this->sendRawGet($searchUrl);
}
}
// Method is POST, or AUTO with a long query
$searchUrl = $this->constructUrl(self::SEARCH_SERVLET);
$options['data'] = $queryString;
$options['headers']['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
return $this->sendRawPost($searchUrl, $options);
}
}

View File

@@ -71,8 +71,8 @@ interface SearchApiSolrConnectionInterface {
/**
* Gets information about the Solr Core.
*
* @return array
* An array with system information.
* @return object
* A response object with system information.
*/
public function getSystemInfo();
@@ -288,8 +288,8 @@ interface SearchApiSolrConnectionInterface {
* documentation). Use arrays for parameter keys used more than once (e.g.,
* facet.field).
* @param string $method
* The HTTP method to use. Must be either "GET" or "POST". Defaults to
* "GET".
* The HTTP method to use. Must be either "GET", "POST" or "AUTO". Defaults
* to "GET".
*
* @return object
* A response object.