You are here

public function ContentEntity::getPartialItemIds in Search API 8

1 call to ContentEntity::getPartialItemIds()
ContentEntity::getItemIds in src/Plugin/search_api/datasource/ContentEntity.php
Returns a list of IDs of items from this datasource.

File

src/Plugin/search_api/datasource/ContentEntity.php, line 780

Class

ContentEntity
Represents a datasource which exposes the content entities.

Namespace

Drupal\search_api\Plugin\search_api\datasource

Code

public function getPartialItemIds($page = NULL, array $bundles = NULL, array $languages = NULL) {

  // These would be pretty pointless calls, but for the sake of completeness
  // we should check for them and return early. (Otherwise makes the rest of
  // the code more complicated.)
  if ($bundles === [] && !$languages || $languages === [] && !$bundles) {
    return NULL;
  }
  $entity_type = $this
    ->getEntityType();
  $entity_id = $entity_type
    ->getKey('id');

  // Use a direct database query when an entity has a defined base table. This
  // should prevent performance issues associated with the use of entity query
  // on large data sets. This allows for better control over what tables are
  // included in the query.
  // If no base table is present, then perform an entity query instead.
  if ($entity_type
    ->getBaseTable()) {
    $select = $this
      ->getDatabaseConnection()
      ->select($entity_type
      ->getBaseTable(), 'base_table')
      ->fields('base_table', [
      $entity_id,
    ]);
  }
  else {
    $select = $this
      ->getEntityTypeManager()
      ->getStorage($this
      ->getEntityTypeId())
      ->getQuery();

    // When tracking items, we never want access checks.
    $select
      ->accessCheck(FALSE);
  }

  // Build up the context for tracking the last ID for this batch page.
  $batch_page_context = [
    'index_id' => $this
      ->getIndex()
      ->id(),
    // The derivative plugin ID includes the entity type ID.
    'datasource_id' => $this
      ->getPluginId(),
    'bundles' => $bundles,
    'languages' => $languages,
  ];
  $context_key = Crypt::hashBase64(serialize($batch_page_context));
  $last_ids = $this
    ->getState()
    ->get(self::TRACKING_PAGE_STATE_KEY, []);

  // We want to determine all entities of either one of the given bundles OR
  // one of the given languages. That means we can't just filter for $bundles
  // if $languages is given. Instead, we have to filter for all bundles we
  // might want to include and later sort out those for which we want only the
  // translations in $languages and those (matching $bundles) where we want
  // all (enabled) translations.
  if ($this
    ->hasBundles()) {
    $bundle_property = $entity_type
      ->getKey('bundle');
    if ($bundles && !$languages) {
      $select
        ->condition($bundle_property, $bundles, 'IN');
    }
    else {
      $enabled_bundles = array_keys($this
        ->getBundles());

      // Since this is also called for removed bundles/languages,
      // $enabled_bundles might not include $bundles.
      if ($bundles) {
        $enabled_bundles = array_unique(array_merge($bundles, $enabled_bundles));
      }
      if (count($enabled_bundles) < count($this
        ->getEntityBundles())) {
        $select
          ->condition($bundle_property, $enabled_bundles, 'IN');
      }
    }
  }
  if (isset($page)) {
    $page_size = $this
      ->getConfigValue('tracking_page_size');
    assert($page_size, 'Tracking page size is not set.');

    // If known, use a condition on the last tracked ID for paging instead of
    // the offset, for performance reasons on large sites.
    $offset = $page * $page_size;
    if ($page > 0) {

      // We only handle the case of picking up from where the last page left
      // off. (This will cause an infinite loop if anyone ever wants to index
      // Search API tasks in an index, so check for that to be on the safe
      // side.)
      if (isset($last_ids[$context_key]) && $last_ids[$context_key]['page'] == $page - 1 && $this
        ->getEntityTypeId() !== 'search_api_task') {
        $select
          ->condition($entity_id, $last_ids[$context_key]['last_id'], '>');
        $offset = 0;
      }
    }
    $select
      ->range($offset, $page_size);

    // For paging to reliably work, a sort should be present.
    if ($select instanceof SelectInterface) {
      $select
        ->orderBy($entity_id);
    }
    else {
      $select
        ->sort($entity_id);
    }
  }
  if ($select instanceof SelectInterface) {
    $entity_ids = $select
      ->execute()
      ->fetchCol();
  }
  else {
    $entity_ids = $select
      ->execute();
  }
  if (!$entity_ids) {
    if (isset($page)) {

      // Clean up state tracking of last ID.
      unset($last_ids[$context_key]);
      $this
        ->getState()
        ->set(self::TRACKING_PAGE_STATE_KEY, $last_ids);
    }
    return NULL;
  }

  // Remember the last tracked ID for the next call.
  if (isset($page)) {
    $last_ids[$context_key] = [
      'page' => (int) $page,
      'last_id' => end($entity_ids),
    ];
    $this
      ->getState()
      ->set(self::TRACKING_PAGE_STATE_KEY, $last_ids);
  }

  // For all loaded entities, compute all their item IDs (one for each
  // translation we want to include). For those matching the given bundles (if
  // any), we want to include translations for all enabled languages. For all
  // other entities, we just want to include the translations for the
  // languages passed to the method (if any).
  $item_ids = [];
  $enabled_languages = array_keys($this
    ->getLanguages());

  // As above for bundles, $enabled_languages might not include $languages.
  if ($languages) {
    $enabled_languages = array_unique(array_merge($languages, $enabled_languages));
  }

  // Also, we want to always include entities with unknown language.
  $enabled_languages[] = LanguageInterface::LANGCODE_NOT_SPECIFIED;
  $enabled_languages[] = LanguageInterface::LANGCODE_NOT_APPLICABLE;

  /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
  foreach ($this
    ->getEntityStorage()
    ->loadMultiple($entity_ids) as $entity_id => $entity) {
    $translations = array_keys($entity
      ->getTranslationLanguages());
    $translations = array_intersect($translations, $enabled_languages);

    // If only languages were specified, keep only those translations matching
    // them. If bundles were also specified, keep all (enabled) translations
    // for those entities that match those bundles.
    if ($languages !== NULL && (!$bundles || !in_array($entity
      ->bundle(), $bundles))) {
      $translations = array_intersect($translations, $languages);
    }
    foreach ($translations as $langcode) {
      $item_ids[] = "{$entity_id}:{$langcode}";
    }
  }
  if (Utility::isRunningInCli()) {

    // When running in the CLI, this might be executed for all entities from
    // within a single process. To avoid running out of memory, reset the
    // static cache after each batch.
    $this
      ->getEntityMemoryCache()
      ->deleteAll();
  }
  return $item_ids;
}