search_api_autocomplete.api.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. <?php
  2. /**
  3. * @file
  4. * Hooks provided by the Search API autocomplete module.
  5. */
  6. /**
  7. * @addtogroup hooks
  8. * @{
  9. */
  10. /**
  11. * Inform the module about types of searches for which autocompletion is available.
  12. *
  13. * The implementation has to take care of altering the search form accordingly
  14. * itself. This should be done by loading the appropriate
  15. * SearchApiAutocompleteSearch entity and calling its alterElement() method with
  16. * the textfield element to which autocompletion should be added. See
  17. * example_form_example_search_form_alter() for an example.
  18. *
  19. * @return array
  20. * An array with search types as the keys, mapped to arrays containing the
  21. * following entries:
  22. * - name: The category name for searches of this type.
  23. * - description: A short description of this type (may contain HTML).
  24. * - list searches: Callback function that returns a list of all known
  25. * searches of this type for a given index. See
  26. * example_list_autocomplete_searches() for the expected function signature.
  27. * - create query: Callback function to create a search query for a search of
  28. * this type and some user input. See example_create_autocomplete_query()
  29. * for the expected function signature.
  30. * - config form: (optional) Callback function for adding a form for
  31. * type-specific options to a search's autocomplete settings form. See
  32. * example_autocomplete_config_form() for the expected function signature.
  33. * This function name will also be the base for custom validation and submit
  34. * callbacks, with "_validate" or "_submit" appended, respectively.
  35. *
  36. * @see example_list_autocomplete_searches()
  37. * @see example_create_autocomplete_query()
  38. * @see example_autocomplete_config_form()
  39. * @see example_autocomplete_config_form_validate()
  40. * @see example_autocomplete_config_form_submit()
  41. * @see example_form_example_search_form_alter()
  42. */
  43. function hook_search_api_autocomplete_types() {
  44. $types['example'] = array(
  45. 'name' => t('Example searches'),
  46. 'description' => t('Searches provided by the <em>Example</em> module.'),
  47. 'list searches' => 'example_list_autocomplete_searches',
  48. 'create query' => 'example_create_autocomplete_query',
  49. 'config form' => 'example_autocomplete_config_form',
  50. );
  51. return $types;
  52. }
  53. /**
  54. * Acts on searches being loaded from the database.
  55. *
  56. * This hook is invoked during search loading, which is handled by
  57. * entity_load(), via the EntityCRUDController.
  58. *
  59. * @param array $searches
  60. * An array of search entities being loaded, keyed by machine name.
  61. *
  62. * @see hook_entity_load()
  63. */
  64. function hook_search_api_autocomplete_search_load(array $searches) {
  65. $result = db_query('SELECT pid, foo FROM {mytable} WHERE pid IN(:ids)', array(':ids' => array_keys($entities)));
  66. foreach ($result as $record) {
  67. $entities[$record->pid]->foo = $record->foo;
  68. }
  69. }
  70. /**
  71. * Responds when a search is inserted.
  72. *
  73. * This hook is invoked after the search is inserted into the database.
  74. *
  75. * @param SearchApiAutocompleteSearch $search
  76. * The search that is being inserted.
  77. *
  78. * @see hook_entity_insert()
  79. */
  80. function hook_search_api_autocomplete_search_insert(SearchApiAutocompleteSearch $search) {
  81. db_insert('mytable')
  82. ->fields(array(
  83. 'id' => entity_id('search_api_autocomplete_search', $search),
  84. 'extra' => print_r($search, TRUE),
  85. ))
  86. ->execute();
  87. }
  88. /**
  89. * Acts on a search being inserted or updated.
  90. *
  91. * This hook is invoked before the search is saved to the database.
  92. *
  93. * @param SearchApiAutocompleteSearch $search
  94. * The search that is being inserted or updated.
  95. *
  96. * @see hook_entity_presave()
  97. */
  98. function hook_search_api_autocomplete_search_presave(SearchApiAutocompleteSearch $search) {
  99. $search->name = 'foo';
  100. }
  101. /**
  102. * Responds to a search being updated.
  103. *
  104. * This hook is invoked after the search has been updated in the database.
  105. *
  106. * @param SearchApiAutocompleteSearch $search
  107. * The search that is being updated.
  108. *
  109. * @see hook_entity_update()
  110. */
  111. function hook_search_api_autocomplete_search_update(SearchApiAutocompleteSearch $search) {
  112. db_update('mytable')
  113. ->fields(array('extra' => print_r($search, TRUE)))
  114. ->condition('id', entity_id('search_api_autocomplete_search', $search))
  115. ->execute();
  116. }
  117. /**
  118. * Responds to search deletion.
  119. *
  120. * This hook is invoked after the search has been removed from the database.
  121. *
  122. * @param SearchApiAutocompleteSearch $search
  123. * The search that is being deleted.
  124. *
  125. * @see hook_entity_delete()
  126. */
  127. function hook_search_api_autocomplete_search_delete(SearchApiAutocompleteSearch $search) {
  128. db_delete('mytable')
  129. ->condition('pid', entity_id('search_api_autocomplete_search', $search))
  130. ->execute();
  131. }
  132. /**
  133. * Define default search configurations.
  134. *
  135. * @return
  136. * An array of default searches, keyed by machine names.
  137. *
  138. * @see hook_default_search_api_autocomplete_search_alter()
  139. */
  140. function hook_default_search_api_autocomplete_search() {
  141. $defaults['main'] = entity_create('search_api_autocomplete_search', array(
  142. // …
  143. ));
  144. return $defaults;
  145. }
  146. /**
  147. * Alter default search configurations.
  148. *
  149. * @param array $defaults
  150. * An array of default searches, keyed by machine names.
  151. *
  152. * @see hook_default_search_api_autocomplete_search()
  153. */
  154. function hook_default_search_api_autocomplete_search_alter(array &$defaults) {
  155. $defaults['main']->name = 'custom name';
  156. }
  157. /**
  158. * @} End of "addtogroup hooks".
  159. */
  160. /**
  161. * Returns a list of searches for the given index.
  162. *
  163. * All searches returned must have a unique and well-defined machine name. The
  164. * implementing module for this type is responsible for being able to map a
  165. * specific search always to the same distinct machine name.
  166. * Since the machine names have to be globally unique, they should be prefixed
  167. * with the search type / module name.
  168. *
  169. * Also, name and machine name have to respect the length constraints from
  170. * search_api_autocomplete_schema().
  171. *
  172. * @param SearchApiIndex $index
  173. * The index whose searches should be returned.
  174. *
  175. * @return array
  176. * An array of searches, keyed by their machine name. The values are arrays
  177. * with the following keys:
  178. * - name: A human-readable name for this search.
  179. * - options: (optional) An array of options to use for this search.
  180. * Type-specific options should go into the "custom" nested key in these
  181. * options.
  182. */
  183. function example_list_autocomplete_searches(SearchApiIndex $index) {
  184. $ret = array();
  185. $result = db_query('SELECT name, machine_name, extra FROM {example_searches} WHERE index_id = :id', array($index->machine_name));
  186. foreach ($result as $row) {
  187. $id = 'example_' . $row->machine_name;
  188. $ret[$id] = array(
  189. 'name' => $row->name,
  190. );
  191. if ($row->extra) {
  192. $ret[$id]['options']['custom']['extra'] = $row->extra;
  193. }
  194. }
  195. return $ret;
  196. }
  197. /**
  198. * Create the query that would be issued for the given search for the complete keys.
  199. *
  200. * @param SearchApiAutocompleteSearch $search
  201. * The search for which to create the query.
  202. * @param $complete
  203. * A string containing the complete search keys.
  204. * @param $incomplete
  205. * A string containing the incomplete last search key.
  206. *
  207. * @return SearchApiQueryInterface
  208. * The query that would normally be executed when only $complete was entered
  209. * as the search keys for the given search.
  210. */
  211. function example_create_autocomplete_query(SearchApiAutocompleteSearch $search, $complete, $incomplete) {
  212. $query = search_api_query($search->index_id);
  213. if ($complete) {
  214. $query->keys($complete);
  215. }
  216. if (!empty($search->options['custom']['extra'])) {
  217. list($f, $v) = explode('=', $search->options['custom']['extra'], 2);
  218. $query->condition($f, $v);
  219. }
  220. if (!empty($search->options['custom']['user_filters'])) {
  221. foreach (explode("\n", $search->options['custom']['user_filters']) as $line) {
  222. list($f, $v) = explode('=', $line, 2);
  223. $query->condition($f, $v);
  224. }
  225. }
  226. return $query;
  227. }
  228. /**
  229. * Form callback for configuring autocompletion for searches of the "example" type.
  230. *
  231. * The returned form array will be nested into an outer form, so you should not
  232. * rely on knowing the array structure (like the elements' parents) and should
  233. * not set "#tree" to FALSE for any element.
  234. *
  235. * @param SearchApiAutocompleteSearch $search
  236. * The search whose config form should be presented.
  237. *
  238. * @see example_autocomplete_config_form_validate()
  239. * @see example_autocomplete_config_form_submit()
  240. */
  241. function example_autocomplete_config_form(array $form, array &$form_state, SearchApiAutocompleteSearch $search) {
  242. $form['user_filters'] = array(
  243. '#type' => 'textarea',
  244. '#title' => t('Custom filters'),
  245. '#description' => t('Enter additional filters set on the autocompletion search. ' .
  246. 'Write one filter on each line, the field and its value separated by an equals sign (=).'),
  247. '#default_value' => empty($search->options['custom']['user_filters']) ? '' : $search->options['custom']['user_filters'],
  248. );
  249. return $form;
  250. }
  251. /**
  252. * Validation callback for example_autocomplete_config_form().
  253. *
  254. * The configured SearchApiAutocompleteSearch object can be found in
  255. * $form_state['search'].
  256. *
  257. * @param array $form
  258. * The type-specific config form, as returned by the "config form" callback.
  259. * @param array $form_state
  260. * The complete form state of the form.
  261. * @param array $values
  262. * The portion of $form_state['values'] that corresponds to the type-specific
  263. * config form.
  264. *
  265. * @see example_autocomplete_config_form()
  266. * @see example_autocomplete_config_form_submit()
  267. */
  268. function example_autocomplete_config_form_validate(array $form, array &$form_state, array &$values) {
  269. $f = array();
  270. foreach (explode("\n", $values['user_filters']) as $line) {
  271. if (preg_match('/^\s*([a-z0-9_:]+)\s*=\s*(.*\S)\s*$/i', $line, $m)) {
  272. $f[] = $m[1] . '=' . $m[2];
  273. }
  274. else {
  275. form_error($form, t('Write one filter on each line, the field and its value separated by an equals sign (=).'));
  276. }
  277. }
  278. $values['user_filters'] = $f;
  279. }
  280. /**
  281. * Submit callback for example_autocomplete_config_form().
  282. *
  283. * After calling this function, the value of $values (if set) will automatically
  284. * be written to $search->options['custom']. This function just has to take care
  285. * of sanitizing the data as necessary. Also, values already present in
  286. * $search->options['custom'], but not in the form, will automatically be
  287. * protected from being overwritten.
  288. *
  289. * The configured SearchApiAutocompleteSearch object can be found in
  290. * $form_state['search'].
  291. *
  292. * @param array $form
  293. * The type-specific config form, as returned by the "config form" callback.
  294. * @param array $form_state
  295. * The complete form state of the form.
  296. * @param array $values
  297. * The portion of $form_state['values'] that corresponds to the type-specific
  298. * config form.
  299. *
  300. * @see example_autocomplete_config_form()
  301. * @see example_autocomplete_config_form_validate()
  302. */
  303. function example_autocomplete_config_form_submit(array $form, array &$form_state, array &$values) {
  304. $values['user_filters'] = implode("\n", $values['user_filters']);
  305. }
  306. /**
  307. * Implements hook_form_FORM_ID_alter().
  308. *
  309. * Alters the example_search_form form to add autocompletion, if enabled by the
  310. * user.
  311. */
  312. function example_form_example_search_form_alter(array &$form, array &$form_state) {
  313. // Compute the machine name that would be generated for this search in the
  314. // 'list searches' callback.
  315. $search_id = 'example_' . $form_state['search id'];
  316. // Look up the corresponding autocompletion configuration, if it exists.
  317. $search = search_api_autocomplete_search_load($search_id);
  318. // Check whether autocompletion for the search is enabled.
  319. // (This is also checked automatically later, so could be skipped here.)
  320. if (!empty($search->enabled)) {
  321. // If it is, pass the textfield for the search keywords to the
  322. // alterElement() method of the search object.
  323. $search->alterElement($form['keys']);
  324. }
  325. }
  326. /**
  327. * Implements hook_search_api_query_alter().
  328. *
  329. * This example hook implementation shows how a custom module could fix the
  330. * problem with Views contextual filters in a specific context.
  331. */
  332. function example_search_api_query_alter(SearchApiQueryInterface $query) {
  333. // Check whether this is an appropriate automcomplete query.
  334. if ($query->getOption('search id') === 'search_api_autocomplete:example') {
  335. // If it is, add the necessary filters that would otherwise be added by
  336. // contextual filters. This is easy if the argument comes from the global
  337. // user or a similar global source. If the argument comes from the URL or
  338. // some other page-specific source, however, you would need to somehow pass
  339. // that information along to this function.
  340. global $user;
  341. $query->condition('group', $user->data['group']);
  342. }
  343. }