Commit 66a87339 authored by João Ventura's avatar João Ventura Committed by Thomas Seiber
Browse files

Issue #2958201 by jcnventura, drunken monkey: Reverted issue #2566529: Added...

Issue #2958201 by jcnventura, drunken monkey: Reverted issue #2566529: Added support for the "Content access" processor for "Multiple types" indexes.
parent 2744b4e4
Loading
Loading
Loading
Loading
+2 −0
Changes for CHANGELOG.txt: 2 added lines, 0 removed lines.
Original line number Diff line number Diff line
Search API 1.x, dev (xxxx-xx-xx):
---------------------------------
- #2958201 by jcnventura, drunken monkey: Reverted issue #2566529: Added
  support for the "Content access" processor for "Multiple types" indexes.

Search API 1.23 (2018-03-31):
-----------------------------
+3 −18
Changes for includes/callback_comment_access.inc: 3 added lines, 18 removed lines.
Original line number Diff line number Diff line
@@ -12,16 +12,10 @@ class SearchApiAlterCommentAccess extends SearchApiAlterNodeAccess {
  /**
   * Overrides SearchApiAlterNodeAccess::supportsIndex().
   *
   * Returns TRUE only for indexes on comments or multi-entity indexes that
   * include comments.
   * Returns TRUE only for indexes on comments.
   */
  public function supportsIndex(SearchApiIndex $index) {
    if ($this->isMultiEntityIndex($index)) {
      return in_array('comment', $index->options['datasource']['types']);
    }
    else {
      return $index->getEntityType() == 'comment';
    }
    return $index->getEntityType() === 'comment';
  }

  /**
@@ -30,16 +24,7 @@ class SearchApiAlterCommentAccess extends SearchApiAlterNodeAccess {
   * Returns the comment's node, instead of the item (i.e., the comment) itself.
   */
  protected function getNode($item) {
    if ($this->isMultiEntityIndex()) {
      if ($item->item_type !== 'comment') {
        return NULL;
      }
      $nid = $item->comment->nid;
    }
    else {
      $nid = $item->nid;
    }
    return node_load($nid);
    return node_load($item->nid);
  }

  /**
+2 −25
Changes for includes/callback_node_access.inc: 2 added lines, 25 removed lines.
Original line number Diff line number Diff line
@@ -12,17 +12,11 @@ class SearchApiAlterNodeAccess extends SearchApiAbstractAlterCallback {
  /**
   * Overrides SearchApiAbstractAlterCallback::supportsIndex().
   *
   * Returns TRUE only for indexes on nodes or multi-entity indexes that include
   * nodes.
   * Returns TRUE only for indexes on nodes.
   */
  public function supportsIndex(SearchApiIndex $index) {
    // Currently only node access is supported.
    if ($this->isMultiEntityIndex($index)) {
      return in_array('node', $index->options['datasource']['types']);
    }
    else {
      return $index->getEntityType() == 'node';
    }
    return $index->getEntityType() === 'node';
  }

  /**
@@ -53,12 +47,6 @@ class SearchApiAlterNodeAccess extends SearchApiAbstractAlterCallback {

    foreach ($items as $id => $item) {
      $node = $this->getNode($item);

      // Skip non-node items.
      if (empty($node)) {
        continue;
      }

      // Check whether all users have access to the node.
      if (!node_access('view', $node, $account)) {
        // Get node access grants.
@@ -83,19 +71,8 @@ class SearchApiAlterNodeAccess extends SearchApiAbstractAlterCallback {
   * In the default implementation for nodes, the item is already the node.
   * Subclasses may override this to easily provide node access checks for
   * items related to nodes.
   *
   * @param $item
   *  The item which is being indexed.
   *
   * @return
   *  Either the node object, or NULL if the current item is not a node.
   */
  protected function getNode($item) {
    if ($this->isMultiEntityIndex()) {
      // For multi-entity indexes, check whether the item is a node.
      return $item->item_type == 'node' ? $item->node : NULL;
    }
    // Normal node index: the whole item is the node.
    return $item;
  }

+4 −39
Changes for includes/callback_node_status.inc: 4 added lines, 39 removed lines.
Original line number Diff line number Diff line
@@ -22,12 +22,7 @@ class SearchApiAlterNodeStatus extends SearchApiAbstractAlterCallback {
   *   TRUE if the callback can run on the given index; FALSE otherwise.
   */
  public function supportsIndex(SearchApiIndex $index) {
    if ($this->isMultiEntityIndex($index)) {
      return in_array('node', $index->options['datasource']['types']);
    }
    else {
      return $index->getEntityType() == 'node';
    }
    return $index->getEntityType() === 'node';
  }

  /**
@@ -40,40 +35,10 @@ class SearchApiAlterNodeStatus extends SearchApiAbstractAlterCallback {
   *   An array of items to be altered, keyed by item IDs.
   */
  public function alterItems(array &$items) {
    foreach ($items as $id => $item) {
      $node = $this->getNode($item);

      // Skip non-node items.
      if (!empty($node) && empty($node->status)) {
        unset($items[$id]);
      }
    }
  }

  /**
   * Retrieves the node related to a search item.
   *
   * @param object $item
   *  The item which is being indexed.
   *
   * @return object|null
   *  Either the node object, or NULL if the current item is not a node.
   */
  protected function getNode($item) {
    if ($this->isMultiEntityIndex()) {
      // For multi-entity indexes, check the item is a node.
      if ($item->item_type == 'node') {
        // The node is stored on a property of the item.
        return $item->node;
      }
      else {
        // Not a node.
        return NULL;
      }
    foreach ($items as $nid => &$item) {
      if (empty($item->status)) {
        unset($items[$nid]);
      }
    else {
      // Normal node index: the whole item is the node.
      return $item;
    }
  }

+36 −110
Changes for search_api.module: 36 added lines, 110 removed lines.
Original line number Diff line number Diff line
@@ -2073,32 +2073,17 @@ function search_api_get_processors() {
function search_api_search_api_query_alter(SearchApiQueryInterface $query) {
  global $user;
  $index = $query->getIndex();

  // Only add node access if the necessary fields are indexed in the index, and
  // unless disabled explicitly by the query.
  $types = array();
  if ($index->getEntityType()) {
    $types[] = $index->getEntityType();
  }
  if (!empty($index->options['datasource']['types'])) {
    $types += $index->options['datasource']['types'];
  }
  $data_alter_callback_exists = FALSE;
  foreach ($types as $type) {
    if (!empty($index->options['data_alter_callbacks']["search_api_alter_{$type}_access"]['status'])) {
      $data_alter_callback_exists = TRUE;
      break;
    }
  }

  if ($data_alter_callback_exists && !$query->getOption('search_api_bypass_access')) {
  $type = $index->getEntityType();
  if (!empty($index->options['data_alter_callbacks']["search_api_alter_{$type}_access"]['status']) && !$query->getOption('search_api_bypass_access')) {
    $account = $query->getOption('search_api_access_account', $user);
    if (is_numeric($account)) {
      $account = user_load($account);
    }
    if (is_object($account)) {
      try {
        _search_api_query_add_node_access($account, $query);
        _search_api_query_add_node_access($account, $query, $type);
      }
      catch (SearchApiException $e) {
        watchdog_exception('search_api', $e);
@@ -2124,125 +2109,66 @@ function search_api_search_api_query_alter(SearchApiQueryInterface $query) {
 *   The user object, who searches.
 * @param SearchApiQueryInterface $query
 *   The query to which a node access filter should be added, if applicable.
 * @param string $type
 *   (optional) The type of search – either "node" or "comment". Defaults to
 *   "node".
 *
 * @throws SearchApiException
 *   If not all necessary fields are indexed on the index.
 */
function _search_api_query_add_node_access($account, SearchApiQueryInterface $query) {
function _search_api_query_add_node_access($account, SearchApiQueryInterface $query, $type = 'node') {
  // Don't do anything if the user can access all content.
  if (user_access('bypass node access', $account)) {
    return;
  }

  $affected_types = array();
  $unaffected_types = array();
  $status_field = NULL;

  $index = $query->getIndex();
  // Gather the affected and unaffected entity types.
  if ($index->item_type === 'multiple') {
    $all_types = array();
    if (isset($index->options['datasource']['types'])) {
      $all_types = drupal_map_assoc($index->options['datasource']['types']);
    }
    foreach ($all_types as $type) {
      if (in_array($type, array('node', 'comment'))) {
        $affected_types[$type] = $type;
        $status_field = "$type:status";
      }
      else {
        $unaffected_types[$type] = $type;
      }
    }
  }
  elseif ($index->item_type === 'node') {
    $affected_types['node'] = 'node';
  }
  elseif ($index->item_type === 'comment') {
    $affected_types['comment'] = 'comment';
  }
  // Unaffected type. Do nothing.
  else {
    return;
  }
  $is_comment = ($type == 'comment');

  // The filter structure we want looks like this:
  //   [belongs to other datasource]
  //   OR
  //   (
  //     [is enabled (or was created by the user, if applicable)]
  //     AND
  //     [grants view access to one of the user's gid/realm combinations]
  //   )
  // If there are no "other" datasources, we don't need the nested OR,
  // however, and can add the inner conditions directly to the query.
  if ($unaffected_types) {
    $outer_conditions = $query->createFilter('OR', array('content_access'));
    $query->filter($outer_conditions);
    foreach ($unaffected_types as $type) {
      $outer_conditions->condition('item_type', $type);
    }
    $access_conditions = $query->createFilter('AND');
    $outer_conditions->filter($access_conditions);
  }
  else {
    $access_conditions = $query;
  // Check whether the necessary fields are indexed.
  $fields = $query->getIndex()->options['fields'];
  $required = array('search_api_access_node', 'status');
  if (!$is_comment) {
    $required[] = 'author';
  }

  if (!user_access('access content', $account)) {
    unset($affected_types['node']);
  foreach ($required as $field) {
    if (empty($fields[$field])) {
      $vars['@field'] = $field;
      $vars['@index'] = $query->getIndex()->name;
      throw new SearchApiException(t('Required field @field not indexed on index @index. Could not perform access checks.', $vars));
    }
  if (!user_access('access comments', $account)) {
    unset($affected_types['comment']);
  }

  // If the user does not have the permission to see any content at all, deny
  // access to all items from affected datasources.
  if (!$affected_types) {
    // If there were "other" datasources, the existing filter will already
    // remove all results of node or comment datasources. Otherwise, we should
    // not return any results at all.
    if (!$unaffected_types) {
  // If the user cannot access content/comments at all, return no results.
  if (!user_access('access content', $account) || ($is_comment && !user_access('access comments', $account))) {
    // Simple hack for returning no results.
      $query->condition($status_field, 0);
      $query->condition($status_field, 1);
    $query->condition('status', 0);
    $query->condition('status', 1);
    watchdog('search_api', 'User @name tried to execute a search, but cannot access content.', array('@name' => theme('username', array('account' => $account))), WATCHDOG_NOTICE);

    }
    return;
  }

  // Collect all the required fields that need to be part of the index.
  $unpublished_own = user_access('view own unpublished content', $account);

  $enabled_conditions = $query->createFilter('OR', array('content_access_enabled'));
  foreach ($affected_types as $entity_type) {
    // If this is a comment datasource, or users cannot view their own
    // unpublished nodes, a simple filter on "status" is enough. Otherwise,
    // it's a bit more complicated.
    $status_field = $entity_type . ':status';
    if ($status_field) {
      $enabled_conditions->condition($status_field, '1');
    }
    if ($entity_type == 'node' && $unpublished_own) {
      $author_field = $entity_type . ':author';
      if ($author_field) {
        $enabled_conditions->condition($author_field, $account->uid);
      }
  // Filter by the "published" status.
  $published = $is_comment ? COMMENT_PUBLISHED : NODE_PUBLISHED;
  if (!$is_comment && user_access('view own unpublished content')) {
    $filter = $query->createFilter('OR');
    $filter->condition('status', $published);
    $filter->condition('author', $account->uid);
    $query->filter($filter);
  }
  else {
    $query->condition('status', $published);
  }

  $access_conditions->filter($enabled_conditions);

  // Filter by the user's node access grants.
  $grants_conditions = $query->createFilter('OR', array('content_access_grants'));
  // Filter by node access grants.
  $filter = $query->createFilter('OR');
  $grants = node_access_grants('view', $account);
  foreach ($grants as $realm => $gids) {
    foreach ($gids as $gid) {
      $grants_conditions->condition('search_api_access_node', "node_access_$realm:$gid");
      $filter->condition('search_api_access_node', "node_access_$realm:$gid");
    }
  }
  $access_conditions->filter($grants_conditions);
  $filter->condition('search_api_access_node', 'node_access__all');
  $query->filter($filter);
}

/**