123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360 |
- <?php
- /**
- * @file
- * Contains SearchApiCombinedEntityDataSourceController.
- */
- /**
- * Provides a datasource for indexing multiple types of entities.
- */
- class SearchApiCombinedEntityDataSourceController extends SearchApiAbstractDataSourceController {
- /**
- * {@inheritdoc}
- */
- protected $table = 'search_api_item_string_id';
- /**
- * {@inheritdoc}
- */
- public function getIdFieldInfo() {
- return array(
- 'key' => 'item_id',
- 'type' => 'string',
- );
- }
- /**
- * {@inheritdoc}
- */
- public function loadItems(array $ids) {
- $ids_by_type = array();
- foreach ($ids as $id) {
- list($type, $entity_id) = explode('/', $id);
- $ids_by_type[$type][$entity_id] = $id;
- }
- $items = array();
- foreach ($ids_by_type as $type => $type_ids) {
- foreach (entity_load($type, array_keys($type_ids)) as $entity_id => $entity) {
- $id = $type_ids[$entity_id];
- $item = (object) array($type => $entity);
- $item->item_id = $id;
- $item->item_type = $type;
- $item->item_entity_id = $entity_id;
- $item->item_bundle = NULL;
- // Add the item language so the "search_api_language" field will work
- // correctly.
- $item->language = isset($entity->language) ? $entity->language : NULL;
- try {
- list(, , $bundle) = entity_extract_ids($type, $entity);
- $item->item_bundle = $bundle ? "$type:$bundle" : NULL;
- }
- catch (EntityMalformedException $e) {
- // Will probably make problems at some other place, but for extracting
- // the bundle it is really not critical enough to fail on – just
- // ignore this exception.
- }
- $items[$id] = $item;
- unset($type_ids[$entity_id]);
- }
- if ($type_ids) {
- search_api_track_item_delete($type, array_keys($type_ids));
- }
- }
- return $items;
- }
- /**
- * {@inheritdoc}
- */
- protected function getPropertyInfo() {
- $info = array(
- 'item_id' => array(
- 'label' => t('ID'),
- 'description' => t('The combined ID of the item, containing both entity type and entity ID.'),
- 'type' => 'token',
- ),
- 'item_type' => array(
- 'label' => t('Entity type'),
- 'description' => t('The entity type of the item.'),
- 'type' => 'token',
- 'options list' => 'search_api_entity_type_options_list',
- ),
- 'item_entity_id' => array(
- 'label' => t('Entity ID'),
- 'description' => t('The entity ID of the item.'),
- 'type' => 'token',
- ),
- 'item_bundle' => array(
- 'label' => t('Bundle'),
- 'description' => t('The bundle of the item, if applicable.'),
- 'type' => 'token',
- 'options list' => 'search_api_combined_bundle_options_list',
- ),
- 'item_label' => array(
- 'label' => t('Label'),
- 'description' => t('The label of the item.'),
- 'type' => 'text',
- // Since this needs a bit more computation than the others, we don't
- // include it always when loading the item but use a getter callback.
- 'getter callback' => 'search_api_get_multi_type_item_label',
- ),
- );
- foreach ($this->getSelectedEntityTypeOptions() as $type => $label) {
- $info[$type] = array(
- 'label' => $label,
- 'description' => t('The indexed entity, if it is of type %type.', array('%type' => $label)),
- 'type' => $type,
- );
- }
- return array('property info' => $info);
- }
- /**
- * {@inheritdoc}
- */
- public function getItemId($item) {
- return isset($item->item_id) ? $item->item_id : NULL;
- }
- /**
- * {@inheritdoc}
- */
- public function getItemLabel($item) {
- return search_api_get_multi_type_item_label($item);
- }
- /**
- * {@inheritdoc}
- */
- public function getItemUrl($item) {
- if ($item->item_type == 'file') {
- return array(
- 'path' => file_create_url($item->file->uri),
- 'options' => array(
- 'entity_type' => 'file',
- 'entity' => $item,
- ),
- );
- }
- $url = entity_uri($item->item_type, $item->{$item->item_type});
- return $url ? $url : NULL;
- }
- /**
- * {@inheritdoc}
- */
- public function startTracking(array $indexes) {
- if (!$this->table) {
- return;
- }
- // We first clear the tracking table for all indexes, so we can just insert
- // all items again without any key conflicts.
- $this->stopTracking($indexes);
- foreach ($indexes as $index) {
- $types = $this->getEntityTypes($index);
- // Wherever possible, use a sub-select instead of the much slower
- // entity_load().
- foreach ($types as $type) {
- $entity_info = entity_get_info($type);
- if (!empty($entity_info['base table'])) {
- // Assumes that all entities use the "base table" property and the
- // "entity keys[id]" in the same way as the default controller.
- $id_field = $entity_info['entity keys']['id'];
- $table = $entity_info['base table'];
- // Select all entity ids.
- $query = db_select($table, 't');
- $query->addExpression("CONCAT(:prefix, t.$id_field)", 'item_id', array(':prefix' => $type . '/'));
- $query->addExpression(':index_id', 'index_id', array(':index_id' => $index->id));
- $query->addExpression('1', 'changed');
- // INSERT ... SELECT ...
- db_insert($this->table)
- ->from($query)
- ->execute();
- unset($types[$type]);
- }
- }
- // In the absence of a "base table", use the slow entity_load().
- if ($types) {
- foreach ($types as $type) {
- $query = new EntityFieldQuery();
- $query->entityCondition('entity_type', $type);
- $result = $query->execute();
- $ids = !empty($result[$type]) ? array_keys($result[$type]) : array();
- if ($ids) {
- foreach ($ids as $i => $id) {
- $ids[$i] = $type . '/' . $id;
- }
- $this->trackItemInsert($ids, array($index), TRUE);
- }
- }
- }
- }
- }
- /**
- * Starts tracking the index status for the given items on the given indexes.
- *
- * @param array $item_ids
- * The IDs of new items to track.
- * @param SearchApiIndex[] $indexes
- * The indexes for which items should be tracked.
- * @param bool $skip_type_check
- * (optional) If TRUE, don't check whether the type matches the index's
- * datasource configuration. Internal use only.
- *
- * @return SearchApiIndex[]|null
- * All indexes for which any items were added; or NULL if items were added
- * for all of them.
- *
- * @throws SearchApiDataSourceException
- * If any error state was encountered.
- */
- public function trackItemInsert(array $item_ids, array $indexes, $skip_type_check = FALSE) {
- $ret = array();
- foreach ($indexes as $index_id => $index) {
- $ids = drupal_map_assoc($item_ids);
- if (!$skip_type_check) {
- $types = $this->getEntityTypes($index);
- foreach ($ids as $id) {
- list($type) = explode('/', $id);
- if (!isset($types[$type])) {
- unset($ids[$id]);
- }
- }
- }
- if ($ids) {
- parent::trackItemInsert($ids, array($index));
- $ret[$index_id] = $index;
- }
- }
- return $ret;
- }
- /**
- * {@inheritdoc}
- */
- public function configurationForm(array $form, array &$form_state) {
- $form['types'] = array(
- '#type' => 'checkboxes',
- '#title' => t('Entity types'),
- '#description' => t('Select the entity types which should be included in this index.'),
- '#options' => array_map('check_plain', search_api_entity_type_options_list()),
- '#attributes' => array('class' => array('search-api-checkboxes-list')),
- '#disabled' => !empty($form_state['index']),
- '#required' => TRUE,
- );
- if (!empty($form_state['index']->options['datasource']['types'])) {
- $form['types']['#default_value'] = $this->getEntityTypes($form_state['index']);
- }
- return $form;
- }
- /**
- * {@inheritdoc}
- */
- public function configurationFormSubmit(array $form, array &$values, array &$form_state) {
- if (!empty($values['types'])) {
- $values['types'] = array_keys(array_filter($values['types']));
- }
- }
- /**
- * {@inheritdoc}
- */
- public function getConfigurationSummary(SearchApiIndex $index) {
- if ($type_labels = $this->getSelectedEntityTypeOptions($index)) {
- $args['!types'] = implode(', ', $type_labels);
- return format_plural(count($type_labels), 'Indexed entity types: !types.', 'Indexed entity types: !types.', $args);
- }
- return NULL;
- }
- /**
- * Retrieves the index for which the current method was called.
- *
- * Very ugly method which uses the stack trace to find the right object.
- *
- * @return SearchApiIndex
- * The active index.
- *
- * @throws SearchApiException
- * Thrown if the active index could not be determined.
- */
- protected function getCallingIndex() {
- foreach (debug_backtrace() as $trace) {
- if (isset($trace['object']) && $trace['object'] instanceof SearchApiIndex) {
- return $trace['object'];
- }
- }
- // If there's only a single index on the site, it's also easy.
- $indexes = search_api_index_load_multiple(FALSE);
- if (count($indexes) === 1) {
- return reset($indexes);
- }
- throw new SearchApiException('Could not determine the active index of the datasource.');
- }
- /**
- * Returns the entity types for which this datasource is configured.
- *
- * Depends on the index from which this method is (indirectly) called.
- *
- * @param SearchApiIndex $index
- * (optional) The index for which to get the enabled entity types. If not
- * given, will be determined automatically.
- *
- * @return string[]
- * The machine names of the datasource's enabled entity types, as both keys
- * and values.
- *
- * @throws SearchApiException
- * Thrown if the active index could not be determined.
- */
- protected function getEntityTypes(SearchApiIndex $index = NULL) {
- if (!$index) {
- $index = $this->getCallingIndex();
- }
- if (isset($index->options['datasource']['types'])) {
- return drupal_map_assoc($index->options['datasource']['types']);
- }
- return array();
- }
- /**
- * Returns the selected entity type options for this datasource.
- *
- * Depends on the index from which this method is (indirectly) called.
- *
- * @param SearchApiIndex $index
- * (optional) The index for which to get the enabled entity types. If not
- * given, will be determined automatically.
- *
- * @return string[]
- * An associative array, mapping the machine names of the enabled entity
- * types to their labels.
- *
- * @throws SearchApiException
- * Thrown if the active index could not be determined.
- */
- protected function getSelectedEntityTypeOptions(SearchApiIndex $index = NULL) {
- return array_intersect_key(search_api_entity_type_options_list(), $this->getEntityTypes($index));
- }
- }
|