datasource_multiple.inc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. <?php
  2. /**
  3. * @file
  4. * Contains SearchApiCombinedEntityDataSourceController.
  5. */
  6. /**
  7. * Provides a datasource for indexing multiple types of entities.
  8. */
  9. class SearchApiCombinedEntityDataSourceController extends SearchApiAbstractDataSourceController {
  10. /**
  11. * {@inheritdoc}
  12. */
  13. protected $table = 'search_api_item_string_id';
  14. /**
  15. * {@inheritdoc}
  16. */
  17. public function getIdFieldInfo() {
  18. return array(
  19. 'key' => 'item_id',
  20. 'type' => 'string',
  21. );
  22. }
  23. /**
  24. * {@inheritdoc}
  25. */
  26. public function loadItems(array $ids) {
  27. $ids_by_type = array();
  28. foreach ($ids as $id) {
  29. list($type, $entity_id) = explode('/', $id);
  30. $ids_by_type[$type][$entity_id] = $id;
  31. }
  32. $items = array();
  33. foreach ($ids_by_type as $type => $type_ids) {
  34. foreach (entity_load($type, array_keys($type_ids)) as $entity_id => $entity) {
  35. $id = $type_ids[$entity_id];
  36. $item = (object) array($type => $entity);
  37. $item->item_id = $id;
  38. $item->item_type = $type;
  39. $item->item_entity_id = $entity_id;
  40. $item->item_bundle = NULL;
  41. try {
  42. list(, , $bundle) = entity_extract_ids($type, $entity);
  43. $item->item_bundle = $bundle ? "$type:$bundle" : NULL;
  44. }
  45. catch (EntityMalformedException $e) {
  46. // Will probably make problems at some other place, but for extracting
  47. // the bundle it is really not critical enough to fail on – just
  48. // ignore this exception.
  49. }
  50. $items[$id] = $item;
  51. unset($type_ids[$entity_id]);
  52. }
  53. if ($type_ids) {
  54. search_api_track_item_delete($type, array_keys($type_ids));
  55. }
  56. }
  57. return $items;
  58. }
  59. /**
  60. * {@inheritdoc}
  61. */
  62. protected function getPropertyInfo() {
  63. $info = array(
  64. 'item_id' => array(
  65. 'label' => t('ID'),
  66. 'description' => t('The combined ID of the item, containing both entity type and entity ID.'),
  67. 'type' => 'token',
  68. ),
  69. 'item_type' => array(
  70. 'label' => t('Entity type'),
  71. 'description' => t('The entity type of the item.'),
  72. 'type' => 'token',
  73. 'options list' => 'search_api_entity_type_options_list',
  74. ),
  75. 'item_entity_id' => array(
  76. 'label' => t('Entity ID'),
  77. 'description' => t('The entity ID of the item.'),
  78. 'type' => 'token',
  79. ),
  80. 'item_bundle' => array(
  81. 'label' => t('Bundle'),
  82. 'description' => t('The bundle of the item, if applicable.'),
  83. 'type' => 'token',
  84. 'options list' => 'search_api_combined_bundle_options_list',
  85. ),
  86. 'item_label' => array(
  87. 'label' => t('Label'),
  88. 'description' => t('The label of the item.'),
  89. 'type' => 'text',
  90. // Since this needs a bit more computation than the others, we don't
  91. // include it always when loading the item but use a getter callback.
  92. 'getter callback' => 'search_api_get_multi_type_item_label',
  93. ),
  94. );
  95. foreach ($this->getSelectedEntityTypeOptions() as $type => $label) {
  96. $info[$type] = array(
  97. 'label' => $label,
  98. 'description' => t('The indexed entity, if it is of type %type.', array('%type' => $label)),
  99. 'type' => $type,
  100. );
  101. }
  102. return array('property info' => $info);
  103. }
  104. /**
  105. * {@inheritdoc}
  106. */
  107. public function getItemId($item) {
  108. return isset($item->item_id) ? $item->item_id : NULL;
  109. }
  110. /**
  111. * {@inheritdoc}
  112. */
  113. public function getItemLabel($item) {
  114. return search_api_get_multi_type_item_label($item);
  115. }
  116. /**
  117. * {@inheritdoc}
  118. */
  119. public function getItemUrl($item) {
  120. if ($item->item_type == 'file') {
  121. return array(
  122. 'path' => file_create_url($item->file->uri),
  123. 'options' => array(
  124. 'entity_type' => 'file',
  125. 'entity' => $item,
  126. ),
  127. );
  128. }
  129. $url = entity_uri($item->item_type, $item->{$item->item_type});
  130. return $url ? $url : NULL;
  131. }
  132. /**
  133. * {@inheritdoc}
  134. */
  135. public function startTracking(array $indexes) {
  136. if (!$this->table) {
  137. return;
  138. }
  139. // We first clear the tracking table for all indexes, so we can just insert
  140. // all items again without any key conflicts.
  141. $this->stopTracking($indexes);
  142. foreach ($indexes as $index) {
  143. $types = $this->getEntityTypes($index);
  144. // Wherever possible, use a sub-select instead of the much slower
  145. // entity_load().
  146. foreach ($types as $type) {
  147. $entity_info = entity_get_info($type);
  148. if (!empty($entity_info['base table'])) {
  149. // Assumes that all entities use the "base table" property and the
  150. // "entity keys[id]" in the same way as the default controller.
  151. $id_field = $entity_info['entity keys']['id'];
  152. $table = $entity_info['base table'];
  153. // Select all entity ids.
  154. $query = db_select($table, 't');
  155. $query->addExpression("CONCAT(:prefix, t.$id_field)", 'item_id', array(':prefix' => $type . '/'));
  156. $query->addExpression(':index_id', 'index_id', array(':index_id' => $index->id));
  157. $query->addExpression('1', 'changed');
  158. // INSERT ... SELECT ...
  159. db_insert($this->table)
  160. ->from($query)
  161. ->execute();
  162. unset($types[$type]);
  163. }
  164. }
  165. // In the absence of a "base table", use the slow entity_load().
  166. if ($types) {
  167. foreach ($types as $type) {
  168. $query = new EntityFieldQuery();
  169. $query->entityCondition('entity_type', $type);
  170. $result = $query->execute();
  171. $ids = !empty($result[$type]) ? array_keys($result[$type]) : array();
  172. if ($ids) {
  173. foreach ($ids as $i => $id) {
  174. $ids[$i] = $type . '/' . $id;
  175. }
  176. $this->trackItemInsert($ids, array($index), TRUE);
  177. }
  178. }
  179. }
  180. }
  181. }
  182. /**
  183. * Starts tracking the index status for the given items on the given indexes.
  184. *
  185. * @param array $item_ids
  186. * The IDs of new items to track.
  187. * @param SearchApiIndex[] $indexes
  188. * The indexes for which items should be tracked.
  189. * @param bool $skip_type_check
  190. * (optional) If TRUE, don't check whether the type matches the index's
  191. * datasource configuration. Internal use only.
  192. *
  193. * @return SearchApiIndex[]|null
  194. * All indexes for which any items were added; or NULL if items were added
  195. * for all of them.
  196. *
  197. * @throws SearchApiDataSourceException
  198. * If any error state was encountered.
  199. */
  200. public function trackItemInsert(array $item_ids, array $indexes, $skip_type_check = FALSE) {
  201. $ret = array();
  202. foreach ($indexes as $index_id => $index) {
  203. $ids = drupal_map_assoc($item_ids);
  204. if (!$skip_type_check) {
  205. $types = $this->getEntityTypes($index);
  206. foreach ($ids as $id) {
  207. list($type) = explode('/', $id);
  208. if (!isset($types[$type])) {
  209. unset($ids[$id]);
  210. }
  211. }
  212. }
  213. if ($ids) {
  214. parent::trackItemInsert($ids, array($index));
  215. $ret[$index_id] = $index;
  216. }
  217. }
  218. return $ret;
  219. }
  220. /**
  221. * {@inheritdoc}
  222. */
  223. public function configurationForm(array $form, array &$form_state) {
  224. $form['types'] = array(
  225. '#type' => 'checkboxes',
  226. '#title' => t('Entity types'),
  227. '#description' => t('Select the entity types which should be included in this index.'),
  228. '#options' => array_map('check_plain', search_api_entity_type_options_list()),
  229. '#attributes' => array('class' => array('search-api-checkboxes-list')),
  230. '#disabled' => !empty($form_state['index']),
  231. '#required' => TRUE,
  232. );
  233. if (!empty($form_state['index']->options['datasource']['types'])) {
  234. $form['types']['#default_value'] = $this->getEntityTypes($form_state['index']);
  235. }
  236. return $form;
  237. }
  238. /**
  239. * {@inheritdoc}
  240. */
  241. public function configurationFormSubmit(array $form, array &$values, array &$form_state) {
  242. if (!empty($values['types'])) {
  243. $values['types'] = array_keys(array_filter($values['types']));
  244. }
  245. }
  246. /**
  247. * {@inheritdoc}
  248. */
  249. public function getConfigurationSummary(SearchApiIndex $index) {
  250. if ($type_labels = $this->getSelectedEntityTypeOptions($index)) {
  251. $args['!types'] = implode(', ', $type_labels);
  252. return format_plural(count($type_labels), 'Indexed entity types: !types.', 'Indexed entity types: !types.', $args);
  253. }
  254. return NULL;
  255. }
  256. /**
  257. * Retrieves the index for which the current method was called.
  258. *
  259. * Very ugly method which uses the stack trace to find the right object.
  260. *
  261. * @return SearchApiIndex
  262. * The active index.
  263. *
  264. * @throws SearchApiException
  265. * Thrown if the active index could not be determined.
  266. */
  267. protected function getCallingIndex() {
  268. foreach (debug_backtrace() as $trace) {
  269. if (isset($trace['object']) && $trace['object'] instanceof SearchApiIndex) {
  270. return $trace['object'];
  271. }
  272. }
  273. // If there's only a single index on the site, it's also easy.
  274. $indexes = search_api_index_load_multiple(FALSE);
  275. if (count($indexes) === 1) {
  276. return reset($indexes);
  277. }
  278. throw new SearchApiException('Could not determine the active index of the datasource.');
  279. }
  280. /**
  281. * Returns the entity types for which this datasource is configured.
  282. *
  283. * Depends on the index from which this method is (indirectly) called.
  284. *
  285. * @param SearchApiIndex $index
  286. * (optional) The index for which to get the enabled entity types. If not
  287. * given, will be determined automatically.
  288. *
  289. * @return string[]
  290. * The machine names of the datasource's enabled entity types, as both keys
  291. * and values.
  292. *
  293. * @throws SearchApiException
  294. * Thrown if the active index could not be determined.
  295. */
  296. protected function getEntityTypes(SearchApiIndex $index = NULL) {
  297. if (!$index) {
  298. $index = $this->getCallingIndex();
  299. }
  300. if (isset($index->options['datasource']['types'])) {
  301. return drupal_map_assoc($index->options['datasource']['types']);
  302. }
  303. return array();
  304. }
  305. /**
  306. * Returns the selected entity type options for this datasource.
  307. *
  308. * Depends on the index from which this method is (indirectly) called.
  309. *
  310. * @param SearchApiIndex $index
  311. * (optional) The index for which to get the enabled entity types. If not
  312. * given, will be determined automatically.
  313. *
  314. * @return string[]
  315. * An associative array, mapping the machine names of the enabled entity
  316. * types to their labels.
  317. *
  318. * @throws SearchApiException
  319. * Thrown if the active index could not be determined.
  320. */
  321. protected function getSelectedEntityTypeOptions(SearchApiIndex $index = NULL) {
  322. return array_intersect_key(search_api_entity_type_options_list(), $this->getEntityTypes($index));
  323. }
  324. }