handler_filter_options.inc 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. <?php
  2. /**
  3. * @file
  4. * Contains the SearchApiViewsHandlerFilterOptions class.
  5. */
  6. /**
  7. * Views filter handler for fields with a limited set of possible values.
  8. */
  9. class SearchApiViewsHandlerFilterOptions extends SearchApiViewsHandlerFilter {
  10. /**
  11. * Stores the values which are available on the form.
  12. *
  13. * @var array
  14. */
  15. protected $value_options = NULL;
  16. /**
  17. * The type of form element used to display the options.
  18. *
  19. * @var string
  20. */
  21. protected $value_form_type = 'checkboxes';
  22. /**
  23. * Retrieves a wrapper for this filter's field.
  24. *
  25. * @return EntityMetadataWrapper|null
  26. * A wrapper for the field which this filter uses.
  27. */
  28. protected function get_wrapper() {
  29. if ($this->query) {
  30. $index = $this->query->getIndex();
  31. }
  32. elseif (substr($this->view->base_table, 0, 17) == 'search_api_index_') {
  33. $index = search_api_index_load(substr($this->view->base_table, 17));
  34. }
  35. else {
  36. return NULL;
  37. }
  38. $wrapper = $index->entityWrapper(NULL, TRUE);
  39. $parts = explode(':', $this->real_field);
  40. foreach ($parts as $i => $part) {
  41. if (!isset($wrapper->$part)) {
  42. return NULL;
  43. }
  44. $wrapper = $wrapper->$part;
  45. $info = $wrapper->info();
  46. if ($i < count($parts) - 1) {
  47. // Unwrap lists.
  48. $level = search_api_list_nesting_level($info['type']);
  49. for ($j = 0; $j < $level; ++$j) {
  50. $wrapper = $wrapper[0];
  51. }
  52. }
  53. }
  54. return $wrapper;
  55. }
  56. /**
  57. * Fills the value_options property with all possible options.
  58. */
  59. protected function get_value_options() {
  60. if (isset($this->value_options)) {
  61. return;
  62. }
  63. $wrapper = $this->get_wrapper();
  64. if ($wrapper) {
  65. $this->value_options = $wrapper->optionsList('view');
  66. }
  67. else {
  68. $this->value_options = array();
  69. }
  70. }
  71. /**
  72. * Provide a list of options for the operator form.
  73. */
  74. public function operator_options() {
  75. $options = array(
  76. '=' => t('Is one of'),
  77. 'all of' => t('Is all of'),
  78. '<>' => t('Is none of'),
  79. 'empty' => t('Is empty'),
  80. 'not empty' => t('Is not empty'),
  81. );
  82. // "Is all of" doesn't make sense for single-valued fields.
  83. if (empty($this->definition['multi-valued'])) {
  84. unset($options['all of']);
  85. }
  86. return $options;
  87. }
  88. /**
  89. * Set "reduce" option to FALSE by default.
  90. */
  91. public function expose_options() {
  92. parent::expose_options();
  93. $this->options['expose']['reduce'] = FALSE;
  94. }
  95. /**
  96. * Add the "reduce" option to the exposed form.
  97. */
  98. public function expose_form(&$form, &$form_state) {
  99. parent::expose_form($form, $form_state);
  100. $form['expose']['reduce'] = array(
  101. '#type' => 'checkbox',
  102. '#title' => t('Limit list to selected items'),
  103. '#description' => t('If checked, the only items presented to the user will be the ones selected here.'),
  104. '#default_value' => !empty($this->options['expose']['reduce']),
  105. );
  106. }
  107. /**
  108. * Define "reduce" option.
  109. */
  110. public function option_definition() {
  111. $options = parent::option_definition();
  112. $options['value'] = array('default' => '');
  113. $options['expose']['contains']['reduce'] = array('default' => FALSE);
  114. return $options;
  115. }
  116. /**
  117. * Reduce the options according to the selection.
  118. */
  119. protected function reduce_value_options() {
  120. foreach ($this->value_options as $id => $option) {
  121. if (!isset($this->options['value'][$id])) {
  122. unset($this->value_options[$id]);
  123. }
  124. }
  125. return $this->value_options;
  126. }
  127. /**
  128. * Save set checkboxes.
  129. */
  130. public function value_submit($form, &$form_state) {
  131. // Drupal's FAPI system automatically puts '0' in for any checkbox that
  132. // was not set, and the key to the checkbox if it is set.
  133. // Unfortunately, this means that if the key to that checkbox is 0,
  134. // we are unable to tell if that checkbox was set or not.
  135. // Luckily, the '#value' on the checkboxes form actually contains
  136. // *only* a list of checkboxes that were set, and we can use that
  137. // instead.
  138. $form_state['values']['options']['value'] = $form['value']['#value'];
  139. }
  140. /**
  141. * Provide a form for setting options.
  142. */
  143. public function value_form(&$form, &$form_state) {
  144. $this->get_value_options();
  145. if (!empty($this->options['expose']['reduce']) && !empty($form_state['exposed'])) {
  146. $options = $this->reduce_value_options();
  147. }
  148. else {
  149. $options = $this->value_options;
  150. }
  151. $form['value'] = array(
  152. '#type' => $this->value_form_type,
  153. '#title' => empty($form_state['exposed']) ? t('Value') : '',
  154. '#options' => $options,
  155. '#multiple' => TRUE,
  156. '#size' => min(4, count($options)),
  157. '#default_value' => is_array($this->value) ? $this->value : array(),
  158. );
  159. // Hide the value box if the operator is 'empty' or 'not empty'.
  160. // Radios share the same selector so we have to add some dummy selector.
  161. if (empty($form_state['exposed'])) {
  162. $form['value']['#states']['visible'] = array(
  163. ':input[name="options[operator]"],dummy-empty' => array('!value' => 'empty'),
  164. ':input[name="options[operator]"],dummy-not-empty' => array('!value' => 'not empty'),
  165. );
  166. }
  167. elseif (!empty($this->options['expose']['use_operator'])) {
  168. $name = $this->options['expose']['operator_id'];
  169. $form['value']['#states']['visible'] = array(
  170. ':input[name="' . $name . '"],dummy-empty' => array('!value' => 'empty'),
  171. ':input[name="' . $name . '"],dummy-not-empty' => array('!value' => 'not empty'),
  172. );
  173. }
  174. }
  175. /**
  176. * Provides a summary of this filter's value for the admin UI.
  177. */
  178. public function admin_summary() {
  179. if (!empty($this->options['exposed'])) {
  180. return t('exposed');
  181. }
  182. if ($this->operator === 'empty') {
  183. return t('is empty');
  184. }
  185. if ($this->operator === 'not empty') {
  186. return t('is not empty');
  187. }
  188. if (!is_array($this->value)) {
  189. return;
  190. }
  191. $operator_options = $this->operator_options();
  192. $operator = $operator_options[$this->operator];
  193. $values = '';
  194. // Remove every element which is not known.
  195. $this->get_value_options();
  196. foreach ($this->value as $i => $value) {
  197. if (!isset($this->value_options[$value])) {
  198. unset($this->value[$i]);
  199. }
  200. }
  201. // Choose different kind of ouput for 0, a single and multiple values.
  202. if (count($this->value) == 0) {
  203. return $this->operator != '<>' ? t('none') : t('any');
  204. }
  205. elseif (count($this->value) == 1) {
  206. switch ($this->operator) {
  207. case '=':
  208. case 'all of':
  209. $operator = '=';
  210. break;
  211. case '<>':
  212. $operator = '<>';
  213. break;
  214. }
  215. // If there is only a single value, use just the plain operator, = or <>.
  216. $operator = check_plain($operator);
  217. $values = check_plain($this->value_options[reset($this->value)]);
  218. }
  219. else {
  220. foreach ($this->value as $value) {
  221. if ($values !== '') {
  222. $values .= ', ';
  223. }
  224. if (drupal_strlen($values) > 20) {
  225. $values .= '…';
  226. break;
  227. }
  228. $values .= check_plain($this->value_options[$value]);
  229. }
  230. }
  231. return $operator . (($values !== '') ? ' ' . $values : '');
  232. }
  233. /**
  234. * {@inheritdoc}
  235. */
  236. function accept_exposed_input($input) {
  237. $accepted = parent::accept_exposed_input($input);
  238. // Grouped filters will have the raw form values structure from the
  239. // checkboxes as the value here. Convert that into the correct array of
  240. // values instead.
  241. if ($accepted && is_array($this->value) && $this->is_a_group()) {
  242. // For some reason, Views thinks it's a good idea to nest the form values
  243. // into a second array in some cases. That one will be numerically indexed
  244. // with just a single entry, though, so it should be relatively easy to
  245. // spot.
  246. if (count($this->value) && isset($this->value[0])) {
  247. $this->value = reset($this->value);
  248. }
  249. $this->value = array_keys(array_filter($this->value));
  250. if (!$this->value) {
  251. return FALSE;
  252. }
  253. }
  254. return $accepted;
  255. }
  256. /**
  257. * Add this filter to the query.
  258. */
  259. public function query() {
  260. if ($this->operator === 'empty') {
  261. $this->query->condition($this->real_field, NULL, '=', $this->options['group']);
  262. return;
  263. }
  264. if ($this->operator === 'not empty') {
  265. $this->query->condition($this->real_field, NULL, '<>', $this->options['group']);
  266. return;
  267. }
  268. // Extract the value.
  269. while (is_array($this->value) && count($this->value) == 1) {
  270. $this->value = reset($this->value);
  271. }
  272. // Determine operator and conjunction. The defaults are already right for
  273. // "all of".
  274. $operator = '=';
  275. $conjunction = 'AND';
  276. switch ($this->operator) {
  277. case '=':
  278. $conjunction = 'OR';
  279. break;
  280. case '<>':
  281. $operator = '<>';
  282. break;
  283. }
  284. // If the value is an empty array, we either want no filter at all (for
  285. // "is none of"), or want to find only items with no value for the field.
  286. if ($this->value === array()) {
  287. if ($operator != '<>') {
  288. $this->query->condition($this->real_field, NULL, '=', $this->options['group']);
  289. }
  290. return;
  291. }
  292. if (is_scalar($this->value) && $this->value !== '') {
  293. $this->query->condition($this->real_field, $this->value, $operator, $this->options['group']);
  294. }
  295. elseif ($this->value) {
  296. $filter = $this->query->createFilter($conjunction);
  297. // $filter will be NULL if there were errors in the query.
  298. if ($filter) {
  299. foreach ($this->value as $v) {
  300. $filter->condition($this->real_field, $v, $operator);
  301. }
  302. $this->query->filter($filter, $this->options['group']);
  303. }
  304. }
  305. }
  306. }