upadted to 1.8
This commit is contained in:
@@ -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);
|
||||
|
||||
|
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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();
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -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));
|
||||
}
|
||||
|
||||
}
|
@@ -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;
|
||||
|
@@ -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']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
128
contrib/search_api_views/includes/plugin_cache.inc
Normal file
128
contrib/search_api_views/includes/plugin_cache.inc
Normal 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
109
contrib/search_api_views/includes/query.inc
Executable file → Normal 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);
|
||||
|
Reference in New Issue
Block a user