Skip to content
upload.module 23.7 KiB
Newer Older
Dries Buytaert's avatar
Dries Buytaert committed
<?php
Dries Buytaert's avatar
Dries Buytaert committed

Dries Buytaert's avatar
 
Dries Buytaert committed
/**
 * @file
 * File-handling and attaching files to nodes.
Dries Buytaert's avatar
 
Dries Buytaert committed
 */

function upload_help($path, $arg) {
  switch ($path) {
      $output = '<p>' . t('The upload module allows users to upload files to the site. The ability to upload files is important for members of a community who want to share work. It is also useful to administrators who want to keep uploaded files connected to posts.') . '</p>';
      $output .= '<p>' . t('Users with the upload files permission can upload attachments to posts. Uploads may be enabled for specific content types on the content types settings page. Each user role can be customized to limit or control the file size of uploads, or the maximum dimension of image files.') . '</p>';
      $output .= '<p>' . t('For more information, see the online handbook entry for <a href="@upload">Upload module</a>.', array('@upload' => 'http://drupal.org/handbook/modules/upload/')) . '</p>';
      return '<p>' . t('Users with the <a href="@permissions">upload files permission</a> can upload attachments. Users with the <a href="@permissions">view uploaded files permission</a> can view uploaded attachments. You can choose which post types can take attachments on the <a href="@types">content types settings</a> page.', array('@permissions' => url('admin/settings/permissions'), '@types' => url('admin/settings/types'))) . '</p>';
Dries Buytaert's avatar
Dries Buytaert committed
  }
}

 */
function upload_theme() {
  return array(
    'upload_attachments' => array(
      'arguments' => array('elements' => NULL),
    ),
    'upload_form_current' => array(
      'arguments' => array('form' => NULL),
    ),
    'upload_form_new' => array(
      'arguments' => array('form' => NULL),
    ),
  );
}

 * Implement hook_permission().
function upload_permission() {
    'upload files' => array(
      'title' => t('Upload files'),
      'description' => t('Attach images and other files to content.'),
    ),
    'view uploaded files' => array(
      'title' => t('View uploaded files'),
      'description' => t('View and download files attached to content.'),
    ),
Dries Buytaert's avatar
Dries Buytaert committed
}

 * Inject links into $node for attachments.
function upload_node_links($node, $build_mode) {
  // Display a link with the number of attachments
  $num_files = 0;
  foreach ($node->files as $file) {
  if ($num_files) {
    $links['upload_attachments'] = array(
      'title' => format_plural($num_files, '1 attachment', '@count attachments'),
      'href' => "node/$node->nid",
      'attributes' => array('title' => t('Read full article to view attachments.')),
      'fragment' => 'attachments'
    );
    $node->content['links']['upload_attachments'] = array(
      '#theme' => 'links',
      '#links' => $links,
      '#attributes' => array('class' => 'links inline'),
function upload_menu() {
  $items['upload/js'] = array(
    'page callback' => 'upload_js',
    'access arguments' => array('upload files'),
    'type' => MENU_CALLBACK,
  );
  $items['admin/settings/uploads'] = array(
    'title' => 'File uploads',
    'description' => 'Control how files may be attached to content.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('upload_admin_settings'),
    'access arguments' => array('administer site configuration'),
    'type' => MENU_NORMAL_ITEM,
  );
  return $items;
}
Dries Buytaert's avatar
 
Dries Buytaert committed

/**
 * Determine the limitations on files that a given user may upload. The user
 * may be in multiple roles so we select the most permissive limitations from
 * all of their roles.
 *
 * @param $user
 *   A Drupal user object.
 * @return
 *   An associative array with the following keys:
 *     'extensions'
 *       A white space separated string containing all the file extensions this
 *       user may upload.
 *     'file_size'
 *       The maximum size of a file upload in bytes.
 *     'user_size'
 *       The total number of bytes for all for a user's files.
 *     'resolution'
 *       A string specifying the maximum resolution of images.
 */
function _upload_file_limits($user) {
  $file_limit = variable_get('upload_uploadsize_default', 1);
  $user_limit = variable_get('upload_usersize_default', 1);
  $all_extensions = explode(' ', variable_get('upload_extensions_default', 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'));
  foreach ($user->roles as $rid => $name) {
    $extensions = variable_get("upload_extensions_$rid", variable_get('upload_extensions_default', 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'));
    $all_extensions = array_merge($all_extensions, explode(' ', $extensions));

    // A zero value indicates no limit, take the least restrictive limit.
    $file_size = variable_get("upload_uploadsize_$rid", variable_get('upload_uploadsize_default', 1)) * 1024 * 1024;
    $file_limit = ($file_limit && $file_size) ? max($file_limit, $file_size) : 0;

    $user_size = variable_get("upload_usersize_$rid", variable_get('upload_usersize_default', 1)) * 1024 * 1024;
    $user_limit = ($user_limit && $user_size) ? max($user_limit, $user_size) : 0;
  }
  $all_extensions = implode(' ', array_unique($all_extensions));
  return array(
    'extensions' => $all_extensions,
    'file_size' => $file_limit,
    'user_size' => $user_limit,
    'resolution' => variable_get('upload_max_resolution', 0),
  );
Dries Buytaert's avatar
Dries Buytaert committed
}

function upload_file_download($filepath) {
  $file = db_query("SELECT f.*, u.nid FROM {file} f INNER JOIN {upload} u ON f.fid = u.fid WHERE uri = :path", array(':path' => $filepath))->fetchObject();

  if ($file && user_access('view uploaded files') && ($node = node_load($file->nid)) && node_access('view', $node)) {
    return array(
      'Content-Type' => $file->filemime,
      'Content-Length' => $file->filesize,
Dries Buytaert's avatar
Dries Buytaert committed
}

 * Save new uploads and store them in the session to be associated to the node
 * on upload_save.
 *
 * @param $node
 *   A node object to associate with uploaded files.
function upload_node_form_submit(&$form, &$form_state) {
  $limits = _upload_file_limits($user);
  $validators = array(
    'file_validate_extensions' => array($limits['extensions']),
    'file_validate_image_resolution' => array($limits['resolution']),
    'file_validate_size' => array($limits['file_size'], $limits['user_size']),
  );
  if (user_access('upload files') && ($file = file_save_upload('upload', $validators, 'public://'))) {
    $file->list = variable_get('upload_list_default', 1);
    $file->description = $file->filename;
    $file->new = TRUE;
    $form['#node']->files[$file->fid] = $file;
    $form_state['values']['files'][$file->fid] = (array)$file;
  if (isset($form_state['values']['files'])) {
    foreach ($form_state['values']['files'] as $fid => $file) {
      $form_state['values']['files'][$fid]['new'] = !empty($form['#node']->files[$fid]->new);
    }
  }

  // Order the form according to the set file weight values.
  if (!empty($form_state['values']['files'])) {
    $microweight = 0.001;
    foreach ($form_state['values']['files'] as $fid => $file) {
      if (is_numeric($fid)) {
        $form_state['values']['files'][$fid]['#weight'] = $file['weight'] + $microweight;
        $microweight += 0.001;
      }
    uasort($form_state['values']['files'], 'element_sort');
function upload_form_alter(&$form, $form_state, $form_id) {
  if ($form_id == 'node_type_form' && isset($form['identity']['type'])) {
    $form['workflow']['upload'] = array(
      '#type' => 'radios',
      '#title' => t('Attachments'),
      '#default_value' => variable_get('upload_' . $form['#node_type']->type, 1),
      '#options' => array(t('Disabled'), t('Enabled')),
    );
  }
    if (variable_get("upload_$node->type", TRUE)) {
      // Attachments fieldset
      $form['attachments'] = array(
        '#type' => 'fieldset',
        '#access' => user_access('upload files'),
        '#title' => t('File attachments'),
        '#collapsible' => TRUE,
        '#collapsed' => empty($node->files),
        '#group' => 'additional_settings',
        '#attached_js' => array(drupal_get_path('module', 'upload') . '/upload.js'),
        '#description' => t('Changes made to the attachments are not permanent until you save this post. The first "listed" file will be included in RSS feeds.'),
      // Wrapper for fieldset contents (used by ajax.js).
      $form['attachments']['wrapper'] = array(
        '#prefix' => '<div id="attach-wrapper">',
        '#suffix' => '</div>',
      );

      // Make sure necessary directories for upload.module exist and are
      // writable before displaying the attachment form.
      $path = file_directory_path();
      $temp = file_directory_path('temporary');
      // Note: pass by reference
      if (!file_prepare_directory($path, FILE_CREATE_DIRECTORY) || !file_prepare_directory($temp, FILE_CREATE_DIRECTORY)) {
        $form['attachments']['#description'] =  t('File attachments are disabled. The file directories have not been properly configured.');
        if (user_access('administer site configuration')) {
          $form['attachments']['#description'] .= ' ' . t('Please visit the <a href="@admin-file-system">file system configuration page</a>.', array('@admin-file-system' => url('admin/settings/file-system')));
          $form['attachments']['#description'] .= ' ' . t('Please contact the site administrator.');
        }
      }
      else {
        $form['attachments']['wrapper'] += _upload_form($node);
      }
      $form['#submit'][] = 'upload_node_form_submit';
  // Add the upload specific data into the file object.
  $result = db_query('SELECT * FROM {upload} u WHERE u.fid IN (:fids)', array(':fids' => array_keys($files)))->fetchAll(PDO::FETCH_ASSOC);
  foreach ($result as $record) {
    foreach ($record as $key => $value) {
      $files[$record['fid']]->$key = $value;
    }
function upload_file_references($file) {
  // If upload.module is still using a file, do not let other modules delete it.
  $file_used = (bool) db_query_range('SELECT 1 FROM {upload} WHERE fid = :fid', array(':fid' => $file->fid), 0, 1)->fetchField();
  if ($file_used) {
    // Return the name of the module and how many references it has to the file.
    return array('upload' => $count);
  }
}

/**
function upload_file_delete($file) {
  // Delete all information associated with the file.
  db_delete('upload')->condition('fid', $file->fid)->execute();
}

function upload_node_load($nodes, $types) {
  // Collect all the revision ids for nodes with upload enabled.
  $node_vids = array();
  foreach ($nodes as $node) {
    if (variable_get("upload_$node->type", 1) == 1) {
      $node_vids[$node->vid] = $node->vid;
      $node->files = array();
  // If there are no vids then there's no point trying to load files.
  if (empty($node_vids)) {
    return;
  }

  // Fetch the fids associated with these node revisions.
  $result = db_query('SELECT u.fid, u.nid, u.vid FROM {upload} u WHERE u.vid IN (:node_vids) ORDER BY u.weight, u.fid', array(':node_vids' => $node_vids));

  // The same file may be attached to several nodes (e.g. translated nodes) so
  // simply calling db_query()->fetchAllAssoc('fid') would return one node
  // per file. Instead we build one array with the file ids for
  // file_load_multiple() and another array with upload records so we can match
  // files back to the nodes.
  $fids = array();
  $uploads = array();
  foreach ($result as $record) {
    $fids[] = $record->fid;
    $uploads[] = $record;
  }

  $files = file_load_multiple($fids);
  foreach ($uploads as $upload) {
    $nodes[$upload->nid]->files[$upload->fid] = $files[$upload->fid];
  }
function upload_node_view($node, $build_mode) {
  if (user_access('view uploaded files') && $build_mode != 'rss') {
        // Add the attachments list to node body with a heavy weight to ensure
        // they're below other elements.
          '#files' => $node->files,
          '#theme' => 'upload_attachments',
        upload_node_links($node, $build_mode);
    // Add the first file as an enclosure to the RSS item. RSS allows only one
    // enclosure per item. See: http://en.wikipedia.org/wiki/RSS_enclosure
    foreach ($node->files as $file) {
      if ($file->list) {
        break;
      }
    }
    if ($file->list) {
      $node->rss_elements[] = array(
        'key' => 'enclosure',
        'attributes' => array(
          'url' => file_create_url($file->uri),
          'length' => $file->filesize,
          'type' => $file->filemime
        )
      );
    }
function upload_node_insert($node) {
function upload_node_update($node) {
function upload_node_delete($node) {
  db_delete('upload')->condition('nid', $node->nid)->execute();
  if (!is_array($node->files)) {
    return;
  }
function upload_node_delete_revision($node) {
  db_delete('upload')->condition('vid', $node->vid)->execute();
  if (!is_array($node->files)) {
    return;
  }
  foreach ($node->files as $file) {
    file_delete($file);
  }
function upload_node_search_result($node) {
  return isset($node->files) && is_array($node->files) ? format_plural(count($node->files), '1 attachment', '@count attachments') : NULL;
}
function theme_upload_attachments($elements) {
  $header = array(t('Attachment'), t('Size'));
  $rows = array();
  foreach ($elements['#files'] as $file) {
    $file = (object)$file;
    if ($file->list && empty($file->remove)) {
      $href = file_create_url($file->uri);
      $text = $file->description ? $file->description : $file->filename;
      $rows[] = array(l($text, $href), format_size($file->filesize));
    }
  }
  if (count($rows)) {
    return theme('table', $header, $rows, array('class' => 'attachments'));
Dries Buytaert's avatar
Dries Buytaert committed
}

/**
 * Determine how much disk space is occupied by a user's uploaded files.
 *
 * @param $uid
 *   The integer user id of a user.
 * @return
 *   The amount of disk space used by the user in bytes.
Dries Buytaert's avatar
Dries Buytaert committed

/**
 * Determine how much disk space is occupied by uploaded files.
 *
 * @return
 *   The amount of disk space used by uploaded files in bytes.
  return db_query('SELECT SUM(f.filesize) FROM {file} f INNER JOIN {upload} u ON f.fid = u.fid')->fetchField();
function upload_save($node) {
  if (empty($node->files) || !is_array($node->files)) {
  foreach ($node->files as $fid => $file) {
    // Convert file to object for compatibility
    $file = (object)$file;

    // Remove file. Process removals first since no further processing
    // will be required.
    if (!empty($file->remove)) {
      // Remove the reference from this revision.
      db_delete('upload')->condition('fid', $file->fid)->condition('vid', $node->vid)->execute();
      // Try a soft delete, if the file isn't used elsewhere it'll be deleted.
      file_delete($file);
      // Remove it from the session in the case of new uploads,
      // that you want to disassociate before node submission.
      // Move on, so the removed file won't be added to new revisions.
      continue;
    // Create a new revision, or associate a new file needed.
    if (!empty($node->old_vid) || $file->new) {
      db_insert('upload')
        ->fields(array(
          'fid' => $file->fid,
          'nid' => $node->nid,
          'vid' => $node->vid,
          'list' => $file->list,
          'description' => $file->description,
          'weight' => $file->weight,
        ))
        ->execute();
      db_update('upload')
        ->fields(array(
          'list' => $file->list,
          'description' => $file->description,
          'weight' => $file->weight,
        ))
        ->condition('fid', $file->fid, '=')
        ->condition('vid', $node->vid, '=')
        ->execute();
Dries Buytaert's avatar
Dries Buytaert committed
}

function _upload_form($node) {
Gerhard Killesreiter's avatar
Gerhard Killesreiter committed

  $form = array(
    '#theme' => 'upload_form_new',
    '#cache' => TRUE,
  );
  if (!empty($node->files) && is_array($node->files)) {
    $form['files']['#theme'] = 'upload_form_current';
    $form['files']['#tree'] = TRUE;
    foreach ($node->files as $file) {
      $form['files'][$key]['description'] = array('#type' => 'textfield', '#default_value' => !empty($file->description) ? $file->description : $file->filename, '#maxlength' => 256, '#description' => '<small>' . file_create_url($file->uri) . '</small>');
      $form['files'][$key]['size'] = array('#markup' => format_size($file->filesize));
      $form['files'][$key]['remove'] = array('#type' => 'checkbox', '#default_value' => !empty($file->remove));
      $form['files'][$key]['list'] = array('#type' => 'checkbox',  '#default_value' => $file->list);
      $form['files'][$key]['weight'] = array('#type' => 'weight', '#delta' => count($node->files), '#default_value' => $file->weight);
      $form['files'][$key]['filename'] = array('#type' => 'value',  '#value' => $file->filename);
      $form['files'][$key]['uri'] = array('#type' => 'value',  '#value' => $file->uri);
      $form['files'][$key]['filemime'] = array('#type' => 'value',  '#value' => $file->filemime);
      $form['files'][$key]['filesize'] = array('#type' => 'value',  '#value' => $file->filesize);
      $form['files'][$key]['fid'] = array('#type' => 'value',  '#value' => $file->fid);
      $form['files'][$key]['new'] = array('#type' => 'value', '#value' => FALSE);
Dries Buytaert's avatar
Dries Buytaert committed
    }
  }
  if (user_access('upload files')) {
    $limits = _upload_file_limits($user);
    $limit_description = t('The maximum size of file uploads is %filesize.', array('%filesize' => format_size($limits['file_size']))) . ' ';
    if (!empty($limits['resolution'])) {
      if (image_get_toolkit()) {
        $limit_description .= t('Images larger than %resolution will be resized.', array('%resolution' => $limits['resolution'])) . ' ';
        $limit_description .= t('Images may not be larger than %resolution.', array('%resolution' => $limits['resolution'])) . ' ';
    $limit_description .= t('Only files with the following extensions may be uploaded: %extensions.', array('%extensions' => $limits['extensions'])) . ' ';
    $form['new']['upload'] = array(
      '#type' => 'file',
      '#title' => t('Attach new file'),
      '#size' => 40,
      '#description' => $limit_description,
    $form['new']['attach'] = array(
      '#type' => 'submit',
      '#value' => t('Attach'),
      '#name' => 'attach',
        'path' => 'upload/js',
        'wrapper' => 'attach-wrapper',
        'progress' => array('type' => 'bar', 'message' => t('Please wait...')),
      '#submit' => array('node_form_submit_build_node'),
Dries Buytaert's avatar
Dries Buytaert committed

/**
 * Theme the attachments list.
function theme_upload_form_current($form) {
  $header = array('', t('Delete'), t('List'), t('Description'), t('Weight'), t('Size'));
  drupal_add_tabledrag('upload-attachments', 'order', 'sibling', 'upload-weight');

  foreach (element_children($form) as $key) {
    // Add class to group weight fields for drag and drop.
    $form[$key]['weight']['#attributes']['class'] = 'upload-weight';

    $row = array('');
    $row[] = drupal_render($form[$key]['remove']);
    $row[] = drupal_render($form[$key]['list']);
    $row[] = drupal_render($form[$key]['description']);
    $row[] = drupal_render($form[$key]['size']);
    $rows[] = array('data' => $row, 'class' => 'draggable');
  $output = theme('table', $header, $rows, array('id' => 'upload-attachments'));
  $output .= drupal_render_children($form);
Dries Buytaert's avatar
Dries Buytaert committed
}

/**
 * Theme the attachment form.
 * Note: required to output prefix/suffix.
 */
function theme_upload_form_new($form) {
  drupal_add_tabledrag('upload-attachments', 'order', 'sibling', 'upload-weight');
  $output = drupal_render_children($form);
/**
 * Menu-callback for JavaScript-based uploads.
 */
function upload_js() {
  $cached_form_state = array();
  $files = array();

  if (!($cached_form = form_get_cache($_POST['form_build_id'], $cached_form_state)) || !isset($cached_form['#node']) || !isset($cached_form['attachments'])) {
    form_set_error('form_token', t('Validation error, please try again. If this error persists, please contact the site administrator.'));
    $output = theme('status_messages');
    print drupal_to_js(array('status' => TRUE, 'data' => $output));
    exit();
  }
  // Handle new uploads, and merge tmp files into node-files.
  upload_node_form_submit($cached_form, $form_state);

  if (!empty($form_state['values']['files'])) {
    foreach ($form_state['values']['files'] as $fid => $file) {
      if (isset($cached_form['#node']->files[$fid])) {
        $files[$fid] = $cached_form['#node']->files[$fid];

  $node = $cached_form['#node'];

  $node->files = $files;

  $form = _upload_form($node);
  unset($cached_form['attachments']['wrapper']['new']);
  $cached_form['attachments']['wrapper'] = array_merge($cached_form['attachments']['wrapper'], $form);

  $cached_form['attachments']['#collapsed'] = FALSE;

  form_set_cache($_POST['form_build_id'], $cached_form, $cached_form_state);

  foreach ($files as $fid => $file) {
    if (is_numeric($fid)) {
      $form['files'][$fid]['description']['#default_value'] = $form_state['values']['files'][$fid]['description'];
      $form['files'][$fid]['list']['#default_value'] = !empty($form_state['values']['files'][$fid]['list']);
      $form['files'][$fid]['remove']['#default_value'] = !empty($form_state['values']['files'][$fid]['remove']);
      $form['files'][$fid]['weight']['#default_value'] = $form_state['values']['files'][$fid]['weight'];
  $form += array(
    '#tree' => FALSE,
    '#parents' => array(),
  );
  drupal_alter('form', $form, array(), 'upload_js');
  $form_state = array('submitted' => FALSE, 'programmed' => FALSE);
  $form = form_builder('upload_js', $form, $form_state);
  $output = theme('status_messages') . drupal_render($form);
  $commands = array();
  $commands[] = ajax_command_replace(NULL, $output);

  // AJAX uploads use an <iframe> and some browsers have problems with the
  // 'text/javascript' Content-Type header with iframes. Passing FALSE to
  // ajax_render() prevents the header from being sent.
  ajax_render($commands, FALSE);