query_type_term.inc 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. <?php
  2. /**
  3. * @file
  4. * Term query type plugin for the Apache Solr adapter.
  5. */
  6. /**
  7. * Plugin for "term" query types.
  8. */
  9. class SearchApiFacetapiTerm extends FacetapiQueryType implements FacetapiQueryTypeInterface {
  10. /**
  11. * Returns the query type associated with the plugin.
  12. *
  13. * @return string
  14. * The query type.
  15. */
  16. static public function getType() {
  17. return 'term';
  18. }
  19. /**
  20. * Adds the filter to the query object.
  21. *
  22. * @param SearchApiQueryInterface $query
  23. * An object containing the query in the backend's native API.
  24. */
  25. public function execute($query) {
  26. // Return terms for this facet.
  27. $this->adapter->addFacet($this->facet, $query);
  28. $settings = $this->getSettings()->settings;
  29. // First check if the facet is enabled for this search.
  30. $default_true = isset($settings['default_true']) ? $settings['default_true'] : TRUE;
  31. $facet_search_ids = isset($settings['facet_search_ids']) ? $settings['facet_search_ids'] : array();
  32. if ($default_true != empty($facet_search_ids[$query->getOption('search id')])) {
  33. // Facet is not enabled for this search ID.
  34. return;
  35. }
  36. // Retrieve the active facet filters.
  37. $active = $this->adapter->getActiveItems($this->facet);
  38. if (empty($active)) {
  39. return;
  40. }
  41. // Create the facet filter, and add a tag to it so that it can be easily
  42. // identified down the line by services when they need to exclude facets.
  43. $operator = $settings['operator'];
  44. if ($operator == FACETAPI_OPERATOR_AND) {
  45. $conjunction = 'AND';
  46. }
  47. elseif ($operator == FACETAPI_OPERATOR_OR) {
  48. $conjunction = 'OR';
  49. // When the operator is OR, remove parent terms from the active ones if
  50. // children are active. If we don't do this, sending a term and its
  51. // parent will produce the same results as just sending the parent.
  52. if (is_callable($this->facet['hierarchy callback']) && !$settings['flatten']) {
  53. // Check the filters in reverse order, to avoid checking parents that
  54. // will afterwards be removed anyways.
  55. $values = array_keys($active);
  56. $parents = call_user_func($this->facet['hierarchy callback'], $values);
  57. foreach (array_reverse($values) as $filter) {
  58. // Skip this filter if it was already removed, or if it is the
  59. // "missing value" filter ("!").
  60. if (!isset($active[$filter]) || !is_numeric($filter)) {
  61. continue;
  62. }
  63. // Go through the entire hierarchy of the value and remove all its
  64. // ancestors.
  65. while (!empty($parents[$filter])) {
  66. $ancestor = array_shift($parents[$filter]);
  67. if (isset($active[$ancestor])) {
  68. unset($active[$ancestor]);
  69. if (!empty($parents[$ancestor])) {
  70. $parents[$filter] = array_merge($parents[$filter], $parents[$ancestor]);
  71. }
  72. }
  73. }
  74. }
  75. }
  76. }
  77. else {
  78. $vars = array(
  79. '%operator' => $operator,
  80. '%facet' => !empty($this->facet['label']) ? $this->facet['label'] : $this->facet['name'],
  81. );
  82. watchdog('search_api_facetapi', 'Unknown facet operator %operator used for facet %facet.', $vars, WATCHDOG_WARNING);
  83. return;
  84. }
  85. $tags = array('facet:' . $this->facet['field']);
  86. $facet_filter = $query->createFilter($conjunction, $tags);
  87. foreach ($active as $filter => $filter_array) {
  88. $field = $this->facet['field'];
  89. $this->addFacetFilter($facet_filter, $field, $filter);
  90. }
  91. // Now add the filter to the query.
  92. $query->filter($facet_filter);
  93. }
  94. /**
  95. * Helper method for setting a facet filter on a query or query filter object.
  96. */
  97. protected function addFacetFilter($query_filter, $field, $filter) {
  98. // Test if this filter should be negated.
  99. $settings = $this->adapter->getFacet($this->facet)->getSettings();
  100. $exclude = !empty($settings->settings['exclude']);
  101. // Integer (or other non-string) filters might mess up some of the following
  102. // comparison expressions.
  103. $filter = (string) $filter;
  104. if ($filter == '!') {
  105. $query_filter->condition($field, NULL, $exclude ? '<>' : '=');
  106. }
  107. elseif ($filter && $filter[0] == '[' && $filter[strlen($filter) - 1] == ']' && ($pos = strpos($filter, ' TO '))) {
  108. $lower = trim(substr($filter, 1, $pos));
  109. $upper = trim(substr($filter, $pos + 4, -1));
  110. if ($lower == '*' && $upper == '*') {
  111. $query_filter->condition($field, NULL, $exclude ? '=' : '<>');
  112. }
  113. elseif (!$exclude) {
  114. if ($lower != '*') {
  115. // Iff we have a range with two finite boundaries, we set two
  116. // conditions (larger than the lower bound and less than the upper
  117. // bound) and therefore have to make sure that we have an AND
  118. // conjunction for those.
  119. if ($upper != '*' && !($query_filter instanceof SearchApiQueryInterface || $query_filter->getConjunction() === 'AND')) {
  120. $original_query_filter = $query_filter;
  121. $query_filter = new SearchApiQueryFilter('AND');
  122. }
  123. $query_filter->condition($field, $lower, '>=');
  124. }
  125. if ($upper != '*') {
  126. $query_filter->condition($field, $upper, '<=');
  127. }
  128. }
  129. else {
  130. // Same as above, but with inverted logic.
  131. if ($lower != '*') {
  132. if ($upper != '*' && ($query_filter instanceof SearchApiQueryInterface || $query_filter->getConjunction() === 'AND')) {
  133. $original_query_filter = $query_filter;
  134. $query_filter = new SearchApiQueryFilter('OR');
  135. }
  136. $query_filter->condition($field, $lower, '<');
  137. }
  138. if ($upper != '*') {
  139. $query_filter->condition($field, $upper, '>');
  140. }
  141. }
  142. }
  143. else {
  144. $query_filter->condition($field, $filter, $exclude ? '<>' : '=');
  145. }
  146. if (isset($original_query_filter)) {
  147. $original_query_filter->filter($query_filter);
  148. }
  149. }
  150. /**
  151. * Initializes the facet's build array.
  152. *
  153. * @return array
  154. * The initialized render array.
  155. */
  156. public function build() {
  157. $facet = $this->adapter->getFacet($this->facet);
  158. // The current search per facet is stored in a static variable (during
  159. // initActiveFilters) so that we can retrieve it here and get the correct
  160. // current search for this facet.
  161. $search_ids = drupal_static('search_api_facetapi_active_facets', array());
  162. $facet_key = $facet['name'] . '@' . $this->adapter->getSearcher();
  163. if (empty($search_ids[$facet_key]) || !search_api_current_search($search_ids[$facet_key])) {
  164. return array();
  165. }
  166. $search_id = $search_ids[$facet_key];
  167. list(, $results) = search_api_current_search($search_id);
  168. $build = array();
  169. // Always include the active facet items.
  170. foreach ($this->adapter->getActiveItems($this->facet) as $filter) {
  171. $build[$filter['value']]['#count'] = 0;
  172. }
  173. // Then, add the facets returned by the server.
  174. if (isset($results['search_api_facets']) && isset($results['search_api_facets'][$this->facet['name']])) {
  175. $values = $results['search_api_facets'][$this->facet['name']];
  176. foreach ($values as $value) {
  177. $filter = $value['filter'];
  178. // As Facet API isn't really suited for our native facet filter
  179. // representations, convert the format here. (The missing facet can
  180. // stay the same.)
  181. if ($filter[0] == '"') {
  182. $filter = substr($filter, 1, -1);
  183. }
  184. elseif ($filter != '!') {
  185. // This is a range filter.
  186. $filter = substr($filter, 1, -1);
  187. $pos = strpos($filter, ' ');
  188. if ($pos !== FALSE) {
  189. $filter = '[' . substr($filter, 0, $pos) . ' TO ' . substr($filter, $pos + 1) . ']';
  190. }
  191. }
  192. $build[$filter] = array(
  193. '#count' => $value['count'],
  194. );
  195. }
  196. }
  197. return $build;
  198. }
  199. }