diff --git a/modules/file/file.field.inc b/modules/file/file.field.inc index d540c0a..1978ff3 100644 --- a/modules/file/file.field.inc +++ b/modules/file/file.field.inc @@ -1002,6 +1002,30 @@ function file_field_formatter_view($entity_type, $entity, $field, $instance, $la } /** + * Determine whether a field references files stored in {file_managed}. + * + * @param array $field + * A field array. + * + * @return + * The field column if the field references {file_managed}.fid, typically + * fid, FALSE if it doesn't. + */ + +function file_field_find_file_reference_column($field) { + foreach ($field['foreign keys'] as $data) { + if ($data['table'] == 'file_managed') { + foreach ($data['columns'] as $field_column => $column) { + if ($column == 'fid') { + return $field_column; + } + } + } + } + return FALSE; +} + +/** * Returns HTML for a file attachments table. * * @param $variables diff --git a/modules/file/file.module b/modules/file/file.module index 5a635fd..aa43173 100644 --- a/modules/file/file.module +++ b/modules/file/file.module @@ -148,7 +148,7 @@ function file_file_download($uri, $field_type = 'file') { // an image preview on a node/add form) in which case, allow download by the // file's owner. if (empty($references) && ($file->status == FILE_STATUS_PERMANENT || $file->uid != $user->uid)) { - return; + return; } // Default to allow access. @@ -160,44 +160,24 @@ function file_file_download($uri, $field_type = 'file') { // and no reference denied access, access is granted as well. If at least one // reference denied access, access is denied. foreach ($references as $field_name => $field_references) { - foreach ($field_references as $entity_type => $type_references) { - foreach ($type_references as $id => $reference) { - // Try to load $entity and $field. - $entity = entity_load($entity_type, array($id)); - $entity = reset($entity); + foreach ($field_references as $entity_type => $entities) { + foreach ($entities as $entity) { $field = field_info_field($field_name); - // Load the field item that references the file. - $field_item = NULL; - if ($entity) { - // Load all field items for that entity. - $field_items = field_get_items($entity_type, $entity, $field_name); - - // Find the field item with the matching URI. - foreach ($field_items as $item) { - if ($item['uri'] == $uri) { - $field_item = $item; - break; - } - } - } - - // Check that $entity, $field and $field_item were loaded successfully - // and check if access to that field is not disallowed. If any of these - // checks fail, stop checking access for this reference. - if (empty($entity) || empty($field) || empty($field_item) || !field_access('view', $field, $entity_type, $entity)) { + // Check if access to this field is not disallowed. + if (!field_access('view', $field, $entity_type, $entity)) { $denied = TRUE; - break; + continue; } // Invoke hook and collect grants/denies for download access. // Default to FALSE and let entities overrule this ruling. $grants = array('system' => FALSE); foreach (module_implements('file_download_access') as $module) { - $grants = array_merge($grants, array($module => module_invoke($module, 'file_download_access', $field_item, $entity_type, $entity))); + $grants = array_merge($grants, array($module => module_invoke($module, 'file_download_access', $field, $entity_type, $entity))); } // Allow other modules to alter the returned grants/denies. - drupal_alter('file_download_access', $grants, $field_item, $entity_type, $entity); + drupal_alter('file_download_access', $grants, $field, $entity_type, $entity); if (in_array(TRUE, $grants)) { // If TRUE is returned, access is granted and no further checks are @@ -984,27 +964,83 @@ function file_icon_map($file) { * FIELD_LOAD_CURRENT to retrieve references only in the current revisions. * @param $field_type * (optional) The name of a field type. If given, limits the reference check - * to fields of the given type. + * to fields of the given type. If both $field and $field_type is given but + * $field is not the same type as $field_type, an empty array will be + * returned. * * @return - * An integer value. + * A multidimensional array. The keys are field_name, entity_type, + * entity_id and the value is an entity referencing this file. */ function file_get_file_references($file, $field = NULL, $age = FIELD_LOAD_REVISION, $field_type = 'file') { - $references = drupal_static(__FUNCTION__, array()); - $fields = isset($field) ? array($field['field_name'] => $field) : field_info_fields(); - - foreach ($fields as $field_name => $file_field) { - if ((empty($field_type) || $file_field['type'] == $field_type) && !isset($references[$field_name])) { - // Get each time this file is used within a field. - $query = new EntityFieldQuery(); - $query - ->fieldCondition($file_field, 'fid', $file->fid) - ->age($age); - $references[$field_name] = $query->execute(); + $references = &drupal_static(__FUNCTION__, array()); + $field_columns = &drupal_static(__FUNCTION__ . ':field_columns', array()); + + // Fill the static cache, disregard $field and $field_type for now. + if (!isset($references[$file->fid][$age])) { + $references[$file->fid][$age] = array(); + $usage_list = file_usage_list($file); + $file_usage_list = isset($usage_list['file']) ? $usage_list['file'] : array(); + foreach ($file_usage_list as $entity_type => $entity_ids) { + $entity_info = entity_get_info($entity_type); + // The usage table contains usage of every revision. If we are looking + // for every revision or the entity does not support revisions then + // every usage is already a match. + $match_entity_type = $age == FIELD_LOAD_REVISION || !isset($entity_info['entity keys']['revision']); + $entities = entity_load($entity_type, $entity_ids); + foreach ($entities as $entity) { + list($id, , $bundle) = entity_extract_ids($entity_type, $entity); + // We need to find file fields for this entity type and bundle. + if (!isset($file_fields[$entity_type][$bundle])) { + $file_fields[$entity_type][$bundle] = array(); + // This contains the possible field names. + $instances = field_info_instances($entity_type, $bundle); + foreach ($instances as $field_name => $instance) { + $current_field = field_info_field($field_name); + // If this is the first time this field type is seen, check + // whether it references files. + if (!isset($field_columns[$current_field['type']])) { + $field_columns[$current_field['type']] = file_field_find_file_reference_column($current_field); + } + // If the field type does reference files then record it. + if ($field_columns[$current_field['type']]) { + $file_fields[$entity_type][$bundle][$field_name] = $field_columns[$current_field['type']]; + } + } + } + foreach ($file_fields[$entity_type][$bundle] as $field_name => $field_column) { + $match = $match_entity_type; + // If we didn't match yet then iterate over the field items to find + // the referenced file. This will fail if the usage checked is in a + // non-current revision because field items are from the current + // revision. + if (!$match && ($field_items = field_get_items($entity_type, $entity, $field_name))) { + foreach ($field_items as $item) { + if ($file->fid == $item[$field_column]) { + $match = TRUE; + break; + } + } + } + if ($match) { + $references[$file->fid][$age][$field_name][$entity_type][$id] = $entity; + } + } + } } } - - return isset($field) ? $references[$field['field_name']] : array_filter($references); + $return = $references[$file->fid][$age]; + // Filter the static cache down to the requested entries. The usual static + // cache is very small so this will be very fast. + if ($field || $field_type) { + foreach ($return as $field_name => $data) { + $current_field = field_info_field($field_name); + if (($field_type && $current_field['type'] != $field_type) || ($field && $field['id'] != $current_field['id'])) { + unset($return[$field_name]); + } + } + } + return $return; } /**