cms/drupal/modules/file/file.module
changeset 541 e756a8c72c3d
child 570 cdf0cb7bf073
equal deleted inserted replaced
540:07239de796bb 541:e756a8c72c3d
       
     1 <?php
       
     2 
       
     3 /**
       
     4  * @file
       
     5  * Defines a "managed_file" Form API field and a "file" field for Field module.
       
     6  */
       
     7 
       
     8 // Load all Field module hooks for File.
       
     9 require_once DRUPAL_ROOT . '/modules/file/file.field.inc';
       
    10 
       
    11 /**
       
    12  * Implements hook_help().
       
    13  */
       
    14 function file_help($path, $arg) {
       
    15   switch ($path) {
       
    16     case 'admin/help#file':
       
    17       $output = '';
       
    18       $output .= '<h3>' . t('About') . '</h3>';
       
    19       $output .= '<p>' . t('The File module defines a <em>File</em> field type for the Field module, which lets you manage and validate uploaded files attached to content on your site (see the <a href="@field-help">Field module help page</a> for more information about fields). For more information, see the online handbook entry for <a href="@file">File module</a>.', array('@field-help' => url('admin/help/field'), '@file' => 'http://drupal.org/documentation/modules/file')) . '</p>';
       
    20       $output .= '<h3>' . t('Uses') . '</h3>';
       
    21       $output .= '<dl>';
       
    22       $output .= '<dt>' . t('Attaching files to content') . '</dt>';
       
    23       $output .= '<dd>' . t('The File module allows users to attach files to content (e.g., PDF files, spreadsheets, etc.), when a <em>File</em> field is added to a given content type using the <a href="@fieldui-help">Field UI module</a>. You can add validation options to your File field, such as specifying a maximum file size and allowed file extensions.', array('@fieldui-help' => url('admin/help/field_ui'))) . '</dd>';
       
    24       $output .= '<dt>' . t('Managing attachment display') . '</dt>';
       
    25       $output .= '<dd>' . t('When you attach a file to content, you can specify whether it is <em>listed</em> or not. Listed files are displayed automatically in a section at the bottom of your content; non-listed files are available for embedding in your content, but are not included in the list at the bottom.') . '</dd>';
       
    26       $output .= '<dt>' . t('Managing file locations') . '</dt>';
       
    27       $output .= '<dd>' . t("When you create a File field, you can specify a directory where the files will be stored, which can be within either the <em>public</em> or <em>private</em> files directory. Files in the public directory can be accessed directly through the web server; when public files are listed, direct links to the files are used, and anyone who knows a file's URL can download the file. Files in the private directory are not accessible directly through the web server; when private files are listed, the links are Drupal path requests. This adds to server load and download time, since Drupal must start up and resolve the path for each file download request, but allows for access restrictions.") . '</dd>';
       
    28       $output .= '</dl>';
       
    29       return $output;
       
    30   }
       
    31 }
       
    32 
       
    33 /**
       
    34  * Implements hook_menu().
       
    35  */
       
    36 function file_menu() {
       
    37   $items = array();
       
    38 
       
    39   $items['file/ajax'] = array(
       
    40     'page callback' => 'file_ajax_upload',
       
    41     'delivery callback' => 'ajax_deliver',
       
    42     'access arguments' => array('access content'),
       
    43     'theme callback' => 'ajax_base_page_theme',
       
    44     'type' => MENU_CALLBACK,
       
    45   );
       
    46   $items['file/progress'] = array(
       
    47     'page callback' => 'file_ajax_progress',
       
    48     'access arguments' => array('access content'),
       
    49     'theme callback' => 'ajax_base_page_theme',
       
    50     'type' => MENU_CALLBACK,
       
    51   );
       
    52 
       
    53   return $items;
       
    54 }
       
    55 
       
    56 /**
       
    57  * Implements hook_element_info().
       
    58  *
       
    59  * The managed file element may be used anywhere in Drupal.
       
    60  */
       
    61 function file_element_info() {
       
    62   $file_path = drupal_get_path('module', 'file');
       
    63   $types['managed_file'] = array(
       
    64     '#input' => TRUE,
       
    65     '#process' => array('file_managed_file_process'),
       
    66     '#value_callback' => 'file_managed_file_value',
       
    67     '#element_validate' => array('file_managed_file_validate'),
       
    68     '#pre_render' => array('file_managed_file_pre_render'),
       
    69     '#theme' => 'file_managed_file',
       
    70     '#theme_wrappers' => array('form_element'),
       
    71     '#progress_indicator' => 'throbber',
       
    72     '#progress_message' => NULL,
       
    73     '#upload_validators' => array(),
       
    74     '#upload_location' => NULL,
       
    75     '#size' => 22,
       
    76     '#extended' => FALSE,
       
    77     '#attached' => array(
       
    78       'css' => array($file_path . '/file.css'),
       
    79       'js' => array($file_path . '/file.js'),
       
    80     ),
       
    81   );
       
    82   return $types;
       
    83 }
       
    84 
       
    85 /**
       
    86  * Implements hook_theme().
       
    87  */
       
    88 function file_theme() {
       
    89   return array(
       
    90     // file.module.
       
    91     'file_link' => array(
       
    92       'variables' => array('file' => NULL, 'icon_directory' => NULL),
       
    93     ),
       
    94     'file_icon' => array(
       
    95       'variables' => array('file' => NULL, 'icon_directory' => NULL, 'alt' => ''),
       
    96     ),
       
    97     'file_managed_file' => array(
       
    98       'render element' => 'element',
       
    99     ),
       
   100 
       
   101     // file.field.inc.
       
   102     'file_widget' => array(
       
   103       'render element' => 'element',
       
   104     ),
       
   105     'file_widget_multiple' => array(
       
   106       'render element' => 'element',
       
   107     ),
       
   108     'file_formatter_table' => array(
       
   109       'variables' => array('items' => NULL),
       
   110     ),
       
   111     'file_upload_help' => array(
       
   112       'variables' => array('description' => NULL, 'upload_validators' => NULL),
       
   113     ),
       
   114   );
       
   115 }
       
   116 
       
   117 /**
       
   118  * Implements hook_file_download().
       
   119  *
       
   120  * This function takes an extra parameter $field_type so that it may
       
   121  * be re-used by other File-like modules, such as Image.
       
   122  */
       
   123 function file_file_download($uri, $field_type = 'file') {
       
   124   global $user;
       
   125 
       
   126   // Get the file record based on the URI. If not in the database just return.
       
   127   $files = file_load_multiple(array(), array('uri' => $uri));
       
   128   if (count($files)) {
       
   129     foreach ($files as $item) {
       
   130       // Since some database servers sometimes use a case-insensitive comparison
       
   131       // by default, double check that the filename is an exact match.
       
   132       if ($item->uri === $uri) {
       
   133         $file = $item;
       
   134         break;
       
   135       }
       
   136     }
       
   137   }
       
   138   if (!isset($file)) {
       
   139     return;
       
   140   }
       
   141 
       
   142   // Find out which (if any) fields of this type contain the file.
       
   143   $references = file_get_file_references($file, NULL, FIELD_LOAD_CURRENT, $field_type);
       
   144 
       
   145   // Stop processing if there are no references in order to avoid returning
       
   146   // headers for files controlled by other modules. Make an exception for
       
   147   // temporary files where the host entity has not yet been saved (for example,
       
   148   // an image preview on a node/add form) in which case, allow download by the
       
   149   // file's owner. For anonymous file owners, only the browser session that
       
   150   // uploaded the file should be granted access.
       
   151   if (empty($references) && ($file->status == FILE_STATUS_PERMANENT || $file->uid != $user->uid || (!$user->uid && empty($_SESSION['anonymous_allowed_file_ids'][$file->fid])))) {
       
   152       return;
       
   153   }
       
   154 
       
   155   // Default to allow access.
       
   156   $denied = FALSE;
       
   157   // Loop through all references of this file. If a reference explicitly allows
       
   158   // access to the field to which this file belongs, no further checks are done
       
   159   // and download access is granted. If a reference denies access, eventually
       
   160   // existing additional references are checked. If all references were checked
       
   161   // and no reference denied access, access is granted as well. If at least one
       
   162   // reference denied access, access is denied.
       
   163   foreach ($references as $field_name => $field_references) {
       
   164     foreach ($field_references as $entity_type => $type_references) {
       
   165       foreach ($type_references as $id => $reference) {
       
   166         // Try to load $entity and $field.
       
   167         $entity = entity_load($entity_type, array($id));
       
   168         $entity = reset($entity);
       
   169         $field = field_info_field($field_name);
       
   170 
       
   171         // Load the field item that references the file.
       
   172         $field_item = NULL;
       
   173         if ($entity) {
       
   174           // Load all field items for that entity.
       
   175           $field_items = field_get_items($entity_type, $entity, $field_name);
       
   176 
       
   177           // Find the field item with the matching URI.
       
   178           foreach ($field_items as $item) {
       
   179             if ($item['uri'] == $uri) {
       
   180               $field_item = $item;
       
   181               break;
       
   182             }
       
   183           }
       
   184         }
       
   185 
       
   186         // Check that $entity, $field and $field_item were loaded successfully
       
   187         // and check if access to that field is not disallowed. If any of these
       
   188         // checks fail, stop checking access for this reference.
       
   189         if (empty($entity) || empty($field) || empty($field_item) || !field_access('view', $field, $entity_type, $entity)) {
       
   190           $denied = TRUE;
       
   191           break;
       
   192         }
       
   193 
       
   194         // Invoke hook and collect grants/denies for download access.
       
   195         // Default to FALSE and let entities overrule this ruling.
       
   196         $grants = array('system' => FALSE);
       
   197         foreach (module_implements('file_download_access') as $module) {
       
   198           $grants = array_merge($grants, array($module => module_invoke($module, 'file_download_access', $field_item, $entity_type, $entity)));
       
   199         }
       
   200         // Allow other modules to alter the returned grants/denies.
       
   201         drupal_alter('file_download_access', $grants, $field_item, $entity_type, $entity);
       
   202 
       
   203         if (in_array(TRUE, $grants)) {
       
   204           // If TRUE is returned, access is granted and no further checks are
       
   205           // necessary.
       
   206           $denied = FALSE;
       
   207           break 3;
       
   208         }
       
   209 
       
   210         if (in_array(FALSE, $grants)) {
       
   211           // If an implementation returns FALSE, access to this entity is denied
       
   212           // but the file could belong to another entity to which the user might
       
   213           // have access. Continue with these.
       
   214           $denied = TRUE;
       
   215         }
       
   216       }
       
   217     }
       
   218   }
       
   219 
       
   220   // Access specifically denied.
       
   221   if ($denied) {
       
   222     return -1;
       
   223   }
       
   224 
       
   225   // Access is granted.
       
   226   $headers = file_get_content_headers($file);
       
   227   return $headers;
       
   228 }
       
   229 
       
   230 /**
       
   231  * Menu callback; Shared Ajax callback for file uploads and deletions.
       
   232  *
       
   233  * This rebuilds the form element for a particular field item. As long as the
       
   234  * form processing is properly encapsulated in the widget element the form
       
   235  * should rebuild correctly using FAPI without the need for additional callbacks
       
   236  * or processing.
       
   237  */
       
   238 function file_ajax_upload() {
       
   239   $form_parents = func_get_args();
       
   240   $form_build_id = (string) array_pop($form_parents);
       
   241 
       
   242   if (empty($_POST['form_build_id']) || $form_build_id != $_POST['form_build_id']) {
       
   243     // Invalid request.
       
   244     drupal_set_message(t('An unrecoverable error occurred. The uploaded file likely exceeded the maximum file size (@size) that this server supports.', array('@size' => format_size(file_upload_max_size()))), 'error');
       
   245     $commands = array();
       
   246     $commands[] = ajax_command_replace(NULL, theme('status_messages'));
       
   247     return array('#type' => 'ajax', '#commands' => $commands);
       
   248   }
       
   249 
       
   250   list($form, $form_state, $form_id, $form_build_id, $commands) = ajax_get_form();
       
   251 
       
   252   if (!$form) {
       
   253     // Invalid form_build_id.
       
   254     drupal_set_message(t('An unrecoverable error occurred. Use of this form has expired. Try reloading the page and submitting again.'), 'error');
       
   255     $commands = array();
       
   256     $commands[] = ajax_command_replace(NULL, theme('status_messages'));
       
   257     return array('#type' => 'ajax', '#commands' => $commands);
       
   258   }
       
   259 
       
   260   // Get the current element and count the number of files.
       
   261   $current_element = $form;
       
   262   foreach ($form_parents as $parent) {
       
   263     $current_element = $current_element[$parent];
       
   264   }
       
   265   $current_file_count = isset($current_element['#file_upload_delta']) ? $current_element['#file_upload_delta'] : 0;
       
   266 
       
   267   // Process user input. $form and $form_state are modified in the process.
       
   268   drupal_process_form($form['#form_id'], $form, $form_state);
       
   269 
       
   270   // Retrieve the element to be rendered.
       
   271   foreach ($form_parents as $parent) {
       
   272     $form = $form[$parent];
       
   273   }
       
   274 
       
   275   // Add the special Ajax class if a new file was added.
       
   276   if (isset($form['#file_upload_delta']) && $current_file_count < $form['#file_upload_delta']) {
       
   277     $form[$current_file_count]['#attributes']['class'][] = 'ajax-new-content';
       
   278   }
       
   279   // Otherwise just add the new content class on a placeholder.
       
   280   else {
       
   281     $form['#suffix'] .= '<span class="ajax-new-content"></span>';
       
   282   }
       
   283 
       
   284   $form['#prefix'] .= theme('status_messages');
       
   285   $output = drupal_render($form);
       
   286   $js = drupal_add_js();
       
   287   $settings = drupal_array_merge_deep_array($js['settings']['data']);
       
   288 
       
   289   $commands[] = ajax_command_replace(NULL, $output, $settings);
       
   290   return array('#type' => 'ajax', '#commands' => $commands);
       
   291 }
       
   292 
       
   293 /**
       
   294  * Menu callback for upload progress.
       
   295  *
       
   296  * @param $key
       
   297  *   The unique key for this upload process.
       
   298  */
       
   299 function file_ajax_progress($key) {
       
   300   $progress = array(
       
   301     'message' => t('Starting upload...'),
       
   302     'percentage' => -1,
       
   303   );
       
   304 
       
   305   $implementation = file_progress_implementation();
       
   306   if ($implementation == 'uploadprogress') {
       
   307     $status = uploadprogress_get_info($key);
       
   308     if (isset($status['bytes_uploaded']) && !empty($status['bytes_total'])) {
       
   309       $progress['message'] = t('Uploading... (@current of @total)', array('@current' => format_size($status['bytes_uploaded']), '@total' => format_size($status['bytes_total'])));
       
   310       $progress['percentage'] = round(100 * $status['bytes_uploaded'] / $status['bytes_total']);
       
   311     }
       
   312   }
       
   313   elseif ($implementation == 'apc') {
       
   314     $status = apc_fetch('upload_' . $key);
       
   315     if (isset($status['current']) && !empty($status['total'])) {
       
   316       $progress['message'] = t('Uploading... (@current of @total)', array('@current' => format_size($status['current']), '@total' => format_size($status['total'])));
       
   317       $progress['percentage'] = round(100 * $status['current'] / $status['total']);
       
   318     }
       
   319   }
       
   320 
       
   321   drupal_json_output($progress);
       
   322 }
       
   323 
       
   324 /**
       
   325  * Determines the preferred upload progress implementation.
       
   326  *
       
   327  * @return
       
   328  *   A string indicating which upload progress system is available. Either "apc"
       
   329  *   or "uploadprogress". If neither are available, returns FALSE.
       
   330  */
       
   331 function file_progress_implementation() {
       
   332   static $implementation;
       
   333   if (!isset($implementation)) {
       
   334     $implementation = FALSE;
       
   335 
       
   336     // We prefer the PECL extension uploadprogress because it supports multiple
       
   337     // simultaneous uploads. APC only supports one at a time.
       
   338     if (extension_loaded('uploadprogress')) {
       
   339       $implementation = 'uploadprogress';
       
   340     }
       
   341     elseif (extension_loaded('apc') && ini_get('apc.rfc1867')) {
       
   342       $implementation = 'apc';
       
   343     }
       
   344   }
       
   345   return $implementation;
       
   346 }
       
   347 
       
   348 /**
       
   349  * Implements hook_file_delete().
       
   350  */
       
   351 function file_file_delete($file) {
       
   352   // TODO: Remove references to a file that is in-use.
       
   353 }
       
   354 
       
   355 /**
       
   356  * Process function to expand the managed_file element type.
       
   357  *
       
   358  * Expands the file type to include Upload and Remove buttons, as well as
       
   359  * support for a default value.
       
   360  */
       
   361 function file_managed_file_process($element, &$form_state, $form) {
       
   362   // Append the '-upload' to the #id so the field label's 'for' attribute
       
   363   // corresponds with the file element.
       
   364   $original_id = $element['#id'];
       
   365   $element['#id'] .= '-upload';
       
   366   $fid = isset($element['#value']['fid']) ? $element['#value']['fid'] : 0;
       
   367 
       
   368   // Set some default element properties.
       
   369   $element['#progress_indicator'] = empty($element['#progress_indicator']) ? 'none' : $element['#progress_indicator'];
       
   370   $element['#file'] = $fid ? file_load($fid) : FALSE;
       
   371   $element['#tree'] = TRUE;
       
   372 
       
   373   $ajax_settings = array(
       
   374     'path' => 'file/ajax/' . implode('/', $element['#array_parents']) . '/' . $form['form_build_id']['#value'],
       
   375     'wrapper' => $original_id . '-ajax-wrapper',
       
   376     'effect' => 'fade',
       
   377     'progress' => array(
       
   378       'type' => $element['#progress_indicator'],
       
   379       'message' => $element['#progress_message'],
       
   380     ),
       
   381   );
       
   382 
       
   383   // Set up the buttons first since we need to check if they were clicked.
       
   384   $element['upload_button'] = array(
       
   385     '#name' => implode('_', $element['#parents']) . '_upload_button',
       
   386     '#type' => 'submit',
       
   387     '#value' => t('Upload'),
       
   388     '#validate' => array(),
       
   389     '#submit' => array('file_managed_file_submit'),
       
   390     '#limit_validation_errors' => array($element['#parents']),
       
   391     '#ajax' => $ajax_settings,
       
   392     '#weight' => -5,
       
   393   );
       
   394 
       
   395   // Force the progress indicator for the remove button to be either 'none' or
       
   396   // 'throbber', even if the upload button is using something else.
       
   397   $ajax_settings['progress']['type'] = ($element['#progress_indicator'] == 'none') ? 'none' : 'throbber';
       
   398   $ajax_settings['progress']['message'] = NULL;
       
   399   $ajax_settings['effect'] = 'none';
       
   400   $element['remove_button'] = array(
       
   401     '#name' => implode('_', $element['#parents']) . '_remove_button',
       
   402     '#type' => 'submit',
       
   403     '#value' => t('Remove'),
       
   404     '#validate' => array(),
       
   405     '#submit' => array('file_managed_file_submit'),
       
   406     '#limit_validation_errors' => array($element['#parents']),
       
   407     '#ajax' => $ajax_settings,
       
   408     '#weight' => -5,
       
   409   );
       
   410 
       
   411   $element['fid'] = array(
       
   412     '#type' => 'hidden',
       
   413     '#value' => $fid,
       
   414   );
       
   415 
       
   416   // Add progress bar support to the upload if possible.
       
   417   if ($element['#progress_indicator'] == 'bar' && $implementation = file_progress_implementation()) {
       
   418     $upload_progress_key = mt_rand();
       
   419 
       
   420     if ($implementation == 'uploadprogress') {
       
   421       $element['UPLOAD_IDENTIFIER'] = array(
       
   422         '#type' => 'hidden',
       
   423         '#value' => $upload_progress_key,
       
   424         '#attributes' => array('class' => array('file-progress')),
       
   425         // Uploadprogress extension requires this field to be at the top of the
       
   426         // form.
       
   427         '#weight' => -20,
       
   428       );
       
   429     }
       
   430     elseif ($implementation == 'apc') {
       
   431       $element['APC_UPLOAD_PROGRESS'] = array(
       
   432         '#type' => 'hidden',
       
   433         '#value' => $upload_progress_key,
       
   434         '#attributes' => array('class' => array('file-progress')),
       
   435         // Uploadprogress extension requires this field to be at the top of the
       
   436         // form.
       
   437         '#weight' => -20,
       
   438       );
       
   439     }
       
   440 
       
   441     // Add the upload progress callback.
       
   442     $element['upload_button']['#ajax']['progress']['path'] = 'file/progress/' . $upload_progress_key;
       
   443   }
       
   444 
       
   445   // The file upload field itself.
       
   446   $element['upload'] = array(
       
   447     '#name' => 'files[' . implode('_', $element['#parents']) . ']',
       
   448     '#type' => 'file',
       
   449     '#title' => t('Choose a file'),
       
   450     '#title_display' => 'invisible',
       
   451     '#size' => $element['#size'],
       
   452     '#theme_wrappers' => array(),
       
   453     '#weight' => -10,
       
   454   );
       
   455 
       
   456   if ($fid && $element['#file']) {
       
   457     $element['filename'] = array(
       
   458       '#type' => 'markup',
       
   459       '#markup' => theme('file_link', array('file' => $element['#file'])) . ' ',
       
   460       '#weight' => -10,
       
   461     );
       
   462     // Anonymous users who have uploaded a temporary file need a
       
   463     // non-session-based token added so file_managed_file_value() can check
       
   464     // that they have permission to use this file on subsequent submissions of
       
   465     // the same form (for example, after an Ajax upload or form validation
       
   466     // error).
       
   467     if (!$GLOBALS['user']->uid && $element['#file']->status != FILE_STATUS_PERMANENT) {
       
   468       $element['fid_token'] = array(
       
   469         '#type' => 'hidden',
       
   470         '#value' => drupal_hmac_base64('file-' . $fid, drupal_get_private_key() . drupal_get_hash_salt()),
       
   471       );
       
   472     }
       
   473   }
       
   474 
       
   475   // Add the extension list to the page as JavaScript settings.
       
   476   if (isset($element['#upload_validators']['file_validate_extensions'][0])) {
       
   477     $extension_list = implode(',', array_filter(explode(' ', $element['#upload_validators']['file_validate_extensions'][0])));
       
   478     $element['upload']['#attached']['js'] = array(
       
   479       array(
       
   480         'type' => 'setting',
       
   481         'data' => array('file' => array('elements' => array('#' . $element['#id'] => $extension_list)))
       
   482       )
       
   483     );
       
   484   }
       
   485 
       
   486   // Prefix and suffix used for Ajax replacement.
       
   487   $element['#prefix'] = '<div id="' . $original_id . '-ajax-wrapper">';
       
   488   $element['#suffix'] = '</div>';
       
   489 
       
   490   return $element;
       
   491 }
       
   492 
       
   493 /**
       
   494  * The #value_callback for a managed_file type element.
       
   495  */
       
   496 function file_managed_file_value(&$element, $input = FALSE, $form_state = NULL) {
       
   497   $fid = 0;
       
   498   $force_default = FALSE;
       
   499 
       
   500   // Find the current value of this field from the form state.
       
   501   $form_state_fid = $form_state['values'];
       
   502   foreach ($element['#parents'] as $parent) {
       
   503     $form_state_fid = isset($form_state_fid[$parent]) ? $form_state_fid[$parent] : 0;
       
   504   }
       
   505 
       
   506   if ($element['#extended'] && isset($form_state_fid['fid'])) {
       
   507     $fid = $form_state_fid['fid'];
       
   508   }
       
   509   elseif (is_numeric($form_state_fid)) {
       
   510     $fid = $form_state_fid;
       
   511   }
       
   512 
       
   513   // Process any input and save new uploads.
       
   514   if ($input !== FALSE) {
       
   515     $return = $input;
       
   516 
       
   517     // Uploads take priority over all other values.
       
   518     if ($file = file_managed_file_save_upload($element)) {
       
   519       $fid = $file->fid;
       
   520     }
       
   521     else {
       
   522       // Check for #filefield_value_callback values.
       
   523       // Because FAPI does not allow multiple #value_callback values like it
       
   524       // does for #element_validate and #process, this fills the missing
       
   525       // functionality to allow File fields to be extended through FAPI.
       
   526       if (isset($element['#file_value_callbacks'])) {
       
   527         foreach ($element['#file_value_callbacks'] as $callback) {
       
   528           $callback($element, $input, $form_state);
       
   529         }
       
   530       }
       
   531       // If a FID was submitted, load the file (and check access if it's not a
       
   532       // public file) to confirm it exists and that the current user has access
       
   533       // to it.
       
   534       if (isset($input['fid']) && ($file = file_load($input['fid']))) {
       
   535         // By default the public:// file scheme provided by Drupal core is the
       
   536         // only one that allows files to be publicly accessible to everyone, so
       
   537         // it is the only one for which the file access checks are bypassed.
       
   538         // Other modules which provide publicly accessible streams of their own
       
   539         // in hook_stream_wrappers() can add the corresponding scheme to the
       
   540         // 'file_public_schema' variable to bypass file access checks for those
       
   541         // as well. This should only be done for schemes that are completely
       
   542         // publicly accessible, with no download restrictions; for security
       
   543         // reasons all other schemes must go through the file_download_access()
       
   544         // check.
       
   545         if (!in_array(file_uri_scheme($file->uri), variable_get('file_public_schema', array('public'))) && !file_download_access($file->uri)) {
       
   546           $force_default = TRUE;
       
   547         }
       
   548         // Temporary files that belong to other users should never be allowed.
       
   549         elseif ($file->status != FILE_STATUS_PERMANENT) {
       
   550           if ($GLOBALS['user']->uid && $file->uid != $GLOBALS['user']->uid) {
       
   551             $force_default = TRUE;
       
   552           }
       
   553           // Since file ownership can't be determined for anonymous users, they
       
   554           // are not allowed to reuse temporary files at all. But they do need
       
   555           // to be able to reuse their own files from earlier submissions of
       
   556           // the same form, so to allow that, check for the token added by
       
   557           // file_managed_file_process().
       
   558           elseif (!$GLOBALS['user']->uid) {
       
   559             $token = drupal_array_get_nested_value($form_state['input'], array_merge($element['#parents'], array('fid_token')));
       
   560             if ($token !== drupal_hmac_base64('file-' . $file->fid, drupal_get_private_key() . drupal_get_hash_salt())) {
       
   561               $force_default = TRUE;
       
   562             }
       
   563           }
       
   564         }
       
   565         // If all checks pass, allow the file to be changed.
       
   566         if (!$force_default) {
       
   567           $fid = $file->fid;
       
   568         }
       
   569       }
       
   570     }
       
   571   }
       
   572 
       
   573   // If there is no input or if the default value was requested above, use the
       
   574   // default value.
       
   575   if ($input === FALSE || $force_default) {
       
   576     if ($element['#extended']) {
       
   577       $default_fid = isset($element['#default_value']['fid']) ? $element['#default_value']['fid'] : 0;
       
   578       $return = isset($element['#default_value']) ? $element['#default_value'] : array('fid' => 0);
       
   579     }
       
   580     else {
       
   581       $default_fid = isset($element['#default_value']) ? $element['#default_value'] : 0;
       
   582       $return = array('fid' => 0);
       
   583     }
       
   584 
       
   585     // Confirm that the file exists when used as a default value.
       
   586     if ($default_fid && $file = file_load($default_fid)) {
       
   587       $fid = $file->fid;
       
   588     }
       
   589   }
       
   590 
       
   591   $return['fid'] = $fid;
       
   592 
       
   593   return $return;
       
   594 }
       
   595 
       
   596 /**
       
   597  * An #element_validate callback for the managed_file element.
       
   598  */
       
   599 function file_managed_file_validate(&$element, &$form_state) {
       
   600   // If referencing an existing file, only allow if there are existing
       
   601   // references. This prevents unmanaged files from being deleted if this
       
   602   // item were to be deleted.
       
   603   $clicked_button = end($form_state['triggering_element']['#parents']);
       
   604   if ($clicked_button != 'remove_button' && !empty($element['fid']['#value'])) {
       
   605     if ($file = file_load($element['fid']['#value'])) {
       
   606       if ($file->status == FILE_STATUS_PERMANENT) {
       
   607         $references = file_usage_list($file);
       
   608         if (empty($references)) {
       
   609           form_error($element, t('The file used in the !name field may not be referenced.', array('!name' => $element['#title'])));
       
   610         }
       
   611       }
       
   612     }
       
   613     else {
       
   614       form_error($element, t('The file referenced by the !name field does not exist.', array('!name' => $element['#title'])));
       
   615     }
       
   616   }
       
   617 
       
   618   // Check required property based on the FID.
       
   619   if ($element['#required'] && empty($element['fid']['#value']) && !in_array($clicked_button, array('upload_button', 'remove_button'))) {
       
   620     form_error($element['upload'], t('!name field is required.', array('!name' => $element['#title'])));
       
   621   }
       
   622 
       
   623   // Consolidate the array value of this field to a single FID.
       
   624   if (!$element['#extended']) {
       
   625     form_set_value($element, $element['fid']['#value'], $form_state);
       
   626   }
       
   627 }
       
   628 
       
   629 /**
       
   630  * Form submission handler for upload / remove buttons of managed_file elements.
       
   631  *
       
   632  * @see file_managed_file_process()
       
   633  */
       
   634 function file_managed_file_submit($form, &$form_state) {
       
   635   // Determine whether it was the upload or the remove button that was clicked,
       
   636   // and set $element to the managed_file element that contains that button.
       
   637   $parents = $form_state['triggering_element']['#array_parents'];
       
   638   $button_key = array_pop($parents);
       
   639   $element = drupal_array_get_nested_value($form, $parents);
       
   640 
       
   641   // No action is needed here for the upload button, because all file uploads on
       
   642   // the form are processed by file_managed_file_value() regardless of which
       
   643   // button was clicked. Action is needed here for the remove button, because we
       
   644   // only remove a file in response to its remove button being clicked.
       
   645   if ($button_key == 'remove_button') {
       
   646     // If it's a temporary file we can safely remove it immediately, otherwise
       
   647     // it's up to the implementing module to clean up files that are in use.
       
   648     if ($element['#file'] && $element['#file']->status == 0) {
       
   649       file_delete($element['#file']);
       
   650     }
       
   651     // Update both $form_state['values'] and $form_state['input'] to reflect
       
   652     // that the file has been removed, so that the form is rebuilt correctly.
       
   653     // $form_state['values'] must be updated in case additional submit handlers
       
   654     // run, and for form building functions that run during the rebuild, such as
       
   655     // when the managed_file element is part of a field widget.
       
   656     // $form_state['input'] must be updated so that file_managed_file_value()
       
   657     // has correct information during the rebuild.
       
   658     $values_element = $element['#extended'] ? $element['fid'] : $element;
       
   659     form_set_value($values_element, NULL, $form_state);
       
   660     drupal_array_set_nested_value($form_state['input'], $values_element['#parents'], NULL);
       
   661   }
       
   662 
       
   663   // Set the form to rebuild so that $form is correctly updated in response to
       
   664   // processing the file removal. Since this function did not change $form_state
       
   665   // if the upload button was clicked, a rebuild isn't necessary in that
       
   666   // situation and setting $form_state['redirect'] to FALSE would suffice.
       
   667   // However, we choose to always rebuild, to keep the form processing workflow
       
   668   // consistent between the two buttons.
       
   669   $form_state['rebuild'] = TRUE;
       
   670 }
       
   671 
       
   672 /**
       
   673  * Saves any files that have been uploaded into a managed_file element.
       
   674  *
       
   675  * @param $element
       
   676  *   The FAPI element whose values are being saved.
       
   677  *
       
   678  * @return
       
   679  *   The file object representing the file that was saved, or FALSE if no file
       
   680  *   was saved.
       
   681  */
       
   682 function file_managed_file_save_upload($element) {
       
   683   $upload_name = implode('_', $element['#parents']);
       
   684   if (empty($_FILES['files']['name'][$upload_name])) {
       
   685     return FALSE;
       
   686   }
       
   687 
       
   688   $destination = isset($element['#upload_location']) ? $element['#upload_location'] : NULL;
       
   689   if (isset($destination) && !file_prepare_directory($destination, FILE_CREATE_DIRECTORY)) {
       
   690     watchdog('file', 'The upload directory %directory for the file field !name could not be created or is not accessible. A newly uploaded file could not be saved in this directory as a consequence, and the upload was canceled.', array('%directory' => $destination, '!name' => $element['#field_name']));
       
   691     form_set_error($upload_name, t('The file could not be uploaded.'));
       
   692     return FALSE;
       
   693   }
       
   694 
       
   695   if (!$file = file_save_upload($upload_name, $element['#upload_validators'], $destination)) {
       
   696     watchdog('file', 'The file upload failed. %upload', array('%upload' => $upload_name));
       
   697     form_set_error($upload_name, t('The file in the !name field was unable to be uploaded.', array('!name' => $element['#title'])));
       
   698     return FALSE;
       
   699   }
       
   700 
       
   701   return $file;
       
   702 }
       
   703 
       
   704 /**
       
   705  * Returns HTML for a managed file element.
       
   706  *
       
   707  * @param $variables
       
   708  *   An associative array containing:
       
   709  *   - element: A render element representing the file.
       
   710  *
       
   711  * @ingroup themeable
       
   712  */
       
   713 function theme_file_managed_file($variables) {
       
   714   $element = $variables['element'];
       
   715 
       
   716   $attributes = array();
       
   717   if (isset($element['#id'])) {
       
   718     $attributes['id'] = $element['#id'];
       
   719   }
       
   720   if (!empty($element['#attributes']['class'])) {
       
   721     $attributes['class'] = (array) $element['#attributes']['class'];
       
   722   }
       
   723   $attributes['class'][] = 'form-managed-file';
       
   724 
       
   725   // This wrapper is required to apply JS behaviors and CSS styling.
       
   726   $output = '';
       
   727   $output .= '<div' . drupal_attributes($attributes) . '>';
       
   728   $output .= drupal_render_children($element);
       
   729   $output .= '</div>';
       
   730   return $output;
       
   731 }
       
   732 
       
   733 /**
       
   734  * #pre_render callback to hide display of the upload or remove controls.
       
   735  *
       
   736  * Upload controls are hidden when a file is already uploaded. Remove controls
       
   737  * are hidden when there is no file attached. Controls are hidden here instead
       
   738  * of in file_managed_file_process(), because #access for these buttons depends
       
   739  * on the managed_file element's #value. See the documentation of form_builder()
       
   740  * for more detailed information about the relationship between #process,
       
   741  * #value, and #access.
       
   742  *
       
   743  * Because #access is set here, it affects display only and does not prevent
       
   744  * JavaScript or other untrusted code from submitting the form as though access
       
   745  * were enabled. The form processing functions for these elements should not
       
   746  * assume that the buttons can't be "clicked" just because they are not
       
   747  * displayed.
       
   748  *
       
   749  * @see file_managed_file_process()
       
   750  * @see form_builder()
       
   751  */
       
   752 function file_managed_file_pre_render($element) {
       
   753   // If we already have a file, we don't want to show the upload controls.
       
   754   if (!empty($element['#value']['fid'])) {
       
   755     $element['upload']['#access'] = FALSE;
       
   756     $element['upload_button']['#access'] = FALSE;
       
   757   }
       
   758   // If we don't already have a file, there is nothing to remove.
       
   759   else {
       
   760     $element['remove_button']['#access'] = FALSE;
       
   761   }
       
   762   return $element;
       
   763 }
       
   764 
       
   765 /**
       
   766  * Returns HTML for a link to a file.
       
   767  *
       
   768  * @param $variables
       
   769  *   An associative array containing:
       
   770  *   - file: A file object to which the link will be created.
       
   771  *   - icon_directory: (optional) A path to a directory of icons to be used for
       
   772  *     files. Defaults to the value of the "file_icon_directory" variable.
       
   773  *
       
   774  * @ingroup themeable
       
   775  */
       
   776 function theme_file_link($variables) {
       
   777   $file = $variables['file'];
       
   778   $icon_directory = $variables['icon_directory'];
       
   779 
       
   780   $url = file_create_url($file->uri);
       
   781 
       
   782   // Human-readable names, for use as text-alternatives to icons.
       
   783   $mime_name = array(
       
   784     'application/msword' => t('Microsoft Office document icon'),
       
   785     'application/vnd.ms-excel' => t('Office spreadsheet icon'),
       
   786     'application/vnd.ms-powerpoint' => t('Office presentation icon'),
       
   787     'application/pdf' => t('PDF icon'),
       
   788     'video/quicktime' => t('Movie icon'),
       
   789     'audio/mpeg' => t('Audio icon'),
       
   790     'audio/wav' => t('Audio icon'),
       
   791     'image/jpeg' => t('Image icon'),
       
   792     'image/png' => t('Image icon'),
       
   793     'image/gif' => t('Image icon'),
       
   794     'application/zip' => t('Package icon'),
       
   795     'text/html' => t('HTML icon'),
       
   796     'text/plain' => t('Plain text icon'),
       
   797     'application/octet-stream' => t('Binary Data'),
       
   798   );
       
   799 
       
   800   $mimetype = file_get_mimetype($file->uri);
       
   801 
       
   802   $icon = theme('file_icon', array(
       
   803     'file' => $file,
       
   804     'icon_directory' => $icon_directory,
       
   805     'alt' => !empty($mime_name[$mimetype]) ? $mime_name[$mimetype] : t('File'),
       
   806   ));
       
   807 
       
   808   // Set options as per anchor format described at
       
   809   // http://microformats.org/wiki/file-format-examples
       
   810   $options = array(
       
   811     'attributes' => array(
       
   812       'type' => $file->filemime . '; length=' . $file->filesize,
       
   813     ),
       
   814   );
       
   815 
       
   816   // Use the description as the link text if available.
       
   817   if (empty($file->description)) {
       
   818     $link_text = $file->filename;
       
   819   }
       
   820   else {
       
   821     $link_text = $file->description;
       
   822     $options['attributes']['title'] = check_plain($file->filename);
       
   823   }
       
   824 
       
   825   return '<span class="file">' . $icon . ' ' . l($link_text, $url, $options) . '</span>';
       
   826 }
       
   827 
       
   828 /**
       
   829  * Returns HTML for an image with an appropriate icon for the given file.
       
   830  *
       
   831  * @param $variables
       
   832  *   An associative array containing:
       
   833  *   - file: A file object for which to make an icon.
       
   834  *   - icon_directory: (optional) A path to a directory of icons to be used for
       
   835  *     files. Defaults to the value of the "file_icon_directory" variable.
       
   836  *   - alt: (optional) The alternative text to represent the icon in text-based
       
   837  *     browsers. Defaults to an empty string.
       
   838  *
       
   839  * @ingroup themeable
       
   840  */
       
   841 function theme_file_icon($variables) {
       
   842   $file = $variables['file'];
       
   843   $alt = $variables['alt'];
       
   844   $icon_directory = $variables['icon_directory'];
       
   845 
       
   846   $mime = check_plain($file->filemime);
       
   847   $icon_url = file_icon_url($file, $icon_directory);
       
   848   return '<img class="file-icon" alt="' . check_plain($alt) . '" title="' . $mime . '" src="' . $icon_url . '" />';
       
   849 }
       
   850 
       
   851 /**
       
   852  * Creates a URL to the icon for a file object.
       
   853  *
       
   854  * @param $file
       
   855  *   A file object.
       
   856  * @param $icon_directory
       
   857  *   (optional) A path to a directory of icons to be used for files. Defaults to
       
   858  *   the value of the "file_icon_directory" variable.
       
   859  *
       
   860  * @return
       
   861  *   A URL string to the icon, or FALSE if an appropriate icon cannot be found.
       
   862  */
       
   863 function file_icon_url($file, $icon_directory = NULL) {
       
   864   if ($icon_path = file_icon_path($file, $icon_directory)) {
       
   865     return base_path() . $icon_path;
       
   866   }
       
   867   return FALSE;
       
   868 }
       
   869 
       
   870 /**
       
   871  * Creates a path to the icon for a file object.
       
   872  *
       
   873  * @param $file
       
   874  *   A file object.
       
   875  * @param $icon_directory
       
   876  *   (optional) A path to a directory of icons to be used for files. Defaults to
       
   877  *   the value of the "file_icon_directory" variable.
       
   878  *
       
   879  * @return
       
   880  *   A string to the icon as a local path, or FALSE if an appropriate icon could
       
   881  *   not be found.
       
   882  */
       
   883 function file_icon_path($file, $icon_directory = NULL) {
       
   884   // Use the default set of icons if none specified.
       
   885   if (!isset($icon_directory)) {
       
   886     $icon_directory = variable_get('file_icon_directory', drupal_get_path('module', 'file') . '/icons');
       
   887   }
       
   888 
       
   889   // If there's an icon matching the exact mimetype, go for it.
       
   890   $dashed_mime = strtr($file->filemime, array('/' => '-'));
       
   891   $icon_path = $icon_directory . '/' . $dashed_mime . '.png';
       
   892   if (file_exists($icon_path)) {
       
   893     return $icon_path;
       
   894   }
       
   895 
       
   896   // For a few mimetypes, we can "manually" map to a generic icon.
       
   897   $generic_mime = (string) file_icon_map($file);
       
   898   $icon_path = $icon_directory . '/' . $generic_mime . '.png';
       
   899   if ($generic_mime && file_exists($icon_path)) {
       
   900     return $icon_path;
       
   901   }
       
   902 
       
   903   // Use generic icons for each category that provides such icons.
       
   904   foreach (array('audio', 'image', 'text', 'video') as $category) {
       
   905     if (strpos($file->filemime, $category . '/') === 0) {
       
   906       $icon_path = $icon_directory . '/' . $category . '-x-generic.png';
       
   907       if (file_exists($icon_path)) {
       
   908         return $icon_path;
       
   909       }
       
   910     }
       
   911   }
       
   912 
       
   913   // Try application-octet-stream as last fallback.
       
   914   $icon_path = $icon_directory . '/application-octet-stream.png';
       
   915   if (file_exists($icon_path)) {
       
   916     return $icon_path;
       
   917   }
       
   918 
       
   919   // No icon can be found.
       
   920   return FALSE;
       
   921 }
       
   922 
       
   923 /**
       
   924  * Determines the generic icon MIME package based on a file's MIME type.
       
   925  *
       
   926  * @param $file
       
   927  *   A file object.
       
   928  *
       
   929  * @return
       
   930  *   The generic icon MIME package expected for this file.
       
   931  */
       
   932 function file_icon_map($file) {
       
   933   switch ($file->filemime) {
       
   934     // Word document types.
       
   935     case 'application/msword':
       
   936     case 'application/vnd.ms-word.document.macroEnabled.12':
       
   937     case 'application/vnd.oasis.opendocument.text':
       
   938     case 'application/vnd.oasis.opendocument.text-template':
       
   939     case 'application/vnd.oasis.opendocument.text-master':
       
   940     case 'application/vnd.oasis.opendocument.text-web':
       
   941     case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
       
   942     case 'application/vnd.stardivision.writer':
       
   943     case 'application/vnd.sun.xml.writer':
       
   944     case 'application/vnd.sun.xml.writer.template':
       
   945     case 'application/vnd.sun.xml.writer.global':
       
   946     case 'application/vnd.wordperfect':
       
   947     case 'application/x-abiword':
       
   948     case 'application/x-applix-word':
       
   949     case 'application/x-kword':
       
   950     case 'application/x-kword-crypt':
       
   951       return 'x-office-document';
       
   952 
       
   953     // Spreadsheet document types.
       
   954     case 'application/vnd.ms-excel':
       
   955     case 'application/vnd.ms-excel.sheet.macroEnabled.12':
       
   956     case 'application/vnd.oasis.opendocument.spreadsheet':
       
   957     case 'application/vnd.oasis.opendocument.spreadsheet-template':
       
   958     case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
       
   959     case 'application/vnd.stardivision.calc':
       
   960     case 'application/vnd.sun.xml.calc':
       
   961     case 'application/vnd.sun.xml.calc.template':
       
   962     case 'application/vnd.lotus-1-2-3':
       
   963     case 'application/x-applix-spreadsheet':
       
   964     case 'application/x-gnumeric':
       
   965     case 'application/x-kspread':
       
   966     case 'application/x-kspread-crypt':
       
   967       return 'x-office-spreadsheet';
       
   968 
       
   969     // Presentation document types.
       
   970     case 'application/vnd.ms-powerpoint':
       
   971     case 'application/vnd.ms-powerpoint.presentation.macroEnabled.12':
       
   972     case 'application/vnd.oasis.opendocument.presentation':
       
   973     case 'application/vnd.oasis.opendocument.presentation-template':
       
   974     case 'application/vnd.openxmlformats-officedocument.presentationml.presentation':
       
   975     case 'application/vnd.stardivision.impress':
       
   976     case 'application/vnd.sun.xml.impress':
       
   977     case 'application/vnd.sun.xml.impress.template':
       
   978     case 'application/x-kpresenter':
       
   979       return 'x-office-presentation';
       
   980 
       
   981     // Compressed archive types.
       
   982     case 'application/zip':
       
   983     case 'application/x-zip':
       
   984     case 'application/stuffit':
       
   985     case 'application/x-stuffit':
       
   986     case 'application/x-7z-compressed':
       
   987     case 'application/x-ace':
       
   988     case 'application/x-arj':
       
   989     case 'application/x-bzip':
       
   990     case 'application/x-bzip-compressed-tar':
       
   991     case 'application/x-compress':
       
   992     case 'application/x-compressed-tar':
       
   993     case 'application/x-cpio-compressed':
       
   994     case 'application/x-deb':
       
   995     case 'application/x-gzip':
       
   996     case 'application/x-java-archive':
       
   997     case 'application/x-lha':
       
   998     case 'application/x-lhz':
       
   999     case 'application/x-lzop':
       
  1000     case 'application/x-rar':
       
  1001     case 'application/x-rpm':
       
  1002     case 'application/x-tzo':
       
  1003     case 'application/x-tar':
       
  1004     case 'application/x-tarz':
       
  1005     case 'application/x-tgz':
       
  1006       return 'package-x-generic';
       
  1007 
       
  1008     // Script file types.
       
  1009     case 'application/ecmascript':
       
  1010     case 'application/javascript':
       
  1011     case 'application/mathematica':
       
  1012     case 'application/vnd.mozilla.xul+xml':
       
  1013     case 'application/x-asp':
       
  1014     case 'application/x-awk':
       
  1015     case 'application/x-cgi':
       
  1016     case 'application/x-csh':
       
  1017     case 'application/x-m4':
       
  1018     case 'application/x-perl':
       
  1019     case 'application/x-php':
       
  1020     case 'application/x-ruby':
       
  1021     case 'application/x-shellscript':
       
  1022     case 'text/vnd.wap.wmlscript':
       
  1023     case 'text/x-emacs-lisp':
       
  1024     case 'text/x-haskell':
       
  1025     case 'text/x-literate-haskell':
       
  1026     case 'text/x-lua':
       
  1027     case 'text/x-makefile':
       
  1028     case 'text/x-matlab':
       
  1029     case 'text/x-python':
       
  1030     case 'text/x-sql':
       
  1031     case 'text/x-tcl':
       
  1032       return 'text-x-script';
       
  1033 
       
  1034     // HTML aliases.
       
  1035     case 'application/xhtml+xml':
       
  1036       return 'text-html';
       
  1037 
       
  1038     // Executable types.
       
  1039     case 'application/x-macbinary':
       
  1040     case 'application/x-ms-dos-executable':
       
  1041     case 'application/x-pef-executable':
       
  1042       return 'application-x-executable';
       
  1043 
       
  1044     default:
       
  1045       return FALSE;
       
  1046   }
       
  1047 }
       
  1048 
       
  1049 /**
       
  1050  * @defgroup file-module-api File module public API functions
       
  1051  * @{
       
  1052  * These functions may be used to determine if and where a file is in use.
       
  1053  */
       
  1054 
       
  1055 /**
       
  1056  * Retrieves a list of references to a file.
       
  1057  *
       
  1058  * @param $file
       
  1059  *   A file object.
       
  1060  * @param $field
       
  1061  *   (optional) A field array to be used for this check. If given, limits the
       
  1062  *   reference check to the given field.
       
  1063  * @param $age
       
  1064  *   (optional) A constant that specifies which references to count. Use
       
  1065  *   FIELD_LOAD_REVISION to retrieve all references within all revisions or
       
  1066  *   FIELD_LOAD_CURRENT to retrieve references only in the current revisions.
       
  1067  * @param $field_type
       
  1068  *   (optional) The name of a field type. If given, limits the reference check
       
  1069  *   to fields of the given type.
       
  1070  *
       
  1071  * @return
       
  1072  *   An integer value.
       
  1073  */
       
  1074 function file_get_file_references($file, $field = NULL, $age = FIELD_LOAD_REVISION, $field_type = 'file') {
       
  1075   $references = drupal_static(__FUNCTION__, array());
       
  1076   $fields = isset($field) ? array($field['field_name'] => $field) : field_info_fields();
       
  1077 
       
  1078   foreach ($fields as $field_name => $file_field) {
       
  1079     if ((empty($field_type) || $file_field['type'] == $field_type) && !isset($references[$field_name])) {
       
  1080       // Get each time this file is used within a field.
       
  1081       $query = new EntityFieldQuery();
       
  1082       $query
       
  1083         ->fieldCondition($file_field, 'fid', $file->fid)
       
  1084         ->age($age);
       
  1085       $references[$field_name] = $query->execute();
       
  1086     }
       
  1087   }
       
  1088 
       
  1089   return isset($field) ? $references[$field['field_name']] : array_filter($references);
       
  1090 }
       
  1091 
       
  1092 /**
       
  1093  * @} End of "defgroup file-module-api".
       
  1094  */