updated to 7.x-1.11

This commit is contained in:
Bachir Soussi Chiadmi
2014-02-07 10:01:18 +01:00
parent a30917d1d2
commit cf03e9ca52
69 changed files with 4629 additions and 1557 deletions

View File

@@ -151,11 +151,9 @@ class SearchApiViewsFacetsBlockDisplay extends views_plugin_display_block {
}
}
public function execute() {
if (substr($this->view->base_table, 0, 17) != 'search_api_index_') {
form_set_error('', t('The "Facets block" display can only be used with base tables based on Search API indexes.'));
return NULL;
}
public function query(){
parent::query();
$facet_field = $this->get_option('facet_field');
if (!$facet_field) {
return NULL;
@@ -165,7 +163,7 @@ class SearchApiViewsFacetsBlockDisplay extends views_plugin_display_block {
if (!$base_path) {
$base_path = $_GET['q'];
}
$this->view->build();
$limit = empty($this->view->query->pager->options['items_per_page']) ? 10 : $this->view->query->pager->options['items_per_page'];
$query_options = &$this->view->query->getOptions();
if (!$this->get_option('hide_block')) {
@@ -179,6 +177,17 @@ class SearchApiViewsFacetsBlockDisplay extends views_plugin_display_block {
}
$query_options['search_api_base_path'] = $base_path;
$this->view->query->range(0, 0);
}
public function render() {
if (substr($this->view->base_table, 0, 17) != 'search_api_index_') {
form_set_error('', t('The "Facets block" display can only be used with base tables based on Search API indexes.'));
return NULL;
}
$facet_field = $this->get_option('facet_field');
if (!$facet_field) {
return NULL;
}
$this->view->execute();
@@ -229,7 +238,7 @@ class SearchApiViewsFacetsBlockDisplay extends views_plugin_display_block {
// Initializes variables passed to theme hook.
$variables = array(
'text' => $name,
'path' => $base_path,
'path' => $this->view->query->getOption('search_api_base_path'),
'count' => $term['count'],
'options' => array(
'attributes' => array('class' => 'facetapi-inactive'),
@@ -249,10 +258,16 @@ class SearchApiViewsFacetsBlockDisplay extends views_plugin_display_block {
return NULL;
}
$info['content']['facets'] = array(
return array(
'facets' => array(
'#theme' => 'item_list',
'#items' => $facets,
)
);
}
public function execute(){
$info['content'] = $this->render();
$info['content']['more'] = $this->render_more_link();
$info['subject'] = filter_xss_admin($this->view->get_title());
return $info;

View File

@@ -1,5 +1,10 @@
<?php
/**
* @file
* Contains SearchApiViewsHandlerArgument.
*/
/**
* Views argument handler class for handling all non-fulltext types.
*/

View File

@@ -0,0 +1,161 @@
<?php
/**
* @file
* Contains the SearchApiViewsHandlerArgumentDate class.
*/
/**
* Defines a contextual filter searching for a date or date range.
*/
class SearchApiViewsHandlerArgumentDate extends SearchApiViewsHandlerArgument {
/**
* {@inheritdoc}
*/
public function query($group_by = FALSE) {
if (empty($this->value)) {
$this->fillValue();
if ($this->value === FALSE) {
$this->abort();
return;
}
}
$outer_conjunction = strtoupper($this->operator);
if (empty($this->options['not'])) {
$operator = '=';
$inner_conjunction = 'OR';
}
else {
$operator = '<>';
$inner_conjunction = 'AND';
}
if (!empty($this->value)) {
if (!empty($this->value)) {
$outer_filter = $this->query->createFilter($outer_conjunction);
foreach ($this->value as $value) {
$value_filter = $this->query->createFilter($inner_conjunction);
$values = explode(';', $value);
$values = array_map(array($this, 'getTimestamp'), $values);
if (in_array(FALSE, $values, TRUE)) {
$this->abort();
return;
}
$is_range = (count($values) > 1);
$inner_filter = ($is_range ? $this->query->createFilter('AND') : $value_filter);
$range_op = (empty($this->options['not']) ? '>=' : '<');
$inner_filter->condition($this->real_field, $values[0], $is_range ? $range_op : $operator);
if ($is_range) {
$range_op = (empty($this->options['not']) ? '<=' : '>');
$inner_filter->condition($this->real_field, $values[1], $range_op);
$value_filter->filter($inner_filter);
}
$outer_filter->filter($value_filter);
}
$this->query->filter($outer_filter);
}
}
}
/**
* Converts a value to a timestamp, if it isn't one already.
*
* @param string|int $value
* The value to convert. Either a timestamp, or a date/time string as
* recognized by strtotime().
*
* @return int|false
* The parsed timestamp, or FALSE if an illegal string was passed.
*/
public function getTimestamp($value) {
if (is_numeric($value)) {
return $value;
}
return strtotime($value);
}
/**
* Fills $this->value with data from the argument.
*/
protected function fillValue() {
if (!empty($this->options['break_phrase'])) {
// Set up defaults:
if (!isset($this->value)) {
$this->value = array();
}
if (!isset($this->operator)) {
$this->operator = 'OR';
}
if (empty($this->argument)) {
return;
}
if (preg_match('/^([-\d;:\s]+\+)*[-\d;:\s]+$/', $this->argument)) {
// The '+' character in a query string may be parsed as ' '.
$this->value = explode('+', $this->argument);
}
elseif (preg_match('/^([-\d;:\s]+,)*[-\d;:\s]+$/', $this->argument)) {
$this->operator = 'AND';
$this->value = explode(',', $this->argument);
}
// Keep an 'error' value if invalid strings were given.
if (!empty($this->argument) && (empty($this->value) || !is_array($this->value))) {
$this->value = FALSE;
}
}
else {
$this->value = array($this->argument);
}
}
/**
* Aborts the associated query due to an illegal argument.
*/
protected function abort() {
$variables['!field'] = $this->definition['group'] . ': ' . $this->definition['title'];
$this->query->abort(t('Illegal argument passed to !field contextual filter.', $variables));
}
/**
* Computes the title this argument will assign the view, given the argument.
*
* @return string
* A title fitting for the passed argument.
*/
public function title() {
if (!empty($this->argument)) {
if (empty($this->value)) {
$this->fillValue();
}
$dates = array();
foreach ($this->value as $date) {
$date_parts = explode(';', $date);
$ts = $this->getTimestamp($date_parts[0]);
$datestr = format_date($ts, 'short');
if (count($date_parts) > 1) {
$ts = $this->getTimestamp($date_parts[1]);
$datestr .= ' - ' . format_date($ts, 'short');
}
if ($datestr) {
$dates[] = $datestr;
}
}
return $dates ? implode(', ', $dates) : check_plain($this->argument);
}
return check_plain($this->argument);
}
}

View File

@@ -1,5 +1,10 @@
<?php
/**
* @file
* Contains SearchApiViewsHandlerArgumentFulltext.
*/
/**
* Views argument handler class for handling fulltext fields.
*/

View File

@@ -1,5 +1,10 @@
<?php
/**
* @file
* Contains SearchApiViewsHandlerArgumentMoreLikeThis.
*/
/**
* Views argument handler providing a list of related items for search servers
* supporting the "search_api_mlt" feature.

View File

@@ -1,5 +1,10 @@
<?php
/**
* @file
* Contains SearchApiViewsHandlerArgumentString.
*/
/**
* Views argument handler class for handling string fields.
*/

View File

@@ -1,5 +1,10 @@
<?php
/**
* @file
* Contains SearchApiViewsHandlerFilter.
*/
/**
* Views filter handler base class for handling all "normal" cases.
*/
@@ -31,8 +36,8 @@ class SearchApiViewsHandlerFilter extends views_handler_filter {
*/
public function operator_options() {
return array(
'<' => t('Is smaller than'),
'<=' => t('Is smaller than or equal to'),
'<' => t('Is less than'),
'<=' => t('Is less than or equal to'),
'=' => t('Is equal to'),
'<>' => t('Is not equal to'),
'>=' => t('Is greater than or equal to'),
@@ -46,8 +51,8 @@ class SearchApiViewsHandlerFilter extends views_handler_filter {
* Provide a form for setting the filter value.
*/
public function value_form(&$form, &$form_state) {
while (is_array($this->value)) {
$this->value = $this->value ? array_shift($this->value) : NULL;
while (is_array($this->value) && count($this->value) < 2) {
$this->value = $this->value ? reset($this->value) : NULL;
}
$form['value'] = array(
'#type' => 'textfield',
@@ -58,10 +63,19 @@ class SearchApiViewsHandlerFilter extends views_handler_filter {
// Hide the value box if the operator is 'empty' or 'not empty'.
// Radios share the same selector so we have to add some dummy selector.
$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'),
);
if (empty($form_state['exposed'])) {
$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'),
);
}
elseif (!empty($this->options['expose']['use_operator'])) {
$name = $this->options['expose']['operator_id'];
$form['value']['#states']['visible'] = array(
':input[name="' . $name . '"],dummy-empty' => array('!value' => 'empty'),
':input[name="' . $name . '"],dummy-not-empty' => array('!value' => 'not empty'),
);
}
}
/**

View File

@@ -1,5 +1,10 @@
<?php
/**
* @file
* Contains SearchApiViewsHandlerFilterBoolean.
*/
/**
* Views filter handler class for handling fulltext fields.
*/

View File

@@ -1,5 +1,10 @@
<?php
/**
* @file
* Contains SearchApiViewsHandlerFilterDate.
*/
/**
* Views filter handler base class for handling all "normal" cases.
*/

View File

@@ -0,0 +1,211 @@
<?php
/**
* @file
* Contains SearchApiViewsHandlerFilterEntity.
*/
/**
* Views filter handler class for entities.
*
* Should be extended for specific entity types, such as
* SearchApiViewsHandlerFilterUser and SearchApiViewsHandlerFilterTaxonomyTerm.
*
* Based on views_handler_filter_term_node_tid.
*/
abstract class SearchApiViewsHandlerFilterEntity extends SearchApiViewsHandlerFilter {
/**
* If exposed form input was successfully validated, the entered entity IDs.
*
* @var array
*/
protected $validated_exposed_input;
/**
* Validates entered entity labels and converts them to entity IDs.
*
* Since this can come from either the form or the exposed filter, this is
* abstracted out a bit so it can handle the multiple input sources.
*
* @param array $form
* The form or form element for which any errors should be set.
* @param array $values
* The entered user names to validate.
*
* @return array
* The entity IDs corresponding to all entities that could be found.
*/
abstract protected function validate_entity_strings(array &$form, array $values);
/**
* Transforms an array of entity IDs into a comma-separated list of labels.
*
* @param array $ids
* The entity IDs to transform.
*
* @return string
* A string containing the labels corresponding to the IDs, separated by
* commas.
*/
abstract protected function ids_to_strings(array $ids);
/**
* {@inheritdoc}
*/
public function operator_options() {
$operators = array(
'=' => $this->isMultiValued() ? t('Is one of') : t('Is'),
'all of' => t('Is all of'),
'<>' => $this->isMultiValued() ? t('Is not one of') : t('Is not'),
'empty' => t('Is empty'),
'not empty' => t('Is not empty'),
);
if (!$this->isMultiValued()) {
unset($operators['all of']);
}
return $operators;
}
/**
* {@inheritdoc}
*/
public function option_definition() {
$options = parent::option_definition();
$options['expose']['multiple']['default'] = TRUE;
return $options;
}
/**
* {@inheritdoc}
*/
public function value_form(&$form, &$form_state) {
parent::value_form($form, $form_state);
if (!is_array($this->value)) {
$this->value = $this->value ? array($this->value) : array();
}
// Set the correct default value in case the admin-set value is used (and a
// value is present). The value is used if the form is either not exposed,
// or the exposed form wasn't submitted yet (there is
if ($this->value && (empty($form_state['input']) || !empty($form_state['input']['live_preview']))) {
$form['value']['#default_value'] = $this->ids_to_strings($this->value);
}
}
/**
* {@inheritdoc}
*/
public function value_validate($form, &$form_state) {
if (!empty($form['value'])) {
$value = &$form_state['values']['options']['value'];
$values = $this->isMultiValued($form_state['values']['options']) ? drupal_explode_tags($value) : array($value);
$ids = $this->validate_entity_strings($form['value'], $values);
if ($ids) {
$value = $ids;
}
}
}
/**
* {@inheritdoc}
*/
public function accept_exposed_input($input) {
$rc = parent::accept_exposed_input($input);
if ($rc) {
// If we have previously validated input, override.
if ($this->validated_exposed_input) {
$this->value = $this->validated_exposed_input;
}
}
return $rc;
}
/**
* {@inheritdoc}
*/
public function exposed_validate(&$form, &$form_state) {
if (empty($this->options['exposed']) || empty($this->options['expose']['identifier'])) {
return;
}
$identifier = $this->options['expose']['identifier'];
$input = $form_state['values'][$identifier];
if ($this->options['is_grouped'] && isset($this->options['group_info']['group_items'][$input])) {
$this->operator = $this->options['group_info']['group_items'][$input]['operator'];
$input = $this->options['group_info']['group_items'][$input]['value'];
}
$values = $this->isMultiValued() ? drupal_explode_tags($input) : array($input);
if (!$this->options['is_grouped'] || ($this->options['is_grouped'] && ($input != 'All'))) {
$this->validated_exposed_input = $this->validate_entity_strings($form[$identifier], $values);
}
else {
$this->validated_exposed_input = FALSE;
}
}
/**
* Determines whether multiple user names can be entered into this filter.
*
* This is either the case if the form isn't exposed, or if the " Allow
* multiple selections" option is enabled.
*
* @param array $options
* (optional) The options array to use. If not supplied, the options set on
* this filter will be used.
*
* @return bool
* TRUE if multiple values can be entered for this filter, FALSE otherwise.
*/
protected function isMultiValued(array $options = array()) {
$options = $options ? $options : $this->options;
return empty($options['exposed']) || !empty($options['expose']['multiple']);
}
/**
* {@inheritdoc}
*/
public function admin_summary() {
$value = $this->value;
$this->value = empty($value) ? '' : $this->ids_to_strings($value);
$ret = parent::admin_summary();
$this->value = $value;
return $ret;
}
/**
* {@inheritdoc}
*/
public function query() {
if ($this->operator === 'empty') {
$this->query->condition($this->real_field, NULL, '=', $this->options['group']);
}
elseif ($this->operator === 'not empty') {
$this->query->condition($this->real_field, NULL, '<>', $this->options['group']);
}
elseif (is_array($this->value)) {
$all_of = $this->operator === 'all of';
$operator = $all_of ? '=' : $this->operator;
if (count($this->value) == 1) {
$this->query->condition($this->real_field, reset($this->value), $operator, $this->options['group']);
}
else {
$filter = $this->query->createFilter($operator === '<>' || $all_of ? 'AND' : 'OR');
foreach ($this->value as $value) {
$filter->condition($this->real_field, $value, $operator);
}
$this->query->filter($filter, $this->options['group']);
}
}
}
}

View File

@@ -1,5 +1,10 @@
<?php
/**
* @file
* Contains SearchApiViewsHandlerFilterFulltext.
*/
/**
* Views filter handler class for handling fulltext fields.
*/
@@ -33,6 +38,7 @@ class SearchApiViewsHandlerFilterFulltext extends SearchApiViewsHandlerFilterTex
$options['operator']['default'] = 'AND';
$options['mode'] = array('default' => 'keys');
$options['min_length'] = array('default' => '');
$options['fields'] = array('default' => array());
return $options;
@@ -75,6 +81,55 @@ class SearchApiViewsHandlerFilterFulltext extends SearchApiViewsHandlerFilterTex
if (isset($form['expose'])) {
$form['expose']['#weight'] = -5;
}
$form['min_length'] = array(
'#title' => t('Minimum keyword length'),
'#description' => t('Minimum length of each word in the search keys. Leave empty to allow all words.'),
'#type' => 'textfield',
'#element_validate' => array('element_validate_integer_positive'),
'#default_value' => $this->options['min_length'],
);
}
/**
* {@inheritdoc}
*/
public function exposed_validate(&$form, &$form_state) {
// Only validate exposed input.
if (empty($this->options['exposed']) || empty($this->options['expose']['identifier'])) {
return;
}
// We only need to validate if there is a minimum word length set.
if ($this->options['min_length'] < 2) {
return;
}
$identifier = $this->options['expose']['identifier'];
$input = &$form_state['values'][$identifier];
if ($this->options['is_grouped'] && isset($this->options['group_info']['group_items'][$input])) {
$this->operator = $this->options['group_info']['group_items'][$input]['operator'];
$input = &$this->options['group_info']['group_items'][$input]['value'];
}
// If there is no input, we're fine.
if (!trim($input)) {
return;
}
$words = preg_split('/\s+/', $input);
foreach ($words as $i => $word) {
if (drupal_strlen($word) < $this->options['min_length']) {
unset($words[$i]);
}
}
if (!$words) {
$vars['@count'] = $this->options['min_length'];
$msg = t('You must include at least one positive keyword with @count characters or more.', $vars);
form_error($form[$identifier], $msg);
}
$input = implode(' ', $words);
}
/**
@@ -108,9 +163,9 @@ 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') {
// If the operator was set to OR or NOT, set OR as the conjunction. (It is
// also set for NOT since otherwise it would be "not all of these words".)
if ($this->operator != 'AND') {
$this->query->setOption('conjunction', $this->operator);
}

View File

@@ -41,6 +41,10 @@ class SearchApiViewsHandlerFilterLanguage extends SearchApiViewsHandlerFilterOpt
*/
public function query() {
global $language_content;
if (!is_array($this->value)) {
$this->value = $this->value ? array($this->value) : array();
}
foreach ($this->value as $i => $v) {
if ($v == 'current') {
$this->value[$i] = $language_content->language;

View File

@@ -1,16 +1,82 @@
<?php
/**
* Views filter handler class for handling fields with a limited set of possible
* values.
*
* Definition items:
* - options: An array of possible values for this field.
* @file
* Contains the SearchApiViewsHandlerFilterOptions class.
*/
/**
* Views filter handler for fields with a limited set of possible values.
*/
class SearchApiViewsHandlerFilterOptions extends SearchApiViewsHandlerFilter {
/**
* Stores the values which are available on the form.
*
* @var array
*/
protected $value_options = NULL;
/**
* The type of form element used to display the options.
*
* @var string
*/
protected $value_form_type = 'checkboxes';
/**
* Retrieves a wrapper for this filter's field.
*
* @return EntityMetadataWrapper|null
* A wrapper for the field which this filter uses.
*/
protected function get_wrapper() {
if ($this->query) {
$index = $this->query->getIndex();
}
elseif (substr($this->view->base_table, 0, 17) == 'search_api_index_') {
$index = search_api_index_load(substr($this->view->base_table, 17));
}
else {
return NULL;
}
$wrapper = $index->entityWrapper(NULL, TRUE);
$parts = explode(':', $this->real_field);
foreach ($parts as $i => $part) {
if (!isset($wrapper->$part)) {
return NULL;
}
$wrapper = $wrapper->$part;
$info = $wrapper->info();
if ($i < count($parts) - 1) {
// Unwrap lists.
$level = search_api_list_nesting_level($info['type']);
for ($j = 0; $j < $level; ++$j) {
$wrapper = $wrapper[0];
}
}
}
return $wrapper;
}
/**
* Fills the value_options property with all possible options.
*/
protected function get_value_options() {
if (isset($this->value_options)) {
return;
}
$wrapper = $this->get_wrapper();
if ($wrapper) {
$this->value_options = $wrapper->optionsList('view');
}
else {
$this->value_options = array();
}
}
/**
* Provide a list of options for the operator form.
*/
@@ -63,13 +129,12 @@ class SearchApiViewsHandlerFilterOptions extends SearchApiViewsHandlerFilter {
* Reduce the options according to the selection.
*/
protected function reduce_value_options() {
$options = array();
foreach ($this->definition['options'] as $id => $option) {
if (isset($this->options['value'][$id])) {
$options[$id] = $option;
foreach ($this->value_options as $id => $option) {
if (!isset($this->options['value'][$id])) {
unset($this->value_options[$id]);
}
}
return $options;
return $this->value_options;
}
/**
@@ -92,27 +157,38 @@ class SearchApiViewsHandlerFilterOptions extends SearchApiViewsHandlerFilter {
* Provide a form for setting options.
*/
public function value_form(&$form, &$form_state) {
$options = array();
$this->get_value_options();
if (!empty($this->options['expose']['reduce']) && !empty($form_state['exposed'])) {
$options += $this->reduce_value_options($form_state);
$options = $this->reduce_value_options();
}
else {
$options += $this->definition['options'];
$options = $this->value_options;
}
$form['value'] = array(
'#type' => $this->value_form_type,
'#title' => empty($form_state['exposed']) ? t('Value') : '',
'#options' => $options,
'#multiple' => TRUE,
'#size' => min(4, count($this->definition['options'])),
'#size' => min(4, count($options)),
'#default_value' => is_array($this->value) ? $this->value : array(),
);
// Hide the value box if operator is 'empty' or 'not empty'.
// Hide the value box if the operator is 'empty' or 'not empty'.
// Radios share the same selector so we have to add some dummy selector.
$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'),
);
if (empty($form_state['exposed'])) {
$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'),
);
}
elseif (!empty($this->options['expose']['use_operator'])) {
$name = $this->options['expose']['operator_id'];
$form['value']['#states']['visible'] = array(
':input[name="' . $name . '"],dummy-empty' => array('!value' => 'empty'),
':input[name="' . $name . '"],dummy-not-empty' => array('!value' => 'not empty'),
);
}
}
/**
@@ -139,8 +215,9 @@ class SearchApiViewsHandlerFilterOptions extends SearchApiViewsHandlerFilter {
$values = '';
// Remove every element which is not known.
$this->get_value_options();
foreach ($this->value as $i => $value) {
if (!isset($this->definition['options'][$value])) {
if (!isset($this->value_options[$value])) {
unset($this->value[$i]);
}
}
@@ -161,7 +238,7 @@ class SearchApiViewsHandlerFilterOptions extends SearchApiViewsHandlerFilter {
}
// If there is only a single value, use just the plain operator, = or <>.
$operator = check_plain($operator);
$values = check_plain($this->definition['options'][reset($this->value)]);
$values = check_plain($this->value_options[reset($this->value)]);
}
else {
foreach ($this->value as $value) {
@@ -172,7 +249,7 @@ class SearchApiViewsHandlerFilterOptions extends SearchApiViewsHandlerFilter {
$values .= '…';
break;
}
$values .= check_plain($this->definition['options'][$value]);
$values .= check_plain($this->value_options[$value]);
}
}
@@ -197,28 +274,24 @@ class SearchApiViewsHandlerFilterOptions extends SearchApiViewsHandlerFilter {
$this->value = reset($this->value);
}
// Determine operator and conjunction.
// Determine operator and conjunction. The defaults are already right for
// "all of".
$operator = '=';
$conjunction = 'AND';
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.
// "is none of"), or want to find only items with no value for the field.
if ($this->value === array()) {
if ($this->operator != '<>') {
if ($operator != '<>') {
$this->query->condition($this->real_field, NULL, '=', $this->options['group']);
}
return;

View File

@@ -0,0 +1,294 @@
<?php
/**
* @file
* Contains SearchApiViewsHandlerFilterTaxonomyTerm.
*/
/**
* Views filter handler class for taxonomy term entities.
*
* Based on views_handler_filter_term_node_tid.
*/
class SearchApiViewsHandlerFilterTaxonomyTerm extends SearchApiViewsHandlerFilterEntity {
/**
* {@inheritdoc}
*/
public function has_extra_options() {
return !empty($this->definition['vocabulary']);
}
/**
* {@inheritdoc}
*/
public function option_definition() {
$options = parent::option_definition();
$options['type'] = array('default' => !empty($this->definition['vocabulary']) ? 'textfield' : 'select');
$options['hierarchy'] = array('default' => 0);
$options['error_message'] = array('default' => TRUE, 'bool' => TRUE);
return $options;
}
/**
* {@inheritdoc}
*/
public function extra_options_form(&$form, &$form_state) {
$form['type'] = array(
'#type' => 'radios',
'#title' => t('Selection type'),
'#options' => array('select' => t('Dropdown'), 'textfield' => t('Autocomplete')),
'#default_value' => $this->options['type'],
);
$form['hierarchy'] = array(
'#type' => 'checkbox',
'#title' => t('Show hierarchy in dropdown'),
'#default_value' => !empty($this->options['hierarchy']),
);
$form['hierarchy']['#states']['visible'][':input[name="options[type]"]']['value'] = 'select';
}
/**
* {@inheritdoc}
*/
public function value_form(&$form, &$form_state) {
parent::value_form($form, $form_state);
if (!empty($this->definition['vocabulary'])) {
$vocabulary = taxonomy_vocabulary_machine_name_load($this->definition['vocabulary']);
$title = t('Select terms from vocabulary @voc', array('@voc' => $vocabulary->name));
}
else {
$vocabulary = FALSE;
$title = t('Select terms');
}
$form['value']['#title'] = $title;
if ($vocabulary && $this->options['type'] == 'textfield') {
$form['value']['#autocomplete_path'] = 'admin/views/ajax/autocomplete/taxonomy/' . $vocabulary->vid;
}
else {
if ($vocabulary && !empty($this->options['hierarchy'])) {
$tree = taxonomy_get_tree($vocabulary->vid);
$options = array();
if ($tree) {
foreach ($tree as $term) {
$choice = new stdClass();
$choice->option = array($term->tid => str_repeat('-', $term->depth) . $term->name);
$options[] = $choice;
}
}
}
else {
$options = array();
$query = db_select('taxonomy_term_data', 'td');
$query->innerJoin('taxonomy_vocabulary', 'tv', 'td.vid = tv.vid');
$query->fields('td');
$query->orderby('tv.weight');
$query->orderby('tv.name');
$query->orderby('td.weight');
$query->orderby('td.name');
$query->addTag('term_access');
if ($vocabulary) {
$query->condition('tv.machine_name', $vocabulary->machine_name);
}
$result = $query->execute();
foreach ($result as $term) {
$options[$term->tid] = $term->name;
}
}
$default_value = (array) $this->value;
if (!empty($form_state['exposed'])) {
$identifier = $this->options['expose']['identifier'];
if (!empty($this->options['expose']['reduce'])) {
$options = $this->reduce_value_options($options);
if (!empty($this->options['expose']['multiple']) && empty($this->options['expose']['required'])) {
$default_value = array();
}
}
if (empty($this->options['expose']['multiple'])) {
if (empty($this->options['expose']['required']) && (empty($default_value) || !empty($this->options['expose']['reduce']))) {
$default_value = 'All';
}
elseif (empty($default_value)) {
$keys = array_keys($options);
$default_value = array_shift($keys);
}
// Due to #1464174 there is a chance that array('') was saved in the
// admin ui. Let's choose a safe default value.
elseif ($default_value == array('')) {
$default_value = 'All';
}
else {
$copy = $default_value;
$default_value = array_shift($copy);
}
}
}
$form['value']['#type'] = 'select';
$form['value']['#multiple'] = TRUE;
$form['value']['#options'] = $options;
$form['value']['#size'] = min(9, count($options));
$form['value']['#default_value'] = $default_value;
if (!empty($form_state['exposed']) && isset($identifier) && !isset($form_state['input'][$identifier])) {
$form_state['input'][$identifier] = $default_value;
}
}
}
/**
* Reduces the available exposed options according to the selection.
*/
protected function reduce_value_options(array $options) {
foreach ($options as $id => $option) {
if (empty($this->options['value'][$id])) {
unset($options[$id]);
}
}
return $options;
}
/**
* {@inheritdoc}
*/
public function value_validate($form, &$form_state) {
// We only validate if they've chosen the text field style.
if ($this->options['type'] != 'textfield') {
return;
}
parent::value_validate($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function accept_exposed_input($input) {
if (empty($this->options['exposed'])) {
return TRUE;
}
// If view is an attachment and is inheriting exposed filters, then assume
// exposed input has already been validated.
if (!empty($this->view->is_attachment) && $this->view->display_handler->uses_exposed()) {
$this->validated_exposed_input = (array) $this->view->exposed_raw_input[$this->options['expose']['identifier']];
}
// If it's non-required and there's no value don't bother filtering.
if (!$this->options['expose']['required'] && empty($this->validated_exposed_input)) {
return FALSE;
}
return parent::accept_exposed_input($input);
}
/**
* {@inheritdoc}
*/
public function exposed_validate(&$form, &$form_state) {
if (empty($this->options['exposed']) || empty($this->options['expose']['identifier'])) {
return;
}
// We only validate if they've chosen the text field style.
if ($this->options['type'] != 'textfield') {
$input = $form_state['values'][$this->options['expose']['identifier']];
if ($this->options['is_grouped'] && isset($this->options['group_info']['group_items'][$input])) {
$input = $this->options['group_info']['group_items'][$input]['value'];
}
if ($input != 'All') {
$this->validated_exposed_input = (array) $input;
}
return;
}
parent::exposed_validate($form, $form_state);
}
/**
* {@inheritdoc}
*/
protected function validate_entity_strings(array &$form, array $values) {
if (empty($values)) {
return array();
}
$tids = array();
$names = array();
$missing = array();
foreach ($values as $value) {
$missing[strtolower($value)] = TRUE;
$names[] = $value;
}
if (!$names) {
return FALSE;
}
$query = db_select('taxonomy_term_data', 'td');
$query->innerJoin('taxonomy_vocabulary', 'tv', 'td.vid = tv.vid');
$query->fields('td');
$query->condition('td.name', $names);
if (!empty($this->definition['vocabulary'])) {
$query->condition('tv.machine_name', $this->definition['vocabulary']);
}
$query->addTag('term_access');
$result = $query->execute();
foreach ($result as $term) {
unset($missing[strtolower($term->name)]);
$tids[] = $term->tid;
}
if ($missing) {
if (!empty($this->options['error_message'])) {
form_error($form, format_plural(count($missing), 'Unable to find term: @terms', 'Unable to find terms: @terms', array('@terms' => implode(', ', array_keys($missing)))));
}
else {
// Add a bogus TID which will show an empty result for a positive filter
// and be ignored for an excluding one.
$tids[] = 0;
}
}
return $tids;
}
/**
* {@inheritdoc}
*/
public function expose_form(&$form, &$form_state) {
parent::expose_form($form, $form_state);
if ($this->options['type'] != 'select') {
unset($form['expose']['reduce']);
}
$form['error_message'] = array(
'#type' => 'checkbox',
'#title' => t('Display error message'),
'#description' => t('Display an error message if one of the entered terms could not be found.'),
'#default_value' => !empty($this->options['error_message']),
);
}
/**
* {@inheritdoc}
*/
protected function ids_to_strings(array $ids) {
return implode(', ', db_select('taxonomy_term_data', 'td')
->fields('td', array('name'))
->condition('td.tid', array_filter($ids))
->execute()
->fetchCol());
}
}

View File

@@ -1,5 +1,10 @@
<?php
/**
* @file
* Contains SearchApiViewsHandlerFilterText.
*/
/**
* Views filter handler class for handling fulltext fields.
*/

View File

@@ -0,0 +1,77 @@
<?php
/**
* @file
* Contains SearchApiViewsHandlerFilterUser.
*/
/**
* Views filter handler class for handling user entities.
*
* Based on views_handler_filter_user_name.
*/
class SearchApiViewsHandlerFilterUser extends SearchApiViewsHandlerFilterEntity {
/**
* {@inheritdoc}
*/
public function value_form(&$form, &$form_state) {
parent::value_form($form, $form_state);
// Set autocompletion.
$path = $this->isMultiValued() ? 'admin/views/ajax/autocomplete/user' : 'user/autocomplete';
$form['value']['#autocomplete_path'] = $path;
}
/**
* {@inheritdoc}
*/
protected function ids_to_strings(array $ids) {
$names = array();
$args[':uids'] = array_filter($ids);
$result = db_query("SELECT uid, name FROM {users} u WHERE uid IN (:uids)", $args);
$result = $result->fetchAllKeyed();
foreach ($ids as $uid) {
if (!$uid) {
$names[] = variable_get('anonymous', t('Anonymous'));
}
elseif (isset($result[$uid])) {
$names[] = $result[$uid];
}
}
return implode(', ', $names);
}
/**
* {@inheritdoc}
*/
protected function validate_entity_strings(array &$form, array $values) {
$uids = array();
$missing = array();
foreach ($values as $value) {
if (drupal_strtolower($value) === drupal_strtolower(variable_get('anonymous', t('Anonymous')))) {
$uids[] = 0;
}
else {
$missing[strtolower($value)] = $value;
}
}
if (!$missing) {
return $uids;
}
$result = db_query("SELECT * FROM {users} WHERE name IN (:names)", array(':names' => array_values($missing)));
foreach ($result as $account) {
unset($missing[strtolower($account->name)]);
$uids[] = $account->uid;
}
if ($missing) {
form_error($form, format_plural(count($missing), 'Unable to find user: @users', 'Unable to find users: @users', array('@users' => implode(', ', $missing))));
}
return $uids;
}
}

View File

@@ -1,5 +1,10 @@
<?php
/**
* @file
* Contains SearchApiViewsHandlerSort.
*/
/**
* Class for sorting results according to a specified field.
*/

View File

@@ -98,7 +98,7 @@ class SearchApiViewsCache extends views_plugin_cache_time {
// 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];
$key_data['exposed_info'] = $_GET['exposed_info'];
}
$this->_results_key = $this->view->name . ':' . $this->display->id . ':results:' . md5(serialize($key_data));

View File

@@ -1,5 +1,10 @@
<?php
/**
* @file
* Contains SearchApiViewsQuery.
*/
/**
* Views query class using a Search API index as the data source.
*/
@@ -180,7 +185,6 @@ class SearchApiViewsQuery extends views_plugin_query {
'#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'])) {
@@ -243,16 +247,6 @@ 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);
@@ -262,6 +256,20 @@ class SearchApiViewsQuery extends views_plugin_query {
if (!empty($this->options['search_api_bypass_access'])) {
$this->query->setOption('search_api_bypass_access', TRUE);
}
// If the View and the Panel conspire to provide an overridden path then
// pass that through as the base path.
if (!empty($this->view->override_path) && strpos(current_path(), $this->view->override_path) !== 0) {
$this->query->setOption('search_api_base_path', $this->view->override_path);
}
}
/**
* {@inheritdoc}
*/
public function alter(&$view) {
parent::alter($view);
drupal_alter('search_api_views_query', $view, $this);
}
/**
@@ -284,7 +292,28 @@ class SearchApiViewsQuery extends views_plugin_query {
return;
}
// Calculate the "skip result count" option, if it wasn't already set to
// FALSE.
$skip_result_count = $this->query->getOption('skip result count', TRUE);
if ($skip_result_count) {
$skip_result_count = !$this->pager->use_count_query() && empty($view->get_total_rows);
$this->query->setOption('skip result count', $skip_result_count);
}
try {
// Trigger pager pre_execute().
$this->pager->pre_execute($this->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);
$start = microtime(TRUE);
// Execute the search.
@@ -292,11 +321,13 @@ class SearchApiViewsQuery extends views_plugin_query {
$this->search_api_results = $results;
// Store the results.
$this->pager->total_items = $view->total_rows = $results['result count'];
if (!empty($this->pager->options['offset'])) {
$this->pager->total_items -= $this->pager->options['offset'];
if (!$skip_result_count) {
$this->pager->total_items = $view->total_rows = $results['result count'];
if (!empty($this->pager->options['offset'])) {
$this->pager->total_items -= $this->pager->options['offset'];
}
$this->pager->update_page_info();
}
$this->pager->update_page_info();
$view->result = array();
if (!empty($results['results'])) {
$this->addResults($results['results'], $view);
@@ -304,6 +335,9 @@ class SearchApiViewsQuery extends views_plugin_query {
// We shouldn't use $results['performance']['complete'] here, since
// extracting the results probably takes considerable time as well.
$view->execute_time = microtime(TRUE) - $start;
// Trigger pager post_execute().
$this->pager->post_execute($view->result);
}
catch (Exception $e) {
$this->errors[] = $e->getMessage();
@@ -317,8 +351,14 @@ class SearchApiViewsQuery extends views_plugin_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.
*
* @param string|null $msg
* Optionally, a translated, unescaped error message to display.
*/
public function abort() {
public function abort($msg = NULL) {
if ($msg) {
$this->errors[] = $msg;
}
$this->abort = TRUE;
}
@@ -520,9 +560,9 @@ class SearchApiViewsQuery extends views_plugin_query {
// Query interface methods (proxy to $this->query)
//
public function createFilter($conjunction = 'AND') {
public function createFilter($conjunction = 'AND', $tags = array()) {
if (!$this->errors) {
return $this->query->createFilter($conjunction);
return $this->query->createFilter($conjunction, $tags);
}
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* @file
* Hooks provided by the Search Views module.
*/
/**
* Alter the query before executing the query.
*
* @param view $view
* The view object about to be processed.
* @param SearchApiViewsQuery $query
* The Search API Views query to be altered.
*
* @see hook_views_query_alter()
*/
function hook_search_api_views_query_alter(view &$view, SearchApiViewsQuery &$query) {
// (Example assuming a view with an exposed filter on node title.)
// If the input for the title filter is a positive integer, filter against
// node ID instead of node title.
if ($view->name == 'my_view' && is_numeric($view->exposed_raw_input['title']) && $view->exposed_raw_input['title'] > 0) {
// Traverse through the 'where' part of the query.
foreach ($query->where as &$condition_group) {
foreach ($condition_group['conditions'] as &$condition) {
// If this is the part of the query filtering on title, chang the
// condition to filter on node ID.
if (reset($condition) == 'node.title') {
$condition = array('node.nid', $view->exposed_raw_input['title'],'=');
}
}
}
}
}

View File

@@ -1,4 +1,3 @@
name = Search views
description = Integrates the Search API with Views, enabling users to create views with searches as filters or arguments.
dependencies[] = search_api
@@ -12,21 +11,25 @@ files[] = includes/handler_argument.inc
files[] = includes/handler_argument_fulltext.inc
files[] = includes/handler_argument_more_like_this.inc
files[] = includes/handler_argument_string.inc
files[] = includes/handler_argument_date.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
files[] = includes/handler_filter_entity.inc
files[] = includes/handler_filter_fulltext.inc
files[] = includes/handler_filter_language.inc
files[] = includes/handler_filter_options.inc
files[] = includes/handler_filter_taxonomy_term.inc
files[] = includes/handler_filter_text.inc
files[] = includes/handler_filter_user.inc
files[] = includes/handler_sort.inc
files[] = includes/plugin_cache.inc
files[] = includes/query.inc
; Information added by drupal.org packaging script on 2013-09-01
version = "7.x-1.8"
; Information added by Drupal.org packaging script on 2013-12-25
version = "7.x-1.11"
core = "7.x"
project = "search_api"
datestamp = "1378025826"
datestamp = "1387965506"

View File

@@ -1,4 +1,5 @@
<?php
/**
* @file
* Install, update and uninstall functions for the search_api_views module.
@@ -24,7 +25,7 @@ function search_api_views_update_7101() {
if (!$table_fields) {
return;
}
foreach (views_get_all_views() as $name => $view) {
foreach (views_get_all_views() as $view) {
if (empty($view->base_table) || empty($table_fields[$view->base_table])) {
continue;
}
@@ -32,7 +33,7 @@ function search_api_views_update_7101() {
$fields = $table_fields[$view->base_table];
$change |= _search_api_views_update_7101_helper($view->base_field, $fields);
if (!empty($view->display)) {
foreach ($view->display as $key => &$display) {
foreach ($view->display as &$display) {
$options = &$display->display_options;
if (isset($options['style_options']['grouping'])) {
$change |= _search_api_views_update_7101_helper($options['style_options']['grouping'], $fields);
@@ -66,8 +67,15 @@ function search_api_views_update_7101() {
/**
* Helper function for replacing field identifiers.
*
* @return
* TRUE iff the identifier was changed.
* @param $field
* Some data to be searched for field names that should be altered. Passed by
* reference.
* @param array $fields
* An array mapping Search API field identifiers (as previously used by Views)
* to the new, sanitized Views field identifiers.
*
* @return bool
* TRUE if any data was changed, FALSE otherwise.
*/
function _search_api_views_update_7101_helper(&$field, array $fields) {
if (is_array($field)) {

View File

@@ -1,5 +1,10 @@
<?php
/**
* @file
* Integrates the Search API with Views.
*/
/**
* Implements hook_views_api().
*/
@@ -12,7 +17,7 @@ function search_api_views_views_api() {
/**
* Implements hook_search_api_index_insert().
*/
function search_api_views_search_api_index_insert(SearchApiIndex $index) {
function search_api_views_search_api_index_insert() {
// Make the new index available for views.
views_invalidate_cache();
}

View File

@@ -1,5 +1,10 @@
<?php
/**
* @file
* Views hook implementations for the Search API Views module.
*/
/**
* Implements hook_views_data().
*/
@@ -28,7 +33,7 @@ function search_api_views_views_data() {
}
try {
$wrapper = $index->entityWrapper(NULL, TRUE);
$wrapper = $index->entityWrapper(NULL, FALSE);
}
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);
@@ -43,6 +48,14 @@ function search_api_views_views_data() {
}
}
try {
$wrapper = $index->entityWrapper(NULL);
}
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 handlers for all indexed fields.
foreach ($index->getFields() as $key => $field) {
$tmp = $wrapper;
@@ -69,7 +82,7 @@ function search_api_views_views_data() {
if ($group) {
// @todo Entity type label instead of $group?
$table[$id]['group'] = $group;
$name = t('@field (indexed)', array('@field' => $name));
$name = t('!field (indexed)', array('!field' => $name));
}
$table[$id]['title'] = $name;
$table[$id]['help'] = empty($info['description']) ? t('(No information available)') : $info['description'];
@@ -145,8 +158,18 @@ function search_api_views_views_data() {
}
/**
* Helper function that returns an array of handler definitions to add to a
* views field definition.
* Adds handler definitions for a field to a Views data table definition.
*
* Helper method for search_api_views_views_data().
*
* @param $id
* The internal identifier of the field.
* @param array $field
* Information about the field.
* @param EntityMetadataWrapper $wrapper
* A wrapper providing further metadata about the field.
* @param array $table
* The existing Views data table definition, as a reference.
*/
function _search_api_views_add_handlers($id, array $field, EntityMetadataWrapper $wrapper, array &$table) {
$type = $field['type'];
@@ -170,9 +193,9 @@ function _search_api_views_add_handlers($id, array $field, EntityMetadataWrapper
return;
}
if ($options = $wrapper->optionsList('view')) {
$info = $wrapper->info();
if (isset($info['options list']) && is_callable($info['options list'])) {
$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') {
@@ -181,6 +204,27 @@ function _search_api_views_add_handlers($id, array $field, EntityMetadataWrapper
elseif ($inner_type == 'date') {
$table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilterDate';
}
elseif (isset($field['entity_type']) && $field['entity_type'] === 'user') {
$table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilterUser';
}
elseif (isset($field['entity_type']) && $field['entity_type'] === 'taxonomy_term') {
$table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilterTaxonomyTerm';
$info = $wrapper->info();
$field_info = field_info_field($info['name']);
// For the "Parent terms" and "All parent terms" properties, we can
// extrapolate the vocabulary from the parent in the selector. (E.g.,
// for "field_tags:parent" we can use the information of "field_tags".)
// Otherwise, we can't include any vocabulary information.
if (!$field_info && ($info['name'] == 'parent' || $info['name'] == 'parents_all')) {
if (!empty($table[$id]['real field'])) {
$parts = explode(':', $table[$id]['real field']);
$field_info = field_info_field($parts[count($parts) - 2]);
}
}
if (isset($field_info['settings']['allowed_values'][0]['vocabulary'])) {
$table[$id]['filter']['vocabulary'] = $field_info['settings']['allowed_values'][0]['vocabulary'];
}
}
else {
$table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilter';
}
@@ -188,6 +232,9 @@ function _search_api_views_add_handlers($id, array $field, EntityMetadataWrapper
if ($inner_type == 'string' || $inner_type == 'uri') {
$table[$id]['argument']['handler'] = 'SearchApiViewsHandlerArgumentString';
}
elseif ($inner_type == 'date') {
$table[$id]['argument']['handler'] = 'SearchApiViewsHandlerArgumentDate';
}
else {
$table[$id]['argument']['handler'] = 'SearchApiViewsHandlerArgument';
}