Explorar el Código

updated search_api and search_api_solr

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -54,6 +54,27 @@ class SearchApiFacetapiTerm extends FacetapiQueryType implements FacetapiQueryTy
     }
     }
     elseif ($operator == FACETAPI_OPERATOR_OR) {
     elseif ($operator == FACETAPI_OPERATOR_OR) {
       $conjunction = '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 {
     else {
       $vars = array(
       $vars = array(
@@ -153,7 +174,7 @@ class SearchApiFacetapiTerm extends FacetapiQueryType implements FacetapiQueryTy
 
 
     // Always include the active facet items.
     // Always include the active facet items.
     foreach ($this->adapter->getActiveItems($this->facet) as $filter)  {
     foreach ($this->adapter->getActiveItems($this->facet) as $filter)  {
-      $build[$filter['value']]['#count'] = $results['result count'];
+      $build[$filter['value']]['#count'] = 0;
     }
     }
 
 
     // Then, add the facets returned by the server.
     // Then, add the facets returned by the server.

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

@@ -9,9 +9,9 @@ files[] = plugins/facetapi/adapter.inc
 files[] = plugins/facetapi/query_type_term.inc
 files[] = plugins/facetapi/query_type_term.inc
 files[] = plugins/facetapi/query_type_date.inc
 files[] = plugins/facetapi/query_type_date.inc
 
 
-; Information added by Drupal.org packaging script on 2014-12-26
-version = "7.x-1.14"
+; Information added by Drupal.org packaging script on 2016-02-26
+version = "7.x-1.16+29-dev"
 core = "7.x"
 core = "7.x"
 project = "search_api"
 project = "search_api"
-datestamp = "1419580682"
+datestamp = "1456500713"
 
 

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

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

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

@@ -24,6 +24,22 @@ When these are present, the normal keywords should be ignored and the related
 items be returned as results instead. Sorting, filtering and range restriction
 items be returned as results instead. Sorting, filtering and range restriction
 should all work normally.
 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
 "Facets block" display
 ----------------------
 ----------------------
 Most features should be clear to users of Views. However, the module also
 Most features should be clear to users of Views. However, the module also

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

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

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

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

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

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

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

@@ -178,12 +178,25 @@ class SearchApiViewsHandlerFilterTaxonomyTerm extends SearchApiViewsHandlerFilte
       return TRUE;
       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
     // If view is an attachment and is inheriting exposed filters, then assume
     // exposed input has already been validated.
     // exposed input has already been validated.
     if (!empty($this->view->is_attachment) && $this->view->display_handler->uses_exposed()) {
     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']];
       $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 it's non-required and there's no value don't bother filtering.
     if (!$this->options['expose']['required'] && empty($this->validated_exposed_input)) {
     if (!$this->options['expose']['required'] && empty($this->validated_exposed_input)) {
       return FALSE;
       return FALSE;

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

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

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

@@ -122,19 +122,63 @@ class SearchApiViewsQuery extends views_plugin_query {
   }
   }
 
 
   /**
   /**
-   * Add a sort to the query.
+   * Adds a sort to the query.
    *
    *
-   * @param $selector
+   * @param string $selector
    *   The field to sort on. All indexed fields of the index are valid values.
    *   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'.
    *   The order to sort items in - either 'ASC' or 'DESC'. Defaults to 'ASC'.
    */
    */
   public function add_selector_orderby($selector, $order = 'ASC') {
   public function add_selector_orderby($selector, $order = 'ASC') {
     $this->query->sort($selector, $order);
     $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.
    * 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.
    * Builds the necessary info to execute the query.
    */
    */
   public function build(&$view) {
   public function build(&$view) {
+    if (!empty($this->errors)) {
+      return;
+    }
+
     $this->view = $view;
     $this->view = $view;
 
 
     // Setup the nested filter structure for this query.
     // Setup the nested filter structure for this query.
@@ -375,8 +423,8 @@ class SearchApiViewsQuery extends views_plugin_query {
     // loading any items.
     // loading any items.
     foreach ($results as $id => $result) {
     foreach ($results as $id => $result) {
       if (!empty($this->options['entity_access']) && ($entity_type = $this->index->getEntityType())) {
       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;
           continue;
         }
         }
       }
       }
@@ -400,7 +448,7 @@ class SearchApiViewsQuery extends views_plugin_query {
       }
       }
 
 
       // Check whether we need to extract any properties from the result item.
       // 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) {
       if ($missing_fields) {
         $missing[$id] = $missing_fields;
         $missing[$id] = $missing_fields;
         if (is_object($row['entity'])) {
         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.
     // Load items of those rows which haven't got all field values, yet.
     if (!empty($ids)) {
     if (!empty($ids)) {
       $items += $this->index->loadItems($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.
     // Finally, add all rows to the Views result set.
@@ -490,31 +538,31 @@ class SearchApiViewsQuery extends views_plugin_query {
    * query backend.
    * query backend.
    */
    */
   public function get_result_wrappers($results, $relationship = NULL, $field = NULL) {
   public function get_result_wrappers($results, $relationship = NULL, $field = NULL) {
-    $entity_type = $this->index->getEntityType();
+    $type = $this->index->getEntityType() ? $this->index->getEntityType() : $this->index->item_type;
     $wrappers = array();
     $wrappers = array();
-    $load_entities = array();
+    $load_items = array();
     foreach ($results as $row_index => $row) {
     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 this entity isn't load, register it for pre-loading.
         if (!is_object($row->entity)) {
         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
     // If the results are entities, we pre-load them to make use of a multiple
     // load. (Otherwise, each result would be loaded individually.)
     // 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.
     // Apply the relationship, if necessary.
-    $type = $entity_type ? $entity_type : $this->index->item_type;
     $selector_suffix = '';
     $selector_suffix = '';
     if ($field && ($pos = strrpos($field, ':'))) {
     if ($field && ($pos = strrpos($field, ':'))) {
       $selector_suffix = substr($field, 0, $pos);
       $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/plugin_cache.inc
 files[] = includes/query.inc
 files[] = includes/query.inc
 
 
-; Information added by Drupal.org packaging script on 2014-12-26
-version = "7.x-1.14"
+; Information added by Drupal.org packaging script on 2016-02-26
+version = "7.x-1.16+29-dev"
 core = "7.x"
 core = "7.x"
 project = "search_api"
 project = "search_api"
-datestamp = "1419580682"
+datestamp = "1456500713"
 
 

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

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

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

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

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

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

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 50 - 29
sites/all/modules/contrib/search/search_api/includes/callback_bundle_filter.inc


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -303,8 +303,9 @@ function search_api_admin_add_server_validate(array $form, array &$form_state) {
     return;
     return;
   }
   }
   $form_state['values']['options']['service'] = $service;
   $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()
  * @see search_api_admin_server_edit_submit()
  */
  */
 function search_api_admin_server_edit_validate(array $form, array &$form_state) {
 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
  * @ingroup forms
  *
  *
+ * @see search_api_admin_add_index_ajax_callback()
  * @see search_api_admin_add_index_validate()
  * @see search_api_admin_add_index_validate()
  * @see search_api_admin_add_index_submit()
  * @see search_api_admin_add_index_submit()
  */
  */
 function search_api_admin_add_index(array $form, array &$form_state) {
 function search_api_admin_add_index(array $form, array &$form_state) {
   drupal_set_title(t('Add index'));
   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['#attached']['css'][] = drupal_get_path('module', 'search_api') . '/search_api.admin.css';
   $form['#tree'] = TRUE;
   $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(
   $form['submit'] = array(
@@ -769,22 +815,33 @@ function search_api_admin_add_index(array $form, array &$form_state) {
   return $form;
   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().
  * Form validation handler for search_api_admin_add_index().
  *
  *
  * @see search_api_admin_add_index_submit()
  * @see search_api_admin_add_index_submit()
  */
  */
 function search_api_admin_add_index_validate(array $form, array &$form_state) {
 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)) {
   if (is_numeric($name)) {
     form_set_error('machine_name', t('The machine name must not be a pure number.'));
     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) {
 function search_api_admin_add_index_submit(array $form, array &$form_state) {
   form_state_values_clean($form_state);
   form_state_values_clean($form_state);
-
   $values = $form_state['values'];
   $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.
   // SearchApiIndex::save() method.
   search_api_index_insert($values);
   search_api_index_insert($values);
 
 
@@ -857,6 +938,7 @@ function search_api_admin_index_view(SearchApiIndex $index, $action = NULL) {
     '#machine_name' => $index->machine_name,
     '#machine_name' => $index->machine_name,
     '#description' => $index->description,
     '#description' => $index->description,
     '#item_type' => $index->item_type,
     '#item_type' => $index->item_type,
+    '#datasource_config' => $index->datasource()->getConfigurationSummary($index),
     '#enabled' => $index->enabled,
     '#enabled' => $index->enabled,
     '#server' => $server,
     '#server' => $server,
     '#options' => $index->options,
     '#options' => $index->options,
@@ -889,6 +971,7 @@ function search_api_admin_index_view(SearchApiIndex $index, $action = NULL) {
  *   - machine_name: The index' machine name.
  *   - machine_name: The index' machine name.
  *   - description: The index' description.
  *   - description: The index' description.
  *   - item_type: The type of items stored in this index.
  *   - 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.
  *   - enabled: Boolean indicating whether the index is enabled.
  *   - server: The server this index currently rests on, if any.
  *   - server: The server this index currently rests on, if any.
  *   - options: The index' options, like cron limit.
  *   - options: The index' options, like cron limit.
@@ -912,6 +995,7 @@ function theme_search_api_index(array $variables) {
   $description = $variables['description'];
   $description = $variables['description'];
   $enabled = $variables['enabled'];
   $enabled = $variables['enabled'];
   $item_type = $variables['item_type'];
   $item_type = $variables['item_type'];
+  $datasource_config = $variables['datasource_config'];
   $server = $variables['server'];
   $server = $variables['server'];
   $options = $variables['options'];
   $options = $variables['options'];
   $status = $variables['status'];
   $status = $variables['status'];
@@ -962,6 +1046,12 @@ function theme_search_api_index(array $variables) {
   $info = check_plain($item_type);
   $info = check_plain($item_type);
   $rows[] = _search_api_deep_copy($row);
   $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) {
   if ($server) {
     $label = t('Server');
     $label = t('Server');
     $info = l($server->name, 'admin/config/search/search_api/server/' . $server->machine_name);
     $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
  * @ingroup forms
  *
  *
+ * @see search_api_admin_index_edit_validate()
  * @see search_api_admin_index_edit_submit()
  * @see search_api_admin_index_edit_submit()
  */
  */
 function search_api_admin_index_edit(array $form, array &$form_state, SearchApiIndex $index) {
 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,
     '#required' => TRUE,
   );
   );
   try {
   try {
-    $enabled_fixed = !$index->enabled && !$index->server();
+    $enabled_fixed = !$index->server();
   }
   }
   catch (Exception $e) {
   catch (Exception $e) {
     watchdog_exception('search_api', $e);
     watchdog_exception('search_api', $e);
@@ -1220,7 +1311,7 @@ function search_api_admin_index_edit(array $form, array &$form_state, SearchApiI
     '#type' => 'checkbox',
     '#type' => 'checkbox',
     '#title' => t('Enabled'),
     '#title' => t('Enabled'),
     '#default_value' => $index->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,
     '#disabled' => $enabled_fixed,
   );
   );
   $form['description'] = array(
   $form['description'] = array(
@@ -1240,6 +1331,15 @@ function search_api_admin_index_edit(array $form, array &$form_state, SearchApiI
   foreach ($servers as $server) {
   foreach ($servers as $server) {
     $form['server']['#options'][$server->machine_name] = $server->name;
     $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(
   $form['read_only'] = array(
     '#type' => 'checkbox',
     '#type' => 'checkbox',
     '#title' => t('Read only'),
     '#title' => t('Read only'),
@@ -1285,14 +1385,33 @@ function search_api_admin_index_edit(array $form, array &$form_state, SearchApiI
   return $form;
   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().
  * 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) {
 function search_api_admin_index_edit_submit(array $form, array &$form_state) {
   form_state_values_clean($form_state);
   form_state_values_clean($form_state);
-
   $values = $form_state['values'];
   $values = $form_state['values'];
+  /** @var SearchApiIndex $index */
   $index = $form_state['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;
   $values['options'] += $index->options;
 
 
   $ret = $index->update($values);
   $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.
   // An array of option arrays for types, keyed by nesting level.
   $types = array(0 => search_api_field_types());
   $types = array(0 => search_api_field_types());
   $entity_types = entity_get_info();
   $entity_types = entity_get_info();
-  // $boosts = drupal_map_assoc(array('0.1', '0.2', '0.3', '0.5', '0.8', '1.0', '2.0', '3.0', '5.0', '8.0', '13.0', '21.0'));
-  $boosts = drupal_map_assoc(array('0.1', '0.2', '0.3', '0.5', '0.8', '1.0', '2.0', '3.0', '5.0', '8.0', '13.0', '21.0', '100', '1000', '1010', '1020', '1030', '1040', '1050', '1060'));
-
-
+  $boosts = drupal_map_assoc(array('0.0', '0.1', '0.2', '0.3', '0.5', '0.8', '1.0', '2.0', '3.0', '5.0', '8.0', '13.0', '21.0'));
 
 
   $fulltext_types = array(0 => array('text'));
   $fulltext_types = array(0 => array('text'));
   // Add all custom data types with fallback "text" to fulltext types as well.
   // Add all custom data types with fallback "text" to fulltext types as well.
@@ -1833,6 +1949,7 @@ function search_api_admin_index_fields(array $form, array &$form_state, SearchAp
   );
   );
 
 
   if ($additional) {
   if ($additional) {
+    asort($additional);
     reset($additional);
     reset($additional);
     $form['additional'] = array(
     $form['additional'] = array(
       '#type' => 'fieldset',
       '#type' => 'fieldset',
@@ -1986,6 +2103,9 @@ function _search_api_admin_get_fields(SearchApiIndex $index, EntityMetadataWrapp
  * @param array $variables
  * @param array $variables
  *   An associative array containing:
  *   An associative array containing:
  *   - element: A render element representing the form.
  *   - element: A render element representing the form.
+ *
+ * @return string
+ *   The HTML for a field list form.
  */
  */
 function theme_search_api_admin_fields_table($variables) {
 function theme_search_api_admin_fields_table($variables) {
   $form = $variables['element'];
   $form = $variables['element'];
@@ -2031,6 +2151,7 @@ function theme_search_api_admin_fields_table($variables) {
 function search_api_admin_index_fields_submit(array $form, array &$form_state) {
 function search_api_admin_index_fields_submit(array $form, array &$form_state) {
   $index = $form_state['index'];
   $index = $form_state['index'];
   $options = isset($index->options) ? $index->options : array();
   $options = isset($index->options) ? $index->options : array();
+  $index_path = 'admin/config/search/search_api/index/' . $index->machine_name;
   if ($form_state['values']['op'] == t('Save changes')) {
   if ($form_state['values']['op'] == t('Save changes')) {
     $fields = $form_state['values']['fields'];
     $fields = $form_state['values']['fields'];
     $default_types = search_api_default_field_types();
     $default_types = search_api_default_field_types();
@@ -2060,18 +2181,18 @@ function search_api_admin_index_fields_submit(array $form, array &$form_state) {
     $ret = $index->update(array('options' => $options));
     $ret = $index->update(array('options' => $options));
 
 
     if ($ret) {
     if ($ret) {
-      drupal_set_message(t('The indexed fields were successfully changed. ' .
-          'The index was cleared and will have to be re-indexed with the new settings.'));
+      $vars = array('@url' => $index_path);
+      drupal_set_message(t('The indexed fields were successfully changed. The index was cleared and will have to be <a href="@url">re-indexed</a> with the new settings.', $vars));
     }
     }
     else {
     else {
       drupal_set_message(t('No values were changed.'));
       drupal_set_message(t('No values were changed.'));
     }
     }
     if (isset($index->options['data_alter_callbacks']) || isset($index->options['processors'])) {
     if (isset($index->options['data_alter_callbacks']) || isset($index->options['processors'])) {
-      $form_state['redirect'] = 'admin/config/search/search_api/index/' . $index->machine_name . '/fields';
+      $form_state['redirect'] = $index_path . '/fields';
     }
     }
     else {
     else {
       drupal_set_message(t('Please set up the indexing workflow.'));
       drupal_set_message(t('Please set up the indexing workflow.'));
-      $form_state['redirect'] = 'admin/config/search/search_api/index/' . $index->machine_name . '/workflow';
+      $form_state['redirect'] = $index_path . '/workflow';
     }
     }
     return;
     return;
   }
   }
@@ -2086,7 +2207,7 @@ function search_api_admin_index_fields_submit(array $form, array &$form_state) {
   else {
   else {
     drupal_set_message(t('No values were changed.'));
     drupal_set_message(t('No values were changed.'));
   }
   }
-  $form_state['redirect'] = 'admin/config/search/search_api/index/' . $index->machine_name . '/fields';
+  $form_state['redirect'] = $index_path . '/fields';
 }
 }
 
 
 /**
 /**

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

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

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

@@ -71,9 +71,10 @@ function search_api_drush_command() {
       'drush sapi-i default_node_index' => dt('Index items for the index with the machine name !name.', array('!name' => 'default_node_index')),
       'drush sapi-i default_node_index' => dt('Index items for the index with the machine name !name.', array('!name' => 'default_node_index')),
       'drush sapi-i 1 100' => dt("Index a maximum number of !limit items (index's cron batch size items per batch run) for the index with the ID !id.", array('!limit' => 100, '!id' => 1)),
       'drush sapi-i 1 100' => dt("Index a maximum number of !limit items (index's cron batch size items per batch run) for the index with the ID !id.", array('!limit' => 100, '!id' => 1)),
       'drush sapi-i 1 100 10' => dt("Index a maximum number of !limit items (!batch_size items per batch run) for the index with the ID !id.", array('!limit' => 100, '!batch_size' => 10, '!id' => 1)),
       'drush sapi-i 1 100 10' => dt("Index a maximum number of !limit items (!batch_size items per batch run) for the index with the ID !id.", array('!limit' => 100, '!batch_size' => 10, '!id' => 1)),
+      'drush sapi-i 0 0 100' => dt("Index all items of all indexes with !batch_size items per batch run.", array('!batch_size' => 100)),
     ),
     ),
     'arguments' => array(
     'arguments' => array(
-      'index_id' => dt('The numeric ID or machine name of an index.'),
+      'index_id' => dt('The numeric ID or machine name of an index. Set to 0 to index all indexes. Defaults to 0 (index all).'),
       'limit' => dt("The number of items to index (index's cron batch size items per run). Set to 0 to index all items. Defaults to 0 (index all)."),
       'limit' => dt("The number of items to index (index's cron batch size items per run). Set to 0 to index all items. Defaults to 0 (index all)."),
       'batch_size' => dt("The number of items to index per batch run. Set to 0 to index all items at once. Defaults to the index's cron batch size."),
       'batch_size' => dt("The number of items to index per batch run. Set to 0 to index all items at once. Defaults to the index's cron batch size."),
     ),
     ),
@@ -121,6 +122,39 @@ function search_api_drush_command() {
     'aliases' => array('sapi-sis'),
     '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;
   return $items;
 }
 }
 
 
@@ -293,6 +327,7 @@ function drush_search_api_index($index_id = NULL, $limit = NULL, $batch_size = N
   if (search_api_drush_static(__FUNCTION__)) {
   if (search_api_drush_static(__FUNCTION__)) {
     return;
     return;
   }
   }
+  $index_id = !empty($index_id) ? $index_id : NULL;
   $indexes = search_api_drush_get_index($index_id);
   $indexes = search_api_drush_get_index($index_id);
   if (empty($indexes)) {
   if (empty($indexes)) {
     return;
     return;
@@ -508,7 +543,8 @@ function search_api_drush_get_server($server_id = NULL) {
   $servers = search_api_server_load_multiple($ids);
   $servers = search_api_server_load_multiple($ids);
   if (empty($servers)) {
   if (empty($servers)) {
     drush_set_error(dt('Invalid server_id or no servers present.'));
     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;
   return $servers;
 }
 }
@@ -533,3 +569,75 @@ function search_api_drush_static($function) {
   $index[$function] = TRUE;
   $index[$function] = TRUE;
   return FALSE;
   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_access.inc
 files[] = includes/callback_node_status.inc
 files[] = includes/callback_node_status.inc
 files[] = includes/callback_role_filter.inc
 files[] = includes/callback_role_filter.inc
+files[] = includes/callback_user_status.inc
 files[] = includes/datasource.inc
 files[] = includes/datasource.inc
 files[] = includes/datasource_entity.inc
 files[] = includes/datasource_entity.inc
 files[] = includes/datasource_external.inc
 files[] = includes/datasource_external.inc
+files[] = includes/datasource_multiple.inc
 files[] = includes/exception.inc
 files[] = includes/exception.inc
 files[] = includes/index_entity.inc
 files[] = includes/index_entity.inc
 files[] = includes/processor.inc
 files[] = includes/processor.inc
@@ -34,9 +36,9 @@ files[] = includes/service.inc
 
 
 configure = admin/config/search/search_api
 configure = admin/config/search/search_api
 
 
-; Information added by Drupal.org packaging script on 2014-12-26
-version = "7.x-1.14"
+; Information added by Drupal.org packaging script on 2016-02-26
+version = "7.x-1.16+29-dev"
 core = "7.x"
 core = "7.x"
 project = "search_api"
 project = "search_api"
-datestamp = "1419580682"
+datestamp = "1456500713"
 
 

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

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

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

@@ -2,7 +2,7 @@
 
 
 /**
 /**
  * @file
  * @file
- * Provides a flexible framework for implementing search servives.
+ * Provides a flexible framework for implementing search services.
  */
  */
 
 
 /**
 /**
@@ -270,6 +270,7 @@ function search_api_theme() {
       'machine_name' => '',
       'machine_name' => '',
       'description' => NULL,
       'description' => NULL,
       'item_type' => NULL,
       'item_type' => NULL,
+      'datasource_config' => NULL,
       'enabled' => NULL,
       'enabled' => NULL,
       'server' => NULL,
       'server' => NULL,
       'options' => array(),
       'options' => array(),
@@ -646,8 +647,9 @@ function search_api_search_api_index_insert(SearchApiIndex $index) {
  * Implements hook_search_api_index_update().
  * Implements hook_search_api_index_update().
  */
  */
 function search_api_search_api_index_update(SearchApiIndex $index) {
 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');
+  search_api_index_update_datasource($index, 'search_api_item_string_id');
 
 
   // If the server was changed, we have to call the appropriate service class
   // If the server was changed, we have to call the appropriate service class
   // hook methods.
   // hook methods.
@@ -856,6 +858,8 @@ function search_api_entity_insert($entity, $type) {
   list($id) = entity_extract_ids($type, $entity);
   list($id) = entity_extract_ids($type, $entity);
   if (isset($id)) {
   if (isset($id)) {
     search_api_track_item_insert($type, array($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
  * datasource controller and calls search_api_track_item_change() for the
  * updated items.
  * updated items.
  *
  *
+ * It also checks whether the entity's bundle changed and acts accordingly.
+ *
  * @see search_api_search_api_item_type_info()
  * @see search_api_search_api_item_type_info()
  */
  */
 function search_api_entity_update($entity, $type) {
 function search_api_entity_update($entity, $type) {
@@ -874,9 +880,20 @@ function search_api_entity_update($entity, $type) {
   if (!entity_get_property_info($type)) {
   if (!entity_get_property_info($type)) {
     return;
     return;
   }
   }
-  list($id) = entity_extract_ids($type, $entity);
+  list($id, , $new_bundle) = entity_extract_ids($type, $entity);
+
+  // Check if the entity's bundle changed.
+  if (!empty($entity->original)) {
+    list(, , $old_bundle) = entity_extract_ids($type, $entity->original);
+    if ($new_bundle != $old_bundle) {
+      _search_api_entity_datasource_bundle_change($type, $id, $old_bundle, $new_bundle);
+    }
+  }
+
   if (isset($id)) {
   if (isset($id)) {
     search_api_track_item_change($type, array($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);
   list($id) = entity_extract_ids($type, $entity);
   if (isset($id)) {
   if (isset($id)) {
     search_api_track_item_delete($type, array($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() {
 function search_api_search_api_item_type_info() {
   $types = array();
   $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;
   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.'),
     'description' => t('Exclude unpublished nodes from the index. <strong>Caution:</strong> This only affects the indexed nodes themselves. If an enabled node has references to disabled nodes, those will still be indexed (or displayed) normally.'),
     'class' => 'SearchApiAlterNodeStatus',
     'class' => 'SearchApiAlterNodeStatus',
   );
   );
+  $callbacks['search_api_alter_user_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;
   return $callbacks;
 }
 }
@@ -1100,7 +1147,10 @@ function search_api_track_item_insert($type, array $item_ids) {
   }
   }
 
 
   try {
   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) {
   catch (SearchApiException $e) {
     $vars['%item_type'] = $type;
     $vars['%item_type'] = $type;
@@ -1137,7 +1187,10 @@ function search_api_track_item_change($type, array $item_ids) {
     return;
     return;
   }
   }
   try {
   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) {
     foreach ($indexes as $index) {
       if (!empty($index->options['index_directly'])) {
       if (!empty($index->options['index_directly'])) {
         // For indexes with the index_directly option set, queue the items to be
         // 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);
   $indexes = search_api_index_load_multiple(FALSE, $conditions);
   if ($indexes) {
   if ($indexes) {
     try {
     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) {
     catch (SearchApiException $e) {
       $vars['%item_type'] = $type;
       $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
   // Then, delete it from all servers. Servers of disabled indexes have to be
   // considered, too!
   // 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 {
     try {
       if ($server = $index->server()) {
       if ($server = $index->server()) {
         $server->deleteItems($item_ids, $index);
         $server->deleteItems($item_ids, $index);
@@ -2043,8 +2100,8 @@ function _search_api_query_add_node_access($account, SearchApiQueryInterface $qu
  * @param array $allowed
  * @param array $allowed
  *   Optionally, an array of allowed types.
  *   Optionally, an array of allowed types.
  *
  *
- * @return
- *   TRUE if $type is either one of the specified types, or a list of such
+ * @return bool
+ *   TRUE if $type is either one of the specified types or a list of such
  *   values. FALSE otherwise.
  *   values. FALSE otherwise.
  *
  *
  * @see search_api_extract_inner_type()
  * @see search_api_extract_inner_type()
@@ -2060,7 +2117,7 @@ function search_api_is_text_type($type, array $allowed = array('text')) {
  * @param $type
  * @param $type
  *   A string containing the type to check.
  *   A string containing the type to check.
  *
  *
- * @return
+ * @return bool
  *   TRUE iff $type is a list type ("list<*>").
  *   TRUE iff $type is a list type ("list<*>").
  */
  */
 function search_api_is_list_type($type) {
 function search_api_is_list_type($type) {
@@ -2073,7 +2130,7 @@ function search_api_is_list_type($type) {
  * @param $type
  * @param $type
  *   A string containing the type to check.
  *   A string containing the type to check.
  *
  *
- * @return
+ * @return int
  *   The nesting level of the type. 0 for singular types, 1 for lists of
  *   The nesting level of the type. 0 for singular types, 1 for lists of
  *   singular types, etc.
  *   singular types, etc.
  */
  */
@@ -2100,7 +2157,7 @@ function search_api_list_nesting_level($type) {
  * @param $nested_type
  * @param $nested_type
  *   Another type, determining the nesting level.
  *   Another type, determining the nesting level.
  *
  *
- * @return
+ * @return string
  *   A list version of $type, as specified above.
  *   A list version of $type, as specified above.
  */
  */
 function search_api_nest_type($type, $nested_type) {
 function search_api_nest_type($type, $nested_type) {
@@ -2117,7 +2174,7 @@ function search_api_nest_type($type, $nested_type) {
  * @param $type
  * @param $type
  *   A string containing the list type to process.
  *   A string containing the list type to process.
  *
  *
- * @return
+ * @return string
  *   A string containing the primitive type contained within the list, e.g.
  *   A string containing the primitive type contained within the list, e.g.
  *   "text" for "list<text>" (or for "list<list<text>>"). If $type is no list
  *   "text" for "list<text>" (or for "list<list<text>>"). If $type is no list
  *   type, it is returned unchanged.
  *   type, it is returned unchanged.
@@ -2139,7 +2196,7 @@ function search_api_extract_inner_type($type) {
  *
  *
  * Modules implementing other datasource controllers, that use a table other
  * Modules implementing other datasource controllers, that use a table other
  * than {search_api_item}, can use this function, too. It should be called
  * than {search_api_item}, can use this function, too. It should be called
- * uncoditionally in a hook_search_api_index_update() implementation. If this
+ * unconditionally in a hook_search_api_index_update() implementation. If this
  * function isn't used, similar code should be added there.
  * function isn't used, similar code should be added there.
  *
  *
  * However, note that this is only necessary (and this function should only be
  * However, note that this is only necessary (and this function should only be
@@ -2177,6 +2234,9 @@ function search_api_index_update_datasource(SearchApiIndex $index, $table, $colu
  *   The $fields array with additional "value" and "original_type" keys set.
  *   The $fields array with additional "value" and "original_type" keys set.
  */
  */
 function search_api_extract_fields(EntityMetadataWrapper $wrapper, array $fields, array $value_options = array()) {
 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.
   // If $wrapper is a list of entities, we have to aggregate their field values.
   $wrapper_info = $wrapper->info();
   $wrapper_info = $wrapper->info();
   if (search_api_is_list_type($wrapper_info['type'])) {
   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'];
       $info['original_type'] = $info['type'];
       if (isset($wrapper->$field)) {
       if (isset($wrapper->$field)) {
         try {
         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();
           $property_info = $wrapper->$field->info();
           $info['original_type'] = $property_info['type'];
           $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) {
         catch (EntityMetadataWrapperException $e) {
@@ -2245,10 +2317,6 @@ function search_api_extract_fields(EntityMetadataWrapper $wrapper, array $fields
   foreach ($nested as $prefix => $nested_fields) {
   foreach ($nested as $prefix => $nested_fields) {
     if (isset($wrapper->$prefix)) {
     if (isset($wrapper->$prefix)) {
       $nested_fields = search_api_extract_fields($wrapper->$prefix, $nested_fields, $value_options);
       $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) {
       foreach ($nested_fields as $field => $info) {
         $fields["$prefix:$field"] = $info;
         $fields["$prefix:$field"] = $info;
       }
       }
@@ -2385,7 +2453,7 @@ function search_api_access_disable_page(Entity $entity) {
  * @param Entity $entity
  * @param Entity $entity
  *   The server or index for which the access to the delete page is checked.
  *   The server or index for which the access to the delete page is checked.
  *
  *
- * @return
+ * @return bool
  *   TRUE if the delete page can be accessed by the user, FALSE otherwise.
  *   TRUE if the delete page can be accessed by the user, FALSE otherwise.
  */
  */
 function search_api_access_delete_page(Entity $entity) {
 function search_api_access_delete_page(Entity $entity) {
@@ -2500,7 +2568,7 @@ function search_api_server_clear($id) {
  * @param $id
  * @param $id
  *   The ID or machine name of the server to delete.
  *   The ID or machine name of the server to delete.
  *
  *
- * @return
+ * @return int|false
  *   1 on success, 0 or FALSE on failure.
  *   1 on success, 0 or FALSE on failure.
  */
  */
 function search_api_server_delete($id) {
 function search_api_server_delete($id) {
@@ -2711,7 +2779,7 @@ function search_api_index_disable($id) {
  * @param $id
  * @param $id
  *   The ID or machine name of the index to re-index.
  *   The ID or machine name of the index to re-index.
  *
  *
- * @return
+ * @return bool
  *   TRUE on success, FALSE on failure.
  *   TRUE on success, FALSE on failure.
  */
  */
 function search_api_index_reindex($id) {
 function search_api_index_reindex($id) {
@@ -2735,7 +2803,7 @@ function _search_api_index_reindex(SearchApiIndex $index) {
  * @param $id
  * @param $id
  *   The ID or machine name of the index to clear.
  *   The ID or machine name of the index to clear.
  *
  *
- * @return
+ * @return bool
  *   TRUE on success, FALSE on failure.
  *   TRUE on success, FALSE on failure.
  */
  */
 function search_api_index_clear($id) {
 function search_api_index_clear($id) {
@@ -2749,7 +2817,7 @@ function search_api_index_clear($id) {
  * @param $id
  * @param $id
  *   The ID or machine name of the index to delete.
  *   The ID or machine name of the index to delete.
  *
  *
- * @return
+ * @return bool
  *   TRUE on success, FALSE on failure.
  *   TRUE on success, FALSE on failure.
  */
  */
 function search_api_index_delete($id) {
 function search_api_index_delete($id) {
@@ -2778,6 +2846,66 @@ function search_api_index_options_list() {
   return $ret;
   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.
  * Shutdown function which indexes all queued items, if any.
  */
  */
@@ -2894,6 +3022,42 @@ function _search_api_deep_copy(array $array) {
   return $copy;
   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.
  * Creates and sets a batch for indexing items.
  *
  *
@@ -3010,7 +3174,7 @@ function _search_api_batch_indexing_callback(SearchApiIndex $index, $batch_size,
  * @param boolean $success
  * @param boolean $success
  *   Whether the batch finished successfully.
  *   Whether the batch finished successfully.
  * @param array $results
  * @param array $results
- *   Detailed informations about the result.
+ *   Detailed information about the result.
  */
  */
 function _search_api_batch_indexing_finished($success, $results) {
 function _search_api_batch_indexing_finished($success, $results) {
   // Check if called from drush.
   // Check if called from drush.

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

@@ -830,6 +830,7 @@ class SearchApiUnitTest extends DrupalWebTestCase {
     $this->index = entity_create('search_api_index', array(
     $this->index = entity_create('search_api_index', array(
       'id' => 1,
       'id' => 1,
       'name' => 'test',
       'name' => 'test',
+      'machine_name' => 'test',
       'enabled' => 1,
       'enabled' => 1,
       'item_type' => 'user',
       'item_type' => 'user',
       'options' => array(
       'options' => array(
@@ -859,6 +860,7 @@ class SearchApiUnitTest extends DrupalWebTestCase {
     $this->checkIgnoreCaseProcessor();
     $this->checkIgnoreCaseProcessor();
     $this->checkTokenizer();
     $this->checkTokenizer();
     $this->checkHtmlFilter();
     $this->checkHtmlFilter();
+    $this->checkEntityDatasource();
   }
   }
 
 
   /**
   /**
@@ -1122,4 +1124,75 @@ END;
     $this->assertEqual($tmp[1]['name']['value'], $processed1, 'Text was correctly processed.');
     $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
 hidden = TRUE
 
 
-; Information added by Drupal.org packaging script on 2014-12-26
-version = "7.x-1.14"
+; Information added by Drupal.org packaging script on 2016-02-26
+version = "7.x-1.16+29-dev"
 core = "7.x"
 core = "7.x"
 project = "search_api"
 project = "search_api"
-datestamp = "1419580682"
+datestamp = "1456500713"
 
 

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

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

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

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

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

@@ -2,12 +2,10 @@ Solr search
 -----------
 -----------
 
 
 This module provides an implementation of the Search API which uses an Apache
 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
 Supported optional features
 ---------------------------
 ---------------------------
@@ -139,6 +137,11 @@ Hidden variables
   what you are doing. Old indexed items will be lost when the hash is changed
   what you are doing. Old indexed items will be lost when the hash is changed
   and all items will have to be reindexed. Can only contain alphanumeric
   and all items will have to be reindexed. Can only contain alphanumeric
   characters.
   characters.
+- search_api_solr_highlight_prefix (default: "tm_")
+  The prefix of Solr fields for which field-level highlighting will be enabled.
+  Since the prefix of fields is used to determine the field type (by default),
+  this lets you enable highlighting for other field types. By default,
+  highlighting will be possible for all fulltext fields.
 
 
 [8] http://wiki.apache.org/solr/UpdateXmlMessages#A.22commit.22_and_.22optimize.22
 [8] http://wiki.apache.org/solr/UpdateXmlMessages#A.22commit.22_and_.22optimize.22
 
 

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

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

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

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

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

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

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

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

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

@@ -161,6 +161,24 @@ function search_api_solr_hook_search_api_data_type_info() {
   );
   );
 }
 }
 
 
+/**
+ * Alter autocomplete suggestions returned from Solr servers.
+ *
+ * @param array $suggestions
+ *   An array of suggestions to be altered, in the structure documented in
+ *   SearchApiAutocompleteSuggesterInterface::getAutocompleteSuggestions().
+ * @param array $alter_data
+ *   An associative array of data about the search, with the following keys:
+ *   "search", "query", "incomplete_key", "user_input", which correspond to the
+ *   arguments to SearchApiAutocompleteInterface::getAutocompleteSuggestions();
+ *   and "responses", an array containing the Solr response objects used for
+ *   constructing the suggestions.
+ */
+function hook_search_api_solr_autocomplete_suggestions_alter(array &$suggestions, array &$alter_data) {
+  // Always also suggest the original user input.
+  array_unshift($suggestions, trim($alter_data['user_input']));
+}
+
 /**
 /**
  * @} End of "addtogroup hooks".
  * @} 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/solr_field.inc
 files[] = includes/spellcheck.inc
 files[] = includes/spellcheck.inc
 
 
-; Information added by Drupal.org packaging script on 2014-09-08
-version = "7.x-1.6"
+; Information added by Drupal.org packaging script on 2016-02-23
+version = "7.x-1.9+10-dev"
 core = "7.x"
 core = "7.x"
 project = "search_api_solr"
 project = "search_api_solr"
-datestamp = "1410186051"
+datestamp = "1456233244"
 
 

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

@@ -10,7 +10,7 @@
  http://wiki.apache.org/solr/SchemaXml
  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.
     <!-- 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.
          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
          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.SnowballPorterFilterFactory" language="English" protected="protwords.txt"/>
         <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
         <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
       </analyzer>
       </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>
     </fieldType>
 
 
     <!-- An unstemmed text field - good if one does not know the language of the field -->
     <!-- 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.LengthFilterFactory" min="2" max="100" />
         <filter class="solr.LowerCaseFilterFactory"/>
         <filter class="solr.LowerCaseFilterFactory"/>
       </analyzer>
       </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>
     </fieldType>
 
 
     <!-- Edge N gram type - for example for matching against queries with results
     <!-- 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
      For more details about configurations options that may appear in
      this file, see http://wiki.apache.org/solr/SolrConfigXml.
      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
   <!-- In all configuration below, a prefix of "solr." for class names
        is an alias that causes solr to search appropriate packages,
        is an alias that causes solr to search appropriate packages,
        including org.apache.solr.(search|update|request|core|analysis)
        including org.apache.solr.(search|update|request|core|analysis)
@@ -871,6 +871,7 @@
     </lst>
     </lst>
     <arr name="last-components">
     <arr name="last-components">
       <str>spellcheck</str>
       <str>spellcheck</str>
+      <str>elevator</str>
     </arr>
     </arr>
   </requestHandler>
   </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
  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.
     <!-- 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.
          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
          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.SnowballPorterFilterFactory" language="English" protected="protwords.txt"/>
         <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
         <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
       </analyzer>
       </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>
     </fieldType>
 
 
     <!-- An unstemmed text field - good if one does not know the language of the field -->
     <!-- 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.LengthFilterFactory" min="2" max="100" />
         <filter class="solr.LowerCaseFilterFactory"/>
         <filter class="solr.LowerCaseFilterFactory"/>
       </analyzer>
       </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>
     </fieldType>
 
 
     <!-- Edge N gram type - for example for matching against queries with results
     <!-- 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
      For more details about configurations options that may appear in
      this file, see http://wiki.apache.org/solr/SolrConfigXml.
      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
   <!-- In all configuration below, a prefix of "solr." for class names
        is an alias that causes solr to search appropriate packages,
        is an alias that causes solr to search appropriate packages,
        including org.apache.solr.(search|update|request|core|analysis)
        including org.apache.solr.(search|update|request|core|analysis)
@@ -880,6 +880,7 @@
     </lst>
     </lst>
     <arr name="last-components">
     <arr name="last-components">
       <str>spellcheck</str>
       <str>spellcheck</str>
+      <str>elevator</str>
     </arr>
     </arr>
   </requestHandler>
   </requestHandler>
 
 
@@ -1068,10 +1069,17 @@
     <lst name="invariants">
     <lst name="invariants">
       <str name="qt">pinkPony</str>
       <str name="qt">pinkPony</str>
       <str name="q">solrpingquery</str>
       <str name="q">solrpingquery</str>
+      <str name="omitHeader">false</str>
     </lst>
     </lst>
     <lst name="defaults">
     <lst name="defaults">
       <str name="echoParams">all</str>
       <str name="echoParams">all</str>
     </lst>
     </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>
   </requestHandler>
 
 
   <!-- Echo the request contents back to the client -->
   <!-- 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.)

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio