Bladeren bron

updated search_api modules to last version
i will try to dont patch them anymore

Bachir Soussi Chiadmi 9 jaren geleden
bovenliggende
commit
7c56b13587
61 gewijzigde bestanden met toevoegingen van 5065 en 636 verwijderingen
  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. 61 24
      sites/all/modules/contrib/search/search_api/CHANGELOG.txt
  6. 21 0
      sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_term.inc
  7. 3 3
      sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/search_api_facetapi.info
  8. 16 0
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/README.txt
  9. 6 5
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_argument_more_like_this.inc
  10. 20 0
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_date.inc
  11. 13 0
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_taxonomy_term.inc
  12. 75 27
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/query.inc
  13. 3 3
      sites/all/modules/contrib/search/search_api/contrib/search_api_views/search_api_views.info
  14. 20 0
      sites/all/modules/contrib/search/search_api/includes/callback_add_aggregation.inc
  15. 50 29
      sites/all/modules/contrib/search/search_api/includes/callback_bundle_filter.inc
  16. 31 0
      sites/all/modules/contrib/search/search_api/includes/callback_user_status.inc
  17. 135 21
      sites/all/modules/contrib/search/search_api/includes/datasource.inc
  18. 192 20
      sites/all/modules/contrib/search/search_api/includes/datasource_entity.inc
  19. 357 0
      sites/all/modules/contrib/search/search_api/includes/datasource_multiple.inc
  20. 4 2
      sites/all/modules/contrib/search/search_api/includes/index_entity.inc
  21. 10 1
      sites/all/modules/contrib/search/search_api/includes/query.inc
  22. 9 1
      sites/all/modules/contrib/search/search_api/includes/service.inc
  23. 197 81
      sites/all/modules/contrib/search/search_api/search_api.admin.inc
  24. 21 1
      sites/all/modules/contrib/search/search_api/search_api.api.php
  25. 107 1
      sites/all/modules/contrib/search/search_api/search_api.drush.inc
  26. 5 3
      sites/all/modules/contrib/search/search_api/search_api.info
  27. 64 0
      sites/all/modules/contrib/search/search_api/search_api.install
  28. 194 30
      sites/all/modules/contrib/search/search_api/search_api.module
  29. 73 0
      sites/all/modules/contrib/search/search_api/search_api.test
  30. 3 3
      sites/all/modules/contrib/search/search_api/tests/search_api_test.info
  31. 22 4
      sites/all/modules/contrib/search/search_api_saved_searches/CHANGELOG.txt
  32. 8 3
      sites/all/modules/contrib/search/search_api_saved_searches/search_api_saved_searches.admin.inc
  33. 3 3
      sites/all/modules/contrib/search/search_api_saved_searches/search_api_saved_searches.info
  34. 72 2
      sites/all/modules/contrib/search/search_api_saved_searches/search_api_saved_searches.install
  35. 118 41
      sites/all/modules/contrib/search/search_api_saved_searches/search_api_saved_searches.module
  36. 94 17
      sites/all/modules/contrib/search/search_api_saved_searches/search_api_saved_searches.pages.inc
  37. 6 1
      sites/all/modules/contrib/search/search_api_saved_searches/search_api_saved_searches.search_entity.inc
  38. 3 3
      sites/all/modules/contrib/search/search_api_saved_searches/search_api_saved_searches_i18n/search_api_saved_searches_i18n.info
  39. 52 16
      sites/all/modules/contrib/search/search_api_solr/CHANGELOG.txt
  40. 0 72
      sites/all/modules/contrib/search/search_api_solr/INSTALL.txt
  41. 3 5
      sites/all/modules/contrib/search/search_api_solr/README.txt
  42. 153 50
      sites/all/modules/contrib/search/search_api_solr/includes/service.inc
  43. 4 23
      sites/all/modules/contrib/search/search_api_solr/includes/solr_connection.inc
  44. 2 3
      sites/all/modules/contrib/search/search_api_solr/includes/solr_connection.interface.inc
  45. 18 0
      sites/all/modules/contrib/search/search_api_solr/search_api_solr.api.php
  46. 3 3
      sites/all/modules/contrib/search/search_api_solr/search_api_solr.info
  47. 43 1
      sites/all/modules/contrib/search/search_api_solr/solr-conf/3.x/schema.xml
  48. 2 1
      sites/all/modules/contrib/search/search_api_solr/solr-conf/3.x/solrconfig.xml
  49. 43 1
      sites/all/modules/contrib/search/search_api_solr/solr-conf/4.x/schema.xml
  50. 9 1
      sites/all/modules/contrib/search/search_api_solr/solr-conf/4.x/solrconfig.xml
  51. 31 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/5.x/elevate.xml
  52. 14 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/5.x/mapping-ISOLatin1Accent.txt
  53. 7 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/5.x/protwords.txt
  54. 693 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/5.x/schema.xml
  55. 23 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/5.x/schema_extra_fields.xml
  56. 34 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/5.x/schema_extra_types.xml
  57. 1808 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/5.x/solrconfig.xml
  58. 80 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/5.x/solrconfig_extra.xml
  59. 20 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/5.x/solrcore.properties
  60. 4 0
      sites/all/modules/contrib/search/search_api_solr/solr-conf/5.x/stopwords.txt
  61. 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
-

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

@@ -1,4 +1,41 @@
-Search API 1.14 (12/26/2014):
+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 +62,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 +75,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 +102,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 +118,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 +159,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 +188,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 +222,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 +249,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 +267,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 +284,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 +294,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 +310,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 +323,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 +334,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 +352,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 +423,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 +437,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 +479,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 +498,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 +546,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 +568,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 +610,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:

+ 21 - 0
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(

+ 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 2015-08-30
+version = "7.x-1.16"
 core = "7.x"
 project = "search_api"
-datestamp = "1419580682"
+datestamp = "1440962813"
 

+ 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

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

@@ -71,11 +71,12 @@ 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);
       }
       $mlt = array(
         'id' => $this->argument,

+ 20 - 0
sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_date.inc

@@ -16,6 +16,7 @@ class SearchApiViewsHandlerFilterDate extends SearchApiViewsHandlerFilter {
   public function option_definition() {
     return parent::option_definition() + array(
       'widget_type' => array('default' => 'default'),
+      'year_range' => array('default' => '-3:+3'),
     );
   }
 
@@ -43,6 +44,24 @@ class SearchApiViewsHandlerFilterDate extends SearchApiViewsHandlerFilter {
         '#options' => $widget_options,
       );
     }
+
+    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'));
+      }
+    }
   }
 
   /**
@@ -56,6 +75,7 @@ class SearchApiViewsHandlerFilterDate extends SearchApiViewsHandlerFilter {
     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_year_range'] = $this->options['year_range'];
       unset($form['value']['#description']);
     }
     elseif (empty($form_state['exposed'])) {

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

+ 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->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 2015-08-30
+version = "7.x-1.16"
 core = "7.x"
 project = "search_api"
-datestamp = "1419580682"
+datestamp = "1440962813"
 

+ 20 - 0
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;
   }
 
   /**
@@ -267,6 +284,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 +295,7 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
           'max' => 'integer',
           'min' => 'integer',
           'first' => 'string',
+          'first_char' => 'string',
           'list' => 'list<string>',
         );
       case 'description':
@@ -287,6 +306,7 @@ 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.'),
         );
     }

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


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

+ 135 - 21
sites/all/modules/contrib/search/search_api/includes/datasource.inc

@@ -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);
+
 }
 
 /**
@@ -521,6 +596,10 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
       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,
@@ -545,23 +623,28 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
    */
   public function trackItemChange($item_ids, array $indexes, $dequeue = FALSE) {
     if (!$this->table) {
-      return;
+      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;
   }
 
   /**
@@ -605,17 +688,22 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
    */
   public function trackItemDelete(array $item_ids, array $indexes) {
     if (!$this->table) {
-      return;
+      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.
    *

+ 192 - 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,24 @@ 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)) {
+          $query->condition($this->bundleKey, $bundles);
+        }
 
         // INSERT ... SELECT ...
         db_insert($this->table)
@@ -129,16 +170,147 @@ class SearchApiEntityDataSourceController extends SearchApiAbstractDataSourceCon
       }
     }
     else {
-      // In the absence of a 'base table', use the slow entity_load().
-      parent::startTracking($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}
+   */
+  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' => $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}
    */
-  protected function getAllItemIds() {
-    return array_keys(entity_load($this->entityType));
+  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' => 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));
+  }
+
+}

+ 4 - 2
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.
    *

+ 10 - 1
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_sort" special field can be used to sort randomly.
    * @param string $order
    *   The order to sort items in - either 'ASC' or 'DESC'.
    *
@@ -586,6 +588,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 +726,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);
   }
 
   /**

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

+ 197 - 81
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.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.

+ 21 - 1
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);
   }
 }
 

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

@@ -121,6 +121,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;
 }
 
@@ -508,7 +541,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 +567,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 2015-08-30
+version = "7.x-1.16"
 core = "7.x"
 project = "search_api"
-datestamp = "1419580682"
+datestamp = "1440962813"
 

+ 64 - 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,38 @@ function search_api_update_7117() {
       ->execute();
   }
 }
+
+/**
+ * Adds the {search_api_item_string_id} table for items with string IDs.
+ */
+function search_api_update_7118() {
+  $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);
+}

+ 194 - 30
sites/all/modules/contrib/search/search_api/search_api.module

@@ -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 (isset($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);
@@ -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;
       }
@@ -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.
  *

+ 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 2015-08-30
+version = "7.x-1.16"
 core = "7.x"
 project = "search_api"
-datestamp = "1419580682"
+datestamp = "1440962813"
 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -1,4 +1,40 @@
-Search API Solr search 1.6 (09/08/2014):
+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 +46,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 +66,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 +84,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 +94,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 +105,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 +120,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 +134,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 +148,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 +158,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 +177,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 +187,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 +217,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 +227,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 +240,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".

+ 3 - 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
 ---------------------------

+ 153 - 50
sites/all/modules/contrib/search/search_api_solr/includes/service.inc

@@ -222,9 +222,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'],
     );
@@ -321,6 +321,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;
@@ -509,7 +510,18 @@ class SearchApiSolrService extends SearchApiAbstractService {
     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;
   }
 
   /**
@@ -519,22 +531,8 @@ class SearchApiSolrService extends SearchApiAbstractService {
     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);
   }
 
   /**
@@ -569,7 +567,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 +585,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 +644,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 +694,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 +736,10 @@ class SearchApiSolrService extends SearchApiAbstractService {
     else {
       $doc->setField($key, $value);
     }
+    if (search_api_is_text_type($type)) {
+      $text_content = $value;
+    }
+    return $text_content;
   }
 
   /**
@@ -790,7 +798,7 @@ 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();
   }
@@ -820,10 +828,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 +855,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 +883,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 +905,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.
@@ -1275,8 +1323,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);
@@ -1497,6 +1545,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]])) {
@@ -1520,15 +1569,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;
   }
 
   /**
@@ -1584,6 +1638,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 +1708,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;
@@ -1666,12 +1722,13 @@ class SearchApiSolrService extends SearchApiAbstractService {
       $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 +1888,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 +1904,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 +1994,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 +2054,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 +2064,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 +2122,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 +2149,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;
     }
 

+ 4 - 23
sites/all/modules/contrib/search/search_api_solr/includes/solr_connection.inc

@@ -392,7 +392,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;
   }
@@ -603,6 +603,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';
@@ -635,7 +636,7 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
   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) {
@@ -720,7 +721,7 @@ class SearchApiSolrConnection implements SearchApiSolrConnectionInterface {
   /**
    * Implements SearchApiSolrConnectionInterface::update().
    */
-  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.
@@ -900,26 +901,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

+ 2 - 3
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

+ 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 2015-08-30
+version = "7.x-1.9"
 core = "7.x"
 project = "search_api_solr"
-datestamp = "1410186051"
+datestamp = "1440962648"
 

+ 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