Browse Source

updated search_api, search_api_solr_override, imce

Bachir Soussi Chiadmi 5 years ago
parent
commit
2e69e3fd4c
41 changed files with 1383 additions and 140 deletions
  1. 3 3
      sites/all/modules/contrib/files/imce/imce/imce.info
  2. 1 0
      sites/all/modules/contrib/files/imce/imce/imce.install
  3. 14 0
      sites/all/modules/contrib/files/imce/imce/imce.module
  4. 7 0
      sites/all/modules/contrib/files/imce/imce/inc/imce.admin.inc
  5. 7 2
      sites/all/modules/contrib/files/imce/imce/js/imce.js
  6. 80 0
      sites/all/modules/contrib/search/search_api/CHANGELOG.txt
  7. 3 3
      sites/all/modules/contrib/search/search_api/README.txt
  8. 1 1
      sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_term.inc
  9. 6 7
      sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/search_api_facetapi.info
  10. 15 0
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/README.txt
  11. 2 2
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_argument.inc
  12. 0 2
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_argument_more_like_this.inc
  13. 41 7
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_date.inc
  14. 6 1
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_fulltext.inc
  15. 12 4
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_language.inc
  16. 248 0
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_numeric.inc
  17. 27 0
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_options.inc
  18. 5 1
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_taxonomy_term.inc
  19. 8 3
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/plugin_cache.inc
  20. 146 0
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/plugin_content_cache.inc
  21. 4 2
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/query.inc
  22. 8 7
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/search_api_views.info
  23. 7 2
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/search_api_views.module
  24. 14 0
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/search_api_views.views.inc
  25. 35 7
      sites/all/modules/contrib/search/search_api/includes/callback_add_aggregation.inc
  26. 1 1
      sites/all/modules/contrib/search/search_api/includes/callback_add_hierarchy.inc
  27. 57 0
      sites/all/modules/contrib/search/search_api/includes/callback_user_content.inc
  28. 88 46
      sites/all/modules/contrib/search/search_api/includes/processor_highlight.inc
  29. 3 4
      sites/all/modules/contrib/search/search_api/includes/processor_stemmer.inc
  30. 39 10
      sites/all/modules/contrib/search/search_api/search_api.admin.inc
  31. 80 0
      sites/all/modules/contrib/search/search_api/search_api.drush.inc
  32. 5 5
      sites/all/modules/contrib/search/search_api/search_api.info
  33. 45 0
      sites/all/modules/contrib/search/search_api/search_api.install
  34. 154 2
      sites/all/modules/contrib/search/search_api/search_api.module
  35. 7 2
      sites/all/modules/contrib/search/search_api/search_api.test
  36. 5 7
      sites/all/modules/contrib/search/search_api/tests/search_api_test.info
  37. 16 0
      sites/all/modules/contrib/search/search_api/tests/search_api_test_2.info
  38. 136 0
      sites/all/modules/contrib/search/search_api/tests/search_api_test_2.module
  39. 39 1
      sites/all/modules/contrib/search/search_api_solr_overrides/README.txt
  40. 3 3
      sites/all/modules/contrib/search/search_api_solr_overrides/search_api_solr_overrides.info
  41. 5 5
      sites/all/modules/contrib/search/search_api_solr_overrides/search_api_solr_overrides.module

+ 3 - 3
sites/all/modules/contrib/files/imce/imce/imce.info

@@ -4,9 +4,9 @@ core = "7.x"
 package = "Media"
 package = "Media"
 configure = "admin/config/media/imce"
 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"
 core = "7.x"
 project = "imce"
 project = "imce"
-datestamp = "1459346870"
+datestamp = "1495890787"
 
 

+ 1 - 0
sites/all/modules/contrib/files/imce/imce/imce.install

@@ -25,6 +25,7 @@ function imce_uninstall() {
   variable_del('imce_settings_replace');
   variable_del('imce_settings_replace');
   variable_del('imce_settings_thumb_method');
   variable_del('imce_settings_thumb_method');
   variable_del('imce_settings_disable_private');
   variable_del('imce_settings_disable_private');
+  variable_del('imce_settings_admin_theme');
   variable_del('imce_custom_content');
   variable_del('imce_custom_content');
   variable_del('imce_custom_process');
   variable_del('imce_custom_process');
   variable_del('imce_custom_init');
   variable_del('imce_custom_init');

+ 14 - 0
sites/all/modules/contrib/files/imce/imce/imce.module

@@ -46,6 +46,20 @@ function imce_menu() {
   return $items;
   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().
  * Implements hook_permission().
  */
  */

+ 7 - 0
sites/all/modules/contrib/files/imce/imce/inc/imce.admin.inc

@@ -109,6 +109,12 @@ function imce_admin_form($form, &$form_state) {
     '#default_value' => variable_get('imce_settings_disable_private', 1),
     '#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.'),
     '#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['submit'] = array('#type' => 'submit', '#value' => t('Save configuration'));
   $form['#theme'] = 'imce_admin';
   $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_replace', $form_state['values']['replace']);
   variable_set('imce_settings_thumb_method', $form_state['values']['thumb_method']);
   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_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.'));
   drupal_set_message(t('Changes have been saved.'));
 }
 }
 
 

+ 7 - 2
sites/all/modules/contrib/files/imce/imce/js/imce.js

@@ -801,12 +801,17 @@ updateUI: function() {
   if (furl.charAt(furl.length - 1) != '/') {
   if (furl.charAt(furl.length - 1) != '/') {
     furl = imce.conf.furl = furl + '/';
     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) {
   if (absurls && !isabs) {
     imce.conf.furl = baseurl + furl;
     imce.conf.furl = baseurl + furl;
   }
   }
   else if (!absurls && isabs && furl.indexOf(baseurl) == 0) {
   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.
   //convert button elements to input elements.
   imce.convertButtons(imce.FW);
   imce.convertButtons(imce.FW);

+ 80 - 0
sites/all/modules/contrib/search/search_api/CHANGELOG.txt

@@ -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):
 Search API 1.21 (2017-02-23):
 -----------------------------
 -----------------------------
 - #2780341 by Berdir: Fixed passing of custom ranges to date facets.
 - #2780341 by Berdir: Fixed passing of custom ranges to date facets.

+ 3 - 3
sites/all/modules/contrib/search/search_api/README.txt

@@ -31,9 +31,9 @@ Terms as used in this module.
   Sphinx or any other professional or simple indexing mechanism. Takes care of
   Sphinx or any other professional or simple indexing mechanism. Takes care of
   the details of all operations, especially indexing or searching content.
   the details of all operations, especially indexing or searching content.
 - Server:
 - 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:
 - Index:
   A configuration object for indexing data of a specific type. What and how data
   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
   is indexed is determined by its settings. Also keeps track of which items

+ 1 - 1
sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_term.inc

@@ -115,7 +115,7 @@ class SearchApiFacetapiTerm extends FacetapiQueryType implements FacetapiQueryTy
     if ($filter == '!') {
     if ($filter == '!') {
       $query_filter->condition($field, NULL, $exclude ? '<>' : '=');
       $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));
       $lower = trim(substr($filter, 1, $pos));
       $upper = trim(substr($filter, $pos + 4, -1));
       $upper = trim(substr($filter, $pos + 4, -1));
       if ($lower == '*' && $upper == '*') {
       if ($lower == '*' && $upper == '*') {

+ 6 - 7
sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/search_api_facetapi.info

@@ -1,7 +1,7 @@
-name = Search facets
+name = Search Facets
 description = "Integrate the Search API with the Facet API to provide facetted searches."
 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
 core = 7.x
 package = Search
 package = Search
 
 
@@ -9,9 +9,8 @@ files[] = plugins/facetapi/adapter.inc
 files[] = plugins/facetapi/query_type_term.inc
 files[] = plugins/facetapi/query_type_term.inc
 files[] = plugins/facetapi/query_type_date.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"
 core = "7.x"
 project = "search_api"
 project = "search_api"
-datestamp = "1487844493"
-
+datestamp = "1552334832"

+ 15 - 0
sites/all/modules/contrib/search/search_api/contrib/search_api_views/README.txt

@@ -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:
 random sort, as an associative array with any of the following keys:
 - seed: A numeric seed value to use for the random sort.
 - 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
 "Facets block" display
 ----------------------
 ----------------------
 Most features should be clear to users of Views. However, the module also
 Most features should be clear to users of Views. However, the module also

+ 2 - 2
sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_argument.inc

@@ -79,8 +79,8 @@ class SearchApiViewsHandlerArgument extends views_handler_argument {
   public function option_definition() {
   public function option_definition() {
     $options = parent::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;
     return $options;
   }
   }

+ 0 - 2
sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_argument_more_like_this.inc

@@ -16,8 +16,6 @@ class SearchApiViewsHandlerArgumentMoreLikeThis extends SearchApiViewsHandlerArg
    */
    */
   public function option_definition() {
   public function option_definition() {
     $options = parent::option_definition();
     $options = parent::option_definition();
-    unset($options['break_phrase']);
-    unset($options['not']);
     $options['entity_type'] = array('default' => FALSE);
     $options['entity_type'] = array('default' => FALSE);
     $options['fields'] = array('default' => array());
     $options['fields'] = array('default' => array());
     return $options;
     return $options;

+ 41 - 7
sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_date.inc

@@ -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.
    * Add a "widget type" option.
@@ -88,9 +88,22 @@ class SearchApiViewsHandlerFilterDate extends SearchApiViewsHandlerFilter {
   public function value_form(&$form, &$form_state) {
   public function value_form(&$form, &$form_state) {
     parent::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
     // If we are using the date popup widget, overwrite the settings of the form
     // according to what date_popup expects.
     // 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']['#type'] = 'date_popup';
       $form['value']['#date_format'] =  $this->options['date_popup_format'];
       $form['value']['#date_format'] =  $this->options['date_popup_format'];
       $form['value']['#date_year_range'] = $this->options['year_range'];
       $form['value']['#date_year_range'] = $this->options['year_range'];
@@ -109,17 +122,38 @@ class SearchApiViewsHandlerFilterDate extends SearchApiViewsHandlerFilter {
    * Add this filter to the query.
    * Add this filter to the query.
    */
    */
   public function query() {
   public function query() {
+    $this->normalizeValue();
+
     if ($this->operator === 'empty') {
     if ($this->operator === 'empty') {
       $this->query->condition($this->real_field, NULL, '=', $this->options['group']);
       $this->query->condition($this->real_field, NULL, '=', $this->options['group']);
     }
     }
     elseif ($this->operator === 'not empty') {
     elseif ($this->operator === 'not empty') {
       $this->query->condition($this->real_field, NULL, '<>', $this->options['group']);
       $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);
+      }
+      $max = $this->value['max'];
+      if ($max !== '') {
+        $max = is_numeric($max) ? $max : strtotime($max, REQUEST_TIME);
       }
       }
-      $v = is_numeric($this->value) ? $this->value : strtotime($this->value, 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) {
       if ($v !== FALSE) {
         $this->query->condition($this->real_field, $v, $this->operator, $this->options['group']);
         $this->query->condition($this->real_field, $v, $this->operator, $this->options['group']);
       }
       }

+ 6 - 1
sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_fulltext.inc

@@ -121,6 +121,11 @@ class SearchApiViewsHandlerFilterFulltext extends SearchApiViewsHandlerFilterTex
     $words = preg_split('/\s+/', $input);
     $words = preg_split('/\s+/', $input);
     $quoted = FALSE;
     $quoted = FALSE;
     foreach ($words as $i => $word) {
     foreach ($words as $i => $word) {
+      $word_length = drupal_strlen($word);
+      if (!$word_length) {
+        unset($words[$i]);
+        continue;
+      }
       // Protect quoted strings.
       // Protect quoted strings.
       if ($quoted && $word[strlen($word) - 1] === '"') {
       if ($quoted && $word[strlen($word) - 1] === '"') {
         $quoted = FALSE;
         $quoted = FALSE;
@@ -130,7 +135,7 @@ class SearchApiViewsHandlerFilterFulltext extends SearchApiViewsHandlerFilterTex
         $quoted = TRUE;
         $quoted = TRUE;
         continue;
         continue;
       }
       }
-      if (drupal_strlen($word) < $this->options['min_length']) {
+      if ($word_length < $this->options['min_length']) {
         unset($words[$i]);
         unset($words[$i]);
       }
       }
     }
     }

+ 12 - 4
sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_language.inc

@@ -18,10 +18,13 @@ class SearchApiViewsHandlerFilterLanguage extends SearchApiViewsHandlerFilterOpt
    */
    */
   protected function get_value_options() {
   protected function get_value_options() {
     parent::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') {
       elseif ($v == 'default') {
         $this->value[$i] = language_default('language');
         $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();
     parent::query();
   }
   }

+ 248 - 0
sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_numeric.inc

@@ -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'] : '',
+    );
+  }
+
+}

+ 27 - 0
sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_options.inc

@@ -121,6 +121,7 @@ class SearchApiViewsHandlerFilterOptions extends SearchApiViewsHandlerFilter {
    */
    */
   public function option_definition() {
   public function option_definition() {
     $options = parent::option_definition();
     $options = parent::option_definition();
+    $options['value'] = array('default' => '');
     $options['expose']['contains']['reduce'] = array('default' => FALSE);
     $options['expose']['contains']['reduce'] = array('default' => FALSE);
     return $options;
     return $options;
   }
   }
@@ -256,6 +257,32 @@ class SearchApiViewsHandlerFilterOptions extends SearchApiViewsHandlerFilter {
     return $operator . (($values !== '') ? ' ' . $values : '');
     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.
    * Add this filter to the query.
    */
    */

+ 5 - 1
sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_taxonomy_term.inc

@@ -321,9 +321,13 @@ class SearchApiViewsHandlerFilterTaxonomyTerm extends SearchApiViewsHandlerFilte
    * {@inheritdoc}
    * {@inheritdoc}
    */
    */
   protected function ids_to_strings(array $ids) {
   protected function ids_to_strings(array $ids) {
+    $ids = array_filter($ids);
+    if (!$ids) {
+      return '';
+    }
     return implode(', ', db_select('taxonomy_term_data', 'td')
     return implode(', ', db_select('taxonomy_term_data', 'td')
       ->fields('td', array('name'))
       ->fields('td', array('name'))
-      ->condition('td.tid', array_filter($ids))
+      ->condition('td.tid', $ids)
       ->execute()
       ->execute()
       ->fetchCol());
       ->fetchCol());
   }
   }

+ 8 - 3
sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/plugin_cache.inc

@@ -35,11 +35,16 @@ class SearchApiViewsCache extends views_plugin_cache_time {
     }
     }
 
 
     $cid = $this->get_results_key();
     $cid = $this->get_results_key();
+    $results = NULL;
+    $query_plugin = $this->view->query;
+    if ($query_plugin instanceof SearchApiViewsQuery) {
+      $results = $query_plugin->getSearchApiResults();
+    }
     $data = array(
     $data = array(
       'result' => $this->view->result,
       'result' => $this->view->result,
       'total_rows' => isset($this->view->total_rows) ? $this->view->total_rows : 0,
       'total_rows' => isset($this->view->total_rows) ? $this->view->total_rows : 0,
       'current_page' => $this->view->get_current_page(),
       '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));
     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().
    * Overrides views_plugin_cache::get_cache_key().
    *
    *
    * Use the Search API query as the main source for the key. Note that in
    * 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()) {
   public function get_cache_key($key_data = array()) {
     global $user;
     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
    * @return SearchApiQueryInterface|null
    *   The Search API query object associated with the current view; or NULL if
    *   The Search API query object associated with the current view; or NULL if

+ 146 - 0
sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/plugin_content_cache.inc

@@ -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;
+  }
+
+}

+ 4 - 2
sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/query.inc

@@ -135,7 +135,9 @@ class SearchApiViewsQuery extends views_plugin_query {
    *   The order to sort items in - either 'ASC' or 'DESC'. Defaults to 'ASC'.
    *   The order to sort items in - either 'ASC' or 'DESC'. Defaults to 'ASC'.
    */
    */
   public function add_selector_orderby($selector, $order = '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'],
       '#default_value' => $this->options['search_api_bypass_access'],
     );
     );
 
 
-    if ($this->index->getEntityType()) {
+    if ($this->index && $this->index->getEntityType()) {
       $form['entity_access'] = array(
       $form['entity_access'] = array(
         '#type' => 'checkbox',
         '#type' => 'checkbox',
         '#title' => t('Additional access checks on result entities'),
         '#title' => t('Additional access checks on result entities'),

+ 8 - 7
sites/all/modules/contrib/search/search_api/contrib/search_api_views/search_api_views.info

@@ -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.
 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
 core = 7.x
 package = Search
 package = Search
 
 
@@ -19,17 +19,18 @@ files[] = includes/handler_filter_date.inc
 files[] = includes/handler_filter_entity.inc
 files[] = includes/handler_filter_entity.inc
 files[] = includes/handler_filter_fulltext.inc
 files[] = includes/handler_filter_fulltext.inc
 files[] = includes/handler_filter_language.inc
 files[] = includes/handler_filter_language.inc
+files[] = includes/handler_filter_numeric.inc
 files[] = includes/handler_filter_options.inc
 files[] = includes/handler_filter_options.inc
 files[] = includes/handler_filter_taxonomy_term.inc
 files[] = includes/handler_filter_taxonomy_term.inc
 files[] = includes/handler_filter_text.inc
 files[] = includes/handler_filter_text.inc
 files[] = includes/handler_filter_user.inc
 files[] = includes/handler_filter_user.inc
 files[] = includes/handler_sort.inc
 files[] = includes/handler_sort.inc
 files[] = includes/plugin_cache.inc
 files[] = includes/plugin_cache.inc
+files[] = includes/plugin_content_cache.inc
 files[] = includes/query.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"
 core = "7.x"
 project = "search_api"
 project = "search_api"
-datestamp = "1487844493"
-
+datestamp = "1552334832"

+ 7 - 2
sites/all/modules/contrib/search/search_api/contrib/search_api_views/search_api_views.module

@@ -27,8 +27,11 @@ function search_api_views_search_api_index_insert() {
  */
  */
 function search_api_views_search_api_index_update(SearchApiIndex $index) {
 function search_api_views_search_api_index_update(SearchApiIndex $index) {
   // Check whether index was disabled.
   // 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);
     _search_api_views_index_unavailable($index);
+    return;
   }
   }
 
 
   // Check whether the indexed fields changed.
   // 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'];
   $old_fields = $old_fields['fields'];
   $new_fields = $index->options + array('fields' => array());
   $new_fields = $index->options + array('fields' => array());
   $new_fields = $new_fields['fields'];
   $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();
     views_invalidate_cache();
   }
   }
 }
 }

+ 14 - 0
sites/all/modules/contrib/search/search_api/contrib/search_api_views/search_api_views.views.inc

@@ -99,6 +99,7 @@ function search_api_views_views_data() {
       $table['search_api_relevance']['title'] = t('Relevance');
       $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']['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']['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']['handler'] = 'entity_views_handler_field_numeric';
       $table['search_api_relevance']['field']['click sortable'] = TRUE;
       $table['search_api_relevance']['field']['click sortable'] = TRUE;
       $table['search_api_relevance']['sort']['handler'] = 'SearchApiViewsHandlerSort';
       $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;
       $table[$id]['filter']['vocabulary'] = $vocabulary;
     }
     }
   }
   }
+  elseif (in_array($inner_type, array('integer', 'decimal', 'duration', 'string'))) {
+    $table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilterNumeric';
+  }
   else {
   else {
     $table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilter';
     $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;
   return $ret;
 }
 }
 
 

+ 35 - 7
sites/all/modules/contrib/search/search_api/includes/callback_add_aggregation.inc

@@ -20,11 +20,23 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
    */
    */
   protected $reductionType;
   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() {
   public function configurationForm() {
     $form['#attached']['css'][] = drupal_get_path('module', 'search_api') . '/search_api.admin.css';
     $form['#attached']['css'][] = drupal_get_path('module', 'search_api') . '/search_api.admin.css';
 
 
     $fields = $this->index->getFields(FALSE);
     $fields = $this->index->getFields(FALSE);
     $field_options = array();
     $field_options = array();
+    $field_properties = array();
     foreach ($fields as $name => $field) {
     foreach ($fields as $name => $field) {
       $field_options[$name] = check_plain($field['name']);
       $field_options[$name] = check_plain($field['name']);
       $field_properties[$name] = array(
       $field_properties[$name] = array(
@@ -79,9 +91,23 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
         '#required' => TRUE,
         '#required' => TRUE,
       );
       );
       $form['fields'][$name]['type_descriptions'] = $type_descriptions;
       $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) {
       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(
       $form['fields'][$name]['fields'] = array_merge($field_properties, array(
         '#type' => 'checkboxes',
         '#type' => 'checkboxes',
         '#title' => t('Contained fields'),
         '#title' => t('Contained fields'),
@@ -125,11 +151,12 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
       return;
       return;
     }
     }
     foreach ($values['fields'] as $name => $field) {
     foreach ($values['fields'] as $name => $field) {
-      $fields = $values['fields'][$name]['fields'] = array_values(array_filter($field['fields']));
       unset($values['fields'][$name]['actions']);
       unset($values['fields'][$name]['actions']);
+      $fields = $values['fields'][$name]['fields'] = array_values(array_filter($field['fields']));
       if ($field['name'] && !$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.'));
         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);
             $values = $this->flattenArray($values);
 
 
             $this->reductionType = $field['type'];
             $this->reductionType = $field['type'];
+            $this->fulltextReductionSeparator = isset($field['separator']) ? $field['separator'] : "\n\n";
             $item->$name = array_reduce($values, array($this, 'reduce'), NULL);
             $item->$name = array_reduce($values, array($this, 'reduce'), NULL);
             if ($field['type'] == 'count' && !$item->$name) {
             if ($field['type'] == 'count' && !$item->$name) {
               $item->$name = 0;
               $item->$name = 0;
@@ -192,7 +220,7 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
   public function reduce($a, $b) {
   public function reduce($a, $b) {
     switch ($this->reductionType) {
     switch ($this->reductionType) {
       case 'fulltext':
       case 'fulltext':
-        return isset($a) ? $a . "\n\n" . $b : $b;
+        return isset($a) ? $a . $this->fulltextReductionSeparator . $b : $b;
       case 'sum':
       case 'sum':
         return $a + $b;
         return $a + $b;
       case 'count':
       case 'count':
@@ -300,10 +328,10 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
           'count' => 'integer',
           'count' => 'integer',
           'max' => 'integer',
           'max' => 'integer',
           'min' => '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':
       case 'description':
         return array(
         return array(

+ 1 - 1
sites/all/modules/contrib/search/search_api/includes/callback_add_hierarchy.inc

@@ -108,7 +108,7 @@ class SearchApiAlterAddHierarchy extends SearchApiAbstractAlterCallback {
         $this->extractHierarchy($child, $prop, $values[$key]);
         $this->extractHierarchy($child, $prop, $values[$key]);
       }
       }
       foreach ($values as $key => $value) {
       foreach ($values as $key => $value) {
-        $item->$key = $value;
+        $item->$key = array_values($value);
       }
       }
     }
     }
   }
   }

+ 57 - 0
sites/all/modules/contrib/search/search_api/includes/callback_user_content.inc

@@ -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];
+      }
+    }
+  }
+
+}

+ 88 - 46
sites/all/modules/contrib/search/search_api/includes/processor_highlight.inc

@@ -315,99 +315,141 @@ class SearchApiHighlight extends SearchApiAbstractProcessor {
    * @param array $keys
    * @param array $keys
    *   Search keywords entered by the user.
    *   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) {
   protected function createExcerpt($text, array $keys) {
     // Prepare text by stripping HTML tags and decoding HTML entities.
     // Prepare text by stripping HTML tags and decoding HTML entities.
     $text = strip_tags(str_replace(array('<', '>'), array(' <', '> '), $text));
     $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();
     $ranges = array();
-    $included = array();
     $length = 0;
     $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;
           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 {
         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;
       return NULL;
     }
     }
 
 
     // Sort the text ranges by starting position.
     // Sort the text ranges by starting position.
     ksort($ranges);
     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();
     $newranges = array();
+    $from1 = $to1 = NULL;
     foreach ($ranges as $from2 => $to2) {
     foreach ($ranges as $from2 => $to2) {
-      if (!isset($from1)) {
+      if ($from1 === NULL) {
+        // This is the first time through this loop: initialize.
         $from1 = $from2;
         $from1 = $from2;
         $to1 = $to2;
         $to1 = $to2;
         continue;
         continue;
       }
       }
       if ($from2 <= $to1) {
       if ($from2 <= $to1) {
+        // The ranges overlap: combine them.
         $to1 = max($to1, $to2);
         $to1 = max($to1, $to2);
       }
       }
       else {
       else {
+        // The ranges do not overlap: save the working range and start a new
+        // one.
         $newranges[$from1] = $to1;
         $newranges[$from1] = $to1;
         $from1 = $from2;
         $from1 = $from2;
         $to1 = $to2;
         $to1 = $to2;
       }
       }
     }
     }
+    // Save the remaining working range.
     $newranges[$from1] = $to1;
     $newranges[$from1] = $to1;
 
 
-    // Fetch text
+    // Fetch text within the combined ranges we found.
     $out = array();
     $out = array();
     foreach ($newranges as $from => $to) {
     foreach ($newranges as $from => $to) {
       $out[] = substr($text, $from, $to - $from);
       $out[] = substr($text, $from, $to - $from);
     }
     }
+    if (!$out) {
+      return NULL;
+    }
 
 
     // Let translators have the ... separator text as one chunk.
     // Let translators have the ... separator text as one chunk.
     $dots = explode('!excerpt', t('... !excerpt ... !excerpt ...'));
     $dots = explode('!excerpt', t('... !excerpt ... !excerpt ...'));

+ 3 - 4
sites/all/modules/contrib/search/search_api/includes/processor_stemmer.inc

@@ -24,12 +24,11 @@ class SearchApiPorterStemmer extends SearchApiAbstractProcessor {
     $form = parent::configurationForm();
     $form = parent::configurationForm();
 
 
     $args = array(
     $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(
     $form += array(
       'help' => 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(
       'exceptions' => array(
         '#type' => 'textarea',
         '#type' => 'textarea',
@@ -66,7 +65,7 @@ class SearchApiPorterStemmer extends SearchApiAbstractProcessor {
         $stemmed[] = $word;
         $stemmed[] = $word;
       }
       }
     }
     }
-    $value = implode('', $stemmed);
+    $value = implode(' ', $stemmed);
   }
   }
 
 
   /**
   /**

+ 39 - 10
sites/all/modules/contrib/search/search_api/search_api.admin.inc

@@ -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
  * @param SearchApiServer $server
  *   The server for which the form is displayed.
  *   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(
   $form['clear'] = array(
     '#type' => 'submit',
     '#type' => 'submit',
     '#value' => t('Delete all indexed data on this server'),
     '#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;
   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;
   $server_id = $form_state['server']->machine_name;
   $form_state['redirect'] = "admin/config/search/search_api/server/$server_id/clear";
   $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(
   $form['processors'] = array(
     '#type' => 'fieldset',
     '#type' => 'fieldset',
     '#title' => t('Processors'),
     '#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,
     '#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.
   // Processor status.
   $form['processors']['status'] = array(
   $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['callbacks']['settings']);
   unset($values['processors']['settings']);
   unset($values['processors']['settings']);
   $index = $form_state['index'];
   $index = $form_state['index'];
+  $index_path = 'admin/config/search/search_api/index/' . $index->machine_name;
 
 
   $options = empty($index->options) ? array() : $index->options;
   $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->save();
     $index->reindex();
     $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 {
   else {
     drupal_set_message(t('No values were changed.'));
     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>'),
         'In any case, fields of type "Fulltext" will always be fulltext-searchable.</p>'),
   );
   );
   if ($index->server) {
   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) {
   foreach ($fields as $key => $info) {
     $form['fields'][$key]['title']['#markup'] = check_plain($info['name']);
     $form['fields'][$key]['title']['#markup'] = check_plain($info['name']);

+ 80 - 0
sites/all/modules/contrib/search/search_api/search_api.drush.inc

@@ -95,6 +95,18 @@ function search_api_drush_command() {
     'aliases' => array('sapi-r'),
     '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(
   $items['search-api-clear'] = array(
     'description' => 'Clear one or all search indexes and mark them for re-indexing.',
     'description' => 'Clear one or all search indexes and mark them for re-indexing.',
     'examples' => array(
     'examples' => array(
@@ -109,6 +121,19 @@ function search_api_drush_command() {
     'aliases' => array('sapi-c'),
     '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(
   $items['search-api-set-index-server'] = array(
     'description' => 'Set the search server used by a given index.',
     'description' => 'Set the search server used by a given index.',
     'examples' => array(
     '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.
  * 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.
  * Set the server for a given index.
  */
  */

+ 5 - 5
sites/all/modules/contrib/search/search_api/search_api.info

@@ -1,6 +1,6 @@
 name = Search API
 name = Search API
 description = "Provides a generic API for modules offering search capabilities."
 description = "Provides a generic API for modules offering search capabilities."
-dependencies[] = entity
+dependencies[] = entity:entity
 core = 7.x
 core = 7.x
 package = Search
 package = Search
 
 
@@ -16,6 +16,7 @@ files[] = includes/callback_language_control.inc
 files[] = includes/callback_node_access.inc
 files[] = includes/callback_node_access.inc
 files[] = includes/callback_node_status.inc
 files[] = includes/callback_node_status.inc
 files[] = includes/callback_role_filter.inc
 files[] = includes/callback_role_filter.inc
+files[] = includes/callback_user_content.inc
 files[] = includes/callback_user_status.inc
 files[] = includes/callback_user_status.inc
 files[] = includes/datasource.inc
 files[] = includes/datasource.inc
 files[] = includes/datasource_entity.inc
 files[] = includes/datasource_entity.inc
@@ -37,9 +38,8 @@ files[] = includes/service.inc
 
 
 configure = admin/config/search/search_api
 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"
 core = "7.x"
 project = "search_api"
 project = "search_api"
-datestamp = "1487844493"
-
+datestamp = "1552334832"

+ 45 - 0
sites/all/modules/contrib/search/search_api/search_api.install

@@ -264,6 +264,51 @@ function search_api_schema() {
   return $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().
  * Implements hook_install().
  *
  *

+ 154 - 2
sites/all/modules/contrib/search/search_api/search_api.module

@@ -72,6 +72,15 @@ function search_api_menu() {
     'type' => MENU_LOCAL_TASK,
     'type' => MENU_LOCAL_TASK,
     'context' => MENU_CONTEXT_INLINE | MENU_CONTEXT_PAGE,
     '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(
   $items[$pre . '/server/%search_api_server/disable'] = array(
     'title' => 'Disable',
     'title' => 'Disable',
     'description' => 'Disable index.',
     'description' => 'Disable index.',
@@ -98,6 +107,13 @@ function search_api_menu() {
     'context' => MENU_CONTEXT_INLINE,
     'context' => MENU_CONTEXT_INLINE,
     'weight' => 10,
     '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(
   $items[$pre . '/index/%search_api_index'] = array(
     'title' => 'View index',
     'title' => 'View index',
     'title callback' => 'search_api_admin_item_title',
     'title callback' => 'search_api_admin_item_title',
@@ -1025,6 +1041,28 @@ function search_api_search_api_item_type_info() {
   return $types;
   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().
  * 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.'),
     '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',
     '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(
   $callbacks['search_api_alter_user_status'] = array(
     'name' => t('Exclude blocked users'),
     '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.'),
     '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
   // Sometimes the order of tasks might be important, so make sure to order by
   // the task ID (which should be in order of insertion).
   // the task ID (which should be in order of insertion).
   $select->orderBy('t.id');
   $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();
   $tasks = $select->execute();
 
 
   $executed_tasks = array();
   $executed_tasks = array();
@@ -1429,7 +1476,7 @@ function search_api_server_tasks_check(SearchApiServer $server = NULL) {
 
 
       default:
       default:
         // This should never happen.
         // This should never happen.
-        continue;
+        continue 2;
     }
     }
     $executed_tasks[] = $task->id;
     $executed_tasks[] = $task->id;
   }
   }
@@ -1438,11 +1485,116 @@ function search_api_server_tasks_check(SearchApiServer $server = NULL) {
   if (!$executed_tasks) {
   if (!$executed_tasks) {
     return TRUE;
     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);
   search_api_server_tasks_delete($executed_tasks);
   return $count_query->execute()->fetchField() === 0;
   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.
  * Adds an entry into a server's list of pending tasks.
  *
  *

+ 7 - 2
sites/all/modules/contrib/search/search_api/search_api.test

@@ -86,6 +86,7 @@ class SearchApiWebTest extends DrupalWebTestCase {
    * and then run tests on it.
    * and then run tests on it.
    */
    */
   public function testFramework() {
   public function testFramework() {
+    module_enable(array('search_api_test_2'));
     $this->drupalLogin($this->drupalCreateUser(array('administer search_api')));
     $this->drupalLogin($this->drupalCreateUser(array('administer search_api')));
     $this->insertItems();
     $this->insertItems();
     $this->createIndex();
     $this->createIndex();
@@ -730,13 +731,17 @@ class SearchApiWebTest extends DrupalWebTestCase {
    * deleteServer()) and that all associated tables and variables are removed.
    * deleteServer()) and that all associated tables and variables are removed.
    */
    */
   protected function disableModules() {
   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);
     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);
     module_disable(array('search_api'), FALSE);
     $this->assertFalse(module_exists('search_api'), 'Search API module was successfully disabled.');
     $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);
     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.');
     $this->assertFalse(db_table_exists('search_api_test'), 'Test module table was successfully removed.');
     drupal_uninstall_modules(array('search_api'), FALSE);
     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.');
     $this->assertEqual(drupal_get_installed_schema_version('search_api', TRUE), SCHEMA_UNINSTALLED, 'Search API module was successfully uninstalled.');

+ 5 - 7
sites/all/modules/contrib/search/search_api/tests/search_api_test.info

@@ -1,18 +1,16 @@
-
-name = Search API test
+name = Search API Test
 description = "Some dummy implementations for testing the Search API."
 description = "Some dummy implementations for testing the Search API."
 core = 7.x
 core = 7.x
 package = Search
 package = Search
 
 
-dependencies[] = search_api
+dependencies[] = search_api:search_api
 
 
 files[] = search_api_test.module
 files[] = search_api_test.module
 
 
 hidden = TRUE
 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"
 core = "7.x"
 project = "search_api"
 project = "search_api"
-datestamp = "1487844493"
-
+datestamp = "1552334832"

+ 16 - 0
sites/all/modules/contrib/search/search_api/tests/search_api_test_2.info

@@ -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"

+ 136 - 0
sites/all/modules/contrib/search/search_api/tests/search_api_test_2.module

@@ -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();
+  }
+}

+ 39 - 1
sites/all/modules/contrib/search/search_api_solr_overrides/README.txt

@@ -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 - 3
sites/all/modules/contrib/search/search_api_solr_overrides/search_api_solr_overrides.info

@@ -3,9 +3,9 @@ description = Provides site specific overrides for search_api_solr configuration
 core = 7.x
 core = 7.x
 dependencies[] = search_api_solr
 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"
 core = "7.x"
 project = "search_api_solr_overrides"
 project = "search_api_solr_overrides"
-datestamp = "1380626863"
+datestamp = "1497319149"
 
 

+ 5 - 5
sites/all/modules/contrib/search/search_api_solr_overrides/search_api_solr_overrides.module

@@ -15,7 +15,7 @@
  * Example:
  * Example:
  * $conf['search_api_solr_overrides'] = array(
  * $conf['search_api_solr_overrides'] = array(
  *   'solr-server-id' => array(
  *   'solr-server-id' => array(
- *     'name' => t('Solr Server (Overridden)'),
+ *     'name' => 'Solr Server (Overridden)',
  *       'options' => array(
  *       'options' => array(
  *         'host' => '127.0.0.1',
  *         'host' => '127.0.0.1',
  *         'port' => 8983,
  *         'port' => 8983,
@@ -32,7 +32,7 @@ function search_api_solr_overrides_search_api_server_load($servers) {
   $overrides = variable_get('search_api_solr_overrides', FALSE);
   $overrides = variable_get('search_api_solr_overrides', FALSE);
 
 
   // Ensure the is information provided.
   // Ensure the is information provided.
-  if (empty($overrides)) {
+  if (empty($overrides) || !is_array($overrides)) {
     return;
     return;
   }
   }
 
 
@@ -41,12 +41,12 @@ function search_api_solr_overrides_search_api_server_load($servers) {
     // Check to see if the server config exists.
     // Check to see if the server config exists.
     if (!empty($servers[$id])) {
     if (!empty($servers[$id])) {
       foreach ($servers[$id] as $key => $field) {
       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;
           continue;
         }
         }
 
 
-        // Check for if the field is an array.
+        // Check if the field contains an array.
         if (is_array($field)) {
         if (is_array($field)) {
           $servers[$id]->$key = array_merge($servers[$id]->$key, $override[$key]);
           $servers[$id]->$key = array_merge($servers[$id]->$key, $override[$key]);
         }
         }