瀏覽代碼

updated search_api and search_api_solr

Bachir Soussi Chiadmi 8 年之前
父節點
當前提交
ba1ec7113e
共有 67 個文件被更改,包括 5174 次插入685 次删除
  1. 0 28
      sites/all/modules/contrib/search/search_api/0001-re-added-own-boosts.patch
  2. 0 28
      sites/all/modules/contrib/search/search_api/0002-taxo-term-translation-bug-added-reference-to-bug-fix.patch
  3. 0 25
      sites/all/modules/contrib/search/search_api/0003-NODE_PUBLISED-in-previous-patches-i-commented-the-ne.patch
  4. 0 50
      sites/all/modules/contrib/search/search_api/0004-Icons.patch
  5. 100 24
      sites/all/modules/contrib/search/search_api/CHANGELOG.txt
  6. 23 6
      sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/adapter.inc
  7. 22 1
      sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_term.inc
  8. 3 3
      sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/search_api_facetapi.info
  9. 3 3
      sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/search_api_facetapi.module
  10. 16 0
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/README.txt
  11. 29 6
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_argument_more_like_this.inc
  12. 40 2
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_date.inc
  13. 2 1
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_fulltext.inc
  14. 13 0
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_taxonomy_term.inc
  15. 48 0
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_text.inc
  16. 75 27
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/query.inc
  17. 3 3
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/search_api_views.info
  18. 2 7
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/search_api_views.views.inc
  19. 27 3
      sites/all/modules/contrib/search/search_api/includes/callback_add_aggregation.inc
  20. 1 1
      sites/all/modules/contrib/search/search_api/includes/callback_add_viewed_entity.inc
  21. 50 29
      sites/all/modules/contrib/search/search_api/includes/callback_bundle_filter.inc
  22. 1 1
      sites/all/modules/contrib/search/search_api/includes/callback_language_control.inc
  23. 31 0
      sites/all/modules/contrib/search/search_api/includes/callback_user_status.inc
  24. 142 28
      sites/all/modules/contrib/search/search_api/includes/datasource.inc
  25. 239 20
      sites/all/modules/contrib/search/search_api/includes/datasource_entity.inc
  26. 357 0
      sites/all/modules/contrib/search/search_api/includes/datasource_multiple.inc
  27. 1 1
      sites/all/modules/contrib/search/search_api/includes/exception.inc
  28. 10 3
      sites/all/modules/contrib/search/search_api/includes/index_entity.inc
  29. 12 5
      sites/all/modules/contrib/search/search_api/includes/processor.inc
  30. 2 2
      sites/all/modules/contrib/search/search_api/includes/processor_highlight.inc
  31. 3 1
      sites/all/modules/contrib/search/search_api/includes/processor_html_filter.inc
  32. 3 1
      sites/all/modules/contrib/search/search_api/includes/processor_stopwords.inc
  33. 16 4
      sites/all/modules/contrib/search/search_api/includes/query.inc
  34. 4 4
      sites/all/modules/contrib/search/search_api/includes/server_entity.inc
  35. 9 1
      sites/all/modules/contrib/search/search_api/includes/service.inc
  36. 207 86
      sites/all/modules/contrib/search/search_api/search_api.admin.inc
  37. 25 5
      sites/all/modules/contrib/search/search_api/search_api.api.php
  38. 110 2
      sites/all/modules/contrib/search/search_api/search_api.drush.inc
  39. 5 3
      sites/all/modules/contrib/search/search_api/search_api.info
  40. 71 0
      sites/all/modules/contrib/search/search_api/search_api.install
  41. 208 44
      sites/all/modules/contrib/search/search_api/search_api.module
  42. 73 0
      sites/all/modules/contrib/search/search_api/search_api.test
  43. 3 3
      sites/all/modules/contrib/search/search_api/tests/search_api_test.info
  44. 68 16
      sites/all/modules/contrib/search/search_api_solr/CHANGELOG.txt
  45. 0 72
      sites/all/modules/contrib/search/search_api_solr/INSTALL.txt
  46. 8 5
      sites/all/modules/contrib/search/search_api_solr/README.txt
  47. 196 65
      sites/all/modules/contrib/search/search_api_solr/includes/service.inc
  48. 69 51
      sites/all/modules/contrib/search/search_api_solr/includes/solr_connection.inc
  49. 5 6
      sites/all/modules/contrib/search/search_api_solr/includes/solr_connection.interface.inc
  50. 4 2
      sites/all/modules/contrib/search/search_api_solr/includes/spellcheck.inc
  51. 18 0
      sites/all/modules/contrib/search/search_api_solr/search_api_solr.api.php
  52. 3 3
      sites/all/modules/contrib/search/search_api_solr/search_api_solr.info
  53. 43 1
      sites/all/modules/contrib/search/search_api_solr/solr-conf/3.x/schema.xml
  54. 2 1
      sites/all/modules/contrib/search/search_api_solr/solr-conf/3.x/solrconfig.xml
  55. 43 1
      sites/all/modules/contrib/search/search_api_solr/solr-conf/4.x/schema.xml
  56. 9 1
      sites/all/modules/contrib/search/search_api_solr/solr-conf/4.x/solrconfig.xml
  57. 31 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/5.x/elevate.xml
  58. 14 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/5.x/mapping-ISOLatin1Accent.txt
  59. 7 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/5.x/protwords.txt
  60. 693 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/5.x/schema.xml
  61. 23 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/5.x/schema_extra_fields.xml
  62. 34 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/5.x/schema_extra_types.xml
  63. 1808 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/5.x/solrconfig.xml
  64. 80 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/5.x/solrconfig_extra.xml
  65. 20 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/5.x/solrcore.properties
  66. 4 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/5.x/stopwords.txt
  67. 3 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/5.x/synonyms.txt

+ 0 - 28
sites/all/modules/contrib/search/search_api/0001-re-added-own-boosts.patch

@@ -1,28 +0,0 @@
-From 6402fc7ab8f6343defbee7111dee7dd16a5082fc Mon Sep 17 00:00:00 2001
-From: Bachir Soussi Chiadmi <bachir@g-u-i.net>
-Date: Fri, 7 Feb 2014 10:10:15 +0100
-Subject: [PATCH 1/4] re-added own boosts
-
----
- search_api.admin.inc | 5 +++--
- 1 file changed, 3 insertions(+), 2 deletions(-)
-
-diff --git a/search_api.admin.inc b/search_api.admin.inc
-index f4210f4..b2269d6 100644
---- a/search_api.admin.inc
-+++ b/search_api.admin.inc
-@@ -1658,8 +1658,9 @@ function search_api_admin_index_fields(array $form, array &$form_state, SearchAp
-   // An array of option arrays for types, keyed by nesting level.
-   $types = array(0 => search_api_field_types());
-   $entity_types = entity_get_info();
--  $boosts = drupal_map_assoc(array('0.1', '0.2', '0.3', '0.5', '0.8', '1.0', '2.0', '3.0', '5.0', '8.0', '13.0', '21.0'));
--
-+  //$boosts = drupal_map_assoc(array('0.1', '0.2', '0.3', '0.5', '0.8', '1.0', '2.0', '3.0', '5.0', '8.0', '13.0', '21.0'));
-+  $boosts = drupal_map_assoc(array('0.1', '0.2', '0.3', '0.5', '0.8', '1.0', '2.0', '3.0', '5.0', '8.0', '13.0', '21.0', '100', '1000', '1010', '1020', '1030', '1040', '1050', '1060'));
-+   
-   $fulltext_types = array(0 => array('text'));
-   // Add all custom data types with fallback "text" to fulltext types as well.
-   foreach (search_api_get_data_type_info() as $id => $type) {
--- 
-2.3.5
-

+ 0 - 28
sites/all/modules/contrib/search/search_api/0002-taxo-term-translation-bug-added-reference-to-bug-fix.patch

@@ -1,28 +0,0 @@
-From 54ee5c7b3a05850e15067d77a182cb8fe723d8e0 Mon Sep 17 00:00:00 2001
-From: Bachir Soussi Chiadmi <bachir@g-u-i.net>
-Date: Fri, 7 Feb 2014 10:29:08 +0100
-Subject: [PATCH 2/4] taxo term translation bug : added reference to bug fix in
- comment
-
----
- search_api.module | 4 ++++
- 1 file changed, 4 insertions(+)
-
-diff --git a/search_api.module b/search_api.module
-index 000cadb..55bd54a 100644
---- a/search_api.module
-+++ b/search_api.module
-@@ -2221,6 +2221,10 @@ function search_api_extract_fields(EntityMetadataWrapper $wrapper, array $fields
-   foreach ($nested as $prefix => $nested_fields) {
-     if (isset($wrapper->$prefix)) {
-       $nested_fields = search_api_extract_fields($wrapper->$prefix, $nested_fields, $value_options);
-+      # http://drupal.org/node/1873910#comment-6876200
-+      // $subwrapper = $wrapper->$prefix;
-+      // $subwrapper->language( $wrapper->language->value() );
-+      // $nested_fields = search_api_extract_fields($subwrapper, $nested_fields, $value_options);  
-       foreach ($nested_fields as $field => $info) {
-         $fields["$prefix:$field"] = $info;
-       }
--- 
-2.3.5
-

+ 0 - 25
sites/all/modules/contrib/search/search_api/0003-NODE_PUBLISED-in-previous-patches-i-commented-the-ne.patch

@@ -1,25 +0,0 @@
-From 85251183d6ad5fadaa65154e33e1e8ac8ca7f9b0 Mon Sep 17 00:00:00 2001
-From: Bachir Soussi Chiadmi <bachir@g-u-i.net>
-Date: Fri, 7 Feb 2014 10:32:26 +0100
-Subject: [PATCH 3/4] NODE_PUBLISED : in previous patches i commented the next
- line, why ? maybe will have to do it again
-
----
- search_api.module | 1 +
- 1 file changed, 1 insertion(+)
-
-diff --git a/search_api.module b/search_api.module
-index 55bd54a..17f611a 100644
---- a/search_api.module
-+++ b/search_api.module
-@@ -1994,6 +1994,7 @@ function _search_api_query_add_node_access($account, SearchApiQueryInterface $qu
-     $query->filter($filter);
-   }
-   else {
-+    // /!\ in previous patches i commented the next line, why ? maybe will have to do it again
-     $query->condition('status', $published);
-   }
- 
--- 
-2.3.5
-

+ 0 - 50
sites/all/modules/contrib/search/search_api/0004-Icons.patch

@@ -1,50 +0,0 @@
-From c06be9a44ed0be31859a1800cf7f0ae6e8ae492a Mon Sep 17 00:00:00 2001
-From: Bachir Soussi Chiadmi <bachir@g-u-i.net>
-Date: Fri, 21 Feb 2014 19:49:45 +0100
-Subject: [PATCH 4/4] Icons re-added icons, why they didn't be here, i don't
- know ...
-
----
- disabled.png | Bin 0 -> 384 bytes
- enabled.png  | Bin 0 -> 383 bytes
- 2 files changed, 0 insertions(+), 0 deletions(-)
- create mode 100644 disabled.png
- create mode 100644 enabled.png
-
-diff --git a/disabled.png b/disabled.png
-new file mode 100644
-index 0000000000000000000000000000000000000000..224776502046765ef7c083ffa3229fd206b9c975
-GIT binary patch
-literal 384
-zcmV-`0e}99P)<h;3K|Lk000e1NJLTq000aC000aK1^@s6R&`wG0000PbVXQnQ*UN;
-zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzB1uF+RCwBik-tg<K@i4gmNYiGco6Uz
-z1Rp_+BwY$iC2}^tfuaNqN)E0PY=wY@f3Z~1&O-&?K=37m)3r_F_{}jFE*3iQyZL7J
-zn_c#nMT9h-L*7E#2LVlo2k}xSM_RBBJcfJ*9ns%$zMRR1dkA@F3^O3`V!2Gwi`45z
-zLR~;$(8^>Hxo5S~v);h!F5lHi?8tY}Y=6k>{VeZk13H0TfJ{L>zr#&187PJtE1&YF
-z#chq}k)8^(h8y8iq7K@{qH60+Je8qL{fT(Z%i(p9?@Xp=Ap7MLyiFg&aBu-LbgHOE
-zFV;2lcsCYG0D;xhDo4mEm@`uJI=W__B!9S*DqmuQ&OZ-#wfQCMPL+ypp>5y+{X%=Y
-e>QV2H00RI@_ptZRUTXUQ0000<MNUMnLSTXuN2F!|
-
-literal 0
-HcmV?d00001
-
-diff --git a/enabled.png b/enabled.png
-new file mode 100644
-index 0000000000000000000000000000000000000000..95f8730e6955f1de7d244817db5ed7678bce0f72
-GIT binary patch
-literal 383
-zcmV-_0f7FAP)<h;3K|Lk000e1NJLTq000aC000aK1^@s6R&`wG0000PbVXQnQ*UN;
-zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzAxT6*RCwBA{Qv(y0|*Fj@h<{WbwJF|
-zfC@eWanoC$jeQ^vBS?eLCf`Lsb}R#au=t(d<~T-yb)Ka_P8S1lpp21kmFrs|LkN$e
-zvp{SB#LPhaj_LoOKSDsv0L0&c_%)Ob!<&HE3Wy_s_%BE;(?6gD5Pt+>Hz0Nf;@42I
-zO+Xy_DRSR0DE}{rX8QO0Hv<r}0WtHJ*h80rv@;M-2jWm5{}<Oh%P1gw1yl_KBl}E~
-z|4_Gn2V&13X{Qgu9M3V!GyzD>fw~_IKsJ1Y+QJFM+u5cX*n=d1bS#iR2V^r;9)v$K
-zvP{rM4_1&(vw%1sYp{YMj=5K3DUcIIAP$!OExr-W1Y&_0|Ns6i2I7xE%z%bLVr3vT
-dAhiGi1_1Nf-q(XP%8~#8002ovPDHLkV1gxRmaYH*
-
-literal 0
-HcmV?d00001
-
--- 
-2.3.5
-

+ 100 - 24
sites/all/modules/contrib/search/search_api/CHANGELOG.txt

@@ -1,4 +1,80 @@
-Search API 1.14 (12/26/2014):
+Search API 1.x, dev (xxxx-xx-xx):
+---------------------------------
+- #2667872 by Les Lim: Added "0" to field boost options.
+- #2654328 by drunken monkey, donquixote: Fixed use of "<" and ">" for open
+  facet ranges.
+- #2639200 by joachim: Added sorting to "related fields" select box.
+- #2638740 by joachim, drunken monkey: Added a link to the index to the
+  "re-indexing necessary" message.
+- #2629136 by drunken monkey, deranga: Fixed wrong facet counts in edge cases
+  for active OR facets.
+- #2569461 by kraynuk.m, drunken monkey: Fixed existing table in update #7118.
+- #2631276 by tauno: Fixed the MLT handler for multi-entity indexes.
+- #2576265 by drunken monkey: Fixed view trying to search on non-fulltext field.
+- #2572487 by drunken monkey: Removed operator setting for date facets.
+- #2611714 by rakesh.gectcr, drunken monkey: Improved compliance with
+  documentation standards.
+- #2613054 by temkin: Fixed the "search-api-index" Drush command to allow
+  setting further options when indexing on all indexes.
+- #2611726 by Hubbs, rakesh.gectcr: Fixed several typos.
+- #2603500 by drunken monkey, krishna savithraj: Fixed Views fulltext searches
+  for keyword "All".
+- #2529262 by kingmackenzie, stefan.r: Added an option to Views date filters to
+  choose the format used by date popup.
+- #2583263 by drunken monkey: Fixed Views integration in combination with
+  Search API ET and similar modules.
+- #2592231 by drunken monkey, balintcsaba: Fixed ignored item language when
+  viewing translated items.
+- #2570879 by thePanz, drunken monkey: Added sorting of remembered search IDs.
+- #2565743 by drunken monkey: Fixed creation of comment indexes with specific
+  bundles.
+- #2563793 by drunken monkey, smitty, ReBa: Fixed Views base table definition
+  for "Multiple types" indexes.
+- #2567775 by joseph.olstad, drunken monkey: Fixed handling of broken HTML in
+  the "HTML filter" processor.
+- #2565005 by drunken monkey: Properly escape labels of "checkboxes"/"radios"
+  options
+- #2524314 by drunken monkey: Fixed bundle-setting for taxonomy term indexes.
+- #2550599 by ACF, drunken monkey: Fixed error on entity rebuilds.
+
+Search API 1.16 (2015-08-30):
+-----------------------------
+- #2502819: Fixed example code for hook_search_api_query_alter().
+- #2491175 by ptmkenny, drunken monkey: Added a data alteration for filtering
+  out blocked users.
+- #1197538 by thePanz, k4v, drunken monkey, ayalon, nadavoid, timodwhit, becw,
+  Elvar: Added support for the "Global: Random" sort in Views.
+- #2520934 by drunken monkey: Added an item type for indexing several types of
+  entities in one index.
+- #2533096 by drunken monkey: Fixed uncaught exception when deleting a server.
+- #2479453 by prics, drunken monkey: Added a Drush command to
+  list/enable/disable servers.
+- #2520684 by drunken monkey: Fixed "bundles" setting on indexes with "Index
+  immediately".
+- #2489882 by dww: Fixed Views taxonomy filter with "is (not) empty" operator
+- #2447213 by drunken monkey: Fixed issues with stale field settings for MLT
+  contextual filter.
+
+Search API 1.15 (2015-06-03):
+-----------------------------
+- #2190627 by m1n0, drunken monkey: Fixed fatal errors for views of disabled
+  indexes.
+- #2448849 by cgoffin: Added "year range" option for date filters.
+- #2414425 by Darren Oh, drunken monkey: Fixed backend form validation when
+  adding or editing a server.
+- #2450333 by drunken monkey: Added performance improvement when indexing
+  entity references.
+- #2450227 by StryKaizer, drunken monkey: Fixed OR facets on taxonomy terms.
+- #1184610 by drunken monkey: Added option to limit indexes to specific entity
+  bundles.
+- #1396222 by drunken monkey: Added a "First letter" aggregation type to the
+  "Aggregated fields" data alteration.
+- #2412895 by drunken monkey: Fixed entity load for Views entity access check.
+- #2414367 by Darren Oh, drunken monkey: Fixed detection of missing fields in
+  Views.
+- #2387161 by drunken monkey: Added a hook for altering search results.
+
+Search API 1.14 (2014-12-26):
 -----------------------------
 - #2382385 by illusionuk, drunken monkey: Fixed error handling when using
   invalid fulltext or sort field in Views.
@@ -25,7 +101,7 @@ Search API 1.14 (12/26/2014):
 - #2278737 by drunken monkey: Fixed use of multiple Views fulltext search
   filters.
 
-Search API 1.13 (07/23/2014):
+Search API 1.13 (2014-07-23):
 -----------------------------
 - #2281535 by areynolds, nicola85: Adapted to latest changes in Views cache
   plugins.
@@ -38,7 +114,7 @@ Search API 1.13 (07/23/2014):
 - #2216345 by bacardi55, fabianderijk, drunken monkey: Fixed array to string
   conversion in Highlighting processor.
 
-Search API 1.12 (05/23/2014):
+Search API 1.12 (2014-05-23):
 -----------------------------
 - #2265349 by drunken monkey: Marked _search_api_settings_equals() as
   deprecated.
@@ -65,7 +141,7 @@ Search API 1.12 (05/23/2014):
 - #2150779 by hefox: Fixed "Overridden" detection for index features.
 - #1227702 by drunken monkey: Improved error handling.
 
-Search API 1.11 (12/25/2013):
+Search API 1.11 (2013-12-25):
 -----------------------------
 - #1879196 by drunken monkey: Fixed invalid old indexes causing errors.
 - #2155127 by drunken monkey: Clarified the scope of the "Node access" and
@@ -81,7 +157,7 @@ Search API 1.11 (12/25/2013):
 - #2146435 by timkang: Fixed Views paging with custom pager add-ons.
 - #2150347 by drunken monkey: Added access callbacks for indexes and servers.
 
-Search API 1.10 (12/09/2013):
+Search API 1.10 (2013-12-09):
 -----------------------------
 - #2130819 by drunken monkey, Bojhan: Added UI improvements for the "View" tabs.
 - #2152327 by sirtet, miro_dietiker: Fixed typo in help text for drush sapi-c.
@@ -122,7 +198,7 @@ Search API 1.10 (12/09/2013):
 - #2100191 by drunken monkey, Bojhan: Added an admin description to the Search
   API landing page.
 
-Search API 1.9 (10/23/2013):
+Search API 1.9 (2013-10-23):
 ----------------------------
 - #2113277 by moonray, drunken monkey: Fixed date facet count for active item.
 - #2086783 by drunken monkey: Removed Views field handlers for "virtual" fields.
@@ -151,7 +227,7 @@ Search API 1.9 (10/23/2013):
 - #2075839 by leeomara, drunken monkey: Added descriptions to field lists for
   'Aggregated Fields'.
 
-Search API 1.8 (09/01/2013):
+Search API 1.8 (2013-09-01):
 ----------------------------
 - #1414048 by drunken monkey: Fixed exception in views.inc removes all Search
   API tables.
@@ -185,7 +261,7 @@ Search API 1.8 (09/01/2013):
 - #2040111 by arpieb: Fixed Views URL argument handler to allow multiple values.
 - #1064520 by drunken monkey: Added a processor for highlighting.
 
-Search API 1.7 (07/01/2013):
+Search API 1.7 (2013-07-01):
 ----------------------------
 - #1612708 by drunken monkey: Fixed Views caching with facet blocks.
 - #2024189 by drunken monkey: Improved serialization of the query class.
@@ -212,7 +288,7 @@ Search API 1.7 (07/01/2013):
 - #1285794 by drunken monkey: Fixed "All" option in Views' exposed "Items per
   page" setting.
 
-Search API 1.6 (05/29/2013):
+Search API 1.6 (2013-05-29):
 ----------------------------
 - #1649976 by Berdir, ilari.stenroth, drunken monkey: Fixed memory error during
   crons run for large indexes.
@@ -230,7 +306,7 @@ Search API 1.6 (05/29/2013):
 - #1760706 by jgraham, das-peter, drunken monkey: Added a flexible way for
   determining whether an index contains entities.
 
-Search API 1.5 (05/04/2013):
+Search API 1.5 (2013-05-04):
 ----------------------------
 - #1169254 by cslavoie, drunken monkey, DYdave: Added transliteration processor.
 - #1959088 by drunken monkey: Fixed titles for contextual filters.
@@ -247,7 +323,7 @@ Search API 1.5 (05/04/2013):
   in the Hierarchy data alteration.
 - #1702604 by JvE, slucero: Added option for maximum date facet depth.
 
-Search API 1.4 (01/09/2013):
+Search API 1.4 (2013-01-09):
 ----------------------------
 - #1827272 by drunken monkey: Fixed regression introduced by #1777710.
 - #1807622 by drunken monkey: Fixed definition of the default node index.
@@ -257,7 +333,7 @@ Search API 1.4 (01/09/2013):
   filters.
 - #1823916 by aschiwi: Fixed batch_sise typos.
 
-Search API 1.3 (10/10/2012):
+Search API 1.3 (2012-10-10):
 ----------------------------
 - Patch by mr.baileys: Fixed "enable" function doesn't use security tokens.
 - #1318904 by becw, das-peter, orakili, drunken monkey: Added improved handling
@@ -273,7 +349,7 @@ Search API 1.3 (10/10/2012):
 - #1414138 by drunken monkey: Fixed internal static index property cache.
 - #1253320 by drunken monkey, fago: Fixed improper error handling.
 
-Search API 1.2 (07/07/2012):
+Search API 1.2 (2012-07-07):
 ----------------------------
 - #1368548 by das-peter: Do not index views results by entity id.
 - #1422750 by drunken monkey, sepgil: Fixed illegal modification of entity
@@ -286,7 +362,7 @@ Search API 1.2 (07/07/2012):
   changed.
 - #1528436 by jsacksick, drunken monkey: Fixed handling of exportable entities.
 
-Search API 1.1 (05/23/2012):
+Search API 1.1 (2012-05-23):
 ----------------------------
 - Fixed escaping of error messages.
 - #1330506 by drunken monkey: Removed the old Facets module.
@@ -297,7 +373,7 @@ Search API 1.1 (05/23/2012):
   $service->configurationFormValidate() for empty forms.
 - #1400882 by mh86: Fixed "Index hierarchy" for "All parents".
 
-Search API 1.0 (12/15/2011):
+Search API 1.0 (2011-12-15):
 ----------------------------
 - #1350322 by drunken monkey: Fixed regressions introduced with cron queue
   indexing.
@@ -315,7 +391,7 @@ Search API 1.0 (12/15/2011):
   dependency plugin.
 - #1337292 by drunken monkey: Fixed facet dependency system.
 
-Search API 1.0, RC 1 (11/10/2011):
+Search API 1.0, RC 1 (2011-11-10):
 ----------------------------------
 API changes:
 - #1260834 by drunken monkey: Added a way to define custom data types.
@@ -386,7 +462,7 @@ Others:
 - #1161532 by drunken monkey: Fixed discerning between delete and revert in
   hook_*_delete().
 
-Search API 1.0, Beta 10 (06/20/2011):
+Search API 1.0, Beta 10 (2011-06-20):
 -------------------------------------
 API changes:
 - #1068342 by drunken monkey: Added a 'fields to run on' option for processors.
@@ -400,7 +476,7 @@ Others:
 - #1133864 by agentrickard, awolfey, greg.1.anderson, drunken monkey: Added
   Drush integration.
 
-Search API 1.0, Beta 9 (06/06/2011):
+Search API 1.0, Beta 9 (2011-06-06):
 ------------------------------------
 API changes:
 - #1089758 by becw, drunken monkey: Updated Views field handlers to utilize new
@@ -442,7 +518,7 @@ Others:
 - #1120850 by drunken monkey, fangel: Fixed type of related entities in nested
   lists.
 
-Search API 1.0, Beta 8 (04/02/2011):
+Search API 1.0, Beta 8 (2011-04-02):
 ------------------------------------
 API changes:
 - #1012878 by drunken monkey: Added a way to index an entity directly.
@@ -461,12 +537,12 @@ Others:
   search_api_facets_by_block_status().
 - #1081666 by danielnolde: Fixed PHP notices when property labels are missing.
 
-Search API 1.0, Beta 7 (03/08/2011):
+Search API 1.0, Beta 7 (2011-03-08):
 ------------------------------------
 - #1083828 by drunken monkey: Added documentation on indexing custom data.
 - #1081244 by drunken monkey: Fixed debug line still contained in DB backend.
 
-Search API 1.0, Beta 6 (03/04/2011):
+Search API 1.0, Beta 6 (2011-03-04):
 ------------------------------------
 API changes:
 - #1075810 by drunken monkey: Added API function for marking entities as dirty.
@@ -509,7 +585,7 @@ Others:
 - #1024514: Error when preprocessing muli-valued fulltext fields.
 - #1020372: CSS classes for facets.
 
-Search API 1.0, Beta 5 (01/05/2011):
+Search API 1.0, Beta 5 (2011-01-05):
 ------------------------------------
 API changes:
 - #917998: Enhance data alterations by making them objects.
@@ -531,7 +607,7 @@ Others:
 - #985324: Add "Current search" block.
 - #984174: Bug in Index::prepareProcessors() when processors have not been set.
 
-Search API 1.0, Beta 4 (11/29/2010):
+Search API 1.0, Beta 4 (2010-11-29):
 ------------------------------------
 API changes:
 - #976876: Move Solr module into its own project.
@@ -573,7 +649,7 @@ Others:
 - #938982: Not all SearchApiQuery options are passed.
 - #931066 by luke_b: HTTP timeout not set correctly.
 
-Search API 1.0, Beta 3 (09/30/2010):
+Search API 1.0, Beta 3 (2010-09-30):
 ------------------------------------
 - API mostly stable.
 - Five contrib modules exist:

+ 23 - 6
sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/adapter.inc

@@ -78,13 +78,10 @@ class SearchApiFacetapiAdapter extends FacetapiAdapter {
       // displayed.
       $facet_search_ids = isset($options['facet_search_ids']) ? $options['facet_search_ids'] : array();
 
+      // Remember this search ID, if necessary.
+      $this->rememberSearchId($index_id, $search_id);
+
       if (array_search($search_id, $facet_search_ids) === FALSE) {
-        $search_ids = variable_get('search_api_facets_search_ids', array());
-        if (empty($search_ids[$index_id][$search_id])) {
-          // Remember this search ID.
-          $search_ids[$index_id][$search_id] = $search_id;
-          variable_set('search_api_facets_search_ids', $search_ids);
-        }
         if (!$default_true) {
           continue; // We are only to show facets for explicitly named search ids.
         }
@@ -103,6 +100,23 @@ class SearchApiFacetapiAdapter extends FacetapiAdapter {
     }
   }
 
+  /**
+   * Adds a search ID to the list of known searches for an index.
+   *
+   * @param string $index_id
+   *   The machine name of the search index.
+   * @param string $search_id
+   *   The identifier of the executed search.
+   */
+  protected function rememberSearchId($index_id, $search_id) {
+    $search_ids = variable_get('search_api_facets_search_ids', array());
+    if (empty($search_ids[$index_id][$search_id])) {
+      $search_ids[$index_id][$search_id] = $search_id;
+      asort($search_ids[$index_id]);
+      variable_set('search_api_facets_search_ids', $search_ids);
+    }
+  }
+
   /**
    * Add the given facet to the query.
    */
@@ -257,6 +271,9 @@ class SearchApiFacetapiAdapter extends FacetapiAdapter {
         '#options' => $granularity_options,
         '#default_value' => isset($options['date_granularity']) ? $options['date_granularity'] : FACETAPI_DATE_MINUTE,
       );
+
+      // Date facets don't support the "OR" operator (for now).
+      $form['global']['operator']['#access'] = FALSE;
     }
 
     // Add an "Exclude" option for terms.

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

@@ -54,6 +54,27 @@ class SearchApiFacetapiTerm extends FacetapiQueryType implements FacetapiQueryTy
     }
     elseif ($operator == FACETAPI_OPERATOR_OR) {
       $conjunction = 'OR';
+      // When the operator is OR, remove parent terms from the active ones if
+      // children are active. If we don't do this, sending a term and its
+      // parent will produce the same results as just sending the parent.
+      if ($settings['flatten'] == '0') {
+        // Check the filters in reverse order, to avoid checking parents that
+        // will afterwards be removed anyways.
+        foreach (array_reverse(array_keys($active)) as $filter) {
+          // Skip this filter if it was already removed, or if it is the
+          // "missing value" filter ("!").
+          if (!isset($active[$filter]) || !is_numeric($filter)) {
+            continue;
+          }
+          $parents = taxonomy_get_parents_all($filter);
+          // The return value of taxonomy_get_parents_all() includes the term
+          // itself at index 0. Remove that to only get the term's ancestors.
+          unset($parents[0]);
+          foreach ($parents as $parent) {
+            unset($active[$parent->tid]);
+          }
+        }
+      }
     }
     else {
       $vars = array(
@@ -153,7 +174,7 @@ class SearchApiFacetapiTerm extends FacetapiQueryType implements FacetapiQueryTy
 
     // Always include the active facet items.
     foreach ($this->adapter->getActiveItems($this->facet) as $filter)  {
-      $build[$filter['value']]['#count'] = $results['result count'];
+      $build[$filter['value']]['#count'] = 0;
     }
 
     // Then, add the facets returned by the server.

+ 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 2014-12-26
-version = "7.x-1.14"
+; Information added by Drupal.org packaging script on 2016-02-26
+version = "7.x-1.16+29-dev"
 core = "7.x"
 project = "search_api"
-datestamp = "1419580682"
+datestamp = "1456500713"
 

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

@@ -340,13 +340,13 @@ function search_api_facetapi_facet_map_callback(array $values, array $options =
     $lower = isset($map[$range['lower']]) ? $map[$range['lower']] : $range['lower'];
     $upper = isset($map[$range['upper']]) ? $map[$range['upper']] : $range['upper'];
     if ($lower == '*' && $upper == '*') {
-      $map[$value] =  t('any');
+      $map[$value] = t('any');
     }
     elseif ($lower == '*') {
-      $map[$value] = "< $upper";
+      $map[$value] = " $upper";
     }
     elseif ($upper == '*') {
-      $map[$value] = "> $lower";
+      $map[$value] = " $lower";
     }
     else {
       $map[$value] = "$lower – $upper";

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

@@ -24,6 +24,22 @@ When these are present, the normal keywords should be ignored and the related
 items be returned as results instead. Sorting, filtering and range restriction
 should all work normally.
 
+"Random sort" feature
+---------------------
+This module defines the "Random sort" feature (feature key:
+"search_api_random_sort") that allows to randomly sort the results returned by a
+search. With a server supporting this, you can use the "Global: Random" sort to
+sort the view's results randomly. Every time the query is run a different
+sorting will be provided.
+
+For developers:
+A service class that wants to support this feature has to check for a
+"search_api_random" field in the search query's sorts and insert a random sort
+in that position. If the query is sorted in this way, then the
+"search_api_random_sort" query option can contain additional options for the
+random sort, as an associative array with any of the following keys:
+- seed: A numeric seed value to use for the random sort.
+
 "Facets block" display
 ----------------------
 Most features should be clear to users of Views. However, the module also

+ 29 - 6
sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_argument_more_like_this.inc

@@ -18,6 +18,7 @@ class SearchApiViewsHandlerArgumentMoreLikeThis extends SearchApiViewsHandlerArg
     $options = parent::option_definition();
     unset($options['break_phrase']);
     unset($options['not']);
+    $options['entity_type'] = array('default' => FALSE);
     $options['fields'] = array('default' => array());
     return $options;
   }
@@ -31,6 +32,20 @@ class SearchApiViewsHandlerArgumentMoreLikeThis extends SearchApiViewsHandlerArg
     unset($form['not']);
 
     $index = search_api_index_load(substr($this->table, 17));
+
+    if ($index->datasource() instanceof SearchApiCombinedEntityDataSourceController) {
+      $types = array_intersect_key(search_api_entity_type_options_list(), array_flip($index->options['datasource']['types']));
+      $form['entity_type'] = array(
+        '#type' => 'select',
+        '#title' => t('Entity type'),
+        '#description' => t('Select the entity type of the argument.'),
+        '#options' => $types,
+        '#default_value' => $this->options['entity_type'],
+        '#required' => TRUE,
+      );
+    }
+
+
     if (!empty($index->options['fields'])) {
       $fields = array();
       foreach ($index->getFields() as $key => $field) {
@@ -71,14 +86,22 @@ class SearchApiViewsHandlerArgumentMoreLikeThis extends SearchApiViewsHandlerArg
         $this->query->abort();
         return;
       }
-      $fields = $this->options['fields'] ? $this->options['fields'] : array();
-      if (empty($fields)) {
-        foreach ($this->query->getIndex()->options['fields'] as $key => $field) {
-          $fields[] = $key;
-        }
+      $index_fields = array_keys($this->query->getIndex()->options['fields']);
+      if (empty($this->options['fields'])) {
+        $fields = $index_fields;
+      }
+      else {
+        $fields = array_intersect($this->options['fields'], $index_fields);
+      }
+      if ($this->query->getIndex()->datasource() instanceof SearchApiCombinedEntityDataSourceController) {
+        $id = $this->options['entity_type'] . '/' . $this->argument;
       }
+      else {
+        $id = $this->argument;
+      }
+
       $mlt = array(
-        'id' => $this->argument,
+        'id' => $id,
         'fields' => $fields,
       );
       $this->query->getSearchApiQuery()->setOption('search_api_mlt', $mlt);

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

@@ -16,6 +16,8 @@ class SearchApiViewsHandlerFilterDate extends SearchApiViewsHandlerFilter {
   public function option_definition() {
     return parent::option_definition() + array(
       'widget_type' => array('default' => 'default'),
+      'date_popup_format' => array('default' => 'm/d/Y'),
+      'year_range' => array('default' => '-3:+3'),
     );
   }
 
@@ -34,14 +36,49 @@ class SearchApiViewsHandlerFilterDate extends SearchApiViewsHandlerFilter {
    */
   public function extra_options_form(&$form, &$form_state) {
     parent::extra_options_form($form, $form_state);
+
     if (module_exists('date_popup')) {
-      $widget_options = array('default' => 'Default', 'date_popup' => 'Date popup');
+      $widget_options = array(
+        'default' => 'Default',
+        'date_popup' => 'Date popup',
+      );
       $form['widget_type'] = array(
         '#type' => 'radios',
         '#title' => t('Date selection form element'),
         '#default_value' => $this->options['widget_type'],
         '#options' => $widget_options,
       );
+      $form['date_popup_format'] = array(
+        '#type' => 'textfield',
+        '#title' => t('Date format'),
+        '#default_value' => $this->options['date_popup_format'],
+        '#description' => t('A date in any format understood by <a href="@doc-link">PHP</a>. For example, "Y-m-d" or "m/d/Y".', array(
+          '@doc-link' => 'http://php.net/manual/en/function.date.php'
+        )),
+        '#states' => array(
+          'visible' => array(
+            ':input[name="options[widget_type]"]' => array('value' => 'date_popup'),
+          ),
+        ),
+      );
+    }
+
+    if (module_exists('date_api')) {
+      $form['year_range'] = array(
+        '#type' => 'date_year_range',
+        '#default_value' => $this->options['year_range'],
+      );
+    }
+  }
+
+  /**
+   * Validate extra options.
+   */
+  public function extra_options_validate($form, &$form_state) {
+    if (isset($form_state['values']['options']['year_range'])) {
+      if (!preg_match('/^(?:\-[0-9]{1,4}|[0-9]{4}):(?:[\+|\-][0-9]{1,4}|[0-9]{4})$/', $form_state['values']['options']['year_range'])) {
+        form_error($form['year_range'], t('Date year range must be in the format -9:+9, 2005:2010, -9:2010, or 2005:+9'));
+      }
     }
   }
 
@@ -55,7 +92,8 @@ class SearchApiViewsHandlerFilterDate extends SearchApiViewsHandlerFilter {
     // according to what date_popup expects.
     if ($this->options['widget_type'] == 'date_popup' && module_exists('date_popup')) {
       $form['value']['#type'] = 'date_popup';
-      $form['value']['#date_format'] = 'm/d/Y';
+      $form['value']['#date_format'] =  $this->options['date_popup_format'];
+      $form['value']['#date_year_range'] = $this->options['year_range'];
       unset($form['value']['#description']);
     }
     elseif (empty($form_state['exposed'])) {

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

@@ -144,7 +144,8 @@ class SearchApiViewsHandlerFilterFulltext extends SearchApiViewsHandlerFilterTex
       return;
     }
     $fields = $this->options['fields'];
-    $fields = $fields ? $fields : array_keys($this->getFulltextFields());
+    $available_fields = array_keys($this->getFulltextFields());
+    $fields = $fields ? array_intersect($fields, $available_fields) : $available_fields;
 
     // If something already specifically set different fields, we silently fall
     // back to mere filtering.

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

@@ -178,12 +178,25 @@ class SearchApiViewsHandlerFilterTaxonomyTerm extends SearchApiViewsHandlerFilte
       return TRUE;
     }
 
+    // We need to know the operator, which is normally set in
+    // views_handler_filter::accept_exposed_input(), before we actually call
+    // the parent version of ourselves.
+    if (!empty($this->options['expose']['use_operator']) && !empty($this->options['expose']['operator_id']) && isset($input[$this->options['expose']['operator_id']])) {
+      $this->operator = $input[$this->options['expose']['operator_id']];
+    }
+
     // If view is an attachment and is inheriting exposed filters, then assume
     // exposed input has already been validated.
     if (!empty($this->view->is_attachment) && $this->view->display_handler->uses_exposed()) {
       $this->validated_exposed_input = (array) $this->view->exposed_raw_input[$this->options['expose']['identifier']];
     }
 
+    // If we're checking for EMPTY or NOT, we don't need any input, and we can
+    // say that our input conditions are met by just having the right operator.
+    if ($this->operator == 'empty' || $this->operator == 'not empty') {
+      return TRUE;
+    }
+
     // If it's non-required and there's no value don't bother filtering.
     if (!$this->options['expose']['required'] && empty($this->validated_exposed_input)) {
       return FALSE;

+ 48 - 0
sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_text.inc

@@ -17,4 +17,52 @@ class SearchApiViewsHandlerFilterText extends SearchApiViewsHandlerFilter {
     return array('=' => t('contains'), '<>' => t("doesn't contain"));
   }
 
+  /**
+   * Determines whether input from the exposed filters affects this filter.
+   *
+   * Overridden to not treat "All" differently.
+   *
+   * @param array $input
+   *   The user input from the exposed filters.
+   *
+   * @return bool
+   *   TRUE if the input should change the behavior of this filter.
+   */
+  public function accept_exposed_input($input) {
+    if (empty($this->options['exposed'])) {
+      return TRUE;
+    }
+
+    if (!empty($this->options['expose']['use_operator']) && !empty($this->options['expose']['operator_id']) && isset($input[$this->options['expose']['operator_id']])) {
+      $this->operator = $input[$this->options['expose']['operator_id']];
+    }
+
+    if (!empty($this->options['expose']['identifier'])) {
+      $value = $input[$this->options['expose']['identifier']];
+
+      // Various ways to check for the absence of non-required input.
+      if (empty($this->options['expose']['required'])) {
+        if (($this->operator == 'empty' || $this->operator == 'not empty') && $value === '') {
+          $value = ' ';
+        }
+
+        if (!empty($this->always_multiple) && $value === '') {
+          return FALSE;
+        }
+      }
+
+      if (isset($value)) {
+        $this->value = $value;
+        if (empty($this->always_multiple) && empty($this->options['expose']['multiple'])) {
+          $this->value = array($value);
+        }
+      }
+      else {
+        return FALSE;
+      }
+    }
+
+    return TRUE;
+  }
+
 }

+ 75 - 27
sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/query.inc

@@ -122,19 +122,63 @@ class SearchApiViewsQuery extends views_plugin_query {
   }
 
   /**
-   * Add a sort to the query.
+   * Adds a sort to the query.
    *
-   * @param $selector
+   * @param string $selector
    *   The field to sort on. All indexed fields of the index are valid values.
-   *   In addition, the special fields 'search_api_relevance' (sort by
-   *   relevance) and 'search_api_id' (sort by item id) may be used.
-   * @param $order
+   *   In addition, these special fields may be used:
+   *   - search_api_relevance: sort by relevance;
+   *   - search_api_id: sort by item id;
+   *   - search_api_random: random sort (available only if the server supports
+   *     the "search_api_random_sort" feature).
+   * @param string $order
    *   The order to sort items in - either 'ASC' or 'DESC'. Defaults to 'ASC'.
    */
   public function add_selector_orderby($selector, $order = 'ASC') {
     $this->query->sort($selector, $order);
   }
 
+  /**
+   * Provides a sorting method as present in the Views default query plugin.
+   *
+   * This is provided so that the "Global: Random" sort included in Views will
+   * work properly with Search API Views. Random sorting is only supported if
+   * the active search server supports the "search_api_random_sort" feature,
+   * though, otherwise the call will be ignored.
+   *
+   * This method can only be used to sort randomly, as would be done with the
+   * default query plugin. All other calls are ignored.
+   *
+   * @param string|null $table
+   *   Only "rand" is recognized here, all other calls are ignored.
+   * @param string|null $field
+   *   Is ignored and only present for compatibility reasons.
+   * @param string $order
+   *   Either "ASC" or "DESC".
+   * @param string|null $alias
+   *   Is ignored and only present for compatibility reasons.
+   * @param array $params
+   *   The following optional parameters are recognized:
+   *   - seed: a predefined seed for the random generator.
+   *
+   * @see views_plugin_query_default::add_orderby()
+   */
+  public function add_orderby($table, $field = NULL, $order = 'ASC', $alias = '', $params = array()) {
+    $server = $this->getIndex()->server();
+    if ($table == 'rand') {
+      if ($server->supportsFeature('search_api_random_sort')) {
+        $this->add_selector_orderby('search_api_random', $order);
+        if ($params) {
+          $this->setOption('search_api_random_sort', $params);
+        }
+      }
+      else {
+        $variables['%server'] = $server->label();
+        watchdog('search_api_views', 'Tried to sort results randomly on server %server which does not support random sorting.', $variables, WATCHDOG_WARNING);
+      }
+    }
+  }
+
   /**
    * Defines the options used by this query plugin.
    *
@@ -203,6 +247,10 @@ class SearchApiViewsQuery extends views_plugin_query {
    * Builds the necessary info to execute the query.
    */
   public function build(&$view) {
+    if (!empty($this->errors)) {
+      return;
+    }
+
     $this->view = $view;
 
     // Setup the nested filter structure for this query.
@@ -375,8 +423,8 @@ class SearchApiViewsQuery extends views_plugin_query {
     // loading any items.
     foreach ($results as $id => $result) {
       if (!empty($this->options['entity_access']) && ($entity_type = $this->index->getEntityType())) {
-        $entity = entity_load($entity_type, array($id));
-        if (!entity_access('view', $entity_type, $entity[$id])) {
+        $entity = $this->index->loadItems(array($id));
+        if (!$entity || !entity_access('view', $entity_type, reset($entity))) {
           continue;
         }
       }
@@ -400,7 +448,7 @@ class SearchApiViewsQuery extends views_plugin_query {
       }
 
       // Check whether we need to extract any properties from the result item.
-      $missing_fields = array_diff_key($this->fields, $row);
+      $missing_fields = array_diff_key($this->fields, $row['_entity_properties']);
       if ($missing_fields) {
         $missing[$id] = $missing_fields;
         if (is_object($row['entity'])) {
@@ -418,14 +466,14 @@ class SearchApiViewsQuery extends views_plugin_query {
     // Load items of those rows which haven't got all field values, yet.
     if (!empty($ids)) {
       $items += $this->index->loadItems($ids);
-      // $items now includes loaded items, and those already passed in the
-      // search results.
-      foreach ($items as $id => $item) {
-        // Extract item properties.
-        $wrapper = $this->index->entityWrapper($item, FALSE);
-        $rows[$id]->_entity_properties += $this->extractFields($wrapper, $missing[$id]);
-        $rows[$id]->entity = $item;
-      }
+    }
+    // $items now includes all loaded items from which fields still need to be
+    // extracted.
+    foreach ($items as $id => $item) {
+      // Extract item properties.
+      $wrapper = $this->index->entityWrapper($item, FALSE);
+      $rows[$id]->_entity_properties += $this->extractFields($wrapper, $missing[$id]);
+      $rows[$id]->entity = $item;
     }
 
     // Finally, add all rows to the Views result set.
@@ -490,31 +538,31 @@ class SearchApiViewsQuery extends views_plugin_query {
    * query backend.
    */
   public function get_result_wrappers($results, $relationship = NULL, $field = NULL) {
-    $entity_type = $this->index->getEntityType();
+    $type = $this->index->getEntityType() ? $this->index->getEntityType() : $this->index->item_type;
     $wrappers = array();
-    $load_entities = array();
+    $load_items = array();
     foreach ($results as $row_index => $row) {
-      if ($entity_type && isset($row->entity)) {
+      if (isset($row->entity)) {
         // If this entity isn't load, register it for pre-loading.
         if (!is_object($row->entity)) {
-          $load_entities[$row->entity] = $row_index;
+          $load_items[$row->entity] = $row_index;
+        }
+        else {
+          $wrappers[$row_index] = $this->index->entityWrapper($row->entity);
         }
-
-        $wrappers[$row_index] = $this->index->entityWrapper($row->entity);
       }
     }
 
     // If the results are entities, we pre-load them to make use of a multiple
     // load. (Otherwise, each result would be loaded individually.)
-    if (!empty($load_entities)) {
-      $entities = entity_load($entity_type, array_keys($load_entities));
-      foreach ($entities as $entity_id => $entity) {
-        $wrappers[$load_entities[$entity_id]] = $this->index->entityWrapper($entity);
+    if (!empty($load_items)) {
+      $items = $this->index->loadItems(array_keys($load_items));
+      foreach ($items as $id => $item) {
+        $wrappers[$load_items[$id]] = $this->index->entityWrapper($item);
       }
     }
 
     // Apply the relationship, if necessary.
-    $type = $entity_type ? $entity_type : $this->index->item_type;
     $selector_suffix = '';
     if ($field && ($pos = strrpos($field, ':'))) {
       $selector_suffix = substr($field, 0, $pos);

+ 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 2014-12-26
-version = "7.x-1.14"
+; Information added by Drupal.org packaging script on 2016-02-26
+version = "7.x-1.16+29-dev"
 core = "7.x"
 project = "search_api"
-datestamp = "1419580682"
+datestamp = "1456500713"
 

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

@@ -11,7 +11,6 @@
 function search_api_views_views_data() {
   try {
     $data = array();
-    $entity_types = entity_get_info();
     foreach (search_api_index_load_multiple(FALSE) as $index) {
       // Fill in base data.
       $key = 'search_api_index_' . $index->machine_name;
@@ -25,12 +24,8 @@ function search_api_views_views_data() {
         'help' => t('Use the %name search index for filtering and retrieving data.', array('%name' => $index->name)),
         'query class' => 'search_api_views_query',
       );
-      if (isset($entity_types[$index->getEntityType()])) {
-        $table['table'] += array(
-          'entity type' => $index->getEntityType(),
-          'skip entity load' => TRUE,
-        );
-      }
+      $table['table']['entity type'] = $index->getEntityType();
+      $table['table']['skip entity load'] = TRUE;
 
       try {
         $wrapper = $index->entityWrapper(NULL, FALSE);

+ 27 - 3
sites/all/modules/contrib/search/search_api/includes/callback_add_aggregation.inc

@@ -10,6 +10,16 @@
  */
 class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
 
+  /**
+   * The type of aggregation currently performed.
+   *
+   * Used to temporarily store the current aggregation type for use of
+   * SearchApiAlterAddAggregation::reduce() with array_reduce().
+   *
+   * @var string
+   */
+  protected $reductionType;
+
   public function configurationForm() {
     $form['#attached']['css'][] = drupal_get_path('module', 'search_api') . '/search_api.admin.css';
 
@@ -193,6 +203,12 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
         return isset($a) ? min($a, $b) : $b;
       case 'first':
         return isset($a) ? $a : $b;
+      case 'first_char':
+        $b = "$b";
+        if (isset($a) || $b === '') {
+          return $a;
+        }
+        return drupal_substr($b, 0, 1);
       case 'list':
         if (!isset($a)) {
           $a = array();
@@ -200,6 +216,7 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
         $a[] = $b;
         return $a;
     }
+    return NULL;
   }
 
   /**
@@ -252,10 +269,13 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
   /**
    * Helper method for getting all available aggregation types.
    *
-   * @param $info (optional)
-   *   One of "name", "type" or "description", to indicate what values should be
-   *   returned for the types. Defaults to "name".
+   * @param string $info
+   *   (optional) One of "name", "type" or "description", to indicate what
+   *   information should be returned for the types.
    *
+   * @return string[]
+   *   An associative array of aggregation type identifiers mapped to their
+   *   names, data types or descriptions, as requested.
    */
   protected function getTypes($info = 'name') {
     switch ($info) {
@@ -267,6 +287,7 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
           'max' => t('Maximum'),
           'min' => t('Minimum'),
           'first' => t('First'),
+          'first_char' => t('First letter'),
           'list' => t('List'),
         );
       case 'type':
@@ -277,6 +298,7 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
           'max' => 'integer',
           'min' => 'integer',
           'first' => 'string',
+          'first_char' => 'string',
           'list' => 'list<string>',
         );
       case 'description':
@@ -287,9 +309,11 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
           'max' => t('The Maximum aggregation computes the numerically largest contained field value.'),
           '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.'),
           'list' => t('The List aggregation collects all field values into a multi-valued field containing all values.'),
         );
     }
+    return array();
   }
 
   /**

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

@@ -74,7 +74,7 @@ class SearchApiAlterAddViewedEntity extends SearchApiAbstractAlterCallback {
       // we use try/catch. This will at least prevent some errors, even though
       // it's no protection against fatal errors and the like.
       try {
-        $render = entity_view($type, array(entity_id($type, $item) => $item), $mode);
+        $render = entity_view($type, array(entity_id($type, $item) => $item), $mode, $item->search_api_language);
         $text = render($render);
         if (!$text) {
           $item->search_api_viewed = NULL;

File diff suppressed because it is too large
+ 50 - 29
sites/all/modules/contrib/search/search_api/includes/callback_bundle_filter.inc


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

@@ -70,7 +70,7 @@ class SearchApiAlterLanguageControl extends SearchApiAbstractAlterCallback {
       foreach ($list as $lang) {
         $name = t($lang->name);
         $native = $lang->native;
-        $languages[$lang->language] = ($name == $native) ? $name : "$name ($native)";
+        $languages[$lang->language] = check_plain(($name == $native) ? $name : "$name ($native)");
         if (!$lang->enabled) {
           $languages[$lang->language] .= ' [' . t('disabled') . ']';
         }

+ 31 - 0
sites/all/modules/contrib/search/search_api/includes/callback_user_status.inc

@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Contains the SearchApiAlterUserStatus class.
+ */
+
+/**
+ * Filters out blocked user accounts.
+ */
+class SearchApiAlterUserStatus extends SearchApiAbstractAlterCallback {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function supportsIndex(SearchApiIndex $index) {
+    return $index->getEntityType() == 'user';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function alterItems(array &$items) {
+    foreach ($items as $id => $account) {
+      if (empty($account->status)) {
+        unset($items[$id]);
+      }
+    }
+  }
+
+}

+ 142 - 28
sites/all/modules/contrib/search/search_api/includes/datasource.inc

@@ -22,7 +22,7 @@
 interface SearchApiDataSourceControllerInterface {
 
   /**
-   * Constructs a new data source controller.
+   * Constructs an SearchApiDataSourceControllerInterface object.
    *
    * @param string $type
    *   The item type for which this controller is created.
@@ -47,7 +47,7 @@ interface SearchApiDataSourceControllerInterface {
    * Loads items of the type of this data source controller.
    *
    * @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.
@@ -158,6 +158,10 @@ interface SearchApiDataSourceControllerInterface {
    * @param SearchApiIndex[] $indexes
    *   The indexes for which items should be tracked.
    *
+   * @return SearchApiIndex[]|null
+   *   All indexes for which any items were added; or NULL if items were added
+   *   for all of them.
+   *
    * @throws SearchApiDataSourceException
    *   If any error state was encountered.
    */
@@ -179,6 +183,10 @@ interface SearchApiDataSourceControllerInterface {
    *   The concept of queued items will be removed in the Drupal 8 version of
    *   this module.
    *
+   * @return SearchApiIndex[]|null
+   *   All indexes for which any items were updated; or NULL if items were
+   *   updated for all of them.
+   *
    * @throws SearchApiDataSourceException
    *   If any error state was encountered.
    */
@@ -227,6 +235,10 @@ interface SearchApiDataSourceControllerInterface {
    * @param SearchApiIndex[] $indexes
    *   The indexes for which the deletions should be tracked.
    *
+   * @return SearchApiIndex[]|null
+   *   All indexes for which any items were deleted; or NULL if items were
+   *   deleted for all of them.
+   *
    * @throws SearchApiDataSourceException
    *   If any error state was encountered.
    */
@@ -281,6 +293,69 @@ interface SearchApiDataSourceControllerInterface {
    */
   public function getEntityType();
 
+  /**
+   * Form constructor for configuring the datasource for a given index.
+   *
+   * @param array $form
+   *   The form returned by configurationForm().
+   * @param array $form_state
+   *   The form state. $form_state['index'] will contain the edited index. If
+   *   this key is empty, then a new index is being created. In case of an edit,
+   *   $form_state['index']->options['datasource'] contains the previous
+   *   settings for the datasource.
+   *
+   * @return array|false
+   *   A form array for configuring this callback, or FALSE if no configuration
+   *   is possible.
+   */
+  public function configurationForm(array $form, array &$form_state);
+
+  /**
+   * Validation callback for the form returned by configurationForm().
+   *
+   * This method will only be called if that form was non-empty.
+   *
+   * @param array $form
+   *   The form returned by configurationForm().
+   * @param array $values
+   *   The part of the $form_state['values'] array corresponding to this form.
+   * @param array $form_state
+   *   The complete form state.
+   */
+  public function configurationFormValidate(array $form, array &$values, array &$form_state);
+
+  /**
+   * Submit callback for the form returned by configurationForm().
+   *
+   * This method will only be called if that form was non-empty.
+   *
+   * Any necessary changes to the submitted values should be made, afterwards
+   * they will automatically be stored as the index's "datasource" options. The
+   * method can also be used by the datasource controller to react to the
+   * possible change in its settings.
+   *
+   * @param array $form
+   *   The form returned by configurationForm().
+   * @param array $values
+   *   The part of the $form_state['values'] array corresponding to this form.
+   * @param array $form_state
+   *   The complete form state.
+   */
+  public function configurationFormSubmit(array $form, array &$values, array &$form_state);
+
+  /**
+   * Returns a summary of an index's current datasource configuration.
+   *
+   * @param SearchApiIndex $index
+   *   The index whose datasource configuration should be summarized.
+   *
+   * @return string|null
+   *   A translated string describing the index's current datasource
+   *   configuration. Or NULL, if there is no configuration (or no description
+   *   is available).
+   */
+  public function getConfigurationSummary(SearchApiIndex $index);
+
 }
 
 /**
@@ -517,10 +592,14 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
    * {@inheritdoc}
    */
   public function trackItemInsert(array $item_ids, array $indexes) {
-    if (!$this->table) {
+    if (!$this->table || $item_ids === array()) {
       return;
     }
 
+    foreach ($indexes as $index) {
+      $this->checkIndex($index);
+    }
+
     // Since large amounts of items can overstrain the database, only add items
     // in chunks.
     foreach (array_chunk($item_ids, 1000) as $chunk) {
@@ -528,7 +607,6 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
         ->fields(array($this->itemIdColumn, $this->indexIdColumn, $this->changedColumn));
       foreach ($chunk as $item_id) {
         foreach ($indexes as $index) {
-          $this->checkIndex($index);
           $insert->values(array(
             $this->itemIdColumn => $item_id,
             $this->indexIdColumn => $index->id,
@@ -544,24 +622,29 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
    * {@inheritdoc}
    */
   public function trackItemChange($item_ids, array $indexes, $dequeue = FALSE) {
-    if (!$this->table) {
-      return;
+    if (!$this->table || $item_ids === array()) {
+      return NULL;
     }
-    $index_ids = array();
+
+    $ret = array();
+
     foreach ($indexes as $index) {
       $this->checkIndex($index);
-      $index_ids[] = $index->id;
-    }
-    $update = db_update($this->table)
-      ->fields(array(
-        $this->changedColumn => REQUEST_TIME,
-      ))
-      ->condition($this->indexIdColumn, $index_ids, 'IN')
-      ->condition($this->changedColumn, 0, $dequeue ? '<=' : '=');
-    if ($item_ids !== FALSE) {
-      $update->condition($this->itemIdColumn, $item_ids, 'IN');
+      $update = db_update($this->table)
+        ->fields(array(
+          $this->changedColumn => REQUEST_TIME,
+        ))
+        ->condition($this->indexIdColumn, $index->id)
+        ->condition($this->changedColumn, 0, $dequeue ? '<=' : '=');
+      if ($item_ids !== FALSE) {
+        $update->condition($this->itemIdColumn, $item_ids, 'IN');
+      }
+      if ($update->execute()) {
+        $ret[] = $index;
+      }
     }
-    $update->execute();
+
+    return $ret;
   }
 
   /**
@@ -569,7 +652,7 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
    */
   public function trackItemQueued($item_ids, SearchApiIndex $index) {
     $this->checkIndex($index);
-    if (!$this->table) {
+    if (!$this->table || $item_ids === array()) {
       return;
     }
     $update = db_update($this->table)
@@ -587,7 +670,7 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
    * {@inheritdoc}
    */
   public function trackItemIndexed(array $item_ids, SearchApiIndex $index) {
-    if (!$this->table) {
+    if (!$this->table || $item_ids === array()) {
       return;
     }
     $this->checkIndex($index);
@@ -604,18 +687,23 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
    * {@inheritdoc}
    */
   public function trackItemDelete(array $item_ids, array $indexes) {
-    if (!$this->table) {
-      return;
+    if (!$this->table || $item_ids === array()) {
+      return NULL;
     }
-    $index_ids = array();
+
+    $ret = array();
+
     foreach ($indexes as $index) {
       $this->checkIndex($index);
-      $index_ids[] = $index->id;
+      $delete = db_delete($this->table)
+        ->condition($this->indexIdColumn, $index->id)
+        ->condition($this->itemIdColumn, $item_ids, 'IN');
+      if ($delete->execute()) {
+        $ret[] = $index;
+      }
     }
-    db_delete($this->table)
-      ->condition($this->itemIdColumn, $item_ids, 'IN')
-      ->condition($this->indexIdColumn, $index_ids, 'IN')
-      ->execute();
+
+    return $ret;
   }
 
   /**
@@ -659,6 +747,32 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
     return array('indexed' => $indexed, 'total' => $total);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function configurationForm(array $form, array &$form_state) {
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function configurationFormValidate(array $form, array &$values, array &$form_state) {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function configurationFormSubmit(array $form, array &$values, array &$form_state) {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfigurationSummary(SearchApiIndex $index) {
+    return NULL;
+  }
+
   /**
    * Checks whether the given index is valid for this datasource controller.
    *

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

@@ -10,28 +10,69 @@
  */
 class SearchApiEntityDataSourceController extends SearchApiAbstractDataSourceController {
 
+  /**
+   * Entity type info for this type.
+   *
+   * @var array
+   */
+  protected $entityInfo;
+
+  /**
+   * The ID key of this entity type, if any.
+   *
+   * @var string|null
+   */
+  protected $idKey;
+
+  /**
+   * The bundle key of this entity type, if any.
+   *
+   * @var string|null
+   */
+  protected $bundleKey;
+
+  /**
+   * Cached return values for getBundles(), keyed by index machine name.
+   *
+   * @var array
+   */
+  protected $bundles = array();
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct($type) {
+    parent::__construct($type);
+
+    $this->entityInfo = entity_get_info($this->entityType);
+    if (!empty($this->entityInfo['entity keys']['id'])) {
+      $this->idKey = $this->entityInfo['entity keys']['id'];
+    }
+    if (!empty($this->entityInfo['entity keys']['bundle'])) {
+      $this->bundleKey = $this->entityInfo['entity keys']['bundle'];
+    }
+  }
+
   /**
    * {@inheritdoc}
    */
   public function getIdFieldInfo() {
-    $info = entity_get_info($this->entityType);
     $properties = entity_get_property_info($this->entityType);
-    if (empty($info['entity keys']['id'])) {
-      throw new SearchApiDataSourceException(t("Entity type @type doesn't specify an ID key.", array('@type' => $info['label'])));
+    if (!$this->idKey) {
+      throw new SearchApiDataSourceException(t("Entity type @type doesn't specify an ID key.", array('@type' => $this->entityInfo['label'])));
     }
-    $field = $info['entity keys']['id'];
-    if (empty($properties['properties'][$field]['type'])) {
-      throw new SearchApiDataSourceException(t("Entity type @type doesn't specify a type for the @prop property.", array('@type' => $info['label'], '@prop' => $field)));
+    if (empty($properties['properties'][$this->idKey]['type'])) {
+      throw new SearchApiDataSourceException(t("Entity type @type doesn't specify a type for the @prop property.", array('@type' => $this->entityInfo['label'], '@prop' => $this->idKey)));
     }
-    $type = $properties['properties'][$field]['type'];
+    $type = $properties['properties'][$this->idKey]['type'];
     if (search_api_is_list_type($type)) {
-      throw new SearchApiDataSourceException(t("Entity type @type uses list field @prop as its ID.", array('@type' => $info['label'], '@prop' => $field)));
+      throw new SearchApiDataSourceException(t("Entity type @type uses list field @prop as its ID.", array('@type' => $this->entityInfo['label'], '@prop' => $this->idKey)));
     }
     if ($type == 'token') {
       $type = 'string';
     }
     return array(
-      'key' => $field,
+      'key' => $this->idKey,
       'type' => $type,
     );
   }
@@ -103,24 +144,53 @@ class SearchApiEntityDataSourceController extends SearchApiAbstractDataSourceCon
     // all items again without any key conflicts.
     $this->stopTracking($indexes);
 
-    $entity_info = entity_get_info($this->entityType);
-
-    if (!empty($entity_info['base table'])) {
+    if (!empty($this->entityInfo['base table']) && $this->idKey) {
       // Use a subselect, which will probably be much faster than entity_load().
 
       // Assumes that all entities use the "base table" property and the
       // "entity keys[id]" in the same way as the default controller.
-      $id_field = $entity_info['entity keys']['id'];
-      $table = $entity_info['base table'];
+      $table = $this->entityInfo['base table'];
 
-      // We could also use a single insert (with a JOIN in the nested query),
+      // We could also use a single insert (with a UNION in the nested query),
       // but this method will be mostly called with a single index, anyways.
       foreach ($indexes as $index) {
         // Select all entity ids.
         $query = db_select($table, 't');
-        $query->addField('t', $id_field, 'item_id');
+        $query->addField('t', $this->idKey, 'item_id');
         $query->addExpression(':index_id', 'index_id', array(':index_id' => $index->id));
         $query->addExpression('1', 'changed');
+        if ($bundles = $this->getIndexBundles($index)) {
+          $bundle_column = $this->bundleKey;
+          if (!db_field_exists($table, $bundle_column)) {
+            if ($this->entityType == 'taxonomy_term') {
+              $bundle_column = 'vid';
+              $bundles = db_query('SELECT vid FROM {taxonomy_vocabulary} WHERE machine_name IN (:bundles)', array(':bundles' => $bundles))->fetchCol();
+            }
+            elseif ($this->entityType == 'comment') {
+              // Comments are significantly more complicated, since they don't
+              // store their bundle explicitly in their database table. Instead,
+              // we need to get all the nodes from the enabled types and filter
+              // by those.
+              $bundle_column = 'nid';
+              $node_types = array();
+              foreach ($bundles as $bundle) {
+                if (substr($bundle, 0, 13) === 'comment_node_') {
+                  $node_types[] = substr($bundle, 13);
+                }
+              }
+              if ($node_types) {
+                $bundles = db_query('SELECT nid FROM {node} WHERE type IN (:bundles)', array(':bundles' => $node_types))->fetchCol();
+              }
+              else {
+                return;
+              }
+            }
+            else {
+              $this->startTrackingFallback(array($index->machine_name => $index));
+            }
+          }
+          $query->condition($bundle_column, $bundles);
+        }
 
         // INSERT ... SELECT ...
         db_insert($this->table)
@@ -129,16 +199,165 @@ class SearchApiEntityDataSourceController extends SearchApiAbstractDataSourceCon
       }
     }
     else {
-      // In the absence of a 'base table', use the slow entity_load().
-      parent::startTracking($indexes);
+      $this->startTrackingFallback($indexes);
+    }
+  }
+
+  /**
+   * Initializes tracking of the index status of items for the given indexes.
+   *
+   * Fallback for when the items cannot directly be loaded into
+   * {search_api_item} via "INSERT INTO … SELECT …".
+   *
+   * @param SearchApiIndex[] $indexes
+   *   The indexes for which item tracking should be initialized.
+   *
+   * @throws SearchApiDataSourceException
+   *   Thrown if any error state was encountered.
+   *
+   * @see SearchApiEntityDataSourceController::startTracking()
+   */
+  protected function startTrackingFallback(array $indexes) {
+    // In the absence of a 'base table', use the slower way of retrieving the
+    // items and inserting them "manually". For each index we get the item IDs
+    // (since selected bundles might differ) and insert all of them as new.
+    foreach ($indexes as $index) {
+      $query = new EntityFieldQuery();
+      $query->entityCondition('entity_type', $this->entityType);
+      if ($bundles = $this->getIndexBundles($index)) {
+        $query->entityCondition('bundle', $bundles);
+      }
+      $result = $query->execute();
+      $ids = !empty($result[$this->entityType]) ? array_keys($result[$this->entityType]) : array();
+      if ($ids) {
+        $this->trackItemInsert($ids, array($index));
+      }
     }
   }
 
   /**
    * {@inheritdoc}
    */
-  protected function getAllItemIds() {
-    return array_keys(entity_load($this->entityType));
+  public function trackItemInsert(array $item_ids, array $indexes) {
+    $ret = array();
+
+    foreach ($indexes as $index_id => $index) {
+      $ids = $item_ids;
+      if ($bundles = $this->getIndexBundles($index)) {
+        $ids = drupal_map_assoc($ids);
+        foreach (entity_load($this->entityType, $ids) as $id => $entity) {
+          if (empty($bundles[$entity->{$this->bundleKey}])) {
+            unset($ids[$id]);
+          }
+        }
+      }
+      if ($ids) {
+        parent::trackItemInsert($ids, array($index));
+        $ret[$index_id] = $index;
+      }
+    }
+
+    return $ret;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function configurationForm(array $form, array &$form_state) {
+    $options = $this->getAvailableBundles();
+    if (!$options) {
+      return FALSE;
+    }
+    $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.'),
+      '#options' => array_map('check_plain', $options),
+      '#attributes' => array('class' => array('search-api-checkboxes-list')),
+      '#disabled' => !empty($form_state['index']),
+    );
+    if (!empty($form_state['index']->options['datasource'])) {
+      $form['bundles']['#default_value'] = drupal_map_assoc($form_state['index']->options['datasource']['bundles']);
+    }
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function configurationFormSubmit(array $form, array &$values, array &$form_state) {
+    if (!empty($values['bundles'])) {
+      $values['bundles'] = array_keys(array_filter($values['bundles']));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfigurationSummary(SearchApiIndex $index) {
+    if ($bundles = $this->getIndexBundles($index)) {
+      $args['!bundles'] = implode(', ', array_intersect_key($this->getAvailableBundles(), $bundles));
+      return format_plural(count($bundles), 'Indexed bundle: !bundles.', 'Indexed bundles: !bundles.', $args);
+    }
+    return NULL;
+  }
+
+  /**
+   * Retrieves the available bundles for this entity type.
+   *
+   * @return array
+   *   An array (which might be empty) mapping this entity type's bundle keys to
+   *   their labels.
+   */
+  protected function getAvailableBundles() {
+    if (!$this->bundleKey || empty($this->entityInfo['bundles'])) {
+      return array();
+    }
+    $bundles = array();
+    foreach ($this->entityInfo['bundles'] as $bundle => $bundle_info) {
+      $bundles[$bundle] = isset($bundle_info['label']) ? $bundle_info['label'] : $bundle;
+    }
+    return $bundles;
+  }
+
+  /**
+   * Computes the bundles that should be indexed for an index.
+   *
+   * @param SearchApiIndex $index
+   *   The index for which to check.
+   *
+   * @return array
+   *   An array containing all bundles that should be included in this index, as
+   *   both the keys and values. An empty array means all current bundles should
+   *   be included.
+   *
+   * @throws SearchApiException
+   *   If the index doesn't belong to this datasource controller.
+   */
+  protected function getIndexBundles(SearchApiIndex $index) {
+    $this->checkIndex($index);
+
+    if (!isset($this->bundles[$index->machine_name])) {
+      $this->bundles[$index->machine_name] = array();
+      if (!empty($index->options['datasource']['bundles'])) {
+        // We retrieve the available bundles here to check whether all of them
+        // are included by the index's setting. In this case, we return an empty
+        // array, too, to save on complexity.
+        // On the other hand, we still want to return deleted bundles since we
+        // do not want to suddenly include all bundles when all selected bundles
+        // were deleted.
+        $available = $this->getAvailableBundles();
+        foreach ($index->options['datasource']['bundles'] as $bundle) {
+          $this->bundles[$index->machine_name][$bundle] = $bundle;
+          unset($available[$bundle]);
+        }
+        if (!$available) {
+          $this->bundles[$index->machine_name] = array();
+        }
+      }
+    }
+
+    return $this->bundles[$index->machine_name];
   }
 
 }

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

@@ -0,0 +1,357 @@
+<?php
+
+/**
+ * @file
+ * Contains SearchApiCombinedEntityDataSourceController.
+ */
+
+/**
+ * Provides a datasource for indexing multiple types of entities.
+ */
+class SearchApiCombinedEntityDataSourceController extends SearchApiAbstractDataSourceController {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $table = 'search_api_item_string_id';
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIdFieldInfo() {
+    return array(
+      'key' => 'item_id',
+      'type' => 'string',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadItems(array $ids) {
+    $ids_by_type = array();
+    foreach ($ids as $id) {
+      list($type, $entity_id) = explode('/', $id);
+      $ids_by_type[$type][$entity_id] = $id;
+    }
+
+    $items = array();
+    foreach ($ids_by_type as $type => $type_ids) {
+      foreach (entity_load($type, array_keys($type_ids)) as $entity_id => $entity) {
+        $id = $type_ids[$entity_id];
+        $item = (object) array($type => $entity);
+        $item->item_id = $id;
+        $item->item_type = $type;
+        $item->item_entity_id = $entity_id;
+        $item->item_bundle = NULL;
+        try {
+          list(, , $bundle) = entity_extract_ids($type, $entity);
+          $item->item_bundle = $bundle ? "$type:$bundle" : NULL;
+        }
+        catch (EntityMalformedException $e) {
+          // Will probably make problems at some other place, but for extracting
+          // the bundle it is really not critical enough to fail on – just
+          // ignore this exception.
+        }
+        $items[$id] = $item;
+        unset($type_ids[$entity_id]);
+      }
+      if ($type_ids) {
+        search_api_track_item_delete($type, array_keys($type_ids));
+      }
+    }
+
+    return $items;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getPropertyInfo() {
+    $info = array(
+      'item_id' => array(
+        'label' => t('ID'),
+        'description' => t('The combined ID of the item, containing both entity type and entity ID.'),
+        'type' => 'token',
+      ),
+      'item_type' => array(
+        'label' => t('Entity type'),
+        'description' => t('The entity type of the item.'),
+        'type' => 'token',
+        'options list' => 'search_api_entity_type_options_list',
+      ),
+      'item_entity_id' => array(
+        'label' => t('Entity ID'),
+        'description' => t('The entity ID of the item.'),
+        'type' => 'token',
+      ),
+      'item_bundle' => array(
+        'label' => t('Bundle'),
+        'description' => t('The bundle of the item, if applicable.'),
+        'type' => 'token',
+        'options list' => 'search_api_combined_bundle_options_list',
+      ),
+      'item_label' => array(
+        'label' => t('Label'),
+        'description' => t('The label of the item.'),
+        'type' => 'text',
+        // Since this needs a bit more computation than the others, we don't
+        // include it always when loading the item but use a getter callback.
+        'getter callback' => 'search_api_get_multi_type_item_label',
+      ),
+    );
+
+    foreach ($this->getSelectedEntityTypeOptions() as $type => $label) {
+      $info[$type] = array(
+        'label' => $label,
+        'description' => t('The indexed entity, if it is of type %type.', array('%type' => $label)),
+        'type' => $type,
+      );
+    }
+
+    return array('property info' => $info);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getItemId($item) {
+    return isset($item->item_id) ? $item->item_id : NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getItemLabel($item) {
+    return search_api_get_multi_type_item_label($item);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getItemUrl($item) {
+    if ($item->item_type == 'file') {
+      return array(
+        'path' => file_create_url($item->file->uri),
+        'options' => array(
+          'entity_type' => 'file',
+          'entity' => $item,
+        ),
+      );
+    }
+    $url = entity_uri($item->item_type, $item->{$item->item_type});
+    return $url ? $url : NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function startTracking(array $indexes) {
+    if (!$this->table) {
+      return;
+    }
+    // We first clear the tracking table for all indexes, so we can just insert
+    // all items again without any key conflicts.
+    $this->stopTracking($indexes);
+
+    foreach ($indexes as $index) {
+      $types = $this->getEntityTypes($index);
+
+      // Wherever possible, use a sub-select instead of the much slower
+      // entity_load().
+      foreach ($types as $type) {
+        $entity_info = entity_get_info($type);
+
+        if (!empty($entity_info['base table'])) {
+          // Assumes that all entities use the "base table" property and the
+          // "entity keys[id]" in the same way as the default controller.
+          $id_field = $entity_info['entity keys']['id'];
+          $table = $entity_info['base table'];
+
+          // Select all entity ids.
+          $query = db_select($table, 't');
+          $query->addExpression("CONCAT(:prefix, t.$id_field)", 'item_id', array(':prefix' => $type . '/'));
+          $query->addExpression(':index_id', 'index_id', array(':index_id' => $index->id));
+          $query->addExpression('1', 'changed');
+
+          // INSERT ... SELECT ...
+          db_insert($this->table)
+            ->from($query)
+            ->execute();
+
+          unset($types[$type]);
+        }
+      }
+
+      // In the absence of a "base table", use the slow entity_load().
+      if ($types) {
+        foreach ($types as $type) {
+          $query = new EntityFieldQuery();
+          $query->entityCondition('entity_type', $type);
+          $result = $query->execute();
+          $ids = !empty($result[$type]) ? array_keys($result[$type]) : array();
+          if ($ids) {
+            foreach ($ids as $i => $id) {
+              $ids[$i] = $type . '/' . $id;
+            }
+            $this->trackItemInsert($ids, array($index), TRUE);
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Starts tracking the index status for the given items on the given indexes.
+   *
+   * @param array $item_ids
+   *   The IDs of new items to track.
+   * @param SearchApiIndex[] $indexes
+   *   The indexes for which items should be tracked.
+   * @param bool $skip_type_check
+   *   (optional) If TRUE, don't check whether the type matches the index's
+   *   datasource configuration. Internal use only.
+   *
+   * @return SearchApiIndex[]|null
+   *   All indexes for which any items were added; or NULL if items were added
+   *   for all of them.
+   *
+   * @throws SearchApiDataSourceException
+   *   If any error state was encountered.
+   */
+  public function trackItemInsert(array $item_ids, array $indexes, $skip_type_check = FALSE) {
+    $ret = array();
+
+    foreach ($indexes as $index_id => $index) {
+      $ids = drupal_map_assoc($item_ids);
+
+      if (!$skip_type_check) {
+        $types = $this->getEntityTypes($index);
+        foreach ($ids as $id) {
+          list($type) = explode('/', $id);
+          if (!isset($types[$type])) {
+            unset($ids[$id]);
+          }
+        }
+      }
+
+      if ($ids) {
+        parent::trackItemInsert($ids, array($index));
+        $ret[$index_id] = $index;
+      }
+    }
+
+    return $ret;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function configurationForm(array $form, array &$form_state) {
+    $form['types'] = array(
+      '#type' => 'checkboxes',
+      '#title' => t('Entity types'),
+      '#description' => t('Select the entity types which should be included in this index.'),
+      '#options' => array_map('check_plain', search_api_entity_type_options_list()),
+      '#attributes' => array('class' => array('search-api-checkboxes-list')),
+      '#disabled' => !empty($form_state['index']),
+      '#required' => TRUE,
+    );
+    if (!empty($form_state['index']->options['datasource']['types'])) {
+      $form['types']['#default_value'] = $this->getEntityTypes($form_state['index']);
+    }
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function configurationFormSubmit(array $form, array &$values, array &$form_state) {
+    if (!empty($values['types'])) {
+      $values['types'] = array_keys(array_filter($values['types']));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfigurationSummary(SearchApiIndex $index) {
+    if ($type_labels = $this->getSelectedEntityTypeOptions($index)) {
+      $args['!types'] = implode(', ', $type_labels);
+      return format_plural(count($type_labels), 'Indexed entity types: !types.', 'Indexed entity types: !types.', $args);
+    }
+    return NULL;
+  }
+
+  /**
+   * Retrieves the index for which the current method was called.
+   *
+   * Very ugly method which uses the stack trace to find the right object.
+   *
+   * @return SearchApiIndex
+   *   The active index.
+   *
+   * @throws SearchApiException
+   *   Thrown if the active index could not be determined.
+   */
+  protected function getCallingIndex() {
+    foreach (debug_backtrace() as $trace) {
+      if (isset($trace['object']) && $trace['object'] instanceof SearchApiIndex) {
+        return $trace['object'];
+      }
+    }
+    // If there's only a single index on the site, it's also easy.
+    $indexes = search_api_index_load_multiple(FALSE);
+    if (count($indexes) === 1) {
+      return reset($indexes);
+    }
+    throw new SearchApiException('Could not determine the active index of the datasource.');
+  }
+
+  /**
+   * Returns the entity types for which this datasource is configured.
+   *
+   * Depends on the index from which this method is (indirectly) called.
+   *
+   * @param SearchApiIndex $index
+   *   (optional) The index for which to get the enabled entity types. If not
+   *   given, will be determined automatically.
+   *
+   * @return string[]
+   *   The machine names of the datasource's enabled entity types, as both keys
+   *   and values.
+   *
+   * @throws SearchApiException
+   *   Thrown if the active index could not be determined.
+   */
+  protected function getEntityTypes(SearchApiIndex $index = NULL) {
+    if (!$index) {
+      $index = $this->getCallingIndex();
+    }
+    if (isset($index->options['datasource']['types'])) {
+      return drupal_map_assoc($index->options['datasource']['types']);
+    }
+    return array();
+  }
+
+  /**
+   * Returns the selected entity type options for this datasource.
+   *
+   * Depends on the index from which this method is (indirectly) called.
+   *
+   * @param SearchApiIndex $index
+   *   (optional) The index for which to get the enabled entity types. If not
+   *   given, will be determined automatically.
+   *
+   * @return string[]
+   *   An associative array, mapping the machine names of the enabled entity
+   *   types to their labels.
+   *
+   * @throws SearchApiException
+   *   Thrown if the active index could not be determined.
+   */
+  protected function getSelectedEntityTypeOptions(SearchApiIndex $index = NULL) {
+    return array_intersect_key(search_api_entity_type_options_list(), $this->getEntityTypes($index));
+  }
+
+}

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

@@ -19,7 +19,7 @@ class SearchApiException extends Exception {
    */
   public function __construct($message = NULL) {
     if (!$message) {
-      $message = t('An error occcurred in the Search API framework.');
+      $message = t('An error occurred in the Search API framework.');
     }
     parent::__construct($message);
   }

+ 10 - 3
sites/all/modules/contrib/search/search_api/includes/index_entity.inc

@@ -115,7 +115,8 @@ class SearchApiIndex extends Entity {
   public $item_type;
 
   /**
-   * An array of options for configuring this index. The layout is as follows:
+   * An array of options for configuring this index. The layout is as follows
+   * (with all keys being optional):
    * - cron_limit: The maximum number of items to be indexed per cron batch.
    * - index_directly: Boolean setting whether entities are indexed immediately
    *   after they are created or updated.
@@ -150,6 +151,8 @@ class SearchApiIndex extends Entity {
    *   - weight: Used for sorting the processors.
    *   - settings: Processor-specific settings, configured via the processor's
    *     configuration form.
+   * - datasource: Datasource-specific settings, configured via the datasource's
+   *   configuration form.
    *
    * @var array
    */
@@ -436,7 +439,6 @@ class SearchApiIndex extends Entity {
     return $this->server()->query($this, $options);
   }
 
-
   /**
    * Indexes items on this index.
    *
@@ -932,7 +934,12 @@ class SearchApiIndex extends Entity {
     $i = $only_indexed ? 1 : 0;
     if (!isset($this->fulltext_fields[$i])) {
       $this->fulltext_fields[$i] = array();
-      $fields = $only_indexed ? $this->options['fields'] : $this->getFields(FALSE);
+      if ($only_indexed) {
+        $fields = isset($this->options['fields']) ? $this->options['fields'] : array();
+      }
+      else {
+        $fields = $this->getFields(FALSE);
+      }
       foreach ($fields as $key => $field) {
         if (search_api_is_text_type($field['type'])) {
           $this->fulltext_fields[$i][] = $key;

+ 12 - 5
sites/all/modules/contrib/search/search_api/includes/processor.inc

@@ -172,7 +172,7 @@ abstract class SearchApiAbstractProcessor implements SearchApiProcessorInterface
       $default_fields = drupal_map_assoc(array_keys($this->options['fields']));
     }
     foreach ($fields as $name => $field) {
-      $field_options[$name] = $field['name'];
+      $field_options[$name] = check_plain($field['name']);
       if (!empty($default_fields[$name]) || (!isset($this->options['fields']) && $this->testField($name, $field))) {
         $default_fields[$name] = $name;
       }
@@ -390,13 +390,15 @@ abstract class SearchApiAbstractProcessor implements SearchApiProcessorInterface
   }
 
   /**
+   * Determines whether to process data from the given field.
+   *
    * @param $name
    *   The field's machine name.
    * @param array $field
    *   The field's information.
    *
-   * @return
-   *   TRUE, iff the field should be processed.
+   * @return bool
+   *   TRUE, if the field should be processed, FALSE otherwise.
    */
   protected function testField($name, array $field) {
     if (empty($this->options['fields'])) {
@@ -406,8 +408,13 @@ abstract class SearchApiAbstractProcessor implements SearchApiProcessorInterface
   }
 
   /**
-   * @return
-   *   TRUE, iff the type should be processed.
+   * Determines whether fields of the given type should normally be processed.
+   *
+   * Defaults to processing text types, but can easily be overridden by
+   * subclasses.
+   *
+   * @return bool
+   *   TRUE, if the type should be processed, FALSE otherwise.
    */
   protected function testType($type) {
     return search_api_is_text_type($type, array('text', 'tokens'));

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

@@ -86,12 +86,12 @@ class SearchApiHighlight extends SearchApiAbstractProcessor {
         ),
       ),
     );
-    // Exclude certain fulltextfields
+    // Exclude certain fulltext fields.
     $fields = $this->index->getFields();
     $fulltext_fields = array();
     foreach ($this->index->getFulltextFields() as $field) {
       if (isset($fields[$field])) {
-        $fulltext_fields[$field] = $fields[$field]['name'] . ' (' . $field . ')';
+        $fulltext_fields[$field] = check_plain($fields[$field]['name'] . ' (' . $field . ')');
       }
     }
     $form['exclude_fields'] = array(

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

@@ -120,7 +120,9 @@ class SearchApiHtmlFilter extends SearchApiAbstractProcessor {
         );
       }
       $text = substr($text, $pos + 1);
-      preg_match('#^(/?)([-:_a-zA-Z]+)#', $text, $m);
+      if (!preg_match('#^(/?)([-:_a-zA-Z]+)#', $text, $m)) {
+        continue;
+      }
       $text = substr($text, strpos($text, '>') + 1);
       if ($m[1]) {
         // Closing tag.

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

@@ -86,7 +86,9 @@ class SearchApiStopWords extends SearchApiAbstractProcessor {
   }
 
   /**
-   * @return
+   * Retrieves the processor's configured stopwords.
+   *
+   * @return array
    *   An array whose keys are the stopwords set in either the file or the text
    *   field.
    */

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

@@ -153,7 +153,9 @@ interface SearchApiQueryInterface {
    *
    * @param string $field
    *   The field to sort by. The special fields 'search_api_relevance' (sort by
-   *   relevance) and 'search_api_id' (sort by item id) may be used.
+   *   relevance) and 'search_api_id' (sort by item id) may be used. Also, if
+   *   the search server supports the "search_api_random_sort" feature, the
+   *   "search_api_random" special field can be used to sort randomly.
    * @param string $order
    *   The order to sort items in - either 'ASC' or 'DESC'.
    *
@@ -217,7 +219,10 @@ interface SearchApiQueryInterface {
    *     - execution: The actual query to the search server, in whatever form.
    *     - postprocessing: Preparing the results for returning.
    *   Additional metadata may be returned in other keys. Only 'result count'
-   *   and 'result' always have to be set, all other entries are optional.
+   *   and 'results' always have to be set, all other entries are optional.
+   *
+   * @throws SearchApiException
+   *   If an error prevented the search from completing.
    */
   public function execute();
 
@@ -466,8 +471,8 @@ class SearchApiQuery implements SearchApiQueryInterface {
     );
     $modes['terms'] = array(
       'name' => t('Multiple terms'),
-      'description' => t('The query is interpreted as multiple keywords seperated by spaces. ' .
-          'Keywords containing spaces may be "quoted". Quoted keywords must still be seperated by spaces.'),
+      'description' => t('The query is interpreted as multiple keywords separated by spaces. ' .
+          'Keywords containing spaces may be "quoted". Quoted keywords must still be separated by spaces.'),
     );
     // @todo Add fourth mode for complicated expressions, e.g.: »"vanilla ice" OR (love NOT hate)«
     return $modes;
@@ -586,6 +591,10 @@ class SearchApiQuery implements SearchApiQueryInterface {
       'search_api_relevance' => array('type' => 'decimal'),
       'search_api_id' => array('type' => 'integer'),
     );
+    if ($this->getIndex()->server()->supportsFeature('search_api_random_sort')) {
+      $fields['search_api_random'] = array('type' => 'integer');
+    }
+
     if (empty($fields[$field])) {
       throw new SearchApiException(t('Trying to sort on unknown field @field.', array('@field' => $field)));
     }
@@ -720,6 +729,9 @@ class SearchApiQuery implements SearchApiQueryInterface {
   public function postExecute(array &$results) {
     // Postprocess results.
     $this->index->postprocessSearchResults($results, $this);
+
+    // Let modules alter the results.
+    drupal_alter('search_api_results', $results, $this);
   }
 
   /**

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

@@ -241,7 +241,7 @@ class SearchApiServer extends Entity {
   /**
    * Adds a new index to this server.
    *
-   * If an exception in the service class implementation of this method occcurs,
+   * If an exception in the service class implementation of this method occurs,
    * it will be caught and the operation saved as an pending server task.
    *
    * @see SearchApiServiceInterface::addIndex()
@@ -268,7 +268,7 @@ class SearchApiServer extends Entity {
    * If the service class implementation of the method returns TRUE, this will
    * automatically take care of marking the items on the index for re-indexing.
    *
-   * If an exception in the service class implementation of this method occcurs,
+   * If an exception in the service class implementation of this method occurs,
    * it will be caught and the operation saved as an pending server task.
    *
    * @see SearchApiServiceInterface::fieldsUpdated()
@@ -296,7 +296,7 @@ class SearchApiServer extends Entity {
   /**
    * Removes an index from this server.
    *
-   * If an exception in the service class implementation of this method occcurs,
+   * If an exception in the service class implementation of this method occurs,
    * it will be caught and the operation saved as an pending server task.
    *
    * @see SearchApiServiceInterface::removeIndex()
@@ -334,7 +334,7 @@ class SearchApiServer extends Entity {
   /**
    * Deletes indexed items from this server.
    *
-   * If an exception in the service class implementation of this method occcurs,
+   * If an exception in the service class implementation of this method occurs,
    * it will be caught and the operation saved as an pending server task.
    *
    * @see SearchApiServiceInterface::deleteItems()

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

@@ -420,7 +420,15 @@ abstract class SearchApiAbstractService implements SearchApiServiceInterface {
   public function preDelete() {
     $indexes = search_api_index_load_multiple(FALSE, array('server' => $this->server->machine_name));
     foreach ($indexes as $index) {
-      $this->removeIndex($index);
+      // removeIndex() might throw exceptions, but this method mustn't.
+      try {
+        $this->removeIndex($index);
+      }
+      catch (SearchApiException $e) {
+        $variables['%index'] = $index->name;
+        $variables['%server'] = $this->server->name;
+        watchdog_exception('search_api', $e, '%type while trying to remove index %index from deleted server %server: !message in %function (line %line of %file).', $variables);
+      }
     }
   }
 

+ 207 - 86
sites/all/modules/contrib/search/search_api/search_api.admin.inc

@@ -303,8 +303,9 @@ function search_api_admin_add_server_validate(array $form, array &$form_state) {
     return;
   }
   $form_state['values']['options']['service'] = $service;
-  $values = isset($form_state['values']['options']['form']) ? $form_state['values']['options']['form'] : array();
-  $service->configurationFormValidate($form['options']['form'], $values, $form_state);
+  if (!empty($form_state['values']['options']['form'])) {
+    $service->configurationFormValidate($form['options']['form'], $form_state['values']['options']['form'], $form_state);
+  }
 }
 
 /**
@@ -630,7 +631,9 @@ function search_api_admin_server_edit(array $form, array &$form_state, SearchApi
  * @see search_api_admin_server_edit_submit()
  */
 function search_api_admin_server_edit_validate(array $form, array &$form_state) {
-  $form_state['server']->configurationFormValidate($form['options']['form'], $form_state['values']['options']['form'], $form_state);
+  if (!empty($form['options']['form']) && !empty($form_state['values']['options']['form'])) {
+    $form_state['server']->configurationFormValidate($form['options']['form'], $form_state['values']['options']['form'], $form_state);
+  }
 }
 
 /**
@@ -682,83 +685,126 @@ function search_api_admin_form_delete_submit($form, &$form_state) {
  *
  * @ingroup forms
  *
+ * @see search_api_admin_add_index_ajax_callback()
  * @see search_api_admin_add_index_validate()
  * @see search_api_admin_add_index_submit()
  */
 function search_api_admin_add_index(array $form, array &$form_state) {
   drupal_set_title(t('Add index'));
 
+  $old_type = empty($form_state['values']['item_type']) ? '' : $form_state['values']['item_type'];
+
   $form['#attached']['css'][] = drupal_get_path('module', 'search_api') . '/search_api.admin.css';
   $form['#tree'] = TRUE;
-  $form['name'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Index name'),
-    '#maxlength' => 50,
-    '#required' => TRUE,
-  );
 
-  $form['machine_name'] = array(
-    '#type' => 'machine_name',
-    '#maxlength' => 50,
-    '#machine_name' => array(
-      'exists' => 'search_api_index_load',
-    ),
-  );
+  if (empty($form_state['step_one'])) {
+    $form['name'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Index name'),
+      '#maxlength' => 50,
+      '#required' => TRUE,
+    );
 
-  $form['item_type'] = array(
-    '#type' => 'select',
-    '#title' => t('Item type'),
-    '#description' => t('Select the type of items that will be indexed in this index. ' .
-        'This setting cannot be changed afterwards.'),
-    '#options' => array(),
-    '#required' => TRUE,
-  );
-  foreach (search_api_get_item_type_info() as $type => $info) {
-    $form['item_type']['#options'][$type] = $info['name'];
+    $form['machine_name'] = array(
+      '#type' => 'machine_name',
+      '#maxlength' => 50,
+      '#machine_name' => array(
+        'exists' => 'search_api_index_load',
+      ),
+    );
+
+    $form['item_type'] = array(
+      '#type' => 'select',
+      '#title' => t('Item type'),
+      '#description' => t('Select the type of items that will be indexed in this index. ' .
+          'This setting cannot be changed afterwards.'),
+      '#options' => array(),
+      '#required' => TRUE,
+      '#ajax' => array(
+        'callback' => 'search_api_admin_add_index_ajax_callback',
+        'wrapper' => 'search-api-datasource-options',
+      ),
+    );
+    $form['datasource'] = array();
+    foreach (search_api_get_item_type_info() as $type => $info) {
+      $form['item_type']['#options'][$type] = $info['name'];
+    }
+    $form['enabled'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Enabled'),
+      '#description' => t('This will only take effect if you also select a server for the index.'),
+      '#default_value' => TRUE,
+    );
+    $form['description'] = array(
+      '#type' => 'textarea',
+      '#title' => t('Index description'),
+    );
+    $form['server'] = array(
+      '#type' => 'select',
+      '#title' => t('Server'),
+      '#description' => t('Select the server this index should reside on.'),
+      '#default_value' => '',
+      '#options' => array('' => t('< No server >'))
+    );
+    $servers = search_api_server_load_multiple(FALSE, array('enabled' => 1));
+    // List enabled servers first.
+    foreach ($servers as $server) {
+      if ($server->enabled) {
+        $form['server']['#options'][$server->machine_name] = $server->name;
+      }
+    }
+    foreach ($servers as $server) {
+      if (!$server->enabled) {
+        $form['server']['#options'][$server->machine_name] = t('@server_name (disabled)', array('@server_name' => $server->name));
+      }
+    }
+    $form['read_only'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Read only'),
+      '#description' => t('Do not write to this index or track the status of items in this index.'),
+      '#default_value' => FALSE,
+    );
+    $form['options']['index_directly'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Index items immediately'),
+      '#description' => t('Immediately index new or updated items instead of waiting for the next cron run. ' .
+          'This might have serious performance drawbacks and is generally not advised for larger sites.'),
+      '#default_value' => FALSE,
+    );
+    $form['options']['cron_limit'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Cron batch size'),
+      '#description' => t('Set how many items will be indexed at once when indexing items during a cron run. ' .
+          '"0" means that no items will be indexed by cron for this index, "-1" means that cron should index all items at once.'),
+      '#default_value' => SEARCH_API_DEFAULT_CRON_LIMIT,
+      '#size' => 4,
+      '#attributes' => array('class' => array('search-api-cron-limit')),
+      '#element_validate' => array('element_validate_integer'),
+    );
   }
-  $form['enabled'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Enabled'),
-    '#description' => t('This will only take effect if the selected server is also enabled.'),
-    '#default_value' => TRUE,
-  );
-  $form['description'] = array(
-    '#type' => 'textarea',
-    '#title' => t('Index description'),
-  );
-  $form['server'] = array(
-    '#type' => 'select',
-    '#title' => t('Server'),
-    '#description' => t('Select the server this index should reside on.'),
-    '#default_value' => '',
-    '#options' => array('' => t('< No server >'))
-  );
-  $servers = search_api_server_load_multiple(FALSE, array('enabled' => 1));
-  // List enabled servers first.
-  foreach ($servers as $server) {
-    $form['server']['#options'][$server->machine_name] = $server->name;
+  elseif (!$old_type) {
+    $old_type = $form_state['step_one']['item_type'];
   }
-  $form['read_only'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Read only'),
-    '#description' => t('Do not write to this index or track the status of items in this index.'),
-    '#default_value' => FALSE,
-  );
-  $form['options']['index_directly'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Index items immediately'),
-    '#description' => t('Immediately index new or updated items instead of waiting for the next cron run. ' .
-        'This might have serious performance drawbacks and is generally not advised for larger sites.'),
-    '#default_value' => FALSE,
+
+  if ($old_type) {
+    $datasource = search_api_get_datasource_controller($old_type);
+    $datasource_form = array();
+    $datasource_form = $datasource->configurationForm($datasource_form, $form_state);
+    if ($datasource_form) {
+      $form['datasource'] = $datasource_form;
+      $form['datasource']['#parents'] = array('options', 'datasource');
+    }
+  }
+  $form['datasource']['#prefix'] = '<div id="search-api-datasource-options">';
+  $form['datasource']['#suffix'] = '</div>';
+
+  $form['old_type'] = array(
+    '#type' => 'value',
+    '#value' => $old_type,
   );
-  $form['options']['cron_limit'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Cron batch size'),
-    '#description' => t('Set how many items will be indexed at once when indexing items during a cron run. ' .
-        '"0" means that no items will be indexed by cron for this index, "-1" means that cron should index all items at once.'),
-    '#default_value' => SEARCH_API_DEFAULT_CRON_LIMIT,
-    '#size' => 4,
-    '#attributes' => array('class' => array('search-api-cron-limit')),
+  $form['datasource_config'] = array(
+    '#type' => 'value',
+    '#value' => !empty($datasource_form),
   );
 
   $form['submit'] = array(
@@ -769,22 +815,33 @@ function search_api_admin_add_index(array $form, array &$form_state) {
   return $form;
 }
 
+/**
+ * AJAX submit callback for search_api_admin_add_index().
+ *
+ * Used for displaying the matching datasource configuration form for the
+ * selected item type.
+ */
+function search_api_admin_add_index_ajax_callback(array $form, array &$form_state) {
+  return $form['datasource'];
+}
+
 /**
  * Form validation handler for search_api_admin_add_index().
  *
  * @see search_api_admin_add_index_submit()
  */
 function search_api_admin_add_index_validate(array $form, array &$form_state) {
-  $name = $form_state['values']['machine_name'];
+  $values = $form_state['values'];
+  $name = $values['machine_name'];
   if (is_numeric($name)) {
     form_set_error('machine_name', t('The machine name must not be a pure number.'));
   }
 
-  $cron_limit = $form_state['values']['options']['cron_limit'];
-  if ($cron_limit != '' . ((int) $cron_limit)) {
-    // We don't enforce stricter rules and treat all negative values as -1.
-    form_set_error('options[cron_limit]', t('The cron batch size must be an integer.'));
+  if (!$values['datasource_config'] || empty($values['item_type']) || $values['item_type'] != $values['old_type']) {
+    return;
   }
+  $datasource = search_api_get_datasource_controller($values['item_type']);
+  $datasource->configurationFormValidate($form['datasource'], $form_state['values']['options']['datasource'], $form_state);
 }
 
 /**
@@ -794,10 +851,34 @@ function search_api_admin_add_index_validate(array $form, array &$form_state) {
  */
 function search_api_admin_add_index_submit(array $form, array &$form_state) {
   form_state_values_clean($form_state);
-
   $values = $form_state['values'];
 
-  // Validation of whether the server of an index is enabled is done in the
+  if (!empty($form_state['step_one'])) {
+    $values += $form_state['step_one'];
+    unset($form_state['step_one']);
+  }
+
+  // The type was changed (or the form submitted without JS for the first time).
+  // If the new type has a configuration form, we have to display it now.
+  $datasource = search_api_get_datasource_controller($values['item_type']);
+  if ($values['item_type'] != $values['old_type']) {
+    $datasource_form = array();
+    if ($datasource->configurationForm($datasource_form, $form_state)) {
+      unset($values['options']['datasource']);
+      $form_state['step_one'] = $values;
+      $form_state['rebuild'] = TRUE;
+      drupal_set_message(t('Please specify further configuration options.'));
+      return;
+    }
+  }
+
+  // If the current type has a configuration form, call the datasource
+  // controller's config submit callback.
+  if ($values['datasource_config']) {
+    $datasource->configurationFormSubmit($form['datasource'], $values['options']['datasource'], $form_state);
+  }
+
+  // Validation of whether a server is set for the index is done in the
   // SearchApiIndex::save() method.
   search_api_index_insert($values);
 
@@ -857,6 +938,7 @@ function search_api_admin_index_view(SearchApiIndex $index, $action = NULL) {
     '#machine_name' => $index->machine_name,
     '#description' => $index->description,
     '#item_type' => $index->item_type,
+    '#datasource_config' => $index->datasource()->getConfigurationSummary($index),
     '#enabled' => $index->enabled,
     '#server' => $server,
     '#options' => $index->options,
@@ -889,6 +971,7 @@ function search_api_admin_index_view(SearchApiIndex $index, $action = NULL) {
  *   - machine_name: The index' machine name.
  *   - description: The index' description.
  *   - item_type: The type of items stored in this index.
+ *   - datasource_config: A summary of the datasource's configuration.
  *   - enabled: Boolean indicating whether the index is enabled.
  *   - server: The server this index currently rests on, if any.
  *   - options: The index' options, like cron limit.
@@ -912,6 +995,7 @@ function theme_search_api_index(array $variables) {
   $description = $variables['description'];
   $enabled = $variables['enabled'];
   $item_type = $variables['item_type'];
+  $datasource_config = $variables['datasource_config'];
   $server = $variables['server'];
   $options = $variables['options'];
   $status = $variables['status'];
@@ -962,6 +1046,12 @@ function theme_search_api_index(array $variables) {
   $info = check_plain($item_type);
   $rows[] = _search_api_deep_copy($row);
 
+  if ($datasource_config) {
+    $label = t('Item type configuration');
+    $info = check_plain($datasource_config);
+    $rows[] = _search_api_deep_copy($row);
+  }
+
   if ($server) {
     $label = t('Server');
     $info = l($server->name, 'admin/config/search/search_api/server/' . $server->machine_name);
@@ -1193,6 +1283,7 @@ function search_api_admin_index_status_form_submit(array $form, array &$form_sta
  *
  * @ingroup forms
  *
+ * @see search_api_admin_index_edit_validate()
  * @see search_api_admin_index_edit_submit()
  */
 function search_api_admin_index_edit(array $form, array &$form_state, SearchApiIndex $index) {
@@ -1208,7 +1299,7 @@ function search_api_admin_index_edit(array $form, array &$form_state, SearchApiI
     '#required' => TRUE,
   );
   try {
-    $enabled_fixed = !$index->enabled && !$index->server();
+    $enabled_fixed = !$index->server();
   }
   catch (Exception $e) {
     watchdog_exception('search_api', $e);
@@ -1220,7 +1311,7 @@ function search_api_admin_index_edit(array $form, array &$form_state, SearchApiI
     '#type' => 'checkbox',
     '#title' => t('Enabled'),
     '#default_value' => $index->enabled,
-    // Can't enable an index lying on a disabled server, or no server at all.
+    // Can't enable an index that's not lying on any server.
     '#disabled' => $enabled_fixed,
   );
   $form['description'] = array(
@@ -1240,6 +1331,15 @@ function search_api_admin_index_edit(array $form, array &$form_state, SearchApiI
   foreach ($servers as $server) {
     $form['server']['#options'][$server->machine_name] = $server->name;
   }
+
+  $datasource_form = !empty($form['options']['datasource']) ? $form['options']['datasource'] : array();
+  $datasource_form = $index->datasource()->configurationForm($datasource_form, $form_state);
+  if ($datasource_form) {
+    $form['options']['datasource'] = $datasource_form;
+    $form['options']['datasource']['#type'] = 'fieldset';
+    $form['options']['datasource']['#title'] = t('Datasource options');
+  }
+
   $form['read_only'] = array(
     '#type' => 'checkbox',
     '#title' => t('Read only'),
@@ -1285,14 +1385,33 @@ function search_api_admin_index_edit(array $form, array &$form_state, SearchApiI
   return $form;
 }
 
+/**
+ * Form validation handler for search_api_admin_index_edit().
+ *
+ * @see search_api_admin_index_edit_submit()
+ */
+function search_api_admin_index_edit_validate(array $form, array &$form_state) {
+  if (!empty($form['options']['datasource'])) {
+    $form_state['values']['options'] += array('datasource' => array());
+    $form_state['index']->datasource()->configurationFormValidate($form['options']['datasource'], $form_state['values']['options']['datasource'], $form_state);
+  }
+}
+
 /**
  * Form submission handler for search_api_admin_index_edit().
+ *
+ * @see search_api_admin_index_edit_validate()
  */
 function search_api_admin_index_edit_submit(array $form, array &$form_state) {
   form_state_values_clean($form_state);
-
   $values = $form_state['values'];
+  /** @var SearchApiIndex $index */
   $index = $form_state['index'];
+
+  if (!empty($form['options']['datasource'])) {
+    $index->datasource()->configurationFormSubmit($form['options']['datasource'], $values['options']['datasource'], $form_state);
+  }
+
   $values['options'] += $index->options;
 
   $ret = $index->update($values);
@@ -1687,10 +1806,7 @@ function search_api_admin_index_fields(array $form, array &$form_state, SearchAp
   // An array of option arrays for types, keyed by nesting level.
   $types = array(0 => search_api_field_types());
   $entity_types = entity_get_info();
-  // $boosts = drupal_map_assoc(array('0.1', '0.2', '0.3', '0.5', '0.8', '1.0', '2.0', '3.0', '5.0', '8.0', '13.0', '21.0'));
-  $boosts = drupal_map_assoc(array('0.1', '0.2', '0.3', '0.5', '0.8', '1.0', '2.0', '3.0', '5.0', '8.0', '13.0', '21.0', '100', '1000', '1010', '1020', '1030', '1040', '1050', '1060'));
-
-
+  $boosts = drupal_map_assoc(array('0.0', '0.1', '0.2', '0.3', '0.5', '0.8', '1.0', '2.0', '3.0', '5.0', '8.0', '13.0', '21.0'));
 
   $fulltext_types = array(0 => array('text'));
   // Add all custom data types with fallback "text" to fulltext types as well.
@@ -1833,6 +1949,7 @@ function search_api_admin_index_fields(array $form, array &$form_state, SearchAp
   );
 
   if ($additional) {
+    asort($additional);
     reset($additional);
     $form['additional'] = array(
       '#type' => 'fieldset',
@@ -1986,6 +2103,9 @@ function _search_api_admin_get_fields(SearchApiIndex $index, EntityMetadataWrapp
  * @param array $variables
  *   An associative array containing:
  *   - element: A render element representing the form.
+ *
+ * @return string
+ *   The HTML for a field list form.
  */
 function theme_search_api_admin_fields_table($variables) {
   $form = $variables['element'];
@@ -2031,6 +2151,7 @@ function theme_search_api_admin_fields_table($variables) {
 function search_api_admin_index_fields_submit(array $form, array &$form_state) {
   $index = $form_state['index'];
   $options = isset($index->options) ? $index->options : array();
+  $index_path = 'admin/config/search/search_api/index/' . $index->machine_name;
   if ($form_state['values']['op'] == t('Save changes')) {
     $fields = $form_state['values']['fields'];
     $default_types = search_api_default_field_types();
@@ -2060,18 +2181,18 @@ function search_api_admin_index_fields_submit(array $form, array &$form_state) {
     $ret = $index->update(array('options' => $options));
 
     if ($ret) {
-      drupal_set_message(t('The indexed fields were successfully changed. ' .
-          'The index was cleared and will have to be re-indexed with the new settings.'));
+      $vars = array('@url' => $index_path);
+      drupal_set_message(t('The indexed fields were successfully changed. The index was cleared and will have to be <a href="@url">re-indexed</a> with the new settings.', $vars));
     }
     else {
       drupal_set_message(t('No values were changed.'));
     }
     if (isset($index->options['data_alter_callbacks']) || isset($index->options['processors'])) {
-      $form_state['redirect'] = 'admin/config/search/search_api/index/' . $index->machine_name . '/fields';
+      $form_state['redirect'] = $index_path . '/fields';
     }
     else {
       drupal_set_message(t('Please set up the indexing workflow.'));
-      $form_state['redirect'] = 'admin/config/search/search_api/index/' . $index->machine_name . '/workflow';
+      $form_state['redirect'] = $index_path . '/workflow';
     }
     return;
   }
@@ -2086,7 +2207,7 @@ function search_api_admin_index_fields_submit(array $form, array &$form_state) {
   else {
     drupal_set_message(t('No values were changed.'));
   }
-  $form_state['redirect'] = 'admin/config/search/search_api/index/' . $index->machine_name . '/fields';
+  $form_state['redirect'] = $index_path . '/fields';
 }
 
 /**

+ 25 - 5
sites/all/modules/contrib/search/search_api/search_api.api.php

@@ -341,7 +341,27 @@ function hook_search_api_query_alter(SearchApiQueryInterface $query) {
   // Exclude entities with ID 0. (Assume the ID field is always indexed.)
   if ($query->getIndex()->getEntityType()) {
     $info = entity_get_info($query->getIndex()->getEntityType());
-    $query->condition($info['entity keys']['id'], 0, '!=');
+    $query->condition($info['entity keys']['id'], 0, '<>');
+  }
+}
+
+/**
+ * Alter the search results before they are returned.
+ *
+ * @param array $results
+ *   The results returned by the server, which may be altered. The data
+ *   structure is the same as returned by SearchApiQueryInterface::execute().
+ * @param SearchApiQueryInterface $query
+ *   The search query that was executed.
+ */
+function hook_search_api_results_alter(array &$results, SearchApiQueryInterface $query) {
+  if ($query->getOption('search id') == 'search_api_views:my_search_view:page') {
+    // Log the number of results.
+    $vars = array(
+      '@keys' => $query->getOriginalKeys(),
+      '@num' => $results['result count'],
+    );
+    watchdog('my_module', 'Search view with query "@keys" had @num results.', $vars, WATCHDOG_DEBUG);
   }
 }
 
@@ -564,15 +584,15 @@ function hook_default_search_api_index_alter(array &$defaults) {
  * This function will be called for fields of the specific data type to convert
  * all individual values of the field to the correct format.
  *
- * @param $value
+ * @param mixed $value
  *   The raw, single value, as extracted from an entity wrapper.
- * @param $original_type
+ * @param string $original_type
  *   The original Entity API type of the value.
- * @param $type
+ * @param string $type
  *   The custom data type to which the value should be converted. Can be ignored
  *   if the callback is only used for a single data type.
  *
- * @return
+ * @return mixed|null
  *   The converted value, if a conversion could be executed. NULL otherwise.
  *
  * @see hook_search_api_data_type_info()

+ 110 - 2
sites/all/modules/contrib/search/search_api/search_api.drush.inc

@@ -71,9 +71,10 @@ function search_api_drush_command() {
       'drush sapi-i default_node_index' => dt('Index items for the index with the machine name !name.', array('!name' => 'default_node_index')),
       'drush sapi-i 1 100' => dt("Index a maximum number of !limit items (index's cron batch size items per batch run) for the index with the ID !id.", array('!limit' => 100, '!id' => 1)),
       'drush sapi-i 1 100 10' => dt("Index a maximum number of !limit items (!batch_size items per batch run) for the index with the ID !id.", array('!limit' => 100, '!batch_size' => 10, '!id' => 1)),
+      'drush sapi-i 0 0 100' => dt("Index all items of all indexes with !batch_size items per batch run.", array('!batch_size' => 100)),
     ),
     'arguments' => array(
-      'index_id' => dt('The numeric ID or machine name of an index.'),
+      'index_id' => dt('The numeric ID or machine name of an index. Set to 0 to index all indexes. Defaults to 0 (index all).'),
       'limit' => dt("The number of items to index (index's cron batch size items per run). Set to 0 to index all items. Defaults to 0 (index all)."),
       'batch_size' => dt("The number of items to index per batch run. Set to 0 to index all items at once. Defaults to the index's cron batch size."),
     ),
@@ -121,6 +122,39 @@ function search_api_drush_command() {
     'aliases' => array('sapi-sis'),
   );
 
+  $items['search-api-server-list'] = array(
+    'description' => 'List all search servers.',
+    'examples' => array(
+      'drush search-api-server-list' => dt('List all search servers.'),
+      'drush sapi-sl' => dt('Alias to list all search servers.'),
+    ),
+    'aliases' => array('sapi-sl'),
+  );
+
+  $items['search-api-server-enable'] = array(
+    'description' => 'Enable a search server.',
+    'examples' => array(
+      'drush search-api-server-e my_solr_server' => dt('Enable the !server search server.', array('!server' => 'my_solr_server')),
+      'drush sapi-se my_solr_server' => dt('Alias to enable the !server search server.', array('!server' => 'my_solr_server')),
+    ),
+    'arguments' => array(
+      'server_id' => dt('The numeric ID or machine name of a search server to enable.'),
+    ),
+    'aliases' => array('sapi-se'),
+  );
+
+  $items['search-api-server-disable'] = array(
+    'description' => 'Disable a search server.',
+    'examples' => array(
+      'drush search-api-server-disable' => dt('Disable the !server search server.', array('!server' => 'my_solr_server')),
+      'drush sapi-sd' => dt('Alias to disable the !server search server.', array('!server' => 'my_solr_server')),
+    ),
+    'arguments' => array(
+      'server_id' => dt('The numeric ID or machine name of a search server to disable.'),
+    ),
+    'aliases' => array('sapi-sd'),
+  );
+
   return $items;
 }
 
@@ -293,6 +327,7 @@ function drush_search_api_index($index_id = NULL, $limit = NULL, $batch_size = N
   if (search_api_drush_static(__FUNCTION__)) {
     return;
   }
+  $index_id = !empty($index_id) ? $index_id : NULL;
   $indexes = search_api_drush_get_index($index_id);
   if (empty($indexes)) {
     return;
@@ -508,7 +543,8 @@ function search_api_drush_get_server($server_id = NULL) {
   $servers = search_api_server_load_multiple($ids);
   if (empty($servers)) {
     drush_set_error(dt('Invalid server_id or no servers present.'));
-    // @todo: Maybe add logic to print table of all servers.
+    drush_print();
+    drush_search_api_server_list();
   }
   return $servers;
 }
@@ -533,3 +569,75 @@ function search_api_drush_static($function) {
   $index[$function] = TRUE;
   return FALSE;
 }
+
+/**
+ * Lists all search servers.
+ */
+function drush_search_api_server_list() {
+  if (search_api_drush_static(__FUNCTION__)) {
+    return;
+  }
+  $servers = search_api_server_load_multiple(FALSE);
+  if (empty($servers)) {
+    drush_print(dt('There are no servers present.'));
+    return;
+  }
+  $rows[] = array(
+    dt('Machine name'),
+    dt('Name'),
+    dt('Status'),
+  );
+  foreach ($servers as $server) {
+    $row = array(
+      $server->machine_name,
+      $server->name,
+      $server->enabled ? dt('enabled') : dt('disabled'),
+    );
+    $rows[] = $row;
+  }
+  drush_print_table($rows);
+}
+
+/**
+ * Enables a search server.
+ *
+ * @param int|string $server_id
+ *   The numeric ID or machine name of the server to enable.
+ */
+function drush_search_api_server_enable($server_id = NULL) {
+  if (!isset($server_id)) {
+    drush_print(dt('Please provide a valid server id.'));
+    return;
+  }
+  $server = search_api_server_load($server_id);
+  if (empty($server)) {
+    drush_print(dt('The server was not able to load.'));
+    return;
+  }
+  else {
+    $server->update(array('enabled' => 1));
+    drush_print(dt('The server was enabled successfully.'));
+  }
+}
+
+/**
+ * Disables a search server.
+ *
+ * @param int|string $server_id
+ *   The numeric ID or machine name of the server to disable.
+ */
+function drush_search_api_server_disable($server_id = NULL) {
+  if (!isset($server_id)) {
+    drush_print(dt('Please provide a valid server id.'));
+    return;
+  }
+  $server = search_api_server_load($server_id);
+  if (empty($server)) {
+    drush_print(dt('The server was not able to load.'));
+    return;
+  }
+  else {
+    $server->update(array('enabled' => 0));
+    drush_print(dt('The server was disabled successfully.'));
+  }
+}

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

@@ -16,9 +16,11 @@ files[] = includes/callback_language_control.inc
 files[] = includes/callback_node_access.inc
 files[] = includes/callback_node_status.inc
 files[] = includes/callback_role_filter.inc
+files[] = includes/callback_user_status.inc
 files[] = includes/datasource.inc
 files[] = includes/datasource_entity.inc
 files[] = includes/datasource_external.inc
+files[] = includes/datasource_multiple.inc
 files[] = includes/exception.inc
 files[] = includes/index_entity.inc
 files[] = includes/processor.inc
@@ -34,9 +36,9 @@ files[] = includes/service.inc
 
 configure = admin/config/search/search_api
 
-; Information added by Drupal.org packaging script on 2014-12-26
-version = "7.x-1.14"
+; Information added by Drupal.org packaging script on 2016-02-26
+version = "7.x-1.16+29-dev"
 core = "7.x"
 project = "search_api"
-datestamp = "1419580682"
+datestamp = "1456500713"
 

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

@@ -191,6 +191,35 @@ function search_api_schema() {
     'primary key' => array('item_id', 'index_id'),
   );
 
+  $schema['search_api_item_string_id'] = array(
+    'description' => 'Stores the items which should be indexed for each index, and their status. Used only for items with string IDs.',
+    'fields' => array(
+      'item_id' => array(
+        'description' => "The item's ID.",
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+      ),
+      'index_id' => array(
+        'description' => 'The {search_api_index}.id this item belongs to.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'changed' => array(
+        'description' => 'Either a flag or a timestamp to indicate if or when the item was changed since it was last indexed.',
+        'type' => 'int',
+        'size' => 'big',
+        'not null' => TRUE,
+        'default' => 1,
+      ),
+    ),
+    'indexes' => array(
+      'indexing' => array('index_id', 'changed'),
+    ),
+    'primary key' => array('item_id', 'index_id'),
+  );
+
   $schema['search_api_task'] = array(
     'description' => 'Stores pending tasks for servers.',
     'fields' => array(
@@ -1001,3 +1030,45 @@ function search_api_update_7117() {
       ->execute();
   }
 }
+
+/**
+ * Adds the {search_api_item_string_id} table for items with string IDs.
+ */
+function search_api_update_7118() {
+  // Some users have reported that the table already existed for them, for
+  // whatever reason. Therefore, just bail if the table already exists, assuming
+  // it already looks as expected.
+  if (db_table_exists('search_api_item_string_id')) {
+    return;
+  }
+
+  $table = array(
+    'description' => 'Stores the items which should be indexed for each index, and their status. Used only for items with string IDs.',
+    'fields' => array(
+      'item_id' => array(
+        'description' => "The item's ID.",
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+      ),
+      'index_id' => array(
+        'description' => 'The {search_api_index}.id this item belongs to.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'changed' => array(
+        'description' => 'Either a flag or a timestamp to indicate if or when the item was changed since it was last indexed.',
+        'type' => 'int',
+        'size' => 'big',
+        'not null' => TRUE,
+        'default' => 1,
+      ),
+    ),
+    'indexes' => array(
+      'indexing' => array('index_id', 'changed'),
+    ),
+    'primary key' => array('item_id', 'index_id'),
+  );
+  db_create_table('search_api_item_string_id', $table);
+}

+ 208 - 44
sites/all/modules/contrib/search/search_api/search_api.module

@@ -2,7 +2,7 @@
 
 /**
  * @file
- * Provides a flexible framework for implementing search servives.
+ * Provides a flexible framework for implementing search services.
  */
 
 /**
@@ -270,6 +270,7 @@ function search_api_theme() {
       'machine_name' => '',
       'description' => NULL,
       'item_type' => NULL,
+      'datasource_config' => NULL,
       'enabled' => NULL,
       'server' => NULL,
       'options' => array(),
@@ -646,8 +647,9 @@ function search_api_search_api_index_insert(SearchApiIndex $index) {
  * Implements hook_search_api_index_update().
  */
 function search_api_search_api_index_update(SearchApiIndex $index) {
-  // Call the datasource update function with the table this module provides.
+  // Call the datasource update function with the tables this module provides.
   search_api_index_update_datasource($index, 'search_api_item');
+  search_api_index_update_datasource($index, 'search_api_item_string_id');
 
   // If the server was changed, we have to call the appropriate service class
   // hook methods.
@@ -856,6 +858,8 @@ function search_api_entity_insert($entity, $type) {
   list($id) = entity_extract_ids($type, $entity);
   if (isset($id)) {
     search_api_track_item_insert($type, array($id));
+    $combined_id = $type . '/' . $id;
+    search_api_track_item_insert('multiple', array($combined_id));
   }
 }
 
@@ -866,6 +870,8 @@ function search_api_entity_insert($entity, $type) {
  * datasource controller and calls search_api_track_item_change() for the
  * updated items.
  *
+ * It also checks whether the entity's bundle changed and acts accordingly.
+ *
  * @see search_api_search_api_item_type_info()
  */
 function search_api_entity_update($entity, $type) {
@@ -874,9 +880,20 @@ function search_api_entity_update($entity, $type) {
   if (!entity_get_property_info($type)) {
     return;
   }
-  list($id) = entity_extract_ids($type, $entity);
+  list($id, , $new_bundle) = entity_extract_ids($type, $entity);
+
+  // Check if the entity's bundle changed.
+  if (!empty($entity->original)) {
+    list(, , $old_bundle) = entity_extract_ids($type, $entity->original);
+    if ($new_bundle != $old_bundle) {
+      _search_api_entity_datasource_bundle_change($type, $id, $old_bundle, $new_bundle);
+    }
+  }
+
   if (isset($id)) {
     search_api_track_item_change($type, array($id));
+    $combined_id = $type . '/' . $id;
+    search_api_track_item_change('multiple', array($combined_id));
   }
 }
 
@@ -898,6 +915,28 @@ function search_api_entity_delete($entity, $type) {
   list($id) = entity_extract_ids($type, $entity);
   if (isset($id)) {
     search_api_track_item_delete($type, array($id));
+    $combined_id = $type . '/' . $id;
+    search_api_track_item_delete('multiple', array($combined_id));
+  }
+}
+
+/**
+ * Implements hook_field_attach_rename_bundle().
+ *
+ * This is implemented on behalf of the SearchApiEntityDataSourceController
+ * datasource controller, to update any bundle settings that contain the changed
+ * bundle.
+ */
+function search_api_field_attach_rename_bundle($entity_type, $bundle_old, $bundle_new) {
+  foreach (search_api_index_load_multiple(FALSE, array('item_type' => $entity_type)) as $index) {
+    $bundles = &$index->options['datasource']['bundles'];
+    if (isset($bundles) && ($pos = array_search($bundle_old, $bundles)) !== FALSE) {
+      $bundles[$pos] = $bundle_new;
+      $index->save();
+      // Clear all caches that could contain the bundle information.
+      $index->resetCaches();
+      drupal_static_reset('search_api_get_datasource_controller');
+    }
   }
 }
 
@@ -935,16 +974,19 @@ function search_api_flush_caches() {
 function search_api_search_api_item_type_info() {
   $types = array();
 
-  foreach (entity_get_property_info() as $type => $property_info) {
-    if ($info = entity_get_info($type)) {
-      $types[$type] = array(
-        'name' => $info['label'],
-        'datasource controller' => 'SearchApiEntityDataSourceController',
-        'entity_type' => $type,
-      );
-    }
+  foreach (search_api_entity_type_options_list() as $type => $label) {
+    $types[$type] = array(
+      'name' => $label,
+      'datasource controller' => 'SearchApiEntityDataSourceController',
+      'entity_type' => $type,
+    );
   }
 
+  $types['multiple'] = array(
+    'name' => t('Multiple types'),
+    'datasource controller' => 'SearchApiCombinedEntityDataSourceController',
+  );
+
   return $types;
 }
 
@@ -1026,6 +1068,11 @@ function search_api_search_api_alter_callback_info() {
     'description' => t('Exclude unpublished nodes from the index. <strong>Caution:</strong> This only affects the indexed nodes themselves. If an enabled node has references to disabled nodes, those will still be indexed (or displayed) normally.'),
     'class' => 'SearchApiAlterNodeStatus',
   );
+  $callbacks['search_api_alter_user_status'] = array(
+    'name' => t('Exclude blocked users'),
+    'description' => t('Exclude blocked users from the index. <strong>Caution:</strong> This only affects the indexed users themselves. If an active user account includes a reference to a disabled user, that reference will still be indexed (or displayed) normally.'),
+    'class' => 'SearchApiAlterUserStatus',
+  );
 
   return $callbacks;
 }
@@ -1100,7 +1147,10 @@ function search_api_track_item_insert($type, array $item_ids) {
   }
 
   try {
-    search_api_get_datasource_controller($type)->trackItemInsert($item_ids, $indexes);
+    $returned_indexes = search_api_get_datasource_controller($type)->trackItemInsert($item_ids, $indexes);
+    if (isset($returned_indexes)) {
+      $indexes = $returned_indexes;
+    }
   }
   catch (SearchApiException $e) {
     $vars['%item_type'] = $type;
@@ -1137,7 +1187,10 @@ function search_api_track_item_change($type, array $item_ids) {
     return;
   }
   try {
-    search_api_get_datasource_controller($type)->trackItemChange($item_ids, $indexes);
+    $returned_indexes = search_api_get_datasource_controller($type)->trackItemChange($item_ids, $indexes);
+    if (isset($returned_indexes)) {
+      $indexes = $returned_indexes;
+    }
     foreach ($indexes as $index) {
       if (!empty($index->options['index_directly'])) {
         // For indexes with the index_directly option set, queue the items to be
@@ -1212,7 +1265,10 @@ function search_api_track_item_delete($type, array $item_ids) {
   $indexes = search_api_index_load_multiple(FALSE, $conditions);
   if ($indexes) {
     try {
-      search_api_get_datasource_controller($type)->trackItemDelete($item_ids, $indexes);
+      $changed_indexes = search_api_get_datasource_controller($type)->trackItemDelete($item_ids, $indexes);
+      if (isset($changed_indexes)) {
+        $indexes = $changed_indexes;
+      }
     }
     catch (SearchApiException $e) {
       $vars['%item_type'] = $type;
@@ -1222,8 +1278,9 @@ function search_api_track_item_delete($type, array $item_ids) {
 
   // Then, delete it from all servers. Servers of disabled indexes have to be
   // considered, too!
-  unset($conditions['enabled']);
-  foreach (search_api_index_load_multiple(FALSE, $conditions) as $index) {
+  $conditions['enabled'] = 0;
+  $indexes = array_merge($indexes, search_api_index_load_multiple(FALSE, $conditions));
+  foreach ($indexes as $index) {
     try {
       if ($server = $index->server()) {
         $server->deleteItems($item_ids, $index);
@@ -2043,8 +2100,8 @@ function _search_api_query_add_node_access($account, SearchApiQueryInterface $qu
  * @param array $allowed
  *   Optionally, an array of allowed types.
  *
- * @return
- *   TRUE if $type is either one of the specified types, or a list of such
+ * @return bool
+ *   TRUE if $type is either one of the specified types or a list of such
  *   values. FALSE otherwise.
  *
  * @see search_api_extract_inner_type()
@@ -2060,7 +2117,7 @@ function search_api_is_text_type($type, array $allowed = array('text')) {
  * @param $type
  *   A string containing the type to check.
  *
- * @return
+ * @return bool
  *   TRUE iff $type is a list type ("list<*>").
  */
 function search_api_is_list_type($type) {
@@ -2073,7 +2130,7 @@ function search_api_is_list_type($type) {
  * @param $type
  *   A string containing the type to check.
  *
- * @return
+ * @return int
  *   The nesting level of the type. 0 for singular types, 1 for lists of
  *   singular types, etc.
  */
@@ -2100,7 +2157,7 @@ function search_api_list_nesting_level($type) {
  * @param $nested_type
  *   Another type, determining the nesting level.
  *
- * @return
+ * @return string
  *   A list version of $type, as specified above.
  */
 function search_api_nest_type($type, $nested_type) {
@@ -2117,7 +2174,7 @@ function search_api_nest_type($type, $nested_type) {
  * @param $type
  *   A string containing the list type to process.
  *
- * @return
+ * @return string
  *   A string containing the primitive type contained within the list, e.g.
  *   "text" for "list<text>" (or for "list<list<text>>"). If $type is no list
  *   type, it is returned unchanged.
@@ -2139,7 +2196,7 @@ function search_api_extract_inner_type($type) {
  *
  * Modules implementing other datasource controllers, that use a table other
  * than {search_api_item}, can use this function, too. It should be called
- * uncoditionally in a hook_search_api_index_update() implementation. If this
+ * unconditionally in a hook_search_api_index_update() implementation. If this
  * function isn't used, similar code should be added there.
  *
  * However, note that this is only necessary (and this function should only be
@@ -2177,6 +2234,9 @@ function search_api_index_update_datasource(SearchApiIndex $index, $table, $colu
  *   The $fields array with additional "value" and "original_type" keys set.
  */
 function search_api_extract_fields(EntityMetadataWrapper $wrapper, array $fields, array $value_options = array()) {
+  $value_options += array(
+    'identifier' => TRUE,
+  );
   // If $wrapper is a list of entities, we have to aggregate their field values.
   $wrapper_info = $wrapper->info();
   if (search_api_is_list_type($wrapper_info['type'])) {
@@ -2214,19 +2274,31 @@ function search_api_extract_fields(EntityMetadataWrapper $wrapper, array $fields
       $info['original_type'] = $info['type'];
       if (isset($wrapper->$field)) {
         try {
-          $info['value'] = $wrapper->$field->value($value_options);
-          // For fulltext fields with options, also include the option labels.
-          if (search_api_is_text_type($info['type']) && $wrapper->$field->optionsList('view')) {
-            _search_api_add_option_values($info['value'], $wrapper->$field->optionsList('view'));
-          }
+          // Set the original type according to the field wrapper's info.
           $property_info = $wrapper->$field->info();
           $info['original_type'] = $property_info['type'];
-          // For entities, we extract the entity ID instead of the whole object.
-          // @todo Use 'identifier' => TRUE instead of always loading the object.
-          $t = search_api_extract_inner_type($property_info['type']);
-          if (isset($entity_infos[$t])) {
-            // If no object is set, set this field to NULL.
-            $info['value'] = $info['value'] ? _search_api_extract_entity_value($wrapper->$field, search_api_is_text_type($info['type'])) : NULL;
+
+          // Extract the basic value from the field wrapper.
+          $info['value'] = $wrapper->$field->value($value_options);
+
+          // For entities, we need to take care to differentiate between
+          // entities with ID 0 and empty fields. In the latter case, the
+          // wrapper's value() method returns, when called with "identifier =
+          // TRUE", FALSE instead of the (more logical) NULL.
+          $is_entity = isset($entity_infos[search_api_extract_inner_type($property_info['type'])]);
+          if ($is_entity && $info['value'] === FALSE) {
+            $info['value'] = NULL;
+          }
+
+          // If we index the field as fulltext, we also include the entity label
+          // or option list label, if applicable.
+          if (search_api_is_text_type($info['type']) && isset($info['value'])) {
+            if ($wrapper->$field->optionsList('view')) {
+              _search_api_add_option_values($info['value'], $wrapper->$field->optionsList('view'));
+            }
+            elseif ($is_entity) {
+              $info['value'] = _search_api_extract_entity_value($wrapper->$field, TRUE);
+            }
           }
         }
         catch (EntityMetadataWrapperException $e) {
@@ -2245,10 +2317,6 @@ function search_api_extract_fields(EntityMetadataWrapper $wrapper, array $fields
   foreach ($nested as $prefix => $nested_fields) {
     if (isset($wrapper->$prefix)) {
       $nested_fields = search_api_extract_fields($wrapper->$prefix, $nested_fields, $value_options);
-      # http://drupal.org/node/1873910#comment-6876200
-      // $subwrapper = $wrapper->$prefix;
-      // $subwrapper->language( $wrapper->language->value() );
-      // $nested_fields = search_api_extract_fields($subwrapper, $nested_fields, $value_options);
       foreach ($nested_fields as $field => $info) {
         $fields["$prefix:$field"] = $info;
       }
@@ -2385,7 +2453,7 @@ function search_api_access_disable_page(Entity $entity) {
  * @param Entity $entity
  *   The server or index for which the access to the delete page is checked.
  *
- * @return
+ * @return bool
  *   TRUE if the delete page can be accessed by the user, FALSE otherwise.
  */
 function search_api_access_delete_page(Entity $entity) {
@@ -2500,7 +2568,7 @@ function search_api_server_clear($id) {
  * @param $id
  *   The ID or machine name of the server to delete.
  *
- * @return
+ * @return int|false
  *   1 on success, 0 or FALSE on failure.
  */
 function search_api_server_delete($id) {
@@ -2711,7 +2779,7 @@ function search_api_index_disable($id) {
  * @param $id
  *   The ID or machine name of the index to re-index.
  *
- * @return
+ * @return bool
  *   TRUE on success, FALSE on failure.
  */
 function search_api_index_reindex($id) {
@@ -2735,7 +2803,7 @@ function _search_api_index_reindex(SearchApiIndex $index) {
  * @param $id
  *   The ID or machine name of the index to clear.
  *
- * @return
+ * @return bool
  *   TRUE on success, FALSE on failure.
  */
 function search_api_index_clear($id) {
@@ -2749,7 +2817,7 @@ function search_api_index_clear($id) {
  * @param $id
  *   The ID or machine name of the index to delete.
  *
- * @return
+ * @return bool
  *   TRUE on success, FALSE on failure.
  */
 function search_api_index_delete($id) {
@@ -2778,6 +2846,66 @@ function search_api_index_options_list() {
   return $ret;
 }
 
+/**
+ * Options list callback for entity types.
+ *
+ * Will only include entity types which specify entity property information.
+ *
+ * @return string[]
+ *   An array of entity type machine names mapped to their human-readable
+ *   names.
+ */
+function search_api_entity_type_options_list() {
+  $types = array();
+  foreach (array_keys(entity_get_property_info()) as $type) {
+    $info = entity_get_info($type);
+    if ($info) {
+      $types[$type] = $info['label'];
+    }
+  }
+  return $types;
+}
+
+/**
+ * Options list callback for entity type bundles.
+ *
+ * Will include all bundles for all entity types which specify entity property
+ * information, in a format combining both entity type and bundle.
+ *
+ * @return string[]
+ *   An array of bundle identifiers mapped to their human-readable names.
+ */
+function search_api_combined_bundle_options_list() {
+  $types = array();
+  foreach (array_keys(entity_get_property_info()) as $type) {
+    $info = entity_get_info($type);
+    if (!empty($info['bundles'])) {
+      foreach ($info['bundles'] as $bundle => $bundle_info) {
+        $types["$type:$bundle"] = $bundle_info['label'];
+      }
+    }
+  }
+  return $types;
+}
+
+/**
+ * Retrieves a human-readable label for a multi-type index item.
+ *
+ * Provided as a non-object alternative to
+ * SearchApiCombinedEntityDataSourceController::getItemLabel() so it can be used
+ * as a getter callback.
+ *
+ * @param object $item
+ *   An item of the "multiple" item type.
+ *
+ * @return string|null
+ *   Either a human-readable label for the item, or NULL if none is available.
+ */
+function search_api_get_multi_type_item_label($item) {
+  $label = entity_label($item->item_type, $item->{$item->item_type});
+  return $label ? $label : NULL;
+}
+
 /**
  * Shutdown function which indexes all queued items, if any.
  */
@@ -2894,6 +3022,42 @@ function _search_api_deep_copy(array $array) {
   return $copy;
 }
 
+/**
+ * Reacts to a change in the bundle of an entity.
+ *
+ * Used as a helper function in search_api_entity_update().
+ *
+ * @param $type
+ *   The entity's type.
+ * @param $id
+ *   The entity's ID.
+ * @param $old_bundle
+ *   The entity's previous bundle.
+ * @param $new_bundle
+ *   The entity's new bundle.
+ */
+function _search_api_entity_datasource_bundle_change($type, $id, $old_bundle, $new_bundle) {
+  $controller = search_api_get_datasource_controller($type);
+  $conditions = array(
+    'enabled' => 1,
+    'item_type' => $type,
+    'read_only' => 0,
+  );
+  foreach (search_api_index_load_multiple(FALSE, $conditions) as $index) {
+    if (!empty($index->options['datasource']['bundles'])) {
+      $bundles = drupal_map_assoc($index->options['datasource']['bundles']);
+      if (empty($bundles[$new_bundle]) != empty($bundles[$old_bundle])) {
+        if (empty($bundles[$new_bundle])) {
+          $controller->trackItemDelete(array($id), array($index));
+        }
+        else {
+          $controller->trackItemInsert(array($id), array($index));
+        }
+      }
+    }
+  }
+}
+
 /**
  * Creates and sets a batch for indexing items.
  *
@@ -3010,7 +3174,7 @@ function _search_api_batch_indexing_callback(SearchApiIndex $index, $batch_size,
  * @param boolean $success
  *   Whether the batch finished successfully.
  * @param array $results
- *   Detailed informations about the result.
+ *   Detailed information about the result.
  */
 function _search_api_batch_indexing_finished($success, $results) {
   // Check if called from drush.

+ 73 - 0
sites/all/modules/contrib/search/search_api/search_api.test

@@ -830,6 +830,7 @@ class SearchApiUnitTest extends DrupalWebTestCase {
     $this->index = entity_create('search_api_index', array(
       'id' => 1,
       'name' => 'test',
+      'machine_name' => 'test',
       'enabled' => 1,
       'item_type' => 'user',
       'options' => array(
@@ -859,6 +860,7 @@ class SearchApiUnitTest extends DrupalWebTestCase {
     $this->checkIgnoreCaseProcessor();
     $this->checkTokenizer();
     $this->checkHtmlFilter();
+    $this->checkEntityDatasource();
   }
 
   /**
@@ -1122,4 +1124,75 @@ END;
     $this->assertEqual($tmp[1]['name']['value'], $processed1, 'Text was correctly processed.');
   }
 
+  /**
+   * Tests the entity datasource controller and its bundle setting.
+   */
+  protected function checkEntityDatasource() {
+    // First, create the necessary content types.
+    $type = (object) array(
+      'type' => 'article',
+      'base' => 'article',
+    );
+    node_type_save($type);
+    $type->type = $type->base = 'page';
+    node_type_save($type);
+
+    // Now, create some nodes.
+    $node = (object) array(
+      'title' => 'Foo',
+      'type' => 'article',
+    );
+    node_save($node);
+    $nid1 = $node->nid;
+    $node = (object) array(
+      'title' => 'Bar',
+      'type' => 'article',
+    );
+    node_save($node);
+    $node = (object) array(
+      'title' => 'Baz',
+      'type' => 'page',
+    );
+    node_save($node);
+
+    // We can't use $this->index here, since users don't have bundles.
+    $index = entity_create('search_api_index', array(
+      'id' => 2,
+      'name' => 'test2',
+      'machine_name' => 'test2',
+      'enabled' => 1,
+      'item_type' => 'node',
+      'options' => array(
+        'fields' => array(
+          'nid' => array(
+            'type' => 'integer',
+          ),
+        ),
+      ),
+    ));
+
+    // Now start tracking and check whether the index status is correct.
+    $datasource = search_api_get_datasource_controller('node');
+    $datasource->startTracking(array($index));
+    $status = $datasource->getIndexStatus($index);
+    $this->assertEqual($status['total'], 3, 'Correct number of items marked for indexing on not bundle-specific index.');
+    $datasource->stopTracking(array($index));
+
+    // Once again, but with only indexing articles.
+    $index->options['datasource']['bundles'] = array('article');
+    drupal_static_reset('search_api_get_datasource_controller');
+    $datasource = search_api_get_datasource_controller('node');
+    $datasource->startTracking(array($index));
+    $status = $datasource->getIndexStatus($index);
+    $this->assertEqual($status['total'], 2, 'Correct number of items marked for indexing on bundle-specific index.');
+    $datasource->stopTracking(array($index));
+
+    // Now test that bundle renaming works.
+    $index->save();
+    field_attach_rename_bundle('node', 'article', 'foo');
+    $index = search_api_index_load('test2', TRUE);
+    $this->assertEqual($index->options['datasource']['bundles'], array('foo'), 'Bundle was correctly renamed in index settings.');
+    $index->delete();
+  }
+
 }

+ 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 2014-12-26
-version = "7.x-1.14"
+; Information added by Drupal.org packaging script on 2016-02-26
+version = "7.x-1.16+29-dev"
 core = "7.x"
 project = "search_api"
-datestamp = "1419580682"
+datestamp = "1456500713"
 

+ 68 - 16
sites/all/modules/contrib/search/search_api_solr/CHANGELOG.txt

@@ -1,4 +1,56 @@
-Search API Solr search 1.6 (09/08/2014):
+Search API Solr Search 1.x, dev (xxxx-xx-xx):
+---------------------------------------------
+- #2645366 by tedfordgif, drunken monkey: Fixed browser incorrectly filling the
+  HTTP Auth form fields.
+- #2611716 by aditya_anurag, drunken monkey: Improved the method documentation
+  comments.
+- #2599658 by Loparev, drunken monkey: Added the possibility to highlight
+  non-fulltext fields.
+- #2598288 by ethan.han777, JeroenT, drunken monkey: Fixed potential notices in
+  the spellchecker code.
+- #2564927 by thePanz: Added support for random sorting in grouped searches.
+- #2527528 by maximpodorov, drunken monkey: Fixed searching of string fields
+  with leading/trailing spaces.
+- #2513314 by fortis, drunken monkey: Fixed error for empty filters.
+- #2551763 by drunken monkey: Fixed mention of INSTALL.txt in an error message.
+
+Search API Solr Search 1.9 (2015-08-30):
+----------------------------------------
+- #2503829 by das-peter: Added support for grouping on "magic" fields.
+- #2503617 by tobiasb: Fixed undefined "status_message" property in HTTP
+  response object.
+- #2313591 by thePanz, nadavoid: Added support for random sorting.
+- #2004596 by drunken monkey: Fixed workarounds for MLT problems.
+- #2486533 by drunken monkey: Fixed the default operator in the Solr 5 configs.
+- #2466897 by drunken monkey: Fixed Solr version options in server settings.
+- #1918904 by arnested, ramlev, drunken monkey: Added an alter hook for
+  autocomplete suggestions.
+- #2532812 by drunken monkey: Improved performance for filter-only queries.
+- #2463523 by bmunslow: Fixed field boosts in multi-index searches.
+
+Search API Solr Search 1.8 (2015-06-15):
+----------------------------------------
+- #2502511 by drunken monkey: Fixed index removal on Solr 5.
+- #2441117 by drunken monkey: Fixed unnecessary reindexing after changing
+  fields' boosts.
+
+Search API Solr Search 1.7 (2015-06-08):
+----------------------------------------
+- #2466489 by drunken monkey: Changed installation instructions to point to the
+  handbook.
+- #2486533 by drunken monkey: Fixed default operator in Solr 5.
+- #2456159 by drunken monkey: Updated config files to their latest version from
+  the common configs.
+- #2442077 by drunken monkey, basvredeling: Added support for Solr 5.
+- #2451037 by drunken monkey: Fixed timeout errors during indexing.
+- #2054551 by paolomainardi, drunken monkey, lex0r: Added possibility to
+  retrieve Solr field values in multi-index searches.
+- #1908990 by drunken monkey: Fixed various issues with excerpts.
+- #2368399 by das-peter, drunken monkey: Added Support for location filtering
+  with a bounding box.
+- #2333133 by drunken monkey: Fixed behavior of filters on fulltext fields.
+
+Search API Solr Search 1.6 (2014-09-08):
 ----------------------------------------
 - #2050961 by das-peter, drunken monkey: Added proximity/distance information
   to search results.
@@ -10,7 +62,7 @@ Search API Solr search 1.6 (09/08/2014):
 - #2270767 by RaF: Fixed search_api_solr_views_data_alter() not always
   returning all virtual fields.
 
-Search API Solr search 1.5 (05/23/2014):
+Search API Solr Search 1.5 (2014-05-23):
 ----------------------------------------
 - #2216895 by das-peter: Added support for empty/non-empty conditions on
   location field types.
@@ -30,7 +82,7 @@ Search API Solr search 1.5 (05/23/2014):
   only” queries.
 - #2147573 by drunken monkey: Improved error handling.
 
-Search API Solr search 1.4 (12/25/2013):
+Search API Solr Search 1.4 (2013-12-25):
 ----------------------------------------
 - #2157839 by drunken monkey, Nick_vh: Updated config files to the newest
   version.
@@ -48,7 +100,7 @@ Search API Solr search 1.4 (12/25/2013):
 - #2064377 by Nick_vh: Made configuration files compatible with Solr Cloud.
 - #2107417 by Nick_vh: Fixed config files for Solr 4.5.
 
-Search API Solr search 1.3 (10/23/2013):
+Search API Solr Search 1.3 (2013-10-23):
 ----------------------------------------
 - #2099683 by drunken monkey: Added support for 'virtual fields' in Views.
 - #1997702 by ianthomas_uk, drunken monkey: Added "AUTO" mode for HTTP method.
@@ -58,7 +110,7 @@ Search API Solr search 1.3 (10/23/2013):
 - #1882190 by corvus_ch, arnested, drunken monkey: Added optional index ID
   prefixes.
 
-Search API Solr search 1.2 (09/01/2013):
+Search API Solr Search 1.2 (2013-09-01):
 ----------------------------------------
 - #1246730 by febbraro, maciej.zgadzaj, drunken monkey: Added a way to alter
   the Solr document when indexing.
@@ -69,7 +121,7 @@ Search API Solr search 1.2 (09/01/2013):
 - #2045355 by drunken monkey, arpieb: Fixed result mapping of item IDs.
 - #2050157 by izus: Fixed typo in stopwords.txt.
 
-Search API Solr search 1.1 (07/21/2013):
+Search API Solr Search 1.1 (2013-07-21):
 ----------------------------------------
 - #1957730 by drunken monkey: Fixed filter query strings for negated filters.
 - #2010818 by kenorb, drunken monkey: Added new Files tab showing all used solr
@@ -84,7 +136,7 @@ Search API Solr search 1.1 (07/21/2013):
   multi-valued fields.
 - #2008034 by bdecarne: Fixed highlighting in multi-index searches.
 
-Search API Solr search 1.0 (06/09/2013):
+Search API Solr Search 1.0 (2013-06-09):
 ----------------------------------------
 - #1896080 by drunken monkey: Included additional required config files in the
   module.
@@ -98,7 +150,7 @@ Search API Solr search 1.0 (06/09/2013):
   support.
 - #1549244 by cferthorney, drunken monkey: Added SSL Support for Solr servers.
 
-Search API Solr search 1.0, RC 5 (05/17/2013):
+Search API Solr Search 1.0, RC 5 (2013-05-17):
 ----------------------------------------------
 - #1190462 by drunken monkey: Documented that enabling HTML filter makes sense.
 - #1986284 by drunken monkey: Updated common configs to the latest version.
@@ -112,7 +164,7 @@ Search API Solr search 1.0, RC 5 (05/17/2013):
 - #1978600 by chaby: Fixed hook_requirements() for install phase.
 - #1976930 by drunken monkey: Fixed duplicate method in SearchApiSolrField.
 
-Search API Solr search 1.0, RC 4 (04/22/2013):
+Search API Solr Search 1.0, RC 4 (2013-04-22):
 ----------------------------------------------
 - #1744250 by mollux, drunken monkey, das-peter: Added support for
   location-based searches.
@@ -122,7 +174,7 @@ Search API Solr search 1.0, RC 4 (04/22/2013):
 - #1900644 by Deciphered: Fixed facet handling for multi-index searches.
 - #1897386 by drunken monkey, NIck_vh: Update the common schema.
 
-Search API Solr search 1.0, RC 3 (01/06/2013):
+Search API Solr Search 1.0, RC 3 (2013-01-06):
 ----------------------------------------------
 - #1828260 by drunken monkey: Fixed filtering by index in multi-index searches.
 - #1509380 by drunken monkey: Adopt common config files.
@@ -141,7 +193,7 @@ Search API Solr search 1.0, RC 3 (01/06/2013):
 - #1299940 by drunken monkey: Fixed handling of empty response.
 - #1507818 by larowlan: Fixed field boosts for standard request handler.
 
-Search API Solr search 1.0, RC 2 (05/23/2012):
+Search API Solr Search 1.0, RC 2 (2012-05-23):
 ----------------------------------------------
 - Fixed escaping of error messages.
 - #1480170 by kotnik: Fixed return value of hook_requirements().
@@ -151,14 +203,14 @@ Search API Solr search 1.0, RC 2 (05/23/2012):
 - #1302406 by Steven Jones: Fixed autoload problem during installation.
 - #1340244 by drunken monkey, alanomaly: Added more helpful error messages.
 
-Search API Solr search 1.0, RC 1 (11/10/2011):
+Search API Solr Search 1.0, RC 1 (2011-11-10):
 ----------------------------------------------
 - #1308638 by drunken monkey: Adapted to new structure of field settings.
 - #1308498 by zenlan, drunken monkey: Added flexibility for facet fields.
 - #1319544 by drunken monkey: Fixed never delete contents of read-only indexes.
 - #1309650 by jonhattan, drunken monkey: Added support for the Libraries API.
 
-Search API Solr search 1.0, Beta 4 (09/08/2011):
+Search API Solr Search 1.0, Beta 4 (2011-09-08):
 ------------------------------------------------
 - #1230536 by thegreat, drunken monkey: Added support for OR facets.
 - #1184002 by drunken monkey: Fixed support of the latest SolrPhpClient version.
@@ -181,7 +233,7 @@ Search API Solr search 1.0, Beta 4 (09/08/2011):
 - #1184002 by drunken monkey: Fixed INSTALL.txt to reflect that the module
   doesn't work with the latest Solr PHP Client version.
 
-Search API Solr search 1.0, Beta 3 (06/06/2011):
+Search API Solr Search 1.0, Beta 3 (2011-06-06):
 ------------------------------------------------
 - #1111852 by miiimooo, drunken monkey: Added a 'More like this' feature.
 - #1153306 by JoeMcGuire, drunken monkey: Added spellchecking support.
@@ -191,7 +243,7 @@ Search API Solr search 1.0, Beta 3 (06/06/2011):
 - #1110820 by becw, drunken monkey: Added support for the Luke request handler.
 - #1095956 by drunken monkey: Added Solr-specific index alter hook.
 
-Search API Solr search 1.0, Beta 2 (03/04/2011):
+Search API Solr Search 1.0, Beta 2 (2011-03-04):
 ------------------------------------------------
 - #1071894 by drunken monkey: Fixed incorrect handling of boolean facets.
 - #1071796: Add additional help for Solr-specific extensions.
@@ -204,7 +256,7 @@ Search API Solr search 1.0, Beta 2 (03/04/2011):
 - #915174: Remove unnecessary files[] declarations from .info file.
 - #984134: Add Solr-specific query alter hooks.
 
-Search API Solr search 1.0, Beta 1 (11/29/2010):
+Search API Solr Search 1.0, Beta 1 (2010-11-29):
 ------------------------------------------------
 Basic functionality is in place and quite well-tested, including support for
 facets and for multi-index searches.

+ 0 - 72
sites/all/modules/contrib/search/search_api_solr/INSTALL.txt

@@ -1,72 +0,0 @@
-Setting up Solr
----------------
-
-In order for this module to work, you will first need to set up a Solr server.
-For this, you can either purchase a server from a web Solr hosts or set up your
-own Solr server on your web server (if you have the necessary rights to do so).
-If you want to use a hosted solution, a number of companies are listed on the
-module's project page [1]. Otherwise, please follow the instructions below.
-A more detailed set of instructions is available at [2].
-
-[1] https://drupal.org/project/search_api_solr
-[2] https://drupal.org/node/1999310
-
-As a pre-requisite for running your own Solr server, you'll need Java 6 or
-higher.
-
-Download the latest version of Solr 4.x from [3] and unpack the archive
-somewhere outside of your web server's document tree.
-
-[3] http://www.apache.org/dyn/closer.cgi/lucene/solr/
-
-This module also supports Solr 3.x. For better performance and more features,
-4.x should be used, though.
-
-For small websites, using the example application, located in $SOLR/example/,
-usually suffices. In any case, you can use it for developing and testing. The
-following instructions will assume you are using the example application,
-otherwise you should be able to substitute the corresponding paths.
-
-CAUTION! For production sites, it is vital that you somehow prevent outside
-access to the Solr server. Otherwise, attackers could read, corrupt or delete
-all your indexed data. Using the example server WON'T prevent this by default.
-If it is available, the probably easiest way of preventing this is to disable
-outside access to the ports used by Solr through your server's network
-configuration or through the use of a firewall.
-Other options include adding basic HTTP authentication or renaming the solr/
-directory to a random string of characters and using that as the path.
-
-Before starting the Solr server you will have to make sure it uses the proper
-configuration files. These are located in the solr-conf/ directory in this
-module, in a sub-directory according to the Solr version you are using. Copy all
-the files from that directory into Solr's configuration directory
-($SOLR/example/solr/collection1/conf/ in case of the 4.x example application),
-after backing up all files that would be overwritten.
-
-NOTE: The mapping-ISOLatin1Accent.txt is only included in the module for
-completeness' sake, as it is required to start the Solr server. It will be
-usually advisable to just use the file of the example application in this case,
-though, as it contains really useful definitions, while the file provided with
-this module is empty, apart from some documentation. For licensing reasons, it
-is not possible for us to include the definitions in the example config file in
-the copy this module provides.
-
-You can then start Solr. For the example application, go to $SOLR/example/ and
-issue the following command (assuming Java is correctly installed):
-
-java -jar start.jar &
-
-Afterwards, go to [4] in your web browser to ensure Solr is running correctly.
-
-[4] http://localhost:8983/solr/
-
-You can then enable this module and create a new server, using the "Solr search"
-service class. Enter the hostname, port and path corresponding to your Solr
-server in the appropriate fields. The default values already correspond to the
-example application, so you won't have to change the values if you use that.
-If you are using HTTP Authentication to protect your Solr server you also have
-to provide the appropriate user and password here.
-
-NOTE: For Solr 4.x, the server's path should also contain the Solr core name.
-E.g., when using the example application unchanged, set the path to
-"/solr/collection1" instead of "/solr".

+ 8 - 5
sites/all/modules/contrib/search/search_api_solr/README.txt

@@ -2,12 +2,10 @@ Solr search
 -----------
 
 This module provides an implementation of the Search API which uses an Apache
-Solr search server for indexing and searching. Before enabling or using this
-module, you'll have to follow the instructions given in INSTALL.txt first.
+Solr search server for indexing and searching. You can find detailed
+instructions for setting up Solr in the module's handbook [1].
 
-For more detailed documentation, see the handbook [1].
-
-[1] https://drupal.org/node/1999280
+[1] https://www.drupal.org/node/1999280
 
 Supported optional features
 ---------------------------
@@ -139,6 +137,11 @@ Hidden variables
   what you are doing. Old indexed items will be lost when the hash is changed
   and all items will have to be reindexed. Can only contain alphanumeric
   characters.
+- search_api_solr_highlight_prefix (default: "tm_")
+  The prefix of Solr fields for which field-level highlighting will be enabled.
+  Since the prefix of fields is used to determine the field type (by default),
+  this lets you enable highlighting for other field types. By default,
+  highlighting will be possible for all fulltext fields.
 
 [8] http://wiki.apache.org/solr/UpdateXmlMessages#A.22commit.22_and_.22optimize.22
 

+ 196 - 65
sites/all/modules/contrib/search/search_api_solr/includes/service.inc

@@ -57,7 +57,7 @@ class SearchApiSolrService extends SearchApiAbstractService {
   protected $request_handler = NULL;
 
   /**
-   * Overrides SearchApiAbstractService::configurationForm().
+   * {@inheritdoc}
    */
   public function configurationForm(array $form, array &$form_state) {
     if ($this->options) {
@@ -176,6 +176,10 @@ class SearchApiSolrService extends SearchApiAbstractService {
       '#type' => 'textfield',
       '#title' => t('Username'),
       '#default_value' => $options['http_user'],
+      // This prefix with no-op text and password field will keep most browsers
+      // from autocompleting these fields, which is hardly ever what the user
+      // wants.
+      '#prefix' => '<input type="text" style="display:none" /><input type="password" style="display:none" />',
     );
     $form['http']['http_pass'] = array(
       '#type' => 'password',
@@ -222,9 +226,9 @@ class SearchApiSolrService extends SearchApiAbstractService {
       '#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"'),
       '#options' => array(
         '' => t('Determine automatically'),
-        '1' => '1.4',
         '3' => '3.x',
         '4' => '4.x',
+        '5' => '5.x',
       ),
       '#default_value' => $options['solr_version'],
     );
@@ -271,7 +275,7 @@ class SearchApiSolrService extends SearchApiAbstractService {
   }
 
   /**
-   * Overrides SearchApiAbstractService::configurationFormValidate().
+   * {@inheritdoc}
    */
   public function configurationFormValidate(array $form, array &$values, array &$form_state) {
     if (isset($values['port']) && (!is_numeric($values['port']) || $values['port'] < 0 || $values['port'] > 65535)) {
@@ -280,7 +284,7 @@ class SearchApiSolrService extends SearchApiAbstractService {
   }
 
   /**
-   * Overrides SearchApiAbstractService::configurationFormSubmit().
+   * {@inheritdoc}
    */
   public function configurationFormSubmit(array $form, array &$values, array &$form_state) {
     // Since the form is nested into another, we can't simply use #parents for
@@ -321,6 +325,7 @@ class SearchApiSolrService extends SearchApiAbstractService {
       'search_api_spellcheck',
       'search_api_data_type_location',
       'search_api_data_type_geohash',
+      'search_api_random_sort',
     ));
     if (isset($supported[$feature])) {
       return TRUE;
@@ -428,8 +433,8 @@ class SearchApiSolrService extends SearchApiAbstractService {
                 $status = 'error';
               }
               elseif (substr($stats_summary['@schema_version'], 0, 9) != 'drupal-4.') {
-                $variables['@url'] = url(drupal_get_path('module', 'search_api_solr') . '/INSTALL.txt');
-                $message = t('You are using an incompatible schema.xml configuration file. Please follow the instructions in the <a href="@url">INSTALL.txt</a> file for setting up Solr.', $variables);
+                $variables['@url'] = url('https://www.drupal.org/node/1999310');
+                $message = t('You are using an incompatible schema.xml configuration file. Please follow the instructions in <a href="@url">the handbook</a> for setting up Solr.', $variables);
                 drupal_set_message($message, 'error');
                 $status = 'error';
               }
@@ -494,7 +499,7 @@ class SearchApiSolrService extends SearchApiAbstractService {
   }
 
   /**
-   * Overrides SearchApiAbstractService::addIndex().
+   * {@inheritdoc}
    */
   public function addIndex(SearchApiIndex $index) {
     if (module_exists('search_api_multi') && module_exists('search_api_views')) {
@@ -503,42 +508,39 @@ class SearchApiSolrService extends SearchApiAbstractService {
   }
 
   /**
-   * Overrides SearchApiAbstractService::fieldsUpdated().
+   * {@inheritdoc}
    */
   public function fieldsUpdated(SearchApiIndex $index) {
     if (module_exists('search_api_multi') && module_exists('search_api_views')) {
       views_invalidate_cache();
     }
-    return TRUE;
+    // Find out if anything changed enough to need re-indexing.
+    $old_fields = isset($index->original->options['fields']) ? $index->original->options['fields'] : array();
+    $new_fields = isset($index->options['fields']) ? $index->options['fields'] : array();
+    if (!$old_fields && !$new_fields) {
+      return FALSE;
+    }
+    if (array_diff_key($old_fields, $new_fields) || array_diff_key($new_fields, $old_fields)) {
+      return TRUE;
+    }
+    $old_field_names = $this->getFieldNames($index->original, TRUE);
+    $new_field_names = $this->getFieldNames($index, TRUE);
+    return $old_field_names != $new_field_names;
   }
 
   /**
-   * Overrides SearchApiAbstractService::removeIndex().
+   * {@inheritdoc}
    */
   public function removeIndex($index) {
     if (module_exists('search_api_multi') && module_exists('search_api_views')) {
       views_invalidate_cache();
     }
-    $index_id = is_object($index) ? $index->machine_name : $index;
-    // Only delete the index's data if the index isn't read-only.
-    if (!is_object($index) || empty($index->read_only)) {
-      $this->connect();
-      $index_id = $this->getIndexId($index_id);
-      // Since the index ID we use for indexing can contain arbitrary
-      // prefixes, we have to escape it for use in the query.
-      $index_id = call_user_func(array($this->connection_class, 'phrase'), $index_id);
-      $query = "index_id:$index_id";
-      if (!empty($this->options['site_hash'])) {
-        // We don't need to escape the site hash, as that consists only of
-        // alphanumeric characters.
-        $query .= ' hash:' . search_api_solr_site_hash();
-      }
-      $this->solr->deleteByQuery($query);
-    }
+
+    parent::removeIndex($index);
   }
 
   /**
-   * Implements SearchApiServiceInterface::indexItems().
+   * {@inheritdoc}
    */
   public function indexItems(SearchApiIndex $index, array $items) {
     $documents = array();
@@ -569,7 +571,9 @@ class SearchApiSolrService extends SearchApiAbstractService {
         $doc->setField('site', $base_urls[$lang]);
       }
 
-      // Now add all fields contained in the item, with dynamic fields.
+      // 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 = '';
       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
@@ -585,8 +589,9 @@ class SearchApiSolrService extends SearchApiAbstractService {
           $doc = NULL;
           break;
         }
-        $this->addIndexField($doc, $fields[$key], $field['value'], $field['type']);
+        $text_content[] = $this->addIndexField($doc, $fields[$key], $field['value'], $field['type']);
       }
+      $doc->setField('content', implode("\n\n", array_filter($text_content)));
 
       if ($doc) {
         $documents[] = $doc;
@@ -643,6 +648,7 @@ class SearchApiSolrService extends SearchApiAbstractService {
       $ret = array(
         'search_api_id' => 'item_id',
         'search_api_relevance' => 'score',
+        'search_api_random' => 'random',
       );
 
       // Add the names of any fields configured on the index.
@@ -692,30 +698,32 @@ class SearchApiSolrService extends SearchApiAbstractService {
    * is the same as specified in SearchApiServiceInterface::indexItems().
    */
   protected function addIndexField(SearchApiSolrDocument $doc, $key, $value, $type, $multi_valued = FALSE) {
+    $text_content = '';
     // Don't index empty values (i.e., when field is missing).
     if (!isset($value)) {
-      return;
+      return $text_content;
     }
     if (search_api_is_list_type($type)) {
       $type = substr($type, 5, -1);
       foreach ($value as $v) {
-        $this->addIndexField($doc, $key, $v, $type, TRUE);
+        $text_content .= $this->addIndexField($doc, $key, $v, $type, TRUE) . "\n\n";
       }
-      return;
+      return trim($text_content);
     }
     switch ($type) {
       case 'tokens':
         foreach ($value as $v) {
+          $text_content .= $v['value'] . ' ';
           $doc->addField($key, $v['value']);
         }
-        return;
+        return trim($text_content);
       case 'boolean':
         $value = $value ? 'true' : 'false';
         break;
       case 'date':
         $value = is_numeric($value) ? (int) $value : strtotime($value);
         if ($value === FALSE) {
-          return;
+          return $text_content;
         }
         $value = format_date($value, 'custom', self::SOLR_DATE_FORMAT, 'UTC');
         break;
@@ -732,6 +740,10 @@ class SearchApiSolrService extends SearchApiAbstractService {
     else {
       $doc->setField($key, $value);
     }
+    if (search_api_is_text_type($type)) {
+      $text_content = $value;
+    }
+    return $text_content;
   }
 
   /**
@@ -764,6 +776,17 @@ class SearchApiSolrService extends SearchApiAbstractService {
    *
    * It is up to the caller to ensure $ids is a valid query when the method is
    * called in this fashion.
+   *
+   * @param array|string $ids
+   *   Either an array containing the ids of the items that should be deleted,
+   *   or 'all' if all items should be deleted. Other formats might be
+   *   recognized by implementing classes, but these are not standardized.
+   * @param SearchApiIndex $index
+   *   The index from which items should be deleted, or NULL if all indexes on
+   *   this server should be cleared (then, $ids has to be 'all').
+   *
+   * @throws SearchApiException
+   *   If an error occurred while trying to delete the items.
    */
   public function deleteItems($ids = 'all', SearchApiIndex $index = NULL) {
     $this->connect();
@@ -790,13 +813,13 @@ class SearchApiSolrService extends SearchApiAbstractService {
       if ($ids != 'all') {
         $query[] = $query ? "($ids)" : $ids;
       }
-      $this->solr->deleteByQuery($query ? implode(' ', $query) : '*:*');
+      $this->solr->deleteByQuery($query ? implode(' AND ', $query) : '*:*');
     }
     $this->scheduleCommit();
   }
 
   /**
-   * Implements SearchApiServiceInterface::search().
+   * {@inheritdoc}
    */
   public function search(SearchApiQueryInterface $query) {
     $time_method_called = microtime(TRUE);
@@ -820,10 +843,12 @@ class SearchApiSolrService extends SearchApiAbstractService {
     $options = $query->getOptions();
     $search_fields = $query->getFields();
     // Get the index fields to be able to retrieve boosts.
-    $index_fields = $index->getFields();
+    $index_fields = $index->getFields() + array(
+      'search_api_relevance' => array('type' => 'decimal', 'indexed' => TRUE),
+      'search_api_id' => array('type' => 'integer', 'indexed' => TRUE),
+    );
     $qf = array();
     foreach ($search_fields as $f) {
-      $boost = '';
       $boost = isset($index_fields[$f]['boost']) ? '^' . $index_fields[$f]['boost'] : '';
       $qf[] = $fields[$f] . $boost;
     }
@@ -845,6 +870,17 @@ class SearchApiSolrService extends SearchApiAbstractService {
       if (substr($f, 0, 3) == 'ss_') {
         $f = 'sort_' . substr($f, 3);
       }
+
+      // The default Solr schema provides a virtual field named "random_SEED"
+      // that can be used to randomly sort the results; the field is available
+      // only at query-time.
+      if ($field == 'search_api_random') {
+        $params = $query->getOption('search_api_random_sort', array());
+        // Random seed: getting the value from parameters or computing a new one.
+        $seed = !empty($params['seed']) ? $params['seed'] : mt_rand();
+        $f = 'random_' . $seed;
+      }
+
       $order = strtolower($order);
       $sort[$field] = "$f $order";
     }
@@ -862,11 +898,16 @@ class SearchApiSolrService extends SearchApiAbstractService {
       $mlt_params['qt'] = 'mlt';
       // The fields to look for similarities in.
       $mlt_fl = array();
+      // Solr 4 (before 4.6) has a bug which results in numeric fields not being
+      // supported in MLT queries.
+      $mlt_no_numeric_fields = FALSE;
+      if ($version == 4) {
+        $system_info = $this->solr->getSystemInfo();
+        $mlt_no_numeric_fields = !isset($system_info->lucene->{'solr-spec-version'}) || version_compare($system_info->lucene->{'solr-spec-version'}, '4.6.0', '<');
+      }
       foreach($mlt['fields'] as $f) {
-        // Solr 4 has a bug which results in numeric fields not being supported
-        // in MLT queries.
         // Date fields don't seem to be supported at all.
-        if ($fields[$f][0] === 'd' || ($version == 4 && in_array($fields[$f][0], array('i', 'f')))) {
+        if ($fields[$f][0] === 'd' || ($mlt_no_numeric_fields && in_array($fields[$f][0], array('i', 'f')))) {
           continue;
         }
         $mlt_fl[] = $fields[$f];
@@ -879,18 +920,40 @@ class SearchApiSolrService extends SearchApiAbstractService {
       $id = $this->createId($index_id, $mlt['id']);
       $id = call_user_func(array($this->connection_class, 'phrase'), $id);
       $keys = 'id:' . $id;
+      // In (early versions of) Solr 5, facets aren't supported with MLT.
+      if ($version >= 5) {
+        $facet_params = array();
+      }
     }
 
     // Handle spatial filters.
     if ($spatials = $query->getOption('search_api_location')) {
       foreach ($spatials as $i => $spatial) {
-        if (empty($spatial['field']) || empty($spatial['lat']) || empty($spatial['lon'])) {
+        // Spatial options all need a field to do anything.
+        if (!isset($spatial['field'])) {
           continue;
         }
 
         unset($radius);
         $field = $fields[$spatial['field']];
         $escaped_field = SearchApiSolrConnection::escapeFieldName($field);
+
+        // If proper bbox coordinates were given use them to filter.
+        if (isset($spatial['bbox'])) {
+          if ($version >= 4) {
+            $bbox = $spatial['bbox'];
+            $fq[] = $escaped_field . ':[' . (float) $bbox['bottom'] . ',' . (float) $bbox['left'] . ' TO ' . (float) $bbox['top'] . ',' . (float) $bbox['right'] . ']';
+          }
+          else {
+            $warnings[] = t('Filtering by a bounding box is not supported in Solr versions below 4.');
+          }
+        }
+
+        // Everything other than a bounding box filter requires a point, so stop
+        // here (for this option) if "lat" and "lon" aren't both set.
+        if (!isset($spatial['lat']) || !isset($spatial['lon'])) {
+          continue;
+        }
         $point = ((float) $spatial['lat']) . ',' . ((float) $spatial['lon']);
 
         // Prepare the filter settings.
@@ -1032,6 +1095,18 @@ class SearchApiSolrService extends SearchApiAbstractService {
               if (substr($f, 0, 3) == 'ss_') {
                 $f = 'sort_' . substr($f, 3);
               }
+
+              // The default Solr schema provides a virtual field named
+              // "random_SEED" that can be used to randomly sort the results;
+              // the field is available only at query-time.
+              if ($group_sort_field == 'search_api_random') {
+                $params = $query->getOption('search_api_random_sort', array());
+                // Random seed: getting the value from parameters or computing a
+                // new one.
+                $seed = !empty($params['seed']) ? $params['seed'] : mt_rand();
+                $f = 'random_' . $seed;
+              }
+
               $order = strtolower($order);
               $group_params['group.sort'][] = $f . ' ' . $order;
             }
@@ -1275,8 +1350,8 @@ class SearchApiSolrService extends SearchApiAbstractService {
     }
     $output = '';
 
-    if (!empty($this->options['excerpt']) && !empty($response->highlighting->$id->spell)) {
-      foreach ($response->highlighting->$id->spell as $snippet) {
+    if (!empty($this->options['excerpt']) && !empty($response->highlighting->$id->content)) {
+      foreach ($response->highlighting->$id->content as $snippet) {
         $snippet = strip_tags($snippet);
         $snippet = preg_replace('/^.*>|<.*$/', '', $snippet);
         $snippet = $this->formatHighlighting($snippet);
@@ -1288,8 +1363,10 @@ class SearchApiSolrService extends SearchApiAbstractService {
       }
     }
     if (!empty($this->options['highlight_data'])) {
+      $prefix = variable_get('search_api_solr_highlight_prefix', 'tm_');
+      $prefix_length = strlen($prefix);
       foreach ($field_mapping as $search_api_property => $solr_property) {
-        if (substr($solr_property, 0, 3) == 'tm_' && !empty($response->highlighting->$id->$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);
@@ -1497,6 +1574,7 @@ class SearchApiSolrService extends SearchApiAbstractService {
   protected function createFilterQueries(SearchApiQueryFilterInterface $filter, array $solr_fields, array $fields) {
     $or = $filter->getConjunction() == 'OR';
     $fq = array();
+    $prefix = '';
     foreach ($filter->getFilters() as $f) {
       if (is_array($f)) {
         if (!isset($fields[$f[0]])) {
@@ -1508,10 +1586,10 @@ class SearchApiSolrService extends SearchApiAbstractService {
       }
       elseif ($f instanceof SearchApiQueryFilterInterface) {
         $q = $this->createFilterQueries($f, $solr_fields, $fields);
-        if ($filter->getConjunction() != $f->getConjunction()) {
+        if ($filter->getConjunction() != $f->getConjunction() && count($q) > 1) {
           // $or == TRUE means the nested filter has conjunction AND, and vice versa
           $sep = $or ? ' ' : ' OR ';
-          $fq[] = count($q) == 1 ? reset($q) : '((' . implode(')' . $sep . '(', $q) . '))';
+          $fq[] = '((' . implode(')' . $sep . '(', $q) . '))';
         }
         else {
           $fq = array_merge($fq, $q);
@@ -1520,15 +1598,20 @@ class SearchApiSolrService extends SearchApiAbstractService {
     }
     if (method_exists($filter, 'getTags')) {
       foreach ($filter->getTags() as $tag) {
-        $tag = "{!tag=$tag}";
-        foreach ($fq as $i => $filter) {
-          $fq[$i] = $tag . $filter;
-        }
+        $prefix = "{!tag=$tag}";
         // We can only apply one tag per filter.
         break;
       }
     }
-    return ($or && count($fq) > 1) ? array('((' . implode(') OR (', $fq) . '))') : $fq;
+    if ($or && count($fq) > 1) {
+      $fq = array('((' . implode(') OR (', $fq) . '))');
+    }
+    if ($prefix) {
+      foreach ($fq as $i => $filter) {
+        $fq[$i] = $prefix . $filter;
+      }
+    }
+    return $fq;
   }
 
   /**
@@ -1550,7 +1633,6 @@ class SearchApiSolrService extends SearchApiAbstractService {
     if ($value === NULL) {
       return ($operator == '=' ? '*:* AND -' : '') . "$field:[* TO *]";
     }
-    $value = trim($value);
     $value = $this->formatFilterValue($value, search_api_extract_inner_type($field_info['type']));
     switch ($operator) {
       case '<>':
@@ -1584,6 +1666,8 @@ class SearchApiSolrService extends SearchApiAbstractService {
         }
         $value = format_date($value, 'custom', self::SOLR_DATE_FORMAT, 'UTC');
         break;
+      case 'text':
+        return '(' . call_user_func(array($this->connection_class, 'escape'), $value) . ')';
     }
     return call_user_func(array($this->connection_class, 'phrase'), $value);
   }
@@ -1652,7 +1736,7 @@ class SearchApiSolrService extends SearchApiAbstractService {
 
     if (!empty($this->options['excerpt']) || !empty($this->options['highlight_data'])) {
       $highlight_params['hl'] = 'true';
-      $highlight_params['hl.fl'] = 'spell';
+      $highlight_params['hl.fl'] = 'content';
       $highlight_params['hl.simple.pre'] = '[HIGHLIGHT]';
       $highlight_params['hl.simple.post'] = '[/HIGHLIGHT]';
       $highlight_params['hl.snippets'] = 3;
@@ -1661,17 +1745,18 @@ class SearchApiSolrService extends SearchApiAbstractService {
     }
 
     if (!empty($this->options['highlight_data'])) {
-      $highlight_params['hl.fl'] = 'tm_*';
+      $highlight_params['hl.fl'] = variable_get('search_api_solr_highlight_prefix', 'tm_') . '*';
       $highlight_params['hl.snippets'] = 1;
       $highlight_params['hl.fragsize'] = 0;
       if (!empty($this->options['excerpt'])) {
         // If we also generate a "normal" excerpt, set the settings for the
-        // "spell" field (which we use to generate the excerpt) back to the
+        // "content" field (which we use to generate the excerpt) back to the
         // above values.
-        $highlight_params['f.spell.hl.snippets'] = 3;
-        $highlight_params['f.spell.hl.fragsize'] = 70;
+        $highlight_params['f.content.hl.snippets'] = 3;
+        $highlight_params['f.content.hl.fragsize'] = 70;
         // It regrettably doesn't seem to be possible to set hl.fl to several
-        // values, if one contains wild cards (i.e., "t_*,spell" wouldn't work).
+        // values, if one contains wild cards (i.e., "t_*,content" wouldn't
+        // work).
         $highlight_params['hl.fl'] = '*';
       }
     }
@@ -1831,6 +1916,14 @@ class SearchApiSolrService extends SearchApiAbstractService {
       $this->setRequestHandler($this->request_handler, $call_args);
     }
     $second_pass = !isset($this->options['autocorrect_suggest_words']) || $this->options['autocorrect_suggest_words'];
+
+    $alter_data = array(
+      'search' => $search,
+      'query' => $query,
+      'incomplete_key' => $incomplete_key,
+      'user_input' => $user_input,
+    );
+
     for ($i = 0; $i < ($second_pass ? 2 : 1); ++$i) {
       try {
         // Send search request
@@ -1839,6 +1932,8 @@ class SearchApiSolrService extends SearchApiAbstractService {
         $this->preQuery($call_args, $query);
         $response = $this->solr->search($keys, $params, $http_method);
 
+        $alter_data['responses'][] = $response;
+
         if (!empty($response->spellcheck->suggestions)) {
           $replace = array();
           foreach ($response->spellcheck->suggestions as $word => $data) {
@@ -1927,9 +2022,11 @@ class SearchApiSolrService extends SearchApiAbstractService {
       }
       // Change parameters for second query.
       unset($params['facet.prefix']);
-      $keys = trim ($keys . ' ' . $incomplete_key);
+      $keys = trim($keys . ' ' . $incomplete_key);
     }
 
+    drupal_alter('search_api_solr_autocomplete_suggestions', $suggestions, $alter_data);
+
     return $suggestions;
   }
 
@@ -1985,7 +2082,8 @@ class SearchApiSolrService extends SearchApiAbstractService {
     $search_fields = $query->getFields();
     $qf = array();
     foreach ($search_fields as $f) {
-      $qf[] = $solr_fields[$f];
+      $boost = isset($fields[$f]['boost']) ? '^' . $fields[$f]['boost'] : '';
+      $qf[] = $solr_fields[$f] . $boost;
     }
 
     // Extract filters
@@ -1994,8 +2092,10 @@ class SearchApiSolrService extends SearchApiAbstractService {
 
     // Restrict search to searched indexes.
     $index_filter = array();
+    $indexes = array();
     foreach ($query->getIndexes() as $index) {
       $index_id = $this->getIndexId($index->machine_name);
+      $indexes[$index_id] = $index;
       $index_filter[] = 'index_id:' . call_user_func(array($this->connection_class, 'phrase'), $index_id);
     }
     $fq[] = implode(' OR ', $index_filter);
@@ -2050,6 +2150,9 @@ class SearchApiSolrService extends SearchApiAbstractService {
     if (!empty($highlight_params)) {
       $params += $highlight_params;
     }
+    if (!empty($this->options['retrieve_data'])) {
+      $params['fl'] = '*,score';
+    }
 
     // Retrieve http method from server options.
     $http_method = !empty($this->options['http_method']) ? $this->options['http_method'] : 'AUTO';
@@ -2074,16 +2177,44 @@ class SearchApiSolrService extends SearchApiAbstractService {
     $results['results'] = array();
     $tmp = array();
     foreach ($response->response->docs as $id => $doc) {
+      if (isset($indexes[$doc->index_id])) {
+        $index = $indexes[$doc->index_id];
+      }
+      else {
+        $index = new SearchApiIndex(array('machine_name' => $doc->index_id));
+      }
+      $fields = $this->getFieldNames($index);
+      $field_options = $index->options['fields'];
       $result = array(
-        'id' => $doc->item_id,
+        'id' => NULL,
         'index_id' => $doc->index_id,
-        'score' => $doc->score,
+        'score' => NULL,
+        'fields' => array(),
       );
-      $solr_id = $this->createId($doc->index_id, $result['id']);
-      $excerpt = $this->getExcerpt($response, $solr_id, $tmp, array());
+      $solr_id = $this->createId($doc->index_id, $doc->item_id);
+      foreach ($fields as $search_api_property => $solr_property) {
+        if (isset($doc->{$solr_property})) {
+          $result['fields'][$search_api_property] = $doc->{$solr_property};
+          // Date fields need some special treatment to become valid date values
+          // (i.e., timestamps) again.
+          if (isset($field_options[$search_api_property]['type'])
+              && $field_options[$search_api_property]['type'] == 'date'
+              && preg_match('/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/', $result['fields'][$search_api_property])) {
+            $result['fields'][$search_api_property] = strtotime($result['fields'][$search_api_property]);
+          }
+        }
+      }
+
+      $excerpt = $this->getExcerpt($response, $solr_id, $result['fields'], $fields);
       if ($excerpt) {
         $result['excerpt'] = $excerpt;
       }
+
+      // We can find the item id and score in the special 'search_api_*'
+      // properties. Mappings are provided for these properties in
+      // SearchApiSolrService::getFieldNames().
+      $result['id'] = $result['fields']['search_api_id'];
+      $result['score'] = $result['fields']['search_api_relevance'];
       $results['results'][$id] = $result;
     }
 

+ 69 - 51
sites/all/modules/contrib/search/search_api_solr/includes/solr_connection.inc

@@ -224,7 +224,7 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
   }
 
   /**
-   * Implements SearchApiSolrConnectionInterface::ping().
+   * {@inheritdoc}
    */
   public function ping($timeout = 2) {
     $start = microtime(TRUE);
@@ -251,28 +251,28 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
   }
 
   /**
-   * Implements SearchApiSolrConnectionInterface::setSoftCommit().
+   * {@inheritdoc}
    */
   public function setSoftCommit($soft_commit) {
     $this->soft_commit = (bool) $soft_commit;
   }
 
   /**
-   * Implements SearchApiSolrConnectionInterface::getSoftCommit().
+   * {@inheritdoc}
    */
   public function getSoftCommit() {
     return $this->soft_commit;
   }
 
   /**
-   * Implements SearchApiSolrConnectionInterface::setStreamContext().
+   * {@inheritdoc}
    */
   public function setStreamContext($stream_context) {
     $this->stream_context = $stream_context;
   }
 
   /**
-   * Implements SearchApiSolrConnectionInterface::getStreamContext().
+   * {@inheritdoc}
    */
   public function getStreamContext() {
     return $this->stream_context;
@@ -333,6 +333,9 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
 
   /**
    * Sets $this->luke with the metadata about the index from admin/luke.
+   *
+   * @param int $num_terms
+   *   (optional) The number of "top terms" to return.
    */
   protected function setLuke($num_terms = 0) {
     if (empty($this->luke[$num_terms])) {
@@ -360,7 +363,7 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
   }
 
   /**
-   * Implements SearchApiSolrConnectionInterface::getFields().
+   * {@inheritdoc}
    */
   public function getFields($num_terms = 0) {
     $fields = array();
@@ -371,7 +374,7 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
   }
 
   /**
-   * Implements SearchApiSolrConnectionInterface::getLuke().
+   * {@inheritdoc}
    */
   public function getLuke($num_terms = 0) {
     if (!isset($this->luke[$num_terms])) {
@@ -381,7 +384,7 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
   }
 
   /**
-   * Implements SearchApiSolrConnectionInterface::getSolrVersion().
+   * {@inheritdoc}
    */
   public function getSolrVersion() {
     // Allow for overrides by the user.
@@ -392,7 +395,7 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
     $system_info = $this->getSystemInfo();
     // Get our solr version number
     if (isset($system_info->lucene->{'solr-spec-version'})) {
-      return $system_info->lucene->{'solr-spec-version'}[0];
+      return (int) $system_info->lucene->{'solr-spec-version'};
     }
     return 0;
   }
@@ -430,7 +433,7 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
   }
 
   /**
-   * Implements SearchApiSolrConnectionInterface::getStats().
+   * {@inheritdoc}
    */
   public function getStats() {
     if (!isset($this->stats)) {
@@ -440,7 +443,7 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
   }
 
   /**
-   * Implements SearchApiSolrConnectionInterface::getStatsSummary().
+   * {@inheritdoc}
    */
   public function getStatsSummary() {
     $stats = $this->getStats();
@@ -506,7 +509,7 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
   }
 
   /**
-   * Implements SearchApiSolrConnectionInterface::clearCache().
+   * {@inheritdoc}
    */
   public function clearCache() {
     if ($cid = $this->getCacheId()) {
@@ -545,7 +548,7 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
   }
 
   /**
-   * Implements SearchApiSolrConnectionInterface::makeServletRequest().
+   * {@inheritdoc}
    */
   public function makeServletRequest($servlet, array $params = array(), array $options = array()) {
     // Add default params.
@@ -561,7 +564,19 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
   }
 
   /**
-   * Central method for making a GET operation against this Solr Server
+   * Sends a GET request to the Solr server.
+   *
+   * @param string $url
+   *   The URL to which the request should be sent.
+   * @param array $options
+   *   Additional options for the request, as recognized by
+   *   drupal_http_request().
+   *
+   * @return object
+   *   The HTTP response, as returned by drupal_http_request().
+   *
+   * @throws SearchApiException
+   *   If an error occurs, either during sending or on the server side.
    */
   protected function sendRawGet($url, array $options = array()) {
     $options['method'] = 'GET';
@@ -571,7 +586,19 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
   }
 
   /**
-   * Central method for making a POST operation against this Solr Server
+   * Sends a PUT request to the Solr server.
+   *
+   * @param string $url
+   *   The URL to which the request should be sent.
+   * @param array $options
+   *   Additional options for the request, as recognized by
+   *   drupal_http_request().
+   *
+   * @return object
+   *   The HTTP response, as returned by drupal_http_request().
+   *
+   * @throws SearchApiException
+   *   If an error occurs, either during sending or on the server side.
    */
   protected function sendRawPost($url, array $options = array()) {
     $options['method'] = 'POST';
@@ -588,6 +615,15 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
    * Sends an HTTP request to Solr.
    *
    * This is just a wrapper around drupal_http_request().
+   *
+   * @param string $url
+   *   The URL to which the request should be sent.
+   * @param array $options
+   *   Additional options for the request, as recognized by
+   *   drupal_http_request().
+   *
+   * @return object
+   *   The HTTP response, as returned by drupal_http_request().
    */
   protected function makeHttpRequest($url, array $options = array()) {
     if (empty($options['method']) || $options['method'] == 'GET' || $options['method'] == 'HEAD') {
@@ -603,6 +639,7 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
 
     $result = drupal_http_request($url, $options);
 
+    $result->status_message = isset($result->status_message) ? $result->status_message : '';
     if (!isset($result->code) || $result->code < 0) {
       $result->code = 0;
       $result->status_message = 'Request failed';
@@ -630,12 +667,12 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
   }
 
   /**
-   * Implements SearchApiSolrConnectionInterface::escape().
+   * {@inheritdoc}
    */
   public static function escape($value, $version = 0) {
     $replacements = array();
 
-    $specials = array('+', '-', '&&', '||', '!', '(', ')', '{', '}', '[', ']', '^', '"', '~', '*', '?', ':', "\\");
+    $specials = array('+', '-', '&&', '||', '!', '(', ')', '{', '}', '[', ']', '^', '"', '~', '*', '?', ':', "\\", 'AND', 'OR', 'NOT');
     // Solr 4.x introduces regular expressions, making the slash also a special
     // character.
     if ($version >= 4) {
@@ -650,7 +687,7 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
   }
 
   /**
-   * Implements SearchApiSolrConnectionInterface::escapePhrase().
+   * {@inheritdoc}
    */
   public static function escapePhrase($value) {
     $replacements['"'] = '\"';
@@ -659,14 +696,14 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
   }
 
   /**
-   * Implements SearchApiSolrConnectionInterface::phrase().
+   * {@inheritdoc}
    */
   public static function phrase($value) {
     return '"' . self::escapePhrase($value) . '"';
   }
 
   /**
-   * Implements SearchApiSolrConnectionInterface::escapeFieldName().
+   * {@inheritdoc}
    */
   public static function escapeFieldName($value) {
     $value = str_replace(':', '\:', $value);
@@ -684,6 +721,7 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
    *   Additional query string to append to the URL.
    *
    * @return string
+   *   The complete URL.
    */
   protected function constructUrl($servlet, array $params = array(), $added_query_string = NULL) {
     // PHP's built in http_build_query() doesn't give us the format Solr wants.
@@ -703,14 +741,14 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
   }
 
   /**
-   * Implements SearchApiSolrConnectionInterface::getBaseUrl().
+   * {@inheritdoc}
    */
   public function getBaseUrl() {
     return $this->base_url;
   }
 
   /**
-   * Implements SearchApiSolrConnectionInterface::setBaseUrl().
+   * {@inheritdoc}
    */
   public function setBaseUrl($url) {
     $this->base_url = $url;
@@ -718,9 +756,9 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
   }
 
   /**
-   * Implements SearchApiSolrConnectionInterface::update().
+   * {@inheritdoc}
    */
-  public function update($rawPost, $timeout = FALSE) {
+  public function update($rawPost, $timeout = 3600) {
     if (empty($this->update_url)) {
       // Store the URL in an instance variable since many updates may be sent
       // via a single instance of this class.
@@ -734,7 +772,7 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
   }
 
   /**
-   * Implements SearchApiSolrConnectionInterface::addDocuments().
+   * {@inheritdoc}
    */
   public function addDocuments(array $documents, $overwrite = NULL, $commitWithin = NULL) {
     $attr = '';
@@ -758,21 +796,21 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
   }
 
   /**
-   * Implements SearchApiSolrConnectionInterface::commit().
+   * {@inheritdoc}
    */
   public function commit($waitSearcher = TRUE, $timeout = 3600) {
     return $this->optimizeOrCommit('commit', $waitSearcher, $timeout);
   }
 
   /**
-   * Implements SearchApiSolrConnectionInterface::deleteById().
+   * {@inheritdoc}
    */
   public function deleteById($id, $timeout = 3600) {
     return $this->deleteByMultipleIds(array($id), $timeout);
   }
 
   /**
-   * Implements SearchApiSolrConnectionInterface::deleteByMultipleIds().
+   * {@inheritdoc}
    */
   public function deleteByMultipleIds(array $ids, $timeout = 3600) {
     $rawPost = '<delete>';
@@ -786,7 +824,7 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
   }
 
   /**
-   * Implements SearchApiSolrConnectionInterface::deleteByQuery().
+   * {@inheritdoc}
    */
   public function deleteByQuery($rawQuery, $timeout = 3600) {
     $rawPost = '<delete><query>' . htmlspecialchars($rawQuery, ENT_NOQUOTES, 'UTF-8') . '</query></delete>';
@@ -795,14 +833,14 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
   }
 
   /**
-   * Implements SearchApiSolrConnectionInterface::optimize().
+   * {@inheritdoc}
    */
   public function optimize($waitSearcher = TRUE, $timeout = 3600) {
     return $this->optimizeOrCommit('optimize', $waitSearcher, $timeout);
   }
 
   /**
-   * Sends an commit or optimize command to the Solr server.
+   * Sends a commit or optimize command to the Solr server.
    *
    * Will be synchronous unless $waitSearcher is set to FALSE.
    *
@@ -900,26 +938,6 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
       // search keys here. (With our normal service class, empty keys won't be
       // set, but another module using this connection class might do that.)
       unset($params['q'], $params['qf']);
-
-      // If we have filters set (which will nearly always be the case, since we
-      // have to filter by index), move them to the q.alt parameter where
-      // possible.
-      if (!empty($params['fq'])) {
-        $qalt = array();
-        foreach ($params['fq'] as $i => $fq) {
-          // Tagged and negative filters cannot be moved to q.alt.
-          if ($fq[0] !== '{' && $fq[0] !== '-') {
-            $qalt[] = "($fq)";
-            unset($params['fq'][$i]);
-          }
-        }
-        if ($qalt) {
-          $params['q.alt'] = implode(' ', $qalt);
-        }
-        if (empty($params['fq'])) {
-          unset($params['fq']);
-        }
-      }
     }
 
     // Build the HTTP query string. We have our own method for that since PHP's

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

@@ -158,8 +158,7 @@ interface SearchApiSolrConnectionInterface {
    * @param string $rawPost
    *   The XML document to send to the Solr server's update service.
    * @param int|false $timeout
-   *   (optional) Maximum expected duration (in seconds). Defaults to not timing
-   *   out.
+   *   (optional) Maximum expected duration (in seconds).
    *
    * @return object
    *   A response object.
@@ -167,7 +166,7 @@ interface SearchApiSolrConnectionInterface {
    * @throws SearchApiException
    *   If an error occurs during the service call
    */
-  public function update($rawPost, $timeout = FALSE);
+  public function update($rawPost, $timeout = 3600);
 
   /**
    * Adds an array of Solr Documents to the index all at once
@@ -229,7 +228,7 @@ interface SearchApiSolrConnectionInterface {
   /**
    * Sends a delete request for several documents, based on the document IDs.
    *
-   * @param array $id
+   * @param array $ids
    *   The IDs of the documents which should be deleted. Expected to be UTF-8
    *   encoded.
    * @param int|false $timeout
@@ -307,8 +306,8 @@ interface SearchApiSolrConnectionInterface {
    *
    * @param string $value
    *   The string to escape.
-   * @param string $version
-   *   An integer representing major solr version release.
+   * @param int $version
+   *   An integer representing the major solr version.
    *
    * @return string
    *   An escaped string suitable for passing to Solr.

+ 4 - 2
sites/all/modules/contrib/search/search_api_solr/includes/spellcheck.inc

@@ -23,8 +23,10 @@ class SearchApiSpellcheckSolr extends SearchApiSpellcheck {
     if (isset($response->spellcheck->suggestions)) {
       $suggestions = $response->spellcheck->suggestions;
       foreach ($suggestions as $word => $data) {
-        foreach ($data->suggestion as $suggestion) {
-          $this->addSuggestion(new SearchApiSpellcheckSuggestion($word, $suggestion));
+        if (isset($data->suggestion)) {
+          foreach ($data->suggestion as $suggestion) {
+            $this->addSuggestion(new SearchApiSpellcheckSuggestion($word, $suggestion));
+          }
         }
       }
     }

+ 18 - 0
sites/all/modules/contrib/search/search_api_solr/search_api_solr.api.php

@@ -161,6 +161,24 @@ function search_api_solr_hook_search_api_data_type_info() {
   );
 }
 
+/**
+ * Alter autocomplete suggestions returned from Solr servers.
+ *
+ * @param array $suggestions
+ *   An array of suggestions to be altered, in the structure documented in
+ *   SearchApiAutocompleteSuggesterInterface::getAutocompleteSuggestions().
+ * @param array $alter_data
+ *   An associative array of data about the search, with the following keys:
+ *   "search", "query", "incomplete_key", "user_input", which correspond to the
+ *   arguments to SearchApiAutocompleteInterface::getAutocompleteSuggestions();
+ *   and "responses", an array containing the Solr response objects used for
+ *   constructing the suggestions.
+ */
+function hook_search_api_solr_autocomplete_suggestions_alter(array &$suggestions, array &$alter_data) {
+  // Always also suggest the original user input.
+  array_unshift($suggestions, trim($alter_data['user_input']));
+}
+
 /**
  * @} End of "addtogroup hooks".
  */

+ 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 2014-09-08
-version = "7.x-1.6"
+; Information added by Drupal.org packaging script on 2016-02-23
+version = "7.x-1.9+10-dev"
 core = "7.x"
 project = "search_api_solr"
-datestamp = "1410186051"
+datestamp = "1456233244"
 

+ 43 - 1
sites/all/modules/contrib/search/search_api_solr/solr-conf/3.x/schema.xml

@@ -10,7 +10,7 @@
  http://wiki.apache.org/solr/SchemaXml
 -->
 
-<schema name="drupal-4.2-solr-3.x" version="1.3">
+<schema name="drupal-4.3-solr-3.x" version="1.3">
     <!-- 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
@@ -211,6 +211,29 @@
         <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"
+                enablePositionIncrements="true"
+                />
+        <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 -->
@@ -248,6 +271,25 @@
         <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"
+                enablePositionIncrements="true"
+                />
+        <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

+ 2 - 1
sites/all/modules/contrib/search/search_api_solr/solr-conf/3.x/solrconfig.xml

@@ -20,7 +20,7 @@
      For more details about configurations options that may appear in
      this file, see http://wiki.apache.org/solr/SolrConfigXml.
 -->
-<config name="drupal-4.2-solr-3.x">
+<config name="drupal-4.3-solr-3.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)
@@ -871,6 +871,7 @@
     </lst>
     <arr name="last-components">
       <str>spellcheck</str>
+      <str>elevator</str>
     </arr>
   </requestHandler>
 

+ 43 - 1
sites/all/modules/contrib/search/search_api_solr/solr-conf/4.x/schema.xml

@@ -10,7 +10,7 @@
  http://wiki.apache.org/solr/SchemaXml
 -->
 
-<schema name="drupal-4.2-solr-4.x" version="1.3">
+<schema name="drupal-4.3-solr-4.x" version="1.3">
     <!-- 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
@@ -211,6 +211,29 @@
         <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"
+                enablePositionIncrements="true"
+                />
+        <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 -->
@@ -248,6 +271,25 @@
         <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"
+                enablePositionIncrements="true"
+                />
+        <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

+ 9 - 1
sites/all/modules/contrib/search/search_api_solr/solr-conf/4.x/solrconfig.xml

@@ -20,7 +20,7 @@
      For more details about configurations options that may appear in
      this file, see http://wiki.apache.org/solr/SolrConfigXml.
 -->
-<config name="drupal-4.2-solr-4.x" >
+<config name="drupal-4.3-solr-4.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)
@@ -880,6 +880,7 @@
     </lst>
     <arr name="last-components">
       <str>spellcheck</str>
+      <str>elevator</str>
     </arr>
   </requestHandler>
 
@@ -1068,10 +1069,17 @@
     <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 -->

+ 31 - 0
sites/all/modules/contrib/search/search_api_solr/solr-conf/5.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/5.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/5.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/5.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.3-solr-5.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/5.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/5.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>

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

@@ -0,0 +1,1808 @@
+<?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.3-solr-4.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:5.0.0}</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
+      -->
+    <mergePolicy class="org.apache.lucene.index.LogByteSizeMergePolicy"/>
+
+    <!-- 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"/>
+       -->
+
+    <!-- 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.
+      -->
+    <mergeFactor>4</mergeFactor>
+
+    <!-- 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> -->
+
+    <!-- Unlock On Startup
+
+         If true, unlock any held write or commit locks on startup.
+         This defeats the locking mechanism that allows multiple
+         processes to safely access a lucene index, and should be used
+         with care. Default is "false".
+
+         This is not needed if lock type is 'single'
+     -->
+    <unlockOnStartup>false</unlockOnStartup>
+
+    <!-- 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>
+       <str name="q.op">AND</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>
+      <str name="q.op">AND</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="q.op">AND</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>
+       <str name="q.op">AND</str>
+     </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>
+      <str name="q.op">AND</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/5.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/5.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=5.0.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/5.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/5.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.)

Some files were not shown because too many files changed in this diff