You are here

class SearchApiCombinedEntityDataSourceController in Search API 7

Provides a datasource for indexing multiple types of entities.

Hierarchy

Expanded class hierarchy of SearchApiCombinedEntityDataSourceController

1 string reference to 'SearchApiCombinedEntityDataSourceController'
search_api_search_api_item_type_info in ./search_api.module
Implements hook_search_api_item_type_info().

File

includes/datasource_multiple.inc, line 11
Contains SearchApiCombinedEntityDataSourceController.

View source
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));
  }

}

Members

Namesort descending Modifiers Type Description Overrides
SearchApiAbstractDataSourceController::$changedColumn protected property When using the default tracking mechanism: the name of the column on $this->table containing the indexing status.
SearchApiAbstractDataSourceController::$entityType protected property The entity type for this controller instance.
SearchApiAbstractDataSourceController::$indexIdColumn protected property When using the default tracking mechanism: the name of the column on $this->table containing the index ID.
SearchApiAbstractDataSourceController::$info protected property The info array for the item type, as specified via hook_search_api_item_type_info().
SearchApiAbstractDataSourceController::$itemIdColumn protected property When using the default tracking mechanism: the name of the column on $this->table containing the item ID.
SearchApiAbstractDataSourceController::$type protected property The item type for this controller instance.
SearchApiAbstractDataSourceController::checkIndex protected function Checks whether the given index is valid for this datasource controller.
SearchApiAbstractDataSourceController::configurationFormValidate public function Validation callback for the form returned by configurationForm(). Overrides SearchApiDataSourceControllerInterface::configurationFormValidate
SearchApiAbstractDataSourceController::getAllItemIds protected function Returns the IDs of all items that are known for this controller's type.
SearchApiAbstractDataSourceController::getChangedItems public function Retrieves a list of items that need to be indexed. Overrides SearchApiDataSourceControllerInterface::getChangedItems 1
SearchApiAbstractDataSourceController::getEntityType public function Retrieves the entity type of items from this datasource. Overrides SearchApiDataSourceControllerInterface::getEntityType
SearchApiAbstractDataSourceController::getIndexStatus public function Retrieves information on how many items have been indexed for a certain index. Overrides SearchApiDataSourceControllerInterface::getIndexStatus 1
SearchApiAbstractDataSourceController::getMetadataWrapper public function Creates a metadata wrapper for this datasource controller's type. Overrides SearchApiDataSourceControllerInterface::getMetadataWrapper 1
SearchApiAbstractDataSourceController::stopTracking public function Stops tracking of the index status of items for the given indexes. Overrides SearchApiDataSourceControllerInterface::stopTracking 1
SearchApiAbstractDataSourceController::trackItemChange public function Sets the tracking status of the given items to "changed"/"dirty". Overrides SearchApiDataSourceControllerInterface::trackItemChange 1
SearchApiAbstractDataSourceController::trackItemDelete public function Stops tracking the index status for the given items on the given indexes. Overrides SearchApiDataSourceControllerInterface::trackItemDelete 1
SearchApiAbstractDataSourceController::trackItemIndexed public function Sets the tracking status of the given items to "indexed". Overrides SearchApiDataSourceControllerInterface::trackItemIndexed 1
SearchApiAbstractDataSourceController::trackItemQueued public function Sets the tracking status of the given items to "queued". Overrides SearchApiDataSourceControllerInterface::trackItemQueued
SearchApiAbstractDataSourceController::__construct public function Constructs an SearchApiDataSourceControllerInterface object. Overrides SearchApiDataSourceControllerInterface::__construct 1
SearchApiCombinedEntityDataSourceController::$table protected property The table used for tracking items. Set to NULL on subclasses to disable the default tracking for an item type, or change the property to use a different table for tracking. Overrides SearchApiAbstractDataSourceController::$table
SearchApiCombinedEntityDataSourceController::configurationForm public function Form constructor for configuring the datasource for a given index. Overrides SearchApiAbstractDataSourceController::configurationForm
SearchApiCombinedEntityDataSourceController::configurationFormSubmit public function Submit callback for the form returned by configurationForm(). Overrides SearchApiAbstractDataSourceController::configurationFormSubmit
SearchApiCombinedEntityDataSourceController::getCallingIndex protected function Retrieves the index for which the current method was called.
SearchApiCombinedEntityDataSourceController::getConfigurationSummary public function Returns a summary of an index's current datasource configuration. Overrides SearchApiAbstractDataSourceController::getConfigurationSummary
SearchApiCombinedEntityDataSourceController::getEntityTypes protected function Returns the entity types for which this datasource is configured.
SearchApiCombinedEntityDataSourceController::getIdFieldInfo public function Returns information on the ID field for this controller's type. Overrides SearchApiDataSourceControllerInterface::getIdFieldInfo
SearchApiCombinedEntityDataSourceController::getItemId public function Retrieves the unique ID of an item. Overrides SearchApiAbstractDataSourceController::getItemId
SearchApiCombinedEntityDataSourceController::getItemLabel public function Retrieves a human-readable label for an item. Overrides SearchApiAbstractDataSourceController::getItemLabel
SearchApiCombinedEntityDataSourceController::getItemUrl public function Retrieves a URL at which the item can be viewed on the web. Overrides SearchApiAbstractDataSourceController::getItemUrl
SearchApiCombinedEntityDataSourceController::getPropertyInfo protected function Retrieves the property info for this item type. Overrides SearchApiAbstractDataSourceController::getPropertyInfo
SearchApiCombinedEntityDataSourceController::getSelectedEntityTypeOptions protected function Returns the selected entity type options for this datasource.
SearchApiCombinedEntityDataSourceController::loadItems public function Loads items of the type of this data source controller. Overrides SearchApiDataSourceControllerInterface::loadItems
SearchApiCombinedEntityDataSourceController::startTracking public function Initializes tracking of the index status of items for the given indexes. Overrides SearchApiAbstractDataSourceController::startTracking
SearchApiCombinedEntityDataSourceController::trackItemInsert public function Starts tracking the index status for the given items on the given indexes. Overrides SearchApiAbstractDataSourceController::trackItemInsert