947 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			Plaintext
		
	
	
			
		
		
	
	
			947 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			Plaintext
		
	
	
<?php
 | 
						|
// $Id$
 | 
						|
 | 
						|
/**
 | 
						|
 * @file
 | 
						|
 * Defines a "managed_file" Form API field and a "file" field for Field module.
 | 
						|
 */
 | 
						|
 | 
						|
// Load all Field module hooks for File.
 | 
						|
module_load_include('inc', 'file', 'file.field');
 | 
						|
 | 
						|
/**
 | 
						|
 * Implement hook_menu().
 | 
						|
 */
 | 
						|
function file_menu() {
 | 
						|
  $items = array();
 | 
						|
 | 
						|
  $items['file/ajax'] = array(
 | 
						|
    'page callback' => 'file_ajax_upload',
 | 
						|
    'delivery callback' => 'ajax_deliver',
 | 
						|
    'access arguments' => array('access content'),
 | 
						|
    'type' => MENU_CALLBACK,
 | 
						|
  );
 | 
						|
  $items['file/progress'] = array(
 | 
						|
    'page callback' => 'file_ajax_progress',
 | 
						|
    'delivery callback' => 'ajax_deliver',
 | 
						|
    'access arguments' => array('access content'),
 | 
						|
    'type' => MENU_CALLBACK,
 | 
						|
  );
 | 
						|
 | 
						|
  return $items;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Implement hook_element_info().
 | 
						|
 *
 | 
						|
 * The managed file element may be used independently anywhere in Drupal.
 | 
						|
 */
 | 
						|
function file_element_info() {
 | 
						|
  $file_path = drupal_get_path('module', 'file');
 | 
						|
  $types['managed_file'] = array(
 | 
						|
    '#input' => TRUE,
 | 
						|
    '#process' => array('file_managed_file_process'),
 | 
						|
    '#value_callback' => 'file_managed_file_value',
 | 
						|
    '#element_validate' => array('file_managed_file_validate'),
 | 
						|
    '#theme' => 'file_managed_file',
 | 
						|
    '#theme_wrappers' => array('form_element'),
 | 
						|
    '#progress_indicator' => 'throbber',
 | 
						|
    '#progress_message' => NULL,
 | 
						|
    '#upload_validators' => array(),
 | 
						|
    '#upload_location' => NULL,
 | 
						|
    '#extended' => FALSE,
 | 
						|
    '#attached' => array(
 | 
						|
      'css' => array($file_path . '/file.css'),
 | 
						|
      'js' => array($file_path . '/file.js'),
 | 
						|
    ),
 | 
						|
  );
 | 
						|
  return $types;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Implement hook_theme().
 | 
						|
 */
 | 
						|
function file_theme() {
 | 
						|
  return array(
 | 
						|
    // file.module.
 | 
						|
    'file_link' => array(
 | 
						|
      'variables' => array('file' => NULL),
 | 
						|
    ),
 | 
						|
    'file_icon' => array(
 | 
						|
      'variables' => array('file' => NULL),
 | 
						|
    ),
 | 
						|
    'file_managed_file' => array(
 | 
						|
      'render element' => 'element',
 | 
						|
    ),
 | 
						|
 | 
						|
    // file.field.inc.
 | 
						|
    'file_widget' => array(
 | 
						|
      'render element' => 'element',
 | 
						|
    ),
 | 
						|
    'file_widget_multiple' => array(
 | 
						|
      'render element' => 'element',
 | 
						|
    ),
 | 
						|
    'file_upload_help' => array(
 | 
						|
      'variables' => array('description' => NULL, 'upload_validators' => NULL),
 | 
						|
    ),
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Implement hook_file_download().
 | 
						|
 *
 | 
						|
 * This function takes an extra parameter $field_type so that it may
 | 
						|
 * be re-used by other File-like modules, such as Image.
 | 
						|
 */
 | 
						|
function file_file_download($uri, $field_type = 'file') {
 | 
						|
  global $user;
 | 
						|
 | 
						|
  // Get the file record based on the URI. If not in the database just return.
 | 
						|
  $files = file_load_multiple(array(), array('uri' => $uri));
 | 
						|
  if (count($files)) {
 | 
						|
    $file = reset($files);
 | 
						|
  }
 | 
						|
  else {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  // Find out which (if any) file fields contain this file.
 | 
						|
  $references = file_get_file_references($file, NULL, FIELD_LOAD_REVISION, $field_type);
 | 
						|
 | 
						|
  // TODO: Check field-level access if available here.
 | 
						|
 | 
						|
  $denied = $file->status ? NULL : FALSE;
 | 
						|
  // Check access to content containing the file fields. If access is allowed
 | 
						|
  // to any of this content, allow the download.
 | 
						|
  foreach ($references as $field_name => $field_references) {
 | 
						|
    foreach ($field_references as $obj_type => $type_references) {
 | 
						|
      foreach ($type_references as $reference) {
 | 
						|
        // If access is allowed to any object, immediately stop and grant
 | 
						|
        // access. If access is denied, continue through in case another object
 | 
						|
        // grants access.
 | 
						|
        // TODO: Switch this to a universal access check mechanism if available.
 | 
						|
        if ($obj_type == 'node' && ($node = node_load($reference->nid))) {
 | 
						|
          if (node_access('view', $node)) {
 | 
						|
            $denied = FALSE;
 | 
						|
            break 3;
 | 
						|
          }
 | 
						|
          else {
 | 
						|
            $denied = TRUE;
 | 
						|
          }
 | 
						|
        }
 | 
						|
        if ($obj_type == 'user') {
 | 
						|
          if (user_access('access user profiles') || $user->uid == $reference->uid) {
 | 
						|
            $denied = FALSE;
 | 
						|
            break 3;
 | 
						|
          }
 | 
						|
          else {
 | 
						|
            $denied = TRUE;
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // No access was denied or granted.
 | 
						|
  if (!isset($denied)) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  // Access specifically denied and not granted elsewhere.
 | 
						|
  elseif ($denied == TRUE) {
 | 
						|
    return -1;
 | 
						|
  }
 | 
						|
 | 
						|
  // Access is granted.
 | 
						|
  $name = mime_header_encode($file->filename);
 | 
						|
  $type = mime_header_encode($file->filemime);
 | 
						|
  // Serve images, text, and flash content for display rather than download.
 | 
						|
  $inline_types = variable_get('file_inline_types', array('^text/', '^image/', 'flash$'));
 | 
						|
  $disposition = 'attachment';
 | 
						|
  foreach ($inline_types as $inline_type) {
 | 
						|
    // Exclamation marks are used as delimiters to avoid escaping slashes.
 | 
						|
    if (preg_match('!' . $inline_type . '!', $file->filemime)) {
 | 
						|
      $disposition = 'inline';
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return array(
 | 
						|
    'Content-Type' => $type . '; name="' . $name . '"',
 | 
						|
    'Content-Length' => $file->filesize,
 | 
						|
    'Content-Disposition' => $disposition . '; filename="' . $name . '"',
 | 
						|
    'Cache-Control' => 'private',
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Menu callback; Shared AJAX callback for file uploads and deletions.
 | 
						|
 *
 | 
						|
 * This rebuilds the form element for a particular field item. As long as the
 | 
						|
 * form processing is properly encapsulated in the widget element the form
 | 
						|
 * should rebuild correctly using FAPI without the need for additional callbacks
 | 
						|
 * or processing.
 | 
						|
 */
 | 
						|
function file_ajax_upload() {
 | 
						|
  $form_parents = func_get_args();
 | 
						|
  $form_build_id = (string) array_pop($form_parents);
 | 
						|
 | 
						|
  if (empty($_POST['form_build_id']) || $form_build_id != $_POST['form_build_id']) {
 | 
						|
    // Invalid request.
 | 
						|
    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');
 | 
						|
    $commands = array();
 | 
						|
    $commands[] = ajax_command_replace(NULL, theme('status_messages'));
 | 
						|
    ajax_render($commands, FALSE);
 | 
						|
  }
 | 
						|
 | 
						|
  list($form, $form_state, $form_id, $form_build_id) = ajax_get_form();
 | 
						|
 | 
						|
  if (!$form) {
 | 
						|
    // Invalid form_build_id.
 | 
						|
    drupal_set_message(t('An unrecoverable error occurred. Use of this form has expired. Try reloading the page and submitting again.'), 'error');
 | 
						|
    $commands = array();
 | 
						|
    $commands[] = ajax_command_replace(NULL, theme('status_messages'));
 | 
						|
    ajax_render($commands, FALSE);
 | 
						|
  }
 | 
						|
 | 
						|
  // Get the current element and count the number of files.
 | 
						|
  $current_element = $form;
 | 
						|
  foreach ($form_parents as $parent) {
 | 
						|
    $current_element = $current_element[$parent];
 | 
						|
  }
 | 
						|
  $current_file_count = isset($current_element['#file_upload_delta']) ? $current_element['#file_upload_delta'] : 0;
 | 
						|
 | 
						|
  // Build, validate and if possible, submit the form.
 | 
						|
  drupal_process_form($form_id, $form, $form_state);
 | 
						|
 | 
						|
  // This call recreates the form relying solely on the form_state that the
 | 
						|
  // drupal_process_form() set up.
 | 
						|
  $form = drupal_rebuild_form($form_id, $form_state, $form_build_id);
 | 
						|
 | 
						|
  // Retrieve the element to be rendered.
 | 
						|
  foreach ($form_parents as $parent) {
 | 
						|
    $form = $form[$parent];
 | 
						|
  }
 | 
						|
 | 
						|
  // Add the special AJAX class if a new file was added.
 | 
						|
  if (isset($form['#file_upload_delta']) && $current_file_count < $form['#file_upload_delta']) {
 | 
						|
    $form[$current_file_count]['#attributes']['class'][] = 'ajax-new-content';
 | 
						|
  }
 | 
						|
  // Otherwise just add the new content class on a placeholder.
 | 
						|
  else {
 | 
						|
    $form['#suffix'] .= '<span class="ajax-new-content"></span>';
 | 
						|
  }
 | 
						|
 | 
						|
  $output = theme('status_messages') . drupal_render($form);
 | 
						|
  $js = drupal_add_js();
 | 
						|
  $settings = call_user_func_array('array_merge_recursive', $js['settings']['data']);
 | 
						|
 | 
						|
  $commands = array();
 | 
						|
  $commands[] = ajax_command_replace(NULL, $output, $settings);
 | 
						|
  ajax_render($commands, FALSE);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Menu callback for upload progress.
 | 
						|
 *
 | 
						|
 * @param $key
 | 
						|
 *   The unique key for this upload process.
 | 
						|
 */
 | 
						|
function file_ajax_progress($key) {
 | 
						|
  $progress = array(
 | 
						|
    'message' => t('Starting upload...'),
 | 
						|
    'percentage' => -1,
 | 
						|
  );
 | 
						|
 | 
						|
  $implementation = file_progress_implementation();
 | 
						|
  if ($implementation == 'uploadprogress') {
 | 
						|
    $status = uploadprogress_get_info($key);
 | 
						|
    if (isset($status['bytes_uploaded']) && !empty($status['bytes_total'])) {
 | 
						|
      $progress['message'] = t('Uploading... (@current of @total)', array('@current' => format_size($status['bytes_uploaded']), '@total' => format_size($status['bytes_total'])));
 | 
						|
      $progress['percentage'] = round(100 * $status['bytes_uploaded'] / $status['bytes_total']);
 | 
						|
    }
 | 
						|
  }
 | 
						|
  elseif ($implementation == 'apc') {
 | 
						|
    $status = apc_fetch('upload_' . $key);
 | 
						|
    if (isset($status['current']) && !empty($status['total'])) {
 | 
						|
      $progress['message'] = t('Uploading... (@current of @total)', array('@current' => format_size($status['current']), '@total' => format_size($status['total'])));
 | 
						|
      $progress['percentage'] = round(100 * $status['current'] / $status['total']);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  drupal_json($progress);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Determine the preferred upload progress implementation.
 | 
						|
 *
 | 
						|
 * @return
 | 
						|
 *   A string indicating which upload progress system is available. Either "apc"
 | 
						|
 *   or "uploadprogress". If neither are available, returns FALSE.
 | 
						|
 */
 | 
						|
function file_progress_implementation() {
 | 
						|
  static $implementation;
 | 
						|
  if (!isset($implementation)) {
 | 
						|
    $implementation = FALSE;
 | 
						|
 | 
						|
    // We prefer the PECL extension uploadprogress because it supports multiple
 | 
						|
    // simultaneous uploads. APC only supports one at a time.
 | 
						|
    if (extension_loaded('uploadprogress')) {
 | 
						|
      $implementation = 'uploadprogress';
 | 
						|
    }
 | 
						|
    elseif (extension_loaded('apc') && ini_get('apc.rfc1867')) {
 | 
						|
      $implementation = 'apc';
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return $implementation;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Implement hook_file_references().
 | 
						|
 */
 | 
						|
function file_file_references($file) {
 | 
						|
  $count = file_get_file_reference_count($file);
 | 
						|
  return $count ? array('file' => $count) : NULL;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Implement hook_file_delete().
 | 
						|
 */
 | 
						|
function file_file_delete($file) {
 | 
						|
  // TODO: Remove references to a file that is in-use.
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Process function to expand the managed_file element type.
 | 
						|
 *
 | 
						|
 * Expands the file type to include Upload and Remove buttons, as well as
 | 
						|
 * support for a default value.
 | 
						|
 */
 | 
						|
function file_managed_file_process($element, &$form_state, $form) {
 | 
						|
  $fid = isset($element['#value']['fid']) ? $element['#value']['fid'] : 0;
 | 
						|
 | 
						|
  // Set some default element properties.
 | 
						|
  $element['#progress_indicator'] = empty($element['#progress_indicator']) ? 'none' : $element['#progress_indicator'];
 | 
						|
  $element['#file'] = $fid ? file_load($fid) : FALSE;
 | 
						|
  $element['#tree'] = TRUE;
 | 
						|
 | 
						|
  $ajax_settings = array(
 | 
						|
    'path' => 'file/ajax/' . implode('/', $element['#parents']) . '/' . $form['form_build_id']['#value'],
 | 
						|
    'wrapper' => $element['#id'] . '-ajax-wrapper',
 | 
						|
    'method' => 'replace',
 | 
						|
    'effect' => 'fade',
 | 
						|
    'progress' => array(
 | 
						|
      'type' => $element['#progress_indicator'],
 | 
						|
      'message' => $element['#progress_message'],
 | 
						|
    ),
 | 
						|
  );
 | 
						|
 | 
						|
  // Set up the buttons first since we need to check if they were clicked.
 | 
						|
  $element['upload_button'] = array(
 | 
						|
    '#name' => implode('_', $element['#parents']) . '_upload_button',
 | 
						|
    '#type' => 'submit',
 | 
						|
    '#value' => t('Upload'),
 | 
						|
    '#validate' => array(),
 | 
						|
    '#submit' => array('file_managed_file_submit'),
 | 
						|
    '#ajax' => $ajax_settings,
 | 
						|
    '#weight' => -5,
 | 
						|
  );
 | 
						|
 | 
						|
  $ajax_settings['progress']['type'] ? $ajax_settings['progress']['type'] == 'bar' : 'throbber';
 | 
						|
  $ajax_settings['progress']['message'] = NULL;
 | 
						|
  $ajax_settings['effect'] = 'none';
 | 
						|
  $element['remove_button'] = array(
 | 
						|
    '#name' => implode('_', $element['#parents']) . '_remove_button',
 | 
						|
    '#type' => 'submit',
 | 
						|
    '#value' => t('Remove'),
 | 
						|
    '#validate' => array(),
 | 
						|
    '#submit' => array('file_managed_file_submit'),
 | 
						|
    '#ajax' => $ajax_settings,
 | 
						|
    '#weight' => -5,
 | 
						|
  );
 | 
						|
 | 
						|
  // Because the output of this field changes depending on the button clicked,
 | 
						|
  // we need to ask FAPI immediately if the remove button was clicked.
 | 
						|
  // It's not good that we call this private function, but
 | 
						|
  // $form_state['clicked_button'] is only available after this #process
 | 
						|
  // callback is finished.
 | 
						|
  if (_form_button_was_clicked($element['remove_button'], $form_state)) {
 | 
						|
    // If it's a temporary file we can safely remove it immediately, otherwise
 | 
						|
    // it's up to the implementing module to clean up files that are in use.
 | 
						|
    if ($element['#file'] && $element['#file']->status == 0) {
 | 
						|
      file_delete($element['#file']);
 | 
						|
    }
 | 
						|
    $element['#file'] = FALSE;
 | 
						|
    $fid = 0;
 | 
						|
  }
 | 
						|
 | 
						|
  // Set access on the buttons.
 | 
						|
  $element['upload_button']['#access'] = empty($fid);
 | 
						|
  $element['remove_button']['#access'] = !empty($fid);
 | 
						|
 | 
						|
  $element['fid'] = array(
 | 
						|
    '#type' => 'hidden',
 | 
						|
    '#value' => $fid,
 | 
						|
  );
 | 
						|
 | 
						|
  // Add progress bar support to the upload if possible.
 | 
						|
  if ($element['#progress_indicator'] == 'bar' && $implementation = file_progress_implementation()) {
 | 
						|
    $upload_progress_key = md5(mt_rand());
 | 
						|
 | 
						|
    if ($implementation == 'uploadprogress') {
 | 
						|
      $element['UPLOAD_IDENTIFIER'] = array(
 | 
						|
        '#type' => 'hidden',
 | 
						|
        '#value' => $upload_progress_key,
 | 
						|
        '#attributes' => array('class' => array('file-progress')),
 | 
						|
      );
 | 
						|
    }
 | 
						|
    elseif ($implementation == 'apc') {
 | 
						|
      $element['APC_UPLOAD_PROGRESS'] = array(
 | 
						|
        '#type' => 'hidden',
 | 
						|
        '#value' => $upload_progress_key,
 | 
						|
        '#attributes' => array('class' => array('file-progress')),
 | 
						|
      );
 | 
						|
    }
 | 
						|
 | 
						|
    // Add the upload progress callback.
 | 
						|
    $element['upload_button']['#ajax']['progress']['path'] = 'file/progress/' . $upload_progress_key;
 | 
						|
  }
 | 
						|
 | 
						|
  // The file upload field itself.
 | 
						|
  $element['upload'] = array(
 | 
						|
    '#name' => 'files[' . implode('_', $element['#parents']) . ']',
 | 
						|
    '#type' => 'file',
 | 
						|
    '#size' => 22,
 | 
						|
    '#access' => empty($fid),
 | 
						|
    '#theme_wrappers' => array(),
 | 
						|
    '#weight' => -10,
 | 
						|
  );
 | 
						|
 | 
						|
  if ($fid && $element['#file']) {
 | 
						|
    $element['filename'] = array(
 | 
						|
      '#type' => 'markup',
 | 
						|
      '#markup' => theme('file_link', array('file' => $element['#file'])) . ' ',
 | 
						|
      '#weight' => -10,
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  // The "accept" attribute is valid XHTML, but not enforced in browsers.
 | 
						|
  // We use it for our own purposes in our JavaScript validation.
 | 
						|
  if (isset($element['#upload_validators']['file_validate_extensions'][0])) {
 | 
						|
    $element['upload']['#attributes']['accept'] = implode(',', array_filter(explode(' ', $element['#upload_validators']['file_validate_extensions'][0])));
 | 
						|
  }
 | 
						|
 | 
						|
  // Prefix and suffix used for AJAX replacement.
 | 
						|
  $element['#prefix'] = '<div id="' . $element['#id'] . '-ajax-wrapper">';
 | 
						|
  $element['#suffix'] = '</div>';
 | 
						|
 | 
						|
  return $element;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * The #value_callback for a managed_file type element.
 | 
						|
 */
 | 
						|
function file_managed_file_value(&$element, $input = FALSE, $form_state = NULL) {
 | 
						|
  $fid = 0;
 | 
						|
 | 
						|
  // Find the current value of this field from the form state.
 | 
						|
  $form_state_fid = $form_state['values'];
 | 
						|
  foreach ($element['#parents'] as $parent) {
 | 
						|
    $form_state_fid = isset($form_state_fid[$parent]) ? $form_state_fid[$parent] : 0;
 | 
						|
  }
 | 
						|
 | 
						|
  if ($element['#extended'] && isset($form_state_fid['fid'])) {
 | 
						|
    $fid = $form_state_fid['fid'];
 | 
						|
  }
 | 
						|
  elseif (is_numeric($form_state_fid)) {
 | 
						|
    $fid = $form_state_fid;
 | 
						|
  }
 | 
						|
 | 
						|
  // Process any input and save new uploads.
 | 
						|
  if ($input !== FALSE) {
 | 
						|
    $return = $input;
 | 
						|
 | 
						|
    // Uploads take priority over all other values.
 | 
						|
    if ($file = file_managed_file_save_upload($element)) {
 | 
						|
      $fid = $file->fid;
 | 
						|
    }
 | 
						|
    else {
 | 
						|
      // Check for #filefield_value_callback values.
 | 
						|
      // Because FAPI does not allow multiple #value_callback values like it
 | 
						|
      // does for #element_validate and #process, this fills the missing
 | 
						|
      // functionality to allow File fields to be extended through FAPI.
 | 
						|
      if (isset($element['#file_value_callbacks'])) {
 | 
						|
        foreach ($element['#file_value_callbacks'] as $callback) {
 | 
						|
          $callback($element, $input);
 | 
						|
        }
 | 
						|
      }
 | 
						|
      // Load file if the FID has changed to confirm it exists.
 | 
						|
      if (isset($input['fid']) && $file = file_load($input['fid'])) {
 | 
						|
        $fid = $file->fid;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // If there is no input, set the default value.
 | 
						|
  else {
 | 
						|
    if ($element['#extended']) {
 | 
						|
      $default_fid = isset($element['#default_value']['fid']) ? $element['#default_value']['fid'] : 0;
 | 
						|
      $return = isset($element['#default_value']) ? $element['#default_value'] : array('fid' => 0);
 | 
						|
    }
 | 
						|
    else {
 | 
						|
      $default_fid = isset($element['#default_value']) ? $element['#default_value'] : 0;
 | 
						|
      $return = array('fid' => 0);
 | 
						|
    }
 | 
						|
 | 
						|
    // Confirm that the file exists when used as a default value.
 | 
						|
    if ($default_fid && $file = file_load($default_fid)) {
 | 
						|
      $fid = $file->fid;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  $return['fid'] = $fid;
 | 
						|
 | 
						|
  return $return;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * An #element_validate callback for the managed_file element.
 | 
						|
 */
 | 
						|
function file_managed_file_validate(&$element, &$form_state) {
 | 
						|
  // If referencing an existing file, only allow if there are existing
 | 
						|
  // references. This prevents unmanaged files from being deleted if this
 | 
						|
  // item were to be deleted.
 | 
						|
  $clicked_button = end($form_state['clicked_button']['#parents']);
 | 
						|
  if ($clicked_button != 'remove_button' && !empty($element['fid']['#value'])) {
 | 
						|
    if ($file = file_load($element['fid']['#value'])) {
 | 
						|
      if ($file->status == FILE_STATUS_PERMANENT) {
 | 
						|
        $reference_count = 0;
 | 
						|
        foreach (module_invoke_all('file_references', $file) as $module => $references) {
 | 
						|
          $reference_count += $references;
 | 
						|
        }
 | 
						|
        if ($reference_count == 0) {
 | 
						|
          form_error($element, t('Referencing to the file used in the !name field is not allowed.', array('!name' => $element['#title'])));
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
    else {
 | 
						|
      form_error($element, t('The file referenced by the !name field does not exist.', array('!name' => $element['#title'])));
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Check required property based on the FID.
 | 
						|
  if ($element['#required'] && empty($element['fid']['#value']) && !in_array($clicked_button, array('upload_button', 'remove_button'))) {
 | 
						|
    form_error($element['upload'], t('!name field is required.', array('!name' => $element['#title'])));
 | 
						|
  }
 | 
						|
 | 
						|
  // Consolidate the array value of this field to a single FID.
 | 
						|
  if (!$element['#extended']) {
 | 
						|
    form_set_value($element, $element['fid']['#value'], $form_state);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Submit handler for non-JavaScript uploads.
 | 
						|
 */
 | 
						|
function file_managed_file_submit($form, &$form_state) {
 | 
						|
  // Do not redirect and leave the page after uploading a file. This keeps
 | 
						|
  // all the current form values in place. The file is saved by the
 | 
						|
  // #value_callback on the form element.
 | 
						|
  $form_state['redirect'] = FALSE;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Given a managed_file element, save any files that have been uploaded into it.
 | 
						|
 *
 | 
						|
 * @param $element
 | 
						|
 *   The FAPI element whose values are being saved.
 | 
						|
 * @return
 | 
						|
 *   The file object representing the file that was saved, or FALSE if no file
 | 
						|
 *   was saved.
 | 
						|
 */
 | 
						|
function file_managed_file_save_upload($element) {
 | 
						|
  $upload_name = implode('_', $element['#parents']);
 | 
						|
  if (empty($_FILES['files']['name'][$upload_name])) {
 | 
						|
    return FALSE;
 | 
						|
  }
 | 
						|
 | 
						|
  $destination = isset($element['#upload_location']) ? $element['#upload_location'] : NULL;
 | 
						|
  if (isset($destination) && !file_prepare_directory($destination, FILE_CREATE_DIRECTORY)) {
 | 
						|
    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']));
 | 
						|
    form_set_error($upload_name, t('The file could not be uploaded.'));
 | 
						|
    return FALSE;
 | 
						|
  }
 | 
						|
 | 
						|
  if (!$file = file_save_upload($upload_name, $element['#upload_validators'], $destination)) {
 | 
						|
    watchdog('file', 'The file upload failed. %upload', array('%upload' => $upload_name));
 | 
						|
    form_set_error($upload_name, t('The file in the !name field was unable to be uploaded.', array('!name' => $element['#title'])));
 | 
						|
    return FALSE;
 | 
						|
  }
 | 
						|
 | 
						|
  return $file;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Theme a managed file element.
 | 
						|
 */
 | 
						|
function theme_file_managed_file($variables) {
 | 
						|
  $element = $variables['element'];
 | 
						|
 | 
						|
  // This wrapper is required to apply JS behaviors and CSS styling.
 | 
						|
  $output = '';
 | 
						|
  $output .= '<div class="form-managed-file">';
 | 
						|
  $output .= drupal_render_children($element);
 | 
						|
  $output .= '</div>';
 | 
						|
  return $output;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Output a link to a file.
 | 
						|
 *
 | 
						|
 * @param $variables
 | 
						|
 *   An associative array containing:
 | 
						|
 *   - file: A file object to which the link will be created.
 | 
						|
 */
 | 
						|
function theme_file_link($variables) {
 | 
						|
  $file = $variables['file'];
 | 
						|
 | 
						|
  $url = file_create_url($file->uri);
 | 
						|
  $icon = theme('file_icon', array('file' => $file));
 | 
						|
 | 
						|
  // Set options as per anchor format described at
 | 
						|
  // http://microformats.org/wiki/file-format-examples
 | 
						|
  $options = array(
 | 
						|
    'attributes' => array(
 | 
						|
      'type' => $file->filemime . '; length=' . $file->filesize,
 | 
						|
    ),
 | 
						|
  );
 | 
						|
 | 
						|
  // Use the description as the link text if available.
 | 
						|
  if (empty($file->description)) {
 | 
						|
    $link_text = check_plain($file->filename);
 | 
						|
  }
 | 
						|
  else {
 | 
						|
    $link_text = check_plain($file->description);
 | 
						|
    $options['attributes']['title'] = check_plain($file->filename);
 | 
						|
  }
 | 
						|
 | 
						|
  return '<span class="file">' . $icon . ' ' . l($link_text, $url, $options) . '</span>';
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Return an image with an appropriate icon for the given file.
 | 
						|
 *
 | 
						|
 * @param $variables
 | 
						|
 *   An associative array containing:
 | 
						|
 *   - file: A file object for which to make an icon.
 | 
						|
 */
 | 
						|
function theme_file_icon($variables) {
 | 
						|
  $file = $variables['file'];
 | 
						|
 | 
						|
  $mime = check_plain($file->filemime);
 | 
						|
  $icon_url = file_icon_url($file);
 | 
						|
  return '<img class="file-icon" alt="" title="' . $mime . '" src="' . $icon_url . '" />';
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Given a file object, create a URL to a matching icon.
 | 
						|
 *
 | 
						|
 * @param $file
 | 
						|
 *   A file object.
 | 
						|
 * @param $icon_directory
 | 
						|
 *   (optional) A path to a directory of icons to be used for files. Defaults to
 | 
						|
 *   the value of the "file_icon_directory" variable.
 | 
						|
 * @return
 | 
						|
 *   A URL string to the icon, or FALSE if an appropriate icon cannot be found.
 | 
						|
 */
 | 
						|
function file_icon_url($file, $icon_directory = NULL) {
 | 
						|
  if ($icon_path = file_icon_path($file, $icon_directory)) {
 | 
						|
    return base_path() . $icon_path;
 | 
						|
  }
 | 
						|
  return FALSE;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Given a file object, create a path to a matching icon.
 | 
						|
 *
 | 
						|
 * @param $file
 | 
						|
 *   A file object.
 | 
						|
 * @param $icon_directory
 | 
						|
 *   (optional) A path to a directory of icons to be used for files. Defaults to
 | 
						|
 *   the value of the "file_icon_directory" variable.
 | 
						|
 * @return
 | 
						|
 *   A string to the icon as a local path, or FALSE if an appropriate icon could
 | 
						|
 *   not be found.
 | 
						|
 */
 | 
						|
function file_icon_path($file, $icon_directory = NULL) {
 | 
						|
  // Use the default set of icons if none specified.
 | 
						|
  if (!isset($icon_directory)) {
 | 
						|
    $icon_directory = variable_get('file_icon_directory', drupal_get_path('module', 'file') . '/icons');
 | 
						|
  }
 | 
						|
 | 
						|
  // If there's an icon matching the exact mimetype, go for it.
 | 
						|
  $dashed_mime = strtr($file->filemime, array('/' => '-'));
 | 
						|
  $icon_path = $icon_directory . '/' . $dashed_mime . '.png';
 | 
						|
  if (file_exists($icon_path)) {
 | 
						|
    return $icon_path;
 | 
						|
  }
 | 
						|
 | 
						|
  // For a few mimetypes, we can "manually" map to a generic icon.
 | 
						|
  $generic_mime = (string) file_icon_map($file);
 | 
						|
  $icon_path = $icon_directory . '/' . $generic_mime . '.png';
 | 
						|
  if ($generic_mime && file_exists($icon_path)) {
 | 
						|
    return $icon_path;
 | 
						|
  }
 | 
						|
 | 
						|
  // Use generic icons for each category that provides such icons.
 | 
						|
  foreach (array('audio', 'image', 'text', 'video') as $category) {
 | 
						|
    if (strpos($file->filemime, $category . '/') === 0) {
 | 
						|
      $icon_path = $icon_directory . '/' . $category . '-x-generic.png';
 | 
						|
      if (file_exists($icon_path)) {
 | 
						|
        return $icon_path;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Try application-octet-stream as last fallback.
 | 
						|
  $icon_path = $icon_directory . '/application-octet-stream.png';
 | 
						|
  if (file_exists($icon_path)) {
 | 
						|
    return $icon_path;
 | 
						|
  }
 | 
						|
 | 
						|
  // No icon can be found.
 | 
						|
  return FALSE;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Determine the generic icon MIME package based on a file's MIME type.
 | 
						|
 *
 | 
						|
 * @param $file
 | 
						|
 *   A file object.
 | 
						|
 * @return
 | 
						|
 *   The generic icon MIME package expected for this file.
 | 
						|
 */
 | 
						|
function file_icon_map($file) {
 | 
						|
  switch ($file->filemime) {
 | 
						|
    // Word document types.
 | 
						|
    case 'application/msword':
 | 
						|
    case 'application/vnd.ms-word.document.macroEnabled.12':
 | 
						|
    case 'application/vnd.oasis.opendocument.text':
 | 
						|
    case 'application/vnd.oasis.opendocument.text-template':
 | 
						|
    case 'application/vnd.oasis.opendocument.text-master':
 | 
						|
    case 'application/vnd.oasis.opendocument.text-web':
 | 
						|
    case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
 | 
						|
    case 'application/vnd.stardivision.writer':
 | 
						|
    case 'application/vnd.sun.xml.writer':
 | 
						|
    case 'application/vnd.sun.xml.writer.template':
 | 
						|
    case 'application/vnd.sun.xml.writer.global':
 | 
						|
    case 'application/vnd.wordperfect':
 | 
						|
    case 'application/x-abiword':
 | 
						|
    case 'application/x-applix-word':
 | 
						|
    case 'application/x-kword':
 | 
						|
    case 'application/x-kword-crypt':
 | 
						|
      return 'x-office-document';
 | 
						|
 | 
						|
    // Spreadsheet document types.
 | 
						|
    case 'application/vnd.ms-excel':
 | 
						|
    case 'application/vnd.ms-excel.sheet.macroEnabled.12':
 | 
						|
    case 'application/vnd.oasis.opendocument.spreadsheet':
 | 
						|
    case 'application/vnd.oasis.opendocument.spreadsheet-template':
 | 
						|
    case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
 | 
						|
    case 'application/vnd.stardivision.calc':
 | 
						|
    case 'application/vnd.sun.xml.calc':
 | 
						|
    case 'application/vnd.sun.xml.calc.template':
 | 
						|
    case 'application/vnd.lotus-1-2-3':
 | 
						|
    case 'application/x-applix-spreadsheet':
 | 
						|
    case 'application/x-gnumeric':
 | 
						|
    case 'application/x-kspread':
 | 
						|
    case 'application/x-kspread-crypt':
 | 
						|
      return 'x-office-spreadsheet';
 | 
						|
 | 
						|
    // Presentation document types.
 | 
						|
    case 'application/vnd.ms-powerpoint':
 | 
						|
    case 'application/vnd.ms-powerpoint.presentation.macroEnabled.12':
 | 
						|
    case 'application/vnd.oasis.opendocument.presentation':
 | 
						|
    case 'application/vnd.oasis.opendocument.presentation-template':
 | 
						|
    case 'application/vnd.openxmlformats-officedocument.presentationml.presentation':
 | 
						|
    case 'application/vnd.stardivision.impress':
 | 
						|
    case 'application/vnd.sun.xml.impress':
 | 
						|
    case 'application/vnd.sun.xml.impress.template':
 | 
						|
    case 'application/x-kpresenter':
 | 
						|
      return 'x-office-presentation';
 | 
						|
 | 
						|
    // Compressed archive types.
 | 
						|
    case 'application/zip':
 | 
						|
    case 'application/x-zip':
 | 
						|
    case 'application/stuffit':
 | 
						|
    case 'application/x-stuffit':
 | 
						|
    case 'application/x-7z-compressed':
 | 
						|
    case 'application/x-ace':
 | 
						|
    case 'application/x-arj':
 | 
						|
    case 'application/x-bzip':
 | 
						|
    case 'application/x-bzip-compressed-tar':
 | 
						|
    case 'application/x-compress':
 | 
						|
    case 'application/x-compressed-tar':
 | 
						|
    case 'application/x-cpio-compressed':
 | 
						|
    case 'application/x-deb':
 | 
						|
    case 'application/x-gzip':
 | 
						|
    case 'application/x-java-archive':
 | 
						|
    case 'application/x-lha':
 | 
						|
    case 'application/x-lhz':
 | 
						|
    case 'application/x-lzop':
 | 
						|
    case 'application/x-rar':
 | 
						|
    case 'application/x-rpm':
 | 
						|
    case 'application/x-tzo':
 | 
						|
    case 'application/x-tar':
 | 
						|
    case 'application/x-tarz':
 | 
						|
    case 'application/x-tgz':
 | 
						|
      return 'package-x-generic';
 | 
						|
 | 
						|
    // Script file types.
 | 
						|
    case 'application/ecmascript':
 | 
						|
    case 'application/javascript':
 | 
						|
    case 'application/mathematica':
 | 
						|
    case 'application/vnd.mozilla.xul+xml':
 | 
						|
    case 'application/x-asp':
 | 
						|
    case 'application/x-awk':
 | 
						|
    case 'application/x-cgi':
 | 
						|
    case 'application/x-csh':
 | 
						|
    case 'application/x-m4':
 | 
						|
    case 'application/x-perl':
 | 
						|
    case 'application/x-php':
 | 
						|
    case 'application/x-ruby':
 | 
						|
    case 'application/x-shellscript':
 | 
						|
    case 'text/vnd.wap.wmlscript':
 | 
						|
    case 'text/x-emacs-lisp':
 | 
						|
    case 'text/x-haskell':
 | 
						|
    case 'text/x-literate-haskell':
 | 
						|
    case 'text/x-lua':
 | 
						|
    case 'text/x-makefile':
 | 
						|
    case 'text/x-matlab':
 | 
						|
    case 'text/x-python':
 | 
						|
    case 'text/x-sql':
 | 
						|
    case 'text/x-tcl':
 | 
						|
      return 'text-x-script';
 | 
						|
 | 
						|
    // HTML aliases.
 | 
						|
    case 'application/xhtml+xml':
 | 
						|
      return 'text-html';
 | 
						|
 | 
						|
    // Executable types.
 | 
						|
    case 'application/x-macbinary':
 | 
						|
    case 'application/x-ms-dos-executable':
 | 
						|
    case 'application/x-pef-executable':
 | 
						|
      return 'application-x-executable';
 | 
						|
 | 
						|
    default:
 | 
						|
      return FALSE;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * @defgroup file-module-api File module public API functions
 | 
						|
 * @{
 | 
						|
 * These functions may be used to determine if and where a file is in use.
 | 
						|
 */
 | 
						|
 | 
						|
/**
 | 
						|
 * Count the number of times the file is referenced.
 | 
						|
 *
 | 
						|
 * @param $file
 | 
						|
 *   A file object.
 | 
						|
 * @param $field
 | 
						|
 *   (optional) A CCK field array or field name as a string. If provided,
 | 
						|
 *   limits the reference check to the given field.
 | 
						|
 * @param $field_type
 | 
						|
 *   (optional) The name of a field type. If provided, limits the reference
 | 
						|
 *   check to fields of the given type.
 | 
						|
 * @return
 | 
						|
 *   An integer value.
 | 
						|
 */
 | 
						|
function file_get_file_reference_count($file, $field = NULL, $field_type = 'file') {
 | 
						|
  // Determine the collection of fields to check.
 | 
						|
  if (isset($field)) {
 | 
						|
    // Support $field as 'field name'.
 | 
						|
    if (is_string($field)) {
 | 
						|
      $field = field_info_field($field);
 | 
						|
    }
 | 
						|
    $fields = array($field['field_name'] => $field);
 | 
						|
  }
 | 
						|
  else {
 | 
						|
    $fields = field_info_fields();
 | 
						|
  }
 | 
						|
 | 
						|
  $types = entity_get_info();
 | 
						|
  $reference_count = 0;
 | 
						|
 | 
						|
  foreach ($fields as $field) {
 | 
						|
    if (empty($field_type) || $field['type'] == $field_type) {
 | 
						|
      // TODO: Use a more efficient mechanism rather than actually retrieving
 | 
						|
      // all the references themselves, such as using a COUNT() query.
 | 
						|
      $references = file_get_file_references($file, $field, FIELD_LOAD_REVISION, $field_type);
 | 
						|
      foreach ($references as $obj_type => $type_references) {
 | 
						|
        $reference_count += count($type_references);
 | 
						|
      }
 | 
						|
 | 
						|
      // If a field_name is present in the file object, the file is being deleted
 | 
						|
      // from this field.
 | 
						|
      if (isset($file->file_field_name) && $field['field_name'] == $file->file_field_name) {
 | 
						|
        // If deleting the entire piece of content, decrement references.
 | 
						|
        if (isset($file->file_field_type) && isset($file->file_field_id)) {
 | 
						|
          if ($file->file_field_type == $obj_type) {
 | 
						|
            $info = entity_get_info($obj_type);
 | 
						|
            $id = $types[$obj_type]['object keys']['id'];
 | 
						|
            foreach ($type_references as $reference) {
 | 
						|
              if ($file->file_field_id == $reference->$id) {
 | 
						|
                $reference_count--;
 | 
						|
              }
 | 
						|
            }
 | 
						|
          }
 | 
						|
        }
 | 
						|
        // Otherwise we're just deleting a single reference in this field.
 | 
						|
        else {
 | 
						|
          $reference_count--;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return $reference_count;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * Get a list of references to a file.
 | 
						|
 *
 | 
						|
 * @param $file
 | 
						|
 *   A file object.
 | 
						|
 * @param $field
 | 
						|
 *   (optional) A field array to be used for this check. If given, limits the
 | 
						|
 *   reference check to the given field.
 | 
						|
 * @param $age
 | 
						|
 *   (optional) A constant that specifies which references to count. Use
 | 
						|
 *   FIELD_LOAD_REVISION to retrieve all references within all revisions or
 | 
						|
 *   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.
 | 
						|
 * @return
 | 
						|
 *   An integer value.
 | 
						|
 */
 | 
						|
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) || $field['type'] == $field_type) && !isset($references[$field_name])) {
 | 
						|
      // Get each time this file is used within a field.
 | 
						|
      $cursor = 0;
 | 
						|
      $references[$field_name] = field_attach_query($file_field['id'], array(array('fid', $file->fid)), array('limit' => FIELD_QUERY_NO_LIMIT, 'cursor' => &$cursor, 'age'=> $age));
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return isset($field) ? $references[$field['field_name']] : $references;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * @} End of "defgroup file-module-api".
 | 
						|
 */
 |