updated search_api, search_api_solr_override, imce
This commit is contained in:
parent
dc39ddbbea
commit
2e69e3fd4c
@ -4,9 +4,9 @@ core = "7.x"
|
||||
package = "Media"
|
||||
configure = "admin/config/media/imce"
|
||||
|
||||
; Information added by Drupal.org packaging script on 2016-03-30
|
||||
version = "7.x-1.10"
|
||||
; Information added by Drupal.org packaging script on 2017-05-27
|
||||
version = "7.x-1.11"
|
||||
core = "7.x"
|
||||
project = "imce"
|
||||
datestamp = "1459346870"
|
||||
datestamp = "1495890787"
|
||||
|
||||
|
@ -25,6 +25,7 @@ function imce_uninstall() {
|
||||
variable_del('imce_settings_replace');
|
||||
variable_del('imce_settings_thumb_method');
|
||||
variable_del('imce_settings_disable_private');
|
||||
variable_del('imce_settings_admin_theme');
|
||||
variable_del('imce_custom_content');
|
||||
variable_del('imce_custom_process');
|
||||
variable_del('imce_custom_init');
|
||||
|
@ -46,6 +46,20 @@ function imce_menu() {
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_admin_paths().
|
||||
*/
|
||||
function imce_admin_paths() {
|
||||
if (variable_get('imce_settings_admin_theme', FALSE)) {
|
||||
return array(
|
||||
'imce' => TRUE,
|
||||
'imce/*' => TRUE,
|
||||
'file/imce/*' => TRUE,
|
||||
'imce-filefield/*' => TRUE,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_permission().
|
||||
*/
|
||||
|
@ -109,6 +109,12 @@ function imce_admin_form($form, &$form_state) {
|
||||
'#default_value' => variable_get('imce_settings_disable_private', 1),
|
||||
'#description' => t('IMCE serves all files under private files directory without applying any access restrictions. This allows anonymous access to any file(/system/files/filename) unless there is a module restricting access to the files. Here you can disable this feature.'),
|
||||
);
|
||||
$form['common']['admin_theme'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Use admin theme for IMCE paths'),
|
||||
'#default_value' => variable_get('imce_settings_admin_theme', FALSE),
|
||||
'#description' => t('If you have user interface issues with the active theme you may consider switching to admin theme.'),
|
||||
);
|
||||
|
||||
$form['submit'] = array('#type' => 'submit', '#value' => t('Save configuration'));
|
||||
$form['#theme'] = 'imce_admin';
|
||||
@ -183,6 +189,7 @@ function imce_admin_submit($form, &$form_state) {
|
||||
variable_set('imce_settings_replace', $form_state['values']['replace']);
|
||||
variable_set('imce_settings_thumb_method', $form_state['values']['thumb_method']);
|
||||
variable_set('imce_settings_disable_private', $form_state['values']['disable_private']);
|
||||
variable_set('imce_settings_admin_theme', $form_state['values']['admin_theme']);
|
||||
drupal_set_message(t('Changes have been saved.'));
|
||||
}
|
||||
|
||||
|
@ -801,12 +801,17 @@ updateUI: function() {
|
||||
if (furl.charAt(furl.length - 1) != '/') {
|
||||
furl = imce.conf.furl = furl + '/';
|
||||
}
|
||||
imce.conf.modfix = imce.conf.clean && furl.indexOf(host + '/system/') > -1;
|
||||
imce.conf.modfix = imce.conf.clean && furl.split('/')[3] === 'system';
|
||||
if (absurls && !isabs) {
|
||||
imce.conf.furl = baseurl + furl;
|
||||
}
|
||||
else if (!absurls && isabs && furl.indexOf(baseurl) == 0) {
|
||||
imce.conf.furl = furl.substr(baseurl.length);
|
||||
furl = furl.substr(baseurl.length);
|
||||
// Server base url is defined with a port which is missing in current page url.
|
||||
if (furl.charAt(0) === ':') {
|
||||
furl = furl.replace(/^:\d*/, '');
|
||||
}
|
||||
imce.conf.furl = furl;
|
||||
}
|
||||
//convert button elements to input elements.
|
||||
imce.convertButtons(imce.FW);
|
||||
|
@ -1,3 +1,83 @@
|
||||
Search API 1.26 (2019-03-11):
|
||||
-----------------------------
|
||||
- #2324023 by drumm, drunken monkey: Changed Views field definition for to
|
||||
float.
|
||||
- #3008849 by pamatt, drunken monkey: Fixed non-exposed numeric and date
|
||||
filters in Views.
|
||||
- #3009744 by evgeny.chernyavskiy, drunken monkey: Fixed wrong "continue" in
|
||||
search_api_server_tasks_check().
|
||||
- #3003742 by Jelle_S, drunken monkey: Fixed problems with Views date filters.
|
||||
- #3002043 by alonaoneill, drunken monkey: Fixed module name capitalization and
|
||||
dependency namespacing in .info files.
|
||||
- #2990940 by drunken monkey: Fixed multi-byte handling of Highlight processor.
|
||||
- #3001424 by drunken monkey: Fixed notice when configuring the More Like This
|
||||
contextual filter.
|
||||
|
||||
Search API 1.25 (2018-09-17):
|
||||
-----------------------------
|
||||
- #2408727 by swim, drunken monkey: Added a batch operation for executing
|
||||
pending tasks.
|
||||
- #2325917 by guillaumev, drunken monkey: Added a Views cache plugin based on
|
||||
Views Content Cache.
|
||||
- #2989578 by KarlShea, drunken monkey: Fixed Views exposed form fields for
|
||||
"not between" operator.
|
||||
- #2982167 by osopolar, drunken monkey: Added a Drush command for re-indexing
|
||||
specific entities.
|
||||
- #1783746 by das-peter, sammys, SpadXIII, drunken monkey, ruloweb, KarlShea,
|
||||
heshanlk, Anas_maw, pinkonomy, Damien Tournoud, rudiedirkx: Added support
|
||||
for the "(not) between" operator.
|
||||
- #2408727 by drunken monkey, OliverColeman: Fixed out-of-memory errors when
|
||||
executing pending tasks.
|
||||
- Issue #2948820 by capysara, drunken monkey: Added a link to the "need to
|
||||
reindex" message on the Filters tab.
|
||||
- #2828883 by JorgenSandstrom, drunken monkey: Fixed property type for
|
||||
string-typed aggregated fields.
|
||||
- #2949899 by drunken monkey, DamienMcKenna: Added a warning against using
|
||||
particular processors with Solr servers to the "Workflow" tab.
|
||||
|
||||
Search API 1.24 (2018-04-05):
|
||||
-----------------------------
|
||||
- #2958201 by jcnventura, drunken monkey: Reverted issue #2566529: Added
|
||||
support for the "Content access" processor for "Multiple types" indexes.
|
||||
|
||||
Search API 1.23 (2018-03-31):
|
||||
-----------------------------
|
||||
- #2949562 by DamienMcKenna, drunken monkey: Fixed stemming of multi-word
|
||||
tokens.
|
||||
- #1903004 by AndyF, joseph.olstad, drunken monkey: Fixed errors at feature
|
||||
module installation in certain edge cases.
|
||||
- #2889989 by kevineinarsson, drunken monkey, kristofferwiklund: Fixed
|
||||
highlighting for text with multi-byte characters.
|
||||
- #1393064 by xlyz, drunken monkey, jannis: Fixed handling of empty facet
|
||||
filters.
|
||||
- #2927692 by drunken monkey, Kristi Wachter: Fixed exposed grouped Views
|
||||
options filters.
|
||||
- #2928769 by jannis, drunken monkey: Fixed Views cache not being cleared when
|
||||
enabling indexes.
|
||||
- #2566529 by Dylan Donkersgoed, drunken monkey, joachim, swirt: Added support
|
||||
for the "Content access" processor for "Multiple types" indexes.
|
||||
- #2905445 by ciss, drunken monkey: Fixed error handling in Views term filter
|
||||
handler.
|
||||
- #2904268 by pobster, drunken monkey: Added support for language hierarchy in
|
||||
Views.
|
||||
|
||||
Search API 1.22 (2017-07-18):
|
||||
-----------------------------
|
||||
- #1710212 by drunken monkey: Added a data alteration for indexing a user's
|
||||
content.
|
||||
- #2879892 by blacklabel_tom, drunken monkey: Fixed link in description of
|
||||
"Stemmer" processor.
|
||||
- #2788593 by drunken monkey: Fixed error in Views query settings for specific
|
||||
setups.
|
||||
- #2749963 by drunken monkey: Fixed "Index hierarchy" not having values
|
||||
numerically indexed.
|
||||
- #2875793 by drunken monkey: Fixed buggy error handling in Views.
|
||||
- #2860624 by drunken monkey: Fixed problem with empty words in Views fulltext
|
||||
filter.
|
||||
- #2855447 by mparker17, drunken monkey: Added "Separator" option for
|
||||
aggregated fields of type "Fulltext".
|
||||
- #2863445 by dbjpanda, drunken monkey: Fixed phrasing in README.txt.
|
||||
|
||||
Search API 1.21 (2017-02-23):
|
||||
-----------------------------
|
||||
- #2780341 by Berdir: Fixed passing of custom ranges to date facets.
|
||||
|
@ -31,9 +31,9 @@ Terms as used in this module.
|
||||
Sphinx or any other professional or simple indexing mechanism. Takes care of
|
||||
the details of all operations, especially indexing or searching content.
|
||||
- Server:
|
||||
One specific place for indexing data, using a set service class. Can
|
||||
e.g. be some tables in a database, a connection to a Solr server or other
|
||||
external services, etc.
|
||||
One specific place for indexing data, using a specific service class. For
|
||||
example this could be some tables in a database, a connection to a Solr server
|
||||
or other external services, etc.
|
||||
- Index:
|
||||
A configuration object for indexing data of a specific type. What and how data
|
||||
is indexed is determined by its settings. Also keeps track of which items
|
||||
|
@ -115,7 +115,7 @@ class SearchApiFacetapiTerm extends FacetapiQueryType implements FacetapiQueryTy
|
||||
if ($filter == '!') {
|
||||
$query_filter->condition($field, NULL, $exclude ? '<>' : '=');
|
||||
}
|
||||
elseif ($filter[0] == '[' && $filter[strlen($filter) - 1] == ']' && ($pos = strpos($filter, ' TO '))) {
|
||||
elseif ($filter && $filter[0] == '[' && $filter[strlen($filter) - 1] == ']' && ($pos = strpos($filter, ' TO '))) {
|
||||
$lower = trim(substr($filter, 1, $pos));
|
||||
$upper = trim(substr($filter, $pos + 4, -1));
|
||||
if ($lower == '*' && $upper == '*') {
|
||||
|
@ -1,7 +1,7 @@
|
||||
name = Search facets
|
||||
name = Search Facets
|
||||
description = "Integrate the Search API with the Facet API to provide facetted searches."
|
||||
dependencies[] = search_api
|
||||
dependencies[] = facetapi
|
||||
dependencies[] = search_api:search_api
|
||||
dependencies[] = facetapi:facetapi
|
||||
core = 7.x
|
||||
package = Search
|
||||
|
||||
@ -9,9 +9,8 @@ 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 2017-02-23
|
||||
version = "7.x-1.21"
|
||||
; Information added by Drupal.org packaging script on 2019-03-11
|
||||
version = "7.x-1.26"
|
||||
core = "7.x"
|
||||
project = "search_api"
|
||||
datestamp = "1487844493"
|
||||
|
||||
datestamp = "1552334832"
|
||||
|
@ -40,6 +40,21 @@ in that position. If the query is sorted in this way, then the
|
||||
random sort, as an associative array with any of the following keys:
|
||||
- seed: A numeric seed value to use for the random sort.
|
||||
|
||||
"BETWEEN operator" feature
|
||||
--------------------------
|
||||
This module defines the "BETWEEN operator" feature (feature key:
|
||||
"search_api_between") that adds the "BETWEEN" and "NOT BETWEEN" filter
|
||||
operators to search queries. If your search server supports this feature, you
|
||||
can use the "Is between" and "Is not between" operators when adding Views
|
||||
filters for numeric, string or date types.
|
||||
|
||||
For developers:
|
||||
A service class that wants to support this feature has to accept "BETWEEN" and
|
||||
"NOT BETWEEN" as additional $operator values in query conditions. The value in
|
||||
both cases is an array with the keys 0 and 1, with the value under key 0 being
|
||||
the lower and the value under key 1 being the upper bound for the range in which
|
||||
the field's value should ("BETWEEN") or should not ("NOT BETWEEN") be.
|
||||
|
||||
"Facets block" display
|
||||
----------------------
|
||||
Most features should be clear to users of Views. However, the module also
|
||||
|
@ -79,8 +79,8 @@ class SearchApiViewsHandlerArgument extends views_handler_argument {
|
||||
public function option_definition() {
|
||||
$options = parent::option_definition();
|
||||
|
||||
$options['break_phrase'] = array('default' => FALSE);
|
||||
$options['not'] = array('default' => FALSE);
|
||||
$options['break_phrase'] = array('default' => FALSE, 'bool' => TRUE);
|
||||
$options['not'] = array('default' => FALSE, 'bool' => TRUE);
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
@ -16,8 +16,6 @@ class SearchApiViewsHandlerArgumentMoreLikeThis extends SearchApiViewsHandlerArg
|
||||
*/
|
||||
public function option_definition() {
|
||||
$options = parent::option_definition();
|
||||
unset($options['break_phrase']);
|
||||
unset($options['not']);
|
||||
$options['entity_type'] = array('default' => FALSE);
|
||||
$options['fields'] = array('default' => array());
|
||||
return $options;
|
||||
|
@ -6,9 +6,9 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Views filter handler base class for handling all "normal" cases.
|
||||
* Views filter handler base class for handling date fields.
|
||||
*/
|
||||
class SearchApiViewsHandlerFilterDate extends SearchApiViewsHandlerFilter {
|
||||
class SearchApiViewsHandlerFilterDate extends SearchApiViewsHandlerFilterNumeric {
|
||||
|
||||
/**
|
||||
* Add a "widget type" option.
|
||||
@ -88,9 +88,22 @@ class SearchApiViewsHandlerFilterDate extends SearchApiViewsHandlerFilter {
|
||||
public function value_form(&$form, &$form_state) {
|
||||
parent::value_form($form, $form_state);
|
||||
|
||||
$is_date_popup = ($this->options['widget_type'] == 'date_popup' && module_exists('date_popup'));
|
||||
|
||||
// If the operator is between
|
||||
if ($this->operator == 'between') {
|
||||
if ($is_date_popup) {
|
||||
$form['value']['min']['#type'] = 'date_popup';
|
||||
$form['value']['min']['#date_format'] = $this->options['date_popup_format'];
|
||||
$form['value']['min']['#date_year_range'] = $this->options['year_range'];
|
||||
$form['value']['max']['#type'] = 'date_popup';
|
||||
$form['value']['max']['#date_format'] = $this->options['date_popup_format'];
|
||||
$form['value']['max']['#date_year_range'] = $this->options['year_range'];
|
||||
}
|
||||
}
|
||||
// If we are using the date popup widget, overwrite the settings of the form
|
||||
// according to what date_popup expects.
|
||||
if ($this->options['widget_type'] == 'date_popup' && module_exists('date_popup')) {
|
||||
elseif ($is_date_popup) {
|
||||
$form['value']['#type'] = 'date_popup';
|
||||
$form['value']['#date_format'] = $this->options['date_popup_format'];
|
||||
$form['value']['#date_year_range'] = $this->options['year_range'];
|
||||
@ -109,17 +122,38 @@ class SearchApiViewsHandlerFilterDate extends SearchApiViewsHandlerFilter {
|
||||
* Add this filter to the query.
|
||||
*/
|
||||
public function query() {
|
||||
$this->normalizeValue();
|
||||
|
||||
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']);
|
||||
}
|
||||
else {
|
||||
while (is_array($this->value)) {
|
||||
$this->value = $this->value ? reset($this->value) : NULL;
|
||||
elseif (in_array($this->operator, array('between', 'not between'), TRUE)) {
|
||||
$min = $this->value['min'];
|
||||
if ($min !== '') {
|
||||
$min = is_numeric($min) ? $min : strtotime($min, REQUEST_TIME);
|
||||
}
|
||||
$v = is_numeric($this->value) ? $this->value : strtotime($this->value, REQUEST_TIME);
|
||||
$max = $this->value['max'];
|
||||
if ($max !== '') {
|
||||
$max = is_numeric($max) ? $max : strtotime($max, REQUEST_TIME);
|
||||
}
|
||||
|
||||
if (is_numeric($min) && is_numeric($max)) {
|
||||
$this->query->condition($this->real_field, array($min, $max), strtoupper($this->operator), $this->options['group']);
|
||||
}
|
||||
elseif (is_numeric($min)) {
|
||||
$operator = $this->operator === 'between' ? '>=' : '<';
|
||||
$this->query->condition($this->real_field, $min, $operator, $this->options['group']);
|
||||
}
|
||||
elseif (is_numeric($max)) {
|
||||
$operator = $this->operator === 'between' ? '<=' : '>';
|
||||
$this->query->condition($this->real_field, $min, $operator, $this->options['group']);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$v = is_numeric($this->value['value']) ? $this->value['value'] : strtotime($this->value['value'], REQUEST_TIME);
|
||||
if ($v !== FALSE) {
|
||||
$this->query->condition($this->real_field, $v, $this->operator, $this->options['group']);
|
||||
}
|
||||
|
@ -121,6 +121,11 @@ class SearchApiViewsHandlerFilterFulltext extends SearchApiViewsHandlerFilterTex
|
||||
$words = preg_split('/\s+/', $input);
|
||||
$quoted = FALSE;
|
||||
foreach ($words as $i => $word) {
|
||||
$word_length = drupal_strlen($word);
|
||||
if (!$word_length) {
|
||||
unset($words[$i]);
|
||||
continue;
|
||||
}
|
||||
// Protect quoted strings.
|
||||
if ($quoted && $word[strlen($word) - 1] === '"') {
|
||||
$quoted = FALSE;
|
||||
@ -130,7 +135,7 @@ class SearchApiViewsHandlerFilterFulltext extends SearchApiViewsHandlerFilterTex
|
||||
$quoted = TRUE;
|
||||
continue;
|
||||
}
|
||||
if (drupal_strlen($word) < $this->options['min_length']) {
|
||||
if ($word_length < $this->options['min_length']) {
|
||||
unset($words[$i]);
|
||||
}
|
||||
}
|
||||
|
@ -18,10 +18,13 @@ class SearchApiViewsHandlerFilterLanguage extends SearchApiViewsHandlerFilterOpt
|
||||
*/
|
||||
protected function get_value_options() {
|
||||
parent::get_value_options();
|
||||
$this->value_options = array(
|
||||
'current' => t("Current user's language"),
|
||||
'default' => t('Default site language'),
|
||||
) + $this->value_options;
|
||||
$options = array();
|
||||
if (module_exists('language_hierarchy')) {
|
||||
$options['fallback'] = t("Current user's language with fallback");
|
||||
}
|
||||
$options['current'] = t("Current user's language");
|
||||
$options['default'] = t('Default site language');
|
||||
$this->value_options = $options + $this->value_options;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -40,6 +43,11 @@ class SearchApiViewsHandlerFilterLanguage extends SearchApiViewsHandlerFilterOpt
|
||||
elseif ($v == 'default') {
|
||||
$this->value[$i] = language_default('language');
|
||||
}
|
||||
elseif ($v == 'fallback' && module_exists('language_hierarchy')) {
|
||||
$fallbacks = array($language_content->language => $language_content->language);
|
||||
$fallbacks += array_keys(language_hierarchy_get_ancestors($language_content->language));
|
||||
$this->value[$i] = drupal_map_assoc($fallbacks);
|
||||
}
|
||||
}
|
||||
parent::query();
|
||||
}
|
||||
|
@ -0,0 +1,248 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiViewsHandlerFilterNumeric.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Views filter handler class for handling numeric and "string" fields.
|
||||
*/
|
||||
class SearchApiViewsHandlerFilterNumeric extends SearchApiViewsHandlerFilter {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function init(&$view, &$options) {
|
||||
parent::init($view, $options);
|
||||
|
||||
$this->normalizeValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function option_definition() {
|
||||
$options = parent::option_definition();
|
||||
$options['value'] = array(
|
||||
'contains' => array(
|
||||
'value' => array('default' => ''),
|
||||
'min' => array('default' => ''),
|
||||
'max' => array('default' => ''),
|
||||
),
|
||||
);
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function operator_options() {
|
||||
$operators = parent::operator_options();
|
||||
|
||||
$index = search_api_index_load(substr($this->table, 17));
|
||||
$server = NULL;
|
||||
try {
|
||||
if ($index) {
|
||||
$server = $index->server();
|
||||
}
|
||||
}
|
||||
catch (SearchApiException $e) {
|
||||
// Ignore.
|
||||
}
|
||||
if ($server && $server->supportsFeature('search_api_between')) {
|
||||
$operators += array(
|
||||
'between' => t('Is between'),
|
||||
'not between' => t('Is not between'),
|
||||
);
|
||||
}
|
||||
|
||||
return $operators;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a form for setting the filter value.
|
||||
*
|
||||
* Heavily borrowed from views_handler_filter_numeric.
|
||||
*
|
||||
* @see views_handler_filter_numeric::value_form()
|
||||
*/
|
||||
public function value_form(&$form, &$form_state) {
|
||||
$form['value']['#tree'] = TRUE;
|
||||
|
||||
$single_field_operators = $this->operator_options();
|
||||
unset(
|
||||
$single_field_operators['empty'],
|
||||
$single_field_operators['not empty'],
|
||||
$single_field_operators['between'],
|
||||
$single_field_operators['not between']
|
||||
);
|
||||
$between_operators = array('between', 'not between');
|
||||
|
||||
// We have to make some choices when creating this as an exposed
|
||||
// filter form. For example, if the operator is locked and thus
|
||||
// not rendered, we can't render dependencies; instead we only
|
||||
// render the form items we need.
|
||||
$which = 'all';
|
||||
$source = NULL;
|
||||
if (!empty($form['operator'])) {
|
||||
$source = ($form['operator']['#type'] == 'radios') ? 'radio:options[operator]' : 'edit-options-operator';
|
||||
}
|
||||
|
||||
$identifier = NULL;
|
||||
if (!empty($form_state['exposed'])) {
|
||||
$identifier = $this->options['expose']['identifier'];
|
||||
if (empty($this->options['expose']['use_operator']) || empty($this->options['expose']['operator_id'])) {
|
||||
// Exposed and locked.
|
||||
$which = in_array($this->operator, $between_operators) ? 'minmax' : 'value';
|
||||
}
|
||||
else {
|
||||
$source = 'edit-' . drupal_html_id($this->options['expose']['operator_id']);
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
if ($which == 'all') {
|
||||
$form['value']['value'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => empty($form_state['exposed']) ? t('Value') : '',
|
||||
'#size' => 30,
|
||||
'#default_value' => $this->value['value'],
|
||||
'#dependency' => array($source => array_keys($single_field_operators)),
|
||||
);
|
||||
if ($identifier && !isset($form_state['input'][$identifier]['value'])) {
|
||||
$form_state['input'][$identifier]['value'] = $this->value['value'];
|
||||
}
|
||||
}
|
||||
elseif ($which == 'value') {
|
||||
// When exposed we drop the value-value and just do value if
|
||||
// the operator is locked.
|
||||
$form['value'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => empty($form_state['exposed']) ? t('Value') : '',
|
||||
'#size' => 30,
|
||||
'#default_value' => isset($this->value['value']) ? $this->value['value'] : '',
|
||||
);
|
||||
if ($identifier && !isset($form_state['input'][$identifier])) {
|
||||
$form_state['input'][$identifier] = isset($this->value['value']) ? $this->value['value'] : '';
|
||||
}
|
||||
}
|
||||
|
||||
if ($which == 'all' || $which == 'minmax') {
|
||||
$form['value']['min'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => empty($form_state['exposed']) ? t('Min') : '',
|
||||
'#size' => 30,
|
||||
'#default_value' => $this->value['min'],
|
||||
);
|
||||
$form['value']['max'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => empty($form_state['exposed']) ? t('And max') : t('And'),
|
||||
'#size' => 30,
|
||||
'#default_value' => $this->value['max'],
|
||||
);
|
||||
|
||||
if ($which == 'all') {
|
||||
$form['value']['min']['#dependency'] = array($source => $between_operators);
|
||||
$form['value']['max']['#dependency'] = array($source => $between_operators);
|
||||
}
|
||||
|
||||
if (!empty($form_state['exposed']) && !isset($form_state['input'][$identifier]['min'])) {
|
||||
$form_state['input'][$identifier]['min'] = $this->value['min'];
|
||||
}
|
||||
if (!empty($form_state['exposed']) && !isset($form_state['input'][$identifier]['max'])) {
|
||||
$form_state['input'][$identifier]['max'] = $this->value['max'];
|
||||
}
|
||||
|
||||
if (!isset($form['value']['value'])) {
|
||||
// Ensure there is something in the 'value'.
|
||||
$form['value']['value'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => NULL,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function admin_summary() {
|
||||
if (!empty($this->options['exposed'])) {
|
||||
return t('exposed');
|
||||
}
|
||||
|
||||
if ($this->operator === 'empty') {
|
||||
return t('is empty');
|
||||
}
|
||||
if ($this->operator === 'not empty') {
|
||||
return t('is not empty');
|
||||
}
|
||||
|
||||
if (in_array($this->operator, array('between', 'not between'), TRUE)) {
|
||||
// This is of course wrong for translation purposes, but copied from
|
||||
// views_handler_filter_numeric::admin_summary() so probably still better
|
||||
// to re-use this than to do it correctly.
|
||||
$operator = $this->operator === 'between' ? t('between') : t('not between');
|
||||
$vars = array(
|
||||
'@min' => (string) $this->value['min'],
|
||||
'@max' => (string) $this->value['max'],
|
||||
);
|
||||
return $operator . ' ' . t('@min and @max', $vars);
|
||||
}
|
||||
|
||||
return check_plain((string) $this->operator) . ' ' . check_plain((string) $this->value['value']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
$this->normalizeValue();
|
||||
|
||||
if (in_array($this->operator, array('between', 'not between'), TRUE)) {
|
||||
$min = $this->value['min'];
|
||||
$max = $this->value['max'];
|
||||
if ($min !== '' && $max !== '') {
|
||||
$this->query->condition($this->real_field, array($min, $max), strtoupper($this->operator), $this->options['group']);
|
||||
}
|
||||
elseif ($min !== '') {
|
||||
$operator = $this->operator === 'between' ? '>=' : '<';
|
||||
$this->query->condition($this->real_field, $min, $operator, $this->options['group']);
|
||||
}
|
||||
elseif ($max !== '') {
|
||||
$operator = $this->operator === 'between' ? '<=' : '>';
|
||||
$this->query->condition($this->real_field, $min, $operator, $this->options['group']);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// The parent handler doesn't expect our value structure, just pass the
|
||||
// scalar value instead.
|
||||
$this->value = $this->value['value'];
|
||||
parent::query();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets $this->value to an array of options as defined by the filter.
|
||||
*
|
||||
* @see SearchApiViewsHandlerFilterNumeric::option_definition()
|
||||
*/
|
||||
protected function normalizeValue() {
|
||||
$value = $this->value;
|
||||
if (is_array($value) && isset($value[0])) {
|
||||
$value = $value[0];
|
||||
}
|
||||
if (!is_array($value)) {
|
||||
$value = array('value' => $value);
|
||||
}
|
||||
$this->value = array(
|
||||
'value' => isset($value['value']) ? $value['value'] : '',
|
||||
'min' => isset($value['min']) ? $value['min'] : '',
|
||||
'max' => isset($value['max']) ? $value['max'] : '',
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -121,6 +121,7 @@ class SearchApiViewsHandlerFilterOptions extends SearchApiViewsHandlerFilter {
|
||||
*/
|
||||
public function option_definition() {
|
||||
$options = parent::option_definition();
|
||||
$options['value'] = array('default' => '');
|
||||
$options['expose']['contains']['reduce'] = array('default' => FALSE);
|
||||
return $options;
|
||||
}
|
||||
@ -256,6 +257,32 @@ class SearchApiViewsHandlerFilterOptions extends SearchApiViewsHandlerFilter {
|
||||
return $operator . (($values !== '') ? ' ' . $values : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
function accept_exposed_input($input) {
|
||||
$accepted = parent::accept_exposed_input($input);
|
||||
|
||||
// Grouped filters will have the raw form values structure from the
|
||||
// checkboxes as the value here. Convert that into the correct array of
|
||||
// values instead.
|
||||
if ($accepted && is_array($this->value) && $this->is_a_group()) {
|
||||
// For some reason, Views thinks it's a good idea to nest the form values
|
||||
// into a second array in some cases. That one will be numerically indexed
|
||||
// with just a single entry, though, so it should be relatively easy to
|
||||
// spot.
|
||||
if (count($this->value) && isset($this->value[0])) {
|
||||
$this->value = reset($this->value);
|
||||
}
|
||||
$this->value = array_keys(array_filter($this->value));
|
||||
if (!$this->value) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return $accepted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this filter to the query.
|
||||
*/
|
||||
|
@ -321,9 +321,13 @@ class SearchApiViewsHandlerFilterTaxonomyTerm extends SearchApiViewsHandlerFilte
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function ids_to_strings(array $ids) {
|
||||
$ids = array_filter($ids);
|
||||
if (!$ids) {
|
||||
return '';
|
||||
}
|
||||
return implode(', ', db_select('taxonomy_term_data', 'td')
|
||||
->fields('td', array('name'))
|
||||
->condition('td.tid', array_filter($ids))
|
||||
->condition('td.tid', $ids)
|
||||
->execute()
|
||||
->fetchCol());
|
||||
}
|
||||
|
@ -35,11 +35,16 @@ class SearchApiViewsCache extends views_plugin_cache_time {
|
||||
}
|
||||
|
||||
$cid = $this->get_results_key();
|
||||
$results = NULL;
|
||||
$query_plugin = $this->view->query;
|
||||
if ($query_plugin instanceof SearchApiViewsQuery) {
|
||||
$results = $query_plugin->getSearchApiResults();
|
||||
}
|
||||
$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(),
|
||||
'search_api results' => $results,
|
||||
);
|
||||
cache_set($cid, $data, $this->table, $this->cache_set_expire($type));
|
||||
}
|
||||
@ -80,7 +85,7 @@ class SearchApiViewsCache extends views_plugin_cache_time {
|
||||
* Overrides views_plugin_cache::get_cache_key().
|
||||
*
|
||||
* Use the Search API query as the main source for the key. Note that in
|
||||
* Views < 3.8, this function does not exist.
|
||||
* Views < 3.8, this method does not exist.
|
||||
*/
|
||||
public function get_cache_key($key_data = array()) {
|
||||
global $user;
|
||||
@ -121,7 +126,7 @@ class SearchApiViewsCache extends views_plugin_cache_time {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Search API query object associated with the current view.
|
||||
* Retrieves 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
|
||||
|
@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains the SearchApiViewsContentCache class.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Plugin class for caching Search API views, with additional invalidation.
|
||||
*/
|
||||
class SearchApiViewsContentCache extends views_content_cache_plugin_cache {
|
||||
|
||||
/**
|
||||
* 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();
|
||||
$results = NULL;
|
||||
$query_plugin = $this->view->query;
|
||||
if ($query_plugin instanceof SearchApiViewsQuery) {
|
||||
$results = $query_plugin->getSearchApiResults();
|
||||
}
|
||||
$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' => $results,
|
||||
);
|
||||
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_cache_key().
|
||||
*
|
||||
* Use the Search API query as the main source for the key. Note that in
|
||||
* Views < 3.8, this method does not exist.
|
||||
*/
|
||||
public function get_cache_key($key_data = array()) {
|
||||
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'],
|
||||
'offset' => $this->view->get_current_page() . '*' . $this->view->get_items_per_page() . '+' . $this->view->get_offset(),
|
||||
);
|
||||
// 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['exposed_info'] = $_GET['exposed_info'];
|
||||
}
|
||||
}
|
||||
$key = drupal_hash_base64(serialize($key_data));
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides views_plugin_cache::get_results_key().
|
||||
*
|
||||
* This is unnecessary for Views >= 3.8.
|
||||
*/
|
||||
public function get_results_key() {
|
||||
if (!isset($this->_results_key)) {
|
||||
$this->_results_key = $this->view->name . ':' . $this->display->id . ':results:' . $this->get_cache_key();
|
||||
}
|
||||
|
||||
return $this->_results_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves 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;
|
||||
}
|
||||
|
||||
}
|
@ -135,7 +135,9 @@ class SearchApiViewsQuery extends views_plugin_query {
|
||||
* The order to sort items in - either 'ASC' or 'DESC'. Defaults to 'ASC'.
|
||||
*/
|
||||
public function add_selector_orderby($selector, $order = 'ASC') {
|
||||
$this->query->sort($selector, $order);
|
||||
if (!$this->errors) {
|
||||
$this->query->sort($selector, $order);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -213,7 +215,7 @@ class SearchApiViewsQuery extends views_plugin_query {
|
||||
'#default_value' => $this->options['search_api_bypass_access'],
|
||||
);
|
||||
|
||||
if ($this->index->getEntityType()) {
|
||||
if ($this->index && $this->index->getEntityType()) {
|
||||
$form['entity_access'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Additional access checks on result entities'),
|
||||
|
@ -1,7 +1,7 @@
|
||||
name = Search views
|
||||
name = Search Views
|
||||
description = Integrates the Search API with Views, enabling users to create views with searches as filters or arguments.
|
||||
dependencies[] = search_api
|
||||
dependencies[] = views
|
||||
dependencies[] = search_api:search_api
|
||||
dependencies[] = views:views
|
||||
core = 7.x
|
||||
package = Search
|
||||
|
||||
@ -19,17 +19,18 @@ 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_numeric.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/plugin_content_cache.inc
|
||||
files[] = includes/query.inc
|
||||
|
||||
; Information added by Drupal.org packaging script on 2017-02-23
|
||||
version = "7.x-1.21"
|
||||
; Information added by Drupal.org packaging script on 2019-03-11
|
||||
version = "7.x-1.26"
|
||||
core = "7.x"
|
||||
project = "search_api"
|
||||
datestamp = "1487844493"
|
||||
|
||||
datestamp = "1552334832"
|
||||
|
@ -27,8 +27,11 @@ function search_api_views_search_api_index_insert() {
|
||||
*/
|
||||
function search_api_views_search_api_index_update(SearchApiIndex $index) {
|
||||
// Check whether index was disabled.
|
||||
if (!$index->enabled && $index->original->enabled) {
|
||||
$is_enabled = $index->enabled;
|
||||
$was_enabled = $index->original->enabled;
|
||||
if (!$is_enabled && $was_enabled) {
|
||||
_search_api_views_index_unavailable($index);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check whether the indexed fields changed.
|
||||
@ -36,7 +39,9 @@ function search_api_views_search_api_index_update(SearchApiIndex $index) {
|
||||
$old_fields = $old_fields['fields'];
|
||||
$new_fields = $index->options + array('fields' => array());
|
||||
$new_fields = $new_fields['fields'];
|
||||
if ($old_fields != $new_fields) {
|
||||
|
||||
// If the index was enabled or its fields changed, invalidate the Views cache.
|
||||
if ($is_enabled != $was_enabled || $old_fields != $new_fields) {
|
||||
views_invalidate_cache();
|
||||
}
|
||||
}
|
||||
|
@ -99,6 +99,7 @@ function search_api_views_views_data() {
|
||||
$table['search_api_relevance']['title'] = t('Relevance');
|
||||
$table['search_api_relevance']['help'] = t('The relevance of this search result with respect to the query.');
|
||||
$table['search_api_relevance']['field']['type'] = 'decimal';
|
||||
$table['search_api_relevance']['field']['float'] = TRUE;
|
||||
$table['search_api_relevance']['field']['handler'] = 'entity_views_handler_field_numeric';
|
||||
$table['search_api_relevance']['field']['click sortable'] = TRUE;
|
||||
$table['search_api_relevance']['sort']['handler'] = 'SearchApiViewsHandlerSort';
|
||||
@ -219,6 +220,9 @@ function _search_api_views_add_handlers($id, array $field, EntityMetadataWrapper
|
||||
$table[$id]['filter']['vocabulary'] = $vocabulary;
|
||||
}
|
||||
}
|
||||
elseif (in_array($inner_type, array('integer', 'decimal', 'duration', 'string'))) {
|
||||
$table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilterNumeric';
|
||||
}
|
||||
else {
|
||||
$table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilter';
|
||||
}
|
||||
@ -285,6 +289,16 @@ function search_api_views_views_plugins() {
|
||||
);
|
||||
}
|
||||
|
||||
if (module_exists('views_content_cache')) {
|
||||
$ret['cache']['search_api_views_content_cache'] = array(
|
||||
'title' => t('Search-specific content-based'),
|
||||
'help' => t("Cache Search API views based on content updates. (Requires Views Content Cache)"),
|
||||
'base' => $bases,
|
||||
'handler' => 'SearchApiViewsContentCache',
|
||||
'uses options' => TRUE,
|
||||
);
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
@ -20,11 +20,23 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
|
||||
*/
|
||||
protected $reductionType;
|
||||
|
||||
/**
|
||||
* A separator to use when the aggregation type is 'fulltext'.
|
||||
*
|
||||
* Used to temporarily store a string separator when the aggregation type is
|
||||
* "fulltext", for use in SearchApiAlterAddAggregation::reduce() with
|
||||
* array_reduce().
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fulltextReductionSeparator;
|
||||
|
||||
public function configurationForm() {
|
||||
$form['#attached']['css'][] = drupal_get_path('module', 'search_api') . '/search_api.admin.css';
|
||||
|
||||
$fields = $this->index->getFields(FALSE);
|
||||
$field_options = array();
|
||||
$field_properties = array();
|
||||
foreach ($fields as $name => $field) {
|
||||
$field_options[$name] = check_plain($field['name']);
|
||||
$field_properties[$name] = array(
|
||||
@ -79,9 +91,23 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
|
||||
'#required' => TRUE,
|
||||
);
|
||||
$form['fields'][$name]['type_descriptions'] = $type_descriptions;
|
||||
$type_selector = ':input[name="callbacks[search_api_alter_add_aggregation][settings][fields][' . $name . '][type]"]';
|
||||
foreach (array_keys($types) as $type) {
|
||||
$form['fields'][$name]['type_descriptions'][$type]['#states']['visible'][':input[name="callbacks[search_api_alter_add_aggregation][settings][fields][' . $name . '][type]"]']['value'] = $type;
|
||||
$form['fields'][$name]['type_descriptions'][$type]['#states']['visible'][$type_selector]['value'] = $type;
|
||||
}
|
||||
$form['fields'][$name]['separator'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Fulltext separator'),
|
||||
'#description' => t('For aggregation type "Fulltext", set the text that should be used to separate the aggregated field values. Use "\t" for tabs and "\n" for newline characters.'),
|
||||
'#default_value' => addcslashes(isset($field['separator']) ? $field['separator'] : "\n\n", "\0..\37\\"),
|
||||
'#states' => array(
|
||||
'visible' => array(
|
||||
$type_selector => array(
|
||||
'value' => 'fulltext',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
$form['fields'][$name]['fields'] = array_merge($field_properties, array(
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => t('Contained fields'),
|
||||
@ -125,11 +151,12 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
|
||||
return;
|
||||
}
|
||||
foreach ($values['fields'] as $name => $field) {
|
||||
$fields = $values['fields'][$name]['fields'] = array_values(array_filter($field['fields']));
|
||||
unset($values['fields'][$name]['actions']);
|
||||
$fields = $values['fields'][$name]['fields'] = array_values(array_filter($field['fields']));
|
||||
if ($field['name'] && !$fields) {
|
||||
form_error($form['fields'][$name]['fields'], t('You have to select at least one field to aggregate. If you want to remove an aggregated field, please delete its name.'));
|
||||
}
|
||||
$values['fields'][$name]['separator'] = stripcslashes($field['separator']);
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,6 +203,7 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
|
||||
$values = $this->flattenArray($values);
|
||||
|
||||
$this->reductionType = $field['type'];
|
||||
$this->fulltextReductionSeparator = isset($field['separator']) ? $field['separator'] : "\n\n";
|
||||
$item->$name = array_reduce($values, array($this, 'reduce'), NULL);
|
||||
if ($field['type'] == 'count' && !$item->$name) {
|
||||
$item->$name = 0;
|
||||
@ -192,7 +220,7 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
|
||||
public function reduce($a, $b) {
|
||||
switch ($this->reductionType) {
|
||||
case 'fulltext':
|
||||
return isset($a) ? $a . "\n\n" . $b : $b;
|
||||
return isset($a) ? $a . $this->fulltextReductionSeparator . $b : $b;
|
||||
case 'sum':
|
||||
return $a + $b;
|
||||
case 'count':
|
||||
@ -300,10 +328,10 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
|
||||
'count' => 'integer',
|
||||
'max' => 'integer',
|
||||
'min' => 'integer',
|
||||
'first' => 'string',
|
||||
'first_char' => 'string',
|
||||
'last' => 'string',
|
||||
'list' => 'list<string>',
|
||||
'first' => 'token',
|
||||
'first_char' => 'token',
|
||||
'last' => 'token',
|
||||
'list' => 'list<token>',
|
||||
);
|
||||
case 'description':
|
||||
return array(
|
||||
|
@ -108,7 +108,7 @@ class SearchApiAlterAddHierarchy extends SearchApiAbstractAlterCallback {
|
||||
$this->extractHierarchy($child, $prop, $values[$key]);
|
||||
}
|
||||
foreach ($values as $key => $value) {
|
||||
$item->$key = $value;
|
||||
$item->$key = array_values($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiAlterAddUserContent.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Adds the nodes created by the indexed user for indexing.
|
||||
*/
|
||||
class SearchApiAlterAddUserContent extends SearchApiAbstractAlterCallback {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function supportsIndex(SearchApiIndex $index) {
|
||||
return $index->getEntityType() === 'user';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function propertyInfo() {
|
||||
return array(
|
||||
'search_api_user_content' => array(
|
||||
'label' => t('User content'),
|
||||
'description' => t('The nodes created by this user'),
|
||||
'type' => 'list<node>',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function alterItems(array &$items) {
|
||||
$uids = array();
|
||||
foreach ($items as $item) {
|
||||
$uids[] = $item->uid;
|
||||
}
|
||||
|
||||
$sql = 'SELECT nid, uid FROM {node} WHERE uid IN (:uids)';
|
||||
$nids = db_query($sql, array(':uids' => $uids));
|
||||
$user_nodes = array();
|
||||
foreach ($nids as $row) {
|
||||
$user_nodes[$row->uid][] = $row->nid;
|
||||
}
|
||||
|
||||
foreach ($items as $item) {
|
||||
$item->search_api_user_content = array();
|
||||
if (!empty($user_nodes[$item->uid])) {
|
||||
$item->search_api_user_content = $user_nodes[$item->uid];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -315,99 +315,141 @@ class SearchApiHighlight extends SearchApiAbstractProcessor {
|
||||
* @param array $keys
|
||||
* Search keywords entered by the user.
|
||||
*
|
||||
* @return string
|
||||
* A string containing HTML for the excerpt.
|
||||
* @return string|null
|
||||
* A string containing HTML for the excerpt, or NULL if none could be
|
||||
* created.
|
||||
*/
|
||||
protected function createExcerpt($text, array $keys) {
|
||||
// Prepare text by stripping HTML tags and decoding HTML entities.
|
||||
$text = strip_tags(str_replace(array('<', '>'), array(' <', '> '), $text));
|
||||
$text = ' ' . decode_entities($text);
|
||||
$text = decode_entities($text);
|
||||
$text = preg_replace('/\s+/', ' ', $text);
|
||||
$text = trim($text, ' ');
|
||||
$text_length = strlen($text);
|
||||
|
||||
// Extract fragments around keywords.
|
||||
// First we collect ranges of text around each keyword, starting/ending
|
||||
// at spaces, trying to get to the requested length.
|
||||
// If the sum of all fragments is too short, we look for second occurrences.
|
||||
// Try to reach the requested excerpt length with about two fragments (each
|
||||
// with a keyword and some context).
|
||||
$ranges = array();
|
||||
$included = array();
|
||||
$length = 0;
|
||||
$work_keys = $keys;
|
||||
while ($length < $this->options['excerpt_length'] && $work_keys) {
|
||||
foreach ($work_keys as $k => $key) {
|
||||
if ($length >= $this->options['excerpt_length']) {
|
||||
$look_start = array();
|
||||
$remaining_keys = $keys;
|
||||
|
||||
// Get the set excerpt length from the configuration. If the length is too
|
||||
// small, only use one fragment.
|
||||
$excerpt_length = $this->options['excerpt_length'];
|
||||
$context_length = round($excerpt_length / 4) - 3;
|
||||
if ($context_length < 32) {
|
||||
$context_length = round($excerpt_length / 2) - 1;
|
||||
}
|
||||
|
||||
while ($length < $excerpt_length && !empty($remaining_keys)) {
|
||||
$found_keys = array();
|
||||
foreach ($remaining_keys as $key) {
|
||||
if ($length >= $excerpt_length) {
|
||||
break;
|
||||
}
|
||||
// Remember occurrence of key so we can skip over it if more occurrences
|
||||
// are desired.
|
||||
if (!isset($included[$key])) {
|
||||
$included[$key] = 0;
|
||||
|
||||
// Remember where we last found $key, in case we are coming through a
|
||||
// second time.
|
||||
if (!isset($look_start[$key])) {
|
||||
$look_start[$key] = 0;
|
||||
}
|
||||
// Locate a keyword (position $p, always >0 because $text starts with a
|
||||
// space).
|
||||
$p = 0;
|
||||
if (empty($this->options['highlight_partial'])) {
|
||||
$regex = '/' . self::$boundary . preg_quote($key, '/') . self::$boundary . '/iu';
|
||||
if (preg_match($regex, $text, $match, PREG_OFFSET_CAPTURE, $included[$key])) {
|
||||
$p = $match[0][1];
|
||||
|
||||
// See if we can find $key after where we found it the last time. Since
|
||||
// we are requiring a match on a word boundary, make sure $text starts
|
||||
// and ends with a space.
|
||||
$matches = array();
|
||||
|
||||
if (!$this->options['highlight_partial']) {
|
||||
$found_position = FALSE;
|
||||
$regex = '/' . static::$boundary . preg_quote($key, '/') . static::$boundary . '/iu';
|
||||
if (preg_match($regex, ' ' . $text . ' ', $matches, PREG_OFFSET_CAPTURE, $look_start[$key])) {
|
||||
$found_position = $matches[0][1];
|
||||
}
|
||||
}
|
||||
else {
|
||||
$p = stripos($text, $key, $included[$key]);
|
||||
$found_position = stripos($text, $key, $look_start[$key]);
|
||||
}
|
||||
// Now locate a space in front (position $q) and behind it (position $s),
|
||||
// leaving about 60 characters extra before and after for context.
|
||||
// Note that a space was added to the front and end of $text above.
|
||||
if ($p) {
|
||||
if (($q = strpos(' ' . $text, ' ', max(0, $p - 61))) !== FALSE) {
|
||||
$end = substr($text . ' ', $p, 80);
|
||||
if (($s = strrpos($end, ' ')) !== FALSE) {
|
||||
// Account for the added spaces.
|
||||
$q = max($q - 1, 0);
|
||||
$s = min($s, strlen($end) - 1);
|
||||
$ranges[$q] = $p + $s;
|
||||
$length += $p + $s - $q;
|
||||
$included[$key] = $p + 1;
|
||||
continue;
|
||||
if ($found_position !== FALSE) {
|
||||
$look_start[$key] = $found_position + 1;
|
||||
// Keep track of which keys we found this time, in case we need to
|
||||
// pass through again to find more text.
|
||||
$found_keys[] = $key;
|
||||
|
||||
// Locate a space before and after this match, leaving some context on
|
||||
// each end.
|
||||
if ($found_position > $context_length) {
|
||||
$before = strpos($text, ' ', $found_position - $context_length);
|
||||
if ($before !== FALSE) {
|
||||
++$before;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$before = 0;
|
||||
}
|
||||
if ($before !== FALSE && $before <= $found_position) {
|
||||
if ($text_length > $found_position + $context_length) {
|
||||
$after = strrpos(substr($text, 0, $found_position + $context_length), ' ', $found_position);
|
||||
}
|
||||
else {
|
||||
$after = $text_length;
|
||||
}
|
||||
if ($after !== FALSE && $after > $found_position) {
|
||||
if ($before < $after) {
|
||||
// Save this range.
|
||||
$ranges[$before] = $after;
|
||||
$length += $after - $before;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Unless we got a match above, we don't need to look for this key any
|
||||
// more.
|
||||
unset($work_keys[$k]);
|
||||
}
|
||||
// Next time through this loop, only look for keys we found this time,
|
||||
// if any.
|
||||
$remaining_keys = $found_keys;
|
||||
}
|
||||
|
||||
if (count($ranges) == 0) {
|
||||
// We didn't find any keyword matches, so just return NULL.
|
||||
if (!$ranges) {
|
||||
// We didn't find any keyword matches, return NULL.
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Sort the text ranges by starting position.
|
||||
ksort($ranges);
|
||||
|
||||
// Now we collapse overlapping text ranges into one. The sorting makes it O(n).
|
||||
// Collapse overlapping text ranges into one. The sorting makes it O(n).
|
||||
$newranges = array();
|
||||
$from1 = $to1 = NULL;
|
||||
foreach ($ranges as $from2 => $to2) {
|
||||
if (!isset($from1)) {
|
||||
if ($from1 === NULL) {
|
||||
// This is the first time through this loop: initialize.
|
||||
$from1 = $from2;
|
||||
$to1 = $to2;
|
||||
continue;
|
||||
}
|
||||
if ($from2 <= $to1) {
|
||||
// The ranges overlap: combine them.
|
||||
$to1 = max($to1, $to2);
|
||||
}
|
||||
else {
|
||||
// The ranges do not overlap: save the working range and start a new
|
||||
// one.
|
||||
$newranges[$from1] = $to1;
|
||||
$from1 = $from2;
|
||||
$to1 = $to2;
|
||||
}
|
||||
}
|
||||
// Save the remaining working range.
|
||||
$newranges[$from1] = $to1;
|
||||
|
||||
// Fetch text
|
||||
// Fetch text within the combined ranges we found.
|
||||
$out = array();
|
||||
foreach ($newranges as $from => $to) {
|
||||
$out[] = substr($text, $from, $to - $from);
|
||||
}
|
||||
if (!$out) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Let translators have the ... separator text as one chunk.
|
||||
$dots = explode('!excerpt', t('... !excerpt ... !excerpt ...'));
|
||||
|
@ -24,12 +24,11 @@ class SearchApiPorterStemmer extends SearchApiAbstractProcessor {
|
||||
$form = parent::configurationForm();
|
||||
|
||||
$args = array(
|
||||
'!algorithm' => url('https://github.com/markfullmer/porter2'),
|
||||
'!exclusions' => url('https://github.com/markfullmer/porter2#user-content-custom-exclusions'),
|
||||
'@algorithm' => url('http://snowball.tartarus.org/algorithms/english/stemmer.html'),
|
||||
);
|
||||
$form += array(
|
||||
'help' => array(
|
||||
'#markup' => '<p>' . t('Optionally, provide an exclusion list to override the stemmer algorithm. Read about the <a href="@algorithm">algorithm</a> and <a href="@exclusions">exclusions</a>.', $args) . '</p>',
|
||||
'#markup' => '<p>' . t('Optionally, provide an exclusion list to override the stemmer algorithm. (<a href="@algorithm">Read about the algorithm</a>.)', $args) . '</p>',
|
||||
),
|
||||
'exceptions' => array(
|
||||
'#type' => 'textarea',
|
||||
@ -66,7 +65,7 @@ class SearchApiPorterStemmer extends SearchApiAbstractProcessor {
|
||||
$stemmed[] = $word;
|
||||
}
|
||||
}
|
||||
$value = implode('', $stemmed);
|
||||
$value = implode(' ', $stemmed);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -528,7 +528,7 @@ function theme_search_api_server(array $variables) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Form constructor for completely clearing a server.
|
||||
* Form constructor for server operations.
|
||||
*
|
||||
* @param SearchApiServer $server
|
||||
* The server for which the form is displayed.
|
||||
@ -543,15 +543,39 @@ function search_api_server_status_form(array $form, array &$form_state, SearchAp
|
||||
$form['clear'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Delete all indexed data on this server'),
|
||||
'#submit' => array('search_api_server_status_form_clear_submit')
|
||||
);
|
||||
|
||||
$count = $server->enabled ? search_api_server_tasks_count($server) : 0;
|
||||
if ($count) {
|
||||
$message = format_plural($count, '@count pending task must be executed before indexing.', '@count pending tasks must be executed before indexing.');
|
||||
drupal_set_message($message, 'warning', FALSE);
|
||||
$form['execute_pending_tasks'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Execute all pending tasks on this server'),
|
||||
'#submit' => array('search_api_server_status_form_execute_pending_tasks_submit')
|
||||
);
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form submission handler for search_api_server_status_form().
|
||||
*/
|
||||
function search_api_server_status_form_submit(array $form, array &$form_state) {
|
||||
* Form submission handler for search_api_server_status_form().
|
||||
*
|
||||
* Used for the "Execute all pending tasks" button.
|
||||
*/
|
||||
function search_api_server_status_form_execute_pending_tasks_submit($form, &$form_state) {
|
||||
$server_id = $form_state['server']->machine_name;
|
||||
$form_state['redirect'] = "admin/config/search/search_api/server/$server_id/execute-tasks";
|
||||
}
|
||||
|
||||
/**
|
||||
* Form submission handler for search_api_server_status_form().
|
||||
*
|
||||
* Used for the "Delete all indexed data" button.
|
||||
*/
|
||||
function search_api_server_status_form_clear_submit(array $form, array &$form_state) {
|
||||
$server_id = $form_state['server']->machine_name;
|
||||
$form_state['redirect'] = "admin/config/search/search_api/server/$server_id/clear";
|
||||
}
|
||||
@ -1566,10 +1590,13 @@ function search_api_admin_index_workflow(array $form, array &$form_state, Search
|
||||
$form['processors'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Processors'),
|
||||
'#description' => t('Select processors which will pre- and post-process data at index and search time, and their order. ' .
|
||||
'Most processors will only influence fulltext fields, but refer to their individual descriptions for details regarding their effect.'),
|
||||
'#description' => '<p>' . t("Select processors which will pre- and post-process data at index and search time, and their order. Most processors will only influence fulltext fields, but refer to their individual descriptions for details regarding their effect.<br />Also, some processors shouldn't be used with more advanced search engines (like Solr or Elasticsearch), since the search engine already provides this functionality.") . '</p>',
|
||||
'#collapsible' => TRUE,
|
||||
);
|
||||
if ($index->server) {
|
||||
$form['processors']['#description'] .= '<p>' . t("Check the <a href='@server-url'>server's</a> service class description for details.",
|
||||
array('@server-url' => url('admin/config/search/search_api/server/' . $index->server . '/edit'))) . '</p>';
|
||||
}
|
||||
|
||||
// Processor status.
|
||||
$form['processors']['status'] = array(
|
||||
@ -1696,6 +1723,7 @@ function search_api_admin_index_workflow_submit(array $form, array &$form_state)
|
||||
unset($values['callbacks']['settings']);
|
||||
unset($values['processors']['settings']);
|
||||
$index = $form_state['index'];
|
||||
$index_path = 'admin/config/search/search_api/index/' . $index->machine_name;
|
||||
|
||||
$options = empty($index->options) ? array() : $index->options;
|
||||
|
||||
@ -1761,13 +1789,14 @@ function search_api_admin_index_workflow_submit(array $form, array &$form_state)
|
||||
|
||||
$index->save();
|
||||
$index->reindex();
|
||||
drupal_set_message(t("The indexing workflow was successfully edited. All content was scheduled for re-indexing so the new settings can take effect."));
|
||||
$vars = array('@url' => url($index_path));
|
||||
drupal_set_message(t('The indexing workflow was successfully edited. All content was scheduled for <a href="@url">re-indexing</a> so the new settings can take effect.', $vars));
|
||||
}
|
||||
else {
|
||||
drupal_set_message(t('No values were changed.'));
|
||||
}
|
||||
|
||||
$form_state['redirect'] = 'admin/config/search/search_api/index/' . $index->machine_name . '/workflow';
|
||||
$form_state['redirect'] = $index_path . '/workflow';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1822,8 +1851,8 @@ function search_api_admin_index_fields(array $form, array &$form_state, SearchAp
|
||||
'In any case, fields of type "Fulltext" will always be fulltext-searchable.</p>'),
|
||||
);
|
||||
if ($index->server) {
|
||||
$form['description']['#description'] .= '<p>' . t('Check the <a href="@server-url">' . "server's</a> service class description for details.",
|
||||
array('@server-url' => url('admin/config/search/search_api/server/' . $index->server))) . '</p>';
|
||||
$form['description']['#description'] .= '<p>' . t("Check the <a href='@server-url'>server's</a> service class description for details.",
|
||||
array('@server-url' => url('admin/config/search/search_api/server/' . $index->server . '/edit'))) . '</p>';
|
||||
}
|
||||
foreach ($fields as $key => $info) {
|
||||
$form['fields'][$key]['title']['#markup'] = check_plain($info['name']);
|
||||
|
@ -95,6 +95,18 @@ function search_api_drush_command() {
|
||||
'aliases' => array('sapi-r'),
|
||||
);
|
||||
|
||||
$items['search-api-reindex-items'] = array(
|
||||
'description' => 'Force re-indexing of one or more specific items.',
|
||||
'examples' => array(
|
||||
'drush search-api-reindex-items node 12,34,56' => dt('Schedule the nodes with ID 12, 34 and 56 for re-indexing.'),
|
||||
),
|
||||
'arguments' => array(
|
||||
'entity_type' => dt('The entity type whose items should be re-indexed.'),
|
||||
'entities' => dt('The entities of the given entity type to be re-indexed.'),
|
||||
),
|
||||
'aliases' => array('sapi-ri'),
|
||||
);
|
||||
|
||||
$items['search-api-clear'] = array(
|
||||
'description' => 'Clear one or all search indexes and mark them for re-indexing.',
|
||||
'examples' => array(
|
||||
@ -109,6 +121,19 @@ function search_api_drush_command() {
|
||||
'aliases' => array('sapi-c'),
|
||||
);
|
||||
|
||||
$items['search-api-execute-tasks'] = array(
|
||||
'description' => 'Execute all pending tasks or all for a given server.',
|
||||
'examples' => array(
|
||||
'drush search-api-execute-tasks my_solr_server' => dt('Execute all pending tasks on !server', array('!server' => 'my_solr_server')),
|
||||
'drush sapi-et my_solr_server' => dt('Execute all pending tasks on !server', array('!server' => 'my_solr_server')),
|
||||
'drush sapi-et' => dt('Execute all pending tasks on all servers.')
|
||||
),
|
||||
'arguments' => array(
|
||||
'server_id' => dt('The numeric ID or machine name of a server to execute tasks on.'),
|
||||
),
|
||||
'aliases' => array('sapi-et')
|
||||
);
|
||||
|
||||
$items['search-api-set-index-server'] = array(
|
||||
'description' => 'Set the search server used by a given index.',
|
||||
'examples' => array(
|
||||
@ -448,6 +473,33 @@ function drush_search_api_reindex($index_id = NULL) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the given entities as needing to be re-indexed.
|
||||
*/
|
||||
function drush_search_api_reindex_items($entity_type, $entities) {
|
||||
if (search_api_drush_static(__FUNCTION__)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate list of entity ids.
|
||||
if (!empty($entities) && !preg_match('#^[0-9]*(,[0-9]*)*$#', $entities)) {
|
||||
drush_log(dt('Entities should be a single numeric entity ID or a list with the numeric entity IDs separated by comma.'), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
$ids = explode(',', $entities);
|
||||
|
||||
if (!empty($ids)) {
|
||||
search_api_track_item_change($entity_type, $ids);
|
||||
|
||||
$combined_ids = array();
|
||||
foreach ($ids as $id) {
|
||||
$combined_ids[] = $entity_type . '/' . $id;
|
||||
}
|
||||
search_api_track_item_change('multiple', $combined_ids);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear an index.
|
||||
*/
|
||||
@ -466,6 +518,34 @@ function drush_search_api_clear($index_id = NULL) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute all pending tasks or all for a given server.
|
||||
*/
|
||||
function drush_search_api_execute_tasks($server_id = NULL) {
|
||||
if (search_api_drush_static(__FUNCTION__)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Attempt to load the associated server.
|
||||
$server = NULL;
|
||||
if ($server_id) {
|
||||
$servers = search_api_drush_get_server($server_id);
|
||||
if (!$servers) {
|
||||
return;
|
||||
}
|
||||
$server = reset($servers);
|
||||
}
|
||||
|
||||
// Process batch op with drush.
|
||||
try {
|
||||
search_api_execute_pending_tasks($server);
|
||||
drush_log(dt('!server tasks have been successfully executed.', array('!server' => $server->machine_name ? $server->machine_name : 'All')), 'ok');
|
||||
}
|
||||
catch (SearchApiException $e) {
|
||||
drush_log($e->getMessage(), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the server for a given index.
|
||||
*/
|
||||
|
@ -1,6 +1,6 @@
|
||||
name = Search API
|
||||
description = "Provides a generic API for modules offering search capabilities."
|
||||
dependencies[] = entity
|
||||
dependencies[] = entity:entity
|
||||
core = 7.x
|
||||
package = Search
|
||||
|
||||
@ -16,6 +16,7 @@ files[] = includes/callback_language_control.inc
|
||||
files[] = includes/callback_node_access.inc
|
||||
files[] = includes/callback_node_status.inc
|
||||
files[] = includes/callback_role_filter.inc
|
||||
files[] = includes/callback_user_content.inc
|
||||
files[] = includes/callback_user_status.inc
|
||||
files[] = includes/datasource.inc
|
||||
files[] = includes/datasource_entity.inc
|
||||
@ -37,9 +38,8 @@ files[] = includes/service.inc
|
||||
|
||||
configure = admin/config/search/search_api
|
||||
|
||||
; Information added by Drupal.org packaging script on 2017-02-23
|
||||
version = "7.x-1.21"
|
||||
; Information added by Drupal.org packaging script on 2019-03-11
|
||||
version = "7.x-1.26"
|
||||
core = "7.x"
|
||||
project = "search_api"
|
||||
datestamp = "1487844493"
|
||||
|
||||
datestamp = "1552334832"
|
||||
|
@ -264,6 +264,51 @@ function search_api_schema() {
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_requirements().
|
||||
*/
|
||||
function search_api_requirements($phase) {
|
||||
$requirements = array();
|
||||
|
||||
if ($phase == 'runtime') {
|
||||
// Check whether at least one server has pending tasks.
|
||||
if (search_api_server_tasks_count()) {
|
||||
$items = array();
|
||||
|
||||
$conditions = array('enabled' => TRUE);
|
||||
foreach (search_api_server_load_multiple(FALSE, $conditions) as $server) {
|
||||
$count = search_api_server_tasks_count($server);
|
||||
if ($count) {
|
||||
$args = array(
|
||||
'@name' => $server->name,
|
||||
);
|
||||
$text = format_plural($count, '@name has @count pending task.', '@name has @count pending tasks.', $args);
|
||||
$items[] = l($text, "admin/config/search/search_api/server/{$server->machine_name}/execute-tasks");
|
||||
}
|
||||
}
|
||||
|
||||
if ($items) {
|
||||
$text = t('There are pending tasks for the following servers:');
|
||||
$text .= theme('item_list', array(
|
||||
'type' => 'ul',
|
||||
'items' => $items,
|
||||
));
|
||||
if (count($items) > 1) {
|
||||
$label = t('Execute pending tasks on all servers');
|
||||
$text .= l($label, 'admin/config/search/search_api/execute-tasks');
|
||||
}
|
||||
$requirements['search_api_pending_tasks'] = array(
|
||||
'title' => t('Search API'),
|
||||
'value' => $text,
|
||||
'severity' => REQUIREMENT_WARNING,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_install().
|
||||
*
|
||||
|
@ -72,6 +72,15 @@ function search_api_menu() {
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'context' => MENU_CONTEXT_INLINE | MENU_CONTEXT_PAGE,
|
||||
);
|
||||
$items[$pre . '/server/%search_api_server/execute-tasks'] = array(
|
||||
'title' => 'Execute pending tasks',
|
||||
'description' => 'Attempt to process pending tasks for a given server.',
|
||||
'page callback' => 'search_api_execute_pending_tasks',
|
||||
'page arguments' => array(5),
|
||||
'access callback' => 'search_api_access_execute_tasks_batch',
|
||||
'access arguments' => array(5),
|
||||
'type' => MENU_CALLBACK,
|
||||
);
|
||||
$items[$pre . '/server/%search_api_server/disable'] = array(
|
||||
'title' => 'Disable',
|
||||
'description' => 'Disable index.',
|
||||
@ -98,6 +107,13 @@ function search_api_menu() {
|
||||
'context' => MENU_CONTEXT_INLINE,
|
||||
'weight' => 10,
|
||||
);
|
||||
$items[$pre . '/execute-tasks'] = array(
|
||||
'title' => 'Execute pending tasks',
|
||||
'description' => 'Attempt to process pending server tasks.',
|
||||
'page callback' => 'search_api_execute_pending_tasks',
|
||||
'access callback' => 'search_api_access_execute_tasks_batch',
|
||||
'type' => MENU_LOCAL_ACTION,
|
||||
);
|
||||
$items[$pre . '/index/%search_api_index'] = array(
|
||||
'title' => 'View index',
|
||||
'title callback' => 'search_api_admin_item_title',
|
||||
@ -1025,6 +1041,28 @@ function search_api_search_api_item_type_info() {
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_module_implements_alter().
|
||||
*
|
||||
* Ensures the item type and service class static caches are invalidated at the
|
||||
* right time.
|
||||
*/
|
||||
function search_api_module_implements_alter(array &$implementations, $hook) {
|
||||
switch ($hook) {
|
||||
case 'modules_enabled':
|
||||
$group = $implementations['search_api'];
|
||||
unset($implementations['search_api']);
|
||||
$implementations = array('search_api' => $group) + $implementations;
|
||||
break;
|
||||
|
||||
case 'modules_disabled':
|
||||
$group = $implementations['search_api'];
|
||||
unset($implementations['search_api']);
|
||||
$implementations['search_api'] = $group;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_modules_enabled().
|
||||
*/
|
||||
@ -1103,6 +1141,11 @@ function search_api_search_api_alter_callback_info() {
|
||||
'description' => t('Exclude unpublished nodes from the index. <strong>Caution:</strong> This only affects the indexed nodes themselves. If an enabled node has references to disabled nodes, those will still be indexed (or displayed) normally.'),
|
||||
'class' => 'SearchApiAlterNodeStatus',
|
||||
);
|
||||
$callbacks['search_api_alter_user_content'] = array(
|
||||
'name' => t('Add user content'),
|
||||
'description' => t('Allows indexing of nodes (and their fields) created by the indexed user. (Caution: This might lead to performance problems, or even errors during indexing, on larger sites.)'),
|
||||
'class' => 'SearchApiAlterAddUserContent',
|
||||
);
|
||||
$callbacks['search_api_alter_user_status'] = array(
|
||||
'name' => t('Exclude blocked users'),
|
||||
'description' => t('Exclude blocked users from the index. <strong>Caution:</strong> This only affects the indexed users themselves. If an active user account includes a reference to a disabled user, that reference will still be indexed (or displayed) normally.'),
|
||||
@ -1382,6 +1425,10 @@ function search_api_server_tasks_check(SearchApiServer $server = NULL) {
|
||||
// Sometimes the order of tasks might be important, so make sure to order by
|
||||
// the task ID (which should be in order of insertion).
|
||||
$select->orderBy('t.id');
|
||||
// Only retrieve and execute 100 tasks at once, to avoid running out of memory
|
||||
// or time. We just can't do anything else until all tasks have been resolved,
|
||||
// but at least we shouldn't crash sites, or keep piling up tasks, that way.
|
||||
$select->range(0, 100);
|
||||
$tasks = $select->execute();
|
||||
|
||||
$executed_tasks = array();
|
||||
@ -1429,7 +1476,7 @@ function search_api_server_tasks_check(SearchApiServer $server = NULL) {
|
||||
|
||||
default:
|
||||
// This should never happen.
|
||||
continue;
|
||||
continue 2;
|
||||
}
|
||||
$executed_tasks[] = $task->id;
|
||||
}
|
||||
@ -1438,11 +1485,116 @@ function search_api_server_tasks_check(SearchApiServer $server = NULL) {
|
||||
if (!$executed_tasks) {
|
||||
return TRUE;
|
||||
}
|
||||
// Otherwise, delete the executed tasks and check if new tasks were created.
|
||||
// Otherwise, delete the executed tasks and check if new tasks were created
|
||||
// (or if we didn't even fetch all due to the 100 tasks limit).
|
||||
search_api_server_tasks_delete($executed_tasks);
|
||||
return $count_query->execute()->fetchField() === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a batch wrapper for search_api_server_tasks_check().
|
||||
*
|
||||
* @param SearchApiServer|null $server
|
||||
* (optional) The server whose tasks should be executed, or NULL to execute
|
||||
* tasks for all servers.
|
||||
*/
|
||||
function search_api_execute_pending_tasks(SearchApiServer $server = NULL) {
|
||||
batch_set(array(
|
||||
'title' => t('Processing pending tasks'),
|
||||
'operations' => array(
|
||||
array(
|
||||
'search_api_execute_pending_tasks_batch',
|
||||
array(
|
||||
$server,
|
||||
),
|
||||
),
|
||||
),
|
||||
'finished' => 'search_api_execute_pending_tasks_finished'
|
||||
));
|
||||
if ($server) {
|
||||
$path = 'admin/config/search/search_api/server/' . $server->machine_name;
|
||||
}
|
||||
else {
|
||||
$path = 'admin/config/search/search_api';
|
||||
}
|
||||
|
||||
if (function_exists('drush_backend_batch_process')) {
|
||||
drush_backend_batch_process();
|
||||
}
|
||||
else {
|
||||
batch_process($path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes pending server tasks as part of a batch operation.
|
||||
*/
|
||||
function search_api_execute_pending_tasks_batch(SearchApiServer $server = NULL, &$context) {
|
||||
if (!isset($context['results']['total'])) {
|
||||
$context['results']['total'] = search_api_server_tasks_count($server);
|
||||
}
|
||||
$total = $context['results']['total'];
|
||||
|
||||
search_api_server_tasks_check($server);
|
||||
|
||||
$remaining = search_api_server_tasks_count($server);
|
||||
$executed = max($total - $remaining, 0);
|
||||
|
||||
$args['@remaining'] = $remaining;
|
||||
$context['message'] = format_plural($executed, 'Successfully executed @count task, @remaining remaining.', 'Successfully executed @count tasks, @remaining remaining.', $args);
|
||||
$context['finished'] = $executed / $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch finish callback for pending server tasks.
|
||||
*/
|
||||
function search_api_execute_pending_tasks_finished($success, $results, $operations) {
|
||||
if ($success) {
|
||||
// Clear the previous warning.
|
||||
drupal_get_messages('warning');
|
||||
|
||||
// Alert user to the number of tasks executed.
|
||||
drupal_set_message(format_plural($results['total'], 'Successfully executed @count task.', 'Successfully executed @count tasks.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of pending tasks.
|
||||
*
|
||||
* @param SearchApiServer|null $server
|
||||
* (optional) The server for which tasks should be counted, or NULL to count
|
||||
* for all enabled servers.
|
||||
*
|
||||
* @return int
|
||||
* The number of pending tasks for the server, or in total.
|
||||
*/
|
||||
function search_api_server_tasks_count(SearchApiServer $server = NULL) {
|
||||
$query = db_select('search_api_task', 't')
|
||||
->fields('t');
|
||||
|
||||
if ($server) {
|
||||
$query->condition('server_id', $server->machine_name);
|
||||
}
|
||||
else {
|
||||
$query->join('search_api_server', 's', 's.machine_name = t.server_id');
|
||||
$query->condition('s.enabled', 1);
|
||||
}
|
||||
|
||||
return $query->countQuery()->execute()->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* Access callback: Checks whether a user can execute pending tasks.
|
||||
*
|
||||
* @param SearchApiServer|null $server
|
||||
* (optional) The server for which tasks would be executed.
|
||||
*/
|
||||
function search_api_access_execute_tasks_batch(SearchApiServer $server = NULL) {
|
||||
return user_access('administer search_api')
|
||||
&& search_api_server_tasks_count($server)
|
||||
&& (!$server || $server->enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an entry into a server's list of pending tasks.
|
||||
*
|
||||
|
@ -86,6 +86,7 @@ class SearchApiWebTest extends DrupalWebTestCase {
|
||||
* and then run tests on it.
|
||||
*/
|
||||
public function testFramework() {
|
||||
module_enable(array('search_api_test_2'));
|
||||
$this->drupalLogin($this->drupalCreateUser(array('administer search_api')));
|
||||
$this->insertItems();
|
||||
$this->createIndex();
|
||||
@ -730,13 +731,17 @@ class SearchApiWebTest extends DrupalWebTestCase {
|
||||
* deleteServer()) and that all associated tables and variables are removed.
|
||||
*/
|
||||
protected function disableModules() {
|
||||
module_disable(array('search_api_test_2'), FALSE);
|
||||
$this->assertFalse(module_exists('search_api_test_2'), 'Second test module was successfully disabled.');
|
||||
module_disable(array('search_api_test'), FALSE);
|
||||
$this->assertFalse(module_exists('search_api_test'), 'Test module was successfully disabled.');
|
||||
$this->assertFalse(module_exists('search_api_test'), 'First test module was successfully disabled.');
|
||||
module_disable(array('search_api'), FALSE);
|
||||
$this->assertFalse(module_exists('search_api'), 'Search API module was successfully disabled.');
|
||||
|
||||
drupal_uninstall_modules(array('search_api_test_2'), FALSE);
|
||||
$this->assertEqual(drupal_get_installed_schema_version('search_api_test_2', TRUE), SCHEMA_UNINSTALLED, 'Second test module was successfully uninstalled.');
|
||||
drupal_uninstall_modules(array('search_api_test'), FALSE);
|
||||
$this->assertEqual(drupal_get_installed_schema_version('search_api_test', TRUE), SCHEMA_UNINSTALLED, 'Test module was successfully uninstalled.');
|
||||
$this->assertEqual(drupal_get_installed_schema_version('search_api_test', TRUE), SCHEMA_UNINSTALLED, 'First test module was successfully uninstalled.');
|
||||
$this->assertFalse(db_table_exists('search_api_test'), 'Test module table was successfully removed.');
|
||||
drupal_uninstall_modules(array('search_api'), FALSE);
|
||||
$this->assertEqual(drupal_get_installed_schema_version('search_api', TRUE), SCHEMA_UNINSTALLED, 'Search API module was successfully uninstalled.');
|
||||
|
@ -1,18 +1,16 @@
|
||||
|
||||
name = Search API test
|
||||
name = Search API Test
|
||||
description = "Some dummy implementations for testing the Search API."
|
||||
core = 7.x
|
||||
package = Search
|
||||
|
||||
dependencies[] = search_api
|
||||
dependencies[] = search_api:search_api
|
||||
|
||||
files[] = search_api_test.module
|
||||
|
||||
hidden = TRUE
|
||||
|
||||
; Information added by Drupal.org packaging script on 2017-02-23
|
||||
version = "7.x-1.21"
|
||||
; Information added by Drupal.org packaging script on 2019-03-11
|
||||
version = "7.x-1.26"
|
||||
core = "7.x"
|
||||
project = "search_api"
|
||||
datestamp = "1487844493"
|
||||
|
||||
datestamp = "1552334832"
|
||||
|
@ -0,0 +1,16 @@
|
||||
name = Search API Test Service 2
|
||||
description = "A module providing a second test search service."
|
||||
core = 7.x
|
||||
package = Search
|
||||
|
||||
dependencies[] = search_api:search_api
|
||||
|
||||
files[] = search_api_test_service_2.module
|
||||
|
||||
hidden = TRUE
|
||||
|
||||
; Information added by Drupal.org packaging script on 2019-03-11
|
||||
version = "7.x-1.26"
|
||||
core = "7.x"
|
||||
project = "search_api"
|
||||
datestamp = "1552334832"
|
@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provides a second test service and server for testing Search API.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_search_api_service_info().
|
||||
*/
|
||||
function search_api_test_2_search_api_service_info() {
|
||||
$name = 'search_api_test_service_2';
|
||||
$services[$name] = array(
|
||||
'name' => $name,
|
||||
'description' => 'search_api_test_service_2 description',
|
||||
'class' => 'SearchApiDummyService',
|
||||
);
|
||||
return $services;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_default_search_api_server().
|
||||
*/
|
||||
function search_api_test_2_default_search_api_server() {
|
||||
$id = 'test_server_2';
|
||||
$items[$id] = entity_create('search_api_server', array(
|
||||
'name' => 'Search API test server 2',
|
||||
'machine_name' => $id,
|
||||
'enabled' => 1,
|
||||
'description' => 'A server used for testing.',
|
||||
'class' => 'search_api_test_service_2',
|
||||
));
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dummy service for testing.
|
||||
*/
|
||||
class SearchApiDummyService implements SearchApiServiceInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(\SearchApiServer $server) {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configurationForm(array $form, array &$form_state) {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configurationFormValidate(array $form, array &$values, array &$form_state) {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configurationFormSubmit(array $form, array &$values, array &$form_state) {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function supportsFeature($feature) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewSettings() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function postCreate() {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function postUpdate() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preDelete() {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addIndex(SearchApiIndex $index) {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fieldsUpdated(SearchApiIndex $index) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeIndex($index) {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function indexItems(SearchApiIndex $index, array $items) {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteItems($ids = 'all', SearchApiIndex $index = NULL) {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query(SearchApiIndex $index, $options = array()) {
|
||||
throw new SearchApiException("The dummy service doesn't support queries");
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function search(SearchApiQueryInterface $query) {
|
||||
return array();
|
||||
}
|
||||
}
|
@ -1 +1,39 @@
|
||||
See major version branches.
|
||||
INTRODUCTION
|
||||
------------
|
||||
Allows you to override solr connection settings on an environment (site) basis,
|
||||
via your settings.php without editing servers managed in features.
|
||||
|
||||
REQUIREMENTS
|
||||
------------
|
||||
* search_api_solr module
|
||||
|
||||
CONFIGURATION
|
||||
-------------
|
||||
The module has no menu or modifiable settings. There is no configuration. When
|
||||
enabled, you can set your override values in your settings.php file.
|
||||
Search api will automatically pick up your values, but make sure to clear your
|
||||
cache first.
|
||||
|
||||
EXAMPLE
|
||||
-------
|
||||
You can add following example to your settings.php file.
|
||||
|
||||
$conf['search_api_solr_overrides'] = array(
|
||||
'solr-server-id' => array(
|
||||
'name' => 'Solr Server (Overridden)',
|
||||
'options' => array(
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 8983,
|
||||
'path' => '/solr',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
MAINTAINERS
|
||||
-----------
|
||||
Current maintainers:
|
||||
* nick_schuch - https://www.drupal.org/u/nick_schuch
|
||||
* cafuego - https://www.drupal.org/u/cafuego
|
||||
|
||||
This project has been sponsored by:
|
||||
* PreviousNext - http://www.previousnext.com.au
|
||||
|
@ -3,9 +3,9 @@ description = Provides site specific overrides for search_api_solr configuration
|
||||
core = 7.x
|
||||
dependencies[] = search_api_solr
|
||||
|
||||
; Information added by drupal.org packaging script on 2013-10-01
|
||||
version = "7.x-1.0-rc1+1-dev"
|
||||
; Information added by Drupal.org packaging script on 2017-06-13
|
||||
version = "7.x-1.0"
|
||||
core = "7.x"
|
||||
project = "search_api_solr_overrides"
|
||||
datestamp = "1380626863"
|
||||
datestamp = "1497319149"
|
||||
|
||||
|
10
sites/all/modules/contrib/search/search_api_solr_overrides/search_api_solr_overrides.module
Normal file → Executable file
10
sites/all/modules/contrib/search/search_api_solr_overrides/search_api_solr_overrides.module
Normal file → Executable file
@ -15,7 +15,7 @@
|
||||
* Example:
|
||||
* $conf['search_api_solr_overrides'] = array(
|
||||
* 'solr-server-id' => array(
|
||||
* 'name' => t('Solr Server (Overridden)'),
|
||||
* 'name' => 'Solr Server (Overridden)',
|
||||
* 'options' => array(
|
||||
* 'host' => '127.0.0.1',
|
||||
* 'port' => 8983,
|
||||
@ -32,7 +32,7 @@ function search_api_solr_overrides_search_api_server_load($servers) {
|
||||
$overrides = variable_get('search_api_solr_overrides', FALSE);
|
||||
|
||||
// Ensure the is information provided.
|
||||
if (empty($overrides)) {
|
||||
if (empty($overrides) || !is_array($overrides)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -41,12 +41,12 @@ function search_api_solr_overrides_search_api_server_load($servers) {
|
||||
// Check to see if the server config exists.
|
||||
if (!empty($servers[$id])) {
|
||||
foreach ($servers[$id] as $key => $field) {
|
||||
// Ensure we need to override.
|
||||
if (empty($override[$key])) {
|
||||
// Ensure we need to override. User isset, so we can set FALSE values.
|
||||
if (!isset($override[$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for if the field is an array.
|
||||
// Check if the field contains an array.
|
||||
if (is_array($field)) {
|
||||
$servers[$id]->$key = array_merge($servers[$id]->$key, $override[$key]);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user