1.20.x file.module _file_query_file_access_alter($query, $type)

Helper for file entity access functions.

Parameters

$query: The query to add conditions to.

$type: Either 'file' or 'entity' depending on what sort of query it is. See file_query_file_access_alter() and file_query_entity_field_access_alter() for more.

Related topics

File

modules/file/file.module, line 2990
Defines a "managed_file" Form API field and a "file" field for Field module.

Code

function _file_query_file_access_alter($query, $type) {
  global $user;

  // Read meta-data from query, if provided.
  if (!$account = $query->getMetaData('account')) {
    $account = $user;
  }

  // If $account can bypass file access, we don't need to alter the query.
  if (user_access('bypass file access', $account)) {
    return;
  }

  $tables = $query->getTables();
  $base_table = $query->getMetaData('base_table');
  // Do not use the base table for general entity queries unless this is
  // querying the file_managed table directly.
  if ($base_table && $type == 'entity' && $base_table != 'file_managed') {
    $base_table = '';
  }

  // If no base table is specified explicitly, search for one.
  if (!$base_table) {
    $fallback = '';
    foreach ($tables as $alias => $table_info) {
      if (!($table_info instanceof SelectQueryInterface || $table_info['table'] instanceof SelectQueryInterface)) {
        $table = $table_info['table'];
        // If the file_managed table is in the query, it wins immediately.
        if ($table == 'file_managed') {
          $base_table = $table;
          break;
        }
        // Check whether the table has a foreign key to file_managed.fid. If it
        // does, do not run this check again as we found a base table and only
        // file_managed can triumph that.
        if (!$base_table) {
          // The schema is cached.
          $schema = backdrop_get_schema($table);
          if (isset($schema['fields']['fid'])) {
            if (isset($schema['foreign keys'])) {
              foreach ($schema['foreign keys'] as $relation) {
                if ($relation['table'] === 'file_managed' && $relation['columns'] === array('fid' => 'fid')) {
                  $base_table = $table;
                }
              }
            }
            else {
              // At least it's a fid. A table with a field called fid is very
              // very likely to be a file_managed.fid in a file access query.
              $fallback = $table;
            }
          }
          elseif (isset($schema['fields']['entity_id']) && isset($schema['fields']['entity_type']) && isset($schema['fields']['deleted']) && isset($schema['fields']['delta'])) {
            // The table is a field data table, use it as fallback.
            $base_table = $table;
          }
        }
      }
    }
    // If there is nothing else, use the fallback.
    if (!$base_table) {
      if ($fallback) {
        watchdog('security', 'Your file listing query is using @fallback as a base table in a query tagged for file access. This might not be secure and might not even work. Specify foreign keys in your schema to file_managed.fid ', array('@fallback' => $fallback), WATCHDOG_WARNING);
        $base_table = $fallback;
      }
      else {
        // Ignore this query as it was a general field query and no
        // relationships were found to the file_managed table.
        if ($type == 'entity') {
          return;
        }
        throw new Exception(t('Query tagged for file access but there is no fid. Add foreign keys to file_managed.fid in schema to fix.'));
      }
    }
  }

  if ($type == 'entity') {
    // The original query looked something like:
    // @code
    //  SELECT fid FROM sometable s
    //  WHERE ($file_access_conditions)
    // @endcode
    //
    // Our query will look like:
    // @code
    //  SELECT entity_type, entity_id
    //  FROM field_data_something s
    //  WHERE (entity_type = 'file' AND $file_access_conditions) OR (entity_type <> 'file')
    // @endcode
    //
    // So instead of directly adding to the query object, we need to collect
    // all of the file access conditions in a separate db_and() object and
    // then add it to the query at the end.
    $file_conditions = db_and();
  }
  foreach ($tables as $falias => $tableinfo) {
    $table = $tableinfo['table'];
    if (!($table instanceof SelectQueryInterface) && $table == $base_table) {
      $subquery = db_select('file_managed', 'fm_access')->fields('fm_access', array('fid'));
      $subquery_conditions = db_or();

      $wrappers = file_get_public_and_private_stream_wrapper_names();
      if (!empty($wrappers['public'])) {
        if (user_access('view files', $account)) {
          foreach (array_keys($wrappers['public']) as $wrapper) {
            $subquery_conditions->condition('fm_access.uri', $wrapper . '%', 'LIKE');
          }
        }
        elseif (user_access('view own files', $account)) {
          foreach (array_keys($wrappers['public']) as $wrapper) {
            $subquery_conditions->condition(db_and()
              ->condition('fm_access.uri', $wrapper . '%', 'LIKE')
              ->condition('fm_access.uid', $account->uid)
              );
          }
        }
      }
      if (!empty($wrappers['private'])) {
        if (user_access('view private files', $account)) {
          foreach (array_keys($wrappers['private']) as $wrapper) {
            $subquery_conditions->condition('fm_access.uri', $wrapper . '%', 'LIKE');
          }
        }
        elseif (user_access('view own private files', $account)) {
          foreach (array_keys($wrappers['private']) as $wrapper) {
            $subquery_conditions->condition(db_and()
              ->condition('fm_access.uri', $wrapper . '%', 'LIKE')
              ->condition('fm_access.uid', $account->uid)
              );
          }
        }
      }

      // If there is no subquery conditions, the query is likely for file usage.
      // Or user can only access public files.
      // If there are subquery conditions then add them to the subquery.
      if ($subquery_conditions->count() >= 1) {
        $subquery->condition($subquery_conditions);
      }

      $field = 'fid';
      // Now handle entities.
      if ($type == 'entity') {
        // Set a common alias for entities.
        $base_alias = $falias;
        $field = ($falias == 'file_managed' ? 'fid' : 'entity_id');
      }
      $subquery->where("$falias.$field = fm_access.fid");

      // For an entity query, attach the subquery to entity conditions.
      if ($type == 'entity') {
        $file_conditions->exists($subquery);
      }
      // Otherwise attach it to the node query itself.
      elseif ($table == 'file_managed') {
        // Fix for https://drupal.org/node/2073085
        $db_or = db_or();
        $db_or->exists($subquery);
        $db_or->isNull($falias . '.' . $field);
        $query->condition($db_or);
      }
      else {
        $query->exists($subquery);
      }
    }
  }

  if ($type == 'entity' && $file_conditions->count()) {
    // All the file access conditions are only for field values belonging to
    // files.
    if ($base_alias !== 'file_managed') {
      $file_conditions->condition("$base_alias.entity_type", 'file');
      $or = db_or();
      $or->condition($file_conditions);
      // If the field value belongs to a non-file entity type then this function
      // does not do anything with it.
      $or->condition("$base_alias.entity_type", 'file', '<>');
      // Add the compiled set of rules to the query.
      $query->condition($or);
    }
    else {
      $query->condition($file_conditions);
    }
  }
}