Same filename and directory in other branches
  1. 4.7.x modules/upload.module

File-handling and attaching files to nodes.

File

modules/upload.module
View source
<?php

/**
 * @file
 * File-handling and attaching files to nodes.
 */

/**
 * Implementation of hook_help().
 */
function upload_help($section) {
  switch ($section) {
    case 'admin/modules#description':
      return t('Allows users to upload and attach files to content.');
    case 'admin/settings/upload':
      return t('<p>Users with the <a href="%permissions">upload files permission</a> can upload attachments. You can choose which post types can take attachments on the <a href="%types">content types settings</a> page.</p>', array(
        '%permissions' => url('admin/access'),
        '%types' => url('admin/node/configure/types'),
      ));
  }
}

/**
 * Implementation of hook_perm().
 */
function upload_perm() {
  return array(
    'upload files',
    'view uploaded files',
  );
}

/**
 * Implementation of hook_link().
 */
function upload_link($type, $node = 0, $main = 0) {
  $links = array();

  // Display a link with the number of attachments
  if ($main && $type == 'node' && $node->files && user_access('view uploaded files')) {
    $num_files = 0;
    foreach ($node->files as $file) {
      if ($file->list) {
        $num_files++;
      }
    }
    if ($num_files) {
      $links[] = l(format_plural($num_files, '1 attachment', '%count attachments'), "node/{$node->nid}", array(
        'title' => t('Read full article to view attachments.'),
      ), NULL, 'attachments');
    }
  }
  return $links;
}

/**
 * Implementation of hook_menu().
 */
function upload_menu($may_cache) {
  $items = array();
  if ($may_cache) {
    $items[] = array(
      'path' => 'admin/settings/upload',
      'title' => t('uploads'),
      'callback' => 'upload_admin',
      'access' => user_access('administer site configuration'),
      'type' => MENU_NORMAL_ITEM,
    );
  }
  else {

    // Add handlers for previewing new uploads.
    if ($_SESSION['file_uploads']) {
      foreach ($_SESSION['file_uploads'] as $key => $file) {
        $filename = file_create_filename($file->filename, file_create_path());
        $items[] = array(
          'path' => $filename,
          'title' => t('file download'),
          'callback' => 'upload_download',
          'access' => user_access('view uploaded files'),
          'type' => MENU_CALLBACK,
        );
        $_SESSION['file_uploads'][$key]->_filename = $filename;
      }
    }
  }
  return $items;
}
function upload_admin() {
  system_settings_save();
  $group .= form_textfield(t('Maximum total file size'), 'upload_maxsize_total', variable_get('upload_maxsize_total', 0), 10, 10, t('The maximum size of a file a user can upload in megabytes. Enter 0 for unlimited.'));
  $group .= form_textfield(t('Maximum resolution for uploaded images'), 'upload_max_resolution', variable_get('upload_max_resolution', 0), 10, 10, t('The maximum allowed image size expressed as WIDTHxHEIGHT (e.g. 640x480). Set to 0 for no restriction.'));
  $output = form_group(t('General settings'), $group);
  $roles = user_roles(0, 'upload files');
  foreach ($roles as $rid => $role) {
    $group = form_textfield(t('Permitted file extensions'), "upload_extensions_{$rid}", variable_get("upload_extensions_{$rid}", "jpg jpeg gif png txt html doc xls pdf ppt pps"), 60, 255, t('Extensions that users in this role can upload. Separate extensions with a space and do not include the leading dot.'));
    $group .= form_textfield(t('Maximum file size per upload'), "upload_uploadsize_{$rid}", variable_get("upload_uploadsize_{$rid}", 1), 5, 5, t('The maximum size of a file a user can upload (in megabytes).'));
    $group .= form_textfield(t('Total file size per user'), "upload_usersize_{$rid}", variable_get("upload_usersize_{$rid}", 10), 5, 5, t('The maximum size of all files a user can have on the site (in megabytes).'));
    $output .= form_group(t('Settings for %role', array(
      '%role' => theme('placeholder', $role),
    )), $group);
  }
  print theme('page', system_settings_form($output));
}
function upload_download() {
  foreach ($_SESSION['file_uploads'] as $file) {
    if ($file->_filename == $_GET['q']) {
      file_transfer($file->filepath, array(
        'Content-Type: ' . mime_header_encode($file->filemime),
        'Content-Length: ' . $file->filesize,
      ));
    }
  }
}
function upload_file_download($file) {
  if (user_access('view uploaded files')) {
    $file = file_create_path($file);
    $result = db_query("SELECT f.nid, f.* FROM {files} f WHERE filepath = '%s'", $file);
    if ($file = db_fetch_object($result)) {
      $node = node_load(array(
        'nid' => $file->nid,
      ));
      if (node_access('view', $node)) {
        $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,
        );
      }
    }
  }
}

/**
 * Implementation of hook_nodeapi().
 */
function upload_nodeapi(&$node, $op, $arg) {
  switch ($op) {
    case 'settings':
      return form_radios(t('Attachments'), 'upload_' . $node->type, variable_get('upload_' . $node->type, 1), array(
        t('Disabled'),
        t('Enabled'),
      ));
    case 'form param':
      if (variable_get("upload_{$node->type}", 1) && user_access('upload files')) {
        $output['options'] = array(
          'enctype' => 'multipart/form-data',
        );
      }
      break;
    case 'validate':
      $node->files = upload_load($node);

      // Double check existing files:
      if (is_array($node->list)) {
        foreach ($node->list as $key => $value) {
          if ($file = file_check_upload($key)) {
            $node->files[$file->source] = $file;
            $node->files[$key]->list = $node->list[$key];
            $node->files[$key]->remove = $node->remove[$key];
            if ($file->source) {
              $filesize += $file->filesize;
            }
          }
        }
      }
      else {
        foreach ($node->files as $key => $file) {
          $node->list[$key] = $file->list;
        }
      }
      if (($file = file_check_upload('upload')) && user_access('upload files')) {
        global $user;
        $file = _upload_image($file);
        $maxsize = variable_get("upload_maxsize_total", 0) * 1024 * 1024;
        $total_size = upload_count_size() + $filesize;
        $total_usersize = upload_count_size($user->uid) + $filesize;
        if ($maxsize && $total_size > $maxsize) {
          form_set_error('upload', t('Error attaching file %name: total file size exceeded', array(
            '%name' => theme('placeholder', $file->filename),
          )));
          break;
        }

        // Don't do any checks for uid #1.
        if ($user->uid != 1) {

          // here, something.php.pps becomes something.php_.pps
          $munged_filename = upload_munge_filename($file->filename, NULL, 0);
          $file->filename = $munged_filename;
          $file->description = $munged_filename;

          // Validate file against all users roles. Only denies an upload when
          // all roles prevent it.
          foreach ($user->roles as $rid => $name) {
            $extensions = variable_get("upload_extensions_{$rid}", 'jpg jpeg gif png txt html doc xls pdf ppt pps');
            $uploadsize = variable_get("upload_uploadsize_{$rid}", 1) * 1024 * 1024;
            $usersize = variable_get("upload_usersize_{$rid}", 1) * 1024 * 1024;
            $regex = '/\\.(' . ereg_replace(' +', '|', preg_quote($extensions)) . ')$/i';
            if (!preg_match($regex, $file->filename)) {
              $error['extension']++;
            }
            if ($file->filesize > $uploadsize) {
              $error['uploadsize']++;
            }
            if ($total_usersize + $file->filesize > $usersize) {
              $error['usersize']++;
            }
          }
        }
        if ($error['extension'] == count($user->roles) && $user->uid != 1) {
          form_set_error('upload', t('Error attaching file %name: invalid extension', array(
            '%name' => theme('placeholder', $file->filename),
          )));
        }
        elseif ($error['uploadsize'] == count($user->roles) && $user->uid != 1) {
          form_set_error('upload', t('Error attaching file %name: exceeds maximum file size', array(
            '%name' => theme('placeholder', $file->filename),
          )));
        }
        elseif ($error['usersize'] == count($user->roles) && $user->uid != 1) {
          form_set_error('upload', t('Error attaching file %name: exceeds maximum file size', array(
            '%name' => theme('placeholder', $file->filename),
          )));
        }
        elseif (strlen($munged_filename) > 255) {
          form_set_error('upload', t('The selected file %name can not be attached to this post, because the filename is to long.', array(
            '%name' => theme('placeholder', $munged_filename),
          )));
        }
        else {
          $key = 'upload_' . count($_SESSION['file_uploads']);
          $file->source = $key;
          $file->list = 1;
          $file = file_save_upload($file);
          $node->files[$key] = $file;
        }
      }
      break;
    case 'form post':
      if (variable_get("upload_{$node->type}", 1) == 1 && user_access('upload files')) {
        $output = upload_form($node);
      }
      break;
    case 'load':
      if (variable_get("upload_{$node->type}", 1) == 1) {
        $output['files'] = upload_load($node);
      }
      break;
    case 'view':
      if ($node->files && user_access('view uploaded files')) {
        $header = array(
          t('Attachment'),
          t('Size'),
        );
        $rows = array();
        $previews = array();

        // Build list of attached files
        foreach ($node->files as $file) {
          if ($file->list) {
            $rows[] = array(
              '<a href="' . check_url($file->fid ? file_create_url($file->filepath) : url(file_create_filename($file->filename, file_create_path()))) . '">' . check_plain($file->filename) . '</a>',
              format_size($file->filesize),
            );

            // We save the list of files still in preview for later
            if (!$file->fid) {
              $previews[] = $file;
            }
          }
        }

        // URLs to files being previewed are actually Drupal paths. When Clean
        // URLs are disabled, the two do not match. We perform an automatic
        // replacement from temporary to permanent URLs. That way, the author
        // can use the final URL in the body before having actually saved (to
        // place inline images for example).
        if (!variable_get('clean_url', 0)) {
          foreach ($previews as $file) {
            $old = file_create_filename($file->filename, file_create_path());
            $new = url($old);
            $node->body = str_replace($old, $new, $node->body);
            $node->teaser = str_replace($old, $new, $node->teaser);
          }
        }
        $teaser = $arg;

        // Add the attachments list
        if (count($rows) && !$teaser) {
          $node->body .= theme('table', $header, $rows, array(
            'id' => 'attachments',
          ));
        }
      }
      break;
    case 'insert':
    case 'update':
      if (user_access('upload files')) {
        upload_save($node);
      }
      break;
    case 'delete':
      upload_delete($node);
      break;
    case 'search result':
      return $node->files ? format_plural(count($node->files), '1 attachment', '%count attachments') : null;
    case 'rss item':
      if ($node->files) {
        $files = array();
        foreach ($node->files as $file) {
          if ($file->list) {
            $files[] = $file;
          }
        }
        if (count($files) > 0) {

          // RSS only allows one enclosure per item
          $file = array_shift($files);
          return array(
            array(
              'key' => 'enclosure',
              'attributes' => array(
                'url' => file_create_url($file->filepath),
                'length' => $file->filesize,
                'type' => $file->filemime,
              ),
            ),
          );
        }
      }
      break;
  }
  return $output;
}
function upload_count_size($uid = 0) {
  if ($uid) {
    $result = db_query("SELECT SUM(f.filesize) FROM {files} f INNER JOIN {node} n ON f.nid = n.nid WHERE uid = %d", $uid);
  }
  else {
    $result = db_query("SELECT SUM(f.filesize) FROM {files} f INNER JOIN {node} n ON f.nid = n.nid");
  }
  return db_result($result);
}

/**
 * Munge the filename as needed for security purposes.
 *
 * @param $filename
 *   The name of a file to modify.
 * @param $extensions
 *   A space separated list of valid extensions. If this is blank, we'll use
 *   the admin-defined defaults for the user role from upload_extensions_$rid.
 * @param $alerts
 *   Whether alerts (watchdog, drupal_set_message()) should be displayed.
 * @return $filename
 *   The potentially modified $filename.
 */
function upload_munge_filename($filename, $extensions = NULL, $alerts = 1) {
  $original = $filename;

  // Allow potentially insecure uploads for very savvy users
  if (!variable_get('allow_insecure_uploads', 0)) {
    global $user;
    if (!isset($extensions)) {
      $extensions = '';
      foreach ($user->roles as $rid => $name) {
        $extensions .= ' ' . variable_get("upload_extensions_{$rid}", variable_get('upload_extensions_default', 'jpg jpeg gif png txt html doc xls pdf ppt pps'));
      }
    }
    $whitelist = array_unique(explode(' ', trim($extensions)));
    $filename_parts = explode('.', $filename);
    $new_filename = array_shift($filename_parts);

    // Remove file basename.
    $final_extension = array_pop($filename_parts);

    // Remove final extension.
    foreach ($filename_parts as $filename_part) {
      $new_filename .= '.' . $filename_part;

      // if the extension is not allowed and contains 2-5 characters followed
      // by an optional digit, then it's potentially harmful, so we add an _
      if (!in_array($filename_part, $whitelist) && preg_match("/^[a-zA-Z]{2,5}\\d?\$/", $filename_part)) {
        $new_filename .= '_';
      }
    }
    $filename = "{$new_filename}.{$final_extension}";
  }
  if ($alerts && $original != $filename) {
    $message = t('Your filename has been renamed to conform to site policy.');
    drupal_set_message($message);
  }
  return $filename;
}

/**
 * Undo the effect of upload_munge_filename().
 */
function upload_unmunge_filename($filename) {
  return str_replace('_.', '.', $filename);
}
function upload_save($node) {
  foreach ((array) $node->files as $key => $file) {
    if ($file->source && !$file->remove) {

      // Clean up the session:
      unset($_SESSION['file_uploads'][$file->source]);

      // Insert new files:
      if ($file = file_save_upload($file, $file->filename)) {
        $fid = db_next_id('{files}_fid');
        db_query("INSERT INTO {files} (fid, nid, filename, filepath, filemime, filesize, list) VALUES (%d, %d, '%s', '%s', '%s', %d, %d)", $fid, $node->nid, $file->filename, $file->filepath, $file->filemime, $file->filesize, $node->list[$key]);
      }
    }
    else {

      // Remove or update existing files:
      if ($node->remove[$key]) {
        file_delete($file->filepath);
        db_query("DELETE FROM {files} WHERE fid = %d", $key);
      }
      if ($file->list != $node->list[$key]) {
        db_query("UPDATE {files} SET list = %d WHERE fid = %d", $node->list[$key], $key);
      }
    }
  }
  return;
}
function upload_delete($node) {
  $node->files = upload_load($node);
  foreach ($node->files as $file) {
    file_delete($file->filepath);
  }
  db_query("DELETE FROM {files} WHERE nid = %d", $node->nid);
}
function upload_form($node) {
  $header = array(
    t('Delete'),
    t('List'),
    t('Url'),
    t('Size'),
  );
  $rows = array();
  if (is_array($node->files)) {
    foreach ($node->files as $key => $file) {
      $rows[] = array(
        form_checkbox('', "remove][{$key}", 1, $file->remove),
        form_checkbox('', "list][{$key}", 1, $file->list),
        $file->filename . "<br /><small>" . file_create_url($file->fid ? $file->filepath : file_create_filename($file->filename, file_create_path())) . "</small>",
        format_size($file->filesize),
      );
    }
  }
  if (count($node->files)) {
    $output = theme('table', $header, $rows);
  }
  if (user_access('upload files')) {
    $output .= form_file(t('Attach new file'), "upload", 40);
    $output .= form_button(t('Attach'), 'fileop');
  }
  return '<div class="attachments">' . form_group(t('Attachments'), $output, t('Changes made to the attachments are not permanent until you save this post.  The first "listed" file will be included in RSS feeds.')) . '</div>';
}
function upload_load($node) {
  $files = array();
  if ($node->nid) {
    $result = db_query("SELECT * FROM {files} WHERE nid = %d", $node->nid);
    while ($file = db_fetch_object($result)) {
      $files[$file->fid] = $file;
    }
  }
  return $files;
}

/**
 * Check an upload, if it is an image, make sure it fits within the
 * maximum dimensions allowed.
 */
function _upload_image($file) {
  $info = image_get_info($file->filepath);
  if ($info) {
    list($width, $height) = explode('x', variable_get('upload_max_resolution', 0));
    if ($width && $height) {
      $result = image_scale($file->filepath, $file->filepath, $width, $height);
      if ($result) {
        $file->filesize = filesize($file->filepath);
        drupal_set_message(t('Your image was resized to fit within the maximum allowed resolution of %resolution pixels.', array(
          '%resolution' => variable_get('upload_max_resolution', 0),
        )));
      }
    }
  }
  return $file;
}

Functions

Namesort descending Description
upload_admin
upload_count_size
upload_delete
upload_download
upload_file_download
upload_form
upload_help Implementation of hook_help().
upload_link Implementation of hook_link().
upload_load
upload_menu Implementation of hook_menu().
upload_munge_filename Munge the filename as needed for security purposes.
upload_nodeapi Implementation of hook_nodeapi().
upload_perm Implementation of hook_perm().
upload_save
upload_unmunge_filename Undo the effect of upload_munge_filename().
_upload_image Check an upload, if it is an image, make sure it fits within the maximum dimensions allowed.