upload.module

<?php
// $Id: upload.module,v 1.31.2.11 2006/06/01 21:55:58 killes Exp $

/**
 * @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;
}

?>
 
 

All source code and documentation on this site is released under the terms of the GNU General Public License, version 2 and later. Drupal is a registered trademark of Dries Buytaert.