upadted to 1.8
This commit is contained in:
@@ -128,8 +128,10 @@ class SearchApiFacetapiAdapter extends FacetapiAdapter {
|
||||
* search_api_current_search(). Or NULL, if no match was found.
|
||||
*/
|
||||
public function getCurrentSearch() {
|
||||
// Even if this fails once, there might be a search query later in the page
|
||||
// request. We therefore don't store anything in $this->current_search in
|
||||
// case of failure, but just try again if the method is called again.
|
||||
if (!isset($this->current_search)) {
|
||||
$this->current_search = FALSE;
|
||||
$index_id = $this->info['instance'];
|
||||
// There is currently no way to configure the "current search" block to
|
||||
// show on a per-searcher basis as we do with the facets. Therefore we
|
||||
@@ -143,7 +145,7 @@ class SearchApiFacetapiAdapter extends FacetapiAdapter {
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->current_search ? $this->current_search : NULL;
|
||||
return $this->current_search;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -172,16 +174,6 @@ class SearchApiFacetapiAdapter extends FacetapiAdapter {
|
||||
// properly.
|
||||
$keys = '[' . t('complex query') . ']';
|
||||
}
|
||||
elseif (!$keys) {
|
||||
// If a base path other than the current one is set, we assume that we
|
||||
// shouldn't report on the current search. Highly hack-y, of course.
|
||||
if ($search[0]->getOption('search_api_base_path', $_GET['q']) !== $_GET['q']) {
|
||||
return NULL;
|
||||
}
|
||||
// Work-around since Facet API won't show the "Current search" block
|
||||
// without keys.
|
||||
$keys = '[' . t('all items') . ']';
|
||||
}
|
||||
drupal_alter('search_api_facetapi_keys', $keys, $search[0]);
|
||||
return $keys;
|
||||
}
|
||||
@@ -238,5 +230,25 @@ class SearchApiFacetapiAdapter extends FacetapiAdapter {
|
||||
'#value' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
// Add a granularity option to date query types.
|
||||
if (isset($facet['query type']) && $facet['query type'] == 'date') {
|
||||
$granularity_options = array(
|
||||
FACETAPI_DATE_YEAR => t('Years'),
|
||||
FACETAPI_DATE_MONTH => t('Months'),
|
||||
FACETAPI_DATE_DAY => t('Days'),
|
||||
FACETAPI_DATE_HOUR => t('Hours'),
|
||||
FACETAPI_DATE_MINUTE => t('Minutes'),
|
||||
FACETAPI_DATE_SECOND => t('Seconds'),
|
||||
);
|
||||
|
||||
$form['global']['date_granularity'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Granularity'),
|
||||
'#description' => t('Determine the maximum drill-down level'),
|
||||
'#options' => $granularity_options,
|
||||
'#default_value' => isset($options['date_granularity']) ? $options['date_granularity'] : FACETAPI_DATE_MINUTE,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -85,8 +85,8 @@ class SearchApiFacetapiDate extends SearchApiFacetapiTerm implements FacetapiQue
|
||||
// this method.
|
||||
|
||||
// Executes query, iterates over results.
|
||||
if (isset($results['search_api_facets']) && isset($results['search_api_facets'][$this->facet['field']])) {
|
||||
$values = $results['search_api_facets'][$this->facet['field']];
|
||||
if (isset($results['search_api_facets']) && isset($results['search_api_facets'][$this->facet['name']])) {
|
||||
$values = $results['search_api_facets'][$this->facet['name']];
|
||||
foreach ($values as $value) {
|
||||
if ($value['count']) {
|
||||
$filter = $value['filter'];
|
||||
@@ -115,20 +115,24 @@ class SearchApiFacetapiDate extends SearchApiFacetapiTerm implements FacetapiQue
|
||||
}
|
||||
}
|
||||
|
||||
// Get the finest level of detail we're allowed to drill down to.
|
||||
$settings = $facet->getSettings()->settings;
|
||||
$granularity = isset($settings['date_granularity']) ? $settings['date_granularity'] : FACETAPI_DATE_MINUTE;
|
||||
|
||||
// Gets active facets, starts building hierarchy.
|
||||
$parent = $gap = NULL;
|
||||
foreach ($this->adapter->getActiveItems($this->facet) as $value => $item) {
|
||||
// If the item is active, the count is the result set count.
|
||||
$build[$value] = array('#count' => $total);
|
||||
|
||||
// Gets next "gap" increment, minute being the lowest we can go.
|
||||
// Gets next "gap" increment.
|
||||
if ($value[0] != '[' || $value[strlen($value) - 1] != ']' || !($pos = strpos($value, ' TO '))) {
|
||||
continue;
|
||||
}
|
||||
$start = substr($value, 1, $pos);
|
||||
$end = substr($value, $pos + 4, -1);
|
||||
$date_gap = facetapi_get_date_gap($start, $end);
|
||||
$gap = facetapi_get_next_date_gap($date_gap, FACETAPI_DATE_MINUTE);
|
||||
$gap = facetapi_get_next_date_gap($date_gap, $granularity);
|
||||
|
||||
// If there is a previous item, there is a parent, uses a reference so the
|
||||
// arrays are populated when they are updated.
|
||||
@@ -150,9 +154,24 @@ class SearchApiFacetapiDate extends SearchApiFacetapiTerm implements FacetapiQue
|
||||
if (NULL === $parent) {
|
||||
if (count($raw_values) > 1) {
|
||||
$gap = facetapi_get_timestamp_gap(min($timestamps), max($timestamps));
|
||||
// Array of numbers used to determine whether the next gap is smaller than
|
||||
// the minimum gap allowed in the drilldown.
|
||||
$gap_numbers = array(
|
||||
FACETAPI_DATE_YEAR => 6,
|
||||
FACETAPI_DATE_MONTH => 5,
|
||||
FACETAPI_DATE_DAY => 4,
|
||||
FACETAPI_DATE_HOUR => 3,
|
||||
FACETAPI_DATE_MINUTE => 2,
|
||||
FACETAPI_DATE_SECOND => 1,
|
||||
);
|
||||
// Gets gap numbers for both the gap and minimum gap, checks if the gap
|
||||
// is within the limit set by the $granularity parameter.
|
||||
if ($gap_numbers[$gap] < $gap_numbers[$granularity]) {
|
||||
$gap = $granularity;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$gap = FACETAPI_DATE_HOUR;
|
||||
$gap = $granularity;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -120,8 +120,8 @@ class SearchApiFacetapiTerm extends FacetapiQueryType implements FacetapiQueryTy
|
||||
$search = search_api_current_search($search_id);
|
||||
$build = array();
|
||||
$results = $search[1];
|
||||
if (isset($results['search_api_facets']) && isset($results['search_api_facets'][$this->facet['field']])) {
|
||||
$values = $results['search_api_facets'][$this->facet['field']];
|
||||
if (isset($results['search_api_facets']) && isset($results['search_api_facets'][$this->facet['name']])) {
|
||||
$values = $results['search_api_facets'][$this->facet['name']];
|
||||
foreach ($values as $value) {
|
||||
$filter = $value['filter'];
|
||||
// As Facet API isn't really suited for our native facet filter
|
||||
|
@@ -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 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"
|
||||
|
||||
|
@@ -65,6 +65,9 @@ function search_api_facetapi_facetapi_searcher_info() {
|
||||
'supports facet mincount' => TRUE,
|
||||
'include default facets' => FALSE,
|
||||
);
|
||||
if (($entity_type = $index->getEntityType()) && $entity_type !== $index->item_type) {
|
||||
$info[$searcher_name]['types'][] = $entity_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $info;
|
||||
@@ -80,7 +83,7 @@ function search_api_facetapi_facetapi_facet_info(array $searcher_info) {
|
||||
if (!empty($index->options['fields'])) {
|
||||
$wrapper = $index->entityWrapper();
|
||||
$bundle_key = NULL;
|
||||
if (($entity_info = entity_get_info($index->item_type)) && !empty($entity_info['bundle keys']['bundle'])) {
|
||||
if ($index->getEntityType() && ($entity_info = entity_get_info($index->getEntityType())) && !empty($entity_info['bundle keys']['bundle'])) {
|
||||
$bundle_key = $entity_info['bundle keys']['bundle'];
|
||||
}
|
||||
|
||||
@@ -144,7 +147,7 @@ function search_api_facetapi_facetapi_facet_info(array $searcher_info) {
|
||||
if ($bundle_key) {
|
||||
if ($key === $bundle_key) {
|
||||
// Set entity type this field contains bundle information for.
|
||||
$facet_info[$key]['field api bundles'][] = $index->item_type;
|
||||
$facet_info[$key]['field api bundles'][] = $index->getEntityType();
|
||||
}
|
||||
else {
|
||||
// Add "bundle" as possible dependency plugin.
|
||||
@@ -313,25 +316,49 @@ function search_api_facetapi_facet_map_callback(array $values, array $options =
|
||||
|
||||
/**
|
||||
* Creates a human-readable label for single facet filter values.
|
||||
*
|
||||
* @param array $values
|
||||
* The values for which labels should be returned.
|
||||
* @param array $options
|
||||
* An associative array containing the following information about the facet:
|
||||
* - field: Field information, as stored in the index, but with an additional
|
||||
* "key" property set to the field's internal name.
|
||||
* - index id: The machine name of the index for this facet.
|
||||
* - map callback: (optional) A callback that will be called at the beginning,
|
||||
* which allows initial mapping of filters. Only values not mapped by that
|
||||
* callback will be processed by this method.
|
||||
* - value callback: A callback used to map single values and the limits of
|
||||
* ranges. The signature is the same as for this function, but all values
|
||||
* will be single values.
|
||||
* - missing label: (optional) The label used for the "missing" facet.
|
||||
*
|
||||
* @return array
|
||||
* An array mapping raw facet values to their labels.
|
||||
*/
|
||||
function _search_api_facetapi_facet_create_label(array $values, array $options) {
|
||||
$field = $options['field'];
|
||||
$map = array();
|
||||
$n = count($values);
|
||||
|
||||
// For entities, we can simply use the entity labels.
|
||||
if (isset($field['entity_type'])) {
|
||||
$type = $field['entity_type'];
|
||||
$entities = entity_load($type, $values);
|
||||
$map = array();
|
||||
foreach ($entities as $id => $entity) {
|
||||
$label = entity_label($type, $entity);
|
||||
if ($label) {
|
||||
$map[$id] = $label;
|
||||
}
|
||||
}
|
||||
return $map;
|
||||
if (count($map) == $n) {
|
||||
return $map;
|
||||
}
|
||||
}
|
||||
|
||||
// Then, we check whether there is an options list for the field.
|
||||
$index = search_api_index_load($options['index id']);
|
||||
$wrapper = $index->entityWrapper();
|
||||
$values = drupal_map_assoc($values);
|
||||
foreach (explode(':', $field['key']) as $part) {
|
||||
if (!isset($wrapper->$part)) {
|
||||
$wrapper = NULL;
|
||||
@@ -342,12 +369,18 @@ function _search_api_facetapi_facet_create_label(array $values, array $options)
|
||||
$wrapper = $wrapper[0];
|
||||
}
|
||||
}
|
||||
if ($wrapper && ($options = $wrapper->optionsList('view'))) {
|
||||
return $options;
|
||||
if ($wrapper && ($options_list = $wrapper->optionsList('view'))) {
|
||||
// We have no use for empty strings, as then the facet links would be
|
||||
// invisible.
|
||||
$map += array_intersect_key(array_filter($options_list, 'strlen'), $values);
|
||||
if (count($map) == $n) {
|
||||
return $map;
|
||||
}
|
||||
}
|
||||
// As a "last resort" we try to create a label based on the field type.
|
||||
$map = array();
|
||||
foreach ($values as $value) {
|
||||
|
||||
// As a "last resort" we try to create a label based on the field type, for
|
||||
// all values that haven't got a mapping yet.
|
||||
foreach (array_diff_key($values, $map) as $value) {
|
||||
switch ($field['type']) {
|
||||
case 'boolean':
|
||||
$map[$value] = $value ? t('true') : t('false');
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
|
@@ -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"
|
||||
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
Reference in New Issue
Block a user