updated search_api and search_api_solr

This commit is contained in:
Bachir Soussi Chiadmi
2016-03-01 16:23:45 +01:00
parent deb2bff572
commit ba1ec7113e
67 changed files with 5209 additions and 686 deletions

View File

@@ -78,13 +78,10 @@ class SearchApiFacetapiAdapter extends FacetapiAdapter {
// displayed.
$facet_search_ids = isset($options['facet_search_ids']) ? $options['facet_search_ids'] : array();
// Remember this search ID, if necessary.
$this->rememberSearchId($index_id, $search_id);
if (array_search($search_id, $facet_search_ids) === FALSE) {
$search_ids = variable_get('search_api_facets_search_ids', array());
if (empty($search_ids[$index_id][$search_id])) {
// Remember this search ID.
$search_ids[$index_id][$search_id] = $search_id;
variable_set('search_api_facets_search_ids', $search_ids);
}
if (!$default_true) {
continue; // We are only to show facets for explicitly named search ids.
}
@@ -103,6 +100,23 @@ class SearchApiFacetapiAdapter extends FacetapiAdapter {
}
}
/**
* Adds a search ID to the list of known searches for an index.
*
* @param string $index_id
* The machine name of the search index.
* @param string $search_id
* The identifier of the executed search.
*/
protected function rememberSearchId($index_id, $search_id) {
$search_ids = variable_get('search_api_facets_search_ids', array());
if (empty($search_ids[$index_id][$search_id])) {
$search_ids[$index_id][$search_id] = $search_id;
asort($search_ids[$index_id]);
variable_set('search_api_facets_search_ids', $search_ids);
}
}
/**
* Add the given facet to the query.
*/
@@ -257,6 +271,9 @@ class SearchApiFacetapiAdapter extends FacetapiAdapter {
'#options' => $granularity_options,
'#default_value' => isset($options['date_granularity']) ? $options['date_granularity'] : FACETAPI_DATE_MINUTE,
);
// Date facets don't support the "OR" operator (for now).
$form['global']['operator']['#access'] = FALSE;
}
// Add an "Exclude" option for terms.

View File

@@ -54,6 +54,27 @@ class SearchApiFacetapiTerm extends FacetapiQueryType implements FacetapiQueryTy
}
elseif ($operator == FACETAPI_OPERATOR_OR) {
$conjunction = 'OR';
// When the operator is OR, remove parent terms from the active ones if
// children are active. If we don't do this, sending a term and its
// parent will produce the same results as just sending the parent.
if ($settings['flatten'] == '0') {
// Check the filters in reverse order, to avoid checking parents that
// will afterwards be removed anyways.
foreach (array_reverse(array_keys($active)) as $filter) {
// Skip this filter if it was already removed, or if it is the
// "missing value" filter ("!").
if (!isset($active[$filter]) || !is_numeric($filter)) {
continue;
}
$parents = taxonomy_get_parents_all($filter);
// The return value of taxonomy_get_parents_all() includes the term
// itself at index 0. Remove that to only get the term's ancestors.
unset($parents[0]);
foreach ($parents as $parent) {
unset($active[$parent->tid]);
}
}
}
}
else {
$vars = array(
@@ -153,7 +174,7 @@ class SearchApiFacetapiTerm extends FacetapiQueryType implements FacetapiQueryTy
// Always include the active facet items.
foreach ($this->adapter->getActiveItems($this->facet) as $filter) {
$build[$filter['value']]['#count'] = $results['result count'];
$build[$filter['value']]['#count'] = 0;
}
// Then, add the facets returned by the server.

View File

@@ -9,9 +9,9 @@ files[] = plugins/facetapi/adapter.inc
files[] = plugins/facetapi/query_type_term.inc
files[] = plugins/facetapi/query_type_date.inc
; Information added by Drupal.org packaging script on 2014-12-26
version = "7.x-1.14"
; Information added by Drupal.org packaging script on 2016-02-26
version = "7.x-1.16+29-dev"
core = "7.x"
project = "search_api"
datestamp = "1419580682"
datestamp = "1456500713"

View File

@@ -340,13 +340,13 @@ function search_api_facetapi_facet_map_callback(array $values, array $options =
$lower = isset($map[$range['lower']]) ? $map[$range['lower']] : $range['lower'];
$upper = isset($map[$range['upper']]) ? $map[$range['upper']] : $range['upper'];
if ($lower == '*' && $upper == '*') {
$map[$value] = t('any');
$map[$value] = t('any');
}
elseif ($lower == '*') {
$map[$value] = "< $upper";
$map[$value] = " $upper";
}
elseif ($upper == '*') {
$map[$value] = "> $lower";
$map[$value] = " $lower";
}
else {
$map[$value] = "$lower $upper";

View File

@@ -24,6 +24,22 @@ When these are present, the normal keywords should be ignored and the related
items be returned as results instead. Sorting, filtering and range restriction
should all work normally.
"Random sort" feature
---------------------
This module defines the "Random sort" feature (feature key:
"search_api_random_sort") that allows to randomly sort the results returned by a
search. With a server supporting this, you can use the "Global: Random" sort to
sort the view's results randomly. Every time the query is run a different
sorting will be provided.
For developers:
A service class that wants to support this feature has to check for a
"search_api_random" field in the search query's sorts and insert a random sort
in that position. If the query is sorted in this way, then the
"search_api_random_sort" query option can contain additional options for the
random sort, as an associative array with any of the following keys:
- seed: A numeric seed value to use for the random sort.
"Facets block" display
----------------------
Most features should be clear to users of Views. However, the module also

View File

@@ -18,6 +18,7 @@ class SearchApiViewsHandlerArgumentMoreLikeThis extends SearchApiViewsHandlerArg
$options = parent::option_definition();
unset($options['break_phrase']);
unset($options['not']);
$options['entity_type'] = array('default' => FALSE);
$options['fields'] = array('default' => array());
return $options;
}
@@ -31,6 +32,20 @@ class SearchApiViewsHandlerArgumentMoreLikeThis extends SearchApiViewsHandlerArg
unset($form['not']);
$index = search_api_index_load(substr($this->table, 17));
if ($index->datasource() instanceof SearchApiCombinedEntityDataSourceController) {
$types = array_intersect_key(search_api_entity_type_options_list(), array_flip($index->options['datasource']['types']));
$form['entity_type'] = array(
'#type' => 'select',
'#title' => t('Entity type'),
'#description' => t('Select the entity type of the argument.'),
'#options' => $types,
'#default_value' => $this->options['entity_type'],
'#required' => TRUE,
);
}
if (!empty($index->options['fields'])) {
$fields = array();
foreach ($index->getFields() as $key => $field) {
@@ -71,14 +86,22 @@ class SearchApiViewsHandlerArgumentMoreLikeThis extends SearchApiViewsHandlerArg
$this->query->abort();
return;
}
$fields = $this->options['fields'] ? $this->options['fields'] : array();
if (empty($fields)) {
foreach ($this->query->getIndex()->options['fields'] as $key => $field) {
$fields[] = $key;
}
$index_fields = array_keys($this->query->getIndex()->options['fields']);
if (empty($this->options['fields'])) {
$fields = $index_fields;
}
else {
$fields = array_intersect($this->options['fields'], $index_fields);
}
if ($this->query->getIndex()->datasource() instanceof SearchApiCombinedEntityDataSourceController) {
$id = $this->options['entity_type'] . '/' . $this->argument;
}
else {
$id = $this->argument;
}
$mlt = array(
'id' => $this->argument,
'id' => $id,
'fields' => $fields,
);
$this->query->getSearchApiQuery()->setOption('search_api_mlt', $mlt);

View File

@@ -16,6 +16,8 @@ class SearchApiViewsHandlerFilterDate extends SearchApiViewsHandlerFilter {
public function option_definition() {
return parent::option_definition() + array(
'widget_type' => array('default' => 'default'),
'date_popup_format' => array('default' => 'm/d/Y'),
'year_range' => array('default' => '-3:+3'),
);
}
@@ -34,14 +36,49 @@ class SearchApiViewsHandlerFilterDate extends SearchApiViewsHandlerFilter {
*/
public function extra_options_form(&$form, &$form_state) {
parent::extra_options_form($form, $form_state);
if (module_exists('date_popup')) {
$widget_options = array('default' => 'Default', 'date_popup' => 'Date popup');
$widget_options = array(
'default' => 'Default',
'date_popup' => 'Date popup',
);
$form['widget_type'] = array(
'#type' => 'radios',
'#title' => t('Date selection form element'),
'#default_value' => $this->options['widget_type'],
'#options' => $widget_options,
);
$form['date_popup_format'] = array(
'#type' => 'textfield',
'#title' => t('Date format'),
'#default_value' => $this->options['date_popup_format'],
'#description' => t('A date in any format understood by <a href="@doc-link">PHP</a>. For example, "Y-m-d" or "m/d/Y".', array(
'@doc-link' => 'http://php.net/manual/en/function.date.php'
)),
'#states' => array(
'visible' => array(
':input[name="options[widget_type]"]' => array('value' => 'date_popup'),
),
),
);
}
if (module_exists('date_api')) {
$form['year_range'] = array(
'#type' => 'date_year_range',
'#default_value' => $this->options['year_range'],
);
}
}
/**
* Validate extra options.
*/
public function extra_options_validate($form, &$form_state) {
if (isset($form_state['values']['options']['year_range'])) {
if (!preg_match('/^(?:\-[0-9]{1,4}|[0-9]{4}):(?:[\+|\-][0-9]{1,4}|[0-9]{4})$/', $form_state['values']['options']['year_range'])) {
form_error($form['year_range'], t('Date year range must be in the format -9:+9, 2005:2010, -9:2010, or 2005:+9'));
}
}
}
@@ -55,7 +92,8 @@ class SearchApiViewsHandlerFilterDate extends SearchApiViewsHandlerFilter {
// according to what date_popup expects.
if ($this->options['widget_type'] == 'date_popup' && module_exists('date_popup')) {
$form['value']['#type'] = 'date_popup';
$form['value']['#date_format'] = 'm/d/Y';
$form['value']['#date_format'] = $this->options['date_popup_format'];
$form['value']['#date_year_range'] = $this->options['year_range'];
unset($form['value']['#description']);
}
elseif (empty($form_state['exposed'])) {

View File

@@ -144,7 +144,8 @@ class SearchApiViewsHandlerFilterFulltext extends SearchApiViewsHandlerFilterTex
return;
}
$fields = $this->options['fields'];
$fields = $fields ? $fields : array_keys($this->getFulltextFields());
$available_fields = array_keys($this->getFulltextFields());
$fields = $fields ? array_intersect($fields, $available_fields) : $available_fields;
// If something already specifically set different fields, we silently fall
// back to mere filtering.

View File

@@ -178,12 +178,25 @@ class SearchApiViewsHandlerFilterTaxonomyTerm extends SearchApiViewsHandlerFilte
return TRUE;
}
// We need to know the operator, which is normally set in
// views_handler_filter::accept_exposed_input(), before we actually call
// the parent version of ourselves.
if (!empty($this->options['expose']['use_operator']) && !empty($this->options['expose']['operator_id']) && isset($input[$this->options['expose']['operator_id']])) {
$this->operator = $input[$this->options['expose']['operator_id']];
}
// If view is an attachment and is inheriting exposed filters, then assume
// exposed input has already been validated.
if (!empty($this->view->is_attachment) && $this->view->display_handler->uses_exposed()) {
$this->validated_exposed_input = (array) $this->view->exposed_raw_input[$this->options['expose']['identifier']];
}
// If we're checking for EMPTY or NOT, we don't need any input, and we can
// say that our input conditions are met by just having the right operator.
if ($this->operator == 'empty' || $this->operator == 'not empty') {
return TRUE;
}
// If it's non-required and there's no value don't bother filtering.
if (!$this->options['expose']['required'] && empty($this->validated_exposed_input)) {
return FALSE;

View File

@@ -17,4 +17,52 @@ class SearchApiViewsHandlerFilterText extends SearchApiViewsHandlerFilter {
return array('=' => t('contains'), '<>' => t("doesn't contain"));
}
/**
* Determines whether input from the exposed filters affects this filter.
*
* Overridden to not treat "All" differently.
*
* @param array $input
* The user input from the exposed filters.
*
* @return bool
* TRUE if the input should change the behavior of this filter.
*/
public function accept_exposed_input($input) {
if (empty($this->options['exposed'])) {
return TRUE;
}
if (!empty($this->options['expose']['use_operator']) && !empty($this->options['expose']['operator_id']) && isset($input[$this->options['expose']['operator_id']])) {
$this->operator = $input[$this->options['expose']['operator_id']];
}
if (!empty($this->options['expose']['identifier'])) {
$value = $input[$this->options['expose']['identifier']];
// Various ways to check for the absence of non-required input.
if (empty($this->options['expose']['required'])) {
if (($this->operator == 'empty' || $this->operator == 'not empty') && $value === '') {
$value = ' ';
}
if (!empty($this->always_multiple) && $value === '') {
return FALSE;
}
}
if (isset($value)) {
$this->value = $value;
if (empty($this->always_multiple) && empty($this->options['expose']['multiple'])) {
$this->value = array($value);
}
}
else {
return FALSE;
}
}
return TRUE;
}
}

View File

@@ -122,19 +122,63 @@ class SearchApiViewsQuery extends views_plugin_query {
}
/**
* Add a sort to the query.
* Adds a sort to the query.
*
* @param $selector
* @param string $selector
* The field to sort on. All indexed fields of the index are valid values.
* In addition, the special fields 'search_api_relevance' (sort by
* relevance) and 'search_api_id' (sort by item id) may be used.
* @param $order
* In addition, these special fields may be used:
* - search_api_relevance: sort by relevance;
* - search_api_id: sort by item id;
* - search_api_random: random sort (available only if the server supports
* the "search_api_random_sort" feature).
* @param string $order
* The order to sort items in - either 'ASC' or 'DESC'. Defaults to 'ASC'.
*/
public function add_selector_orderby($selector, $order = 'ASC') {
$this->query->sort($selector, $order);
}
/**
* Provides a sorting method as present in the Views default query plugin.
*
* This is provided so that the "Global: Random" sort included in Views will
* work properly with Search API Views. Random sorting is only supported if
* the active search server supports the "search_api_random_sort" feature,
* though, otherwise the call will be ignored.
*
* This method can only be used to sort randomly, as would be done with the
* default query plugin. All other calls are ignored.
*
* @param string|null $table
* Only "rand" is recognized here, all other calls are ignored.
* @param string|null $field
* Is ignored and only present for compatibility reasons.
* @param string $order
* Either "ASC" or "DESC".
* @param string|null $alias
* Is ignored and only present for compatibility reasons.
* @param array $params
* The following optional parameters are recognized:
* - seed: a predefined seed for the random generator.
*
* @see views_plugin_query_default::add_orderby()
*/
public function add_orderby($table, $field = NULL, $order = 'ASC', $alias = '', $params = array()) {
$server = $this->getIndex()->server();
if ($table == 'rand') {
if ($server->supportsFeature('search_api_random_sort')) {
$this->add_selector_orderby('search_api_random', $order);
if ($params) {
$this->setOption('search_api_random_sort', $params);
}
}
else {
$variables['%server'] = $server->label();
watchdog('search_api_views', 'Tried to sort results randomly on server %server which does not support random sorting.', $variables, WATCHDOG_WARNING);
}
}
}
/**
* Defines the options used by this query plugin.
*
@@ -203,6 +247,10 @@ class SearchApiViewsQuery extends views_plugin_query {
* Builds the necessary info to execute the query.
*/
public function build(&$view) {
if (!empty($this->errors)) {
return;
}
$this->view = $view;
// Setup the nested filter structure for this query.
@@ -375,8 +423,8 @@ class SearchApiViewsQuery extends views_plugin_query {
// loading any items.
foreach ($results as $id => $result) {
if (!empty($this->options['entity_access']) && ($entity_type = $this->index->getEntityType())) {
$entity = entity_load($entity_type, array($id));
if (!entity_access('view', $entity_type, $entity[$id])) {
$entity = $this->index->loadItems(array($id));
if (!$entity || !entity_access('view', $entity_type, reset($entity))) {
continue;
}
}
@@ -400,7 +448,7 @@ class SearchApiViewsQuery extends views_plugin_query {
}
// Check whether we need to extract any properties from the result item.
$missing_fields = array_diff_key($this->fields, $row);
$missing_fields = array_diff_key($this->fields, $row['_entity_properties']);
if ($missing_fields) {
$missing[$id] = $missing_fields;
if (is_object($row['entity'])) {
@@ -418,14 +466,14 @@ class SearchApiViewsQuery extends views_plugin_query {
// Load items of those rows which haven't got all field values, yet.
if (!empty($ids)) {
$items += $this->index->loadItems($ids);
// $items now includes loaded items, and those already passed in the
// search results.
foreach ($items as $id => $item) {
// Extract item properties.
$wrapper = $this->index->entityWrapper($item, FALSE);
$rows[$id]->_entity_properties += $this->extractFields($wrapper, $missing[$id]);
$rows[$id]->entity = $item;
}
}
// $items now includes all loaded items from which fields still need to be
// extracted.
foreach ($items as $id => $item) {
// Extract item properties.
$wrapper = $this->index->entityWrapper($item, FALSE);
$rows[$id]->_entity_properties += $this->extractFields($wrapper, $missing[$id]);
$rows[$id]->entity = $item;
}
// Finally, add all rows to the Views result set.
@@ -490,31 +538,31 @@ class SearchApiViewsQuery extends views_plugin_query {
* query backend.
*/
public function get_result_wrappers($results, $relationship = NULL, $field = NULL) {
$entity_type = $this->index->getEntityType();
$type = $this->index->getEntityType() ? $this->index->getEntityType() : $this->index->item_type;
$wrappers = array();
$load_entities = array();
$load_items = array();
foreach ($results as $row_index => $row) {
if ($entity_type && isset($row->entity)) {
if (isset($row->entity)) {
// If this entity isn't load, register it for pre-loading.
if (!is_object($row->entity)) {
$load_entities[$row->entity] = $row_index;
$load_items[$row->entity] = $row_index;
}
else {
$wrappers[$row_index] = $this->index->entityWrapper($row->entity);
}
$wrappers[$row_index] = $this->index->entityWrapper($row->entity);
}
}
// If the results are entities, we pre-load them to make use of a multiple
// load. (Otherwise, each result would be loaded individually.)
if (!empty($load_entities)) {
$entities = entity_load($entity_type, array_keys($load_entities));
foreach ($entities as $entity_id => $entity) {
$wrappers[$load_entities[$entity_id]] = $this->index->entityWrapper($entity);
if (!empty($load_items)) {
$items = $this->index->loadItems(array_keys($load_items));
foreach ($items as $id => $item) {
$wrappers[$load_items[$id]] = $this->index->entityWrapper($item);
}
}
// Apply the relationship, if necessary.
$type = $entity_type ? $entity_type : $this->index->item_type;
$selector_suffix = '';
if ($field && ($pos = strrpos($field, ':'))) {
$selector_suffix = substr($field, 0, $pos);

View File

@@ -27,9 +27,9 @@ files[] = includes/handler_sort.inc
files[] = includes/plugin_cache.inc
files[] = includes/query.inc
; Information added by Drupal.org packaging script on 2014-12-26
version = "7.x-1.14"
; Information added by Drupal.org packaging script on 2016-02-26
version = "7.x-1.16+29-dev"
core = "7.x"
project = "search_api"
datestamp = "1419580682"
datestamp = "1456500713"

View File

@@ -11,7 +11,6 @@
function search_api_views_views_data() {
try {
$data = array();
$entity_types = entity_get_info();
foreach (search_api_index_load_multiple(FALSE) as $index) {
// Fill in base data.
$key = 'search_api_index_' . $index->machine_name;
@@ -25,12 +24,8 @@ function search_api_views_views_data() {
'help' => t('Use the %name search index for filtering and retrieving data.', array('%name' => $index->name)),
'query class' => 'search_api_views_query',
);
if (isset($entity_types[$index->getEntityType()])) {
$table['table'] += array(
'entity type' => $index->getEntityType(),
'skip entity load' => TRUE,
);
}
$table['table']['entity type'] = $index->getEntityType();
$table['table']['skip entity load'] = TRUE;
try {
$wrapper = $index->entityWrapper(NULL, FALSE);