From ca9413af4d29a83bb376def744f158f36454c0b8 Mon Sep 17 00:00:00 2001 From: Bachir Soussi Chiadmi Date: Mon, 20 Apr 2015 19:18:35 +0200 Subject: [PATCH] updated and repatched search_api module please check this thread : Decide on strategy for language aware search https://www.drupal.org/node/1393058 --- .../search_api/0001-re-added-own-boosts.patch | 28 +++ ...ation-bug-added-reference-to-bug-fix.patch | 28 +++ ...-previous-patches-i-commented-the-ne.patch | 25 ++ .../search/search_api/0004-Icons.patch | 50 ++++ .../contrib/search/search_api/CHANGELOG.txt | 67 +++++ .../search_api/boosts-and-queryconditon.patch | 39 --- .../plugins/facetapi/query_type_date.inc | 152 +++++++---- .../plugins/facetapi/query_type_term.inc | 21 +- .../search_api_facetapi.info | 6 +- .../search_api_facetapi.module | 160 +++++++++++- .../includes/handler_argument_fulltext.inc | 8 +- .../handler_argument_more_like_this.inc | 38 +-- .../includes/handler_filter_entity.inc | 23 +- .../includes/handler_filter_fulltext.inc | 56 ++++- .../includes/handler_filter_language.inc | 22 +- .../includes/handler_sort.inc | 17 +- .../includes/plugin_cache.inc | 23 +- .../search_api_views/includes/query.inc | 16 +- .../search_api_views/search_api_views.info | 6 +- .../search_api_views.views.inc | 39 ++- .../includes/callback_add_aggregation.inc | 11 + .../search_api/includes/index_entity.inc | 116 ++++++--- .../includes/processor_highlight.inc | 95 +++++-- .../includes/processor_html_filter.inc | 12 +- .../search/search_api/includes/query.inc | 57 ++++- .../search_api/includes/server_entity.inc | 4 +- .../search/search_api/search_api.admin.inc | 87 +++++-- .../search/search_api/search_api.api.php | 2 +- .../search/search_api/search_api.drush.inc | 235 ++++++++++++++---- .../contrib/search/search_api/search_api.info | 6 +- .../search/search_api/search_api.install | 13 +- .../search/search_api/search_api.module | 177 +++++++------ .../search/search_api/search_api.rules.inc | 4 + .../search_api/tests/search_api_test.info | 6 +- 34 files changed, 1272 insertions(+), 377 deletions(-) create mode 100644 sites/all/modules/contrib/search/search_api/0001-re-added-own-boosts.patch create mode 100644 sites/all/modules/contrib/search/search_api/0002-taxo-term-translation-bug-added-reference-to-bug-fix.patch create mode 100644 sites/all/modules/contrib/search/search_api/0003-NODE_PUBLISED-in-previous-patches-i-commented-the-ne.patch create mode 100644 sites/all/modules/contrib/search/search_api/0004-Icons.patch delete mode 100644 sites/all/modules/contrib/search/search_api/boosts-and-queryconditon.patch diff --git a/sites/all/modules/contrib/search/search_api/0001-re-added-own-boosts.patch b/sites/all/modules/contrib/search/search_api/0001-re-added-own-boosts.patch new file mode 100644 index 00000000..bfc155f8 --- /dev/null +++ b/sites/all/modules/contrib/search/search_api/0001-re-added-own-boosts.patch @@ -0,0 +1,28 @@ +From 6402fc7ab8f6343defbee7111dee7dd16a5082fc Mon Sep 17 00:00:00 2001 +From: Bachir Soussi Chiadmi +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 + diff --git a/sites/all/modules/contrib/search/search_api/0002-taxo-term-translation-bug-added-reference-to-bug-fix.patch b/sites/all/modules/contrib/search/search_api/0002-taxo-term-translation-bug-added-reference-to-bug-fix.patch new file mode 100644 index 00000000..efcdf4cf --- /dev/null +++ b/sites/all/modules/contrib/search/search_api/0002-taxo-term-translation-bug-added-reference-to-bug-fix.patch @@ -0,0 +1,28 @@ +From 54ee5c7b3a05850e15067d77a182cb8fe723d8e0 Mon Sep 17 00:00:00 2001 +From: Bachir Soussi Chiadmi +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 + diff --git a/sites/all/modules/contrib/search/search_api/0003-NODE_PUBLISED-in-previous-patches-i-commented-the-ne.patch b/sites/all/modules/contrib/search/search_api/0003-NODE_PUBLISED-in-previous-patches-i-commented-the-ne.patch new file mode 100644 index 00000000..186ebbb3 --- /dev/null +++ b/sites/all/modules/contrib/search/search_api/0003-NODE_PUBLISED-in-previous-patches-i-commented-the-ne.patch @@ -0,0 +1,25 @@ +From 85251183d6ad5fadaa65154e33e1e8ac8ca7f9b0 Mon Sep 17 00:00:00 2001 +From: Bachir Soussi Chiadmi +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 + diff --git a/sites/all/modules/contrib/search/search_api/0004-Icons.patch b/sites/all/modules/contrib/search/search_api/0004-Icons.patch new file mode 100644 index 00000000..8cf0e373 --- /dev/null +++ b/sites/all/modules/contrib/search/search_api/0004-Icons.patch @@ -0,0 +1,50 @@ +From c06be9a44ed0be31859a1800cf7f0ae6e8ae492a Mon Sep 17 00:00:00 2001 +From: Bachir Soussi Chiadmi +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)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@_ptZRUTXUQ0000Hz0Nf;@42I +zO+Xy_DRSR0DE}{rX8QO0Hvfw~_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 + diff --git a/sites/all/modules/contrib/search/search_api/CHANGELOG.txt b/sites/all/modules/contrib/search/search_api/CHANGELOG.txt index 605484d8..d1980c91 100644 --- a/sites/all/modules/contrib/search/search_api/CHANGELOG.txt +++ b/sites/all/modules/contrib/search/search_api/CHANGELOG.txt @@ -1,3 +1,70 @@ +Search API 1.14 (12/26/2014): +----------------------------- +- #2382385 by illusionuk, drunken monkey: Fixed error handling when using + invalid fulltext or sort field in Views. +- #2371099 by drunken monkey: Fixed display of active "Exclude" facets. +- #1861134 by Cyberwolf, jackbravo, drunken monkey: Fixed indexing on multiple + indexes with Drush. +- #2347367 by drunken monkey, das-peter: Fixed forgotten usages of + $index->item_type. +- #2359201 by drunken monkey: Added a "List" option to "Aggregated fields". +- #2364247 by drunken monkey: Fixed documentation for + SearchApiQueryFilterInterface::getFilters(). +- #2364875 by Xano: Fixed Views argument handler for fulltext fields. +- #2174163 by drunken monkey: Fixed detection of field type changes by data + alterations. +- #2305755 by drunken monkey, pfrenssen: Fixed invalidation of the stored index + fields cache. +- #2334727 by Alex Bukach, drunken monkey: Fixed Views caching does not take + items_per_page into account. +- #1372092 by drunken monkey: Added an error message when no service class is + available when creating a server. +- #2305627 by drunken monkey, cpliakas: Fixed date facets not displayed when + the configured granularity is larger than the calculated granularity. +- #2319263 by solotandem: Added easier way to subclass entity classes. +- #2278737 by drunken monkey: Fixed use of multiple Views fulltext search + filters. + +Search API 1.13 (07/23/2014): +----------------------------- +- #2281535 by areynolds, nicola85: Adapted to latest changes in Views cache + plugins. +- #2145547 by aaronbauman: Fixed duplicated sorts (one exposed) in Views. +- #2146435 by alanmackenzie: Fixed Views paging with custom pager add-ons. +- #2278791 by drunken monkey | tksmd: Fixed excerpt when searching single CJK + word. +- #2272983 by idflood, drunken monkey: Fixed Highlighting processor for queries + without returned results. +- #2216345 by bacardi55, fabianderijk, drunken monkey: Fixed array to string + conversion in Highlighting processor. + +Search API 1.12 (05/23/2014): +----------------------------- +- #2265349 by drunken monkey: Marked _search_api_settings_equals() as + deprecated. +- #2256891 by justanothermark: Fixed "0" entity labels. +- #2233749 by rjacobs, drunken monkey: Added drush support to change the server + used by an index. +- #2219553 by drunken monkey: Fixed Views fulltext filter operators. +- #2135697 by drunken monkey: Fixed handling of HTML attributes in the + Highlighting processor. +- #2179755 by drunken monkey, fago: Fixed whitespaces after HTML filter. +- #2204847 by drunken monkey, alanmackenzie: Fixed Views caching issues with + pagination. +- #2198791 by drunken monkey: Fixed empty Views entity filters. +- #2195469 by freakalis, drunken monkey: Added "Exclude fields" options to + Highlighting processor. +- #2169455 by drunken monkey: Fixed "undefined index" in + search_api_update_7116(). +- #2219563 by drunken monkey: Added __toString() methods for queries and + filters. +- #1888174 by drunken monkey, ipallian: Fixed problems with date facets. +- #2187487 by drunken monkey: Fixed admin summary of language filter. +- #2198261 by drunken monkey: Fixed fatal error on view editing. +- #2168713 by idebr: Fixed highlighting of keys containing slashes. +- #2150779 by hefox: Fixed "Overridden" detection for index features. +- #1227702 by drunken monkey: Improved error handling. + Search API 1.11 (12/25/2013): ----------------------------- - #1879196 by drunken monkey: Fixed invalid old indexes causing errors. diff --git a/sites/all/modules/contrib/search/search_api/boosts-and-queryconditon.patch b/sites/all/modules/contrib/search/search_api/boosts-and-queryconditon.patch deleted file mode 100644 index 35edba50..00000000 --- a/sites/all/modules/contrib/search/search_api/boosts-and-queryconditon.patch +++ /dev/null @@ -1,39 +0,0 @@ -diff --git a/search_api.admin.inc b/search_api.admin.inc -index 5fbc8d8..9a5122e 100644 ---- a/search_api.admin.inc -+++ b/search_api.admin.inc -@@ -1480,8 +1480,8 @@ function search_api_admin_index_fields(array $form, array &$form_state, SearchAp - $fulltext_type = array(0 => 'text'); - $entity_types = entity_get_info(); - $default_types = search_api_default_field_types(); -- $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', '5000', '10000', '20000', '40000', '80000', '160000', '320000')); -+ $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')); - $form_state['index'] = $index; - $form['#theme'] = 'search_api_admin_fields_table'; - $form['#tree'] = TRUE; -diff --git a/search_api.module b/search_api.module -index bba0681..ba27465 100644 ---- a/search_api.module -+++ b/search_api.module -@@ -1444,7 +1444,7 @@ function _search_api_query_add_node_access($account, SearchApiQueryInterface $qu - $query->filter($filter); - } - else { -- $query->condition('status', NODE_PUBLISHED); -+ // $query->condition('status', NODE_PUBLISHED); - } - // Filter by node access grants. - $filter = $query->createFilter('OR'); -@@ -1636,6 +1636,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; - } diff --git a/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_date.inc b/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_date.inc index b045fc8f..8a565302 100644 --- a/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_date.inc +++ b/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_date.inc @@ -57,14 +57,94 @@ class SearchApiFacetapiDate extends SearchApiFacetapiTerm implements FacetapiQue if ($active = $this->adapter->getActiveItems($this->facet)) { $item = end($active); $field = $this->facet['field']; - $regex = str_replace(array('^', '$'), '', FACETAPI_REGEX_DATE); - $filter = preg_replace_callback($regex, array($this, 'replaceDateString'), $item['value']); + $filter = $this->createRangeFilter($item['value']); $this->addFacetFilter($query, $field, $filter); } } + /** + * Rewrites the handler-specific date range syntax to the normal facet syntax. + * + * @param $value + * The user-facing facet value. + * + * @return string + * A facet to add as a filter, in the format used internally in this module. + */ + protected function createRangeFilter($value) { + // Gets the granularity. Ignore any filters passed directly from the server + // (range or missing). We always create filters starting with a year. + if (!$value || !ctype_digit($value[0])) { + return $value; + } + + $parts = explode('-', $value); + $date = new DateTime(); + switch (count($parts)) { + case 1: + $date->setDate($parts[0], 1, 1); + $date->setTime(0, 0, 0); + $lower = $date->format('U'); + $date->setDate($parts[0] + 1, 1, 1); + $date->setTime(0, 0, -1); + $upper = $date->format('U'); + break; + + case 2: + // Luckily, $month = 13 is treated as January of next year. (The same + // goes for all other parameters.) We use the inverse trick for the + // seconds of the upper bound, since that's inclusive and we want to + // stop at a second before the next segment starts. + $date->setDate($parts[0], $parts[1], 1); + $date->setTime(0, 0, 0); + $lower = $date->format('U'); + $date->setDate($parts[0], $parts[1] + 1, 1); + $date->setTime(0, 0, -1); + $upper = $date->format('U'); + break; + + case 3: + $date->setDate($parts[0], $parts[1], $parts[2]); + $date->setTime(0, 0, 0); + $lower = $date->format('U'); + $date->setDate($parts[0], $parts[1], $parts[2] + 1); + $date->setTime(0, 0, -1); + $upper = $date->format('U'); + break; + + case 4: + $date->setDate($parts[0], $parts[1], $parts[2]); + $date->setTime($parts[3], 0, 0); + $lower = $date->format('U'); + $date->setTime($parts[3] + 1, 0, -1); + $upper = $date->format('U'); + break; + + case 5: + $date->setDate($parts[0], $parts[1], $parts[2]); + $date->setTime($parts[3], $parts[4], 0); + $lower = $date->format('U'); + $date->setTime($parts[3], $parts[4] + 1, -1); + $upper = $date->format('U'); + break; + + case 6: + $date->setDate($parts[0], $parts[1], $parts[2]); + $date->setTime($parts[3], $parts[4], $parts[5]); + return $date->format('U'); + + default: + return $value; + } + + return "[$lower TO $upper]"; + } + /** * Replacement callback for replacing ISO dates with timestamps. + * + * Not used anymore, but kept for backwards compatibility with potential + * subclasses. */ public function replaceDateString($matches) { return strtotime($matches[0]); @@ -86,15 +166,9 @@ class SearchApiFacetapiDate extends SearchApiFacetapiTerm implements FacetapiQue $build = array(); $search = search_api_current_search($search_id); $results = $search[1]; - if (!$results['result count']) { - return array(); - } // Gets total number of documents matched in search. $total = $results['result count']; - // Most of the code below is copied from search_facetapi's implementation of - // this method. - // Executes query, iterates over results. if (isset($results['search_api_facets']) && isset($results['search_api_facets'][$this->facet['name']])) { $values = $results['search_api_facets'][$this->facet['name']]; @@ -113,13 +187,6 @@ class SearchApiFacetapiDate extends SearchApiFacetapiTerm implements FacetapiQue } } else { - $filter = substr($value['filter'], 1, -1); - $pos = strpos($filter, ' '); - if ($pos !== FALSE) { - $lower = facetapi_isodate(substr($filter, 0, $pos), FACETAPI_DATE_DAY); - $upper = facetapi_isodate(substr($filter, $pos + 1), FACETAPI_DATE_DAY); - $filter = '[' . $lower . ' TO ' . $upper . ']'; - } $build[$filter]['#count'] = $value['count']; } } @@ -128,23 +195,28 @@ class SearchApiFacetapiDate extends SearchApiFacetapiTerm implements FacetapiQue // Get the finest level of detail we're allowed to drill down to. $settings = $facet->getSettings()->settings; - $granularity = isset($settings['date_granularity']) ? $settings['date_granularity'] : FACETAPI_DATE_MINUTE; + $max_granularity = isset($settings['date_granularity']) ? $settings['date_granularity'] : FACETAPI_DATE_MINUTE; // Gets active facets, starts building hierarchy. - $parent = $gap = NULL; + $parent = $granularity = NULL; $active_items = $this->adapter->getActiveItems($this->facet); foreach ($active_items as $value => $item) { // If the item is active, the count is the result set count. $build[$value] = array('#count' => $total); - // Gets next "gap" increment. - if ($value[0] != '[' || $value[strlen($value) - 1] != ']' || !($pos = strpos($value, ' TO '))) { + // Gets next "gap" increment. Ignore any filters passed directly from the + // server (range or missing). We always create filters starting with a + // year. + $value = "$value"; + if (!$value || !ctype_digit($value[0])) { continue; } - $start = substr($value, 1, $pos); - $end = substr($value, $pos + 4, -1); - $date_gap = facetapi_get_date_gap($start, $end); - $gap = facetapi_get_next_date_gap($date_gap, $granularity); + + $granularity = search_api_facetapi_date_get_granularity($value); + if (!$granularity) { + continue; + } + $granularity = facetapi_get_next_date_gap($granularity, $max_granularity); // If there is a previous item, there is a parent, uses a reference so the // arrays are populated when they are updated. @@ -156,6 +228,7 @@ class SearchApiFacetapiDate extends SearchApiFacetapiTerm implements FacetapiQue // Stores the last value iterated over. $parent = $value; } + if (empty($raw_values)) { return $build; } @@ -165,7 +238,7 @@ class SearchApiFacetapiDate extends SearchApiFacetapiTerm implements FacetapiQue $timestamps = array_keys($raw_values); if (NULL === $parent) { if (count($raw_values) > 1) { - $gap = facetapi_get_timestamp_gap(min($timestamps), max($timestamps)); + $granularity = facetapi_get_timestamp_gap(min($timestamps), max($timestamps), $max_granularity); // Array of numbers used to determine whether the next gap is smaller than // the minimum gap allowed in the drilldown. $gap_numbers = array( @@ -178,36 +251,20 @@ class SearchApiFacetapiDate extends SearchApiFacetapiTerm implements FacetapiQue ); // Gets gap numbers for both the gap and minimum gap, checks if the gap // is within the limit set by the $granularity parameter. - if ($gap_numbers[$gap] < $gap_numbers[$granularity]) { - $gap = $granularity; + if ($gap_numbers[$granularity] < $gap_numbers[$max_granularity]) { + $granularity = $max_granularity; } } else { - $gap = $granularity; + $granularity = $max_granularity; } } - // Converts all timestamps to dates in ISO 8601 format. - $dates = array(); - foreach ($timestamps as $timestamp) { - $dates[$timestamp] = facetapi_isodate($timestamp, $gap); - } - - // Treat each date as the range start and next date as the range end. - $range_end = array(); - $previous = NULL; - foreach (array_unique($dates) as $date) { - if (NULL !== $previous) { - $range_end[$previous] = facetapi_get_next_date_increment($previous, $gap); - } - $previous = $date; - } - $range_end[$previous] = facetapi_get_next_date_increment($previous, $gap); - - // Groups dates by the range they belong to, builds the $build array - // with the facet counts and formatted range values. + // Groups dates by the range they belong to, builds the $build array with + // the facet counts and formatted range values. + $format = search_api_facetapi_date_get_granularity_format($granularity); foreach ($raw_values as $value => $count) { - $new_value = '[' . $dates[$value] . ' TO ' . $range_end[$dates[$value]] . ']'; + $new_value = date($format, $value); if (!isset($build[$new_value])) { $build[$new_value] = array('#count' => $count); } @@ -226,4 +283,5 @@ class SearchApiFacetapiDate extends SearchApiFacetapiTerm implements FacetapiQue return $build; } + } diff --git a/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_term.inc b/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_term.inc index 587598a3..b2c02c37 100644 --- a/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_term.inc +++ b/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_term.inc @@ -30,7 +30,7 @@ class SearchApiFacetapiTerm extends FacetapiQueryType implements FacetapiQueryTy // Return terms for this facet. $this->adapter->addFacet($this->facet, $query); - $settings = $this->adapter->getFacet($this->facet)->getSettings()->settings; + $settings = $this->getSettings()->settings; // First check if the facet is enabled for this search. $default_true = isset($settings['default_true']) ? $settings['default_true'] : TRUE; @@ -56,7 +56,12 @@ class SearchApiFacetapiTerm extends FacetapiQueryType implements FacetapiQueryTy $conjunction = 'OR'; } else { - throw new SearchApiException(t('Unknown facet operator %operator.', array('%operator' => $operator))); + $vars = array( + '%operator' => $operator, + '%facet' => !empty($this->facet['label']) ? $this->facet['label'] : $this->facet['name'], + ); + watchdog('search_api_facetapi', 'Unknown facet operator %operator used for facet %facet.', $vars, WATCHDOG_WARNING); + return; } $tags = array('facet:' . $this->facet['field']); $facet_filter = $query->createFilter($conjunction, $tags); @@ -77,7 +82,7 @@ class SearchApiFacetapiTerm extends FacetapiQueryType implements FacetapiQueryTy // Test if this filter should be negated. $settings = $this->adapter->getFacet($this->facet)->getSettings(); $exclude = !empty($settings->settings['exclude']); - // Integer (or other nun-string) filters might mess up some of the following + // Integer (or other non-string) filters might mess up some of the following // comparison expressions. $filter = (string) $filter; if ($filter == '!') { @@ -143,9 +148,15 @@ class SearchApiFacetapiTerm extends FacetapiQueryType implements FacetapiQueryTy return array(); } $search_id = $search_ids[$facet['name']]; - $search = search_api_current_search($search_id); + list(, $results) = search_api_current_search($search_id); $build = array(); - $results = $search[1]; + + // Always include the active facet items. + foreach ($this->adapter->getActiveItems($this->facet) as $filter) { + $build[$filter['value']]['#count'] = $results['result count']; + } + + // Then, add the facets returned by the server. if (isset($results['search_api_facets']) && isset($results['search_api_facets'][$this->facet['name']])) { $values = $results['search_api_facets'][$this->facet['name']]; foreach ($values as $value) { diff --git a/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/search_api_facetapi.info b/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/search_api_facetapi.info index a0b9dcba..d64416b2 100644 --- a/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/search_api_facetapi.info +++ b/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/search_api_facetapi.info @@ -9,9 +9,9 @@ files[] = plugins/facetapi/adapter.inc files[] = plugins/facetapi/query_type_term.inc files[] = plugins/facetapi/query_type_date.inc -; Information added by Drupal.org packaging script on 2013-12-25 -version = "7.x-1.11" +; Information added by Drupal.org packaging script on 2014-12-26 +version = "7.x-1.14" core = "7.x" project = "search_api" -datestamp = "1387965506" +datestamp = "1419580682" diff --git a/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/search_api_facetapi.module b/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/search_api_facetapi.module index 29a2dbd5..b7558be4 100644 --- a/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/search_api_facetapi.module +++ b/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/search_api_facetapi.module @@ -53,7 +53,7 @@ function search_api_facetapi_facetapi_searcher_info() { $info = array(); $indexes = search_api_index_load_multiple(FALSE); foreach ($indexes as $index) { - if ($index->enabled && $index->server()->supportsFeature('search_api_facets')) { + if (_search_api_facetapi_index_support_feature($index)) { $searcher_name = 'search_api@' . $index->machine_name; $info[$searcher_name] = array( 'label' => t('Search service: @name', array('@name' => $index->name)), @@ -97,7 +97,7 @@ function search_api_facetapi_facetapi_facet_info(array $searcher_info) { 'date' => array( 'query type' => 'date', 'map options' => array( - 'map callback' => 'facetapi_map_date', + 'map callback' => 'search_api_facetapi_map_date', ), ), ); @@ -116,7 +116,7 @@ function search_api_facetapi_facetapi_facet_info(array $searcher_info) { 'description' => t('Filter by @type.', array('@type' => $field['name'])), 'allowed operators' => array( FACETAPI_OPERATOR_AND => TRUE, - FACETAPI_OPERATOR_OR => $index->server()->supportsFeature('search_api_facets_operator_or'), + FACETAPI_OPERATOR_OR => _search_api_facetapi_index_support_feature($index, 'search_api_facets_operator_or'), ), 'dependency plugins' => array('role'), 'facet missing allowed' => TRUE, @@ -218,7 +218,7 @@ function search_api_facetapi_settings($realm_name, SearchApiIndex $index) { if (!$index->enabled) { return array('#markup' => t('Since this index is at the moment disabled, no facets can be activated.')); } - if (!$index->server()->supportsFeature('search_api_facets')) { + if (!_search_api_facetapi_index_support_feature($index)) { return array('#markup' => t('This index uses a server that does not support facet functionality.')); } $searcher_name = 'search_api@' . $index->machine_name; @@ -226,6 +226,28 @@ function search_api_facetapi_settings($realm_name, SearchApiIndex $index) { return drupal_get_form('facetapi_realm_settings_form', $searcher_name, $realm_name); } +/** + * Checks whether a certain feature is supported for an index. + * + * @param SearchApiIndex $index + * The search index which should be checked. + * @param string $feature + * (optional) The feature to check for. Defaults to "search_api_facets". + * + * @return bool + * TRUE if the feature is supported by the index's server (and the index is + * currently enabled), FALSE otherwise. + */ +function _search_api_facetapi_index_support_feature(SearchApiIndex $index, $feature = 'search_api_facets') { + try { + $server = $index->server(); + return $server && $server->supportsFeature($feature); + } + catch (SearchApiException $e) { + return FALSE; + } +} + /** * Gets hierarchy information for taxonomy terms. * @@ -366,7 +388,7 @@ function _search_api_facetapi_facet_create_label(array $values, array $options) $entities = entity_load($type, $values); foreach ($entities as $id => $entity) { $label = entity_label($type, $entity); - if ($label) { + if ($label !== FALSE) { $map[$id] = $label; } } @@ -432,3 +454,131 @@ function search_api_facetapi_search_api_admin_index_fields_submit($form, &$form_ $cid = 'facetapi:facet_info:search_api@' . $form_state['index']->machine_name . ':'; cache_clear_all($cid, 'cache', TRUE); } + +/** + * Computes the granularity of a date facet filter. + * + * @param $filter + * The filter value to examine. + * + * @return string|null + * Either one of the FACETAPI_DATE_* constants corresponding to the + * granularity of the filter, or NULL if it couldn't be computed. + */ +function search_api_facetapi_date_get_granularity($filter) { + // Granularity corresponds to number of dashes in filter value. + $units = array( + FACETAPI_DATE_YEAR, + FACETAPI_DATE_MONTH, + FACETAPI_DATE_DAY, + FACETAPI_DATE_HOUR, + FACETAPI_DATE_MINUTE, + FACETAPI_DATE_SECOND, + ); + $count = substr_count($filter, '-'); + return isset($units[$count]) ? $units[$count] : NULL; +} + +/** + * Returns the date format used for a given granularity. + * + * @param $granularity + * One of the FACETAPI_DATE_* constants. + * + * @return string + * The date format used for the given granularity. + */ +function search_api_facetapi_date_get_granularity_format($granularity) { + $formats = array( + FACETAPI_DATE_YEAR => 'Y', + FACETAPI_DATE_MONTH => 'Y-m', + FACETAPI_DATE_DAY => 'Y-m-d', + FACETAPI_DATE_HOUR => 'Y-m-d-H', + FACETAPI_DATE_MINUTE => 'Y-m-d-H-i', + FACETAPI_DATE_SECOND => 'Y-m-d-H-i-s', + ); + return $formats[$granularity]; +} + +/** + * Constructs labels for date facet filter values. + * + * @param array $values + * The date facet filter values, as used in URL parameters. + * @param array $options + * (optional) Options for creating the mapping. The following options are + * recognized: + * - format callback: A callback for creating a label for a timestamp. The + * function signature is like search_api_facetapi_format_timestamp(), + * receiving a timestamp and one of the FACETAPI_DATE_* constants as the + * parameters and returning a human-readable label. + * + * @return array + * An array of labels for the given facet filters. + */ +function search_api_facetapi_map_date(array $values, array $options = array()) { + $map = array(); + foreach ($values as $value) { + // Ignore any filters passed directly from the server (range or missing). We + // always create filters starting with a year. + $value = "$value"; + if (!$value || !ctype_digit($value[0])) { + continue; + } + + // Get the granularity of the filter. + $granularity = search_api_facetapi_date_get_granularity($value); + if (!$granularity) { + continue; + } + + // For years, the URL value is already the label. + if ($granularity == FACETAPI_DATE_YEAR) { + $map[$value] = $value; + continue; + } + + // Otherwise, parse the timestamp from the known format and format it as a + // label. + $format = search_api_facetapi_date_get_granularity_format($granularity); + $date = DateTime::createFromFormat($format, $value); + if (!$date) { + continue; + } + $format_callback = 'search_api_facetapi_format_timestamp'; + if (!empty($options['format callback']) && is_callable($options['format callback'])) { + $format_callback = $options['format callback']; + } + $map[$value] = call_user_func($format_callback, $date->format('U'), $granularity); + } + return $map; +} + +/** + * Format a date according to the default timezone and the given precision. + * + * @param int $timestamp + * An integer containing the Unix timestamp being formated. + * @param string $precision + * A string containing the formatting precision. See the FACETAPI_DATE_* + * constants for valid values. + * + * @return string + * A human-readable representation of the timestamp. + */ +function search_api_facetapi_format_timestamp($timestamp, $precision = FACETAPI_DATE_YEAR) { + $formats = array( + FACETAPI_DATE_YEAR => 'Y', + FACETAPI_DATE_MONTH => 'F Y', + FACETAPI_DATE_DAY => 'F j, Y', + FACETAPI_DATE_HOUR => 'H:__', + FACETAPI_DATE_MINUTE => 'H:i', + FACETAPI_DATE_SECOND => 'H:i:s', + ); + + if (!isset($formats[$precision])) { + $precision = FACETAPI_DATE_YEAR; + } + + return format_date($timestamp, 'custom', $formats[$precision]); +} diff --git a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_argument_fulltext.inc b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_argument_fulltext.inc index a6f00e2d..a4db8e25 100644 --- a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_argument_fulltext.inc +++ b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_argument_fulltext.inc @@ -66,7 +66,13 @@ class SearchApiViewsHandlerArgumentFulltext extends SearchApiViewsHandlerArgumen */ public function query($group_by = FALSE) { if ($this->options['fields']) { - $this->query->fields($this->options['fields']); + try { + $this->query->fields($this->options['fields']); + } + catch (SearchApiException $e) { + $this->query->abort($e->getMessage()); + return; + } } if ($this->options['conjunction'] != 'AND') { $this->query->setOption('conjunction', $this->options['conjunction']); diff --git a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_argument_more_like_this.inc b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_argument_more_like_this.inc index 9683c253..69e4a54c 100644 --- a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_argument_more_like_this.inc +++ b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_argument_more_like_this.inc @@ -62,24 +62,30 @@ class SearchApiViewsHandlerArgumentMoreLikeThis extends SearchApiViewsHandlerArg * The argument sent may be found at $this->argument. */ public function query($group_by = FALSE) { - $server = $this->query->getIndex()->server(); - if (!$server->supportsFeature('search_api_mlt')) { - $class = search_api_get_service_info($server->class); - watchdog('search_api_views', 'The search service "@class" does not offer "More like this" functionality.', + try { + $server = $this->query->getIndex()->server(); + if (!$server->supportsFeature('search_api_mlt')) { + $class = search_api_get_service_info($server->class); + watchdog('search_api_views', 'The search service "@class" does not offer "More like this" functionality.', array('@class' => $class['name']), WATCHDOG_ERROR); - $this->query->abort(); - return; - } - $fields = $this->options['fields'] ? $this->options['fields'] : array(); - if (empty($fields)) { - foreach ($this->query->getIndex()->options['fields'] as $key => $field) { - $fields[] = $key; + $this->query->abort(); + return; } + $fields = $this->options['fields'] ? $this->options['fields'] : array(); + if (empty($fields)) { + foreach ($this->query->getIndex()->options['fields'] as $key => $field) { + $fields[] = $key; + } + } + $mlt = array( + 'id' => $this->argument, + 'fields' => $fields, + ); + $this->query->getSearchApiQuery()->setOption('search_api_mlt', $mlt); + } + catch (SearchApiException $e) { + $this->query->abort($e->getMessage()); } - $mlt = array( - 'id' => $this->argument, - 'fields' => $fields, - ); - $this->query->getSearchApiQuery()->setOption('search_api_mlt', $mlt); } + } diff --git a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_entity.inc b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_entity.inc index 7fb2e240..ffae8ad4 100644 --- a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_entity.inc +++ b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_entity.inc @@ -90,7 +90,8 @@ abstract class SearchApiViewsHandlerFilterEntity extends SearchApiViewsHandlerFi // Set the correct default value in case the admin-set value is used (and a // value is present). The value is used if the form is either not exposed, - // or the exposed form wasn't submitted yet (there is + // or the exposed form wasn't submitted yet. (There doesn't seem to be an + // easier way to check for that.) if ($this->value && (empty($form_state['input']) || !empty($form_state['input']['live_preview']))) { $form['value']['#default_value'] = $this->ids_to_strings($this->value); } @@ -102,11 +103,13 @@ abstract class SearchApiViewsHandlerFilterEntity extends SearchApiViewsHandlerFi public function value_validate($form, &$form_state) { if (!empty($form['value'])) { $value = &$form_state['values']['options']['value']; - $values = $this->isMultiValued($form_state['values']['options']) ? drupal_explode_tags($value) : array($value); - $ids = $this->validate_entity_strings($form['value'], $values); + if (strlen($value)) { + $values = $this->isMultiValued($form_state['values']['options']) ? drupal_explode_tags($value) : array($value); + $ids = $this->validate_entity_strings($form['value'], $values); - if ($ids) { - $value = $ids; + if ($ids) { + $value = $ids; + } } } } @@ -135,6 +138,7 @@ abstract class SearchApiViewsHandlerFilterEntity extends SearchApiViewsHandlerFi return; } + $this->validated_exposed_input = FALSE; $identifier = $this->options['expose']['identifier']; $input = $form_state['values'][$identifier]; @@ -143,14 +147,14 @@ abstract class SearchApiViewsHandlerFilterEntity extends SearchApiViewsHandlerFi $input = $this->options['group_info']['group_items'][$input]['value']; } + if (!strlen($input)) { + return; + } $values = $this->isMultiValued() ? drupal_explode_tags($input) : array($input); if (!$this->options['is_grouped'] || ($this->options['is_grouped'] && ($input != 'All'))) { $this->validated_exposed_input = $this->validate_entity_strings($form[$identifier], $values); } - else { - $this->validated_exposed_input = FALSE; - } } /** @@ -175,6 +179,9 @@ abstract class SearchApiViewsHandlerFilterEntity extends SearchApiViewsHandlerFi * {@inheritdoc} */ public function admin_summary() { + if (!is_array($this->value)) { + $this->value = $this->value ? array($this->value) : array(); + } $value = $this->value; $this->value = empty($value) ? '' : $this->ids_to_strings($value); $ret = parent::admin_summary(); diff --git a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_fulltext.inc b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_fulltext.inc index 55226218..71745aea 100644 --- a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_fulltext.inc +++ b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_fulltext.inc @@ -156,8 +156,9 @@ class SearchApiViewsHandlerFilterFulltext extends SearchApiViewsHandlerFilterTex if ($filter) { $filter = $this->query->createFilter('OR'); + $op = $this->operator === 'NOT' ? '<>' : '='; foreach ($fields as $field) { - $filter->condition($field, $this->value, $this->operator); + $filter->condition($field, $this->value, $op); } $this->query->filter($filter); return; @@ -166,11 +167,18 @@ class SearchApiViewsHandlerFilterFulltext extends SearchApiViewsHandlerFilterTex // If the operator was set to OR or NOT, set OR as the conjunction. (It is // also set for NOT since otherwise it would be "not all of these words".) if ($this->operator != 'AND') { - $this->query->setOption('conjunction', $this->operator); + $this->query->setOption('conjunction', 'OR'); } - $this->query->fields($fields); - $old = $this->query->getOriginalKeys(); + try { + $this->query->fields($fields); + } + catch (SearchApiException $e) { + $this->query->abort($e->getMessage()); + return; + } + $old = $this->query->getKeys(); + $old_original = $this->query->getOriginalKeys(); $this->query->keys($this->value); if ($this->operator == 'NOT') { $keys = &$this->query->getKeys(); @@ -181,16 +189,44 @@ class SearchApiViewsHandlerFilterFulltext extends SearchApiViewsHandlerFilterTex // We can't know how negation is expressed in the server's syntax. } } + + // If there were fulltext keys set, we take care to combine them in a + // meaningful way (especially with negated keys). if ($old) { $keys = &$this->query->getKeys(); + // Array-valued keys are combined. if (is_array($keys)) { - $keys[] = $old; + // If the old keys weren't parsed into an array, we instead have to + // combine the original keys. + if (is_scalar($old)) { + $keys = "($old) ({$this->value})"; + } + else { + // If the conjunction or negation settings aren't the same, we have to + // nest both old and new keys array. + if (!empty($keys['#negation']) != !empty($old['#negation']) || $keys['#conjunction'] != $old['#conjunction']) { + $keys = array( + '#conjunction' => 'AND', + $old, + $keys, + ); + } + // Otherwise, just add all individual words from the old keys to the + // new ones. + else { + foreach (element_children($old) as $i) { + $keys[] = $old[$i]; + } + } + } } - elseif (is_array($old)) { - // We don't support such nonsense. - } - else { - $keys = "($old) ($keys)"; + // If the parse mode was "direct" for both old and new keys, we + // concatenate them and set them both via method and reference (to also + // update the originalKeys property. + elseif (is_scalar($old_original)) { + $combined_keys = "($old_original) ($keys)"; + $this->query->keys($combined_keys); + $keys = $combined_keys; } } } diff --git a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_language.inc b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_language.inc index 399f80c1..a7de5f94 100644 --- a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_language.inc +++ b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_language.inc @@ -14,26 +14,14 @@ class SearchApiViewsHandlerFilterLanguage extends SearchApiViewsHandlerFilterOptions { /** - * Provide a form for setting options. + * {@inheritdoc} */ - public function value_form(&$form, &$form_state) { - parent::value_form($form, $form_state); - $form['value']['#options'] = array( + protected function get_value_options() { + parent::get_value_options(); + $this->value_options = array( 'current' => t("Current user's language"), 'default' => t('Default site language'), - ) + $form['value']['#options']; - } - - /** - * Provides a summary of this filter's value for the admin UI. - */ - public function admin_summary() { - $tmp = $this->definition['options']; - $this->definition['options']['current'] = t('current'); - $this->definition['options']['default'] = t('default'); - $ret = parent::admin_summary(); - $this->definition['options'] = $tmp; - return $ret; + ) + $this->value_options; } /** diff --git a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_sort.inc b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_sort.inc index 1b2cb195..d7ca1e47 100644 --- a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_sort.inc +++ b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_sort.inc @@ -28,8 +28,23 @@ class SearchApiViewsHandlerSort extends views_handler_sort { unset($this->query->orderby); $sort = &$this->query->getSort(); $sort = array(); + unset($sort); + } + + // If two of the same fields are used for sort, ignore the latter in order + // for the prior to take precedence. (Temporary workaround until + // https://www.drupal.org/node/2145547 is fixed in Views.) + $alreadySorted = $this->query->getSort(); + if (is_array($alreadySorted) && isset($alreadySorted[$this->real_field])) { + return; + } + + try { + $this->query->sort($this->real_field, $this->options['order']); + } + catch (SearchApiException $e) { + $this->query->abort($e->getMessage()); } - $this->query->sort($this->real_field, $this->options['order']); } } diff --git a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/plugin_cache.inc b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/plugin_cache.inc index 52724a00..5e0dff34 100644 --- a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/plugin_cache.inc +++ b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/plugin_cache.inc @@ -77,22 +77,24 @@ class SearchApiViewsCache extends views_plugin_cache_time { } /** - * Overrides views_plugin_cache::get_results_key(). + * Overrides views_plugin_cache::get_cache_key(). * - * Use the Search API query as the main source for the key. + * Use the Search API query as the main source for the key. Note that in + * Views < 3.8, this function does not exist. */ - public function get_results_key() { + public function get_cache_key($key_data = array()) { global $user; if (!isset($this->_results_key)) { $query = $this->getSearchApiQuery(); $query->preExecute(); - $key_data = array( + $key_data += array( 'query' => $query, 'roles' => array_keys($user->roles), 'super-user' => $user->uid == 1, // special caching for super user. 'language' => $GLOBALS['language']->language, 'base_url' => $GLOBALS['base_url'], + 'offset' => $this->view->get_current_page() . '*' . $this->view->get_items_per_page() . '+' . $this->view->get_offset(), ); // Not sure what gets passed in exposed_info, so better include it. All // other parameters used in the parent method are already reflected in the @@ -100,8 +102,19 @@ class SearchApiViewsCache extends views_plugin_cache_time { if (isset($_GET['exposed_info'])) { $key_data['exposed_info'] = $_GET['exposed_info']; } + } + $key = md5(serialize($key_data)); + return $key; + } - $this->_results_key = $this->view->name . ':' . $this->display->id . ':results:' . md5(serialize($key_data)); + /** + * Overrides views_plugin_cache::get_results_key(). + * + * This is unnecessary for Views >= 3.8. + */ + public function get_results_key() { + if (!isset($this->_results_key)) { + $this->_results_key = $this->view->name . ':' . $this->display->id . ':results:' . $this->get_cache_key(); } return $this->_results_key; diff --git a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/query.inc b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/query.inc index 36bc2326..9356bbab 100644 --- a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/query.inc +++ b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/query.inc @@ -169,7 +169,7 @@ class SearchApiViewsQuery extends views_plugin_query { '#default_value' => $this->options['search_api_bypass_access'], ); - if (entity_get_info($this->index->item_type)) { + if ($this->index->getEntityType()) { $form['entity_access'] = array( '#type' => 'checkbox', '#title' => t('Additional access checks on result entities'), @@ -342,7 +342,7 @@ class SearchApiViewsQuery extends views_plugin_query { catch (Exception $e) { $this->errors[] = $e->getMessage(); // Recursion to get the same error behaviour as above. - return $this->execute($view); + $this->execute($view); } } @@ -374,9 +374,9 @@ class SearchApiViewsQuery extends views_plugin_query { // First off, we try to gather as much field values as possible without // loading any items. foreach ($results as $id => $result) { - if (!empty($this->options['entity_access'])) { - $entity = entity_load($this->index->item_type, array($id)); - if (!entity_access('view', $this->index->item_type, $entity[$id])) { + 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])) { continue; } } @@ -660,16 +660,18 @@ class SearchApiViewsQuery extends views_plugin_query { return $ret; } - public function getOption($name) { + public function getOption($name, $default = NULL) { if (!$this->errors) { - return $this->query->getOption($name); + return $this->query->getOption($name, $default); } + return $default; } public function setOption($name, $value) { if (!$this->errors) { return $this->query->setOption($name, $value); } + return NULL; } public function &getOptions() { diff --git a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/search_api_views.info b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/search_api_views.info index 0a4ad477..630f172e 100644 --- a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/search_api_views.info +++ b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/search_api_views.info @@ -27,9 +27,9 @@ files[] = includes/handler_sort.inc files[] = includes/plugin_cache.inc files[] = includes/query.inc -; Information added by Drupal.org packaging script on 2013-12-25 -version = "7.x-1.11" +; Information added by Drupal.org packaging script on 2014-12-26 +version = "7.x-1.14" core = "7.x" project = "search_api" -datestamp = "1387965506" +datestamp = "1419580682" diff --git a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/search_api_views.views.inc b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/search_api_views.views.inc index b55a85a7..a87e5bcc 100644 --- a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/search_api_views.views.inc +++ b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/search_api_views.views.inc @@ -134,8 +134,8 @@ function search_api_views_views_data() { if (isset($field['entity_type']) && $field['entity_type'] === 'taxonomy_term') { $field_id = ($pos = strrpos($key, ':')) ? substr($key, $pos + 1) : $key; $field_info = field_info_field($field_id); - if (isset($field_info['settings']['allowed_values'][0]['vocabulary'])) { - $vocabulary_fields[$field_info['settings']['allowed_values'][0]['vocabulary']][] = $key; + if ($vocabulary = _search_api_views_get_field_vocabulary($field_info)) { + $vocabulary_fields[$vocabulary][] = $key; } else { $vocabulary_fields[''][] = $key; @@ -184,7 +184,7 @@ function _search_api_views_add_handlers($id, array $field, EntityMetadataWrapper if ($inner_type == 'text') { $table[$id] += array( 'argument' => array( - 'handler' => 'SearchApiViewsHandlerArgument', + 'handler' => 'SearchApiViewsHandlerArgumentString', ), 'filter' => array( 'handler' => 'SearchApiViewsHandlerFilterText', @@ -209,7 +209,6 @@ function _search_api_views_add_handlers($id, array $field, EntityMetadataWrapper } elseif (isset($field['entity_type']) && $field['entity_type'] === 'taxonomy_term') { $table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilterTaxonomyTerm'; - $info = $wrapper->info(); $field_info = field_info_field($info['name']); // For the "Parent terms" and "All parent terms" properties, we can // extrapolate the vocabulary from the parent in the selector. (E.g., @@ -221,8 +220,8 @@ function _search_api_views_add_handlers($id, array $field, EntityMetadataWrapper $field_info = field_info_field($parts[count($parts) - 2]); } } - if (isset($field_info['settings']['allowed_values'][0]['vocabulary'])) { - $table[$id]['filter']['vocabulary'] = $field_info['settings']['allowed_values'][0]['vocabulary']; + if ($vocabulary = _search_api_views_get_field_vocabulary($field_info)) { + $table[$id]['filter']['vocabulary'] = $vocabulary; } } else { @@ -293,3 +292,31 @@ function search_api_views_views_plugins() { return $ret; } + +/** + * Returns the vocabulary machine name of a term field. + * + * @param array|null $field_info + * The field's field info array, or NULL if the field is not provided by the + * Field API. See the return value of field_info_field(). + * + * @return string|null + * If the field contains taxonomy terms of a single vocabulary (which could be + * determined), that vocabulary's machine name; NULL otherwise. + */ +function _search_api_views_get_field_vocabulary($field_info) { + // Test for "Term reference" fields. + if (isset($field_info['settings']['allowed_values'][0]['vocabulary'])) { + return $field_info['settings']['allowed_values'][0]['vocabulary']; + } + // Test for "Entity reference" fields. + elseif (isset($field_info['settings']['handler']) && $field_info['settings']['handler'] === 'base') { + if (!empty($field_info['settings']['handler_settings']['target_bundles'])) { + $bundles = $field_info['settings']['handler_settings']['target_bundles']; + if (count($bundles) == 1) { + return key($bundles); + } + } + } + return NULL; +} diff --git a/sites/all/modules/contrib/search/search_api/includes/callback_add_aggregation.inc b/sites/all/modules/contrib/search/search_api/includes/callback_add_aggregation.inc index 31566c70..d1ed9bce 100644 --- a/sites/all/modules/contrib/search/search_api/includes/callback_add_aggregation.inc +++ b/sites/all/modules/contrib/search/search_api/includes/callback_add_aggregation.inc @@ -193,6 +193,12 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback { return isset($a) ? min($a, $b) : $b; case 'first': return isset($a) ? $a : $b; + case 'list': + if (!isset($a)) { + $a = array(); + } + $a[] = $b; + return $a; } } @@ -261,6 +267,7 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback { 'max' => t('Maximum'), 'min' => t('Minimum'), 'first' => t('First'), + 'list' => t('List'), ); case 'type': return array( @@ -270,6 +277,7 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback { 'max' => 'integer', 'min' => 'integer', 'first' => 'string', + 'list' => 'list', ); case 'description': return array( @@ -279,6 +287,7 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback { 'max' => t('The Maximum aggregation computes the numerically largest contained field value.'), 'min' => t('The Minimum aggregation computes the numerically smallest contained field value.'), 'first' => t('The First aggregation will simply keep the first encountered field value. This is helpful foremost when you know that a list field will only have a single value.'), + 'list' => t('The List aggregation collects all field values into a multi-valued field containing all values.'), ); } } @@ -289,6 +298,8 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback { public function formButtonSubmit(array $form, array &$form_state) { $button_name = $form_state['triggering_element']['#name']; if ($button_name == 'op') { + // Increment $i until the corresponding field is not set, then create the + // field with that number as suffix. for ($i = 1; isset($this->options['fields']['search_api_aggregation_' . $i]); ++$i) { } $this->options['fields']['search_api_aggregation_' . $i] = array( diff --git a/sites/all/modules/contrib/search/search_api/includes/index_entity.inc b/sites/all/modules/contrib/search/search_api/includes/index_entity.inc index b2168842..e252e1f1 100644 --- a/sites/all/modules/contrib/search/search_api/includes/index_entity.inc +++ b/sites/all/modules/contrib/search/search_api/includes/index_entity.inc @@ -172,20 +172,25 @@ class SearchApiIndex extends Entity { /** * Constructor as a helper to the parent constructor. */ - public function __construct(array $values = array()) { - parent::__construct($values, 'search_api_index'); + public function __construct(array $values = array(), $entity_type = 'search_api_index') { + parent::__construct($values, $entity_type); } /** * Execute necessary tasks for a newly created index. */ public function postCreate() { - if ($this->enabled) { - $this->queueItems(); + try { + if ($server = $this->server()) { + // Tell the server about the new index. + $server->addIndex($this); + if ($this->enabled) { + $this->queueItems(); + } + } } - if ($server = $this->server()) { - // Tell the server about the new index. - $server->addIndex($this); + catch (SearchApiException $e) { + watchdog_exception('search_api', $e); } } @@ -193,8 +198,13 @@ class SearchApiIndex extends Entity { * Execute necessary tasks when the index is removed from the database. */ public function postDelete() { - if ($server = $this->server()) { - $server->removeIndex($this); + try { + if ($server = $this->server()) { + $server->removeIndex($this); + } + } + catch (SearchApiException $e) { + watchdog_exception('search_api', $e); } // Stop tracking entities for indexing. @@ -206,7 +216,12 @@ class SearchApiIndex extends Entity { */ public function queueItems() { if (!$this->read_only) { - $this->datasource()->startTracking(array($this)); + try { + $this->datasource()->startTracking(array($this)); + } + catch (SearchApiException $e) { + watchdog_exception('search_api', $e); + } } } @@ -214,7 +229,12 @@ class SearchApiIndex extends Entity { * Remove all records of entities to index. */ public function dequeueItems() { - $this->datasource()->stopTracking(array($this)); + try { + $this->datasource()->stopTracking(array($this)); + } + catch (SearchApiException $e) { + watchdog_exception('search_api', $e); + } } /** @@ -231,16 +251,25 @@ class SearchApiIndex extends Entity { if (empty($this->description)) { $this->description = NULL; } - if (empty($this->server)) { + $server = FALSE; + if (!empty($this->server)) { + $server = search_api_server_load($this->server); + if (!$server) { + $vars['%server'] = $this->server; + $vars['%index'] = $this->name; + watchdog('search_api', 'Unknown server %server specified for index %index.', $vars, WATCHDOG_ERROR); + } + } + if (!$server) { $this->server = NULL; $this->enabled = FALSE; } - // This will also throw an exception if the server doesn't exist – which is good. - elseif (!$this->server(TRUE)->enabled) { - $this->enabled = FALSE; - $this->server = NULL; + if (!empty($this->options['fields'])) { + ksort($this->options['fields']); } + $this->resetCaches(); + return parent::save(); } @@ -305,7 +334,12 @@ class SearchApiIndex extends Entity { return TRUE; } - $this->server()->deleteItems('all', $this); + try { + $this->server()->deleteItems('all', $this); + } + catch (SearchApiException $e) { + watchdog_exception('search_api', $e); + } _search_api_index_reindex($this); module_invoke_all('search_api_index_reindex', $this, TRUE); @@ -350,7 +384,12 @@ class SearchApiIndex extends Entity { * otherwise. */ public function getEntityType() { - return $this->datasource()->getEntityType(); + try { + return $this->datasource()->getEntityType(); + } + catch (SearchApiException $e) { + return NULL; + } } /** @@ -385,7 +424,7 @@ class SearchApiIndex extends Entity { * SearchApiQueryInterface::__construct(). * * @throws SearchApiException - * If the index is currently disabled. + * If the index is currently disabled or its server doesn't exist. * * @return SearchApiQueryInterface * A query object for searching this index. @@ -399,15 +438,20 @@ class SearchApiIndex extends Entity { /** - * Indexes items on this index. Will return an array of IDs of items that - * should be marked as indexed – i.e., items that were either rejected by a - * data-alter callback or were successfully indexed. + * Indexes items on this index. + * + * Will return an array of IDs of items that should be marked as indexed – + * i.e., items that were either rejected by a data-alter callback or were + * successfully indexed. * * @param array $items - * An array of items to index. + * An array of items to index, of this index's item type. * * @return array * An array of the IDs of all items that should be marked as indexed. + * + * @throws SearchApiException + * If an error occurred during indexing. */ public function index(array $items) { if ($this->read_only) { @@ -925,12 +969,18 @@ class SearchApiIndex extends Entity { * @return EntityMetadataWrapper * A wrapper for the item type of this index, optionally loaded with the * given data and having additional fields according to the data alterations - * of this index. + * of this index (if $alter wasn't set to FALSE). */ public function entityWrapper($item = NULL, $alter = TRUE) { - $info['property info alter'] = $alter ? array($this, 'propertyInfoAlter') : '_search_api_wrapper_add_all_properties'; - $info['property defaults']['property info alter'] = '_search_api_wrapper_add_all_properties'; - return $this->datasource()->getMetadataWrapper($item, $info); + try { + $info['property info alter'] = $alter ? array($this, 'propertyInfoAlter') : '_search_api_wrapper_add_all_properties'; + $info['property defaults']['property info alter'] = '_search_api_wrapper_add_all_properties'; + return $this->datasource()->getMetadataWrapper($item, $info); + } + catch (SearchApiException $e) { + watchdog_exception('search_api', $e); + return entity_metadata_wrapper($this->item_type); + } } /** @@ -945,16 +995,24 @@ class SearchApiIndex extends Entity { * @see SearchApiDataSourceControllerInterface::loadItems() */ public function loadItems(array $ids) { - return $this->datasource()->loadItems($ids); + try { + return $this->datasource()->loadItems($ids); + } + catch (SearchApiException $e) { + watchdog_exception('search_api', $e); + return array(); + } } /** - * Reset internal static caches. + * Reset internal caches. * * Should be used when things like fields or data alterations change to avoid * using stale data. */ public function resetCaches() { + cache_clear_all($this->getCacheId(''), 'cache', TRUE); + $this->datasource = NULL; $this->server_object = NULL; $this->callbacks = NULL; diff --git a/sites/all/modules/contrib/search/search_api/includes/processor_highlight.inc b/sites/all/modules/contrib/search/search_api/includes/processor_highlight.inc index eda797c8..f094d9d4 100644 --- a/sites/all/modules/contrib/search/search_api/includes/processor_highlight.inc +++ b/sites/all/modules/contrib/search/search_api/includes/processor_highlight.inc @@ -22,8 +22,6 @@ class SearchApiHighlight extends SearchApiAbstractProcessor { /** * PREG regular expression for splitting words. * - * We highlight around non-indexable or CJK characters. - * * @var string */ protected static $split; @@ -40,7 +38,7 @@ class SearchApiHighlight extends SearchApiAbstractProcessor { '\x{F900}-\x{FAFF}\x{FF21}-\x{FF3A}\x{FF41}-\x{FF5A}\x{FF66}-\x{FFDC}' . '\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}'; self::$boundary = '(?:(?<=[' . PREG_CLASS_UNICODE_WORD_BOUNDARY . $cjk . '])|(?=[' . PREG_CLASS_UNICODE_WORD_BOUNDARY . $cjk . ']))'; - self::$split = '/[' . PREG_CLASS_UNICODE_WORD_BOUNDARY . $cjk . ']+/iu'; + self::$split = '/[' . PREG_CLASS_UNICODE_WORD_BOUNDARY . ']+/iu'; } /** @@ -53,6 +51,7 @@ class SearchApiHighlight extends SearchApiAbstractProcessor { 'excerpt' => TRUE, 'excerpt_length' => 256, 'highlight' => 'always', + 'exclude_fields' => array(), ); $form['prefix'] = array( @@ -87,6 +86,22 @@ class SearchApiHighlight extends SearchApiAbstractProcessor { ), ), ); + // Exclude certain fulltextfields + $fields = $this->index->getFields(); + $fulltext_fields = array(); + foreach ($this->index->getFulltextFields() as $field) { + if (isset($fields[$field])) { + $fulltext_fields[$field] = $fields[$field]['name'] . ' (' . $field . ')'; + } + } + $form['exclude_fields'] = array( + '#type' => 'checkboxes', + '#title' => t('Exclude fields from excerpt'), + '#description' => t('Exclude certain fulltext fields from being displayed in the excerpt.'), + '#options' => $fulltext_fields, + '#default_value' => $this->options['exclude_fields'], + '#attributes' => array('class' => array('search-api-checkboxes-list')), + ); $form['highlight'] = array( '#type' => 'select', '#title' => t('Highlight returned field data'), @@ -106,21 +121,29 @@ class SearchApiHighlight extends SearchApiAbstractProcessor { * {@inheritdoc} */ public function configurationFormValidate(array $form, array &$values, array &$form_state) { - // Overridden so $form['fields'] is not checked. + $values['exclude_fields'] = array_filter($values['exclude_fields']); } /** * {@inheritdoc} */ public function postprocessSearchResults(array &$response, SearchApiQuery $query) { - if (!$response['result count'] || !($keys = $this->getKeywords($query))) { + if (empty($response['results']) || !($keys = $this->getKeywords($query))) { return; } + $fulltext_fields = $this->index->getFulltextFields(); + if (!empty($this->options['exclude_fields'])) { + $fulltext_fields = drupal_map_assoc($fulltext_fields); + foreach ($this->options['exclude_fields'] as $field) { + unset($fulltext_fields[$field]); + } + } + foreach ($response['results'] as $id => &$result) { if ($this->options['excerpt']) { $text = array(); - $fields = $this->getFulltextFields($response['results'], $id); + $fields = $this->getFulltextFields($response['results'], $id, $fulltext_fields); foreach ($fields as $data) { if (is_array($data)) { $text = array_merge($text, $data); @@ -129,10 +152,11 @@ class SearchApiHighlight extends SearchApiAbstractProcessor { $text[] = $data; } } - $result['excerpt'] = $this->createExcerpt(implode("\n\n", $text), $keys); + + $result['excerpt'] = $this->createExcerpt($this->flattenArrayValues($text), $keys); } if ($this->options['highlight'] != 'never') { - $fields = $this->getFulltextFields($response['results'], $id, $this->options['highlight'] == 'always'); + $fields = $this->getFulltextFields($response['results'], $id, $fulltext_fields, $this->options['highlight'] == 'always'); foreach ($fields as $field => $data) { if (is_array($data)) { foreach ($data as $i => $text) { @@ -155,6 +179,8 @@ class SearchApiHighlight extends SearchApiAbstractProcessor { * @param int|string $i * The index in the results array of the result whose data should be * returned. + * @param array $fulltext_fields + * The fulltext fields from which the excerpt should be created. * @param bool $load * TRUE if the item should be loaded if necessary, FALSE if only fields * already returned in the results should be used. @@ -163,7 +189,7 @@ class SearchApiHighlight extends SearchApiAbstractProcessor { * An array containing fulltext field names mapped to the text data * contained in them for the given result. */ - protected function getFulltextFields(array &$results, $i, $load = TRUE) { + protected function getFulltextFields(array &$results, $i, array $fulltext_fields, $load = TRUE) { global $language; $data = array(); @@ -171,7 +197,6 @@ class SearchApiHighlight extends SearchApiAbstractProcessor { // Act as if $load is TRUE if we have a loaded item. $load |= !empty($result['entity']); $result += array('fields' => array()); - $fulltext_fields = $this->index->getFulltextFields(); // We only need detailed fields data if $load is TRUE. $fields = $load ? $this->index->getFields() : array(); $needs_extraction = array(); @@ -309,7 +334,7 @@ class SearchApiHighlight extends SearchApiAbstractProcessor { // Locate a keyword (position $p, always >0 because $text starts with a // space). $p = 0; - if (preg_match('/' . self::$boundary . $key . self::$boundary . '/iu', $text, $match, PREG_OFFSET_CAPTURE, $included[$key])) { + if (preg_match('/' . self::$boundary . preg_quote($key, '/') . self::$boundary . '/iu', $text, $match, PREG_OFFSET_CAPTURE, $included[$key])) { $p = $match[0][1]; } // Now locate a space in front (position $q) and behind it (position $s), @@ -379,7 +404,9 @@ class SearchApiHighlight extends SearchApiAbstractProcessor { $text = (isset($newranges[0]) ? '' : $dots[0]) . implode($dots[1], $out) . $dots[2]; $text = check_plain($text); - return $this->highlightField($text, $keys); + // Since we stripped the tags at the beginning, highlighting doesn't need to + // handle HTML anymore. + return $this->highlightField($text, $keys, FALSE); } /** @@ -389,15 +416,55 @@ class SearchApiHighlight extends SearchApiAbstractProcessor { * The text of the field. * @param array $keys * Search keywords entered by the user. + * @param bool $html + * Whether the text can contain HTML tags or not. In the former case, text + * inside tags (i.e., tag names and attributes) won't be highlighted. * * @return string * The field's text with all occurrences of search keywords highlighted. */ - protected function highlightField($text, array $keys) { + protected function highlightField($text, array $keys, $html = TRUE) { + if (is_array($text)) { + $text = $this->flattenArrayValues($text); + } + + if ($html) { + $texts = preg_split('#((?:"\']*|"[^"]*"|\'[^\']\')*>)+)#i', $text, -1, PREG_SPLIT_DELIM_CAPTURE); + for ($i = 0; $i < count($texts); $i += 2) { + $texts[$i] = $this->highlightField($texts[$i], $keys, FALSE); + } + return implode('', $texts); + } $replace = $this->options['prefix'] . '\0' . $this->options['suffix']; - $keys = implode('|', array_map('preg_quote', $keys)); + + $keys = implode('|', array_map('preg_quote', $keys, array_fill(0, count($keys), '/'))); $text = preg_replace('/' . self::$boundary . '(' . $keys . ')' . self::$boundary . '/iu', $replace, ' ' . $text . ' '); return substr($text, 1, -1); } + /** + * Flattens a (possibly multidimensional) array into a string. + * + * @param array $array + * The array to flatten. + * @param string $glue + * The separator to insert between individual array items. + * + * @return string + * The glued string. + */ + protected function flattenArrayValues(array $array, $glue = "\n\n") { + $ret = array(); + foreach ($array as $item) { + if (is_array($item)) { + $ret[] = $this->flattenArrayValues($item, $glue); + } + else { + $ret[] = $item; + } + } + + return implode($glue, $ret); + } + } diff --git a/sites/all/modules/contrib/search/search_api/includes/processor_html_filter.inc b/sites/all/modules/contrib/search/search_api/includes/processor_html_filter.inc index 41777bd2..0e714c32 100644 --- a/sites/all/modules/contrib/search/search_api/includes/processor_html_filter.inc +++ b/sites/all/modules/contrib/search/search_api/includes/processor_html_filter.inc @@ -102,6 +102,8 @@ class SearchApiHtmlFilter extends SearchApiAbstractProcessor { } else { $value = strip_tags($text); + // Remove any multiple or leading/trailing spaces we might have introduced. + $value = preg_replace('/\s\s+/', ' ', trim($value)); } } @@ -109,8 +111,11 @@ class SearchApiHtmlFilter extends SearchApiAbstractProcessor { $ret = array(); while (($pos = strpos($text, '<')) !== FALSE) { if ($boost && $pos > 0) { + $token = html_entity_decode(substr($text, 0, $pos), ENT_QUOTES, 'UTF-8'); + // Remove any multiple or leading/trailing spaces we might have introduced. + $token = preg_replace('/\s\s+/', ' ', trim($token)); $ret[] = array( - 'value' => html_entity_decode(substr($text, 0, $pos), ENT_QUOTES, 'UTF-8'), + 'value' => $token, 'score' => $boost, ); } @@ -130,8 +135,11 @@ class SearchApiHtmlFilter extends SearchApiAbstractProcessor { } } if ($text) { + $token = html_entity_decode($text, ENT_QUOTES, 'UTF-8'); + // Remove any multiple or leading/trailing spaces we might have introduced. + $token = preg_replace('/\s\s+/', ' ', trim($token)); $ret[] = array( - 'value' => html_entity_decode($text, ENT_QUOTES, 'UTF-8'), + 'value' => $token, 'score' => $boost, ); $text = ''; diff --git a/sites/all/modules/contrib/search/search_api/includes/query.inc b/sites/all/modules/contrib/search/search_api/includes/query.inc index e9ccc207..b887c795 100644 --- a/sites/all/modules/contrib/search/search_api/includes/query.inc +++ b/sites/all/modules/contrib/search/search_api/includes/query.inc @@ -226,6 +226,9 @@ interface SearchApiQueryInterface { * * This method should always be called by execute() and contain all necessary * operations before the query is passed to the server's search() method. + * + * @throws SearchApiException + * If any error occurred during the preparation of the query. */ public function preExecute(); @@ -366,7 +369,7 @@ class SearchApiQuery implements SearchApiQueryInterface { /** * The index's machine name. * - * used during serialization to avoid serializing the whole index object. + * Used during serialization to avoid serializing the whole index object. * * @var string */ @@ -811,6 +814,31 @@ class SearchApiQuery implements SearchApiQueryInterface { $this->filter = clone $this->filter; } + /** + * Implements the magic __toString() method to simplify debugging. + */ + public function __toString() { + $ret = 'Index: ' . $this->index->machine_name . "\n"; + $ret .= 'Keys: ' . str_replace("\n", "\n ", var_export($this->orig_keys, TRUE)) . "\n"; + if (isset($this->keys)) { + $ret .= 'Parsed keys: ' . str_replace("\n", "\n ", var_export($this->keys, TRUE)) . "\n"; + $ret .= 'Searched fields: ' . (isset($this->fields) ? implode(', ', $this->fields) : '[ALL]') . "\n"; + } + if ($filter = (string) $this->filter) { + $filter = str_replace("\n", "\n ", $filter); + $ret .= "Filters:\n $filter\n"; + } + if ($this->sort) { + $sort = array(); + foreach ($this->sort as $field => $order) { + $sort[] = "$field $order"; + } + $ret .= 'Sorting: ' . implode(', ', $sort) . "\n"; + } + $ret .= 'Options: ' . str_replace("\n", "\n ", var_export($this->options, TRUE)) . "\n"; + return $ret; + } + } /** @@ -890,8 +918,11 @@ interface SearchApiQueryFilterInterface { * Return all conditions and nested filters contained in this filter. * * @return array - * An array containing this filter's subfilters. Each of these is either an - * array (field, value, operator), or another SearchApiFilter object. + * An array containing this filter's subfilters. Each of these is either a + * condition, represented as a numerically indexed array with the arguments + * of a previous SearchApiQueryFilterInterface::condition() call (field, + * value, operator); or a nested filter, represented by a + * SearchApiQueryFilterInterface filter object. */ public function &getFilters(); @@ -1010,4 +1041,24 @@ class SearchApiQueryFilter implements SearchApiQueryFilterInterface { } } + /** + * Implements the magic __toString() method to simplify debugging. + */ + public function __toString() { + // Special case for a single, nested filter: + if (count($this->filters) == 1 && is_object($this->filters[0])) { + return (string) $this->filters[0]; + } + $ret = array(); + foreach ($this->filters as $filter) { + if (is_object($filter)) { + $ret[] = "[\n " . str_replace("\n", "\n ", (string) $filter) . "\n ]"; + } + else { + $ret[] = "$filter[0] $filter[2] " . str_replace("\n", "\n ", var_export($filter[1], TRUE)); + } + } + return $ret ? ' ' . implode("\n{$this->conjunction}\n ", $ret) : ''; + } + } diff --git a/sites/all/modules/contrib/search/search_api/includes/server_entity.inc b/sites/all/modules/contrib/search/search_api/includes/server_entity.inc index be2a568c..4a991166 100644 --- a/sites/all/modules/contrib/search/search_api/includes/server_entity.inc +++ b/sites/all/modules/contrib/search/search_api/includes/server_entity.inc @@ -74,8 +74,8 @@ class SearchApiServer extends Entity { /** * Constructor as a helper to the parent constructor. */ - public function __construct(array $values = array()) { - parent::__construct($values, 'search_api_server'); + public function __construct(array $values = array(), $entity_type = 'search_api_server') { + parent::__construct($values, $entity_type); } /** diff --git a/sites/all/modules/contrib/search/search_api/search_api.admin.inc b/sites/all/modules/contrib/search/search_api/search_api.admin.inc index b2269d68..0a0d7f13 100644 --- a/sites/all/modules/contrib/search/search_api/search_api.admin.inc +++ b/sites/all/modules/contrib/search/search_api/search_api.admin.inc @@ -252,6 +252,13 @@ function search_api_admin_add_server(array $form, array &$form_state) { $form['options']['#prefix'] = '
'; $form['options']['#suffix'] = '
'; + // If $info is not set, there are no service classes. Display an error message + // telling the user how to change that and return an empty form. + if (!isset($info)) { + drupal_set_message(t('There are no service classes available for the Search API. Please install a module that provides a service class to proceed.', array('@url' => url('https://www.drupal.org/node/1254698'))), 'error'); + return array(); + } + $form['submit'] = array( '#type' => 'submit', '#value' => t('Create server'), @@ -834,6 +841,15 @@ function search_api_admin_index_view(SearchApiIndex $index, $action = NULL) { } $status = search_api_index_status($index); + try { + $server = $index->server(); + } + catch (SearchApiException $e) { + $server = NULL; + $vars['%server'] = $index->server; + $message = t('The index has an unknown server (ID: %server) set. Please check the index settings.', $vars); + drupal_set_message($message, 'error'); + } $ret['view'] = array( '#theme' => 'search_api_index', '#id' => $index->id, @@ -842,15 +858,21 @@ function search_api_admin_index_view(SearchApiIndex $index, $action = NULL) { '#description' => $index->description, '#item_type' => $index->item_type, '#enabled' => $index->enabled, - '#server' => $index->server(), + '#server' => $server, '#options' => $index->options, '#fields' => $index->getFields(), '#indexed_items' => $status['indexed'], - '#on_server' => _search_api_get_items_on_server($index), + '#on_server' => NULL, '#total_items' => $status['total'], '#status' => $index->status, '#read_only' => $index->read_only, ); + try{ + $ret['view']['#on_server'] = _search_api_get_items_on_server($index); + } + catch (SearchApiException $e) { + watchdog_exception('search_api', $e); + } if ($index->enabled && !$index->read_only) { $ret['form'] = drupal_get_form('search_api_admin_index_status_form', $index, $status); } @@ -873,7 +895,8 @@ function search_api_admin_index_view(SearchApiIndex $index, $action = NULL) { * - fields: All indexed fields of the index. * - indexed_items: The number of items already indexed in their latest * version on this index. - * - on_server: The number of items actually indexed on the server. + * - on_server: The number of items actually indexed on the server. NULL if + * the search for finding out the item count failed. * - total_items: The total number of items that have to be indexed for this * index. * - status: The entity configuration status (in database, in code, etc.). @@ -963,15 +986,21 @@ function theme_search_api_index(array $variables) { $rows[] = _search_api_deep_copy($row); $theme = array( - 'percent' => (int) (100 * $indexed_items / $total_items), + 'percent' => $total_items ? (int) (100 * $indexed_items / $total_items) : 100, 'message' => t('@indexed/@total indexed', array('@indexed' => $indexed_items, '@total' => $total_items)), ); $output .= '

' . t('Index status') . '

'; $output .= '
' . theme('progress_bar', $theme) . '
'; - $vars['@url'] = url('https://drupal.org/node/2009804#server-index-status'); - $info = format_plural($on_server, 'There is 1 item indexed on the server for this index. (More information)', 'There are @count items indexed on the server for this index. (More information)', $vars); - $class = ''; + if (!isset($on_server)) { + $info = t('An error occurred while trying to determine the server index status. Please check the logs for details.'); + $class = 'error'; + } + else { + $vars['@url'] = url('https://drupal.org/node/2009804#server-index-status'); + $info = format_plural($on_server, 'There is 1 item indexed on the server for this index. (More information)', 'There are @count items indexed on the server for this index. (More information)', $vars); + $class = ''; + } $label = t('Server index status'); $rows[] = _search_api_deep_copy($row); } @@ -1178,12 +1207,21 @@ function search_api_admin_index_edit(array $form, array &$form_state, SearchApiI '#default_value' => $index->name, '#required' => TRUE, ); + try { + $enabled_fixed = !$index->enabled && !$index->server(); + } + catch (Exception $e) { + watchdog_exception('search_api', $e); + // The exception only occurs if the index is disabled, and for an unknown + // server we of course want do prevent the index from being enabled. + $enabled_fixed = TRUE; + } $form['enabled'] = array( '#type' => 'checkbox', '#title' => t('Enabled'), '#default_value' => $index->enabled, // Can't enable an index lying on a disabled server, or no server at all. - '#disabled' => !$index->enabled && (!$index->server() || !$index->server()->enabled), + '#disabled' => $enabled_fixed, ); $form['description'] = array( '#type' => 'textarea', @@ -1565,6 +1603,7 @@ function search_api_admin_index_workflow_submit(array $form, array &$form_state) foreach ($form_state['callbacks'] as $name => $callback) { // Check whether callback status has changed. if ($values['callbacks'][$name]['status'] == empty($options['data_alter_callbacks'][$name]['status'])) { + $callbacks_changed = TRUE; if ($values['callbacks'][$name]['status']) { // Callback was just enabled, add its fields. $properties = $callback->propertyInfo(); @@ -1591,16 +1630,6 @@ function search_api_admin_index_workflow_submit(array $form, array &$form_state) } } } - else { - // Callback was just disabled, remove its fields. - $properties = $callback->propertyInfo(); - if ($properties) { - foreach ($properties as $key => $field) { - unset($index->options['fields'][$key]); - } - } - - } } } @@ -1614,9 +1643,9 @@ function search_api_admin_index_workflow_submit(array $form, array &$form_state) uasort($index->options['data_alter_callbacks'], 'search_api_admin_element_compare'); uasort($index->options['processors'], 'search_api_admin_element_compare'); - // Reset the index's internal property cache to correctly incorporate the - // new data alterations. - $index->resetCaches(); + // Re-calculate the fields, since they might have changed in hard-to-predict + // ways. + search_api_index_recalculate_fields(array($index)); $index->save(); $index->reindex(); @@ -1658,9 +1687,11 @@ 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) { @@ -2210,8 +2241,16 @@ function search_api_admin_confirm_submit(array $form, array &$form_state) { $action = $values['action']; $id = $values['id']; + $success = FALSE; $function = "search_api_{$type}_{$action}"; - if ($function($id)) { + try { + // Some actions, like disabling, can actually throw an exception. + $success = $function($id); + } + catch (SearchApiException $e) { + watchdog_exception('search_api', $e); + } + if ($success) { drupal_set_message($values['message']); } else { diff --git a/sites/all/modules/contrib/search/search_api/search_api.api.php b/sites/all/modules/contrib/search/search_api/search_api.api.php index c1af2a14..550ecbc2 100644 --- a/sites/all/modules/contrib/search/search_api/search_api.api.php +++ b/sites/all/modules/contrib/search/search_api/search_api.api.php @@ -335,7 +335,7 @@ function hook_search_api_items_indexed(SearchApiIndex $index, array $item_ids) { * Lets modules alter a search query before executing it. * * @param SearchApiQueryInterface $query - * The SearchApiQueryInterface object representing the search query. + * The search query being executed. */ function hook_search_api_query_alter(SearchApiQueryInterface $query) { // Exclude entities with ID 0. (Assume the ID field is always indexed.) diff --git a/sites/all/modules/contrib/search/search_api/search_api.drush.inc b/sites/all/modules/contrib/search/search_api/search_api.drush.inc index e1841e8f..927514d0 100644 --- a/sites/all/modules/contrib/search/search_api/search_api.drush.inc +++ b/sites/all/modules/contrib/search/search_api/search_api.drush.inc @@ -108,6 +108,19 @@ function search_api_drush_command() { 'aliases' => array('sapi-c'), ); + $items['search-api-set-index-server'] = array( + 'description' => 'Set the search server used by a given index.', + 'examples' => array( + 'drush search-api-set-index-server default_node_index my_solr_server' => dt('Set the !index index to use the !server server.', array('!index' => 'default_node_index', '!server' => 'my_solr_server')), + 'drush sapi-sis default_node_index my_solr_server' => dt('Alias to set the !index index to use the !server server.', array('!index' => 'default_node_index', '!server' => 'my_solr_server')), + ), + 'arguments' => array( + 'index_id' => dt('The numeric ID or machine name of an index.'), + 'server_id' => dt('The numeric ID or machine name of a server to set on the index.'), + ), + 'aliases' => array('sapi-sis'), + ); + return $items; } @@ -137,15 +150,21 @@ function drush_search_api_list() { foreach ($indexes as $index) { $type = search_api_get_item_type_info($index->item_type); $type = isset($type['name']) ? $type['name'] : $index->item_type; - $server = $index->server(); - $server = $server ? $server->name : '(' . t('none') . ')'; + try { + $server = $index->server(); + $server = $server ? $server->name : '(' . dt('none') . ')'; + } + catch (SearchApiException $e) { + watchdog_exception('search_api', $e); + $server = '(' . dt('unknown: !server', array('server' => $index->server)) . ')'; + } $row = array( $index->id, $index->name, $index->machine_name, $server, $type, - $index->enabled ? t('enabled') : t('disabled'), + $index->enabled ? dt('enabled') : dt('disabled'), $index->options['cron_limit'], ); $rows[] = $row; @@ -168,17 +187,24 @@ function drush_search_api_enable($index_id = NULL) { return; } foreach ($indexes as $index) { + $vars = array('!index' => $index->name); if (!$index->enabled) { - drush_log(dt("Enabling index !index and queueing items for indexing.", array('!index' => $index->name)), 'notice'); - if (search_api_index_enable($index->id)) { - drush_log(dt("The index !index was successfully enabled.", array('!index' => $index->name)), 'ok'); + drush_log(dt("Enabling index !index and queueing items for indexing.", $vars), 'notice'); + $success = FALSE; + try { + if ($success = search_api_index_enable($index->id)) { + drush_log(dt("The index !index was successfully enabled.", $vars), 'ok'); + } } - else { - drush_log(dt("Error enabling index !index.", array('!index' => $index->name)), 'error'); + catch (SearchApiException $e) { + drush_log($e->getMessage(), 'error'); + } + if (!$success) { + drush_log(dt("Error enabling index !index.", $vars), 'error'); } } else { - drush_log(dt("The index !index is already enabled.", array('!index' => $index->name)), 'error'); + drush_log(dt("The index !index is already enabled.", $vars), 'error'); } } } @@ -198,16 +224,23 @@ function drush_search_api_disable($index_id = NULL) { return; } foreach ($indexes as $index) { + $vars = array('!index' => $index->name); if ($index->enabled) { - if (search_api_index_disable($index->id)) { - drush_log(dt("The index !index was successfully disabled.", array('!index' => $index->name)), 'ok'); + $success = FALSE; + try { + if ($success = search_api_index_disable($index->id)) { + drush_log(dt("The index !index was successfully disabled.", $vars), 'ok'); + } } - else { - drush_log(dt("Error disabling index !index.", array('!index' => $index->name)), 'error'); + catch (SearchApiException $e) { + drush_log($e->getMessage(), 'error'); + } + if (!$success) { + drush_log(dt("Error disabling index !index.", $vars), 'error'); } } else { - drush_log(dt("The index !index is already disabled.", array('!index' => $index->name)), 'error'); + drush_log(dt("The index !index is already disabled.", $vars), 'error'); } } } @@ -264,40 +297,72 @@ function drush_search_api_index($index_id = NULL, $limit = NULL, $batch_size = N if (empty($indexes)) { return; } + + $process_batch = FALSE; foreach ($indexes as $index) { - // Get the number of remaing items to index. - $datasource = $index->datasource(); - $index_status = $datasource->getIndexStatus($index); - $remaining = $index_status['total'] - $index_status['indexed']; - if ($remaining <= 0) { - drush_log(dt("The index !index is up to date.", array('!index' => $index->name)), 'ok'); - continue; - } - - // Get the number of items to index per batch run. - if (!isset($batch_size)) { - $batch_size = empty($index->options['cron_limit']) ? SEARCH_API_DEFAULT_CRON_LIMIT : $index->options['cron_limit']; - } - elseif ($batch_size <= 0) { - $batch_size = $remaining; - } - - // Get the number items to index. - if (!isset($limit) || !is_int($limit += 0) || $limit <= 0) { - $limit = $remaining; - } - - drush_log(dt("Indexing a maximum number of !limit items (!batch_size items per batch run) for the index !index.", array('!index' => $index->name, '!limit' => $limit, '!batch_size' => $batch_size)), 'ok'); - - // Create the batch. - if (!_search_api_batch_indexing_create($index, $batch_size, $limit, $remaining, TRUE)) { - drush_log(dt("Couldn't create a batch, please check the batch size and limit parameters."), 'error'); - } - else { - // Launch the batch process. - drush_backend_batch_process(); + if (_drush_search_api_batch_indexing_create($index, $limit, $batch_size)) { + $process_batch = TRUE; } } + + if ($process_batch) { + drush_backend_batch_process(); + } +} + +/** + * Creates and sets a batch for indexing items for a particular index. + * + * @param SearchApiIndex $index + * The index for which items should be indexed. + * @param int $limit + * (optional) The maximum number of items to index, or NULL to index all + * items. + * @param int $batch_size + * (optional) The number of items to index per batch, or NULL to index all + * items at once. + * + * @return bool + * TRUE if batch was created, FALSE otherwise. + */ +function _drush_search_api_batch_indexing_create(SearchApiIndex $index, $limit = NULL, $batch_size = NULL) { + // Get the number of remaining items to index. + try { + $datasource = $index->datasource(); + } + catch (SearchApiException $e) { + drush_log($e->getMessage(), 'error'); + return FALSE; + } + $index_status = $datasource->getIndexStatus($index); + $remaining = $index_status['total'] - $index_status['indexed']; + if ($remaining <= 0) { + drush_log(dt("The index !index is up to date.", array('!index' => $index->name)), 'ok'); + return FALSE; + } + + // Get the number of items to index per batch run. + if (!isset($batch_size)) { + $batch_size = empty($index->options['cron_limit']) ? SEARCH_API_DEFAULT_CRON_LIMIT : $index->options['cron_limit']; + } + elseif ($batch_size <= 0) { + $batch_size = $remaining; + } + + // Get the total number of items to index. + if (!isset($limit) || !is_int($limit += 0) || $limit <= 0) { + $limit = $remaining; + } + + drush_log(dt("Indexing a maximum number of !limit items (!batch_size items per batch run) for the index !index.", array('!index' => $index->name, '!limit' => $limit, '!batch_size' => $batch_size)), 'ok'); + + // Create the batch. + if (!_search_api_batch_indexing_create($index, $batch_size, $limit, $remaining, TRUE)) { + drush_log(dt("Couldn't create a batch, please check the batch size and limit parameters."), 'error'); + return FALSE; + } + + return TRUE; } /** @@ -367,12 +432,54 @@ function drush_search_api_clear($index_id = NULL) { } /** - * Helper function to return an index or all indexes as an array. + * Set the server for a given index. + */ +function drush_search_api_set_index_server($index_id = NULL, $server_id = NULL) { + if (search_api_drush_static(__FUNCTION__)) { + return; + } + // Make sure we have parameters to work with. + if (empty($index_id) || empty($server_id)) { + drush_log(dt('You must specify both an index and server.'), 'error'); + return; + } + // Fetch current index and server data. + $indexes = search_api_drush_get_index($index_id); + $servers = search_api_drush_get_server($server_id); + if (empty($indexes) || empty($servers)) { + // If the specified index or server can't be found, just return. An + // appropriate error message should have been printed already. + return; + } + // Set the new server on the index. + $success = FALSE; + $index = reset($indexes); + $server = reset($servers); + try { + $success = $index->update(array('server' => $server->machine_name)); + } + catch (SearchApiException $e) { + drush_log($e->getMessage(), 'error'); + } + if ($success === FALSE) { + drush_log(dt('There was an error setting index !index to use server !server.', array('!index' => $index->name, '!server' => $server->name)), 'error'); + } + elseif (!$success) { + drush_log(dt('Index !index was already using server !server.', array('!index' => $index->name, '!server' => $server->name)), 'ok'); + } + else { + drush_log(dt('Index !index has been set to use server !server and items have been queued for indexing.', array('!index' => $index->name, '!server' => $server->name)), 'ok'); + } +} + +/** + * Returns an index or all indexes as an array. * - * @param $index_id - * (optional) The provided index id. + * @param string|int|null $index_id + * (optional) The ID or machine name of the index to load. Defaults to + * loading all available indexes. * - * @return + * @return SearchApiIndex[] * An array of indexes. */ function search_api_drush_get_index($index_id = NULL) { @@ -387,7 +494,34 @@ function search_api_drush_get_index($index_id = NULL) { } /** - * Static lookup to prevent Drush 4 from running twice. + * Returns a server or all servers as an array. + * + * @param string|int|null $server_id + * (optional) The ID or machine name of the server to load. Defaults to + * loading all available servers. + * + * @return SearchApiServer[] + * An array of servers. + */ +function search_api_drush_get_server($server_id = NULL) { + $ids = isset($server_id) ? array($server_id) : FALSE; + $servers = search_api_server_load_multiple($ids); + if (empty($servers)) { + drush_set_error(dt('Invalid server_id or no servers present.')); + // @todo: Maybe add logic to print table of all servers. + } + return $servers; +} + +/** + * Does a static lookup to prevent Drush 4 from running twice. + * + * @param string $function + * The Drush function being called. + * + * @return bool + * TRUE if the function was already called in this Drush execution, FALSE + * otherwise. * * @see http://drupal.org/node/704848 */ @@ -397,4 +531,5 @@ function search_api_drush_static($function) { return TRUE; } $index[$function] = TRUE; + return FALSE; } diff --git a/sites/all/modules/contrib/search/search_api/search_api.info b/sites/all/modules/contrib/search/search_api/search_api.info index 37e52c83..1d67252e 100644 --- a/sites/all/modules/contrib/search/search_api/search_api.info +++ b/sites/all/modules/contrib/search/search_api/search_api.info @@ -34,9 +34,9 @@ files[] = includes/service.inc configure = admin/config/search/search_api -; Information added by Drupal.org packaging script on 2013-12-25 -version = "7.x-1.11" +; Information added by Drupal.org packaging script on 2014-12-26 +version = "7.x-1.14" core = "7.x" project = "search_api" -datestamp = "1387965506" +datestamp = "1419580682" diff --git a/sites/all/modules/contrib/search/search_api/search_api.install b/sites/all/modules/contrib/search/search_api/search_api.install index 86812a4c..850b8280 100644 --- a/sites/all/modules/contrib/search/search_api/search_api.install +++ b/sites/all/modules/contrib/search/search_api/search_api.install @@ -349,8 +349,13 @@ function search_api_enable() { } } foreach ($types as $type => $indexes) { - $controller = search_api_get_datasource_controller($type); - $controller->startTracking($indexes); + try { + $controller = search_api_get_datasource_controller($type); + $controller->startTracking($indexes); + } + catch (SearchApiException $e) { + watchdog_exception('search_api', $e); + } } } @@ -961,6 +966,10 @@ function search_api_update_7116() { $insert = db_insert('search_api_task') ->fields(array('server_id', 'type', 'index_id', 'data')); foreach ($tasks as $task) { + $task += array( + 'index_id' => NULL, + 'data' => NULL, + ); $insert->values($task); } $insert->execute(); diff --git a/sites/all/modules/contrib/search/search_api/search_api.module b/sites/all/modules/contrib/search/search_api/search_api.module index 17f611a2..ddffb00a 100644 --- a/sites/all/modules/contrib/search/search_api/search_api.module +++ b/sites/all/modules/contrib/search/search_api/search_api.module @@ -275,7 +275,7 @@ function search_api_theme() { 'options' => array(), 'fields' => array(), 'indexed_items' => 0, - 'on_server' => 0, + 'on_server' => NULL, 'total_items' => 0, 'status' => ENTITY_CUSTOM, 'read_only' => 0, @@ -662,9 +662,21 @@ function search_api_search_api_index_update(SearchApiIndex $index) { } if ($index->server) { - $new_server = $index->server(TRUE); - // If the server is enabled, we call addIndex(); otherwise, we save the task. - $new_server->addIndex($index); + try { + $new_server = $index->server(TRUE); + // If the server is enabled, we call addIndex(); otherwise, we save the task. + $new_server->addIndex($index); + } + catch (SearchApiException $e) { + watchdog_exception('search_api', $e); + // If the new server doesn't exist, we remove the index from all + // servers. Note that saving an entity in its own update hook is usually + // a recipe for disaster, but since we are only doing this if a server + // is set and remove the server here before saving, it should be safe + // enough. + $index->server = NULL; + $index->save(); + } } // We also have to re-index all content. @@ -672,28 +684,17 @@ function search_api_search_api_index_update(SearchApiIndex $index) { } // If the fields were changed, call the appropriate service class hook method - // and re-index the content, if necessary. Also, clear the fields cache. + // and re-index the content, if necessary. $old_fields = $index->original->options + array('fields' => array()); $old_fields = $old_fields['fields']; $new_fields = $index->options + array('fields' => array()); $new_fields = $new_fields['fields']; if ($old_fields != $new_fields) { - cache_clear_all($index->getCacheId(), 'cache', TRUE); if ($index->server) { $index->server()->fieldsUpdated($index); } } - // If additional fields changed, clear the index's specific cache which - // includes them. - $old_additional = $index->original->options + array('additional fields' => array()); - $old_additional = $old_additional['additional fields']; - $new_additional = $index->options + array('additional fields' => array()); - $new_additional = $new_additional['additional fields']; - if ($old_additional != $new_additional) { - cache_clear_all($index->getCacheId() . '-0-1', 'cache'); - } - // If the index's enabled or read-only status is being changed, queue or // dequeue items for indexing. if (!$index->read_only && $index->enabled != $index->original->enabled) { @@ -1098,7 +1099,14 @@ function search_api_track_item_insert($type, array $item_ids) { return; } - search_api_get_datasource_controller($type)->trackItemInsert($item_ids, $indexes); + try { + search_api_get_datasource_controller($type)->trackItemInsert($item_ids, $indexes); + } + catch (SearchApiException $e) { + $vars['%item_type'] = $type; + watchdog_exception('search_api', $e, '%type while inserting items of type %item_type: !message in %function (line %line of %file).', $vars); + return; + } foreach ($indexes as $index) { if (!empty($index->options['index_directly'])) { @@ -1128,19 +1136,26 @@ function search_api_track_item_change($type, array $item_ids) { if (!$indexes) { return; } - search_api_get_datasource_controller($type)->trackItemChange($item_ids, $indexes); - foreach ($indexes as $index) { - if (!empty($index->options['index_directly'])) { - // For indexes with the index_directly option set, queue the items to be - // indexed at the end of the request. - try { - search_api_index_specific_items_delayed($index, $item_ids); - } - catch (SearchApiException $e) { - watchdog_exception('search_api', $e); + try { + search_api_get_datasource_controller($type)->trackItemChange($item_ids, $indexes); + foreach ($indexes as $index) { + if (!empty($index->options['index_directly'])) { + // For indexes with the index_directly option set, queue the items to be + // indexed at the end of the request. + try { + search_api_index_specific_items_delayed($index, $item_ids); + } + catch (SearchApiException $e) { + watchdog_exception('search_api', $e); + } } } } + catch (SearchApiException $e) { + $vars['%item_type'] = $type; + watchdog_exception('search_api', $e, '%type while updating items of type %item_type: !message in %function (line %line of %file).', $vars); + return; + } } /** @@ -1158,7 +1173,12 @@ function search_api_track_item_change($type, array $item_ids) { * the Drupal 8 version of this module. */ function search_api_track_item_queued(SearchApiIndex $index, array $item_ids) { - $index->datasource()->trackItemQueued($item_ids, $index); + try { + $index->datasource()->trackItemQueued($item_ids, $index); + } + catch (SearchApiException $e) { + watchdog_exception('search_api', $e); + } } /** @@ -1191,16 +1211,27 @@ function search_api_track_item_delete($type, array $item_ids) { ); $indexes = search_api_index_load_multiple(FALSE, $conditions); if ($indexes) { - search_api_get_datasource_controller($type)->trackItemDelete($item_ids, $indexes); + try { + search_api_get_datasource_controller($type)->trackItemDelete($item_ids, $indexes); + } + catch (SearchApiException $e) { + $vars['%item_type'] = $type; + watchdog_exception('search_api', $e, '%type while deleting items of type %item_type: !message in %function (line %line of %file).', $vars); + } } // Then, delete it from all servers. Servers of disabled indexes have to be // considered, too! unset($conditions['enabled']); foreach (search_api_index_load_multiple(FALSE, $conditions) as $index) { - if ($index->server) { - $server = $index->server(); - $server->deleteItems($item_ids, $index); + try { + if ($server = $index->server()) { + $server->deleteItems($item_ids, $index); + } + } + catch (Exception $e) { + $vars['%item_type'] = $type; + watchdog_exception('search_api', $e, '%type while deleting items of type %item_type: !message in %function (line %line of %file).', $vars); } } } @@ -1389,7 +1420,7 @@ function search_api_index_recalculate_fields($indexes = FALSE) { } // Use a more accurate method of determining if the fields settings are // equal to avoid needlessly re-indexing the whole index. - if (!_search_api_settings_equals($fields, $index->options['fields'])) { + if ($fields != $index->options['fields']) { $options = $index->options; $options['fields'] = $fields; $index->update(array('options' => $options)); @@ -1400,9 +1431,6 @@ function search_api_index_recalculate_fields($indexes = FALSE) { /** * Test two setting arrays (or individual settings) for equality. * - * While a simple == also works in some cases, this function takes into account - * that the order of keys (usually) doesn't matter in settings arrays. - * * @param mixed $setting1 * The first setting (array). * @param mixed $setting2 @@ -1410,6 +1438,8 @@ function search_api_index_recalculate_fields($indexes = FALSE) { * * @return bool * TRUE if both settings are identical, FALSE otherwise. + * + * @deprecated The simple "==" operator will achieve the same. */ function _search_api_settings_equals($setting1, $setting2) { if (!is_array($setting1) || !is_array($setting2)) { @@ -1495,10 +1525,7 @@ function search_api_index_specific_items(SearchApiIndex $index, array $ids) { // some specific setups. $type = search_api_get_item_type_info($index->item_type); $type = $type ? $type['name'] : $index->item_type; - watchdog('search_api', - "Error during indexing: invalid item loaded for @type with ID @id.", - array('@id' => $id, '@type' => $type), - WATCHDOG_WARNING); + watchdog('search_api', "Error during indexing: invalid item loaded for @type with ID @id.", array('@id' => $id, '@type' => $type), WATCHDOG_WARNING); } } $indexed = $items ? $index->index($cloned_items) : array(); @@ -1578,25 +1605,14 @@ function search_api_get_items_to_index(SearchApiIndex $index, $limit = -1) { * @param $id * The ID or machine name of the index to execute the search on. * @param $options - * An associative array of options. The following are recognized: - * - filters: Either a SearchApiQueryFilterInterface object or an array of - * filters used to filter the search. - * - sort: An array of sort directives of the form $field => $order, where - * $order is either 'ASC' or 'DESC'. - * - offset: The position of the first returned search results relative to the - * whole result in the index. - * - limit: The maximum number of search results to return. -1 means no limit. - * - 'query class': The query class to use. Must be a subtype of - * SearchApiQueryInterface. - * - conjunction: The type of conjunction to use for this query - either - * 'AND' or 'OR'. 'AND' by default. - * - 'parse mode': The mode with which to parse the $keys variable, if it - * is set and not already an array. See SearchApiQuery::parseModes() for - * parse modes recognized by the SearchApiQuery class. - * Subclasses might define additional modes. + * An associative array of options to be passed to + * SearchApiQueryInterface::__construct(). * * @return SearchApiQueryInterface * An object for searching on the specified index. + * + * @throws SearchApiException + * If the index is unknown or disabled, or some other error was encountered. */ function search_api_query($id, array $options = array()) { $index = search_api_index_load($id); @@ -1607,9 +1623,10 @@ function search_api_query($id, array $options = array()) { } /** - * Static store for the searches executed on the current page. Can either be - * used to store an executed search, or to retrieve a previously stored - * search. + * Stores or retrieves a search executed in this page request. + * + * Static storage for the searches executed during the current page request. Can + * used to store an executed search, or to retrieve a previously stored search. * * @param $search_id * For pages displaying multiple searches, an optional ID identifying the @@ -1935,7 +1952,14 @@ function search_api_search_api_query_alter(SearchApiQueryInterface $query) { } } else { - watchdog('search_api', 'An illegal user UID was given for node access: @uid.', array('@uid' => $query->getOption('search_api_access_account', $user)), WATCHDOG_WARNING); + $account = $query->getOption('search_api_access_account', '(' . t('none') . ')'); + if (is_object($account)) { + $account = $account->uid; + } + if (!is_scalar($account)) { + $account = var_export($account, TRUE); + } + watchdog('search_api', 'An illegal user UID was given for node access: @uid.', array('@uid' => $account), WATCHDOG_WARNING); } } } @@ -1994,7 +2018,6 @@ 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); } @@ -2225,7 +2248,7 @@ function search_api_extract_fields(EntityMetadataWrapper $wrapper, array $fields # 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); + // $nested_fields = search_api_extract_fields($subwrapper, $nested_fields, $value_options); foreach ($nested_fields as $field => $info) { $fields["$prefix:$field"] = $info; } @@ -2561,12 +2584,18 @@ function search_api_index_url(SearchApiIndex $index) { * @param SearchApiIndex $index * The index whose server should be returned. * - * @return SearchApiServer + * @return SearchApiServer|null * The server this index currently resides on, or NULL if the index is * currently unassigned. */ function search_api_index_get_server(SearchApiIndex $index) { - return $index->server(); + try { + return $index->server(); + } + catch (SearchApiException $e) { + watchdog_exception('search_api', $e); + return NULL; + } } /** @@ -2643,14 +2672,14 @@ function search_api_index_edit_fields($id, array $fields) { /** * Enables a search index. * - * @param $id + * @param string|int $id * The ID or machine name of the index to enable. * - * @throws SearchApiException - * If the index' server isn't enabled. - * - * @return + * @return int|false * 1 on success, 0 or FALSE on failure. + * + * @throws SearchApiException + * If the index's server doesn't exist. */ function search_api_index_enable($id) { $index = search_api_index_load($id, TRUE); @@ -2661,11 +2690,14 @@ function search_api_index_enable($id) { /** * Disables a search index. * - * @param $id + * @param string|int $id * The ID or machine name of the index to disable. * - * @return + * @return int|false * 1 on success, 0 or FALSE on failure. + * + * @throws SearchApiException + * If the index's server doesn't exist. */ function search_api_index_disable($id) { $index = search_api_index_load($id, TRUE); @@ -2813,6 +2845,9 @@ function _search_api_convert_custom_type($callback, $value, $original_type, $typ * @return int * The number of items found on the server for this index, if the latter is * enabled. 0 otherwise. + * + * @throws SearchApiException + * If an error prevented the search from completing. */ function _search_api_get_items_on_server(SearchApiIndex $index) { if (!$index->enabled) { diff --git a/sites/all/modules/contrib/search/search_api/search_api.rules.inc b/sites/all/modules/contrib/search/search_api/search_api.rules.inc index eef4cc08..37143f87 100644 --- a/sites/all/modules/contrib/search/search_api/search_api.rules.inc +++ b/sites/all/modules/contrib/search/search_api/search_api.rules.inc @@ -52,6 +52,9 @@ function _search_api_rules_access() { * Rules action for indexing an item. */ function _search_api_rules_action_index(EntityDrupalWrapper $wrapper, SearchApiIndex $index = NULL, $index_immediately = TRUE) { + // If we do not have an index, we need to guess the item type to use. + // @todo Since this can only be used with entities anyways, we can just loop + // over the item type information and use all types with that entity type. $type = $wrapper->type(); $item_ids = array($wrapper->getIdentifier()); @@ -61,6 +64,7 @@ function _search_api_rules_action_index(EntityDrupalWrapper $wrapper, SearchApiI } if ($index) { + $type = $index->item_type; $indexes = array($index); } else { diff --git a/sites/all/modules/contrib/search/search_api/tests/search_api_test.info b/sites/all/modules/contrib/search/search_api/tests/search_api_test.info index 07cf4ad3..e1b377d1 100644 --- a/sites/all/modules/contrib/search/search_api/tests/search_api_test.info +++ b/sites/all/modules/contrib/search/search_api/tests/search_api_test.info @@ -10,9 +10,9 @@ files[] = search_api_test.module hidden = TRUE -; Information added by Drupal.org packaging script on 2013-12-25 -version = "7.x-1.11" +; Information added by Drupal.org packaging script on 2014-12-26 +version = "7.x-1.14" core = "7.x" project = "search_api" -datestamp = "1387965506" +datestamp = "1419580682"