search_api_views.views.inc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. <?php
  2. /**
  3. * @file
  4. * Views hook implementations for the Search API Views module.
  5. */
  6. /**
  7. * Implements hook_views_data().
  8. */
  9. function search_api_views_views_data() {
  10. try {
  11. $data = array();
  12. foreach (search_api_index_load_multiple(FALSE) as $index) {
  13. // Fill in base data.
  14. $key = 'search_api_index_' . $index->machine_name;
  15. $table = &$data[$key];
  16. $type_info = search_api_get_item_type_info($index->item_type);
  17. $table['table']['group'] = t('Indexed @entity_type', array('@entity_type' => $type_info['name']));
  18. $table['table']['base'] = array(
  19. 'field' => 'search_api_id',
  20. 'index' => $index->machine_name,
  21. 'title' => $index->name,
  22. 'help' => t('Use the %name search index for filtering and retrieving data.', array('%name' => $index->name)),
  23. 'query class' => 'search_api_views_query',
  24. );
  25. $table['table']['entity type'] = $index->getEntityType();
  26. $table['table']['skip entity load'] = TRUE;
  27. try {
  28. $wrapper = $index->entityWrapper(NULL, FALSE);
  29. }
  30. catch (EntityMetadataWrapperException $e) {
  31. watchdog_exception('search_api_views', $e, "%type while retrieving metadata for index %index: !message in %function (line %line of %file).", array('%index' => $index->name), WATCHDOG_WARNING);
  32. continue;
  33. }
  34. // Add field handlers and relationships provided by the Entity API.
  35. foreach ($wrapper as $key => $property) {
  36. $info = $property->info();
  37. if ($info) {
  38. entity_views_field_definition($key, $info, $table);
  39. }
  40. }
  41. try {
  42. $wrapper = $index->entityWrapper(NULL);
  43. }
  44. catch (EntityMetadataWrapperException $e) {
  45. watchdog_exception('search_api_views', $e, "%type while retrieving metadata for index %index: !message in %function (line %line of %file).", array('%index' => $index->name), WATCHDOG_WARNING);
  46. continue;
  47. }
  48. // Add handlers for all indexed fields.
  49. foreach ($index->getFields() as $key => $field) {
  50. $tmp = $wrapper;
  51. $group = '';
  52. $name = '';
  53. $parts = explode(':', $key);
  54. foreach ($parts as $i => $part) {
  55. if (!isset($tmp->$part)) {
  56. continue 2;
  57. }
  58. $tmp = $tmp->$part;
  59. $info = $tmp->info();
  60. $group = ($group ? $group . ' » ' . $name : ($name ? $name : ''));
  61. $name = $info['label'];
  62. if ($i < count($parts) - 1) {
  63. // Unwrap lists.
  64. $level = search_api_list_nesting_level($info['type']);
  65. for ($j = 0; $j < $level; ++$j) {
  66. $tmp = $tmp[0];
  67. }
  68. }
  69. }
  70. $id = _entity_views_field_identifier($key, $table);
  71. if ($group) {
  72. // @todo Entity type label instead of $group?
  73. $table[$id]['group'] = $group;
  74. $name = t('!field (indexed)', array('!field' => $name));
  75. }
  76. $table[$id]['title'] = $name;
  77. $table[$id]['help'] = empty($info['description']) ? t('(No information available)') : $info['description'];
  78. $table[$id]['type'] = $field['type'];
  79. if ($id != $key) {
  80. $table[$id]['real field'] = $key;
  81. }
  82. _search_api_views_add_handlers($key, $field, $tmp, $table);
  83. }
  84. // Special handlers
  85. $table['search_api_language']['filter']['handler'] = 'SearchApiViewsHandlerFilterLanguage';
  86. $table['search_api_id']['title'] = t('Entity ID');
  87. $table['search_api_id']['help'] = t("The entity's ID.");
  88. $table['search_api_id']['sort']['handler'] = 'SearchApiViewsHandlerSort';
  89. $table['search_api_relevance']['group'] = t('Search');
  90. $table['search_api_relevance']['title'] = t('Relevance');
  91. $table['search_api_relevance']['help'] = t('The relevance of this search result with respect to the query.');
  92. $table['search_api_relevance']['field']['type'] = 'decimal';
  93. $table['search_api_relevance']['field']['float'] = TRUE;
  94. $table['search_api_relevance']['field']['handler'] = 'entity_views_handler_field_numeric';
  95. $table['search_api_relevance']['field']['click sortable'] = TRUE;
  96. $table['search_api_relevance']['sort']['handler'] = 'SearchApiViewsHandlerSort';
  97. $table['search_api_excerpt']['group'] = t('Search');
  98. $table['search_api_excerpt']['title'] = t('Excerpt');
  99. $table['search_api_excerpt']['help'] = t('The search result excerpted to show found search terms.');
  100. $table['search_api_excerpt']['field']['type'] = 'text';
  101. $table['search_api_excerpt']['field']['handler'] = 'entity_views_handler_field_text';
  102. $table['search_api_views_fulltext']['group'] = t('Search');
  103. $table['search_api_views_fulltext']['title'] = t('Fulltext search');
  104. $table['search_api_views_fulltext']['help'] = t('Search several or all fulltext fields at once.');
  105. $table['search_api_views_fulltext']['filter']['handler'] = 'SearchApiViewsHandlerFilterFulltext';
  106. $table['search_api_views_fulltext']['argument']['handler'] = 'SearchApiViewsHandlerArgumentFulltext';
  107. $table['search_api_views_more_like_this']['group'] = t('Search');
  108. $table['search_api_views_more_like_this']['title'] = t('More like this');
  109. $table['search_api_views_more_like_this']['help'] = t('Find similar content.');
  110. $table['search_api_views_more_like_this']['argument']['handler'] = 'SearchApiViewsHandlerArgumentMoreLikeThis';
  111. // If there are taxonomy term references indexed in the index, include the
  112. // "Indexed taxonomy term fields" contextual filter. We also save for all
  113. // fields whether they contain only terms of a certain vocabulary, keying
  114. // that information by vocabulary for later ease of use.
  115. $vocabulary_fields = array();
  116. foreach ($index->getFields() as $key => $field) {
  117. if (isset($field['entity_type']) && $field['entity_type'] === 'taxonomy_term') {
  118. $field_id = ($pos = strrpos($key, ':')) ? substr($key, $pos + 1) : $key;
  119. $field_info = field_info_field($field_id);
  120. if ($vocabulary = _search_api_views_get_field_vocabulary($field_info)) {
  121. $vocabulary_fields[$vocabulary][] = $key;
  122. }
  123. else {
  124. $vocabulary_fields[''][] = $key;
  125. }
  126. }
  127. }
  128. if ($vocabulary_fields) {
  129. $table['search_api_views_taxonomy_term']['group'] = t('Search');
  130. $table['search_api_views_taxonomy_term']['title'] = t('Indexed taxonomy term fields');
  131. $table['search_api_views_taxonomy_term']['help'] = t('Search in all indexed taxonomy term fields.');
  132. $table['search_api_views_taxonomy_term']['argument']['handler'] = 'SearchApiViewsHandlerArgumentTaxonomyTerm';
  133. $table['search_api_views_taxonomy_term']['argument']['vocabulary_fields'] = $vocabulary_fields;
  134. }
  135. }
  136. return $data;
  137. }
  138. catch (Exception $e) {
  139. watchdog_exception('search_api_views', $e);
  140. }
  141. }
  142. /**
  143. * Adds handler definitions for a field to a Views data table definition.
  144. *
  145. * Helper method for search_api_views_views_data().
  146. *
  147. * @param $id
  148. * The internal identifier of the field.
  149. * @param array $field
  150. * Information about the field.
  151. * @param EntityMetadataWrapper $wrapper
  152. * A wrapper providing further metadata about the field.
  153. * @param array $table
  154. * The existing Views data table definition, as a reference.
  155. */
  156. function _search_api_views_add_handlers($id, array $field, EntityMetadataWrapper $wrapper, array &$table) {
  157. $type = $field['type'];
  158. $inner_type = search_api_extract_inner_type($type);
  159. if (strpos($id, ':')) {
  160. entity_views_field_definition($id, $wrapper->info(), $table);
  161. }
  162. $id = _entity_views_field_identifier($id, $table);
  163. $table += array($id => array());
  164. if ($inner_type == 'text') {
  165. $table[$id] += array(
  166. 'argument' => array(
  167. 'handler' => 'SearchApiViewsHandlerArgumentString',
  168. ),
  169. 'filter' => array(
  170. 'handler' => 'SearchApiViewsHandlerFilterText',
  171. ),
  172. );
  173. return;
  174. }
  175. $info = $wrapper->info();
  176. if (isset($info['options list']) && is_callable($info['options list'])) {
  177. $table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilterOptions';
  178. $table[$id]['filter']['multi-valued'] = search_api_is_list_type($type);
  179. }
  180. elseif ($inner_type == 'boolean') {
  181. $table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilterBoolean';
  182. }
  183. elseif ($inner_type == 'date') {
  184. $table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilterDate';
  185. }
  186. elseif (isset($field['entity_type']) && $field['entity_type'] === 'user') {
  187. $table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilterUser';
  188. }
  189. elseif (isset($field['entity_type']) && $field['entity_type'] === 'taxonomy_term') {
  190. $table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilterTaxonomyTerm';
  191. $field_info = field_info_field($info['name']);
  192. // For the "Parent terms" and "All parent terms" properties, we can
  193. // extrapolate the vocabulary from the parent in the selector. (E.g.,
  194. // for "field_tags:parent" we can use the information of "field_tags".)
  195. // Otherwise, we can't include any vocabulary information.
  196. if (!$field_info && ($info['name'] == 'parent' || $info['name'] == 'parents_all')) {
  197. if (!empty($table[$id]['real field'])) {
  198. $parts = explode(':', $table[$id]['real field']);
  199. $field_info = field_info_field($parts[count($parts) - 2]);
  200. }
  201. }
  202. if ($vocabulary = _search_api_views_get_field_vocabulary($field_info)) {
  203. $table[$id]['filter']['vocabulary'] = $vocabulary;
  204. }
  205. }
  206. elseif (in_array($inner_type, array('integer', 'decimal', 'duration', 'string'))) {
  207. $table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilterNumeric';
  208. }
  209. else {
  210. $table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilter';
  211. }
  212. if ($inner_type == 'string' || $inner_type == 'uri') {
  213. $table[$id]['argument']['handler'] = 'SearchApiViewsHandlerArgumentString';
  214. }
  215. elseif ($inner_type == 'date') {
  216. $table[$id]['argument']['handler'] = 'SearchApiViewsHandlerArgumentDate';
  217. }
  218. else {
  219. $table[$id]['argument']['handler'] = 'SearchApiViewsHandlerArgument';
  220. }
  221. // We can only sort according to single-valued fields.
  222. if ($type == $inner_type) {
  223. $table[$id]['sort']['handler'] = 'SearchApiViewsHandlerSort';
  224. if (isset($table[$id]['field'])) {
  225. $table[$id]['field']['click sortable'] = TRUE;
  226. }
  227. }
  228. }
  229. /**
  230. * Implements hook_views_plugins().
  231. */
  232. function search_api_views_views_plugins() {
  233. // Collect all base tables provided by this module.
  234. $bases = array();
  235. foreach (search_api_index_load_multiple(FALSE) as $index) {
  236. $bases[] = 'search_api_index_' . $index->machine_name;
  237. }
  238. $ret = array(
  239. 'query' => array(
  240. 'search_api_views_query' => array(
  241. 'title' => t('Search API Query'),
  242. 'help' => t('Query will be generated and run using the Search API.'),
  243. 'handler' => 'SearchApiViewsQuery',
  244. ),
  245. ),
  246. 'cache' => array(
  247. 'search_api_views_cache' => array(
  248. 'title' => t('Search-specific'),
  249. 'help' => t("Cache Search API views. (Other methods probably won't work with search views.)"),
  250. 'base' => $bases,
  251. 'handler' => 'SearchApiViewsCache',
  252. 'uses options' => TRUE,
  253. ),
  254. ),
  255. );
  256. if (module_exists('search_api_facetapi')) {
  257. $ret['display']['search_api_views_facets_block'] = array(
  258. 'title' => t('Facets block'),
  259. 'help' => t('Display facets for this search as a block anywhere on the site.'),
  260. 'handler' => 'SearchApiViewsFacetsBlockDisplay',
  261. 'uses hook block' => TRUE,
  262. 'use ajax' => FALSE,
  263. 'use pager' => FALSE,
  264. 'use more' => TRUE,
  265. 'accept attachments' => TRUE,
  266. 'admin' => t('Facets block'),
  267. );
  268. }
  269. if (module_exists('views_content_cache')) {
  270. $ret['cache']['search_api_views_content_cache'] = array(
  271. 'title' => t('Search-specific content-based'),
  272. 'help' => t("Cache Search API views based on content updates. (Requires Views Content Cache)"),
  273. 'base' => $bases,
  274. 'handler' => 'SearchApiViewsContentCache',
  275. 'uses options' => TRUE,
  276. );
  277. }
  278. return $ret;
  279. }
  280. /**
  281. * Returns the vocabulary machine name of a term field.
  282. *
  283. * @param array|null $field_info
  284. * The field's field info array, or NULL if the field is not provided by the
  285. * Field API. See the return value of field_info_field().
  286. *
  287. * @return string|null
  288. * If the field contains taxonomy terms of a single vocabulary (which could be
  289. * determined), that vocabulary's machine name; NULL otherwise.
  290. */
  291. function _search_api_views_get_field_vocabulary($field_info) {
  292. // Test for "Term reference" fields.
  293. if (isset($field_info['settings']['allowed_values'][0]['vocabulary'])) {
  294. return $field_info['settings']['allowed_values'][0]['vocabulary'];
  295. }
  296. // Test for "Entity reference" fields.
  297. elseif (isset($field_info['settings']['handler']) && $field_info['settings']['handler'] === 'base') {
  298. if (!empty($field_info['settings']['handler_settings']['target_bundles'])) {
  299. $bundles = $field_info['settings']['handler_settings']['target_bundles'];
  300. if (count($bundles) == 1) {
  301. return key($bundles);
  302. }
  303. }
  304. }
  305. return NULL;
  306. }