Skip to content
filefield.module 11.6 KiB
Newer Older
 * FileField: Defines a CCK file field type.
 * Uses content.module to store the fid and field specific metadata,
 * and Drupal's {files} table to store the actual file data.
function filefield_init() {

  /**
   * include filefield dependecies.
   * @todo: explore better dynamic loading.
   */

  // field file api.
  module_load_include('inc', 'filefield', 'field_file');
  
  // widget hooks and callbacks.
  module_load_include('inc', 'filefield', 'filefield_widget');
  // formatter hooks and callbacks.
  module_load_include('inc', 'filefield', 'filefield_formatter');

  // file hooks and callbacks.
  module_load_include('inc', 'filefield', 'filefield_file');
}
/**
 * Implementation of hook_menu().
 */
function filefield_menu() {
  $items['filefield/js/upload/%/%/%'] = array(
    'page callback' => 'filefield_js',
    'page arguments' => array(3, 4, 5, 'filefield_file_upload_js'),
    'access callback' => 'filefield_edit_access',
    'type' => MENU_CALLBACK,
    'file' => 'filefield_widget.inc',
  );
  $items['filefield/js/delete/%/%/%'] = array(
    'page callback' => 'filefield_js',
    'page arguments' => array(3, 4, 5, 'filefield_file_edit_delete_js'),
    'access callback' => 'filefield_edit_access',
    'file' => 'filefield_widget.inc',
/**
 * Implementation of hook_elements().
 */
function filefield_elements() {
  $elements = array();
  $elements['filefield_widget'] =  array(
    '#columns' => array('fid', 'description', 'list', 'data'),
    '#process' => array('filefield_widget_process'),
    '#after_build' => array('filefield_widget_after_build'),
    '#value_callback' => 'filefield_widget_value',
    '#description' => t('Changes made to the attachments are not permanent until you save this post.'),
  $elements['filefield_extensible'] =  array(
    '#columns' => array('fid', 'description', 'list', 'data'),
    '#process' => array('filefield_extensible_process'),
    '#after_build' => array('filefield_extensible_after_build'),
    '#value_callback' => 'filefield_extensible_value',
    '#description' => t('Changes made to the attachments are not permanent until you save this post.'),
/**
 * Implementation of hook_theme().
 */
function filefield_theme() {
  return array(
      'arguments' => array('element' => NULL),
    
    // @todo: verify the need for the rest one by one.
    'filefield_container_item' => array(
      'arguments' => array('element' => NULL),
      'file' => 'filefield.theme.inc',
    ),
    'filefield_icon' => array(
      'arguments' => array('file' => NULL),
      'file' => 'filefield.theme.inc',
      'arguments' => array('element' => NULL),
      'file' => 'filefield_widget.inc',
    'filefield_file_edit' => array(
      'arguments' => array('element' => NULL),
      'file' => 'filefield_widget.inc',
      'arguments' => array('element' => NULL),
    'filefield_formatter_default' => array(
      'arguments' => array('element' => NULL),
      'file' => 'filefield.formatter.inc',
      'arguments' => array('file' => NULL, 'field' => NULL),
      'file' => 'filefield.formatter.inc',
      'arguments' => array('file' => NULL, 'field' => NULL),
      'file' => 'filefield.formatter.inc',
    ),
    'filefield_file_formatter_generic' => array(
      'arguments' => array(
        'file' => NULL, 'field' => NULL, 'file_formatter_settings' => NULL,
      ),
      'file' => 'filefield.formatter.inc',
/**
 * Implementation of hook_file_download(). Yes, *that* hook that causes
 * any attempt for file upload module interoperability to fail spectacularly.
 */
function filefield_file_download($file) {
  $file = file_create_path($file);
  $result = db_query("SELECT * FROM {files} WHERE filepath = '%s'", $file);
  if (!$file = db_fetch_object($result)) {
    // We don't really care about this file.
    return;
  }
  // Find out if any filefield contains this file, and if so, which field
  // and node it belongs to. Required for later access checking.
  $cck_files = array();
  foreach (content_fields() as $field) {
    if ($field['type'] == 'file') {
      $db_info = content_database_info($field);
      $table = $db_info['table'];
      $fid_column = $db_info['columns']['fid']['column'];

      $columns = array('vid', 'nid');
      foreach ($db_info['columns'] as $property_name => $column_info) {
        $columns[] = $column_info['column'] .' AS '. $property_name;
      }
      $result = db_query("SELECT ". implode(', ', $columns) ."
                          FROM {". $table ."}
                          WHERE ". $fid_column ." = %d", $file->fid);

      while ($content = db_fetch_array($result)) {
        $content['field'] = $field;
        $cck_files[$field['field_name']][$content['vid']] = $content;
      }
    }
  // If no filefield item is involved with this file, we don't care about it.
  if (empty($cck_files)) {
    return;
  }

  // If any node includes this file but the user may not view this field,
  // then deny the download.
  foreach ($cck_files as $field_name => $field_files) {
    if (!filefield_view_access($field_name)) {
      return -1;
    }
  }

  // So the overall field view permissions are not denied, but if access is
  // denied for a specific node containing the file, deny the download as well.
  // It's probably a little too restrictive, but I can't think of a
  // better way at the moment. Input appreciated.
  // (And yeah, node access checks also include checking for 'access content'.)
  $nodes = array();
  foreach ($cck_files as $field_name => $field_files) {
    foreach ($field_files as $revision_id => $content) {
      // Checking separately for each revision is probably not the best idea -
      // what if 'view revisions' is disabled? So, let's just check for the
      // current revision of that node.
      if (isset($nodes[$content['nid']])) {
        continue; // don't check the same node twice
      }
      $node = node_load($content['nid']);
      if (!node_access('view', $node)) {
        // You don't have permission to view the node this file is attached to.
        return -1;
      }
      $nodes[$content['nid']] = $node;
    }

  // Well I guess you can see this file.
  $name = mime_header_encode($file->filename);
  $type = mime_header_encode($file->filemime);
  // Serve images and text inline for the browser to display rather than download.
  $disposition = ereg('^(text/|image/)', $file->filemime) ? 'inline' : 'attachment';
  return array(
    'Content-Type: '. $type .'; name='. $name,
    'Content-Length: '. $file->filesize,
    'Content-Disposition: '. $disposition .'; filename='. $name,
    'Cache-Control: private',
  );
 * Implementation of CCK's hook_field_info().
 */
function filefield_field_info() {
  return array(
    'filefield' => array(
      'label' => 'File',
      'description' => t('Store an arbitrary file.'),
    ),
  );
}

/**
 * Implementation of hook_field_settings().
 */
function filefield_field_settings($op, $field) {
  module_load_include('inc','filefield','filefield_field_settings');
  $op = str_replace(' ', '_', $op);
  // add filefield specific handlers...
  $function = 'filefield_field_settings_'. $op;
  if (function_exists($function)) {
    return $function($field);
  }
}

/**
 * Implementtation of CCK's hook_field().
function filefield_field($op, $node, $field, &$items, $teaser, $page) {
  module_load_include('inc','filefield','filefield_field');
  $op = str_replace(' ', '_', $op);
  // add filefield specific handlers...
  $function = 'filefield_field_'. $op;
  if (function_exists($function)) {
    return $function($node, $field, $items, $teaser, $page);
 * Implementation of CCK's hook_widget_settings().
function filefield_widget_settings($op, $widget) {
  module_load_include('inc','filefield','filefield_widget_settings');
  $op = str_replace(' ', '_', $op);
  $function = 'filefield_field_settings_'. $op;
  if (function_exists($function)) {
    return $function($widget);

/**
 * Implementation of hook_widget().
 */
function filefield_widget(&$form, &$form_state, $field, $items, $delta = 0) {
  if (empty($items[$delta])) { 
    $items[$delta] = array('fid' => 0, 'description' => '', 'list' => 0, 'data' => '');
  }
  $form['#attributes'] = array('enctype' => 'multipart/form-data');
  $element = array(
    '#type' => $field['widget']['type'],
    '#default_value' => $items[$delta],
  );
  return $element;
}

/**
 * Implementation of CCK's hook_content_is_empty().
 *
 * The result of this determines whether content.module will save
 * the value of the field.
 */
function filefield_content_is_empty($item, $field) {
  if (empty($item['fid'])) {
    return TRUE;
  }
  return FALSE;
}




/**
 * Implementation of CCK's hook_widget_info().
 */
function filefield_widget_info() {
  return array(
    'filefield_widget' => array(
      'label' => t('File Upload'),
      'field types' => array('file'),
      'multiple values' => CONTENT_HANDLE_CORE,
      'callbacks' => array('default value' => CONTENT_CALLBACK_CUSTOM),
      'description' => t('A plain file upload widget.'),
    ),
    'filefield_combo' => array(
      'label' => 'Extensible File',
      'field types' => array('file'),
      'multiple values' => CONTENT_HANDLE_CORE,
      'callbacks' => array('default value' => CONTENT_CALLBACK_CUSTOM),
      'description' => t('(Experimental)An extensible file upload widget.'),
    ),
  );
}

/**
 * Implementation of CCK's hook_field_formatter_info().
 */
function filefield_field_formatter_info() {
  return array(
    'filefield_default' => array(
      'label' => t('Generic files'),
      'suitability callback' => TRUE,
      'field types' => array('file','image'),
      'multiple values' => CONTENT_HANDLE_CORE,
      'description' => t('Displays all kinds of files with an icon and a linked file description.'),
    ),
    'filefield_dynamic' => array(
      'label' => t('Dynamic file formatters'),
      'suitability callback' => TRUE,
      'field types' => array('file'),
      'multiple values' => CONTENT_HANDLE_CORE,
      'description' => t('(experimental) An extensible formatter for filefield.'),
    ),
  );
}




/**
 * Determine the most appropriate icon for the given file's mimetype.
 *
 * @return The URL of the icon image file, or FALSE if no icon could be found.
 */
function filefield_icon_url($file) {
  include_once(drupal_get_path('module', 'filefield') .'/filefield.theme.inc');
  return _filefield_icon_url($file);
}


/**
 * Access callback for the JavaScript upload and deletion AHAH callbacks.
 * The content_permissions module provides nice fine-grained permissions for
 * us to check, so we can make sure that the user may actually edit the file.
 */
function filefield_edit_access($field_name) {
  if (module_exists('content_permissions')) {
    return user_access('edit '. $field_name);
  }
  // No content permissions to check, so let's fall back to a more general permission.
  return user_access('access content');
}

/**
 * Access callback that checks if the current user may view the filefield.
 */
function filefield_view_access($field_name) {
  if (module_exists('content_permissions')) {
    return user_access('view '. $field_name);
  }
  // No content permissions to check, so let's fall back to a more general permission.
  return user_access('access content');
}