upadted to 1.8

This commit is contained in:
Bachir Soussi Chiadmi
2013-09-26 15:49:26 +02:00
parent e0ae80791b
commit 128640cd15
52 changed files with 2604 additions and 1015 deletions

View File

@@ -54,8 +54,7 @@ linked to for the filter to have an effect.
Since the block will trigger a search on pages where it is set to appear, you
can also enable additional „normal“ facet blocks for that search, via the
„Facets“ tab for the index. They will automatically also point to the same
search that you specified for the display. The Search ID of the „Facets blocks“
display can easily be recognized by the "-facet_block" suffix.
search that you specified for the display.
If you want to use only the normal facets and not display anything at all in
the Views block, just activate the display's „Hide block“ option.
@@ -63,6 +62,50 @@ Note: If you want to display the block not only on a few pages, you should in
any case take care that it isn't displayed on the search page, since that might
confuse users.
Access features
---------------
Search views created with this module contain two query settings (located in
the "Advanced" fieldset) which let you control the access checks executed for
search results displayed in the view.
- Bypass access checks
This option allows you to deactivate access filters that would otherwise be
added to the search, if the index supports this. This is, for instance, the case
for indexes on the "Node" item type, when the "Node access" data alteration is
activated.
Use this either to slightly speed up searches where additional checks are
unnecessary (e.g., because you already filter on "Node: Published") and there is
no other node access mechanism on your site) or to show certain data that users
normally wouldn't have access to (e.g., a list of all matching node titles,
published or not).
- Additional access checks on result entities
When this option is activated, all result entities will be passed to an
additional access check, even if search-time access checks are available for
this index. The advantage is that access rules are guaranteed to be enforced
stale data in the index, which might make other access checks incorrect, won't
influence this access check. You can also use it for item types for which no
other access mechanisms are available.
However, note that results filtered out this way will mess up paging, result
counts and possibly other things too (like facet counts), as the result row is
only hidden from display after the search has been executed. Where possible,
you should therefore only use this in combination with appropriate filter
settings ensuring that only when the index isn't up-to-date items will be
filtered out this way.
This option is only available for indexes on entity types.
Other features
--------------
- Change parse mode
You can determine how search keys entered by the user will be parsed by going to
"Advanced" > "Query settings" within your View's settings. "Direct" can be
useful, e.g., when you want to give users the full power of Solr. In other
cases, "Multiple terms" is usually what you want / what users expect.
Caution: For letting users use fulltext searches, always use the "Search:
Fulltext search" filter or contextual filter using a normal filter on a
fulltext field won't parse the search keys, which means multiple words will only
be found when they appear as that exact phrase.
FAQ: Why „*Indexed* Node“?
--------------------------
The group name used for the search result itself (in fields, filters, etc.) is

View File

@@ -177,7 +177,6 @@ class SearchApiViewsFacetsBlockDisplay extends views_plugin_display_block {
'min_count' => 1,
);
}
$query_options['search id'] = 'search_api_views:' . $this->view->name . '-facets_block';
$query_options['search_api_base_path'] = $base_path;
$this->view->query->range(0, 0);

View File

@@ -12,6 +12,17 @@ class SearchApiViewsHandlerArgument extends views_handler_argument {
*/
public $query;
/**
* The operator to use for multiple arguments.
*
* Either "and" or "or".
*
* @var string
*
* @see views_break_phrase
*/
public $operator;
/**
* Determine if the argument can generate a breadcrumb
*
@@ -64,6 +75,7 @@ class SearchApiViewsHandlerArgument extends views_handler_argument {
$options = parent::option_definition();
$options['break_phrase'] = array('default' => FALSE);
$options['not'] = array('default' => FALSE);
return $options;
}
@@ -79,6 +91,14 @@ class SearchApiViewsHandlerArgument extends views_handler_argument {
'#default_value' => $this->options['break_phrase'],
'#fieldset' => 'more',
);
$form['not'] = array(
'#type' => 'checkbox',
'#title' => t('Exclude'),
'#description' => t('If selected, the numbers entered for the filter will be excluded rather than limiting the view.'),
'#default_value' => !empty($this->options['not']),
'#fieldset' => 'more',
);
}
/**
@@ -86,37 +106,31 @@ class SearchApiViewsHandlerArgument extends views_handler_argument {
*
* The argument sent may be found at $this->argument.
*/
// @todo Provide options to select the operator, instead of always using '='?
public function query($group_by = FALSE) {
if (!empty($this->options['break_phrase'])) {
views_break_phrase($this->argument, $this);
}
else {
$this->value = array($this->argument);
if (empty($this->value)) {
if (!empty($this->options['break_phrase'])) {
views_break_phrase($this->argument, $this);
}
else {
$this->value = array($this->argument);
}
}
$operator = empty($this->options['not']) ? '=' : '<>';
if (count($this->value) > 1) {
$filter = $this->query->createFilter(drupal_strtoupper($this->operator));
// $filter will be NULL if there were errors in the query.
if ($filter) {
foreach ($this->value as $value) {
$filter->condition($this->real_field, $value, '=');
$filter->condition($this->real_field, $value, $operator);
}
$this->query->filter($filter);
}
}
else {
$this->query->condition($this->real_field, reset($this->value));
$this->query->condition($this->real_field, reset($this->value), $operator);
}
}
/**
* Get the title this argument will assign the view, given the argument.
*
* This usually needs to be overridden to provide a proper title.
*/
public function title() {
return t('Search @field for "@arg"', array('@field' => $this->definition['title'], '@arg' => $this->argument));
}
}

View File

@@ -3,7 +3,7 @@
/**
* Views argument handler class for handling fulltext fields.
*/
class SearchApiViewsHandlerArgumentFulltext extends SearchApiViewsHandlerArgumentText {
class SearchApiViewsHandlerArgumentFulltext extends SearchApiViewsHandlerArgument {
/**
* Specify the options this filter uses.
@@ -11,6 +11,7 @@ class SearchApiViewsHandlerArgumentFulltext extends SearchApiViewsHandlerArgumen
public function option_definition() {
$options = parent::option_definition();
$options['fields'] = array('default' => array());
$options['conjunction'] = array('default' => 'AND');
return $options;
}
@@ -20,6 +21,8 @@ class SearchApiViewsHandlerArgumentFulltext extends SearchApiViewsHandlerArgumen
public function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$form['help']['#markup'] = t('Note: You can change how search keys are parsed under "Advanced" > "Query settings".');
$fields = $this->getFulltextFields();
if (!empty($fields)) {
$form['fields'] = array(
@@ -31,6 +34,17 @@ class SearchApiViewsHandlerArgumentFulltext extends SearchApiViewsHandlerArgumen
'#multiple' => TRUE,
'#default_value' => $this->options['fields'],
);
$form['conjunction'] = array(
'#title' => t('Operator'),
'#description' => t('Determines how multiple keywords entered for the search will be combined.'),
'#type' => 'radios',
'#options' => array(
'AND' => t('Contains all of these words'),
'OR' => t('Contains any of these words'),
),
'#default_value' => $this->options['conjunction'],
);
}
else {
$form['fields'] = array(
@@ -49,6 +63,9 @@ class SearchApiViewsHandlerArgumentFulltext extends SearchApiViewsHandlerArgumen
if ($this->options['fields']) {
$this->query->fields($this->options['fields']);
}
if ($this->options['conjunction'] != 'AND') {
$this->query->setOption('conjunction', $this->options['conjunction']);
}
$old = $this->query->getOriginalKeys();
$this->query->keys($this->argument);

View File

@@ -12,6 +12,7 @@ class SearchApiViewsHandlerArgumentMoreLikeThis extends SearchApiViewsHandlerArg
public function option_definition() {
$options = parent::option_definition();
unset($options['break_phrase']);
unset($options['not']);
$options['fields'] = array('default' => array());
return $options;
}
@@ -22,6 +23,7 @@ class SearchApiViewsHandlerArgumentMoreLikeThis extends SearchApiViewsHandlerArg
public function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
unset($form['break_phrase']);
unset($form['not']);
$index = search_api_index_load(substr($this->table, 17));
if (!empty($index->options['fields'])) {
@@ -58,8 +60,9 @@ class SearchApiViewsHandlerArgumentMoreLikeThis extends SearchApiViewsHandlerArg
$server = $this->query->getIndex()->server();
if (!$server->supportsFeature('search_api_mlt')) {
$class = search_api_get_service_info($server->class);
throw new SearchApiException(t('The search service "@class" does not offer "More like this" functionality.',
array('@class' => $class['name'])));
watchdog('search_api_views', 'The search service "@class" does not offer "More like this" functionality.',
array('@class' => $class['name']), WATCHDOG_ERROR);
$this->query->abort();
return;
}
$fields = $this->options['fields'] ? $this->options['fields'] : array();

View File

@@ -0,0 +1,26 @@
<?php
/**
* Views argument handler class for handling string fields.
*/
class SearchApiViewsHandlerArgumentString extends SearchApiViewsHandlerArgument {
/**
* Set up the query for this argument.
*
* The argument sent may be found at $this->argument.
*/
public function query($group_by = FALSE) {
if (empty($this->value)) {
if (!empty($this->options['break_phrase'])) {
views_break_phrase_string($this->argument, $this);
}
else {
$this->value = array($this->argument);
}
}
parent::query($group_by);
}
}

View File

@@ -0,0 +1,104 @@
<?php
/**
* @file
* Contains the SearchApiViewsHandlerArgumentTaxonomyTerm class.
*/
/**
* Defines a contextual filter searching through all indexed taxonomy fields.
*/
class SearchApiViewsHandlerArgumentTaxonomyTerm extends SearchApiViewsHandlerArgument {
/**
* Set up the query for this argument.
*
* The argument sent may be found at $this->argument.
*/
public function query($group_by = FALSE) {
if (empty($this->value)) {
$this->fillValue();
}
$outer_conjunction = strtoupper($this->operator);
if (empty($this->options['not'])) {
$operator = '=';
$inner_conjunction = 'OR';
}
else {
$operator = '<>';
$inner_conjunction = 'AND';
}
if (!empty($this->value)) {
$terms = entity_load('taxonomy_term', $this->value);
if (!empty($terms)) {
$filter = $this->query->createFilter($outer_conjunction);
$vocabulary_fields = $this->definition['vocabulary_fields'];
$vocabulary_fields += array('' => array());
foreach ($terms as $term) {
$inner_filter = $filter;
if ($outer_conjunction != $inner_conjunction) {
$inner_filter = $this->query->createFilter($inner_conjunction);
}
// Set filters for all term reference fields which don't specify a
// vocabulary, as well as for all fields specifying the term's
// vocabulary.
if (!empty($this->definition['vocabulary_fields'][$term->vocabulary_machine_name])) {
foreach ($this->definition['vocabulary_fields'][$term->vocabulary_machine_name] as $field) {
$inner_filter->condition($field, $term->tid, $operator);
}
}
foreach ($vocabulary_fields[''] as $field) {
$inner_filter->condition($field, $term->tid, $operator);
}
if ($outer_conjunction != $inner_conjunction) {
$filter->filter($inner_filter);
}
}
$this->query->filter($filter);
}
}
}
/**
* Get the title this argument will assign the view, given the argument.
*/
public function title() {
if (!empty($this->argument)) {
if (empty($this->value)) {
$this->fillValue();
}
$terms = array();
foreach ($this->value as $tid) {
$taxonomy_term = taxonomy_term_load($tid);
if ($taxonomy_term) {
$terms[] = check_plain($taxonomy_term->name);
}
}
return $terms ? implode(', ', $terms) : check_plain($this->argument);
}
else {
return check_plain($this->argument);
}
}
/**
* Fill $this->value with data from the argument.
*
* Uses views_break_phrase(), if appropriate.
*/
protected function fillValue() {
if (!empty($this->options['break_phrase'])) {
views_break_phrase($this->argument, $this);
}
else {
$this->value = array($this->argument);
}
}
}

View File

@@ -1,17 +0,0 @@
<?php
/**
* Views argument handler class for handling fulltext fields.
*/
class SearchApiViewsHandlerArgumentText extends SearchApiViewsHandlerArgument {
/**
* Get the title this argument will assign the view, given the argument.
*
* This usually needs to be overridden to provide a proper title.
*/
public function title() {
return t('Search for "@arg"', array('@field' => $this->definition['title'], '@arg' => $this->argument));
}
}

View File

@@ -5,12 +5,33 @@
*/
class SearchApiViewsHandlerFilterFulltext extends SearchApiViewsHandlerFilterText {
/**
* Displays the operator form, adding a description.
*/
public function show_operator_form(&$form, &$form_state) {
$this->operator_form($form, $form_state);
$form['operator']['#description'] = t('This operator is only useful when using \'Search keys\'.');
}
/**
* Provide a list of options for the operator form.
*/
public function operator_options() {
return array(
'AND' => t('Contains all of these words'),
'OR' => t('Contains any of these words'),
'NOT' => t('Contains none of these words'),
);
}
/**
* Specify the options this filter uses.
*/
public function option_definition() {
$options = parent::option_definition();
$options['operator']['default'] = 'AND';
$options['mode'] = array('default' => 'keys');
$options['fields'] = array('default' => array());
@@ -27,7 +48,7 @@ class SearchApiViewsHandlerFilterFulltext extends SearchApiViewsHandlerFilterTex
'#title' => t('Use as'),
'#type' => 'radios',
'#options' => array(
'keys' => t('Search keys multiple words will be split and the filter will influence relevance.'),
'keys' => t('Search keys multiple words will be split and the filter will influence relevance. You can change how search keys are parsed under "Advanced" > "Query settings".'),
'filter' => t("Search filter use as a single phrase that restricts the result set but doesn't influence relevance."),
),
'#default_value' => $this->options['mode'],
@@ -87,10 +108,16 @@ class SearchApiViewsHandlerFilterFulltext extends SearchApiViewsHandlerFilterTex
return;
}
// If the operator was set to OR, set it as the conjunction. (AND is set by
// default.)
if ($this->operator === 'OR') {
$this->query->setOption('conjunction', $this->operator);
}
$this->query->fields($fields);
$old = $this->query->getOriginalKeys();
$this->query->keys($this->value);
if ($this->operator != '=') {
if ($this->operator == 'NOT') {
$keys = &$this->query->getKeys();
if (is_array($keys)) {
$keys['#negation'] = TRUE;

View File

@@ -15,12 +15,18 @@ class SearchApiViewsHandlerFilterOptions extends SearchApiViewsHandlerFilter {
* Provide a list of options for the operator form.
*/
public function operator_options() {
return array(
$options = array(
'=' => t('Is one of'),
'<>' => t('Is not one of'),
'all of' => t('Is all of'),
'<>' => t('Is none of'),
'empty' => t('Is empty'),
'not empty' => t('Is not empty'),
);
// "Is all of" doesn't make sense for single-valued fields.
if (empty($this->definition['multi-valued'])) {
unset($options['all of']);
}
return $options;
}
/**
@@ -99,12 +105,10 @@ class SearchApiViewsHandlerFilterOptions extends SearchApiViewsHandlerFilter {
'#options' => $options,
'#multiple' => TRUE,
'#size' => min(4, count($this->definition['options'])),
'#default_value' => isset($this->value) ? $this->value : array(),
'#default_value' => is_array($this->value) ? $this->value : array(),
);
// Hide the value box if operator is 'empty' or 'not empty'.
// Radios share the same selector so we have to add some dummy selector.
// #states replace #dependency (http://drupal.org/node/1595022).
$form['value']['#states']['visible'] = array(
':input[name="options[operator]"],dummy-empty' => array('!value' => 'empty'),
':input[name="options[operator]"],dummy-not-empty' => array('!value' => 'not empty'),
@@ -142,11 +146,21 @@ class SearchApiViewsHandlerFilterOptions extends SearchApiViewsHandlerFilter {
}
// Choose different kind of ouput for 0, a single and multiple values.
if (count($this->value) == 0) {
return $this->operator == '=' ? t('none') : t('any');
return $this->operator != '<>' ? t('none') : t('any');
}
elseif (count($this->value) == 1) {
switch ($this->operator) {
case '=':
case 'all of':
$operator = '=';
break;
case '<>':
$operator = '<>';
break;
}
// If there is only a single value, use just the plain operator, = or <>.
$operator = check_plain($this->operator);
$operator = check_plain($operator);
$values = check_plain($this->definition['options'][reset($this->value)]);
}
else {
@@ -171,33 +185,56 @@ class SearchApiViewsHandlerFilterOptions extends SearchApiViewsHandlerFilter {
public function query() {
if ($this->operator === 'empty') {
$this->query->condition($this->real_field, NULL, '=', $this->options['group']);
return;
}
elseif ($this->operator === 'not empty') {
if ($this->operator === 'not empty') {
$this->query->condition($this->real_field, NULL, '<>', $this->options['group']);
return;
}
else {
while (is_array($this->value) && count($this->value) == 1) {
$this->value = reset($this->value);
// Extract the value.
while (is_array($this->value) && count($this->value) == 1) {
$this->value = reset($this->value);
}
// Determine operator and conjunction.
switch ($this->operator) {
case '=':
$operator = '=';
$conjunction = 'OR';
break;
case 'all of':
$operator = '=';
$conjunction = 'AND';
break;
case '<>':
$operator = '<>';
$conjunction = 'AND';
break;
}
// If the value is an empty array, we either want no filter at all (for
// "is none of", or want to find only items with no value for the field.
if ($this->value === array()) {
if ($this->operator != '<>') {
$this->query->condition($this->real_field, NULL, '=', $this->options['group']);
}
if (is_scalar($this->value) && $this->value !== '') {
$this->query->condition($this->real_field, $this->value, $this->operator, $this->options['group']);
}
elseif ($this->value) {
if ($this->operator == '=') {
$filter = $this->query->createFilter('OR');
// $filter will be NULL if there were errors in the query.
if ($filter) {
foreach ($this->value as $v) {
$filter->condition($this->real_field, $v, '=');
}
$this->query->filter($filter, $this->options['group']);
}
}
else {
foreach ($this->value as $v) {
$this->query->condition($this->real_field, $v, $this->operator, $this->options['group']);
}
return;
}
if (is_scalar($this->value) && $this->value !== '') {
$this->query->condition($this->real_field, $this->value, $operator, $this->options['group']);
}
elseif ($this->value) {
$filter = $this->query->createFilter($conjunction);
// $filter will be NULL if there were errors in the query.
if ($filter) {
foreach ($this->value as $v) {
$filter->condition($this->real_field, $v, $operator);
}
$this->query->filter($filter, $this->options['group']);
}
}
}

View File

@@ -0,0 +1,128 @@
<?php
/**
* @file
* Contains the SearchApiViewsCache class.
*/
/**
* Plugin class for caching Search API views.
*/
class SearchApiViewsCache extends views_plugin_cache_time {
/**
* Static cache for get_results_key().
*
* @var string
*/
protected $_results_key = NULL;
/**
* Static cache for getSearchApiQuery().
*
* @var SearchApiQueryInterface
*/
protected $search_api_query = NULL;
/**
* Overrides views_plugin_cache::cache_set().
*
* Also stores Search API's internal search results.
*/
public function cache_set($type) {
if ($type != 'results') {
return parent::cache_set($type);
}
$cid = $this->get_results_key();
$data = array(
'result' => $this->view->result,
'total_rows' => isset($this->view->total_rows) ? $this->view->total_rows : 0,
'current_page' => $this->view->get_current_page(),
'search_api results' => $this->view->query->getSearchApiResults(),
);
cache_set($cid, $data, $this->table, $this->cache_set_expire($type));
}
/**
* Overrides views_plugin_cache::cache_get().
*
* Additionally stores successfully retrieved results with
* search_api_current_search().
*/
public function cache_get($type) {
if ($type != 'results') {
return parent::cache_get($type);
}
// Values to set: $view->result, $view->total_rows, $view->execute_time,
// $view->current_page.
if ($cache = cache_get($this->get_results_key(), $this->table)) {
$cutoff = $this->cache_expire($type);
if (!$cutoff || $cache->created > $cutoff) {
$this->view->result = $cache->data['result'];
$this->view->total_rows = $cache->data['total_rows'];
$this->view->set_current_page($cache->data['current_page']);
$this->view->execute_time = 0;
// Trick Search API into believing a search happened, to make facetting
// et al. work.
$query = $this->getSearchApiQuery();
search_api_current_search($query->getOption('search id'), $query, $cache->data['search_api results']);
return TRUE;
}
}
return FALSE;
}
/**
* Overrides views_plugin_cache::get_results_key().
*
* Use the Search API query as the main source for the key.
*/
public function get_results_key() {
global $user;
if (!isset($this->_results_key)) {
$query = $this->getSearchApiQuery();
$query->preExecute();
$key_data = array(
'query' => $query,
'roles' => array_keys($user->roles),
'super-user' => $user->uid == 1, // special caching for super user.
'language' => $GLOBALS['language']->language,
'base_url' => $GLOBALS['base_url'],
);
// Not sure what gets passed in exposed_info, so better include it. All
// other parameters used in the parent method are already reflected in the
// Search API query object we use.
if (isset($_GET['exposed_info'])) {
$key_data[$key] = $_GET[$key];
}
$this->_results_key = $this->view->name . ':' . $this->display->id . ':results:' . md5(serialize($key_data));
}
return $this->_results_key;
}
/**
* Get the Search API query object associated with the current view.
*
* @return SearchApiQueryInterface|null
* The Search API query object associated with the current view; or NULL if
* there is none.
*/
protected function getSearchApiQuery() {
if (!isset($this->search_api_query)) {
$this->search_api_query = FALSE;
if (isset($this->view->query) && $this->view->query instanceof SearchApiViewsQuery) {
$this->search_api_query = $this->view->query->getSearchApiQuery();
}
}
return $this->search_api_query ? $this->search_api_query : NULL;
}
}

109
contrib/search_api_views/includes/query.inc Executable file → Normal file
View File

@@ -50,6 +50,13 @@ class SearchApiViewsQuery extends views_plugin_query {
*/
protected $errors;
/**
* Whether to abort the search instead of executing it.
*
* @var bool
*/
protected $abort = FALSE;
/**
* The names of all fields whose value is required by a handler.
*
@@ -85,7 +92,7 @@ class SearchApiViewsQuery extends views_plugin_query {
$id = substr($base_table, 17);
$this->index = search_api_index_load($id);
$this->query = $this->index->query(array(
'parse mode' => 'terms',
'parse mode' => $this->options['parse_mode'],
));
}
}
@@ -126,13 +133,19 @@ class SearchApiViewsQuery extends views_plugin_query {
/**
* Defines the options used by this query plugin.
*
* Adds an option to bypass access checks.
* Adds some access options.
*/
public function option_definition() {
return parent::option_definition() + array(
'search_api_bypass_access' => array(
'default' => FALSE,
),
'entity_access' => array(
'default' => FALSE,
),
'parse_mode' => array(
'default' => 'terms',
),
);
}
@@ -150,6 +163,36 @@ class SearchApiViewsQuery extends views_plugin_query {
'#description' => t('If the underlying search index has access checks enabled, this option allows to disable them for this view.'),
'#default_value' => $this->options['search_api_bypass_access'],
);
if (entity_get_info($this->index->item_type)) {
$form['entity_access'] = array(
'#type' => 'checkbox',
'#title' => t('Additional access checks on result entities'),
'#description' => t("Execute an access check for all result entities. This prevents users from seeing inappropriate content when the index contains stale data, or doesn't provide access checks. However, result counts, paging and other things won't work correctly if results are eliminated in this way, so only use this as a last ressort (and in addition to other checks, if possible)."),
'#default_value' => $this->options['entity_access'],
);
}
$form['parse_mode'] = array(
'#type' => 'select',
'#title' => t('Parse mode'),
'#description' => t('Choose how the search keys will be parsed.'),
'#options' => array(),
'#default_value' => $this->options['parse_mode'],
);
$modes = array();
foreach ($this->query->parseModes() as $key => $mode) {
$form['parse_mode']['#options'][$key] = $mode['name'];
if (!empty($mode['description'])) {
$states['visible'][':input[name="query[options][parse_mode]"]']['value'] = $key;
$form["parse_mode_{$key}_description"] = array(
'#type' => 'item',
'#title' => $mode['name'],
'#description' => $mode['description'],
'#states' => $states,
);
}
}
}
/**
@@ -173,6 +216,7 @@ class SearchApiViewsQuery extends views_plugin_query {
// Add a nested filter for each filter group, with its set conjunction.
foreach ($this->where as $group_id => $group) {
if (!empty($group['conditions']) || !empty($group['filters'])) {
$group += array('type' => 'AND');
// For filters without a group, we want to always add them directly to
// the query.
$filter = ($group_id === '') ? $this->query : $this->query->createFilter($group['type']);
@@ -199,6 +243,21 @@ class SearchApiViewsQuery extends views_plugin_query {
$view->init_pager();
$this->pager->query();
// Views passes sometimes NULL and sometimes the integer 0 for "All" in a
// pager. If set to 0 items, a string "0" is passed. Therefore, we unset
// the limit if an empty value OTHER than a string "0" was passed.
if (!$this->limit && $this->limit !== '0') {
$this->limit = NULL;
}
// Set the range. (We always set this, as there might even be an offset if
// all items are shown.)
$this->query->range($this->offset, $this->limit);
// Set the search ID, if it was not already set.
if ($this->query->getOption('search id') == get_class($this->query)) {
$this->query->setOption('search id', 'search_api_views:' . $view->name . ':' . $view->current_display);
}
// Add the "search_api_bypass_access" option to the query, if desired.
if (!empty($this->options['search_api_bypass_access'])) {
$this->query->setOption('search_api_bypass_access', TRUE);
@@ -213,7 +272,7 @@ class SearchApiViewsQuery extends views_plugin_query {
* $view->pager['current_page'].
*/
public function execute(&$view) {
if ($this->errors) {
if ($this->errors || $this->abort) {
if (error_displayable()) {
foreach ($this->errors as $msg) {
drupal_set_message(check_plain($msg), 'error');
@@ -227,11 +286,6 @@ class SearchApiViewsQuery extends views_plugin_query {
try {
$start = microtime(TRUE);
// Add range and search ID (if it wasn't already set).
$this->query->range($this->offset, $this->limit);
if ($this->query->getOption('search id') == get_class($this->query)) {
$this->query->setOption('search id', 'search_api_views:' . $view->name . ':' . $view->current_display);
}
// Execute the search.
$results = $this->query->execute();
@@ -258,6 +312,16 @@ class SearchApiViewsQuery extends views_plugin_query {
}
}
/**
* Aborts this search query.
*
* Used by handlers to flag a fatal error which shouldn't be displayed but
* still lead to the view returning empty and the search not being executed.
*/
public function abort() {
$this->abort = TRUE;
}
/**
* Helper function for adding results to a view in the format expected by the
* view.
@@ -270,6 +334,12 @@ class SearchApiViewsQuery extends views_plugin_query {
// First off, we try to gather as much field values as possible without
// loading any items.
foreach ($results as $id => $result) {
if (!empty($this->options['entity_access'])) {
$entity = entity_load($this->index->item_type, array($id));
if (!entity_access('view', $this->index->item_type, $entity[$id])) {
continue;
}
}
$row = array();
// Include the loaded item for this result row, if present, or the item
@@ -353,12 +423,21 @@ class SearchApiViewsQuery extends views_plugin_query {
public function get_result_entities($results, $relationship = NULL, $field = NULL) {
list($type, $wrappers) = $this->get_result_wrappers($results, $relationship, $field);
$return = array();
foreach ($wrappers as $id => $wrapper) {
foreach ($wrappers as $i => $wrapper) {
try {
$return[$id] = $wrapper->value();
// Get the entity ID beforehand for possible watchdog messages.
$id = $wrapper->value(array('identifier' => TRUE));
// Only add results that exist.
if ($entity = $wrapper->value()) {
$return[$i] = $entity;
}
else {
watchdog('search_api_views', 'The search index returned a reference to an entity with ID @id, which does not exist in the database. Your index may be out of sync and should be rebuilt.', array('@id' => $id), WATCHDOG_ERROR);
}
}
catch (EntityMetadataWrapperException $e) {
// Ignore.
watchdog_exception('search_api_views', $e, "%type while trying to load search result entity with ID @id: !message in %function (line %line of %file).", array('@id' => $id), WATCHDOG_ERROR);
}
}
return array($type, $return);
@@ -371,11 +450,11 @@ class SearchApiViewsQuery extends views_plugin_query {
* query backend.
*/
public function get_result_wrappers($results, $relationship = NULL, $field = NULL) {
$is_entity = (boolean) entity_get_info($this->index->item_type);
$entity_type = $this->index->getEntityType();
$wrappers = array();
$load_entities = array();
foreach ($results as $row_index => $row) {
if ($is_entity && isset($row->entity)) {
if ($entity_type && 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;
@@ -388,14 +467,14 @@ class SearchApiViewsQuery extends views_plugin_query {
// 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($this->index->item_type, array_keys($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);
}
}
// Apply the relationship, if necessary.
$type = $this->index->item_type;
$type = $entity_type ? $entity_type : $this->index->item_type;
$selector_suffix = '';
if ($field && ($pos = strrpos($field, ':'))) {
$selector_suffix = substr($field, 0, $pos);

View File

@@ -6,12 +6,13 @@ dependencies[] = views
core = 7.x
package = Search
; Views handlers
; Views handlers/plugins
files[] = includes/display_facet_block.inc
files[] = includes/handler_argument.inc
files[] = includes/handler_argument_fulltext.inc
files[] = includes/handler_argument_more_like_this.inc
files[] = includes/handler_argument_text.inc
files[] = includes/handler_argument_string.inc
files[] = includes/handler_argument_taxonomy_term.inc
files[] = includes/handler_filter.inc
files[] = includes/handler_filter_boolean.inc
files[] = includes/handler_filter_date.inc
@@ -20,11 +21,12 @@ files[] = includes/handler_filter_language.inc
files[] = includes/handler_filter_options.inc
files[] = includes/handler_filter_text.inc
files[] = includes/handler_sort.inc
files[] = includes/plugin_cache.inc
files[] = includes/query.inc
; Information added by drupal.org packaging script on 2013-01-09
version = "7.x-1.4"
; Information added by drupal.org packaging script on 2013-09-01
version = "7.x-1.8"
core = "7.x"
project = "search_api"
datestamp = "1357726719"
datestamp = "1378025826"

View File

@@ -21,9 +21,19 @@ function search_api_views_search_api_index_insert(SearchApiIndex $index) {
* Implements hook_search_api_index_update().
*/
function search_api_views_search_api_index_update(SearchApiIndex $index) {
// Check whether index was disabled.
if (!$index->enabled && $index->original->enabled) {
_search_api_views_index_unavailable($index);
}
// Check whether the indexed fields changed.
$old_fields = $index->original->options + array('fields' => array());
$old_fields = $old_fields['fields'];
$new_fields = $index->options + array('fields' => array());
$new_fields = $new_fields['fields'];
if ($old_fields != $new_fields) {
views_invalidate_cache();
}
}
/**

View File

@@ -20,14 +20,20 @@ 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->item_type])) {
if (isset($entity_types[$index->getEntityType()])) {
$table['table'] += array(
'entity type' => $index->item_type,
'entity type' => $index->getEntityType(),
'skip entity load' => TRUE,
);
}
$wrapper = $index->entityWrapper(NULL, TRUE);
try {
$wrapper = $index->entityWrapper(NULL, TRUE);
}
catch (EntityMetadataWrapperException $e) {
watchdog_exception('search_api_views', $e, "%type while retrieving metadata for index %index: !message in %function (line %line of %file).", array('%index' => $index->name), WATCHDOG_WARNING);
continue;
}
// Add field handlers and relationships provided by the Entity API.
foreach ($wrapper as $key => $property) {
@@ -105,6 +111,31 @@ function search_api_views_views_data() {
$table['search_api_views_more_like_this']['title'] = t('More like this');
$table['search_api_views_more_like_this']['help'] = t('Find similar content.');
$table['search_api_views_more_like_this']['argument']['handler'] = 'SearchApiViewsHandlerArgumentMoreLikeThis';
// If there are taxonomy term references indexed in the index, include the
// "Indexed taxonomy term fields" contextual filter. We also save for all
// fields whether they contain only terms of a certain vocabulary, keying
// that information by vocabulary for later ease of use.
$vocabulary_fields = array();
foreach ($index->getFields() as $key => $field) {
if (isset($field['entity_type']) && $field['entity_type'] === 'taxonomy_term') {
$field_id = ($pos = strrpos($key, ':')) ? substr($key, $pos + 1) : $key;
$field_info = field_info_field($field_id);
if (isset($field_info['settings']['allowed_values'][0]['vocabulary'])) {
$vocabulary_fields[$field_info['settings']['allowed_values'][0]['vocabulary']][] = $key;
}
else {
$vocabulary_fields[''][] = $key;
}
}
}
if ($vocabulary_fields) {
$table['search_api_views_taxonomy_term']['group'] = t('Search');
$table['search_api_views_taxonomy_term']['title'] = t('Indexed taxonomy term fields');
$table['search_api_views_taxonomy_term']['help'] = t('Search in all indexed taxonomy term fields.');
$table['search_api_views_taxonomy_term']['argument']['handler'] = 'SearchApiViewsHandlerArgumentTaxonomyTerm';
$table['search_api_views_taxonomy_term']['argument']['vocabulary_fields'] = $vocabulary_fields;
}
}
return $data;
}
@@ -130,7 +161,7 @@ function _search_api_views_add_handlers($id, array $field, EntityMetadataWrapper
if ($inner_type == 'text') {
$table[$id] += array(
'argument' => array(
'handler' => 'SearchApiViewsHandlerArgumentText',
'handler' => 'SearchApiViewsHandlerArgument',
),
'filter' => array(
'handler' => 'SearchApiViewsHandlerFilterText',
@@ -142,6 +173,7 @@ function _search_api_views_add_handlers($id, array $field, EntityMetadataWrapper
if ($options = $wrapper->optionsList('view')) {
$table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilterOptions';
$table[$id]['filter']['options'] = $options;
$table[$id]['filter']['multi-valued'] = search_api_is_list_type($type);
}
elseif ($inner_type == 'boolean') {
$table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilterBoolean';
@@ -153,7 +185,12 @@ function _search_api_views_add_handlers($id, array $field, EntityMetadataWrapper
$table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilter';
}
$table[$id]['argument']['handler'] = 'SearchApiViewsHandlerArgument';
if ($inner_type == 'string' || $inner_type == 'uri') {
$table[$id]['argument']['handler'] = 'SearchApiViewsHandlerArgumentString';
}
else {
$table[$id]['argument']['handler'] = 'SearchApiViewsHandlerArgument';
}
// We can only sort according to single-valued fields.
if ($type == $inner_type) {
@@ -168,12 +205,27 @@ function _search_api_views_add_handlers($id, array $field, EntityMetadataWrapper
* Implements hook_views_plugins().
*/
function search_api_views_views_plugins() {
// Collect all base tables provided by this module.
$bases = array();
foreach (search_api_index_load_multiple(FALSE) as $index) {
$bases[] = 'search_api_index_' . $index->machine_name;
}
$ret = array(
'query' => array(
'search_api_views_query' => array(
'title' => t('Search API Query'),
'help' => t('Query will be generated and run using the Search API.'),
'handler' => 'SearchApiViewsQuery'
'handler' => 'SearchApiViewsQuery',
),
),
'cache' => array(
'search_api_views_cache' => array(
'title' => t('Search-specific'),
'help' => t("Cache Search API views. (Other methods probably won't work with search views.)"),
'base' => $bases,
'handler' => 'SearchApiViewsCache',
'uses options' => TRUE,
),
),
);