Bladeren bron

updated search_api modules as indexation was not working localy
had to be tested in production

Bachir Soussi Chiadmi 7 jaren geleden
bovenliggende
commit
28c7a0965a
53 gewijzigde bestanden met toevoegingen van 4165 en 186 verwijderingen
  1. 38 0
      sites/all/modules/contrib/search/search_api/CHANGELOG.txt
  2. 4 0
      sites/all/modules/contrib/search/search_api/README.txt
  3. 21 1
      sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/adapter.inc
  4. 18 5
      sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_date.inc
  5. 3 3
      sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/search_api_facetapi.info
  6. 8 6
      sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/search_api_facetapi.install
  7. 2 2
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/display_facet_block.inc
  8. 10 0
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_fulltext.inc
  9. 2 2
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_taxonomy_term.inc
  10. 4 2
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_user.inc
  11. 1 1
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/plugin_cache.inc
  12. 3 0
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/query.inc
  13. 3 3
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/search_api_views.info
  14. 4 1
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/search_api_views.module
  15. 15 0
      sites/all/modules/contrib/search/search_api/includes/callback.inc
  16. 5 0
      sites/all/modules/contrib/search/search_api/includes/callback_add_aggregation.inc
  17. 0 15
      sites/all/modules/contrib/search/search_api/includes/callback_bundle_filter.inc
  18. 16 3
      sites/all/modules/contrib/search/search_api/includes/callback_role_filter.inc
  19. 20 7
      sites/all/modules/contrib/search/search_api/includes/datasource.inc
  20. 2 2
      sites/all/modules/contrib/search/search_api/includes/datasource_entity.inc
  21. 1 1
      sites/all/modules/contrib/search/search_api/includes/datasource_external.inc
  22. 3 0
      sites/all/modules/contrib/search/search_api/includes/datasource_multiple.inc
  23. 29 16
      sites/all/modules/contrib/search/search_api/includes/processor_highlight.inc
  24. 732 0
      sites/all/modules/contrib/search/search_api/includes/processor_stemmer.inc
  25. 28 1
      sites/all/modules/contrib/search/search_api/includes/query.inc
  26. 4 3
      sites/all/modules/contrib/search/search_api/search_api.info
  27. 1 1
      sites/all/modules/contrib/search/search_api/search_api.install
  28. 8 2
      sites/all/modules/contrib/search/search_api/search_api.module
  29. 1 1
      sites/all/modules/contrib/search/search_api/search_api.rules.inc
  30. 3 3
      sites/all/modules/contrib/search/search_api/tests/search_api_test.info
  31. 22 4
      sites/all/modules/contrib/search/search_api_saved_searches/CHANGELOG.txt
  32. 8 3
      sites/all/modules/contrib/search/search_api_saved_searches/search_api_saved_searches.admin.inc
  33. 3 3
      sites/all/modules/contrib/search/search_api_saved_searches/search_api_saved_searches.info
  34. 72 2
      sites/all/modules/contrib/search/search_api_saved_searches/search_api_saved_searches.install
  35. 118 41
      sites/all/modules/contrib/search/search_api_saved_searches/search_api_saved_searches.module
  36. 94 17
      sites/all/modules/contrib/search/search_api_saved_searches/search_api_saved_searches.pages.inc
  37. 6 1
      sites/all/modules/contrib/search/search_api_saved_searches/search_api_saved_searches.search_entity.inc
  38. 3 3
      sites/all/modules/contrib/search/search_api_saved_searches/search_api_saved_searches_i18n/search_api_saved_searches_i18n.info
  39. 26 2
      sites/all/modules/contrib/search/search_api_solr/CHANGELOG.txt
  40. 83 21
      sites/all/modules/contrib/search/search_api_solr/includes/service.inc
  41. 36 5
      sites/all/modules/contrib/search/search_api_solr/includes/solr_connection.inc
  42. 3 3
      sites/all/modules/contrib/search/search_api_solr/search_api_solr.info
  43. 31 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/6.x/elevate.xml
  44. 14 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/6.x/mapping-ISOLatin1Accent.txt
  45. 7 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/6.x/protwords.txt
  46. 693 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/6.x/schema.xml
  47. 23 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/6.x/schema_extra_fields.xml
  48. 34 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/6.x/schema_extra_types.xml
  49. 1793 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/6.x/solrconfig.xml
  50. 80 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/6.x/solrconfig_extra.xml
  51. 20 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/6.x/solrcore.properties
  52. 4 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/6.x/stopwords.txt
  53. 3 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/6.x/synonyms.txt

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

@@ -1,3 +1,41 @@
+Search API 1.21 (2017-02-23):
+-----------------------------
+- #2780341 by Berdir: Fixed passing of custom ranges to date facets.
+- #2765317 by JorgenSandstrom, NWOM, drunken monkey: Added a "Last" aggregation
+  type.
+- #2842856 by drunken monkey: Fixed language filters for "Multiple types"
+  indexes.
+- #2844990 by drunken monkey: Made the "Role filter" data alteration available
+  for multi-type indexes.
+- #2837745 by drunken monkey, klausi: Fixed NULL tags on old serialized queries.
+- #2833482 by drunken monkey: Fixed undefined constant when uninstalling facets
+  module.
+- #2840261 by alan-ps: Fixed usage of outdated hash functions.
+- #1670420 by kyletaylored, dorficus, drunken monkey: Fixed potential fatal
+  error in facet adapter's getSearchKeys() method.
+- #2838075 by dsnopek: Fixed possible race condition in
+  hook_system_info_alter().
+- #2836687 by sarthak drupal: Fixed one doc comment typo.
+- #2632880 by drunken monkey, donquixote: Added possibility to change indexed
+  bundles on disabled indexes.
+- #2828380 by jansete: Fixed taxonomy term access tag in Views filter.
+- #2827717 by Fabien.Godineau, drunken monkey: Fixed disabling of search views
+  when reverting an index.
+- #2822836 by prince_zyxware: Fixed some Drupal coding standards violations.
+- #2822145 by drunken monkey: Fixed problem with phrase search in Views
+  fulltext filter.
+- #2778261 by drunken monkey, BAHbKA: Fixed "Index items immediately"
+  functionality for unindexed items.
+- #2358065 by Jelle_S, graper, drunken monkey: Added the option for
+  highlighting of partial matches to the processor.
+- #2779159 by mark_fullmer, drunken monkey: Added a Stemmer processor.
+- #2649412 by relaxnow, GoZ: Added support for minimum granularity to date
+  facets.
+- #2769021 by Plazik, drunken monkey: Added the generated Search API query to
+  the Views preview.
+- #2769877 by mfernea: Fixed database exception when filtering for anonymous
+  user.
+
 Search API 1.20 (2016-07-21):
 -----------------------------
 - #2731103 by drunken monkey: Fixed the default value for the taxonomy term

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

@@ -385,6 +385,10 @@ Included components
     Enables the admin to specify a stopwords file, the words contained in which
     will be filtered out of the text data indexed. This can be used to exclude
     too common words from indexing, for servers not supporting this natively.
+  * Stem words
+    Uses the PorterStemmer method to reduce words to stems. A search for
+    "garden" will return results for "gardening" and "garden," as will a search
+    for "gardening."
 
 - Additional modules
 

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

@@ -192,6 +192,12 @@ class SearchApiFacetapiAdapter extends FacetapiAdapter {
    */
   public function getSearchKeys() {
     $search = $this->getCurrentSearch();
+
+    // If the search is empty then there's no reason to continue.
+    if (!$search) {
+      return NULL;
+    }
+
     $keys = $search[0]->getOriginalKeys();
     if (is_array($keys)) {
       // This will happen nearly never when displaying the search keys to the
@@ -281,10 +287,24 @@ class SearchApiFacetapiAdapter extends FacetapiAdapter {
 
       // Date facets don't support the "OR" operator (for now).
       $form['global']['operator']['#access'] = FALSE;
+
+      $default_value = FACETAPI_DATE_YEAR;
+      if (isset($options['date_granularity_min'])) {
+        $default_value = $options['date_granularity_min'];
+      }
+      $form['global']['date_granularity_min'] = array(
+        '#type' => 'select',
+        '#title' => t('Minimum granularity'),
+        '#description' => t('Determine the minimum drill-down level to start at'),
+        '#prefix' => '<div class="facetapi-global-setting">',
+        '#suffix' => '</div>',
+        '#options' => $granularity_options,
+        '#default_value' => $default_value,
+      );
     }
 
     // Add an "Exclude" option for terms.
-    if(!empty($facet['query types']) && in_array('term', $facet['query types'])) {
+    if (!empty($facet['query types']) && in_array('term', $facet['query types'])) {
       $form['global']['operator']['#weight'] = -2;
       unset($form['global']['operator']['#suffix']);
       $form['global']['exclude'] = array(

+ 18 - 5
sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_date.inc

@@ -76,7 +76,7 @@ class SearchApiFacetapiDate extends SearchApiFacetapiTerm implements FacetapiQue
    */
   protected function createRangeFilter($value) {
     // Ignore any filters passed directly from the server (range or missing).
-    if (!$value || $value == '!' || (!ctype_digit($value[0]) && preg_match('/^[\[(][^ ]+ [^ ]+[])]$/', $value))) {
+    if (!$value || $value == '!' || (!ctype_digit($value[0]) && preg_match('/^[\[(][^ ]+ TO [^ ]+[\])]$/', $value))) {
       return $value ? $value : NULL;
     }
 
@@ -245,9 +245,19 @@ class SearchApiFacetapiDate extends SearchApiFacetapiTerm implements FacetapiQue
       }
     }
 
-    // Get the finest level of detail we're allowed to drill down to.
     $settings = $facet->getSettings()->settings;
-    $max_granularity = isset($settings['date_granularity']) ? $settings['date_granularity'] : FACETAPI_DATE_MINUTE;
+
+    // Get the finest level of detail we're allowed to drill down to.
+    $max_granularity = FACETAPI_DATE_MINUTE;
+    if (isset($settings['date_granularity'])) {
+      $max_granularity = $settings['date_granularity'];
+    }
+
+    // Get the coarsest level of detail we're allowed to start at.
+    $min_granularity = FACETAPI_DATE_YEAR;
+    if (isset($settings['date_granularity_min'])) {
+      $min_granularity = $settings['date_granularity_min'];
+    }
 
     // Gets active facets, starts building hierarchy.
     $parent = $granularity = NULL;
@@ -301,11 +311,14 @@ class SearchApiFacetapiDate extends SearchApiFacetapiTerm implements FacetapiQue
           FACETAPI_DATE_MINUTE => 2,
           FACETAPI_DATE_SECOND => 1,
         );
-        // Gets gap numbers for both the gap and minimum gap, checks if the gap
-        // is within the limit set by the $granularity parameter.
+        // Gets gap numbers for both the gap, minimum and maximum gap, checks if
+        // the gap is within the limit set by the $granularity parameters.
         if ($gap_numbers[$granularity] < $gap_numbers[$max_granularity]) {
           $granularity = $max_granularity;
         }
+        if ($gap_numbers[$granularity] > $gap_numbers[$min_granularity]) {
+          $granularity = $min_granularity;
+        }
       }
       else {
         $granularity = $max_granularity;

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

@@ -9,9 +9,9 @@ files[] = plugins/facetapi/adapter.inc
 files[] = plugins/facetapi/query_type_term.inc
 files[] = plugins/facetapi/query_type_date.inc
 
-; Information added by Drupal.org packaging script on 2016-07-21
-version = "7.x-1.20"
+; Information added by Drupal.org packaging script on 2017-02-23
+version = "7.x-1.21"
 core = "7.x"
 project = "search_api"
-datestamp = "1469117342"
+datestamp = "1487844493"
 

+ 8 - 6
sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/search_api_facetapi.install

@@ -22,12 +22,14 @@ function search_api_facetapi_install() {
  */
 function search_api_facetapi_uninstall() {
   variable_del('search_api_facets_search_ids');
-  variable_del('date_format_search_api_facetapi_' . FACETAPI_DATE_YEAR);
-  variable_del('date_format_search_api_facetapi_' . FACETAPI_DATE_MONTH);
-  variable_del('date_format_search_api_facetapi_' . FACETAPI_DATE_DAY);
-  variable_del('date_format_search_api_facetapi_' . FACETAPI_DATE_HOUR);
-  variable_del('date_format_search_api_facetapi_' . FACETAPI_DATE_MINUTE);
-  variable_del('date_format_search_api_facetapi_' . FACETAPI_DATE_SECOND);
+  // We have to use the literal values here, as the Facet API module could have
+  // already been disabled at this point.
+  variable_del('date_format_search_api_facetapi_YEAR');
+  variable_del('date_format_search_api_facetapi_MONTH');
+  variable_del('date_format_search_api_facetapi_DAY');
+  variable_del('date_format_search_api_facetapi_HOUR');
+  variable_del('date_format_search_api_facetapi_MINUTE');
+  variable_del('date_format_search_api_facetapi_SECOND');
 }
 
 /**

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

@@ -151,7 +151,7 @@ class SearchApiViewsFacetsBlockDisplay extends views_plugin_display_block {
     }
   }
 
-  public function query(){
+  public function query() {
     parent::query();
 
     $facet_field = $this->get_option('facet_field');
@@ -291,7 +291,7 @@ class SearchApiViewsFacetsBlockDisplay extends views_plugin_display_block {
     );
   }
 
-  public function execute(){
+  public function execute() {
     $info['content'] = $this->render();
     $info['content']['more'] = $this->render_more_link();
     $info['subject'] = filter_xss_admin($this->view->get_title());

+ 10 - 0
sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_fulltext.inc

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

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

@@ -93,7 +93,7 @@ class SearchApiViewsHandlerFilterTaxonomyTerm extends SearchApiViewsHandlerFilte
         $query->orderby('tv.name');
         $query->orderby('td.weight');
         $query->orderby('td.name');
-        $query->addTag('term_access');
+        $query->addTag('taxonomy_term_access');
         if ($vocabulary) {
           $query->condition('tv.machine_name', $vocabulary->machine_name);
         }
@@ -272,7 +272,7 @@ class SearchApiViewsHandlerFilterTaxonomyTerm extends SearchApiViewsHandlerFilte
     if (!empty($this->definition['vocabulary'])) {
       $query->condition('tv.machine_name', $this->definition['vocabulary']);
     }
-    $query->addTag('term_access');
+    $query->addTag('taxonomy_term_access');
     $result = $query->execute();
     foreach ($result as $term) {
       unset($missing[strtolower($term->name)]);

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

@@ -29,8 +29,10 @@ class SearchApiViewsHandlerFilterUser extends SearchApiViewsHandlerFilterEntity
   protected function ids_to_strings(array $ids) {
     $names = array();
     $args[':uids'] = array_filter($ids);
-    $result = db_query("SELECT uid, name FROM {users} u WHERE uid IN (:uids)", $args);
-    $result = $result->fetchAllKeyed();
+    if ($args[':uids']) {
+      $result = db_query('SELECT uid, name FROM {users} u WHERE uid IN (:uids)', $args);
+      $result = $result->fetchAllKeyed();
+    }
     foreach ($ids as $uid) {
       if (!$uid) {
         $names[] = variable_get('anonymous', t('Anonymous'));

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

@@ -103,7 +103,7 @@ class SearchApiViewsCache extends views_plugin_cache_time {
         $key_data['exposed_info'] = $_GET['exposed_info'];
       }
     }
-    $key = md5(serialize($key_data));
+    $key = drupal_hash_base64(serialize($key_data));
     return $key;
   }
 

+ 3 - 0
sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/query.inc

@@ -310,6 +310,9 @@ class SearchApiViewsQuery extends views_plugin_query {
     if (!empty($this->view->override_path) && strpos(current_path(), $this->view->override_path) !== 0) {
       $this->query->setOption('search_api_base_path', $this->view->override_path);
     }
+
+    // Save query information for Views UI.
+    $view->build_info['query'] = (string) $this->query;
   }
 
   /**

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

@@ -27,9 +27,9 @@ files[] = includes/handler_sort.inc
 files[] = includes/plugin_cache.inc
 files[] = includes/query.inc
 
-; Information added by Drupal.org packaging script on 2016-07-21
-version = "7.x-1.20"
+; Information added by Drupal.org packaging script on 2017-02-23
+version = "7.x-1.21"
 core = "7.x"
 project = "search_api"
-datestamp = "1469117342"
+datestamp = "1487844493"
 

+ 4 - 1
sites/all/modules/contrib/search/search_api/contrib/search_api_views/search_api_views.module

@@ -45,7 +45,10 @@ function search_api_views_search_api_index_update(SearchApiIndex $index) {
  * Implements hook_search_api_index_delete().
  */
 function search_api_views_search_api_index_delete(SearchApiIndex $index) {
-  _search_api_views_index_unavailable($index);
+  // Only do this if this is a "real" deletion, no revert.
+  if (!$index->hasStatus(ENTITY_IN_CODE)) {
+    _search_api_views_index_unavailable($index);
+  }
 }
 
 /**

+ 15 - 0
sites/all/modules/contrib/search/search_api/includes/callback.inc

@@ -182,4 +182,19 @@ abstract class SearchApiAbstractAlterCallback implements SearchApiAlterCallbackI
     return array();
   }
 
+  /**
+   * Determines whether the given index contains multiple types of entities.
+   *
+   * @param SearchApiIndex|null $index
+   *   (optional) The index to examine. Defaults to the index set for this
+   *   plugin.
+   *
+   * @return bool
+   *   TRUE if the index is a multi-entity index, FALSE otherwise.
+   */
+  protected function isMultiEntityIndex(SearchApiIndex $index = NULL) {
+    $index = $index ? $index : $this->index;
+    return $index->datasource() instanceof SearchApiCombinedEntityDataSourceController;
+  }
+
 }

+ 5 - 0
sites/all/modules/contrib/search/search_api/includes/callback_add_aggregation.inc

@@ -209,6 +209,8 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
           return $a;
         }
         return drupal_substr($b, 0, 1);
+      case 'last':
+        return isset($b) ? $b : $a;
       case 'list':
         if (!isset($a)) {
           $a = array();
@@ -288,6 +290,7 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
           'min' => t('Minimum'),
           'first' => t('First'),
           'first_char' => t('First letter'),
+          'last' => t('Last'),
           'list' => t('List'),
         );
       case 'type':
@@ -299,6 +302,7 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
           'min' => 'integer',
           'first' => 'string',
           'first_char' => 'string',
+          'last' => 'string',
           'list' => 'list<string>',
         );
       case 'description':
@@ -310,6 +314,7 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
           'min' => t('The Minimum aggregation computes the numerically smallest contained field value.'),
           'first' => t('The First aggregation will simply keep the first encountered field value. This is helpful foremost when you know that a list field will only have a single value.'),
           'first_char' => t('The "First letter" aggregation uses just the first letter of the first encountered field value as the aggregated value. This can, for example, be used to build a Glossary view.'),
+          'last' => t('The Last aggregation will simply keep the last encountered field value.'),
           'list' => t('The List aggregation collects all field values into a multi-valued field containing all values.'),
         );
     }

+ 0 - 15
sites/all/modules/contrib/search/search_api/includes/callback_bundle_filter.inc

@@ -132,19 +132,4 @@ class SearchApiAlterBundleFilter extends SearchApiAbstractAlterCallback {
     return !empty($entity_info['entity keys']['bundle']) && !empty($entity_info['bundles']);
   }
 
-  /**
-   * Determines whether the given index contains multiple types of entities.
-   *
-   * @param SearchApiIndex|null $index
-   *   (optional) The index to examine. Defaults to the index set for this
-   *   plugin.
-   *
-   * @return bool
-   *   TRUE if the index is a multi-entity index, FALSE otherwise.
-   */
-  protected function isMultiEntityIndex(SearchApiIndex $index = NULL) {
-    $index = $index ? $index : $this->index;
-    return $index->datasource() instanceof SearchApiCombinedEntityDataSourceController;
-  }
-
 }

+ 16 - 3
sites/all/modules/contrib/search/search_api/includes/callback_role_filter.inc

@@ -16,6 +16,9 @@ class SearchApiAlterRoleFilter extends SearchApiAbstractAlterCallback {
    * This plugin only supports indexes containing users.
    */
   public function supportsIndex(SearchApiIndex $index) {
+    if ($this->isMultiEntityIndex($index)) {
+      return in_array('user', $index->options['datasource']['types']);
+    }
     return $index->getEntityType() == 'user';
   }
 
@@ -23,10 +26,20 @@ class SearchApiAlterRoleFilter extends SearchApiAbstractAlterCallback {
    * Implements SearchApiAlterCallbackInterface::alterItems().
    */
   public function alterItems(array &$items) {
-    $roles = $this->options['roles'];
+    $selected_roles = $this->options['roles'];
     $default = (bool) $this->options['default'];
-    foreach ($items as $id => $account) {
-      $role_match = (count(array_diff_key($account->roles, $roles)) !== count($account->roles));
+    $multi_types = $this->isMultiEntityIndex($this->index);
+    foreach ($items as $id => $item) {
+      if ($multi_types) {
+        if ($item->item_type !== 'user') {
+          continue;
+        }
+        $item_roles = $item->user->roles;
+      }
+      else {
+        $item_roles = $item->roles;
+      }
+      $role_match = (count(array_diff_key($item_roles, $selected_roles)) !== count($item_roles));
       if ($role_match === $default) {
         unset($items[$id]);
       }

+ 20 - 7
sites/all/modules/contrib/search/search_api/includes/datasource.inc

@@ -626,8 +626,7 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
       return NULL;
     }
 
-    $ret = array();
-
+    $indexes_by_id = array();
     foreach ($indexes as $index) {
       $this->checkIndex($index);
       $update = db_update($this->table)
@@ -639,12 +638,26 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
       if ($item_ids !== FALSE) {
         $update->condition($this->itemIdColumn, $item_ids, 'IN');
       }
-      if ($update->execute()) {
-        $ret[] = $index;
-      }
+      $update->execute();
+      $indexes_by_id[$index->id] = $index;
     }
 
-    return $ret;
+    // Determine and return the indexes with any changed items. If $item_ids is
+    // FALSE, all items are marked as changed and, thus, all indexes will be
+    // affected (unless they don't have any items, but no real point in treating
+    // that special case).
+    if ($item_ids !== FALSE) {
+      $indexes_with_items = db_select($this->table, 't')
+        ->fields('t', array($this->indexIdColumn))
+        ->distinct()
+        ->condition($this->indexIdColumn, array_keys($indexes_by_id), 'IN')
+        ->condition($this->itemIdColumn, $item_ids, 'IN')
+        ->execute()
+        ->fetchCol();
+      return array_intersect_key($indexes_by_id, array_flip($indexes_with_items));
+    }
+
+    return NULL;
   }
 
   /**
@@ -715,7 +728,7 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
     }
     $this->checkIndex($index);
     $select = db_select($this->table, 't');
-    $select->addField('t', 'item_id');
+    $select->addField('t', $this->itemIdColumn);
     $select->condition($this->indexIdColumn, $index->id);
     $select->condition($this->changedColumn, 0, '>');
     $select->orderBy($this->changedColumn, 'ASC');

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

@@ -278,10 +278,10 @@ class SearchApiEntityDataSourceController extends SearchApiAbstractDataSourceCon
     $form['bundles'] = array(
       '#type' => 'checkboxes',
       '#title' => t('Bundles'),
-      '#description' => t('Restrict the entity bundles that will be included in this index. Leave blank to include all bundles. This setting cannot be changed for existing indexes.'),
+      '#description' => t('Restrict the entity bundles that will be included in this index. Leave blank to include all bundles. This setting cannot be changed for enabled indexes.'),
       '#options' => array_map('check_plain', $options),
       '#attributes' => array('class' => array('search-api-checkboxes-list')),
-      '#disabled' => !empty($form_state['index']),
+      '#disabled' => !empty($form_state['index']) && $form_state['index']->enabled,
     );
     if (!empty($form_state['index']->options['datasource'])) {
       $form['bundles']['#default_value'] = drupal_map_assoc($form_state['index']->options['datasource']['bundles']);

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

@@ -49,7 +49,7 @@ class SearchApiExternalDataSourceController extends SearchApiAbstractDataSourceC
    * loadable, specify a function here.
    *
    * @param array $ids
-   *   The IDs of the items to laod.
+   *   The IDs of the items to load.
    *
    * @return array
    *   The loaded items, keyed by ID.

+ 3 - 0
sites/all/modules/contrib/search/search_api/includes/datasource_multiple.inc

@@ -44,6 +44,9 @@ class SearchApiCombinedEntityDataSourceController extends SearchApiAbstractDataS
         $item->item_type = $type;
         $item->item_entity_id = $entity_id;
         $item->item_bundle = NULL;
+        // Add the item language so the "search_api_language" field will work
+        // correctly.
+        $item->language = isset($entity->language) ? $entity->language : NULL;
         try {
           list(, , $bundle) = entity_extract_ids($type, $entity);
           $item->item_bundle = $bundle ? "$type:$bundle" : NULL;

+ 29 - 16
sites/all/modules/contrib/search/search_api/includes/processor_highlight.inc

@@ -51,6 +51,7 @@ class SearchApiHighlight extends SearchApiAbstractProcessor {
       'excerpt' => TRUE,
       'excerpt_length' => 256,
       'highlight' => 'always',
+      'highlight_partial' => FALSE,
       'exclude_fields' => array(),
     );
 
@@ -114,6 +115,13 @@ class SearchApiHighlight extends SearchApiAbstractProcessor {
       '#default_value' => $this->options['highlight'],
     );
 
+    $form['highlight_partial'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Highlight partial matches'),
+      '#description' => t('When enabled, matches in parts of words will be highlighted as well.'),
+      '#default_value' => $this->options['highlight_partial'],
+    );
+
     return $form;
   }
 
@@ -322,9 +330,9 @@ class SearchApiHighlight extends SearchApiAbstractProcessor {
     $ranges = array();
     $included = array();
     $length = 0;
-    $workkeys = $keys;
-    while ($length < $this->options['excerpt_length'] && count($workkeys)) {
-      foreach ($workkeys as $k => $key) {
+    $work_keys = $keys;
+    while ($length < $this->options['excerpt_length'] && $work_keys) {
+      foreach ($work_keys as $k => $key) {
         if ($length >= $this->options['excerpt_length']) {
           break;
         }
@@ -336,8 +344,14 @@ class SearchApiHighlight extends SearchApiAbstractProcessor {
         // Locate a keyword (position $p, always >0 because $text starts with a
         // space).
         $p = 0;
-        if (preg_match('/' . self::$boundary . preg_quote($key, '/') . self::$boundary . '/iu', $text, $match, PREG_OFFSET_CAPTURE, $included[$key])) {
-          $p = $match[0][1];
+        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];
+          }
+        }
+        else {
+          $p = stripos($text, $key, $included[$key]);
         }
         // Now locate a space in front (position $q) and behind it (position $s),
         // leaving about 60 characters extra before and after for context.
@@ -352,18 +366,13 @@ class SearchApiHighlight extends SearchApiAbstractProcessor {
               $ranges[$q] = $p + $s;
               $length += $p + $s - $q;
               $included[$key] = $p + 1;
-            }
-            else {
-              unset($workkeys[$k]);
+              continue;
             }
           }
-          else {
-            unset($workkeys[$k]);
-          }
-        }
-        else {
-          unset($workkeys[$k]);
         }
+        // Unless we got a match above, we don't need to look for this key any
+        // more.
+        unset($work_keys[$k]);
       }
     }
 
@@ -437,10 +446,14 @@ class SearchApiHighlight extends SearchApiAbstractProcessor {
       }
       return implode('', $texts);
     }
-    $replace = $this->options['prefix'] . '\0' . $this->options['suffix'];
 
     $keys = implode('|', array_map('preg_quote', $keys, array_fill(0, count($keys), '/')));
-    $text = preg_replace('/' . self::$boundary . '(' . $keys . ')' . self::$boundary . '/iu', $replace, ' ' . $text . ' ');
+    // If "Highlight partial matches" is disabled, we only want to highlight
+    // matches that are complete words. Otherwise, we want all of them.
+    $boundary = empty($this->options['highlight_partial']) ? self::$boundary : '';
+    $regex = '/' . $boundary . '(?:' . $keys . ')' . $boundary . '/iu';
+    $replace = $this->options['prefix'] . '\0' . $this->options['suffix'];
+    $text = preg_replace($regex, $replace, ' ' . $text . ' ');
     return substr($text, 1, -1);
   }
 

+ 732 - 0
sites/all/modules/contrib/search/search_api/includes/processor_stemmer.inc

@@ -0,0 +1,732 @@
+<?php
+
+/**
+ * @file
+ * Contains SearchApiPorterStemmer and SearchApiPorter2.
+ */
+
+/**
+ * Stems words to their roots.
+ */
+class SearchApiPorterStemmer extends SearchApiAbstractProcessor {
+
+  /**
+   * Static cache for already generated stems.
+   *
+   * @var array
+   */
+  protected $stems = array();
+
+  /**
+   * {@inheritdoc}
+   */
+  public function configurationForm() {
+    $form = parent::configurationForm();
+
+    $args = array(
+      '!algorithm' => url('https://github.com/markfullmer/porter2'),
+      '!exclusions' => url('https://github.com/markfullmer/porter2#user-content-custom-exclusions'),
+    );
+    $form += array(
+      'help' => array(
+        '#markup' => '<p>' . t('Optionally, provide an exclusion list to override the stemmer algorithm. Read about the <a href="@algorithm">algorithm</a> and <a href="@exclusions">exclusions</a>.', $args) . '</p>',
+      ),
+      'exceptions' => array(
+        '#type' => 'textarea',
+        '#title' => t('Exceptions'),
+        '#description' => t('Enter exceptions in the form of WORD=STEM, where "WORD" is the term entered and "STEM" is the resulting stem. List each exception on a separate line.'),
+        '#default_value' => "texan=texa",
+      ),
+    );
+
+    if (!empty($this->options['exceptions'])) {
+      $form['exceptions']['#default_value'] = $this->options['exceptions'];
+    }
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function process(&$value) {
+    // Load custom exceptions.
+    $exceptions = $this->getExceptions();
+
+    $words = preg_split('/[^\p{L}\p{N}]+/u', $value, -1 , PREG_SPLIT_DELIM_CAPTURE);
+    $stemmed = array();
+    foreach ($words as $i => $word) {
+      if ($i % 2 == 0 && strlen($word)) {
+        if (!isset($this->stems[$word])) {
+          $stem = new SearchApiPorter2($word, $exceptions);
+          $this->stems[$word] = $stem->stem();
+        }
+        $stemmed[] = $this->stems[$word];
+      }
+      else {
+        $stemmed[] = $word;
+      }
+    }
+    $value = implode('', $stemmed);
+  }
+
+  /**
+   * Retrieves the processor's configured exceptions.
+   *
+   * @return string[]
+   *   An associative array of exceptions, with words as keys and stems as their
+   *   replacements.
+   */
+  protected function getExceptions() {
+    if (!empty($this->options['exceptions'])) {
+      $exceptions = parse_ini_string($this->options['exceptions'], TRUE);
+      return is_array($exceptions) ? $exceptions : array();
+    }
+    return array();
+  }
+
+}
+
+/**
+ * Implements the Porter2 stemming algorithm.
+ *
+ * @see https://github.com/markfullmer/porter2
+ */
+class SearchApiPorter2 {
+
+  /**
+   * The word being stemmed.
+   *
+   * @var string
+   */
+  protected $word;
+
+  /**
+   * The R1 of the word.
+   *
+   * @var int
+   *
+   * @see http://snowball.tartarus.org/texts/r1r2.html.
+   */
+  protected $r1;
+
+  /**
+   * The R2 of the word.
+   *
+   * @var int
+   *
+   * @see http://snowball.tartarus.org/texts/r1r2.html.
+   */
+  protected $r2;
+
+  /**
+   * List of exceptions to be used.
+   *
+   * @var string[]
+   */
+  protected $exceptions = array();
+
+  /**
+   * Constructs a SearchApiPorter2 object.
+   *
+   * @param string $word
+   *   The word to stem.
+   * @param string[] $custom_exceptions
+   *   (optional) A custom list of exceptions.
+   */
+  public function __construct($word, $custom_exceptions = array()) {
+    $this->word = $word;
+    $this->exceptions = $custom_exceptions + array(
+      'skis' => 'ski',
+      'skies' => 'sky',
+      'dying' => 'die',
+      'lying' => 'lie',
+      'tying' => 'tie',
+      'idly' => 'idl',
+      'gently' => 'gentl',
+      'ugly' => 'ugli',
+      'early' => 'earli',
+      'only' => 'onli',
+      'singly' => 'singl',
+      'sky' => 'sky',
+      'news' => 'news',
+      'howe' => 'howe',
+      'atlas' => 'atlas',
+      'cosmos' => 'cosmos',
+      'bias' => 'bias',
+      'andes' => 'andes',
+    );
+
+    // Set initial y, or y after a vowel, to Y.
+    $inc = 0;
+    while ($inc <= $this->length()) {
+      if (substr($this->word, $inc, 1) === 'y' && ($inc == 0 || $this->isVowel($inc - 1))) {
+        $this->word = substr_replace($this->word, 'Y', $inc, 1);
+
+      }
+      $inc++;
+    }
+    // Establish the regions R1 and R2. See function R().
+    $this->r1 = $this->R(1);
+    $this->r2 = $this->R(2);
+  }
+
+  /**
+   * Computes the stem of the word.
+   *
+   * @return string
+   *   The word's stem.
+   */
+  public function stem() {
+    // Ignore exceptions & words that are two letters or less.
+    if ($this->exceptions() || $this->length() <= 2) {
+      return strtolower($this->word);
+    }
+    else {
+      $this->step0();
+      $this->step1a();
+      $this->step1b();
+      $this->step1c();
+      $this->step2();
+      $this->step3();
+      $this->step4();
+      $this->step5();
+    }
+    return strtolower($this->word);
+  }
+
+  /**
+   * Determines whether the word is contained in our list of exceptions.
+   *
+   * If so, the $word property is changed to the stem listed in the exceptions.
+   *
+   * @return bool
+   *   TRUE if the word was an exception, FALSE otherwise.
+   */
+  protected function exceptions() {
+    if (isset($this->exceptions[$this->word])) {
+      $this->word = $this->exceptions[$this->word];
+      return TRUE;
+    }
+    return FALSE;
+  }
+
+  /**
+   * Searches for the longest among the "s" suffixes and removes it.
+   *
+   * Implements step 0 of the Porter2 algorithm.
+   */
+  protected function step0() {
+    $found = FALSE;
+    $checks = array("'s'", "'s", "'");
+    foreach ($checks as $check) {
+      if (!$found && $this->hasEnding($check)) {
+        $this->removeEnding($check);
+        $found = TRUE;
+      }
+    }
+  }
+
+  /**
+   * Handles various suffixes, of which the longest is replaced.
+   *
+   * Implements step 1a of the Porter2 algorithm.
+   */
+  protected function step1a() {
+    $found = FALSE;
+    if ($this->hasEnding('sses')) {
+      $this->removeEnding('sses');
+      $this->addEnding('ss');
+      $found = TRUE;
+    }
+    $checks = array('ied', 'ies');
+    foreach ($checks as $check) {
+      if (!$found && $this->hasEnding($check)) {
+        $length = $this->length();
+        $this->removeEnding($check);
+        if ($length > 4) {
+          $this->addEnding('i');
+        }
+        else {
+          $this->addEnding('ie');
+        }
+        $found = TRUE;
+      }
+    }
+    if ($this->hasEnding('us') || $this->hasEnding('ss')) {
+      $found = TRUE;
+    }
+    // Delete if preceding word part has a vowel not immediately before the s.
+    if (!$found && $this->hasEnding('s') && $this->containsVowel(substr($this->word, 0, -2))) {
+      $this->removeEnding('s');
+    }
+  }
+
+  /**
+   * Handles various suffixes, of which the longest is replaced.
+   *
+   * Implements step 1b of the Porter2 algorithm.
+   */
+  protected function step1b() {
+    $exceptions = array(
+      'inning',
+      'outing',
+      'canning',
+      'herring',
+      'earring',
+      'proceed',
+      'exceed',
+      'succeed',
+    );
+    if (in_array($this->word, $exceptions)) {
+      return;
+    }
+    $checks = array('eedly', 'eed');
+    foreach ($checks as $check) {
+      if ($this->hasEnding($check)) {
+        if ($this->r1 !== $this->length()) {
+          $this->removeEnding($check);
+          $this->addEnding('ee');
+        }
+        return;
+      }
+    }
+    $checks = array('ingly', 'edly', 'ing', 'ed');
+    $second_endings = array('at', 'bl', 'iz');
+    foreach ($checks as $check) {
+      // If the ending is present and the previous part contains a vowel.
+      if ($this->hasEnding($check) && $this->containsVowel(substr($this->word, 0, -strlen($check)))) {
+        $this->removeEnding($check);
+        foreach ($second_endings as $ending) {
+          if ($this->hasEnding($ending)) {
+            $this->addEnding('e');
+            return;
+          }
+        }
+        // If the word ends with a double, remove the last letter.
+        $found = $this->removeDoubles();
+        // If the word is short, add e (so hop -> hope).
+        if (!$found && ($this->isShort())) {
+          $this->addEnding('e');
+        }
+        return;
+      }
+    }
+  }
+
+  /**
+   * Replaces suffix y or Y with i if after non-vowel not @ word begin.
+   *
+   * Implements step 1c of the Porter2 algorithm.
+   */
+  protected function step1c() {
+    if (($this->hasEnding('y') || $this->hasEnding('Y')) && $this->length() > 2 && !($this->isVowel($this->length() - 2))) {
+      $this->removeEnding('y');
+      $this->addEnding('i');
+    }
+  }
+
+  /**
+   * Implements step 2 of the Porter2 algorithm.
+   */
+  protected function step2() {
+    $checks = array(
+      "ization" => "ize",
+      "iveness" => "ive",
+      "fulness" => "ful",
+      "ational" => "ate",
+      "ousness" => "ous",
+      "biliti" => "ble",
+      "tional" => "tion",
+      "lessli" => "less",
+      "fulli" => "ful",
+      "entli" => "ent",
+      "ation" => "ate",
+      "aliti" => "al",
+      "iviti" => "ive",
+      "ousli" => "ous",
+      "alism" => "al",
+      "abli" => "able",
+      "anci" => "ance",
+      "alli" => "al",
+      "izer" => "ize",
+      "enci" => "ence",
+      "ator" => "ate",
+      "bli" => "ble",
+      "ogi" => "og",
+    );
+    foreach ($checks as $find => $replace) {
+      if ($this->hasEnding($find)) {
+        if ($this->inR1($find)) {
+          $this->removeEnding($find);
+          $this->addEnding($replace);
+        }
+        return;
+      }
+    }
+    if ($this->hasEnding('li')) {
+      if ($this->length() > 4 && $this->validLi($this->charAt(-3))) {
+        $this->removeEnding('li');
+      }
+    }
+  }
+
+  /**
+   * Implements step 3 of the Porter2 algorithm.
+   */
+  protected function step3() {
+    $checks = array(
+      'ational' => 'ate',
+      'tional' => 'tion',
+      'alize' => 'al',
+      'icate' => 'ic',
+      'iciti' => 'ic',
+      'ical' => 'ic',
+      'ness' => '',
+      'ful' => '',
+    );
+    foreach ($checks as $find => $replace) {
+      if ($this->hasEnding($find)) {
+        if ($this->inR1($find)) {
+          $this->removeEnding($find);
+          $this->addEnding($replace);
+        }
+        return;
+      }
+    }
+    if ($this->hasEnding('ative')) {
+      if ($this->inR2('ative')) {
+        $this->removeEnding('ative');
+      }
+    }
+  }
+
+  /**
+   * Implements step 4 of the Porter2 algorithm.
+   */
+  protected function step4() {
+    $checks = array(
+      'ement',
+      'ment',
+      'ance',
+      'ence',
+      'able',
+      'ible',
+      'ant',
+      'ent',
+      'ion',
+      'ism',
+      'ate',
+      'iti',
+      'ous',
+      'ive',
+      'ize',
+      'al',
+      'er',
+      'ic',
+    );
+    foreach ($checks as $check) {
+      // Among the suffixes, if found and in R2, delete.
+      if ($this->hasEnding($check)) {
+        if ($this->inR2($check)) {
+          if ($check !== 'ion' || in_array($this->charAt(-4), array('s', 't'))) {
+            $this->removeEnding($check);
+          }
+        }
+        return;
+      }
+    }
+  }
+
+  /**
+   * Implements step 5 of the Porter2 algorithm.
+   */
+  protected function step5() {
+    if ($this->hasEnding('e')) {
+      // Delete if in R2, or in R1 and not preceded by a short syllable.
+      if ($this->inR2('e') || ($this->inR1('e') && !$this->isShortSyllable($this->length() - 3))) {
+        $this->removeEnding('e');
+      }
+      return;
+    }
+    if ($this->hasEnding('l')) {
+      // Delete if in R2 and preceded by l.
+      if ($this->inR2('l') && $this->charAt(-2) == 'l') {
+        $this->removeEnding('l');
+      }
+    }
+  }
+
+  /**
+   * Removes certain double consonants from the word's end.
+   *
+   * @return bool
+   *   TRUE if a match was found and removed, FALSE otherwise.
+   */
+  protected function removeDoubles() {
+    $found = FALSE;
+    $doubles = array('bb', 'dd', 'ff', 'gg', 'mm', 'nn', 'pp', 'rr', 'tt');
+    foreach ($doubles as $double) {
+      if (substr($this->word, -2) == $double) {
+        $this->word = substr($this->word, 0, -1);
+        $found = TRUE;
+        break;
+      }
+    }
+    return $found;
+  }
+
+  /**
+   * Checks whether a character is a vowel.
+   *
+   * @param int $position
+   *   The character's position.
+   * @param string|null $word
+   *   (optional) The word in which to check. Defaults to $this->word.
+   * @param string[] $additional
+   *   (optional) Additional characters that should count as vowels.
+   *
+   * @return bool
+   *   TRUE if the character is a vowel, FALSE otherwise.
+   */
+  protected function isVowel($position, $word = NULL, $additional = array()) {
+    if ($word === NULL) {
+      $word = $this->word;
+    }
+    $vowels = array_merge(array('a', 'e', 'i', 'o', 'u', 'y'), $additional);
+    return in_array($this->charAt($position, $word), $vowels);
+  }
+
+  /**
+   * Retrieves the character at the given position.
+   *
+   * @param int $position
+   *   The 0-based index of the character. If a negative number is given, the
+   *   position is counted from the end of the string.
+   * @param string|null $word
+   *   (optional) The word from which to retrieve the character. Defaults to
+   *   $this->word.
+   *
+   * @return string
+   *   The character at the given position, or an empty string if the given
+   *   position was illegal.
+   */
+  protected function charAt($position, $word = NULL) {
+    if ($word === NULL) {
+      $word = $this->word;
+    }
+    $length = strlen($word);
+    if (abs($position) >= $length) {
+      return '';
+    }
+    if ($position < 0) {
+      $position += $length;
+    }
+    return $word[$position];
+  }
+
+  /**
+   * Determines whether the word ends in a "vowel-consonant" suffix.
+   *
+   * Unless the word is only two characters long, it also checks that the
+   * third-last character is neither "w", "x" nor "Y".
+   *
+   * @param int|null $position
+   *   (optional) If given, do not check the end of the word, but the character
+   *   at the given position, and the next one.
+   *
+   * @return bool
+   *   TRUE if the word has the described suffix, FALSE otherwise.
+   */
+  protected function isShortSyllable($position = NULL) {
+    if ($position === NULL) {
+      $position = $this->length() - 2;
+    }
+    // A vowel at the beginning of the word followed by a non-vowel.
+    if ($position === 0) {
+      return $this->isVowel(0) && !$this->isVowel(1);
+    }
+    // Vowel followed by non-vowel other than w, x, Y and preceded by
+    // non-vowel.
+    $additional = array('w', 'x', 'Y');
+    return !$this->isVowel($position - 1) && $this->isVowel($position) && !$this->isVowel($position + 1, NULL, $additional);
+  }
+
+  /**
+   * Determines whether the word is short.
+   *
+   * A word is called short if it ends in a short syllable and if R1 is null.
+   *
+   * @return bool
+   *   TRUE if the word is short, FALSE otherwise.
+   */
+  protected function isShort() {
+    return $this->isShortSyllable() && $this->r1 == $this->length();
+  }
+
+  /**
+   * Determines the start of a certain "R" region.
+   *
+   * R is a region after the first non-vowel following a vowel, or end of word.
+   *
+   * @param int $type
+   *   (optional) 1 or 2. If 2, then calculate the R after the R1.
+   *
+   * @return int
+   *   The R position.
+   */
+  protected function R($type = 1) {
+    $inc = 1;
+    if ($type === 2) {
+      $inc = $this->r1;
+    }
+    elseif ($this->length() > 5) {
+      $prefix_5 = substr($this->word, 0, 5);
+      if ($prefix_5 === 'gener' || $prefix_5 === 'arsen') {
+        return 5;
+      }
+      if ($this->length() > 6 && substr($this->word, 0, 6) === 'commun') {
+        return 6;
+      }
+    }
+
+    while ($inc <= $this->length()) {
+      if (!$this->isVowel($inc) && $this->isVowel($inc - 1)) {
+        $position = $inc;
+        break;
+      }
+      $inc++;
+    }
+    if (!isset($position)) {
+      $position = $this->length();
+    }
+    else {
+      // We add one, as this is the position AFTER the first non-vowel.
+      $position++;
+    }
+    return $position;
+  }
+
+  /**
+   * Checks whether the given string is contained in R1.
+   *
+   * @param string $string
+   *   The string.
+   *
+   * @return bool
+   *   TRUE if the string is in R1, FALSE otherwise.
+   */
+  protected function inR1($string) {
+    $r1 = substr($this->word, $this->r1);
+    return strpos($r1, $string) !== FALSE;
+  }
+
+  /**
+   * Checks whether the given string is contained in R2.
+   *
+   * @param string $string
+   *   The string.
+   *
+   * @return bool
+   *   TRUE if the string is in R2, FALSE otherwise.
+   */
+  protected function inR2($string) {
+    $r2 = substr($this->word, $this->r2);
+    return strpos($r2, $string) !== FALSE;
+  }
+
+  /**
+   * Determines the string length of the current word.
+   *
+   * @return int
+   *   The string length of the current word.
+   */
+  protected function length() {
+    return strlen($this->word);
+  }
+
+  /**
+   * Checks whether the word ends with the given string.
+   *
+   * @param string $string
+   *   The string.
+   *
+   * @return bool
+   *   TRUE if the word ends with the given string, FALSE otherwise.
+   */
+  protected function hasEnding($string) {
+    $length = strlen($string);
+    if ($length > $this->length()) {
+      return FALSE;
+    }
+    return (substr_compare($this->word, $string, -1 * $length, $length) === 0);
+  }
+
+  /**
+   * Appends a given string to the current word.
+   *
+   * @param string $string
+   *   The ending to append.
+   */
+  protected function addEnding($string) {
+    $this->word = $this->word . $string;
+  }
+
+  /**
+   * Removes a given string from the end of the current word.
+   *
+   * Does not check whether the ending is actually there.
+   *
+   * @param string $string
+   *   The ending to remove.
+   */
+  protected function removeEnding($string) {
+    $this->word = substr($this->word, 0, -strlen($string));
+  }
+
+  /**
+   * Checks whether the given string contains a vowel.
+   *
+   * @param string $string
+   *   The string to check.
+   *
+   * @return bool
+   *   TRUE if the string contains a vowel, FALSE otherwise.
+   */
+  protected function containsVowel($string) {
+    $inc = 0;
+    $return = FALSE;
+    while ($inc < strlen($string)) {
+      if ($this->isVowel($inc, $string)) {
+        $return = TRUE;
+        break;
+      }
+      $inc++;
+    }
+    return $return;
+  }
+
+  /**
+   * Checks whether the given string is a valid -li prefix.
+   *
+   * @param string $string
+   *   The string to check.
+   *
+   * @return bool
+   *   TRUE if the given string is a valid -li prefix, FALSE otherwise.
+   */
+  protected function validLi($string) {
+    return in_array($string, array(
+      'c',
+      'd',
+      'e',
+      'g',
+      'h',
+      'k',
+      'm',
+      'n',
+      'r',
+      't',
+    ));
+  }
+
+}

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

@@ -856,10 +856,33 @@ class SearchApiQuery implements SearchApiQueryInterface {
       }
       $ret .= 'Sorting: ' . implode(', ', $sort) . "\n";
     }
-    $ret .= 'Options: ' . str_replace("\n", "\n  ", var_export($this->options, TRUE)) . "\n";
+    $options = $this->sanitizeOptions($this->options);
+    $options = str_replace("\n", "\n  ", var_export($options, TRUE));
+    $ret .= 'Options: ' . $options . "\n";
     return $ret;
   }
 
+  /**
+   * Sanitizes an array of options in a way that plays nice with var_export().
+   *
+   * @param array $options
+   *   An array of options.
+   *
+   * @return array
+   *   The sanitized options.
+   */
+  protected function sanitizeOptions(array $options) {
+    foreach ($options as $key => $value) {
+      if (is_object($value)) {
+        $options[$key] = 'object (' . get_class($value) . ')';
+      }
+      elseif (is_array($value)) {
+        $options[$key] = $this->sanitizeOptions($value);
+      }
+    }
+    return $options;
+  }
+
 }
 
 /**
@@ -1048,6 +1071,10 @@ class SearchApiQueryFilter implements SearchApiQueryFilterInterface {
    * {@inheritdoc}
    */
   public function &getTags() {
+    // Tags can sometimes be NULL for old serialized query filter objects.
+    if (!isset($this->tags)) {
+      $this->tags = array();
+    }
     return $this->tags;
   }
 

+ 4 - 3
sites/all/modules/contrib/search/search_api/search_api.info

@@ -27,6 +27,7 @@ files[] = includes/processor.inc
 files[] = includes/processor_highlight.inc
 files[] = includes/processor_html_filter.inc
 files[] = includes/processor_ignore_case.inc
+files[] = includes/processor_stemmer.inc
 files[] = includes/processor_stopwords.inc
 files[] = includes/processor_tokenizer.inc
 files[] = includes/processor_transliteration.inc
@@ -36,9 +37,9 @@ files[] = includes/service.inc
 
 configure = admin/config/search/search_api
 
-; Information added by Drupal.org packaging script on 2016-07-21
-version = "7.x-1.20"
+; Information added by Drupal.org packaging script on 2017-02-23
+version = "7.x-1.21"
 core = "7.x"
 project = "search_api"
-datestamp = "1469117342"
+datestamp = "1487844493"
 

+ 1 - 1
sites/all/modules/contrib/search/search_api/search_api.install

@@ -362,7 +362,7 @@ function search_api_install() {
     ),
   );
   search_api_index_insert($values);
-  drupal_set_message('The Search API module was installed. A new default node index was created.');
+  drupal_set_message(t('The Search API module was installed. A new default node index was created.'));
 }
 
 /**

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

@@ -784,7 +784,7 @@ function search_api_features_export_alter(&$export) {
  * @see hook_search_api_item_type_info()
  */
 function search_api_system_info_alter(&$info, $file, $type) {
-  if ($type != 'module' || $file->name == 'search_api') {
+  if ($type != 'module' || $file->name == 'search_api' || !module_exists($file->name)) {
     return;
   }
   // Check for defined item types.
@@ -1152,11 +1152,17 @@ function search_api_search_api_processor_info() {
     'class' => 'SearchApiStopWords',
     'weight' => 30,
   );
+  $processors['search_api_porter_stemmer'] = array(
+    'name' => t('Stem words'),
+    'description' => t('This processor reduces words to a stem (e.g., "talking" to "talk"). For best results, it should only be executed after tokenizing.'),
+    'class' => 'SearchApiPorterStemmer',
+    'weight' => 35,
+  );
   $processors['search_api_highlighting'] = array(
     'name' => t('Highlighting'),
     'description' => t('Adds highlighting for search results.'),
     'class' => 'SearchApiHighlight',
-    'weight' => 35,
+    'weight' => 40,
   );
 
   return $processors;

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

@@ -10,7 +10,7 @@
  * Implements hook_rules_action_info().
  */
 function search_api_rules_action_info() {
-  $items['search_api_index'] = array (
+  $items['search_api_index'] = array(
    'parameter' => array(
       'entity' => array(
         'type' => 'entity',

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

@@ -10,9 +10,9 @@ files[] = search_api_test.module
 
 hidden = TRUE
 
-; Information added by Drupal.org packaging script on 2016-07-21
-version = "7.x-1.20"
+; Information added by Drupal.org packaging script on 2017-02-23
+version = "7.x-1.21"
 core = "7.x"
 project = "search_api"
-datestamp = "1469117342"
+datestamp = "1487844493"
 

+ 22 - 4
sites/all/modules/contrib/search/search_api_saved_searches/CHANGELOG.txt

@@ -1,11 +1,29 @@
-Search API Saved Searches 1.3 (05/24/2014):
+Search API Saved Searches 1.5 (2015-06-08):
+-------------------------------------------
+- #2346679 by drunken monkey, dcmul: Added display of saved search
+  enabled/disabled status to UI.
+- #2346677 by drunken monkey, dcmul: Fixed firing of saved searches for
+  disabled users.
+- #2387155 by Dimiona, drunken monkey: Fixed default value for custom
+  notification interval.
+- #2359003 by drunken monkey: Fixed removal of cron queue items of deleted
+  searches.
+- #2347243 by queenvictoria, drunken monkey: Added tokens from the first search
+  to the mail texts.
+- #1829678 by aschmoe: Fixed render order of "Save search" block in panels.
+- #2354863 by drunken monkey: Fixed alert intervals not being respected.
+- #2300175 by drunken monkey: Added database indexes to improve performance.
+- #2221683 by drunken monkey | rjacobs: Fixed duplicate mails due to overlaps
+  conditions in cron queue.
+
+Search API Saved Searches 1.3 (2014-05-24):
 -------------------------------------------
 - #2082325 by balajidharma, drunken monkey: Added classes to edit and delete
   links.
 - #2088045 by leeomara, drunken monkey: Added hook to override generated names.
 - #2042299 by drunken monkey: Added access callbacks for both entity types.
 
-Search API Saved Searches 1.2 (07/21/2013):
+Search API Saved Searches 1.2 (2013-07-21):
 -------------------------------------------
 - #2040469 by drunken monkey: Fixed Views display of saved search results.
 - #2027441 by drunken monkey: Added option to filter by date instead of IDs.
@@ -14,7 +32,7 @@ Search API Saved Searches 1.2 (07/21/2013):
 - #2018983 by drunken monkey: Fixed error after deleting last saved search.
 - #2012714 by drunken monkey: Fixed the $reset parameter for load functions.
 
-Search API Saved Searches 1.1 (03/27/2013):
+Search API Saved Searches 1.1 (2013-03-27):
 -------------------------------------------
 - #1371344 by leeomara, drunken monkey: Added a README.txt file.
 - #1888140 by drunken monkey: Added extended Views integration.
@@ -36,6 +54,6 @@ Search API Saved Searches 1.1 (03/27/2013):
 - #1398310 by drunken monkey: Fixed visibility of "custom time" field.
 - #1857822 by sepgil: Added quantity tokens.
 
-Search API Saved Searches 1.0 (10/04/2012):
+Search API Saved Searches 1.0 (2012-10-04):
 -------------------------------------------
 First stable release of the Search API Saved Searches project.

+ 8 - 3
sites/all/modules/contrib/search/search_api_saved_searches/search_api_saved_searches.admin.inc

@@ -280,7 +280,7 @@ You can configure your saved searches at the following address:
   $form['options']['interval']['set_interval_custom'] = array(
     '#type' => 'textfield',
     '#description' => t('Enter the custom notification interval, in seconds. Use a negative value to disable notifications.'),
-    '#default_value' => isset($interval_options[$options['set_interval']]) ? $options['set_interval'] : 0,
+    '#default_value' => $options['set_interval'],
     '#parents' => array('options', 'set_interval_custom'),
     '#states' => array(
       'visible' => array(
@@ -343,7 +343,8 @@ You can configure your saved searches at the following address:
     '#type' => 'textfield',
     '#title' => t('Subject'),
     '#description' => t("Enter the mail's subject.") . ' ' . t('Available variables are: @vars.',
-        array('@vars' => '[site:name], [user:name], [user:mail]')),
+        array('@vars' => '[site:name], [user:name], [user:mail], [search-api-saved-search:name], [search-api-saved-search:created]')) . '<br />' .
+        t('The search-specific variables are taken from the first saved search of the mail. (Alerts from multiple searches are sent at the same time.)'),
     '#default_value' => $options['mail']['notify']['title'],
     '#required' => TRUE,
   );
@@ -352,8 +353,12 @@ You can configure your saved searches at the following address:
     '#title' => t('Body'),
     '#description' => t("Enter the mail's body.") . ' ' . t('Available variables are: @vars.',
         array('@vars' => '[site:name], [site:url], [user:name], [user:mail], [site:url-brief], ' .
-            '[search-api-saved-searches:results], [user:search-api-saved-searches-url]')) . ' ' .
+            '[search-api-saved-searches:results], [user:search-api-saved-searches-url], ' .
+            '[search-api-saved-search:name], [search-api-saved-search:created], ' .
+            '[search-api-saved-search:results-capped], [search-api-saved-search:view-url], ' .
+            '[search-api-saved-search:edit-url], [search-api-saved-search:delete-url]')) . ' ' .
         t('The replacement for @var can be configured below.', array('@var' => '[search-api-saved-searches:results]')) . '<br />' .
+        t('The search-specific variables are taken from the first saved search of the mail. (Alerts from multiple searches are sent at the same time.)') . '<br />' .
         t('Please note: For anonymous users, the [user:*] variables will be empty.'),
     '#default_value' => $options['mail']['notify']['body'],
     '#rows' => 12,

+ 3 - 3
sites/all/modules/contrib/search/search_api_saved_searches/search_api_saved_searches.info

@@ -10,9 +10,9 @@ files[] = views/handler_field_saved_search_interval.inc
 files[] = views/handler_field_saved_search_link.inc
 files[] = views/handler_field_saved_search_name.inc
 
-; Information added by Drupal.org packaging script on 2014-05-24
-version = "7.x-1.3"
+; Information added by Drupal.org packaging script on 2015-06-08
+version = "7.x-1.5"
 core = "7.x"
 project = "search_api_saved_searches"
-datestamp = "1400922530"
+datestamp = "1433745784"
 

+ 72 - 2
sites/all/modules/contrib/search/search_api_saved_searches/search_api_saved_searches.install

@@ -112,6 +112,11 @@ function search_api_saved_searches_schema() {
         'type' => 'int',
         'not null' => TRUE,
       ),
+      'last_queued' => array(
+        'description' => 'The Unix timestamp when the saved search was last queued.',
+        'type' => 'int',
+        'not null' => TRUE,
+      ),
       'last_execute' => array(
         'description' => 'The Unix timestamp when the saved search was last executed.',
         'type' => 'int',
@@ -141,8 +146,8 @@ function search_api_saved_searches_schema() {
       ),
     ),
     'indexes' => array(
-      'uid' => array('uid'),
-      'mail' => array('mail'),
+      'user' => array('mail', 'uid'),
+      'notify' => array('enabled', 'notify_interval', 'last_queued', 'last_execute'),
     ),
     'primary key' => array('id'),
   );
@@ -172,3 +177,68 @@ function search_api_saved_searches_update_7101() {
       ->execute();
   }
 }
+
+/**
+ * Add {search_api_saved_search}.last_queued for more accurate queue tracking.
+ */
+function search_api_saved_searches_update_7102() {
+  $spec = array(
+    'description' => 'The Unix timestamp when the saved search was last queued.',
+    'type' => 'int',
+    'not null' => TRUE,
+  );
+  db_change_field('search_api_saved_search', 'last_execute', 'last_queued', $spec);
+  $spec['description'] = 'The Unix timestamp when the saved search was last executed.';
+  $spec['not null'] = FALSE;
+  db_add_field('search_api_saved_search', 'last_execute', $spec);
+  db_update('search_api_saved_search')
+    ->expression('last_execute', 'last_queued')
+    ->execute();
+  $spec['not null'] = TRUE;
+  db_change_field('search_api_saved_search', 'last_execute', 'last_execute', $spec);
+}
+
+/**
+ * Optimize indexes on {search_api_saved_search} to improve performance.
+ */
+function search_api_saved_searches_update_7103() {
+  db_drop_index('search_api_saved_search', 'uid');
+  db_drop_index('search_api_saved_search', 'mail');
+  db_add_index('search_api_saved_search', 'user', array('mail', 'uid'));
+  db_add_index('search_api_saved_search', 'notify', array('enabled', 'notify_interval', 'last_queued', 'last_execute'));
+}
+
+/**
+ * Fix incorrect {search_api_saved_search}.last_queued values.
+ *
+ * This might lead to duplicate mails for saved searches in some rare cases, but
+ * is necessary to prevent saved searches from never being executed at all
+ * anymore in others.
+ *
+ * @see https://www.drupal.org/node/2354863
+ */
+function search_api_saved_searches_update_7104() {
+  db_update('search_api_saved_search')
+    ->expression('last_queued', 'last_execute')
+    ->where('last_queued > last_execute')
+    ->execute();
+}
+
+/**
+ * Disable saved searches whose users were disabled.
+ */
+function search_api_saved_searches_update_7105() {
+  // Get the UIDs of all inactive users.
+  $query = db_select('users', 'u');
+  $query->fields('u', array('uid'))
+    ->condition('u.status', 0)
+    ->condition('u.uid', 0, '<>');
+
+  // Then, disable all their searches.
+  db_update('search_api_saved_search')
+    ->condition('uid', $query, 'IN')
+    ->fields(array(
+      'enabled' => 0,
+    ))
+    ->execute();
+}

+ 118 - 41
sites/all/modules/contrib/search/search_api_saved_searches/search_api_saved_searches.module

@@ -66,6 +66,24 @@ function search_api_saved_searches_menu() {
     'access arguments' => array(NULL, 2, 4),
     'file' => 'search_api_saved_searches.pages.inc',
   );
+  $items['search-api/saved-search/%search_api_saved_search/enable'] = array(
+    'title' => 'Enable/Disable saved search',
+    'description' => 'Enable or disable a saved search.',
+    'page callback' => 'search_api_saved_searches_search_enable',
+    'page arguments' => array(2),
+    'access callback' => 'search_api_saved_search_edit_access',
+    'access arguments' => array(NULL, 2, 4),
+    'file' => 'search_api_saved_searches.pages.inc',
+  );
+  $items['search-api/saved-search/%search_api_saved_search/disable'] = array(
+    'title' => 'Enable/Disable saved search',
+    'description' => 'Enable or disable a saved search.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('search_api_saved_searches_search_disable_form', 2),
+    'access callback' => 'search_api_saved_search_edit_access',
+    'access arguments' => array(NULL, 2, 4),
+    'file' => 'search_api_saved_searches.pages.inc',
+  );
   $items['search-api/saved-search/%search_api_saved_search/edit'] = array(
     'title' => 'Edit saved search',
     'description' => 'Edit a saved search.',
@@ -160,6 +178,7 @@ function search_api_saved_searches_entity_property_info_alter(array &$info) {
   $searches['settings_id']['type'] = 'token';
   $searches['enabled']['type'] = 'boolean';
   $searches['created']['type'] = 'date';
+  $searches['last_queued']['type'] = 'date';
   $searches['last_execute']['type'] = 'date';
   // We can't assign "duration" until Entity API Views integration supports
   // this.
@@ -290,11 +309,14 @@ function search_api_saved_searches_user_insert(&$edit, $account, $category) {
  * If a user gets activated, associate saved searches with the same mail address
  * with them.
  *
+ * If a user gets deactivated, disable all related saved searches.
+ *
  * Also, change mail address of saved searches when the user mail address
  * changes.
  */
 function search_api_saved_searches_user_update(&$edit, $account, $category) {
-  // Changes of mail and status.
+  // For newly activated users, transfer all saved searches with their mail
+  // address to them.
   if (!empty($account->status) && empty($account->original->status)) {
     foreach (search_api_saved_search_load_multiple(FALSE, array('mail' => $account->mail, 'uid' => 0)) as $search) {
       $search->uid = $account->uid;
@@ -304,6 +326,15 @@ function search_api_saved_searches_user_update(&$edit, $account, $category) {
       $search->save();
     }
   }
+  // If an account gets deactivated/banned, disable all associated searches.
+  if (empty($account->status) && !empty($account->original->status)) {
+    foreach (search_api_saved_search_load_multiple(FALSE, array('uid' => $account->uid)) as $search) {
+      $search->enabled = FALSE;
+      $search->save();
+    }
+  }
+  // If the user's mail address changed, also change the mail address of the
+  // user's saved searches.
   if ($account->mail != $account->original->mail) {
     foreach (search_api_saved_search_load_multiple(FALSE, array('mail' => $account->mail, 'uid' => $account->uid)) as $search) {
       $search->mail = $account->mail;
@@ -450,9 +481,8 @@ function search_api_saved_searches_settings_load($id, $reset = FALSE) {
  * @param bool $reset
  *   If TRUE, will reset the internal entity cache.
  *
- * @return array
- *   An array of SearchApiSavedSearchesSettings objects matching the conditions,
- *   keyed by delta.
+ * @return SearchApiSavedSearchesSettings[]
+ *   All saved search settings matching the conditions, keyed by delta.
  */
 function search_api_saved_searches_settings_load_multiple($ids = FALSE, array $conditions = array(), $reset = FALSE) {
   $settings = entity_load('search_api_saved_searches_settings', $ids, $conditions, $reset);
@@ -478,16 +508,16 @@ function search_api_saved_search_load($id, $reset = FALSE) {
 /**
  * Loads multiple saved search objects.
  *
- * @param $ids
+ * @param int[]|false $ids
  *   The saved search's IDs; or FALSE to load all saved searches.
  * @param array $conditions
  *   Associative array of field => value conditions that returned objects must
  *   satisfy.
- * @param $reset
+ * @param bool $reset
  *   If TRUE, will reset the internal entity cache.
  *
- * @return array
- *   An array of SearchApiSavedSearch objects matching the conditions.
+ * @return SearchApiSavedSearch[]
+ *   All saved searches matching the conditions, keyed by their IDs.
  */
 function search_api_saved_search_load_multiple($ids = FALSE, array $conditions = array(), $reset = FALSE) {
   return entity_load('search_api_saved_search', $ids, $conditions, $reset);
@@ -624,6 +654,15 @@ function search_api_saved_searches_block_info() {
   return $blocks;
 }
 
+/**
+ * Implements hook_ctools_block_info().
+ */
+function search_api_saved_searches_ctools_block_info($module, $delta, &$info) {
+  $info['category'] = t('Search API Saved Searches');
+  // Allow blocks to be used before the search results in Panels.
+  $info['render last'] = TRUE;
+}
+
 /**
  * Implements hook_block_configure().
  */
@@ -970,6 +1009,7 @@ function search_api_saved_searches_save_form_submit(array $form, array &$form_st
     'name' => $values['name'],
     'mail' => $values['mail'],
     'created' => REQUEST_TIME,
+    'last_queued' => REQUEST_TIME,
     'last_execute' => REQUEST_TIME,
     'notify_interval' => $values['notify_interval'],
     'query' => $query,
@@ -1057,9 +1097,14 @@ function search_api_saved_searches_mail($key, array &$message, array $params) {
 
     case 'notify':
       $settings = $params['settings'];
+      $search = $params['searches'][0]['search'];
       $data = array(
         'user' => $params['user'],
         'search_api_saved_searches' => $params['searches'],
+        'search_api_saved_search_info' => array(
+          'search' => $search,
+          'results' => array(),
+        ),
       );
       $title = $settings->getTranslatedOption('mail.notify.title', $language->language);
       $message['subject'] .= token_replace($title, $data, array('language' => $language, 'sanitize' => FALSE));
@@ -1081,7 +1126,9 @@ function search_api_saved_searches_cron() {
   $ids = db_select('search_api_saved_search', 's')
     ->fields('s', array('id'))
     ->condition('enabled', 1)
-    ->where('notify_interval >= 0 AND last_execute + notify_interval < :time', array(':time' => REQUEST_TIME + 15))
+    ->condition('notify_interval', 0, '>=')
+    ->where('last_execute >= last_queued')
+    ->where('last_queued + notify_interval < :time', array(':time' => REQUEST_TIME + 15))
     ->execute()
     ->fetchCol();
   if (!$ids) {
@@ -1093,21 +1140,18 @@ function search_api_saved_searches_cron() {
   // Group the search according to mail and settings. Grouping by mail prevents
   // a user from getting several mails at once, for different searches. Grouping
   // by settings is necessary since the mails can differ between settings.
+  $user_searches = array();
   foreach ($searches as $search) {
-    $user_searches[$search->mail . ' ' . $search->settings_id][] = $search;
+    $user_searches[$search->mail . ' ' . $search->settings_id][] = $search->id;
+    // Set the last execution timestamp now, so the interval doesn't move and we
+    // don't get problems if the next cron run occurs before the queue is
+    // completely executed.
+    $search->last_queued = REQUEST_TIME;
+    $search->save();
   }
   foreach ($user_searches as $searches) {
     $queue->createItem($searches);
   }
-  // Set the last execution timestamp now, so the interval doesn't move and we
-  // don't get problems if the next cron run occurrs before the queue is
-  // completely executed.
-  db_update('search_api_saved_search')
-    ->fields(array(
-      'last_execute' => REQUEST_TIME,
-    ))
-    ->condition('id', $ids, 'IN')
-    ->execute();
 }
 
 /**
@@ -1127,17 +1171,44 @@ function search_api_saved_searches_cron_queue_info() {
 /**
  * Checks for new results for saved searches, and sends a mail if necessary.
  *
- * @param array $searches
- *   An array of SearchApiSavedSearch objects to check for new results. All of
- *   these should have the same mail address and base settings.
+ * Used as a worker callback for the homonymous cron queue.
+ *
+ * @param int[] $search_ids
+ *   The IDs of the saved searches to check for new results. All of these should
+ *   have the same mail address and base settings.
+ *
+ * @throws SearchApiException
+ *   If an error occurred in one of the searches.
+ *
+ * @see search_api_saved_searches_cron_queue_info()
  */
-function search_api_saved_searches_check_updates(array $searches) {
-  try {
-    $search = reset($searches);
-    $settings = $search->settings();
-    $index = $settings->index();
-    $mail_params = array();
+function search_api_saved_searches_check_updates(array $search_ids) {
+  if (!$search_ids) {
+    return;
+  }
+  // Since in earlier versions this function got the loaded searches passed
+  // directly instead of just IDs, and there might still be some such items in
+  // the queue when updating to the new style, we have to stay
+  // backwards-compatible here. So, when an array of loaded searches is passed,
+  // we first replace them with their IDs and only then load them again.
+  if (!is_scalar(reset($search_ids))) {
+    /** @var SearchApiSavedSearch[] $searches */
+    $searches = $search_ids;
+    $search_ids = array();
     foreach ($searches as $search) {
+      $search_ids[] = $search->id;
+    }
+  }
+  $searches = search_api_saved_search_load_multiple($search_ids, array('enabled' => 1));
+  if (!$searches) {
+    return;
+  }
+  $search = $searches[key($searches)];
+  $settings = $search->settings();
+  $index = $settings->index();
+  $mail_params = array();
+  foreach ($searches as $search) {
+    try {
       // Make sure we run the query as the user who owns the saved search.
       // Otherwise node access will not work properly.
       $search->query['options']['search_api_access_account'] = $search->uid;
@@ -1150,6 +1221,7 @@ function search_api_saved_searches_check_updates(array $searches) {
       }
       $response = $query->execute();
       if (!empty($response['results'])) {
+        $old = array();
         $new = $results = drupal_map_assoc(array_keys($response['results']));
         if (empty($settings->options['date_field'])) {
           // ID-based method: Compare these results to the old ones.
@@ -1189,23 +1261,28 @@ function search_api_saved_searches_check_updates(array $searches) {
         if (empty($settings->options['date_field']) && ($new || array_diff($old, $results))) {
           // The results changed in some way: store the latest version.
           $search->results = implode(',', $results);
-          $search->save();
         }
       }
+      // Use time() instead of REQUEST_TIME to minimize the potential of sending
+      // duplicate results due to longer-running cron queue workers.
+      $search->last_execute = time();
+      $search->save();
     }
-    // If we set any searches in the mail parameters, send the mail.
-    if ($mail_params) {
-      $mail_params['user'] = user_load($search->uid);
-      $mail_params['settings'] = $settings;
-      $message = drupal_mail('search_api_saved_searches', 'notify', $search->mail,
-          user_preferred_language($mail_params['user']), $mail_params);
-      if ($message['result']) {
-        watchdog('search_api_saved_searches', 'A mail with new saved search results was sent to @mail.',
-            array('@mail' => $search->mail), WATCHDOG_INFO);
-      }
+    catch (SearchApiException $e) {
+      $args = _drupal_decode_exception($e);
+      $args['@id'] = $search->id;
+      throw new SearchApiException(t('%type while trying to check for new results on saved search @id: !message in %function (line %line of %file).', $args));
     }
   }
-  catch (SearchApiException $e) {
-    watchdog('search_api_saved_searches', $e->getMessage(), NULL, WATCHDOG_ERROR);
+  // If we set any searches in the mail parameters, send the mail.
+  if ($mail_params) {
+    $mail_params['user'] = user_load($search->uid);
+    $mail_params['settings'] = $settings;
+    $message = drupal_mail('search_api_saved_searches', 'notify', $search->mail,
+        user_preferred_language($mail_params['user']), $mail_params);
+    if ($message['result']) {
+      watchdog('search_api_saved_searches', 'A mail with new saved search results was sent to @mail.',
+          array('@mail' => $search->mail), WATCHDOG_INFO);
+    }
   }
 }

+ 94 - 17
sites/all/modules/contrib/search/search_api_saved_searches/search_api_saved_searches.pages.inc

@@ -45,16 +45,30 @@ function search_api_saved_searches_user_listing($account) {
       $interval = format_interval($search->notify_interval, 1);
     }
 
+    $enable_options['attributes']['class'][] = 'saved-search-enable';
     $edit_options['attributes']['class'][] = 'saved-search-edit';
     $delete_options['attributes']['class'][] = 'saved-search-delete';
 
+    if ($search->enabled) {
+      $enable_action = '/disable';
+      $enable_label = t('disable');
+    }
+    else {
+      $enable_action = '/enable';
+      $enable_label = t('enable');
+      $enable_options['query']['token'] = drupal_get_token($search->id);
+    }
+
     $path = $base_path . $search->id;
+    $enable_link = l($enable_label, $path . $enable_action, $enable_options);
+    $edit_link = l(t('edit'), $path . '/edit', $edit_options);
+    $delete_link = l(t('delete'), $path . '/delete', $delete_options);
     $rows[] = array(
       $name,
       $created,
       $last_execute,
       $interval,
-      l(t('edit'), $path . '/edit', $edit_options) . ' | ' . l(t('delete'), $path . '/delete', $delete_options),
+      $enable_link . ' | ' . $edit_link . ' | ' . $delete_link,
     );
   }
 
@@ -170,6 +184,12 @@ function search_api_saved_searches_search_edit_form(array $form, array &$form_st
     $form_state['destination'] = array($search->options['page']['path'], $search->options['page']);
   }
 
+  $form['enabled'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Enabled'),
+    '#description' => t('Disable to stop receiving notifications from this saved search.'),
+    '#default_value' => $search->enabled,
+  );
   $form['name'] = array(
     '#type' => 'textfield',
     '#title' => t('Name'),
@@ -213,6 +233,7 @@ function search_api_saved_searches_search_edit_form_submit(array $form, array &$
   $values = $form_state['values'];
   $search = $form_state['search'];
   $search->name = $values['name'];
+  $search->enabled = $values['enabled'];
   if (isset($values['notify_interval'])) {
     $search->notify_interval = $values['notify_interval'];
   }
@@ -227,6 +248,56 @@ function search_api_saved_searches_search_edit_form_submit(array $form, array &$
   }
 }
 
+/**
+ * Page callback: Enables a saved search.
+ *
+ * @param SearchApiSavedSearch $search
+ *   The search to enable.
+ */
+function search_api_saved_searches_search_enable(SearchApiSavedSearch $search = NULL) {
+  if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], $search->id)) {
+    return MENU_ACCESS_DENIED;
+  }
+
+  $search->enabled = TRUE;
+  if ($search->save()) {
+    drupal_set_message(t('The saved search was successfully enabled.'));
+  }
+  else {
+    drupal_set_message(t('The saved search could not be enabled.'), 'error');
+  }
+
+  drupal_goto(_search_api_saved_searches_admin_redirect_url($search));
+  return NULL;
+}
+
+/**
+ * Page callback: Constructs a form for disabling a saved search.
+ *
+ * @param SearchApiSavedSearch $search
+ *   The search to disable.
+ *
+ * @see search_api_saved_searches_search_disable_form_submit()
+ *
+ * @ingroup forms
+ */
+function search_api_saved_searches_search_disable_form(array $form, array &$form_state, SearchApiSavedSearch $search) {
+  $form_state['search'] = $search;
+  $url = _search_api_saved_searches_admin_redirect_url($search);
+  return confirm_form($form, t('Do you really want to disable this saved search?'), $url, t('You will not receive any updates for this saved search until it is re-enabled.'));
+}
+
+/**
+ * Form submission handler for search_api_saved_searches_search_disable_form().
+ */
+function search_api_saved_searches_search_disable_form_submit(array $form, array &$form_state) {
+  $search = $form_state['search'];
+  $search->enabled = FALSE;
+  $search->save();
+  drupal_set_message(t('The saved search was successfully disabled.'));
+  $form_state['redirect'] = _search_api_saved_searches_admin_redirect_url($search);
+}
+
 /**
  * Form builder for confirming the deletion of a saved search.
  *
@@ -238,15 +309,7 @@ function search_api_saved_searches_search_edit_form_submit(array $form, array &$
  */
 function search_api_saved_searches_search_delete_form(array $form, array &$form_state, SearchApiSavedSearch $search) {
   $form_state['search'] = $search;
-  if ($search->uid && search_api_saved_search_edit_access(user_load($search->uid))) {
-    $url = 'user/' . $search->uid . '/saved-searches';
-  }
-  elseif (!empty($search->options['page'])) {
-    $url = $search->options['page'];
-  }
-  else {
-    $url = '<front>';
-  }
+  $url = _search_api_saved_searches_admin_redirect_url($search);
   return confirm_form($form, t('Do you really want to delete this saved search?'), $url);
 }
 
@@ -258,15 +321,29 @@ function search_api_saved_searches_search_delete_form(array $form, array &$form_
 function search_api_saved_searches_search_delete_form_submit(array $form, array &$form_state) {
   $search = $form_state['search'];
   $search->delete();
+  drupal_set_message(t('The saved search was successfully deleted.'));
+  $form_state['redirect'] = _search_api_saved_searches_admin_redirect_url($search);
+}
+
+/**
+ * Returns the correct redirect URL after changing a saved search.
+ *
+ * This will be the user's "Saved searches" overview tab, if it is accessible;
+ * otherwise, if the search has a page associated with it, that page; and if
+ * none of the two are the case, the front page.
+ *
+ * @param SearchApiSavedSearch $search
+ *   The saved search that was edited, deleted or otherwise changed.
+ *
+ * @return string
+ *   The URL to redirect to.
+ */
+function _search_api_saved_searches_admin_redirect_url(SearchApiSavedSearch $search) {
   if ($search->uid && search_api_saved_search_edit_access(user_load($search->uid))) {
-    $url = 'user/' . $search->uid . '/saved-searches';
+    return 'user/' . $search->uid . '/saved-searches';
   }
   elseif (!empty($search->options['page'])) {
-    $url = $search->options['page'];
+    return $search->options['page'];
   }
-  else {
-    $url = '<front>';
-  }
-  drupal_set_message(t('The saved search was successfully deleted.'));
-  $form_state['redirect'] = $url;
+  return '<front>';
 }

+ 6 - 1
sites/all/modules/contrib/search/search_api_saved_searches/search_api_saved_searches.search_entity.inc

@@ -68,6 +68,11 @@ class SearchApiSavedSearch extends Entity {
    */
   public $created;
 
+  /**
+   * @var integer
+   */
+  public $last_queued;
+
   /**
    * @var integer
    */
@@ -145,7 +150,7 @@ class SearchApiSavedSearch extends Entity {
   }
 
   /**
-   * @return SearchApiIndex
+   * @return SearchApiSavedSearchesSettings
    *   The settings this saved search uses.
    *
    * @throws SearchApiException

+ 3 - 3
sites/all/modules/contrib/search/search_api_saved_searches/search_api_saved_searches_i18n/search_api_saved_searches_i18n.info

@@ -8,9 +8,9 @@ package = Multilingual - Internationalization
 files[] = search_api_saved_searches_i18n.controller.inc
 files[] = search_api_saved_searches_i18n.string_wrapper.inc
 
-; Information added by Drupal.org packaging script on 2014-05-24
-version = "7.x-1.3"
+; Information added by Drupal.org packaging script on 2015-06-08
+version = "7.x-1.5"
 core = "7.x"
 project = "search_api_saved_searches"
-datestamp = "1400922530"
+datestamp = "1433745784"
 

+ 26 - 2
sites/all/modules/contrib/search/search_api_solr/CHANGELOG.txt

@@ -1,5 +1,29 @@
-Search API Solr Search 1.x, dev (xxxx-xx-xx):
----------------------------------------------
+Search API Solr Search 1.12 (2017-02-23):
+-----------------------------------------
+- #2612770 by Temoor, drunken monkey: Fixed conjunction in complex filter
+  conditions.
+- #2711685 by berliner: Added support for Solr 6.
+- #2842661 by gabrielu, drunken monkey: Fixed duplicate code line in
+  clearCache().
+- #2834159 by zniki.ru: Fixed left-over explicit
+  SearchApiSolrConnection::escapeFieldName() call.
+- #2677912 by jts86, mian3010: Added option to disable committing for Solr.
+- #2828473 by detroz: Fixed incorrect variable initialization.
+- #2826565 by Sardis, drunken monkey: Fixed multi-valued field highlighting.
+- #2807327 by drunken monkey: Fixed configuration form descriptions.
+- #2772199 by drunken monkey: Added a warning to the description for the
+  highlighting server option.
+- #2733625 by joelstein, drunken monkey: Fixed notice in
+  Connection::getFields().
+
+Search API Solr Search 1.11 (2016-07-06):
+-----------------------------------------
+- #2710397 by drunken monkey: Fixed escaping of highlighting tags.
+- #2636016 by drunken monkey: Fixed location search distance facets.
+
+Search API Solr Search 1.10 (2016-03-14):
+-----------------------------------------
+- #2604322 by lex0r, drunken monkey: Added the option to log all Solr queries.
 - #2645366 by tedfordgif, drunken monkey: Fixed browser incorrectly filling the
   HTTP Auth form fields.
 - #2611716 by aditya_anurag, drunken monkey: Improved the method documentation

+ 83 - 21
sites/all/modules/contrib/search/search_api_solr/includes/service.inc

@@ -80,6 +80,9 @@ class SearchApiSolrService extends SearchApiAbstractService {
       'retrieve_data' => FALSE,
       'highlight_data' => FALSE,
       'skip_schema_check' => FALSE,
+      'log_query' => FALSE,
+      'log_response' => FALSE,
+      'commits_disabled' => FALSE,
       'solr_version' => '',
       'http_method' => 'AUTO',
       // Default to TRUE for new servers, but to FALSE for existing ones.
@@ -161,7 +164,7 @@ class SearchApiSolrService extends SearchApiAbstractService {
     $form['path'] = array(
       '#type' => 'textfield',
       '#title' => t('Solr path'),
-      '#description' => t('The path that identifies the Solr instance to use on the server. (For Solr 4.x servers, this should include the name of the core to use.)'),
+      '#description' => t('The path that identifies the Solr instance to use on the server. (For Solr versions 4 and above, this should include the name of the core to use.)'),
       '#default_value' => $options['path'],
     );
 
@@ -211,19 +214,24 @@ class SearchApiSolrService extends SearchApiAbstractService {
     $form['advanced']['highlight_data'] = array(
       '#type' => 'checkbox',
       '#title' => t('Highlight retrieved data'),
-      '#description' => t('When retrieving result data from the Solr server, try to highlight the search terms in the returned fulltext fields.'),
+      '#description' => t('When retrieving result data from the Solr server, try to highlight the search terms in the returned fulltext fields. <strong>Note:</strong> Do not use the "Highlighting" processor for the index together with this option – use one or the other.'),
       '#default_value' => $options['highlight_data'],
     );
+    // Highlighting retrieved data only makes sense when we retrieve data.
+    // (Actually, internally it doesn't really matter. However, from a user's
+    // perspective, having to check both probably makes sense.)
+    $form['advanced']['highlight_data']['#states']['invisible']
+    [':input[name="options[form][advanced][retrieve_data]"]']['checked'] = FALSE;
     $form['advanced']['skip_schema_check'] = array(
       '#type' => 'checkbox',
       '#title' => t('Skip schema verification'),
-      '#description' => t('Skip the automatic check for schema-compatibillity. Use this override if you are seeing an error-message about an incompatible schema.xml configuration file, and you are sure the configuration is compatible.'),
+      '#description' => t('Skip the automatic check for schema-compatibility. Use this override if you are seeing an error-message about an incompatible schema.xml configuration file, and you are sure the configuration is compatible.'),
       '#default_value' => $options['skip_schema_check'],
     );
     $form['advanced']['solr_version'] = array(
       '#type' => 'select',
       '#title' => t('Solr version override'),
-      '#description' => t('Specify the Solr version manually in case it cannot be retrived automatically. The version can be found in the Solr admin interface under "Solr Specification Version" or "solr-spec"'),
+      '#description' => t('Specify the Solr version manually in case it cannot be retrieved automatically. The version can be found in the Solr admin interface under "Solr Specification Version" or "solr-spec".'),
       '#options' => array(
         '' => t('Determine automatically'),
         '3' => '3.x',
@@ -232,12 +240,6 @@ class SearchApiSolrService extends SearchApiAbstractService {
       ),
       '#default_value' => $options['solr_version'],
     );
-    // Highlighting retrieved data only makes sense when we retrieve data.
-    // (Actually, internally it doesn't really matter. However, from a user's
-    // perspective, having to check both probably makes sense.)
-    $form['advanced']['highlight_data']['#states']['invisible']
-        [':input[name="options[form][advanced][retrieve_data]"]']['checked'] = FALSE;
-
     $form['advanced']['http_method'] = array(
       '#type' => 'select',
       '#title' => t('HTTP method'),
@@ -249,6 +251,24 @@ class SearchApiSolrService extends SearchApiAbstractService {
         'GET' => 'GET',
       ),
     );
+    $form['advanced']['log_query'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Log search requests'),
+      '#description' => t('Log all outgoing Solr search requests.'),
+      '#default_value' => $options['log_query'],
+    );
+    $form['advanced']['log_response'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Log search results'),
+      '#description' => t('Log all search result responses received from Solr. NOTE: This may slow down your site since all response data (including possible retrieved data) will be saved in the Drupal log.'),
+      '#default_value' => $options['log_response'],
+    );
+    $form['advanced']['commits_disabled'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Disable explicit committing'),
+      '#description' => t('Do not send any commit commands to the Solr server.'),
+      '#default_value' => $options['commits_disabled'],
+    );
 
     if (module_exists('search_api_autocomplete')) {
       $form['advanced']['autocomplete'] = array(
@@ -573,7 +593,7 @@ class SearchApiSolrService extends SearchApiAbstractService {
 
       // Now add all fields contained in the item, with dynamic fields. Also,
       // gather the contents of all text fields to also add them to "content".
-      $text_content = '';
+      $text_content = array();
       foreach ($item as $key => $field) {
         // If the field is not known for the index, something weird has
         // happened. We refuse to index the items and hope that the others are
@@ -936,7 +956,7 @@ class SearchApiSolrService extends SearchApiAbstractService {
 
         unset($radius);
         $field = $fields[$spatial['field']];
-        $escaped_field = SearchApiSolrConnection::escapeFieldName($field);
+        $escaped_field = call_user_func(array($this->connection_class, 'escapeFieldName'), $field);
 
         // If proper bbox coordinates were given use them to filter.
         if (isset($spatial['bbox'])) {
@@ -970,7 +990,7 @@ class SearchApiSolrService extends SearchApiAbstractService {
           // If the fq consists only of a filter on this field, replace it with
           // a range.
           $preg_field = preg_quote($escaped_field, '/');
-          if (preg_match('/^' . $preg_field . ':\["?(\*|\d+(?:\.\d+)?)"? TO "?(\*|\d+(?:\.\d+)?)"?\]$/', $value, $m)) {
+          if (preg_match('/^(?:\{!tag[^}]+\})?' . $preg_field . ':\["?(\*|\d+(?:\.\d+)?)"? TO "?(\*|\d+(?:\.\d+)?)"?\]$/', $value, $m)) {
             unset($fq[$key]);
             if ($m[1] && is_numeric($m[1])) {
               $min_radius = isset($min_radius) ? max($min_radius, $m[1]) : $m[1];
@@ -1354,6 +1374,7 @@ class SearchApiSolrService extends SearchApiAbstractService {
       foreach ($response->highlighting->$id->content as $snippet) {
         $snippet = strip_tags($snippet);
         $snippet = preg_replace('/^.*>|<.*$/', '', $snippet);
+        $snippet = check_plain($snippet);
         $snippet = $this->formatHighlighting($snippet);
         // The created fragments sometimes have leading or trailing punctuation.
         // We remove that here for all common cases, but take care not to remove
@@ -1367,9 +1388,27 @@ class SearchApiSolrService extends SearchApiAbstractService {
       $prefix_length = strlen($prefix);
       foreach ($field_mapping as $search_api_property => $solr_property) {
         if (substr($solr_property, 0, $prefix_length) == $prefix && !empty($response->highlighting->$id->$solr_property)) {
-          // Contrary to above, we here want to preserve HTML, so we just
-          // replace the [HIGHLIGHT] tags with the appropriate format.
-          $fields[$search_api_property] = $this->formatHighlighting($response->highlighting->$id->$solr_property);
+          $value = $response->highlighting->$id->$solr_property;
+          $value = $this->sanitizeHighlightValue($value, $search_api_property);
+          // Remove highlight prefixes and suffixes so we could compare values
+          // in order to replace the correspond items.
+          $orig_value = preg_replace('#\[(/?)HIGHLIGHT\]#', '', $value);
+          $field_values = $this->sanitizeHighlightValue($fields[$search_api_property]);
+          foreach ($field_values as $delta => $field_value) {
+            foreach ($orig_value as $num => $item) {
+              if ($item === $field_value) {
+                $field_values[$delta] = $this->formatHighlighting($value[$num]);
+                $change = TRUE;
+                continue 2;
+              }
+            }
+          }
+          if (!empty($change)) {
+            $fields[$search_api_property] = array(
+              '#value' => $field_values,
+              '#sanitize_callback' => FALSE,
+            );
+          }
         }
       }
     }
@@ -1377,13 +1416,34 @@ class SearchApiSolrService extends SearchApiAbstractService {
     return $output;
   }
 
+  /**
+   * Sanitizes a highlighted field value.
+   *
+   * @param string|array $value
+   *   Either a highlighted field value, or an array of such values.
+   * @param string|null $field_id
+   *   (optional) The ID of the field for which this sanitizing occurs, if any.
+   *
+   * @return string|array
+   *   The sanitized input.
+   */
+  protected function sanitizeHighlightValue($value, $field_id = NULL) {
+    if (is_array($value)) {
+      foreach ($value as $i => $nested_value) {
+        $value[$i] = $this->sanitizeHighlightValue($nested_value, $field_id);
+      }
+      return $value;
+    }
+    return check_plain(strip_tags($value));
+  }
+
   /**
    * Changes highlighting tags from our custom, HTML-safe ones to HTML.
    *
-   * @param string|array $snippet
+   * @param string|string[] $snippet
    *   The snippet(s) to format.
    *
-   * @return string|array
+   * @return string|string[]
    *   The snippet(s), properly formatted as HTML.
    */
   protected function formatHighlighting($snippet) {
@@ -1587,9 +1647,7 @@ class SearchApiSolrService extends SearchApiAbstractService {
       elseif ($f instanceof SearchApiQueryFilterInterface) {
         $q = $this->createFilterQueries($f, $solr_fields, $fields);
         if ($filter->getConjunction() != $f->getConjunction() && count($q) > 1) {
-          // $or == TRUE means the nested filter has conjunction AND, and vice versa
-          $sep = $or ? ' ' : ' OR ';
-          $fq[] = '((' . implode(')' . $sep . '(', $q) . '))';
+          $fq[] = '((' . implode(') ' . $f->getConjunction() . ' (', $q) . '))';
         }
         else {
           $fq = array_merge($fq, $q);
@@ -2315,6 +2373,10 @@ class SearchApiSolrService extends SearchApiAbstractService {
    * Sends a commit command to the Solr server.
    */
   public function commit() {
+    // If committing has been disabled altogether, do nothing here.
+    if (!empty($this->options['commits_disabled'])) {
+      return;
+    }
     try {
       $this->connect();
       return $this->solr->commit(FALSE);

+ 36 - 5
sites/all/modules/contrib/search/search_api_solr/includes/solr_connection.inc

@@ -367,8 +367,11 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
    */
   public function getFields($num_terms = 0) {
     $fields = array();
-    foreach ($this->getLuke($num_terms)->fields as $name => $info) {
-      $fields[$name] = new SearchApiSolrField($info);
+    $luke_data = $this->getLuke($num_terms);
+    if (isset($luke_data->fields)) {
+      foreach ($luke_data->fields as $name => $info) {
+        $fields[$name] = new SearchApiSolrField($info);
+      }
     }
     return $fields;
   }
@@ -514,7 +517,6 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
   public function clearCache() {
     if ($cid = $this->getCacheId()) {
       cache_clear_all($cid, 'cache_search_api_solr', TRUE);
-      cache_clear_all($cid, 'cache_search_api_solr', TRUE);
     }
     $this->luke = array();
     $this->stats = NULL;
@@ -944,10 +946,18 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
     // built-in http_build_query() doesn't give us the format Solr wants.
     $queryString = $this->httpBuildQuery($params);
 
+    if (!empty($this->options['log_query'])) {
+      watchdog('search_api_solr', 'Query: @query', array('@query' => $queryString), WATCHDOG_DEBUG);
+    }
+
     if ($method == 'GET' || $method == 'AUTO') {
       $searchUrl = $this->constructUrl(self::SEARCH_SERVLET, array(), $queryString);
       if ($method == 'GET' || strlen($searchUrl) <= variable_get('search_api_solr_http_get_max_length', 4000)) {
-        return $this->sendRawGet($searchUrl);
+        $response = $this->sendRawGet($searchUrl);
+        if (!empty($this->options['log_response'])) {
+          $this->logResponse($response);
+        }
+        return $response;
       }
     }
 
@@ -955,7 +965,28 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
     $searchUrl = $this->constructUrl(self::SEARCH_SERVLET);
     $options['data'] = $queryString;
     $options['headers']['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
-    return $this->sendRawPost($searchUrl, $options);
+    $response = $this->sendRawPost($searchUrl, $options);
+
+    if (!empty($this->options['log_response'])) {
+      $this->logResponse($response);
+    }
+
+    return $response;
+  }
+
+  /**
+   * Logs a Solr response object.
+   *
+   * @param object $response
+   *   The response received from Solr.
+   */
+  protected function logResponse($response) {
+    $data = $response->code . ' ' . $response->status_message . "\n" . print_r($response->response, TRUE);
+    watchdog('search_api_solr', 'Response: <div style="white-space: pre-wrap;">@response</div>', array('@response' => $data), WATCHDOG_DEBUG);
+
+    if (!empty($response->facet_counts)) {
+      watchdog('search_api_solr', 'Facets: <div style="white-space: pre-wrap;">@facets</div>', array('@facets' => print_r($response->facet_counts, TRUE)), WATCHDOG_DEBUG);
+    }
   }
 
 }

+ 3 - 3
sites/all/modules/contrib/search/search_api_solr/search_api_solr.info

@@ -11,9 +11,9 @@ files[] = includes/solr_connection.interface.inc
 files[] = includes/solr_field.inc
 files[] = includes/spellcheck.inc
 
-; Information added by Drupal.org packaging script on 2016-02-23
-version = "7.x-1.9+10-dev"
+; Information added by Drupal.org packaging script on 2017-02-23
+version = "7.x-1.12"
 core = "7.x"
 project = "search_api_solr"
-datestamp = "1456233244"
+datestamp = "1487844794"
 

+ 31 - 0
sites/all/modules/contrib/search/search_api_solr/solr-conf/6.x/elevate.xml

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!--
+ This file allows you to boost certain search items to the top of search
+ results. You can find out an item's ID by searching directly on the Solr
+ server. The item IDs are in general constructed as follows:
+   Search API:
+     $document->id = $index_id . '-' . $item_id;
+   Apache Solr Search Integration:
+     $document->id = $site_hash . '/' . $entity_type . '/' . $entity->id;
+
+ If you want this file to be automatically re-loaded when a Solr commit takes
+ place (e.g., if you have an automatic script active which updates elevate.xml
+ according to newly-indexed data), place it into Solr's data/ directory.
+ Otherwise, place it with the other configuration files into the conf/
+ directory.
+
+ See http://wiki.apache.org/solr/QueryElevationComponent for more information.
+-->
+
+<elevate>
+<!-- Example for ranking the node #1 first in searches for "example query": -->
+<!--
+ <query text="example query">
+  <doc id="default_node_index-1" />
+  <doc id="7v3jsc/node/1" />
+ </query>
+-->
+<!-- Multiple <query> elements can be specified, contained in one <elevate>. -->
+<!-- <query text="...">...</query> -->
+</elevate>

+ 14 - 0
sites/all/modules/contrib/search/search_api_solr/solr-conf/6.x/mapping-ISOLatin1Accent.txt

@@ -0,0 +1,14 @@
+# This file contains character mappings for the default fulltext field type.
+# The source characters (on the left) will be replaced by the respective target
+# characters before any other processing takes place.
+# Lines starting with a pound character # are ignored.
+#
+# For sensible defaults, use the mapping-ISOLatin1Accent.txt file distributed
+# with the example application of your Solr version.
+#
+# Examples:
+#   "À" => "A"
+#   "\u00c4" => "A"
+#   "\u00c4" => "\u0041"
+#   "æ" => "ae"
+#   "\n" => " "

+ 7 - 0
sites/all/modules/contrib/search/search_api_solr/solr-conf/6.x/protwords.txt

@@ -0,0 +1,7 @@
+#-----------------------------------------------------------------------
+# This file blocks words from being operated on by the stemmer and word delimiter.
+&amp;
+&lt;
+&gt;
+&#039;
+&quot;

+ 693 - 0
sites/all/modules/contrib/search/search_api_solr/solr-conf/6.x/schema.xml

@@ -0,0 +1,693 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!--
+ This is the Solr schema file. This file should be named "schema.xml" and
+ should be in the conf directory under the solr home
+ (i.e. ./solr/conf/schema.xml by default)
+ or located where the classloader for the Solr webapp can find it.
+
+ For more information, on how to customize this file, please see
+ http://wiki.apache.org/solr/SchemaXml
+-->
+
+<schema name="drupal-4.4-solr-6.x" version="1.5">
+    <!-- attribute "name" is the name of this schema and is only used for
+         display purposes. Applications should change this to reflect the nature
+         of the search collection.
+         version="1.2" is Solr's version number for the schema syntax and
+         semantics. It should not normally be changed by applications.
+
+         1.0: multiValued attribute did not exist, all fields are multiValued by
+              nature
+         1.1: multiValued attribute introduced, false by default
+         1.2: omitTermFreqAndPositions attribute introduced, true by default
+              except for text fields.
+         1.3: removed optional field compress feature
+         1.4: autoGeneratePhraseQueries attribute introduced to drive
+              QueryParser behavior when a single string produces multiple
+              tokens. Defaults to off for version >= 1.4
+         1.5: omitNorms defaults to true for primitive field types
+              (int, float, boolean, string...)
+       -->
+
+  <types>
+    <!-- field type definitions. The "name" attribute is
+       just a label to be used by field definitions.  The "class"
+       attribute and any other attributes determine the real
+       behavior of the fieldType.
+         Class names starting with "solr" refer to java classes in the
+       org.apache.solr.analysis package.
+    -->
+
+    <!-- The StrField type is not analyzed, but indexed/stored verbatim.
+       - StrField and TextField support an optional compressThreshold which
+       limits compression (if enabled in the derived fields) to values which
+       exceed a certain size (in characters).
+    -->
+    <fieldType name="string" class="solr.StrField" sortMissingLast="true"/>
+
+    <!-- boolean type: "true" or "false" -->
+    <fieldType name="boolean" class="solr.BoolField" sortMissingLast="true"/>
+    <!--Binary data type. The data should be sent/retrieved in as Base64 encoded Strings -->
+    <fieldtype name="binary" class="solr.BinaryField"/>
+
+    <!-- The optional sortMissingLast and sortMissingFirst attributes are
+         currently supported on types that are sorted internally as strings.
+       - If sortMissingLast="true", then a sort on this field will cause documents
+         without the field to come after documents with the field,
+         regardless of the requested sort order (asc or desc).
+       - If sortMissingFirst="true", then a sort on this field will cause documents
+         without the field to come before documents with the field,
+         regardless of the requested sort order.
+       - If sortMissingLast="false" and sortMissingFirst="false" (the default),
+         then default lucene sorting will be used which places docs without the
+         field first in an ascending sort and last in a descending sort.
+    -->
+
+    <!-- numeric field types that can be sorted, but are not optimized for range queries -->
+    <fieldType name="integer" class="solr.TrieIntField" precisionStep="0" positionIncrementGap="0"/>
+    <fieldType name="float" class="solr.TrieFloatField" precisionStep="0" positionIncrementGap="0"/>
+    <fieldType name="long" class="solr.TrieLongField" precisionStep="0" positionIncrementGap="0"/>
+    <fieldType name="double" class="solr.TrieDoubleField" precisionStep="0" positionIncrementGap="0"/>
+
+    <!--
+      Note:
+      These should only be used for compatibility with existing indexes (created with older Solr versions)
+      or if "sortMissingFirst" or "sortMissingLast" functionality is needed. Use Trie based fields instead.
+
+      Numeric field types that manipulate the value into
+      a string value that isn't human-readable in its internal form,
+      but with a lexicographic ordering the same as the numeric ordering,
+      so that range queries work correctly.
+    -->
+    <fieldType name="sint" class="solr.TrieIntField" sortMissingLast="true"/>
+    <fieldType name="sfloat" class="solr.TrieFloatField" sortMissingLast="true"/>
+    <fieldType name="slong" class="solr.TrieLongField" sortMissingLast="true"/>
+    <fieldType name="sdouble" class="solr.TrieDoubleField" sortMissingLast="true"/>
+
+    <!--
+     Numeric field types that index each value at various levels of precision
+     to accelerate range queries when the number of values between the range
+     endpoints is large. See the javadoc for NumericRangeQuery for internal
+     implementation details.
+
+     Smaller precisionStep values (specified in bits) will lead to more tokens
+     indexed per value, slightly larger index size, and faster range queries.
+     A precisionStep of 0 disables indexing at different precision levels.
+    -->
+    <fieldType name="tint" class="solr.TrieIntField" precisionStep="8" positionIncrementGap="0"/>
+    <fieldType name="tfloat" class="solr.TrieFloatField" precisionStep="8" positionIncrementGap="0"/>
+    <fieldType name="tlong" class="solr.TrieLongField" precisionStep="8" positionIncrementGap="0"/>
+    <fieldType name="tdouble" class="solr.TrieDoubleField" precisionStep="8" positionIncrementGap="0"/>
+
+    <!--
+     The ExternalFileField type gets values from an external file instead of the
+     index. This is useful for data such as rankings that might change frequently
+     and require different update frequencies than the documents they are
+     associated with.
+    -->
+    <fieldType name="file" keyField="id" defVal="1" stored="false" indexed="false" class="solr.ExternalFileField" valType="float"/>
+
+    <!-- The format for this date field is of the form 1995-12-31T23:59:59Z, and
+         is a more restricted form of the canonical representation of dateTime
+         http://www.w3.org/TR/xmlschema-2/#dateTime
+         The trailing "Z" designates UTC time and is mandatory.
+         Optional fractional seconds are allowed: 1995-12-31T23:59:59.999Z
+         All other components are mandatory.
+
+         Expressions can also be used to denote calculations that should be
+         performed relative to "NOW" to determine the value, ie...
+
+               NOW/HOUR
+                  ... Round to the start of the current hour
+               NOW-1DAY
+                  ... Exactly 1 day prior to now
+               NOW/DAY+6MONTHS+3DAYS
+                  ... 6 months and 3 days in the future from the start of
+                      the current day
+
+         Consult the TrieDateField javadocs for more information.
+
+         Note: For faster range queries, consider the tdate type
+      -->
+    <fieldType name="date" class="solr.TrieDateField" precisionStep="0" positionIncrementGap="0" sortMissingLast="true" omitNorms="true"/>
+
+    <!-- A Trie based date field for faster date range queries and date faceting. -->
+    <fieldType name="tdate" class="solr.TrieDateField" precisionStep="6" positionIncrementGap="0"/>
+
+    <!-- solr.TextField allows the specification of custom text analyzers
+         specified as a tokenizer and a list of token filters. Different
+         analyzers may be specified for indexing and querying.
+
+         The optional positionIncrementGap puts space between multiple fields of
+         this type on the same document, with the purpose of preventing false phrase
+         matching across fields.
+
+         For more info on customizing your analyzer chain, please see
+         http://wiki.apache.org/solr/AnalyzersTokenizersTokenFilters
+     -->
+
+    <!-- One can also specify an existing Analyzer class that has a
+         default constructor via the class attribute on the analyzer element
+    <fieldType name="text_greek" class="solr.TextField">
+      <analyzer class="org.apache.lucene.analysis.el.GreekAnalyzer"/>
+    </fieldType>
+    -->
+
+    <!-- A text field that only splits on whitespace for exact matching of words -->
+    <fieldType name="text_ws" class="solr.TextField" omitNorms="true" positionIncrementGap="100">
+      <analyzer>
+        <tokenizer class="solr.WhitespaceTokenizerFactory"/>
+        <filter class="solr.LowerCaseFilterFactory"/>
+      </analyzer>
+    </fieldType>
+
+    <!-- A text field that uses WordDelimiterFilter to enable splitting and matching of
+        words on case-change, alpha numeric boundaries, and non-alphanumeric chars,
+        so that a query of "wifi" or "wi fi" could match a document containing "Wi-Fi".
+        Synonyms and stopwords are customized by external files, and stemming is enabled.
+        Duplicate tokens at the same position (which may result from Stemmed Synonyms or
+        WordDelim parts) are removed.
+        -->
+    <fieldType name="text" class="solr.TextField" positionIncrementGap="100">
+      <analyzer type="index">
+        <charFilter class="solr.MappingCharFilterFactory" mapping="mapping-ISOLatin1Accent.txt"/>
+        <tokenizer class="solr.WhitespaceTokenizerFactory"/>
+        <!-- in this example, we will only use synonyms at query time
+        <filter class="solr.SynonymFilterFactory" synonyms="index_synonyms.txt" ignoreCase="true" expand="false"/>
+        -->
+        <!-- Case insensitive stop word removal. -->
+        <filter class="solr.StopFilterFactory"
+                ignoreCase="true"
+                words="stopwords.txt"
+                />
+        <filter class="solr.WordDelimiterFilterFactory"
+                protected="protwords.txt"
+                generateWordParts="1"
+                generateNumberParts="1"
+                catenateWords="1"
+                catenateNumbers="1"
+                catenateAll="0"
+                splitOnCaseChange="0"
+                preserveOriginal="1"/>
+        <filter class="solr.LengthFilterFactory" min="2" max="100" />
+        <filter class="solr.LowerCaseFilterFactory"/>
+        <filter class="solr.SnowballPorterFilterFactory" language="English" protected="protwords.txt"/>
+        <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
+      </analyzer>
+      <analyzer type="query">
+        <charFilter class="solr.MappingCharFilterFactory" mapping="mapping-ISOLatin1Accent.txt"/>
+        <tokenizer class="solr.WhitespaceTokenizerFactory"/>
+        <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
+        <filter class="solr.StopFilterFactory"
+                ignoreCase="true"
+                words="stopwords.txt"
+                />
+        <filter class="solr.WordDelimiterFilterFactory"
+                protected="protwords.txt"
+                generateWordParts="1"
+                generateNumberParts="1"
+                catenateWords="0"
+                catenateNumbers="0"
+                catenateAll="0"
+                splitOnCaseChange="0"
+                preserveOriginal="1"/>
+        <filter class="solr.LengthFilterFactory" min="2" max="100" />
+        <filter class="solr.LowerCaseFilterFactory"/>
+        <filter class="solr.SnowballPorterFilterFactory" language="English" protected="protwords.txt"/>
+        <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
+      </analyzer>
+      <analyzer type="multiterm">
+        <charFilter class="solr.MappingCharFilterFactory" mapping="mapping-ISOLatin1Accent.txt"/>
+        <tokenizer class="solr.WhitespaceTokenizerFactory"/>
+        <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
+        <filter class="solr.StopFilterFactory"
+                ignoreCase="true"
+                words="stopwords.txt"
+                />
+        <filter class="solr.WordDelimiterFilterFactory"
+                protected="protwords.txt"
+                generateWordParts="1"
+                generateNumberParts="1"
+                catenateWords="0"
+                catenateNumbers="0"
+                catenateAll="0"
+                splitOnCaseChange="1"
+                preserveOriginal="1"/>
+        <filter class="solr.LengthFilterFactory" min="2" max="100" />
+        <filter class="solr.LowerCaseFilterFactory"/>
+        <filter class="solr.SnowballPorterFilterFactory" language="English" protected="protwords.txt"/>
+        <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
+      </analyzer>
+    </fieldType>
+
+    <!-- An unstemmed text field - good if one does not know the language of the field -->
+    <fieldType name="text_und" class="solr.TextField" positionIncrementGap="100">
+      <analyzer type="index">
+        <tokenizer class="solr.WhitespaceTokenizerFactory"/>
+        <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
+        <filter class="solr.WordDelimiterFilterFactory"
+                protected="protwords.txt"
+                generateWordParts="1"
+                generateNumberParts="1"
+                catenateWords="1"
+                catenateNumbers="1"
+                catenateAll="0"
+                splitOnCaseChange="0"/>
+        <filter class="solr.LengthFilterFactory" min="2" max="100" />
+        <filter class="solr.LowerCaseFilterFactory"/>
+      </analyzer>
+      <analyzer type="query">
+        <tokenizer class="solr.WhitespaceTokenizerFactory"/>
+        <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
+        <filter class="solr.StopFilterFactory"
+                ignoreCase="true"
+                words="stopwords.txt"
+                />
+        <filter class="solr.WordDelimiterFilterFactory"
+                protected="protwords.txt"
+                generateWordParts="1"
+                generateNumberParts="1"
+                catenateWords="0"
+                catenateNumbers="0"
+                catenateAll="0"
+                splitOnCaseChange="0"/>
+        <filter class="solr.LengthFilterFactory" min="2" max="100" />
+        <filter class="solr.LowerCaseFilterFactory"/>
+      </analyzer>
+      <analyzer type="multiterm">
+        <tokenizer class="solr.WhitespaceTokenizerFactory"/>
+        <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
+        <filter class="solr.StopFilterFactory"
+                ignoreCase="true"
+                words="stopwords.txt"
+                />
+        <filter class="solr.WordDelimiterFilterFactory"
+                protected="protwords.txt"
+                generateWordParts="1"
+                generateNumberParts="1"
+                catenateWords="0"
+                catenateNumbers="0"
+                catenateAll="0"
+                splitOnCaseChange="0"/>
+        <filter class="solr.LengthFilterFactory" min="2" max="100" />
+        <filter class="solr.LowerCaseFilterFactory"/>
+      </analyzer>
+    </fieldType>
+
+    <!-- Edge N gram type - for example for matching against queries with results
+        KeywordTokenizer leaves input string intact as a single term.
+        see: http://www.lucidimagination.com/blog/2009/09/08/auto-suggest-from-popular-queries-using-edgengrams/
+      -->
+    <fieldType name="edge_n2_kw_text" class="solr.TextField" omitNorms="true" positionIncrementGap="100">
+      <analyzer type="index">
+        <tokenizer class="solr.KeywordTokenizerFactory"/>
+        <filter class="solr.LowerCaseFilterFactory"/>
+        <filter class="solr.EdgeNGramFilterFactory" minGramSize="2" maxGramSize="25" />
+      </analyzer>
+      <analyzer type="query">
+        <tokenizer class="solr.KeywordTokenizerFactory"/>
+        <filter class="solr.LowerCaseFilterFactory"/>
+      </analyzer>
+    </fieldType>
+    <!--  Setup simple analysis for spell checking -->
+
+    <fieldType name="textSpell" class="solr.TextField" positionIncrementGap="100">
+      <analyzer>
+        <tokenizer class="solr.StandardTokenizerFactory" />
+        <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
+        <filter class="solr.LengthFilterFactory" min="4" max="20" />
+        <filter class="solr.LowerCaseFilterFactory" />
+        <filter class="solr.RemoveDuplicatesTokenFilterFactory" />
+      </analyzer>
+    </fieldType>
+
+    <!-- This is an example of using the KeywordTokenizer along
+         With various TokenFilterFactories to produce a sortable field
+         that does not include some properties of the source text
+      -->
+    <fieldType name="sortString" class="solr.TextField" sortMissingLast="true" omitNorms="true">
+      <analyzer>
+        <!-- KeywordTokenizer does no actual tokenizing, so the entire
+            input string is preserved as a single token
+          -->
+        <tokenizer class="solr.KeywordTokenizerFactory"/>
+        <!-- The LowerCase TokenFilter does what you expect, which can be
+            when you want your sorting to be case insensitive
+          -->
+        <filter class="solr.LowerCaseFilterFactory" />
+        <!-- The TrimFilter removes any leading or trailing whitespace -->
+        <filter class="solr.TrimFilterFactory" />
+        <!-- The PatternReplaceFilter gives you the flexibility to use
+            Java Regular expression to replace any sequence of characters
+            matching a pattern with an arbitrary replacement string,
+            which may include back refrences to portions of the orriginal
+            string matched by the pattern.
+
+            See the Java Regular Expression documentation for more
+            infomation on pattern and replacement string syntax.
+
+            http://java.sun.com/j2se/1.5.0/docs/api/java/util/regex/package-summary.html
+
+        <filter class="solr.PatternReplaceFilterFactory"
+               pattern="(^\p{Punct}+)" replacement="" replace="all"
+        />
+        -->
+      </analyzer>
+    </fieldType>
+
+    <!-- The "RandomSortField" is not used to store or search any
+         data.  You can declare fields of this type it in your schema
+         to generate pseudo-random orderings of your docs for sorting
+         or function purposes.  The ordering is generated based on the field
+         name and the version of the index. As long as the index version
+         remains unchanged, and the same field name is reused,
+         the ordering of the docs will be consistent.
+         If you want different psuedo-random orderings of documents,
+         for the same version of the index, use a dynamicField and
+         change the field name in the request.
+      -->
+    <fieldType name="rand" class="solr.RandomSortField" indexed="true" />
+
+    <!-- Fulltext type for matching words based on how they sound – i.e.,
+         "phonetic matching".
+      -->
+    <fieldType name="phonetic" class="solr.TextField" >
+      <analyzer>
+        <tokenizer class="solr.StandardTokenizerFactory"/>
+        <filter class="solr.DoubleMetaphoneFilterFactory" inject="false"/>
+      </analyzer>
+    </fieldType>
+
+    <!-- since fields of this type are by default not stored or indexed,
+         any data added to them will be ignored outright.  -->
+    <fieldType name="ignored" stored="false" indexed="false" multiValued="true" class="solr.StrField" />
+
+    <!-- This point type indexes the coordinates as separate fields (subFields)
+      If subFieldType is defined, it references a type, and a dynamic field
+      definition is created matching *___<typename>.  Alternately, if
+      subFieldSuffix is defined, that is used to create the subFields.
+      Example: if subFieldType="double", then the coordinates would be
+        indexed in fields myloc_0___double,myloc_1___double.
+      Example: if subFieldSuffix="_d" then the coordinates would be indexed
+        in fields myloc_0_d,myloc_1_d
+      The subFields are an implementation detail of the fieldType, and end
+      users normally should not need to know about them.
+     -->
+    <fieldType name="point" class="solr.PointType" dimension="2" subFieldType="tdouble"/>
+
+    <!-- A specialized field for geospatial search. If indexed, this fieldType must not be multivalued. -->
+    <fieldType name="location" class="solr.LatLonType" subFieldType="tdouble"/>
+
+    <!-- A Geohash is a compact representation of a latitude longitude pair in a single field.
+         See http://wiki.apache.org/solr/SpatialSearch
+     -->
+    <fieldtype name="geohash" class="solr.GeoHashField"/>
+
+    <!-- Improved location type which supports advanced functionality like
+         filtering by polygons or other shapes, indexing shapes, multi-valued
+         fields, etc.
+      -->
+    <fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
+        geo="true" distErrPct="0.025" maxDistErr="0.001" distanceUnits="kilometers" />
+
+    <!-- Spatial rectangle (bounding box) field. It supports most spatial predicates, and has
+     special relevancy modes: score=overlapRatio|area|area2D (local-param to the query).  DocValues is recommended for
+     relevancy. -->
+    <fieldType name="bbox" class="solr.BBoxField"
+               geo="true" distanceUnits="kilometers" numberType="_bbox_coord" />
+    <fieldType name="_bbox_coord" class="solr.TrieDoubleField" precisionStep="8" docValues="true" stored="false"/>
+
+  </types>
+
+  <!-- Following is a dynamic way to include other types, added by other contrib modules -->
+  <xi:include href="schema_extra_types.xml" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <xi:fallback></xi:fallback>
+  </xi:include>
+
+   <!-- Valid attributes for fields:
+     name: mandatory - the name for the field
+     type: mandatory - the name of a field type from the <types> fieldType
+       section
+     indexed: true if this field should be indexed (searchable or sortable)
+     stored: true if this field should be retrievable
+     docValues: true if this field should have doc values. Doc values are
+       useful for faceting, grouping, sorting and function queries. Although not
+       required, doc values will make the index faster to load, more
+       NRT-friendly and more memory-efficient. They however come with some
+       limitations: they are currently only supported by StrField, UUIDField
+       and all Trie*Fields, and depending on the field type, they might
+       require the field to be single-valued, be required or have a default
+       value (check the documentation of the field type you're interested in
+       for more information)
+     multiValued: true if this field may contain multiple values per document
+     omitNorms: (expert) set to true to omit the norms associated with
+       this field (this disables length normalization and index-time
+       boosting for the field, and saves some memory).  Only full-text
+       fields or fields that need an index-time boost need norms.
+       Norms are omitted for primitive (non-analyzed) types by default.
+     termVectors: [false] set to true to store the term vector for a
+       given field.
+       When using MoreLikeThis, fields used for similarity should be
+       stored for best performance.
+     termPositions: Store position information with the term vector.
+       This will increase storage costs.
+     termOffsets: Store offset information with the term vector. This
+       will increase storage costs.
+     required: The field is required.  It will throw an error if the
+       value does not exist
+     default: a value that should be used if no value is specified
+       when adding a document.
+   -->
+  <fields>
+
+    <!-- The document id is usually derived from a site-spcific key (hash) and the
+      entity type and ID like:
+      Search Api :
+        The format used is $document->id = $index_id . '-' . $item_id
+      Apache Solr Search Integration
+        The format used is $document->id = $site_hash . '/' . $entity_type . '/' . $entity->id;
+    -->
+    <field name="id" type="string" indexed="true" stored="true" required="true" />
+
+    <!-- Add Solr Cloud version field as mentioned in
+         http://wiki.apache.org/solr/SolrCloud#Required_Config
+    -->
+    <field name="_version_" type="long" indexed="true" stored="true" multiValued="false"/>
+
+    <!-- Search Api specific fields -->
+    <!-- item_id contains the entity ID, e.g. a node's nid. -->
+    <field name="item_id"  type="string" indexed="true" stored="true" />
+    <!-- index_id is the machine name of the search index this entry belongs to. -->
+    <field name="index_id" type="string" indexed="true" stored="true" />
+  <!-- copyField commands copy one field to another at the time a document
+        is added to the index.  It's used either to index the same field differently,
+        or to add multiple fields to the same field for easier/faster searching.  -->
+    <!-- Since sorting by ID is explicitly allowed, store item_id also in a sortable way. -->
+    <copyField source="item_id" dest="sort_search_api_id" />
+
+    <!-- Apache Solr Search Integration specific fields -->
+    <!-- entity_id is the numeric object ID, e.g. Node ID, File ID -->
+    <field name="entity_id"  type="long" indexed="true" stored="true" />
+    <!-- entity_type is 'node', 'file', 'user', or some other Drupal object type -->
+    <field name="entity_type" type="string" indexed="true" stored="true" />
+    <!-- bundle is a node type, or as appropriate for other entity types -->
+    <field name="bundle" type="string" indexed="true" stored="true"/>
+    <field name="bundle_name" type="string" indexed="true" stored="true"/>
+    <field name="site" type="string" indexed="true" stored="true"/>
+    <field name="hash" type="string" indexed="true" stored="true"/>
+    <field name="url" type="string" indexed="true" stored="true"/>
+    <!-- label is the default field for a human-readable string for this entity (e.g. the title of a node) -->
+    <field name="label" type="text" indexed="true" stored="true" termVectors="true" omitNorms="true"/>
+    <!-- The string version of the title is used for sorting -->
+    <copyField source="label" dest="sort_label"/>
+
+    <!-- content is the default field for full text search - dump crap here -->
+    <field name="content" type="text" indexed="true" stored="true" termVectors="true"/>
+    <field name="teaser" type="text" indexed="false" stored="true"/>
+    <field name="path" type="string" indexed="true" stored="true"/>
+    <field name="path_alias" type="text" indexed="true" stored="true" termVectors="true" omitNorms="true"/>
+
+    <!-- These are the fields that correspond to a Drupal node. The beauty of having
+      Lucene store title, body, type, etc., is that we retrieve them with the search
+      result set and don't need to go to the database with a node_load. -->
+    <field name="tid"  type="long" indexed="true" stored="true" multiValued="true"/>
+    <field name="taxonomy_names" type="text" indexed="true" stored="false" termVectors="true" multiValued="true" omitNorms="true"/>
+    <!-- Copy terms to a single field that contains all taxonomy term names -->
+    <copyField source="tm_vid_*" dest="taxonomy_names"/>
+
+    <!-- Here, default is used to create a "timestamp" field indicating
+         when each document was indexed.-->
+    <field name="timestamp" type="tdate" indexed="true" stored="true" default="NOW" multiValued="false"/>
+
+    <!-- This field is used to build the spellchecker index -->
+    <field name="spell" type="textSpell" indexed="true" stored="true" multiValued="true"/>
+
+    <!-- copyField commands copy one field to another at the time a document
+         is added to the index.  It's used either to index the same field differently,
+         or to add multiple fields to the same field for easier/faster searching.  -->
+    <copyField source="label" dest="spell"/>
+    <copyField source="content" dest="spell"/>
+
+    <copyField source="ts_*" dest="spell"/>
+    <copyField source="tm_*" dest="spell"/>
+
+    <!-- Dynamic field definitions.  If a field name is not found, dynamicFields
+         will be used if the name matches any of the patterns.
+         RESTRICTION: the glob-like pattern in the name attribute must have
+         a "*" only at the start or the end.
+         EXAMPLE:  name="*_i" will match any field ending in _i (like myid_i, z_i)
+         Longer patterns will be matched first.  if equal size patterns
+         both match, the first appearing in the schema will be used.  -->
+
+    <!-- A set of fields to contain text extracted from HTML tag contents which we
+         can boost at query time. -->
+    <dynamicField name="tags_*" type="text"   indexed="true" stored="false" omitNorms="true"/>
+
+    <!-- For 2 and 3 letter prefix dynamic fields, the 1st letter indicates the data type and
+         the last letter is 's' for single valued, 'm' for multi-valued -->
+
+    <!-- We use long for integer since 64 bit ints are now common in PHP. -->
+    <dynamicField name="is_*"  type="long"    indexed="true"  stored="true" multiValued="false"/>
+    <dynamicField name="im_*"  type="long"    indexed="true"  stored="true" multiValued="true"/>
+    <!-- List of floats can be saved in a regular float field -->
+    <dynamicField name="fs_*"  type="float"   indexed="true"  stored="true" multiValued="false"/>
+    <dynamicField name="fm_*"  type="float"   indexed="true"  stored="true" multiValued="true"/>
+    <!-- List of doubles can be saved in a regular double field -->
+    <dynamicField name="ps_*"  type="double"   indexed="true"  stored="true" multiValued="false"/>
+    <dynamicField name="pm_*"  type="double"   indexed="true"  stored="true" multiValued="true"/>
+    <!-- List of booleans can be saved in a regular boolean field -->
+    <dynamicField name="bm_*"  type="boolean" indexed="true"  stored="true" multiValued="true"/>
+    <dynamicField name="bs_*"  type="boolean" indexed="true"  stored="true" multiValued="false"/>
+    <!-- Regular text (without processing) can be stored in a string field-->
+    <dynamicField name="ss_*"  type="string"  indexed="true"  stored="true" multiValued="false"/>
+    <dynamicField name="sm_*"  type="string"  indexed="true"  stored="true" multiValued="true"/>
+    <!-- Normal text fields are for full text - the relevance of a match depends on the length of the text -->
+    <dynamicField name="ts_*"  type="text"    indexed="true"  stored="true" multiValued="false" termVectors="true"/>
+    <dynamicField name="tm_*"  type="text"    indexed="true"  stored="true" multiValued="true" termVectors="true"/>
+    <!-- Unstemmed text fields for full text - the relevance of a match depends on the length of the text -->
+    <dynamicField name="tus_*" type="text_und" indexed="true"  stored="true" multiValued="false" termVectors="true"/>
+    <dynamicField name="tum_*" type="text_und" indexed="true"  stored="true" multiValued="true" termVectors="true"/>
+    <!-- These text fields omit norms - useful for extracted text like taxonomy_names -->
+    <dynamicField name="tos_*" type="text"    indexed="true"  stored="true" multiValued="false" termVectors="true" omitNorms="true"/>
+    <dynamicField name="tom_*" type="text"    indexed="true"  stored="true" multiValued="true" termVectors="true" omitNorms="true"/>
+    <!-- Special-purpose text fields -->
+    <dynamicField name="tes_*" type="edge_n2_kw_text" indexed="true" stored="true" multiValued="false" omitTermFreqAndPositions="true" />
+    <dynamicField name="tem_*" type="edge_n2_kw_text" indexed="true" stored="true" multiValued="true" omitTermFreqAndPositions="true" />
+    <dynamicField name="tws_*" type="text_ws" indexed="true" stored="true" multiValued="false"/>
+    <dynamicField name="twm_*" type="text_ws" indexed="true" stored="true" multiValued="true"/>
+
+    <!-- trie dates are preferred, so give them the 2 letter prefix -->
+    <dynamicField name="ds_*"  type="tdate"   indexed="true"  stored="true" multiValued="false"/>
+    <dynamicField name="dm_*"  type="tdate"   indexed="true"  stored="true" multiValued="true"/>
+    <dynamicField name="its_*" type="tlong"   indexed="true"  stored="true" multiValued="false"/>
+    <dynamicField name="itm_*" type="tlong"   indexed="true"  stored="true" multiValued="true"/>
+    <dynamicField name="fts_*" type="tfloat"  indexed="true"  stored="true" multiValued="false"/>
+    <dynamicField name="ftm_*" type="tfloat"  indexed="true"  stored="true" multiValued="true"/>
+    <dynamicField name="pts_*" type="tdouble" indexed="true"  stored="true" multiValued="false"/>
+    <dynamicField name="ptm_*" type="tdouble" indexed="true"  stored="true" multiValued="true"/>
+    <!-- Binary fields can be populated using base64 encoded data. Useful e.g. for embedding
+         a small image in a search result using the data URI scheme -->
+    <dynamicField name="xs_*"  type="binary"  indexed="false" stored="true" multiValued="false"/>
+    <dynamicField name="xm_*"  type="binary"  indexed="false" stored="true" multiValued="true"/>
+    <!-- In rare cases a date rather than tdate is needed for sortMissingLast -->
+    <dynamicField name="dds_*" type="date"    indexed="true"  stored="true" multiValued="false"/>
+    <dynamicField name="ddm_*" type="date"    indexed="true"  stored="true" multiValued="true"/>
+    <!-- Sortable fields, good for sortMissingLast support &
+         We use long for integer since 64 bit ints are now common in PHP. -->
+    <dynamicField name="iss_*" type="slong"   indexed="true"  stored="true" multiValued="false"/>
+    <dynamicField name="ism_*" type="slong"   indexed="true"  stored="true" multiValued="true"/>
+    <!-- In rare cases a sfloat rather than tfloat is needed for sortMissingLast -->
+    <dynamicField name="fss_*" type="sfloat"  indexed="true"  stored="true" multiValued="false"/>
+    <dynamicField name="fsm_*" type="sfloat"  indexed="true"  stored="true" multiValued="true"/>
+    <dynamicField name="pss_*" type="sdouble" indexed="true"  stored="true" multiValued="false"/>
+    <dynamicField name="psm_*" type="sdouble" indexed="true"  stored="true" multiValued="true"/>
+    <!-- In case a 32 bit int is really needed, we provide these fields. 'h' is mnemonic for 'half word', i.e. 32 bit on 64 arch -->
+    <dynamicField name="hs_*" type="integer" indexed="true"  stored="true" multiValued="false"/>
+    <dynamicField name="hm_*" type="integer" indexed="true"  stored="true" multiValued="true"/>
+    <dynamicField name="hss_*" type="sint"   indexed="true"  stored="true" multiValued="false"/>
+    <dynamicField name="hsm_*" type="sint"   indexed="true"  stored="true" multiValued="true"/>
+    <dynamicField name="hts_*" type="tint"   indexed="true"  stored="true" multiValued="false"/>
+    <dynamicField name="htm_*" type="tint"   indexed="true"  stored="true" multiValued="true"/>
+
+    <!-- Unindexed string fields that can be used to store values that won't be searchable -->
+    <dynamicField name="zs_*" type="string"   indexed="false"  stored="true" multiValued="false"/>
+    <dynamicField name="zm_*" type="string"   indexed="false"  stored="true" multiValued="true"/>
+
+    <!-- Fields for location searches.
+         http://wiki.apache.org/solr/SpatialSearch#geodist_-_The_distance_function -->
+    <dynamicField name="points_*" type="point" indexed="true"  stored="true" multiValued="false"/>
+    <dynamicField name="pointm_*" type="point" indexed="true"  stored="true" multiValued="true"/>
+    <dynamicField name="locs_*" type="location" indexed="true"  stored="true" multiValued="false"/>
+    <dynamicField name="locm_*" type="location" indexed="true"  stored="true" multiValued="true"/>
+    <dynamicField name="geos_*" type="geohash" indexed="true"  stored="true" multiValued="false"/>
+    <dynamicField name="geom_*" type="geohash" indexed="true"  stored="true" multiValued="true"/>
+    <dynamicField name="bboxs_*" type="bbox" indexed="true" stored="true" multiValued="false" />
+    <dynamicField name="bboxm_*" type="bbox" indexed="true" stored="true" multiValued="true" />
+    <dynamicField name="rpts_*" type="location_rpt" indexed="true" stored="true" multiValued="false" />
+    <dynamicField name="rptm_*" type="location_rpt" indexed="true" stored="true" multiValued="true" />
+
+    <!-- Special fields for Solr 5 functionality. -->
+    <dynamicField name="phons_*" type="phonetic" indexed="true" stored="true" multiValued="false" />
+    <dynamicField name="phonm_*" type="phonetic" indexed="true" stored="true" multiValued="true" />
+
+    <!-- External file fields -->
+    <dynamicField name="eff_*" type="file"/>
+
+    <!-- Sortable version of the dynamic string field -->
+    <dynamicField name="sort_*" type="sortString" indexed="true" stored="false"/>
+    <copyField source="ss_*" dest="sort_*"/>
+
+    <!-- A random sort field -->
+    <dynamicField name="random_*" type="rand" indexed="true" stored="true"/>
+
+    <!-- This field is used to store access information (e.g. node access grants), as opposed to field data -->
+    <dynamicField name="access_*" type="integer" indexed="true" stored="false" multiValued="true"/>
+
+    <!-- The following causes solr to ignore any fields that don't already match an existing
+         field name or dynamic field, rather than reporting them as an error.
+         Alternately, change the type="ignored" to some other type e.g. "text" if you want
+         unknown fields indexed and/or stored by default -->
+    <dynamicField name="*" type="ignored" multiValued="true" />
+
+  </fields>
+
+  <!-- Following is a dynamic way to include other fields, added by other contrib modules -->
+  <xi:include href="schema_extra_fields.xml" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <xi:fallback></xi:fallback>
+  </xi:include>
+
+  <!-- Field to use to determine and enforce document uniqueness.
+       Unless this field is marked with required="false", it will be a required field
+    -->
+  <uniqueKey>id</uniqueKey>
+
+  <!-- Similarity is the scoring routine for each document vs. a query.
+       A custom Similarity or SimilarityFactory may be specified here, but
+       the default is fine for most applications.
+       For more info: http://wiki.apache.org/solr/SchemaXml#Similarity
+    -->
+  <!--
+     <similarity class="com.example.solr.CustomSimilarityFactory">
+       <str name="paramkey">param value</str>
+     </similarity>
+    -->
+
+  <!-- DEPRECATED: The defaultSearchField is consulted by various query parsers
+    when parsing a query string that isn't explicit about the field.  Machine
+    (non-user) generated queries are best made explicit, or they can use the
+    "df" request parameter which takes precedence over this.
+    Note: Un-commenting defaultSearchField will be insufficient if your request
+    handler in solrconfig.xml defines "df", which takes precedence. That would
+    need to be removed.
+  <defaultSearchField>content</defaultSearchField> -->
+
+  <!-- DEPRECATED: The defaultOperator (AND|OR) is consulted by various query
+    parsers when parsing a query string to determine if a clause of the query
+    should be marked as required or optional, assuming the clause isn't already
+    marked by some operator. The default is OR, which is generally assumed so it
+    is not a good idea to change it globally here. The "q.op" request parameter
+    takes precedence over this.
+  <solrQueryParser defaultOperator="OR"/> -->
+
+</schema>

+ 23 - 0
sites/all/modules/contrib/search/search_api_solr/solr-conf/6.x/schema_extra_fields.xml

@@ -0,0 +1,23 @@
+<fields>
+<!--
+  Example: Adding German dynamic field types to our Solr Schema.
+  If you enable this, make sure you have a folder called lang containing
+  stopwords_de.txt and synonyms_de.txt.
+  This also requires to enable the content in schema_extra_types.xml.
+-->
+<!--
+   <field name="label_de" type="text_de" indexed="true" stored="true" termVectors="true" omitNorms="true"/>
+   <field name="content_de" type="text_de" indexed="true" stored="true" termVectors="true"/>
+   <field name="teaser_de" type="text_de" indexed="false" stored="true"/>
+   <field name="path_alias_de" type="text_de" indexed="true" stored="true" termVectors="true" omitNorms="true"/>
+   <field name="taxonomy_names_de" type="text_de" indexed="true" stored="false" termVectors="true" multiValued="true" omitNorms="true"/>
+   <field name="spell_de" type="text_de" indexed="true" stored="true" multiValued="true"/>
+   <copyField source="label_de" dest="spell_de"/>
+   <copyField source="content_de" dest="spell_de"/>
+   <dynamicField name="tags_de_*" type="text_de" indexed="true" stored="false" omitNorms="true"/>
+   <dynamicField name="ts_de_*" type="text_de" indexed="true" stored="true" multiValued="false" termVectors="true"/>
+   <dynamicField name="tm_de_*" type="text_de" indexed="true" stored="true" multiValued="true" termVectors="true"/>
+   <dynamicField name="tos_de_*" type="text_de" indexed="true" stored="true" multiValued="false" termVectors="true" omitNorms="true"/>
+   <dynamicField name="tom_de_*" type="text_de" indexed="true" stored="true" multiValued="true" termVectors="true" omitNorms="true"/>
+-->
+</fields>

+ 34 - 0
sites/all/modules/contrib/search/search_api_solr/solr-conf/6.x/schema_extra_types.xml

@@ -0,0 +1,34 @@
+<types>
+<!--
+  Example: Adding German language field types to our Solr Schema.
+  If you enable this, make sure you have a folder called lang containing
+  stopwords_de.txt and synonyms_de.txt.
+
+  For examples from other languages, see
+  ./server/solr/configsets/sample_techproducts_configs/conf/schema.xml
+  from your Solr installation.
+-->
+<!--
+    <fieldType name="text_de" class="solr.TextField" positionIncrementGap="100">
+      <analyzer type="index">
+        <charFilter class="solr.MappingCharFilterFactory" mapping="mapping-ISOLatin1Accent.txt"/>
+        <tokenizer class="solr.WhitespaceTokenizerFactory"/>
+        <filter class="solr.StopFilterFactory" words="lang/stopwords_de.txt" format="snowball" ignoreCase="true"/>
+        <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" splitOnCaseChange="1" splitOnNumerics="1" catenateWords="1" catenateNumbers="1" catenateAll="0" protected="protwords.txt" preserveOriginal="1"/>
+        <filter class="solr.LowerCaseFilterFactory"/>
+        <filter class="solr.GermanLightStemFilterFactory"/>
+        <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
+      </analyzer>
+      <analyzer type="query">
+        <charFilter class="solr.MappingCharFilterFactory" mapping="mapping-ISOLatin1Accent.txt"/>
+        <tokenizer class="solr.WhitespaceTokenizerFactory"/>
+        <filter class="solr.SynonymFilterFactory" synonyms="lang/synonyms_de.txt" ignoreCase="true" expand="true"/>
+        <filter class="solr.StopFilterFactory" words="lang/stopwords_de.txt" format="snowball" ignoreCase="true"/>
+        <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" splitOnCaseChange="1" splitOnNumerics="1" catenateWords="0" catenateNumbers="0" catenateAll="0" protected="protwords.txt" preserveOriginal="1"/>
+        <filter class="solr.LowerCaseFilterFactory"/>
+        <filter class="solr.GermanLightStemFilterFactory"/>
+        <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
+      </analyzer>
+    </fieldType>
+-->
+</types>

+ 1793 - 0
sites/all/modules/contrib/search/search_api_solr/solr-conf/6.x/solrconfig.xml

@@ -0,0 +1,1793 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!--
+     For more details about configurations options that may appear in
+     this file, see http://wiki.apache.org/solr/SolrConfigXml.
+-->
+<config name="drupal-4.4-solr-6.x" >
+  <!-- In all configuration below, a prefix of "solr." for class names
+       is an alias that causes solr to search appropriate packages,
+       including org.apache.solr.(search|update|request|core|analysis)
+
+       You may also specify a fully qualified Java classname if you
+       have your own custom plugins.
+    -->
+
+  <!-- Set this to 'false' if you want solr to continue working after
+       it has encountered an severe configuration error.  In a
+       production environment, you may want solr to keep working even
+       if one handler is mis-configured.
+
+       You may also set this to false using by setting the system
+       property:
+
+         -Dsolr.abortOnConfigurationError=false
+    -->
+  <abortOnConfigurationError>${solr.abortOnConfigurationError:true}</abortOnConfigurationError>
+
+  <!-- Controls what version of Lucene various components of Solr
+       adhere to.  Generally, you want to use the latest version to
+       get all bug fixes and improvements. It is highly recommended
+       that you fully re-index after changing this setting as it can
+       affect both how text is indexed and queried.
+    -->
+  <luceneMatchVersion>${solr.luceneMatchVersion:LUCENE_60}</luceneMatchVersion>
+
+  <!-- <lib/> directives can be used to instruct Solr to load any Jars
+       identified and use them to resolve any "plugins" specified in
+       your solrconfig.xml or schema.xml (ie: Analyzers, Request
+       Handlers, etc...).
+
+       All directories and paths are resolved relative to the
+       instanceDir.
+
+       Please note that <lib/> directives are processed in the order
+       that they appear in your solrconfig.xml file, and are "stacked"
+       on top of each other when building a ClassLoader - so if you have
+       plugin jars with dependencies on other jars, the "lower level"
+       dependency jars should be loaded first.
+
+       If a "./lib" directory exists in your instanceDir, all files
+       found in it are included as if you had used the following
+       syntax...
+
+              <lib dir="./lib" />
+    -->
+
+  <!-- A "dir" option by itself adds any files found in the directory to the
+       classpath, this is useful for including all jars in a directory.
+    -->
+  <lib dir="${solr.contrib.dir:../../../contrib}/extraction/lib" />
+  <lib dir="${solr.contrib.dir:../../../contrib}/clustering/lib/" />
+
+  <!-- The velocity library has been known to crash Solr in some
+       instances when deployed as a war file to Tomcat. Therefore all
+       references have been removed from the default configuration.
+       @see http://drupal.org/node/1612556
+  -->
+  <!-- <lib dir="../../contrib/velocity/lib" /> -->
+
+  <!-- When a regex is specified in addition to a directory, only the
+       files in that directory which completely match the regex
+       (anchored on both ends) will be included.
+    -->
+  <!--<lib dir="../../dist/" regex="apache-solr-cell-\d.*\.jar" />-->
+  <!--<lib dir="../../dist/" regex="apache-solr-clustering-\d.*\.jar" />-->
+  <!--<lib dir="../../dist/" regex="apache-solr-dataimporthandler-\d.*\.jar" />-->
+  <!--<lib dir="../../dist/" regex="apache-solr-langid-\d.*\.jar" />-->
+  <!-- <lib dir="../../dist/" regex="apache-solr-velocity-\d.*\.jar" /> -->
+
+  <!-- If a dir option (with or without a regex) is used and nothing
+       is found that matches, it will be ignored
+    -->
+  <!--<lib dir="../../contrib/clustering/lib/" />-->
+  <!--<lib dir="/total/crap/dir/ignored" />-->
+
+  <!-- an exact path can be used to specify a specific file.  This
+       will cause a serious error to be logged if it can't be loaded.
+    -->
+  <!--
+  <lib path="../a-jar-that-does-not-exist.jar" />
+  -->
+
+  <!-- Data Directory
+
+       Used to specify an alternate directory to hold all index data
+       other than the default ./data under the Solr home.  If
+       replication is in use, this should match the replication
+       configuration.
+    -->
+  <dataDir>${solr.data.dir:}</dataDir>
+
+  <!-- The DirectoryFactory to use for indexes.
+
+       solr.StandardDirectoryFactory is filesystem
+       based and tries to pick the best implementation for the current
+       JVM and platform.  solr.NRTCachingDirectoryFactory, the default,
+       wraps solr.StandardDirectoryFactory and caches small files in memory
+       for better NRT performance.
+
+       One can force a particular implementation via solr.MMapDirectoryFactory,
+       solr.NIOFSDirectoryFactory, or solr.SimpleFSDirectoryFactory.
+
+       solr.RAMDirectoryFactory is memory based, not
+       persistent, and doesn't work with replication.
+    -->
+  <directoryFactory name="DirectoryFactory"
+                    class="${solr.directoryFactory:solr.NRTCachingDirectoryFactory}"/>
+
+  <!-- The CodecFactory for defining the format of the inverted index.
+       The default implementation is SchemaCodecFactory, which is the official
+       Lucene index format, but hooks into the schema to provide per-field
+       customization of the postings lists and per-document values in the
+       fieldType element (postingsFormat/docValuesFormat). Note that most of the
+       alternative implementations are experimental, so if you choose to
+       customize the index format, it's a good idea to convert back to the
+       official format e.g. via IndexWriter.addIndexes(IndexReader) before
+       upgrading to a newer version to avoid unnecessary reindexing.
+  -->
+  <codecFactory class="solr.SchemaCodecFactory"/>
+
+  <!-- To enable dynamic schema REST APIs, use the following for <schemaFactory>:
+
+       <schemaFactory class="ManagedIndexSchemaFactory">
+         <bool name="mutable">true</bool>
+         <str name="managedSchemaResourceName">managed-schema</str>
+       </schemaFactory>
+
+       When ManagedIndexSchemaFactory is specified, Solr will load the schema from
+       the resource named in 'managedSchemaResourceName', rather than from schema.xml.
+       Note that the managed schema resource CANNOT be named schema.xml.  If the managed
+       schema does not exist, Solr will create it after reading schema.xml, then rename
+       'schema.xml' to 'schema.xml.bak'.
+
+       Do NOT hand edit the managed schema - external modifications will be ignored and
+       overwritten as a result of schema modification REST API calls.
+
+       When ManagedIndexSchemaFactory is specified with mutable = true, schema
+       modification REST API calls will be allowed; otherwise, error responses will be
+       sent back for these requests.
+  -->
+  <schemaFactory class="ClassicIndexSchemaFactory"/>
+
+  <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+       Index Config - These settings control low-level behavior of indexing
+       Most example settings here show the default value, but are commented
+       out, to more easily see where customizations have been made.
+
+       Note: This replaces <indexDefaults> and <mainIndex> from older versions
+       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
+  <indexConfig>
+    <!-- maxFieldLength was removed in 4.0. To get similar behavior, include a
+         LimitTokenCountFilterFactory in your fieldType definition. E.g.
+     <filter class="solr.LimitTokenCountFilterFactory" maxTokenCount="10000"/>
+    -->
+    <!-- Maximum time to wait for a write lock (ms) for an IndexWriter. Default: 1000 -->
+    <!-- <writeLockTimeout>1000</writeLockTimeout>  -->
+
+    <!-- The maximum number of simultaneous threads that may be
+         indexing documents at once in IndexWriter; if more than this
+         many threads arrive they will wait for others to finish.
+         Default in Solr/Lucene is 8. -->
+    <!-- <maxIndexingThreads>8</maxIndexingThreads>  -->
+
+    <!-- Expert: Enabling compound file will use less files for the index,
+         using fewer file descriptors on the expense of performance decrease.
+         Default in Lucene is "true". Default in Solr is "false" (since 3.6) -->
+    <!-- <useCompoundFile>false</useCompoundFile> -->
+
+    <!-- ramBufferSizeMB sets the amount of RAM that may be used by Lucene
+         indexing for buffering added documents and deletions before they are
+         flushed to the Directory.
+         maxBufferedDocs sets a limit on the number of documents buffered
+         before flushing.
+         If both ramBufferSizeMB and maxBufferedDocs is set, then
+         Lucene will flush based on whichever limit is hit first.
+         The default is 100 MB.  -->
+    <ramBufferSizeMB>32</ramBufferSizeMB>
+    <!-- <maxBufferedDocs>1000</maxBufferedDocs> -->
+
+    <!-- Expert: Merge Policy
+
+         The Merge Policy in Lucene controls how merging is handled by
+         Lucene.  The default in Solr 3.3 is TieredMergePolicy.
+
+         The default in 2.3 was the LogByteSizeMergePolicy,
+         previous versions used LogDocMergePolicy.
+
+         LogByteSizeMergePolicy chooses segments to merge based on
+         their size.  The Lucene 2.2 default, LogDocMergePolicy chose
+         when to merge based on number of documents
+
+         Other implementations of MergePolicy must have a no-argument
+         constructor
+      -->
+
+    <mergePolicyFactory class="org.apache.solr.index.LogByteSizeMergePolicyFactory">
+      <!-- Merge Factor
+           The merge factor controls how many segments will get merged at a time.
+           For TieredMergePolicy, mergeFactor is a convenience parameter which
+           will set both MaxMergeAtOnce and SegmentsPerTier at once.
+           For LogByteSizeMergePolicy, mergeFactor decides how many new segments
+           will be allowed before they are merged into one.
+        -->
+      <int name="mergeFactor">4</int>
+    </mergePolicyFactory>
+
+    <!-- Expert: Merge Scheduler
+
+         The Merge Scheduler in Lucene controls how merges are
+         performed.  The ConcurrentMergeScheduler (Lucene 2.3 default)
+         can perform merges in the background using separate threads.
+         The SerialMergeScheduler (Lucene 2.2 default) does not.
+     -->
+    <!--
+       <mergeScheduler class="org.apache.lucene.index.ConcurrentMergeScheduler"/>
+       -->
+
+    <!-- LockFactory
+
+         This option specifies which Lucene LockFactory implementation
+         to use.
+
+         single = SingleInstanceLockFactory - suggested for a
+                  read-only index or when there is no possibility of
+                  another process trying to modify the index.
+         native = NativeFSLockFactory - uses OS native file locking.
+                  Do not use when multiple solr webapps in the same
+                  JVM are attempting to share a single index.
+         simple = SimpleFSLockFactory  - uses a plain file for locking
+
+         Defaults: 'native' is default for Solr3.6 and later, otherwise
+                   'simple' is the default
+
+         More details on the nuances of each LockFactory...
+         http://wiki.apache.org/lucene-java/AvailableLockFactories
+    -->
+    <lockType>${solr.lock.type:native}</lockType>
+
+    <!-- Expert: Controls how often Lucene loads terms into memory
+         Default is 128 and is likely good for most everyone.
+      -->
+    <!-- <termIndexInterval>256</termIndexInterval> -->
+
+    <!-- If true, IndexReaders will be reopened (often more efficient)
+         instead of closed and then opened.
+      -->
+    <reopenReaders>true</reopenReaders>
+
+    <!-- Commit Deletion Policy
+
+         Custom deletion policies can be specified here. The class must
+         implement org.apache.lucene.index.IndexDeletionPolicy.
+
+         http://lucene.apache.org/java/2_9_1/api/all/org/apache/lucene/index/IndexDeletionPolicy.html
+
+         The default Solr IndexDeletionPolicy implementation supports
+         deleting index commit points on number of commits, age of
+         commit point and optimized status.
+
+         The latest commit point should always be preserved regardless
+         of the criteria.
+    -->
+    <deletionPolicy class="solr.SolrDeletionPolicy">
+      <!-- The number of commit points to be kept -->
+      <str name="maxCommitsToKeep">1</str>
+      <!-- The number of optimized commit points to be kept -->
+      <str name="maxOptimizedCommitsToKeep">0</str>
+      <!--
+          Delete all commit points once they have reached the given age.
+          Supports DateMathParser syntax e.g.
+        -->
+      <!--
+         <str name="maxCommitAge">30MINUTES</str>
+         <str name="maxCommitAge">1DAY</str>
+      -->
+    </deletionPolicy>
+
+    <!-- Lucene Infostream
+
+         To aid in advanced debugging, Lucene provides an "InfoStream"
+         of detailed information when indexing.
+
+         Setting the value to true will instruct the underlying Lucene
+         IndexWriter to write its info stream to solr's log. By default,
+         this is enabled here, and controlled through log4j.properties.
+      -->
+     <infoStream>true</infoStream>
+
+  </indexConfig>
+
+  <!-- JMX
+
+       This example enables JMX if and only if an existing MBeanServer
+       is found, use this if you want to configure JMX through JVM
+       parameters. Remove this to disable exposing Solr configuration
+       and statistics to JMX.
+
+       For more details see http://wiki.apache.org/solr/SolrJmx
+    -->
+  <!-- <jmx /> -->
+  <!-- If you want to connect to a particular server, specify the
+       agentId
+    -->
+  <!-- <jmx agentId="myAgent" /> -->
+  <!-- If you want to start a new MBeanServer, specify the serviceUrl -->
+  <!-- <jmx serviceUrl="service:jmx:rmi:///jndi/rmi://localhost:9999/solr"/>
+    -->
+
+  <!-- The default high-performance update handler -->
+  <updateHandler class="solr.DirectUpdateHandler2">
+
+    <!-- AutoCommit
+
+         Perform a <commit/> automatically under certain conditions.
+         Instead of enabling autoCommit, consider using "commitWithin"
+         when adding documents.
+
+         http://wiki.apache.org/solr/UpdateXmlMessages
+
+         maxDocs - Maximum number of documents to add since the last
+                   commit before automatically triggering a new commit.
+
+         maxTime - Maximum amount of time that is allowed to pass
+                   since a document was added before automaticly
+                   triggering a new commit.
+      -->
+    <autoCommit>
+      <maxDocs>${solr.autoCommit.MaxDocs:10000}</maxDocs>
+      <maxTime>${solr.autoCommit.MaxTime:120000}</maxTime>
+    </autoCommit>
+
+    <!-- softAutoCommit is like autoCommit except it causes a
+         'soft' commit which only ensures that changes are visible
+         but does not ensure that data is synced to disk.  This is
+         faster and more near-realtime friendly than a hard commit.
+    -->
+    <autoSoftCommit>
+      <maxDocs>${solr.autoSoftCommit.MaxDocs:2000}</maxDocs>
+      <maxTime>${solr.autoSoftCommit.MaxTime:10000}</maxTime>
+    </autoSoftCommit>
+
+    <!-- Update Related Event Listeners
+
+         Various IndexWriter related events can trigger Listeners to
+         take actions.
+
+         postCommit - fired after every commit or optimize command
+         postOptimize - fired after every optimize command
+      -->
+    <!-- The RunExecutableListener executes an external command from a
+         hook such as postCommit or postOptimize.
+
+         exe - the name of the executable to run
+         dir - dir to use as the current working directory. (default=".")
+         wait - the calling thread waits until the executable returns.
+                (default="true")
+         args - the arguments to pass to the program.  (default is none)
+         env - environment variables to set.  (default is none)
+      -->
+    <!-- This example shows how RunExecutableListener could be used
+         with the script based replication...
+         http://wiki.apache.org/solr/CollectionDistribution
+      -->
+    <!--
+       <listener event="postCommit" class="solr.RunExecutableListener">
+         <str name="exe">solr/bin/snapshooter</str>
+         <str name="dir">.</str>
+         <bool name="wait">true</bool>
+         <arr name="args"> <str>arg1</str> <str>arg2</str> </arr>
+         <arr name="env"> <str>MYVAR=val1</str> </arr>
+       </listener>
+      -->
+    <!-- Enables a transaction log, currently used for real-time get.
+         "dir" - the target directory for transaction logs, defaults to the
+         solr data directory.  -->
+    <updateLog>
+      <str name="dir">${solr.data.dir:}</str>
+      <!-- if you want to take control of the synchronization you may specify
+           the syncLevel as one of the following where ''flush'' is the default.
+           Fsync will reduce throughput.
+           <str name="syncLevel">flush|fsync|none</str>
+      -->
+    </updateLog>
+  </updateHandler>
+
+  <!-- IndexReaderFactory
+
+       Use the following format to specify a custom IndexReaderFactory,
+       which allows for alternate IndexReader implementations.
+
+       ** Experimental Feature **
+
+       Please note - Using a custom IndexReaderFactory may prevent
+       certain other features from working. The API to
+       IndexReaderFactory may change without warning or may even be
+       removed from future releases if the problems cannot be
+       resolved.
+
+       ** Features that may not work with custom IndexReaderFactory **
+
+       The ReplicationHandler assumes a disk-resident index. Using a
+       custom IndexReader implementation may cause incompatibility
+       with ReplicationHandler and may cause replication to not work
+       correctly. See SOLR-1366 for details.
+
+    -->
+  <!--
+  <indexReaderFactory name="IndexReaderFactory" class="package.class">
+    <str name="someArg">Some Value</str>
+  </indexReaderFactory >
+  -->
+
+  <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+       Query section - these settings control query time things like caches
+       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
+  <query>
+    <!-- Max Boolean Clauses
+
+         Maximum number of clauses in each BooleanQuery,  an exception
+         is thrown if exceeded.
+
+         ** WARNING **
+
+         This option actually modifies a global Lucene property that
+         will affect all SolrCores.  If multiple solrconfig.xml files
+         disagree on this property, the value at any given moment will
+         be based on the last SolrCore to be initialized.
+
+      -->
+    <maxBooleanClauses>1024</maxBooleanClauses>
+
+    <!-- Slow Query Threshold (in millis)
+
+         At high request rates, logging all requests can become a bottleneck
+         and therefore INFO logging is often turned off. However, it is still
+         useful to be able to set a latency threshold above which a request
+         is considered "slow" and log that request at WARN level so we can
+         easily identify slow queries.
+    -->
+    <slowQueryThresholdMillis>-1</slowQueryThresholdMillis>
+
+    <!-- Solr Internal Query Caches
+
+         There are two implementations of cache available for Solr,
+         LRUCache, based on a synchronized LinkedHashMap, and
+         FastLRUCache, based on a ConcurrentHashMap.
+
+         FastLRUCache has faster gets and slower puts in single
+         threaded operation and thus is generally faster than LRUCache
+         when the hit ratio of the cache is high (> 75%), and may be
+         faster under other scenarios on multi-cpu systems.
+    -->
+
+    <!-- Filter Cache
+
+         Cache used by SolrIndexSearcher for filters (DocSets),
+         unordered sets of *all* documents that match a query.  When a
+         new searcher is opened, its caches may be prepopulated or
+         "autowarmed" using data from caches in the old searcher.
+         autowarmCount is the number of items to prepopulate.  For
+         LRUCache, the autowarmed items will be the most recently
+         accessed items.
+
+         Parameters:
+           class - the SolrCache implementation LRUCache or
+               (LRUCache or FastLRUCache)
+           size - the maximum number of entries in the cache
+           initialSize - the initial capacity (number of entries) of
+               the cache.  (see java.util.HashMap)
+           autowarmCount - the number of entries to prepopulate from
+               and old cache.
+      -->
+    <filterCache class="solr.FastLRUCache"
+                 size="512"
+                 initialSize="512"
+                 autowarmCount="0"/>
+
+    <!-- Query Result Cache
+
+         Caches results of searches - ordered lists of document ids
+         (DocList) based on a query, a sort, and the range of documents requested.
+      -->
+    <queryResultCache class="solr.LRUCache"
+                     size="512"
+                     initialSize="512"
+                     autowarmCount="32"/>
+
+    <!-- Document Cache
+
+         Caches Lucene Document objects (the stored fields for each
+         document).  Since Lucene internal document ids are transient,
+         this cache will not be autowarmed.
+      -->
+    <documentCache class="solr.LRUCache"
+                   size="512"
+                   initialSize="512"
+                   autowarmCount="0"/>
+
+    <!-- Field Value Cache
+
+         Cache used to hold field values that are quickly accessible
+         by document id.  The fieldValueCache is created by default
+         even if not configured here.
+      -->
+    <!--
+       <fieldValueCache class="solr.FastLRUCache"
+                        size="512"
+                        autowarmCount="128"
+                        showItems="32" />
+      -->
+
+    <!-- Custom Cache
+
+         Example of a generic cache.  These caches may be accessed by
+         name through SolrIndexSearcher.getCache(),cacheLookup(), and
+         cacheInsert().  The purpose is to enable easy caching of
+         user/application level data.  The regenerator argument should
+         be specified as an implementation of solr.CacheRegenerator
+         if autowarming is desired.
+      -->
+    <!--
+       <cache name="myUserCache"
+              class="solr.LRUCache"
+              size="4096"
+              initialSize="1024"
+              autowarmCount="1024"
+              regenerator="com.mycompany.MyRegenerator"
+              />
+      -->
+
+    <!-- Lazy Field Loading
+
+         If true, stored fields that are not requested will be loaded
+         lazily.  This can result in a significant speed improvement
+         if the usual case is to not load all stored fields,
+         especially if the skipped fields are large compressed text
+         fields.
+    -->
+    <enableLazyFieldLoading>true</enableLazyFieldLoading>
+
+   <!-- Use Filter For Sorted Query
+
+        A possible optimization that attempts to use a filter to
+        satisfy a search.  If the requested sort does not include
+        score, then the filterCache will be checked for a filter
+        matching the query. If found, the filter will be used as the
+        source of document ids, and then the sort will be applied to
+        that.
+
+        For most situations, this will not be useful unless you
+        frequently get the same search repeatedly with different sort
+        options, and none of them ever use "score"
+     -->
+   <!--
+      <useFilterForSortedQuery>true</useFilterForSortedQuery>
+     -->
+
+   <!-- Result Window Size
+
+        An optimization for use with the queryResultCache.  When a search
+        is requested, a superset of the requested number of document ids
+        are collected.  For example, if a search for a particular query
+        requests matching documents 10 through 19, and queryWindowSize is 50,
+        then documents 0 through 49 will be collected and cached.  Any further
+        requests in that range can be satisfied via the cache.
+     -->
+   <queryResultWindowSize>20</queryResultWindowSize>
+
+   <!-- Maximum number of documents to cache for any entry in the
+        queryResultCache.
+     -->
+   <queryResultMaxDocsCached>200</queryResultMaxDocsCached>
+
+   <!-- Query Related Event Listeners
+
+        Various IndexSearcher related events can trigger Listeners to
+        take actions.
+
+        newSearcher - fired whenever a new searcher is being prepared
+        and there is a current searcher handling requests (aka
+        registered).  It can be used to prime certain caches to
+        prevent long request times for certain requests.
+
+        firstSearcher - fired whenever a new searcher is being
+        prepared but there is no current registered searcher to handle
+        requests or to gain autowarming data from.
+
+     -->
+    <!-- QuerySenderListener takes an array of NamedList and executes a
+         local query request for each NamedList in sequence.
+      -->
+    <listener event="newSearcher" class="solr.QuerySenderListener">
+      <arr name="queries">
+        <!--
+           <lst><str name="q">solr</str><str name="sort">price asc</str></lst>
+           <lst><str name="q">rocks</str><str name="sort">weight asc</str></lst>
+          -->
+      </arr>
+    </listener>
+    <listener event="firstSearcher" class="solr.QuerySenderListener">
+      <arr name="queries">
+        <lst>
+          <str name="q">solr rocks</str><str name="start">0</str><str name="rows">10</str>
+        </lst>
+      </arr>
+    </listener>
+
+    <!-- Use Cold Searcher
+
+         If a search request comes in and there is no current
+         registered searcher, then immediately register the still
+         warming searcher and use it.  If "false" then all requests
+         will block until the first searcher is done warming.
+      -->
+    <useColdSearcher>false</useColdSearcher>
+
+    <!-- Max Warming Searchers
+
+         Maximum number of searchers that may be warming in the
+         background concurrently.  An error is returned if this limit
+         is exceeded.
+
+         Recommend values of 1-2 for read-only slaves, higher for
+         masters w/o cache warming.
+      -->
+    <maxWarmingSearchers>2</maxWarmingSearchers>
+
+  </query>
+
+  <!-- Request Dispatcher
+
+       This section contains instructions for how the SolrDispatchFilter
+       should behave when processing requests for this SolrCore.
+
+       handleSelect affects the behavior of requests such as /select?qt=XXX
+
+       handleSelect="true" will cause the SolrDispatchFilter to process
+       the request and will result in consistent error handling and
+       formatting for all types of requests.
+
+       handleSelect="false" will cause the SolrDispatchFilter to
+       ignore "/select" requests and fallback to using the legacy
+       SolrServlet and it's Solr 1.1 style error formatting
+    -->
+  <requestDispatcher handleSelect="true" >
+    <!-- Request Parsing
+
+         These settings indicate how Solr Requests may be parsed, and
+         what restrictions may be placed on the ContentStreams from
+         those requests
+
+         enableRemoteStreaming - enables use of the stream.file
+         and stream.url parameters for specifying remote streams.
+
+         multipartUploadLimitInKB - specifies the max size (in KiB) of
+         Multipart File Uploads that Solr will allow in a Request.
+
+         formdataUploadLimitInKB - specifies the max size (in KiB) of
+         form data (application/x-www-form-urlencoded) sent via
+         POST. You can use POST to pass request parameters not
+         fitting into the URL.
+
+         addHttpRequestToContext - if set to true, it will instruct
+         the requestParsers to include the original HttpServletRequest
+         object in the context map of the SolrQueryRequest under the
+         key "httpRequest". It will not be used by any of the existing
+         Solr components, but may be useful when developing custom
+         plugins.
+
+         *** WARNING ***
+         The settings below authorize Solr to fetch remote files, You
+         should make sure your system has some authentication before
+         using enableRemoteStreaming="true"
+
+      -->
+    <requestParsers enableRemoteStreaming="true"
+                    multipartUploadLimitInKB="2048000"
+                    formdataUploadLimitInKB="2048"
+                    addHttpRequestToContext="false"/>
+
+    <!-- HTTP Caching
+
+         Set HTTP caching related parameters (for proxy caches and clients).
+
+         The options below instruct Solr not to output any HTTP Caching
+         related headers
+      -->
+    <httpCaching never304="true" />
+    <!-- If you include a <cacheControl> directive, it will be used to
+         generate a Cache-Control header (as well as an Expires header
+         if the value contains "max-age=")
+
+         By default, no Cache-Control header is generated.
+
+         You can use the <cacheControl> option even if you have set
+         never304="true"
+      -->
+    <!--
+       <httpCaching never304="true" >
+         <cacheControl>max-age=30, public</cacheControl>
+       </httpCaching>
+      -->
+    <!-- To enable Solr to respond with automatically generated HTTP
+         Caching headers, and to response to Cache Validation requests
+         correctly, set the value of never304="false"
+
+         This will cause Solr to generate Last-Modified and ETag
+         headers based on the properties of the Index.
+
+         The following options can also be specified to affect the
+         values of these headers...
+
+         lastModFrom - the default value is "openTime" which means the
+         Last-Modified value (and validation against If-Modified-Since
+         requests) will all be relative to when the current Searcher
+         was opened.  You can change it to lastModFrom="dirLastMod" if
+         you want the value to exactly correspond to when the physical
+         index was last modified.
+
+         etagSeed="..." is an option you can change to force the ETag
+         header (and validation against If-None-Match requests) to be
+         different even if the index has not changed (ie: when making
+         significant changes to your config file)
+
+         (lastModifiedFrom and etagSeed are both ignored if you use
+         the never304="true" option)
+      -->
+    <!--
+       <httpCaching lastModifiedFrom="openTime"
+                    etagSeed="Solr">
+         <cacheControl>max-age=30, public</cacheControl>
+       </httpCaching>
+      -->
+  </requestDispatcher>
+
+  <!-- Request Handlers
+
+       http://wiki.apache.org/solr/SolrRequestHandler
+
+       Incoming queries will be dispatched to a specific handler by name
+       based on the path specified in the request.
+
+       Legacy behavior: If the request path uses "/select" but no Request
+       Handler has that name, and if handleSelect="true" has been specified in
+       the requestDispatcher, then the Request Handler is dispatched based on
+       the qt parameter.  Handlers without a leading '/' are accessed this way
+       like so: http://host/app/[core/]select?qt=name  If no qt is
+       given, then the requestHandler that declares default="true" will be
+       used or the one named "standard".
+
+       If a Request Handler is declared with startup="lazy", then it will
+       not be initialized until the first request that uses it.
+
+    -->
+  <!-- SearchHandler
+
+       http://wiki.apache.org/solr/SearchHandler
+
+       For processing Search Queries, the primary Request Handler
+       provided with Solr is "SearchHandler" It delegates to a sequent
+       of SearchComponents (see below) and supports distributed
+       queries across multiple shards
+    -->
+  <!--<requestHandler name="/select" class="solr.SearchHandler">-->
+    <!-- default values for query parameters can be specified, these
+         will be overridden by parameters in the request
+      -->
+     <!--<lst name="defaults">
+       <str name="echoParams">explicit</str>
+       <int name="rows">10</int>
+     </lst>-->
+    <!-- In addition to defaults, "appends" params can be specified
+         to identify values which should be appended to the list of
+         multi-val params from the query (or the existing "defaults").
+      -->
+    <!-- In this example, the param "fq=instock:true" would be appended to
+         any query time fq params the user may specify, as a mechanism for
+         partitioning the index, independent of any user selected filtering
+         that may also be desired (perhaps as a result of faceted searching).
+
+         NOTE: there is *absolutely* nothing a client can do to prevent these
+         "appends" values from being used, so don't use this mechanism
+         unless you are sure you always want it.
+      -->
+    <!--
+       <lst name="appends">
+         <str name="fq">inStock:true</str>
+       </lst>
+      -->
+    <!-- "invariants" are a way of letting the Solr maintainer lock down
+         the options available to Solr clients.  Any params values
+         specified here are used regardless of what values may be specified
+         in either the query, the "defaults", or the "appends" params.
+
+         In this example, the facet.field and facet.query params would
+         be fixed, limiting the facets clients can use.  Faceting is
+         not turned on by default - but if the client does specify
+         facet=true in the request, these are the only facets they
+         will be able to see counts for; regardless of what other
+         facet.field or facet.query params they may specify.
+
+         NOTE: there is *absolutely* nothing a client can do to prevent these
+         "invariants" values from being used, so don't use this mechanism
+         unless you are sure you always want it.
+      -->
+    <!--
+       <lst name="invariants">
+         <str name="facet.field">cat</str>
+         <str name="facet.field">manu_exact</str>
+         <str name="facet.query">price:[* TO 500]</str>
+         <str name="facet.query">price:[500 TO *]</str>
+       </lst>
+      -->
+    <!-- If the default list of SearchComponents is not desired, that
+         list can either be overridden completely, or components can be
+         prepended or appended to the default list.  (see below)
+      -->
+    <!--
+       <arr name="components">
+         <str>nameOfCustomComponent1</str>
+         <str>nameOfCustomComponent2</str>
+       </arr>
+      -->
+    <!--</requestHandler>-->
+
+  <!-- A request handler that returns indented JSON by default -->
+  <requestHandler name="/query" class="solr.SearchHandler">
+     <lst name="defaults">
+       <str name="echoParams">explicit</str>
+       <str name="wt">json</str>
+       <str name="indent">true</str>
+       <str name="df">text</str>
+     </lst>
+  </requestHandler>
+
+  <!--
+    The export request handler is used to export full sorted result sets.
+    Do not change these defaults.
+  -->
+
+  <requestHandler name="/export" class="solr.SearchHandler">
+    <lst name="invariants">
+      <str name="rq">{!xport}</str>
+      <str name="wt">xsort</str>
+      <str name="distrib">false</str>
+    </lst>
+
+    <arr name="components">
+      <str>query</str>
+    </arr>
+  </requestHandler>
+
+  <!-- A Robust Example
+
+       This example SearchHandler declaration shows off usage of the
+       SearchHandler with many defaults declared
+
+       Note that multiple instances of the same Request Handler
+       (SearchHandler) can be registered multiple times with different
+       names (and different init parameters)
+    -->
+  <!--
+  <requestHandler name="/browse" class="solr.SearchHandler">
+     <lst name="defaults">
+       <str name="echoParams">explicit</str>-->
+
+       <!-- VelocityResponseWriter settings -->
+       <!--<str name="wt">velocity</str>
+
+       <str name="v.template">browse</str>
+       <str name="v.layout">layout</str>
+       <str name="title">Solritas</str>
+
+       <str name="defType">edismax</str>
+       <str name="qf">
+          text^0.5 features^1.0 name^1.2 sku^1.5 id^10.0 manu^1.1 cat^1.4
+          title^10.0 description^5.0 keywords^5.0 author^2.0 resourcename^1.0
+       </str>
+       <str name="mm">100%</str>
+       <str name="q.alt">*:*</str>
+       <str name="rows">10</str>
+       <str name="fl">*,score</str>
+       <str name="mlt.qf">
+         text^0.5 features^1.0 name^1.2 sku^1.5 id^10.0 manu^1.1 cat^1.4
+       </str>
+       <str name="mlt.fl">text,features,name,sku,id,manu,cat</str>
+       <int name="mlt.count">3</int>
+
+       <str name="qf">
+          text^0.5 features^1.0 name^1.2 sku^1.5 id^10.0 manu^1.1 cat^1.4
+       </str>
+
+       <str name="facet">on</str>
+       <str name="facet.field">cat</str>
+       <str name="facet.field">manu_exact</str>
+       <str name="facet.query">ipod</str>
+       <str name="facet.query">GB</str>
+       <str name="facet.mincount">1</str>
+       <str name="facet.pivot">cat,inStock</str>
+       <str name="facet.range.other">after</str>
+       <str name="facet.range">price</str>
+       <int name="f.price.facet.range.start">0</int>
+       <int name="f.price.facet.range.end">600</int>
+       <int name="f.price.facet.range.gap">50</int>
+       <str name="facet.range">popularity</str>
+       <int name="f.popularity.facet.range.start">0</int>
+       <int name="f.popularity.facet.range.end">10</int>
+       <int name="f.popularity.facet.range.gap">3</int>
+       <str name="facet.range">manufacturedate_dt</str>
+       <str name="f.manufacturedate_dt.facet.range.start">NOW/YEAR-10YEARS</str>
+       <str name="f.manufacturedate_dt.facet.range.end">NOW</str>
+       <str name="f.manufacturedate_dt.facet.range.gap">+1YEAR</str>
+       <str name="f.manufacturedate_dt.facet.range.other">before</str>
+       <str name="f.manufacturedate_dt.facet.range.other">after</str>-->
+
+       <!-- Highlighting defaults -->
+       <!--<str name="hl">on</str>
+       <str name="hl.fl">text features name</str>
+       <str name="f.name.hl.fragsize">0</str>
+       <str name="f.name.hl.alternateField">name</str>
+     </lst>
+     <arr name="last-components">
+       <str>spellcheck</str>
+     </arr>-->
+     <!--
+     <str name="url-scheme">httpx</str>
+     -->
+  <!--</requestHandler>-->
+  <!-- trivia: the name pinkPony requestHandler was an agreement between the Search API and the
+    apachesolr maintainers. The decision was taken during the Drupalcon Munich codesprint.
+    -->
+  <requestHandler name="pinkPony" class="solr.SearchHandler" default="true">
+    <lst name="defaults">
+      <str name="defType">edismax</str>
+      <str name="df">content</str>
+      <str name="echoParams">explicit</str>
+      <bool name="omitHeader">true</bool>
+      <float name="tie">0.01</float>
+      <!-- Don't abort searches for the pinkPony request handler (set in solrcore.properties) -->
+      <int name="timeAllowed">${solr.pinkPony.timeAllowed:-1}</int>
+      <str name="q.alt">*:*</str>
+
+      <!-- By default, don't spell check -->
+      <str name="spellcheck">false</str>
+      <!-- Defaults for the spell checker when used -->
+      <str name="spellcheck.onlyMorePopular">true</str>
+      <str name="spellcheck.extendedResults">false</str>
+      <!--  The number of suggestions to return -->
+      <str name="spellcheck.count">1</str>
+    </lst>
+    <arr name="last-components">
+      <str>spellcheck</str>
+      <str>elevator</str>
+    </arr>
+  </requestHandler>
+
+  <!-- The more like this handler offers many advantages over the standard handler,
+     when performing moreLikeThis requests.-->
+  <requestHandler name="mlt" class="solr.MoreLikeThisHandler">
+    <lst name="defaults">
+      <str name="df">content</str>
+      <str name="mlt.mintf">1</str>
+      <str name="mlt.mindf">1</str>
+      <str name="mlt.minwl">3</str>
+      <str name="mlt.maxwl">15</str>
+      <str name="mlt.maxqt">20</str>
+      <str name="mlt.match.include">false</str>
+      <!-- Abort any searches longer than 2 seconds (set in solrcore.properties) -->
+      <int name="timeAllowed">${solr.mlt.timeAllowed:2000}</int>
+    </lst>
+  </requestHandler>
+
+  <!-- A minimal query type for doing luene queries -->
+  <requestHandler name="standard" class="solr.SearchHandler">
+     <lst name="defaults">
+       <str name="df">content</str>
+       <str name="echoParams">explicit</str>
+       <bool name="omitHeader">true</bool>
+     </lst>
+  </requestHandler>
+
+  <!-- Update Request Handler.
+
+       http://wiki.apache.org/solr/UpdateXmlMessages
+
+       The canonical Request Handler for Modifying the Index through
+       commands specified using XML, JSON, CSV, or JAVABIN
+
+       Note: Since solr1.1 requestHandlers requires a valid content
+       type header if posted in the body. For example, curl now
+       requires: -H 'Content-type:text/xml; charset=utf-8'
+
+       To override the request content type and force a specific
+       Content-type, use the request parameter:
+         ?update.contentType=text/csv
+
+       This handler will pick a response format to match the input
+       if the 'wt' parameter is not explicit
+    -->
+  <!--<requestHandler name="/update" class="solr.UpdateRequestHandler">
+  </requestHandler>-->
+  <initParams path="/update/**,/query,/select,/tvrh,/elevate,/spell,/browse">
+    <lst name="defaults">
+      <str name="df">text</str>
+    </lst>
+  </initParams>
+
+  <initParams path="/update/json/docs">
+    <lst name="defaults">
+      <!--this ensures that the entire json doc will be stored verbatim into one field-->
+      <str name="srcField">_src_</str>
+      <!--This means a the uniqueKeyField will be extracted from the fields and
+       all fields go into the 'df' field. In this config df is already configured to be 'text'
+        -->
+      <str name="mapUniqueKeyOnly">true</str>
+    </lst>
+
+  </initParams>
+
+  <!-- CSV Update Request Handler
+       http://wiki.apache.org/solr/UpdateCSV
+    -->
+  <requestHandler name="/update/csv"
+                  class="solr.CSVRequestHandler"
+                  startup="lazy" />
+
+  <!-- JSON Update Request Handler
+       http://wiki.apache.org/solr/UpdateJSON
+    -->
+  <requestHandler name="/update/json"
+                  class="solr.JsonUpdateRequestHandler"
+                  startup="lazy" />
+
+  <!-- Solr Cell Update Request Handler
+
+       http://wiki.apache.org/solr/ExtractingRequestHandler
+
+    -->
+  <requestHandler name="/update/extract"
+                  startup="lazy"
+                  class="solr.extraction.ExtractingRequestHandler" >
+    <lst name="defaults">
+      <!-- All the main content goes into "text"... if you need to return
+           the extracted text or do highlighting, use a stored field. -->
+      <str name="fmap.content">text</str>
+      <str name="lowernames">true</str>
+      <str name="uprefix">ignored_</str>
+
+      <!-- capture link hrefs but ignore div attributes -->
+      <str name="captureAttr">true</str>
+      <str name="fmap.a">links</str>
+      <str name="fmap.div">ignored_</str>
+    </lst>
+  </requestHandler>
+
+  <!-- XSLT Update Request Handler
+       Transforms incoming XML with stylesheet identified by tr=
+  -->
+  <requestHandler name="/update/xslt"
+                   startup="lazy"
+                   class="solr.XsltUpdateRequestHandler"/>
+
+  <!-- Field Analysis Request Handler
+
+       RequestHandler that provides much the same functionality as
+       analysis.jsp. Provides the ability to specify multiple field
+       types and field names in the same request and outputs
+       index-time and query-time analysis for each of them.
+
+       Request parameters are:
+       analysis.fieldname - field name whose analyzers are to be used
+
+       analysis.fieldtype - field type whose analyzers are to be used
+       analysis.fieldvalue - text for index-time analysis
+       q (or analysis.q) - text for query time analysis
+       analysis.showmatch (true|false) - When set to true and when
+           query analysis is performed, the produced tokens of the
+           field value analysis will be marked as "matched" for every
+           token that is produces by the query analysis
+   -->
+  <requestHandler name="/analysis/field"
+                  startup="lazy"
+                  class="solr.FieldAnalysisRequestHandler" />
+
+  <!-- Document Analysis Handler
+
+       http://wiki.apache.org/solr/AnalysisRequestHandler
+
+       An analysis handler that provides a breakdown of the analysis
+       process of provided documents. This handler expects a (single)
+       content stream with the following format:
+
+       <docs>
+         <doc>
+           <field name="id">1</field>
+           <field name="name">The Name</field>
+           <field name="text">The Text Value</field>
+         </doc>
+         <doc>...</doc>
+         <doc>...</doc>
+         ...
+       </docs>
+
+    Note: Each document must contain a field which serves as the
+    unique key. This key is used in the returned response to associate
+    an analysis breakdown to the analyzed document.
+
+    Like the FieldAnalysisRequestHandler, this handler also supports
+    query analysis by sending either an "analysis.query" or "q"
+    request parameter that holds the query text to be analyzed. It
+    also supports the "analysis.showmatch" parameter which when set to
+    true, all field tokens that match the query tokens will be marked
+    as a "match".
+  -->
+  <requestHandler name="/analysis/document"
+                  class="solr.DocumentAnalysisRequestHandler"
+                  startup="lazy" />
+
+  <!-- Admin Handlers
+
+       As of Solr 5.0.0, the "/admin/" handlers are registered implicitly.
+    -->
+  <!-- <requestHandler name="/admin/" class="solr.admin.AdminHandlers" /> -->
+  <!-- This single handler is equivalent to the following... -->
+  <!--
+     <requestHandler name="/admin/luke"       class="solr.admin.LukeRequestHandler" />
+     <requestHandler name="/admin/system"     class="solr.admin.SystemInfoHandler" />
+     <requestHandler name="/admin/plugins"    class="solr.admin.PluginInfoHandler" />
+     <requestHandler name="/admin/threads"    class="solr.admin.ThreadDumpHandler" />
+     <requestHandler name="/admin/properties" class="solr.admin.PropertiesRequestHandler" />
+     <requestHandler name="/admin/file"       class="solr.admin.ShowFileRequestHandler" >
+    -->
+  <!-- If you wish to hide files under ${solr.home}/conf, explicitly
+       register the ShowFileRequestHandler using the definition below.
+       NOTE: The glob pattern ('*') is the only pattern supported at present, *.xml will
+             not exclude all files ending in '.xml'. Use it to exclude _all_ updates
+    -->
+  <!--
+     <requestHandler name="/admin/file"
+                     class="solr.admin.ShowFileRequestHandler" >
+       <lst name="invariants">
+         <str name="hidden">synonyms.txt</str>
+         <str name="hidden">anotherfile.txt</str>
+         <str name="hidden">*</str>
+       </lst>
+     </requestHandler>
+    -->
+  <!--
+    Enabling this request handler (which is NOT a default part of the admin handler) will allow the Solr UI to edit
+    all the config files. This is intended for secure/development use ONLY! Leaving available and publically
+    accessible is a security vulnerability and should be done with extreme caution!
+  -->
+  <!--
+  <requestHandler name="/admin/fileedit" class="solr.admin.EditFileRequestHandler" >
+    <lst name="invariants">
+      <str name="qt">pinkPony</str>
+      <str name="q">solrpingquery</str>
+      <str name="omitHeader">false</str>
+    </lst>
+    <lst name="defaults">
+      <str name="echoParams">all</str>
+    </lst>
+    <!- An optional feature of the PingRequestHandler is to configure the
+         handler with a "healthcheckFile" which can be used to enable/disable
+         the PingRequestHandler.
+         relative paths are resolved against the data dir
+    -->
+    <!-- <str name="healthcheckFile">server-enabled.txt</str> -->
+  <!-- </requestHandler>
+  -->
+
+  <!-- Echo the request contents back to the client -->
+  <requestHandler name="/debug/dump" class="solr.DumpRequestHandler" >
+    <lst name="defaults">
+     <str name="echoParams">explicit</str>
+     <str name="echoHandler">true</str>
+    </lst>
+  </requestHandler>
+
+  <!-- Solr Replication
+
+       The SolrReplicationHandler supports replicating indexes from a
+       "master" used for indexing and "slaves" used for queries.
+
+       http://wiki.apache.org/solr/SolrReplication
+
+       In the example below, remove the <lst name="master"> section if
+       this is just a slave and remove  the <lst name="slave"> section
+       if this is just a master.
+  -->
+  <requestHandler name="/replication" class="solr.ReplicationHandler" >
+    <lst name="master">
+      <str name="enable">${solr.replication.master:false}</str>
+      <str name="replicateAfter">commit</str>
+      <str name="replicateAfter">startup</str>
+      <str name="confFiles">${solr.replication.confFiles:schema.xml,mapping-ISOLatin1Accent.txt,protwords.txt,stopwords.txt,synonyms.txt,elevate.xml}</str>
+    </lst>
+    <lst name="slave">
+      <str name="enable">${solr.replication.slave:false}</str>
+      <str name="masterUrl">${solr.replication.masterUrl:http://localhost:8983/solr}/replication</str>
+      <str name="pollInterval">${solr.replication.pollInterval:00:00:60}</str>
+    </lst>
+  </requestHandler>
+
+  <!-- Realtime get handler, guaranteed to return the latest stored fields of
+       any document, without the need to commit or open a new searcher.  The
+       current implementation relies on the updateLog feature being enabled.
+  -->
+  <requestHandler name="/get" class="solr.RealTimeGetHandler">
+    <lst name="defaults">
+      <str name="omitHeader">true</str>
+      <str name="wt">json</str>
+      <str name="indent">true</str>
+    </lst>
+  </requestHandler>
+
+  <!-- Search Components
+
+       Search components are registered to SolrCore and used by
+       instances of SearchHandler (which can access them by name)
+
+       By default, the following components are available:
+
+       <searchComponent name="query"     class="solr.QueryComponent" />
+       <searchComponent name="facet"     class="solr.FacetComponent" />
+       <searchComponent name="mlt"       class="solr.MoreLikeThisComponent" />
+       <searchComponent name="highlight" class="solr.HighlightComponent" />
+       <searchComponent name="stats"     class="solr.StatsComponent" />
+       <searchComponent name="debug"     class="solr.DebugComponent" />
+
+       Default configuration in a requestHandler would look like:
+
+       <arr name="components">
+         <str>query</str>
+         <str>facet</str>
+         <str>mlt</str>
+         <str>highlight</str>
+         <str>stats</str>
+         <str>debug</str>
+       </arr>
+
+       If you register a searchComponent to one of the standard names,
+       that will be used instead of the default.
+
+       To insert components before or after the 'standard' components, use:
+
+       <arr name="first-components">
+         <str>myFirstComponentName</str>
+       </arr>
+
+       <arr name="last-components">
+         <str>myLastComponentName</str>
+       </arr>
+
+       NOTE: The component registered with the name "debug" will
+       always be executed after the "last-components"
+
+     -->
+
+  <!-- A request handler for demonstrating the spellcheck component.
+
+       NOTE: This is purely as an example.  The whole purpose of the
+       SpellCheckComponent is to hook it into the request handler that
+       handles your normal user queries so that a separate request is
+       not needed to get suggestions.
+
+       IN OTHER WORDS, THERE IS REALLY GOOD CHANCE THE SETUP BELOW IS
+       NOT WHAT YOU WANT FOR YOUR PRODUCTION SYSTEM!
+
+       See http://wiki.apache.org/solr/SpellCheckComponent for details
+       on the request parameters.
+    -->
+  <requestHandler name="/spell" class="solr.SearchHandler" startup="lazy">
+    <lst name="defaults">
+      <!-- Solr will use suggestions from both the 'default' spellchecker
+           and from the 'wordbreak' spellchecker and combine them.
+           collations (re-written queries) can include a combination of
+           corrections from both spellcheckers -->
+      <str name="spellcheck.dictionary">default</str>
+      <str name="spellcheck.dictionary">wordbreak</str>
+      <str name="spellcheck.onlyMorePopular">false</str>
+      <str name="spellcheck.extendedResults">false</str>
+      <str name="spellcheck.count">1</str>
+      <str name="spellcheck.alternativeTermCount">5</str>
+      <str name="spellcheck.maxResultsForSuggest">5</str>
+      <str name="spellcheck.collate">true</str>
+      <str name="spellcheck.collateExtendedResults">true</str>
+      <str name="spellcheck.maxCollationTries">10</str>
+      <str name="spellcheck.maxCollations">5</str>
+    </lst>
+    <arr name="last-components">
+      <str>spellcheck</str>
+    </arr>
+  </requestHandler>
+
+  <!-- This is disabled by default because it currently causes long startup times on
+       big indexes, even when never used.  See SOLR-6679 for background.
+
+       To use this suggester, set the "solr.suggester.enabled=true" system property
+    -->
+  <searchComponent name="suggest" class="solr.SuggestComponent"
+                   enable="${solr.suggester.enabled:false}"     >
+    <lst name="suggester">
+      <str name="name">mySuggester</str>
+      <str name="lookupImpl">FuzzyLookupFactory</str>
+      <str name="dictionaryImpl">DocumentDictionaryFactory</str>
+      <str name="field">cat</str>
+      <str name="weightField">price</str>
+      <str name="suggestAnalyzerFieldType">string</str>
+    </lst>
+  </searchComponent>
+
+  <requestHandler name="/suggest" class="solr.SearchHandler"
+                  startup="lazy" enable="${solr.suggester.enabled:false}" >
+    <lst name="defaults">
+      <str name="suggest">true</str>
+      <str name="suggest.count">10</str>
+    </lst>
+    <arr name="components">
+      <str>suggest</str>
+    </arr>
+  </requestHandler>
+
+  <!-- Term Vector Component
+
+       http://wiki.apache.org/solr/TermVectorComponent
+    -->
+  <searchComponent name="tvComponent" class="solr.TermVectorComponent"/>
+
+  <!-- A request handler for demonstrating the term vector component
+
+       This is purely as an example.
+
+       In reality you will likely want to add the component to your
+       already specified request handlers.
+    -->
+  <requestHandler name="/tvrh" class="solr.SearchHandler" startup="lazy">
+    <lst name="defaults">
+      <bool name="tv">true</bool>
+    </lst>
+    <arr name="last-components">
+      <str>tvComponent</str>
+    </arr>
+  </requestHandler>
+
+  <!-- Clustering Component
+
+       http://wiki.apache.org/solr/ClusteringComponent
+
+       This relies on third party jars which are notincluded in the
+       release.  To use this component (and the "/clustering" handler)
+       Those jars will need to be downloaded, and you'll need to set
+       the solr.cluster.enabled system property when running solr...
+
+          java -Dsolr.clustering.enabled=true -jar start.jar
+    -->
+  <!-- <searchComponent name="clustering"
+                   enable="${solr.clustering.enabled:false}"
+                   class="solr.clustering.ClusteringComponent" > -->
+    <!-- Declare an engine -->
+    <!--<lst name="engine">-->
+      <!-- The name, only one can be named "default" -->
+      <!--<str name="name">default</str>-->
+
+      <!-- Class name of Carrot2 clustering algorithm.
+
+           Currently available algorithms are:
+
+           * org.carrot2.clustering.lingo.LingoClusteringAlgorithm
+           * org.carrot2.clustering.stc.STCClusteringAlgorithm
+           * org.carrot2.clustering.kmeans.BisectingKMeansClusteringAlgorithm
+
+           See http://project.carrot2.org/algorithms.html for the
+           algorithm's characteristics.
+        -->
+      <!--<str name="carrot.algorithm">org.carrot2.clustering.lingo.LingoClusteringAlgorithm</str>-->
+
+      <!-- Overriding values for Carrot2 default algorithm attributes.
+
+           For a description of all available attributes, see:
+           http://download.carrot2.org/stable/manual/#chapter.components.
+           Use attribute key as name attribute of str elements
+           below. These can be further overridden for individual
+           requests by specifying attribute key as request parameter
+           name and attribute value as parameter value.
+        -->
+      <!--<str name="LingoClusteringAlgorithm.desiredClusterCountBase">20</str>-->
+
+      <!-- Location of Carrot2 lexical resources.
+
+           A directory from which to load Carrot2-specific stop words
+           and stop labels. Absolute or relative to Solr config directory.
+           If a specific resource (e.g. stopwords.en) is present in the
+           specified dir, it will completely override the corresponding
+           default one that ships with Carrot2.
+
+           For an overview of Carrot2 lexical resources, see:
+           http://download.carrot2.org/head/manual/#chapter.lexical-resources
+        -->
+      <!--<str name="carrot.lexicalResourcesDir">clustering/carrot2</str>-->
+
+      <!-- The language to assume for the documents.
+
+           For a list of allowed values, see:
+           http://download.carrot2.org/stable/manual/#section.attribute.lingo.MultilingualClustering.defaultLanguage
+       -->
+      <!--<str name="MultilingualClustering.defaultLanguage">ENGLISH</str>
+    </lst>
+    <lst name="engine">
+      <str name="name">stc</str>
+      <str name="carrot.algorithm">org.carrot2.clustering.stc.STCClusteringAlgorithm</str>
+    </lst>
+  </searchComponent>-->
+
+  <!-- A request handler for demonstrating the clustering component
+
+       This is purely as an example.
+
+       In reality you will likely want to add the component to your
+       already specified request handlers.
+    -->
+  <!--<requestHandler name="/clustering"
+                  startup="lazy"
+                  enable="${solr.clustering.enabled:false}"
+                  class="solr.SearchHandler">
+    <lst name="defaults">
+      <bool name="clustering">true</bool>
+      <str name="clustering.engine">default</str>
+      <bool name="clustering.results">true</bool>-->
+      <!-- The title field -->
+      <!--<str name="carrot.title">name</str>-->
+      <!--<str name="carrot.url">id</str>-->
+      <!-- The field to cluster on -->
+       <!--<str name="carrot.snippet">features</str>-->
+       <!-- produce summaries -->
+       <!--<bool name="carrot.produceSummary">true</bool>-->
+       <!-- the maximum number of labels per cluster -->
+       <!--<int name="carrot.numDescriptions">5</int>-->
+       <!-- produce sub clusters -->
+       <!--<bool name="carrot.outputSubClusters">false</bool>-->
+
+       <!--<str name="defType">edismax</str>
+       <str name="qf">
+          text^0.5 features^1.0 name^1.2 sku^1.5 id^10.0 manu^1.1 cat^1.4
+       </str>
+       <str name="q.alt">*:*</str>
+       <str name="rows">10</str>
+       <str name="fl">*,score</str>
+    </lst>
+    <arr name="last-components">
+      <str>clustering</str>
+    </arr>
+  </requestHandler>-->
+
+  <!-- Terms Component
+
+       http://wiki.apache.org/solr/TermsComponent
+
+       A component to return terms and document frequency of those
+       terms
+    -->
+  <searchComponent name="terms" class="solr.TermsComponent"/>
+
+  <!-- A request handler for demonstrating the terms component -->
+  <requestHandler name="/terms" class="solr.SearchHandler" startup="lazy">
+     <lst name="defaults">
+      <bool name="terms">true</bool>
+    </lst>
+    <arr name="components">
+      <str>terms</str>
+    </arr>
+  </requestHandler>
+
+  <!-- Query Elevation Component
+
+       http://wiki.apache.org/solr/QueryElevationComponent
+
+       a search component that enables you to configure the top
+       results for a given query regardless of the normal lucene
+       scoring.
+    -->
+  <searchComponent name="elevator" class="solr.QueryElevationComponent" >
+    <!-- pick a fieldType to analyze queries -->
+    <str name="queryFieldType">string</str>
+    <str name="config-file">elevate.xml</str>
+  </searchComponent>
+
+  <!-- A request handler for demonstrating the elevator component -->
+  <requestHandler name="/elevate" class="solr.SearchHandler" startup="lazy">
+    <lst name="defaults">
+      <str name="echoParams">explicit</str>
+    </lst>
+    <arr name="last-components">
+      <str>elevator</str>
+    </arr>
+  </requestHandler>
+
+  <!-- Highlighting Component
+
+       http://wiki.apache.org/solr/HighlightingParameters
+    -->
+  <searchComponent class="solr.HighlightComponent" name="highlight">
+    <highlighting>
+      <!-- Configure the standard fragmenter -->
+      <!-- This could most likely be commented out in the "default" case -->
+      <fragmenter name="gap"
+                  default="true"
+                  class="solr.highlight.GapFragmenter">
+        <lst name="defaults">
+          <int name="hl.fragsize">100</int>
+        </lst>
+      </fragmenter>
+
+      <!-- A regular-expression-based fragmenter
+           (for sentence extraction)
+        -->
+      <fragmenter name="regex"
+                  class="solr.highlight.RegexFragmenter">
+        <lst name="defaults">
+          <!-- slightly smaller fragsizes work better because of slop -->
+          <int name="hl.fragsize">70</int>
+          <!-- allow 50% slop on fragment sizes -->
+          <float name="hl.regex.slop">0.5</float>
+          <!-- a basic sentence pattern -->
+          <str name="hl.regex.pattern">[-\w ,/\n\&quot;&apos;]{20,200}</str>
+        </lst>
+      </fragmenter>
+
+      <!-- Configure the standard formatter -->
+      <formatter name="html"
+                 default="true"
+                 class="solr.highlight.HtmlFormatter">
+        <lst name="defaults">
+          <str name="hl.simple.pre"><![CDATA[<strong>]]></str>
+          <str name="hl.simple.post"><![CDATA[</strong>]]></str>
+        </lst>
+      </formatter>
+
+      <!-- Configure the standard encoder -->
+      <encoder name="html"
+               class="solr.highlight.HtmlEncoder" />
+
+      <!-- Configure the standard fragListBuilder -->
+      <fragListBuilder name="simple"
+                       default="true"
+                       class="solr.highlight.SimpleFragListBuilder"/>
+
+      <!-- Configure the single fragListBuilder -->
+      <fragListBuilder name="single"
+                       class="solr.highlight.SingleFragListBuilder"/>
+
+      <!-- default tag FragmentsBuilder -->
+      <fragmentsBuilder name="default"
+                        default="true"
+                        class="solr.highlight.ScoreOrderFragmentsBuilder">
+        <!--
+        <lst name="defaults">
+          <str name="hl.multiValuedSeparatorChar">/</str>
+        </lst>
+        -->
+      </fragmentsBuilder>
+
+      <!-- multi-colored tag FragmentsBuilder -->
+      <fragmentsBuilder name="colored"
+                        class="solr.highlight.ScoreOrderFragmentsBuilder">
+        <lst name="defaults">
+          <str name="hl.tag.pre"><![CDATA[
+               <b style="background:yellow">,<b style="background:lawgreen">,
+               <b style="background:aquamarine">,<b style="background:magenta">,
+               <b style="background:palegreen">,<b style="background:coral">,
+               <b style="background:wheat">,<b style="background:khaki">,
+               <b style="background:lime">,<b style="background:deepskyblue">]]></str>
+          <str name="hl.tag.post"><![CDATA[</b>]]></str>
+        </lst>
+      </fragmentsBuilder>
+
+      <boundaryScanner name="default"
+                       default="true"
+                       class="solr.highlight.SimpleBoundaryScanner">
+        <lst name="defaults">
+          <str name="hl.bs.maxScan">10</str>
+          <str name="hl.bs.chars">.,!? &#9;&#10;&#13;</str>
+        </lst>
+      </boundaryScanner>
+
+      <boundaryScanner name="breakIterator"
+                       class="solr.highlight.BreakIteratorBoundaryScanner">
+        <lst name="defaults">
+          <!-- type should be one of CHARACTER, WORD(default), LINE and SENTENCE -->
+          <str name="hl.bs.type">WORD</str>
+          <!-- language and country are used when constructing Locale object.  -->
+          <!-- And the Locale object will be used when getting instance of BreakIterator -->
+          <str name="hl.bs.language">en</str>
+          <str name="hl.bs.country">US</str>
+        </lst>
+      </boundaryScanner>
+    </highlighting>
+  </searchComponent>
+
+  <!-- Update Processors
+
+       Chains of Update Processor Factories for dealing with Update
+       Requests can be declared, and then used by name in Update
+       Request Processors
+
+       http://wiki.apache.org/solr/UpdateRequestProcessor
+
+    -->
+  <!-- Deduplication
+
+       An example dedup update processor that creates the "id" field
+       on the fly based on the hash code of some other fields.  This
+       example has overwriteDupes set to false since we are using the
+       id field as the signatureField and Solr will maintain
+       uniqueness based on that anyway.
+
+    -->
+  <!--
+     <updateRequestProcessorChain name="dedupe">
+       <processor class="solr.processor.SignatureUpdateProcessorFactory">
+         <bool name="enabled">true</bool>
+         <str name="signatureField">id</str>
+         <bool name="overwriteDupes">false</bool>
+         <str name="fields">name,features,cat</str>
+         <str name="signatureClass">solr.processor.Lookup3Signature</str>
+       </processor>
+       <processor class="solr.LogUpdateProcessorFactory" />
+       <processor class="solr.RunUpdateProcessorFactory" />
+     </updateRequestProcessorChain>
+    -->
+
+  <!-- Language identification
+
+       This example update chain identifies the language of the incoming
+       documents using the langid contrib. The detected language is
+       written to field language_s. No field name mapping is done.
+       The fields used for detection are text, title, subject and description,
+       making this example suitable for detecting languages form full-text
+       rich documents injected via ExtractingRequestHandler.
+       See more about langId at http://wiki.apache.org/solr/LanguageDetection
+    -->
+    <!--
+     <updateRequestProcessorChain name="langid">
+       <processor class="org.apache.solr.update.processor.TikaLanguageIdentifierUpdateProcessorFactory">
+         <str name="langid.fl">text,title,subject,description</str>
+         <str name="langid.langField">language_s</str>
+         <str name="langid.fallback">en</str>
+       </processor>
+       <processor class="solr.LogUpdateProcessorFactory" />
+       <processor class="solr.RunUpdateProcessorFactory" />
+     </updateRequestProcessorChain>
+    -->
+
+  <!-- Response Writers
+
+       http://wiki.apache.org/solr/QueryResponseWriter
+
+       Request responses will be written using the writer specified by
+       the 'wt' request parameter matching the name of a registered
+       writer.
+
+       The "default" writer is the default and will be used if 'wt' is
+       not specified in the request.
+    -->
+  <!-- The following response writers are implicitly configured unless
+       overridden...
+    -->
+  <!--
+     <queryResponseWriter name="xml"
+                          default="true"
+                          class="solr.XMLResponseWriter" />
+     <queryResponseWriter name="json" class="solr.JSONResponseWriter"/>
+     <queryResponseWriter name="python" class="solr.PythonResponseWriter"/>
+     <queryResponseWriter name="ruby" class="solr.RubyResponseWriter"/>
+     <queryResponseWriter name="php" class="solr.PHPResponseWriter"/>
+     <queryResponseWriter name="phps" class="solr.PHPSerializedResponseWriter"/>
+     <queryResponseWriter name="csv" class="solr.CSVResponseWriter"/>
+    -->
+
+  <queryResponseWriter name="json" class="solr.JSONResponseWriter">
+     <!-- For the purposes of the tutorial, JSON responses are written as
+      plain text so that they are easy to read in *any* browser.
+      If you expect a MIME type of "application/json" just remove this override.
+     -->
+    <str name="content-type">text/plain; charset=UTF-8</str>
+  </queryResponseWriter>
+
+  <!--
+     Custom response writers can be declared as needed...
+    -->
+    <!-- The solr.velocity.enabled flag is used by Solr's test cases so that this response writer is not
+         loaded (causing an error if contrib/velocity has not been built fully) -->
+    <!-- <queryResponseWriter name="velocity" class="solr.VelocityResponseWriter" enable="${solr.velocity.enabled:true}"/> -->
+
+  <!-- XSLT response writer transforms the XML output by any xslt file found
+       in Solr's conf/xslt directory.  Changes to xslt files are checked for
+       every xsltCacheLifetimeSeconds.
+    -->
+  <queryResponseWriter name="xslt" class="solr.XSLTResponseWriter">
+    <int name="xsltCacheLifetimeSeconds">5</int>
+  </queryResponseWriter>
+
+  <!-- Query Parsers
+
+       http://wiki.apache.org/solr/SolrQuerySyntax
+
+       Multiple QParserPlugins can be registered by name, and then
+       used in either the "defType" param for the QueryComponent (used
+       by SearchHandler) or in LocalParams
+    -->
+  <!-- example of registering a query parser -->
+  <!--
+     <queryParser name="myparser" class="com.mycompany.MyQParserPlugin"/>
+    -->
+
+  <!-- Function Parsers
+
+       http://wiki.apache.org/solr/FunctionQuery
+
+       Multiple ValueSourceParsers can be registered by name, and then
+       used as function names when using the "func" QParser.
+    -->
+  <!-- example of registering a custom function parser  -->
+  <!--
+     <valueSourceParser name="myfunc"
+                        class="com.mycompany.MyValueSourceParser" />
+    -->
+
+  <!-- Legacy config for the admin interface -->
+  <admin>
+    <defaultQuery>*:*</defaultQuery>
+
+    <!-- configure a healthcheck file for servers behind a
+         loadbalancer
+      -->
+    <!--
+       <healthcheck type="file">server-enabled</healthcheck>
+      -->
+  </admin>
+
+  <!-- Following is a dynamic way to include other components or any customized solrconfig.xml stuff, added by other contrib modules -->
+  <xi:include href="solrconfig_extra.xml" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <xi:fallback>
+    <!-- Spell Check
+
+        The spell check component can return a list of alternative spelling
+        suggestions. This component must be defined in
+        solrconfig_extra.xml if present, since it's used in the search handler.
+
+        http://wiki.apache.org/solr/SpellCheckComponent
+     -->
+    <searchComponent name="spellcheck" class="solr.SpellCheckComponent">
+
+    <str name="queryAnalyzerFieldType">textSpell</str>
+
+    <!-- a spellchecker built from a field of the main index -->
+      <lst name="spellchecker">
+        <str name="name">default</str>
+        <str name="field">spell</str>
+        <str name="spellcheckIndexDir">spellchecker</str>
+        <str name="buildOnOptimize">true</str>
+      </lst>
+    </searchComponent>
+    </xi:fallback>
+  </xi:include>
+
+</config>

+ 80 - 0
sites/all/modules/contrib/search/search_api_solr/solr-conf/6.x/solrconfig_extra.xml

@@ -0,0 +1,80 @@
+<!-- Spell Check
+
+    The spell check component can return a list of alternative spelling
+    suggestions.
+
+    http://wiki.apache.org/solr/SpellCheckComponent
+ -->
+<searchComponent name="spellcheck" class="solr.SpellCheckComponent">
+
+<str name="queryAnalyzerFieldType">textSpell</str>
+
+<!-- Multiple "Spell Checkers" can be declared and used by this
+     component
+  -->
+
+<!-- a spellchecker built from a field of the main index, and
+     written to disk
+  -->
+<lst name="spellchecker">
+  <str name="name">default</str>
+  <str name="field">spell</str>
+  <str name="spellcheckIndexDir">spellchecker</str>
+  <str name="buildOnOptimize">true</str>
+  <!-- uncomment this to require terms to occur in 1% of the documents in order to be included in the dictionary
+    <float name="thresholdTokenFrequency">.01</float>
+  -->
+</lst>
+
+<!--
+  Adding German spellhecker index to our Solr index
+  This also requires to enable the content in schema_extra_types.xml and schema_extra_fields.xml
+-->
+<!--
+<lst name="spellchecker">
+  <str name="name">spellchecker_de</str>
+  <str name="field">spell_de</str>
+  <str name="spellcheckIndexDir">./spellchecker_de</str>
+  <str name="buildOnOptimize">true</str>
+</lst>
+-->
+
+<!-- a spellchecker that uses a different distance measure -->
+<!--
+   <lst name="spellchecker">
+     <str name="name">jarowinkler</str>
+     <str name="field">spell</str>
+     <str name="distanceMeasure">
+       org.apache.lucene.search.spell.JaroWinklerDistance
+     </str>
+     <str name="spellcheckIndexDir">spellcheckerJaro</str>
+   </lst>
+ -->
+
+<!-- a spellchecker that use an alternate comparator
+
+     comparatorClass be one of:
+      1. score (default)
+      2. freq (Frequency first, then score)
+      3. A fully qualified class name
+  -->
+<!--
+   <lst name="spellchecker">
+     <str name="name">freq</str>
+     <str name="field">lowerfilt</str>
+     <str name="spellcheckIndexDir">spellcheckerFreq</str>
+     <str name="comparatorClass">freq</str>
+     <str name="buildOnCommit">true</str>
+  -->
+
+<!-- A spellchecker that reads the list of words from a file -->
+<!--
+   <lst name="spellchecker">
+     <str name="classname">solr.FileBasedSpellChecker</str>
+     <str name="name">file</str>
+     <str name="sourceLocation">spellings.txt</str>
+     <str name="characterEncoding">UTF-8</str>
+     <str name="spellcheckIndexDir">spellcheckerFile</str>
+   </lst>
+  -->
+</searchComponent>

+ 20 - 0
sites/all/modules/contrib/search/search_api_solr/solr-conf/6.x/solrcore.properties

@@ -0,0 +1,20 @@
+# Defines Solr properties for this specific core.
+solr.replication.master=false
+solr.replication.slave=false
+solr.replication.pollInterval=00:00:60
+solr.replication.masterUrl=http://localhost:8983/solr
+solr.replication.confFiles=schema.xml,mapping-ISOLatin1Accent.txt,protwords.txt,stopwords.txt,synonyms.txt,elevate.xml
+solr.mlt.timeAllowed=2000
+# You should not set your luceneMatchVersion to anything lower than your Solr
+# Version.
+solr.luceneMatchVersion=6.0
+solr.pinkPony.timeAllowed=-1
+# autoCommit after 10000 docs
+solr.autoCommit.MaxDocs=10000
+# autoCommit after 2 minutes
+solr.autoCommit.MaxTime=120000
+# autoSoftCommit after 2000 docs
+solr.autoSoftCommit.MaxDocs=2000
+# autoSoftCommit after 10 seconds
+solr.autoSoftCommit.MaxTime=10000
+solr.contrib.dir=../../../contrib

+ 4 - 0
sites/all/modules/contrib/search/search_api_solr/solr-conf/6.x/stopwords.txt

@@ -0,0 +1,4 @@
+# Contains words which shouldn't be indexed for fulltext fields, e.g., because
+# they're too common. For documentation of the format, see
+# http://wiki.apache.org/solr/AnalyzersTokenizersTokenFilters#solr.StopFilterFactory
+# (Lines starting with a pound character # are ignored.)

+ 3 - 0
sites/all/modules/contrib/search/search_api_solr/solr-conf/6.x/synonyms.txt

@@ -0,0 +1,3 @@
+# Contains synonyms to use for your index. For the format used, see
+# http://wiki.apache.org/solr/AnalyzersTokenizersTokenFilters#solr.SynonymFilterFactory
+# (Lines starting with a pound character # are ignored.)