? .cache
? .project
? .projectOptions
? files
? includes/_file.inc_
? misc/Thumbs.db
? misc/farbtastic/Thumbs.db
? modules/gd
? modules/imagemagick
? sites/all/modules
? sites/default/settings.php
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.668
diff -u -r1.668 common.inc
--- includes/common.inc	1 Jul 2007 17:41:14 -0000	1.668
+++ includes/common.inc	1 Jul 2007 21:42:21 -0000
@@ -1629,7 +1629,7 @@
       >x', '\1', $data);
 
     // Create the CSS file.
-    file_save_data($data, $csspath .'/'. $filename, FILE_EXISTS_REPLACE);
+    _file_save_data($data, $csspath .'/'. $filename, FILE_EXISTS_REPLACE);
   }
   return $csspath .'/'. $filename;
 }
@@ -1638,7 +1638,7 @@
  * Delete all cached CSS files.
  */
 function drupal_clear_css_cache() {
-  file_scan_directory(file_create_path('css'), '.*', array('.', '..', 'CVS'), 'file_delete', TRUE);
+  file_scan_directory(file_create_path('css'), '.*', array('.', '..', 'CVS'), '_file_delete', TRUE);
 }
 
 /**
@@ -1845,7 +1845,7 @@
     }
 
     // Create the JS file.
-    file_save_data($contents, $jspath .'/'. $filename, FILE_EXISTS_REPLACE);
+    _file_save_data($contents, $jspath .'/'. $filename, FILE_EXISTS_REPLACE);
   }
 
   return $jspath .'/'. $filename;
@@ -2079,7 +2079,7 @@
  * Delete all cached JS files.
  */
 function drupal_clear_js_cache() {
-  file_scan_directory(file_create_path('js'), '.*', array('.', '..', 'CVS'), 'file_delete', TRUE);
+  file_scan_directory(file_create_path('js'), '.*', array('.', '..', 'CVS'), '_file_delete', TRUE);
   variable_set('javascript_parsed', '');
 }
 
Index: includes/file.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/file.inc,v
retrieving revision 1.101
diff -u -r1.101 file.inc
--- includes/file.inc	5 Jun 2007 12:13:20 -0000	1.101
+++ includes/file.inc	1 Jul 2007 21:42:21 -0000
@@ -35,8 +35,10 @@
 /**
  * Create the download path to a file.
  *
- * @param $path A string containing the path of the file to generate URL for.
- * @return A string containing a URL that can be used to download the file.
+ * @param $path
+ *   A string containing the path of the file to generate URL for.
+ * @return
+ *   A string containing a URL that can be used to download the file.
  */
 function file_create_url($path) {
   // Strip file_directory_path from $path. We only include relative paths in urls.
@@ -55,10 +57,12 @@
  * Make sure the destination is a complete path and resides in the file system
  * directory, if it is not prepend the file system directory.
  *
- * @param $dest A string containing the path to verify. If this value is
- *   omitted, Drupal's 'files' directory will be used.
- * @return A string containing the path to file, with file system directory
- *   appended if necessary, or FALSE if the path is invalid (i.e. outside the
+ * @param $dest 
+ *   A string containing the path to verify. If this value is omitted, Drupal's 
+ *   'files' directory will be used.
+ * @return
+ *   A string containing the path to file, with file system directory appended 
+ *   if necessary, or FALSE if the path is invalid (i.e. outside the
  *   configured 'files' or temp directories).
  */
 function file_create_path($dest = 0) {
@@ -66,11 +70,13 @@
   if (!$dest) {
     return $file_path;
   }
-  // file_check_location() checks whether the destination is inside the Drupal files directory.
+  // file_check_location() checks whether the destination is inside the Drupal 
+  // files directory.
   if (file_check_location($dest, $file_path)) {
     return $dest;
   }
-  // check if the destination is instead inside the Drupal temporary files directory.
+  // Check if the destination is instead inside the Drupal temporary files 
+  // directory.
   else if (file_check_location($dest, file_directory_temp())) {
     return $dest;
   }
@@ -86,14 +92,18 @@
  * Check that the directory exists and is writable. Directories need to
  * have execute permissions to be considered a directory by FTP servers, etc.
  *
- * @param $directory A string containing the name of a directory path.
- * @param $mode A Boolean value to indicate if the directory should be created
- *   if it does not exist or made writable if it is read-only.
- * @param $form_item An optional string containing the name of a form item that
- *   any errors will be attached to. This is useful for settings forms that
- *   require the user to specify a writable directory. If it can't be made to
- *   work, a form error will be set preventing them from saving the settings.
- * @return FALSE when directory not found, or TRUE when directory exists.
+ * @param $directory
+ *   A string containing the name of a directory path.
+ * @param $mode
+ *   A Boolean value to indicate if the directory should be created if it does 
+ *   not exist or made writable if it is read-only.
+ * @param $form_item 
+ *   An optional string containing the name of a form item that any errors will 
+ *   be attached to. This is useful for settings forms that require the user to 
+ *   specify a writable directory. If it can't be made to work, a form error 
+ *   will be set preventing them from saving the settings.
+ * @return 
+ *   FALSE when directory not found, or TRUE when directory exists.
  */
 function file_check_directory(&$directory, $mode = 0, $form_item = NULL) {
   $directory = rtrim($directory, '/\\');
@@ -143,10 +153,11 @@
 /**
  * Checks path to see if it is a directory, or a dir/file.
  *
- * @param $path A string containing a file path. This will be set to the
- *   directory's path.
- * @return If the directory is not in a Drupal writable directory, FALSE is
- *   returned. Otherwise, the base name of the path is returned.
+ * @param $path 
+ *   A string containing a file path. This will be set to the directory's path.
+ * @return 
+ *   If the directory is not in a Drupal writable directory, FALSE is returned. 
+ *   Otherwise, the base name of the path is returned.
  */
 function file_check_path(&$path) {
   // Check if path is a directory.
@@ -174,9 +185,12 @@
  *   file_check_location('/www/example.com/files/../../../etc/passwd', '/www/example.com/files');
  * @endcode
  *
- * @param $source A string set to the file to check.
- * @param $directory A string where the file should be located.
- * @return 0 for invalid path or the real path of the source.
+ * @param $source
+ *   A string set to the file to check.
+ * @param $directory
+ *   A string where the file should be located.
+ * @return
+ *   0 for invalid path or the real path of the source.
  */
 function file_check_location($source, $directory = '') {
   $check = realpath($source);
@@ -195,25 +209,71 @@
 }
 
 /**
- * Copies a file to a new location. This is a powerful function that in many ways
- * performs like an advanced version of copy().
+ * Copies a file to a new location and adds a record for the new file to the
+ * database. This is a powerful function that in many ways performs like an
+ * advanced version of copy(). 
  * - Checks if $source and $dest are valid and readable/writable.
  * - Performs a file copy if $source is not equal to $dest.
  * - If file already exists in $dest either the call will error out, replace the
  *   file or rename the file based on the $replace parameter.
+ * - Adds thew new file to the files database. If the source file is a 
+ *   temporary file, the resulting file will also be a temporary file.
  *
- * @param $source A string specifying the file location of the original file.
- *   This parameter will contain the resulting destination filename in case of
- *   success.
- * @param $dest A string containing the directory $source should be copied to.
- *   If this value is omitted, Drupal's 'files' directory will be used.
- * @param $replace Replace behavior when the destination file already exists.
+ * @param $source
+ *   A file object location of the original file.
+ * @param $dest 
+ *   A string containing the directory $source should be copied to. If this 
+ *   value is omitted, Drupal's 'files' directory will be used.
+ * @param $replace
+ *   Replace behavior when the destination file already exists.
  *   - FILE_EXISTS_REPLACE - Replace the existing file
  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique
  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
- * @return True for success, FALSE for failure.
+ * @return
+ *   File object if the copy is successful, or FALSE in the event of an error.
  */
-function file_copy(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) {
+function file_copy($source, $dest = 0, $replace = FILE_EXISTS_RENAME) {
+  if ($result = _file_copy($source->filepath, $dest, $replace)) {
+    $file = drupal_clone($source);
+    $file->fid       = NULL;
+    $file->filename  = basename($result);
+    $file->filepath  = $result;
+    if ($file = file_save($file)) {
+      module_invoke_all('file', 'copy', $file, $source);
+      return $file;
+    }
+  }
+  return FALSE; 
+}
+
+/**
+ * Copies a file to a new location. This is a powerful function that in many 
+ * ways performs like an advanced version of copy().
+ * - Checks if $source and $dest are valid and readable/writable.
+ * - Performs a file copy if $source is not equal to $dest.
+ * - If file already exists in $dest either the call will error out, replace the
+ *   file or rename the file based on the $replace parameter.
+ *
+ * @param $source
+ *   A string specifying the file location of the original file.
+ * @param $dest
+ *   A string containing the directory $source should be copied to. If this 
+ *   value is omitted, Drupal's 'files' directory will be used.
+ * @param $replace
+ *   Replace behavior when the destination file already exists.
+ *   - FILE_EXISTS_REPLACE - Replace the existing file
+ *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique
+ *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
+ * @return
+ *   The path to the new file, or FALSE in the event of an error.
+ */
+function _file_copy($source, $dest = 0, $replace = FILE_EXISTS_RENAME) {
+  $source = realpath($source);
+  if (!file_exists($source)) {
+    drupal_set_message(t('The selected file %file could not be copied, because no file by that name exists. Please check that you supplied the correct filename.', array('%file' => $source)), 'error');
+    return FALSE;
+  }
+  
   $dest = file_create_path($dest);
 
   $directory = $dest;
@@ -221,25 +281,8 @@
 
   // Make sure we at least have a valid directory.
   if ($basename === FALSE) {
-    $source = is_object($source) ? $source->filepath : $source;
-    drupal_set_message(t('The selected file %file could not be uploaded, because the destination %directory is not properly configured.', array('%file' => $source, '%directory' => $dest)), 'error');
-    watchdog('file system', 'The selected file %file could not be uploaded, because the destination %directory could not be found, or because its permissions do not allow the file to be written.', array('%file' => $source, '%directory' => $dest), WATCHDOG_ERROR);
-    return 0;
-  }
-
-  // Process a file upload object.
-  if (is_object($source)) {
-    $file = $source;
-    $source = $file->filepath;
-    if (!$basename) {
-      $basename = $file->filename;
-    }
-  }
-
-  $source = realpath($source);
-  if (!file_exists($source)) {
-    drupal_set_message(t('The selected file %file could not be copied, because no file by that name exists. Please check that you supplied the correct filename.', array('%file' => $source)), 'error');
-    return 0;
+    drupal_set_message(t('The selected file %file could not be copied, because the destination %directory is not properly configured.', array('%file' => $source, '%directory' => $dest)), 'error');
+    return FALSE;
   }
 
   // If the destination file is not specified then use the filename of the source file.
@@ -257,7 +300,7 @@
 
     if (!@copy($source, $dest)) {
       drupal_set_message(t('The selected file %file could not be copied.', array('%file' => $source)), 'error');
-      return 0;
+      return FALSE;
     }
 
     // Give everyone read access so that FTP'd users or
@@ -267,29 +310,23 @@
     @chmod($dest, 0664);
   }
 
-  if (isset($file) && is_object($file)) {
-    $file->filename = $basename;
-    $file->filepath = $dest;
-    $source = $file;
-  }
-  else {
-    $source = $dest;
-  }
-
-  return 1; // Everything went ok.
+  return $dest;
 }
 
 /**
  * Determines the destination path for a file depending on how replacement of
  * existing files should be handled.
  *
- * @param $destination A string specifying the desired path.
- * @param $replace Replace behavior when the destination file already exists.
+ * @param $destination
+ *   A string specifying the desired path.
+ * @param $replace
+ *   Replace behavior when the destination file already exists.
  *   - FILE_EXISTS_REPLACE - Replace the existing file
  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
  *     unique
  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
- * @return The destination file path or FALSE if the file already exists and
+ * @return
+ *   The destination file path or FALSE if the file already exists and 
  *   FILE_EXISTS_ERROR was specified.
  */
 function file_destination($destination, $replace) {
@@ -310,47 +347,56 @@
 }
 
 /**
- * Moves a file to a new location.
+ * Moves a file to a new location and updates the file's database entry. The 
+ * move is performed by copying the file to the new location and the deleting
+ * the original. 
  * - Checks if $source and $dest are valid and readable/writable.
  * - Performs a file move if $source is not equal to $dest.
  * - If file already exists in $dest either the call will error out, replace the
  *   file or rename the file based on the $replace parameter.
+ * - Adds the new file to the files database.
  *
- * @param $source A string specifying the file location of the original file.
- *   This parameter will contain the resulting destination filename in case of
- *   success.
- * @param $dest A string containing the directory $source should be copied to.
- *   If this value is omitted, Drupal's 'files' directory will be used.
- * @param $replace Replace behavior when the destination file already exists.
+ * @param $source
+ *   A file object for the original file.
+ * @param $dest
+ *   A string containing the directory $source should be copied to. If this 
+ *   value is omitted, Drupal's 'files' directory will be used.
+ * @param $replace
+ *   Replace behavior when the destination file already exists.
  *   - FILE_EXISTS_REPLACE - Replace the existing file
  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique
  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
- * @return True for success, FALSE for failure.
+ * @return
+ *   Resulting file object for success, or FALSE in the event of an error.
  */
-function file_move(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) {
-  $path_original = is_object($source) ? $source->filepath : $source;
-
-  if (file_copy($source, $dest, $replace)) {
-    $path_current = is_object($source) ? $source->filepath : $source;
-
-    if ($path_original == $path_current || file_delete($path_original)) {
-      return 1;
+function file_move($source, $dest = 0, $replace = FILE_EXISTS_RENAME) {
+  if ($result = _file_copy($source->filepath, $dest, $replace)) {
+    if ($source->filepath == $result || _file_delete($source->filepath)) {
+      $file = drupal_clone($source);
+      $file->filename = basename($result);
+      $file->filepath = $result;
+      if ($file = file_save($file)) {
+        module_invoke_all('file', 'move', $file, $source);
+        return $file;
+      }
     }
-    drupal_set_message(t('The removal of the original file %file has failed.', array('%file' => $path_original)), 'error');
+    drupal_set_message(t('The removal of the original file %file has failed.', array('%file' => $source->filepath)), 'error');
   }
-  return 0;
+  return FALSE;
 }
 
 /**
  * Munge the filename as needed for security purposes. For instance the file
  * name "exploit.php.pps" would become "exploit.php_.pps".
  *
- * @param $filename The name of a file to modify.
- * @param $extensions A space separated list of extensions that should not
- *   be altered.
- * @param $alerts Whether alerts (watchdog, drupal_set_message()) should be
- *   displayed.
- * @return $filename The potentially modified $filename.
+ * @param $filename
+ *   The name of a file to modify.
+ * @param $extensions
+ *   A space separated list of extensions that should not be altered.
+ * @param $alerts 
+ *   Whether alerts (watchdog, drupal_set_message()) should be displayed.
+ * @return $filename
+ *   The potentially modified $filename.
  */
 function file_munge_filename($filename, $extensions, $alerts = TRUE) {
   $original = $filename;
@@ -387,7 +433,8 @@
 /**
  * Undo the effect of upload_munge_filename().
  *
- * @param $filename string filename
+ * @param $filename
+ *   A filename string.
  * @return string
  */
 function file_unmunge_filename($filename) {
@@ -398,8 +445,10 @@
  * Create a full file path from a directory and filename. If a file with the
  * specified name already exists, an alternative will be used.
  *
- * @param $basename string filename
- * @param $directory string directory
+ * @param $basename
+ *   A filename string.
+ * @param $directory 
+ *   A directory string.
  * @return
  */
 function file_create_filename($basename, $directory) {
@@ -425,12 +474,34 @@
 }
 
 /**
+ * Delete a file and its database record.
+ *
+ * @param $path 
+ *   A file object.
+ * @return mixed 
+ *   TRUE for success, or FALSE in the event of an error.
+ */
+function file_delete($file) {
+  // If any module returns a value from the delete hook, block the 
+  // deletion of the file. 
+  module_invoke_all('file', 'delete', $file);
+  
+  if (_file_delete($file->filepath)) {
+    db_query('DELETE FROM {files} WHERE fid = %d', $file->fid);
+    return TRUE;
+  }
+  return FALSE;
+}
+
+/**
  * Delete a file.
  *
- * @param $path A string containing a file path.
- * @return TRUE for success, FALSE for failure.
+ * @param $path
+ *   A string containing a file path.
+ * @return
+ *   TRUE for success, or FALSE in the event of an error.
  */
-function file_delete($path) {
+function _file_delete($path) {
   if (is_file($path)) {
     return unlink($path);
   }
@@ -440,12 +511,13 @@
  * Determine the total amount of disk space used by a single user's files, or
  * the filesystem as a whole.
  *
- * @param $uid An optional, user id. A NULL value returns the total space used
- *   by all files.
- */
-function file_space_used($uid = NULL) {
-  if (is_null($uid)) {
-    return db_result(db_query('SELECT SUM(filesize) FROM {files} WHERE uid = %d', $uid));
+ * @param $uid
+ *   An optional, user id. A NULL value returns the total space used by all 
+ *   non-temporary files.
+ */
+function file_space_used($uid = NULL, $status = NULL) {
+  if (isset($uid)) {
+    return db_result(db_query('SELECT SUM(filesize) FROM {files} WHERE uid = %d AND status > 0', $uid));
   }
   return db_result(db_query('SELECT SUM(filesize) FROM {files}'));
 }
@@ -475,7 +547,8 @@
  *   destination directory should overwritten. A false value will generate a
  *   new, unique filename in the destination directory.
  * @return
- *   An object containing the file information, or 0 in the event of an error.
+ *   An object containing the file information, or FALSE in the event of an 
+ *   error.
  */
 function file_save_upload($source, $validators = array(), $dest = FALSE, $replace = FILE_EXISTS_RENAME) {
   global $user;
@@ -492,8 +565,8 @@
 
   // If a file was uploaded, process it.
   if (isset($_FILES['files']) && $_FILES['files']['name'][$source] && is_uploaded_file($_FILES['files']['tmp_name'][$source])) {
-    // Check for file upload errors and return FALSE if a
-    // lower level system error occurred.
+    // Check for file upload errors and return FALSE if a lower level system
+    // error occurred.
     switch ($_FILES['files']['error'][$source]) {
       // @see http://php.net/manual/en/features.file-upload.errors.php
       case UPLOAD_ERR_OK:
@@ -502,17 +575,17 @@
       case UPLOAD_ERR_INI_SIZE:
       case UPLOAD_ERR_FORM_SIZE:
         drupal_set_message(t('The file %file could not be saved, because it exceeds %maxsize, the maximum allowed size for uploads.', array('%file' => $source, '%maxsize' => format_size(file_upload_max_size()))), 'error');
-        return 0;
+        return FALSE;
 
       case UPLOAD_ERR_PARTIAL:
       case UPLOAD_ERR_NO_FILE:
         drupal_set_message(t('The file %file could not be saved, because the upload did not complete.', array('%file' => $source)), 'error');
-        return 0;
+        return FALSE;
 
         // Unknown error
       default:
         drupal_set_message(t('The file %file could not be saved. An unknown error has occurred.', array('%file' => $source)), 'error');
-        return 0;
+        return FALSE;
     }
 
     // Build the list of non-munged extensions.
@@ -525,9 +598,12 @@
 
     // Begin building file object.
     $file = new stdClass();
+    $file->uid      = $user->uid;
+    $file->status   = FILE_STATUS_TEMPORARY;
     $file->filename = file_munge_filename(trim(basename($_FILES['files']['name'][$source]), '.'), $extensions);
     $file->filepath = $_FILES['files']['tmp_name'][$source];
     $file->filemime = $_FILES['files']['type'][$source];
+    $file->filesize = $_FILES['files']['size'][$source];
 
     // Rename potentially executable files, to help prevent exploits.
     if (preg_match('/\.(php|pl|py|cgi|asp|js)$/i', $file->filename) && (substr($file->filename, -4) != '.txt')) {
@@ -542,16 +618,18 @@
     }
     $file->source = $source;
     $file->destination = $dest;
-    $file->filesize = $_FILES['files']['size'][$source];
 
-    // Call the validation functions.
+    // Call the validation functions specified by this function's caller.
     $errors = array();
     foreach ($validators as $function => $args) {
       array_unshift($args, $file);
       $errors = array_merge($errors, call_user_func_array($function, $args));
     }
 
-    // Check for validation errors.
+    // Let other modules perform validation on the new file.
+    $errors = array_merge($errors, module_invoke_all('file', 'validate', $file));
+
+    // Check for errors.
     if (!empty($errors)) {
       $message = t('The selected file %name could not be uploaded. ', array('%name' => $file->filename));
       if (count($errors) > 1) {
@@ -561,27 +639,27 @@
         $message .= array_pop($errors);
       }
       form_set_error($source, $message);
-      return 0;
+      return FALSE;
     }
 
-    // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary directory.
-    // This overcomes open_basedir restrictions for future file operations.
+    // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary 
+    // directory. This overcomes open_basedir restrictions for future file 
+    // operations.
     $file->filepath = $file->destination;
     if (!move_uploaded_file($_FILES['files']['tmp_name'][$source], $file->filepath)) {
       form_set_error($source, t('File upload error. Could not move uploaded file.'));
       watchdog('file', t('Upload error. Could not move uploaded file %file to destination %destination.', array('%file' => $file->filename, '%destination', $file->filepath)));
-      return 0;
+      return FALSE;
     }
 
-    // If we made it this far it's safe to record this file in the database.
-    db_query("INSERT INTO {files} (uid, filename, filepath, filemime, filesize, status, timestamp) VALUES (%d, '%s', '%s', '%s', %d, %d, %d)", $user->uid, $file->filename, $file->filepath, $file->filemime, $file->filesize, FILE_STATUS_TEMPORARY, time());
-    $file->fid = db_last_insert_id('files', 'fid');
+    if ($file = file_save($file)) {
+      // Add file to the cache.
+      $upload_cache[$source] = $file;
 
-    // Add file to the cache.
-    $upload_cache[$source] = $file;
-    return $file;
+      return $file;
+    }
   }
-  return 0;
+  return FALSE;
 }
 
 /**
@@ -735,46 +813,91 @@
 }
 
 /**
- * Save a string to the specified destination.
+ * Save a string to the specified destination. The file will be added to the 
+ * database as a permanent file. 
  *
- * @param $data A string containing the contents of the file.
- * @param $dest A string containing the destination location.
- * @param $replace Replace behavior when the destination file already exists.
+ * @param $data
+ *   A string containing the contents of the file.
+ * @param $dest
+ *   A string containing the destination location.
+ * @param $replace
+ *   Replace behavior when the destination file already exists.
  *   - FILE_EXISTS_REPLACE - Replace the existing file
  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique
  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
  *
- * @return A string containing the resulting filename or 0 on error
+ * @return
+ *   A file object for the resulting file, or FALSE in the event of an error.
  */
 function file_save_data($data, $dest, $replace = FILE_EXISTS_RENAME) {
-  $temp = file_directory_temp();
-  $file = tempnam($temp, 'file');
-  if (!$fp = fopen($file, 'wb')) {
+  global $user;
+  
+  if ($filepath = _file_save_data($data, $dest, $replace)) { 
+    // Create a file object.
+    $file = new stdClass();
+    $file->filepath = $filepath;
+    $file->filename = basename($file->filepath);
+    $file->filemime = 'text/plain';
+    $file->uid      = $user->uid;
+    $file->status   = FILE_STATUS_PERMANENT;
+    
+    return file_save($file);
+  }
+  return FALSE;
+}
+
+/**
+ * Save a string to the specified destination. 
+ *
+ * @param $data
+ *   A string containing the contents of the file.
+ * @param $dest
+ *   A string containing the destination location.
+ * @param $replace
+ *   Replace behavior when the destination file already exists.
+ *   - FILE_EXISTS_REPLACE - Replace the existing file
+ *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique
+ *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
+ *
+ * @return
+ *   A string with the path of the resulting file, or FALSE in the event of an 
+ *   error.
+ */
+function _file_save_data($data, $dest, $replace = FILE_EXISTS_RENAME) {
+  global $user;
+  
+  // Write the data to a temporary file.
+  $temp_name = tempnam(file_directory_temp(), 'file');
+  if (!$fp = fopen($temp_name, 'wb')) {
     drupal_set_message(t('The file could not be created.'), 'error');
-    return 0;
+    return FALSE;
   }
   fwrite($fp, $data);
   fclose($fp);
 
-  if (!file_move($file, $dest, $replace)) {
-    return 0;
-  }
-
-  return $file;
+  // Move the file to its final destination.
+  return _file_move($temp_name, $dest, $replace);
 }
 
 /**
  * Set the status of a file.
  *
- * @param file A Drupal file object
- * @param status A status value to set the file to.
- * @return FALSE on failure, TRUE on success and $file->status will contain the
- *     status.
+ * @param $file
+ *   A Drupal file object
+ * @param $status 
+ *   A status value to set the file to.
+ *   - FILE_STATUS_TEMPORARY - A temporary file that Drupal's garbage 
+ *                             collection will remove.
+ *   - FILE_STATUS_PERMANENT - A permanant file that will drupal's garbage 
+ *                             collection will not remove.
+ * @return
+ *   File object if the change is successful, or FALSE in the event of an error.
  */
-function file_set_status(&$file, $status) {
+function file_set_status($file, $status = FILE_STATUS_PERMANENT) {
   if (db_query('UPDATE {files} SET status = %d WHERE fid = %d', $status, $file->fid)) {
     $file->status = $status;
-    return TRUE;
+    module_invoke_all('file', 'status', $file);
+    return $file;
   }
   return FALSE;
 }
@@ -783,8 +906,10 @@
  * Transfer file using http to client. Pipes a file through Drupal to the
  * client.
  *
- * @param $source File to transfer.
- * @param $headers An array of http headers to send along with file.
+ * @param $source
+ *   File to transfer.
+ * @param $headers
+ *   An array of http headers to send along with file.
  */
 function file_transfer($source, $headers) {
   ob_end_clean();
@@ -908,7 +1033,8 @@
 /**
  * Determine the default temporary directory.
  *
- * @return A string containing a temp directory.
+ * @return
+ *   A string containing a temp directory.
  */
 function file_directory_temp() {
   $temporary_directory = variable_get('file_directory_temp', NULL);
@@ -949,7 +1075,8 @@
 /**
  * Determine the default 'files' directory.
  *
- * @return A string containing the path to Drupal's 'files' directory.
+ * @return
+ *   A string containing the path to Drupal's 'files' directory.
  */
 function file_directory_path() {
   return variable_get('file_directory_path', 'files');
@@ -972,3 +1099,64 @@
   }
   return $max_size;
 }
+
+/**
+ * Load a file object from the database.
+ * 
+ * @param $file_id
+ *   A numeric file id or string containg the file path.
+  * @param $reset
+ *   Whether to reset the internal file_load cache.
+ */
+function file_load($file_id, $reset = NULL) {
+  static $files = array();
+
+  if ($reset) {
+    $files = array();
+  }
+
+  if (is_numeric($file_id)) {
+    if (isset($files[$file_id])) {
+      return drupal_clone($files[$file_id]);
+    }
+    $file = db_fetch_object(db_query('SELECT f.* FROM {files} f WHERE f.fid = %d', $file_id));
+  }
+  else {
+    $file = db_fetch_object(db_query("SELECT f.* FROM {files} f WHERE f.filepath = '%s'", $file_id));
+  }
+
+  module_invoke_all('file', 'load', $file);
+
+  // Cache the fully loaded value.
+  $files[$file->fid] = drupal_clone($file);
+  
+  return $file;
+}
+
+/**
+ * Save a file object to the database. If the $file->fid is not set a new record
+ * will be added. Re-saving an existing file will not change its status.
+ *
+ * @param $file
+ *   A file object.
+ * @return
+ *   The updated file object. 
+ */
+function file_save($file) {
+  $file->timestamp = time();
+  $file->filesize = filesize($file->filepath);
+
+  if (empty($file->fid)) {
+    db_query("INSERT INTO {files} (uid, filename, filepath, filemime, filesize, status, timestamp) VALUES (%d, '%s', '%s', '%s', %d, %d, %d)", $file->uid, $file->filename, $file->filepath, $file->filemime, $file->filesize, $file->status, $file->timestamp);
+    $file->fid = db_last_insert_id('files', 'fid');
+
+    module_invoke_all('file', 'insert', $file);
+  }
+  else {
+    db_query("UPDATE {files} SET uid = %d, filename = '%s', filepath = '%s', filemime = '%s', filesize = %d, timestamp = %d WHERE fid = %d", $file->uid, $file->filename, $file->filepath, $file->filemime, $file->filesize, $file->timestamp, $file->fid);
+
+    module_invoke_all('file', 'update', $file);
+  }
+
+  return $file;
+}
Index: modules/blogapi/blogapi.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/blogapi/blogapi.module,v
retrieving revision 1.107
diff -u -r1.107 blogapi.module
--- modules/blogapi/blogapi.module	30 Jun 2007 19:46:55 -0000	1.107
+++ modules/blogapi/blogapi.module	1 Jul 2007 21:42:21 -0000
@@ -362,12 +362,12 @@
     return blogapi_error(t('No file sent.'));
   }
 
-  if (!$file = file_save_data($data, $name)) {
+  if (!$filepath = _file_save_data($data, $name)) {
     return blogapi_error(t('Error storing file.'));
   }
 
   // Return the successful result.
-  return array('url' => file_create_url($file), 'struct');
+  return array('url' => _file_create_url($filepath), 'struct');
 }
 /**
  * Blogging API callback. Returns a list of the taxonomy terms that can be
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.504
diff -u -r1.504 system.module
--- modules/system/system.module	1 Jul 2007 17:41:16 -0000	1.504
+++ modules/system/system.module	1 Jul 2007 21:42:21 -0000
@@ -2279,9 +2279,9 @@
     // The image was saved using file_save_upload() and was added to the
     // files table as a temorary file. We'll make a copy and let the garbage
     // collector delete the original upload.
-    if (file_copy($file, $filename, FILE_EXISTS_REPLACE)) {
+    if ($filepath = _file_copy($file->filepath, $filename, FILE_EXISTS_REPLACE)) {
       $_POST['default_logo'] = 0;
-      $_POST['logo_path'] = $file->filepath;
+      $_POST['logo_path'] = $filepath;
       $_POST['toggle_logo'] = 1;
     }
   }
@@ -2294,9 +2294,9 @@
     // The image was saved using file_save_upload() and was added to the
     // files table as a temorary file. We'll make a copy and let the garbage
     // collector delete the original upload.
-    if (file_copy($file, $filename)) {
+    if ($filepath = _file_copy($file['filepath'], $filename, FILE_EXISTS_REPLACE)) {
       $_POST['default_favicon'] = 0;
-      $_POST['favicon_path'] = $file->filepath;
+      $_POST['favicon_path'] = $filepath;
       $_POST['toggle_favicon'] = 1;
     }
   }
@@ -2754,12 +2754,14 @@
     if (file_exists($file->filepath)) {
       // If files that exist cannot be deleted, continue so the database remains
       // consistant.
-      if (!file_delete($file->filepath)) {
+      if (!file_delete($file)) {
         watchdog('file system', t('Could not delete temporary file "%path" during garbage collection', array('%path' => $file->filepath)), 'error');
         continue;
       }
     }
-    db_query('DELETE FROM {files} WHERE fid = %d', $file->fid);
+    else {
+      db_query('DELETE FROM {files} WHERE fid = %d', $file->fid);
+    }
   }
 }
 
Index: modules/upload/upload.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/upload/upload.module,v
retrieving revision 1.170
diff -u -r1.170 upload.module
--- modules/upload/upload.module	1 Jul 2007 17:41:16 -0000	1.170
+++ modules/upload/upload.module	1 Jul 2007 21:42:21 -0000
@@ -160,21 +160,29 @@
     '#title' => t('General settings'),
     '#collapsible' => TRUE,
   );
+  
+  if ($toolkit = image_get_toolkit()) {
+    $resize_message = t('Images exceeding these dimensions will be resized using the %toolkit image toolkit.', array('%toolkit' => $toolkit));
+  }
+  else {
+    $resize_message = t('There is no <a href="!image-toolkit-link">image toolkit</a> is installed so images will not be resized.', array('!image-toolkit-link' => url('admin/settings/image-toolkit')));
+  }
   $form['settings_general']['upload_max_resolution'] = array(
     '#type' => 'textfield',
     '#title' => t('Maximum resolution for uploaded images'),
     '#default_value' => variable_get('upload_max_resolution', 0),
     '#size' => 15,
     '#maxlength' => 10,
-    '#description' => t('The maximum allowed image size (e.g. 640x480). Set to 0 for no restriction. If an <a href="!image-toolkit-link">image toolkit</a> is installed, files exceeding this value will be scalled down to fit.', array('!image-toolkit-link' => url('admin/settings/image-toolkit'))),
+    '#description' => t('The maximum allowed image size (e.g. 640x480). Set to 0 for no restriction. ') . $resize_message,
     '#field_suffix' => '<kbd>'. t('WIDTHxHEIGHT') .'</kbd>'
   );
+
+    
   $form['settings_general']['upload_list_default'] = array(
-    '#type' => 'select',
+    '#type' => 'checkbox',
     '#title' => t('List files by default'),
     '#default_value' => variable_get('upload_list_default', 1),
-    '#options' => array(0 => t('No'), 1 => t('Yes')),
-    '#description' => t('Set whether files attached to nodes are listed or not in the node view by default.'),
+    '#description' => t('Determines whether files attached to nodes are listed or not in the node view by default.'),
   );
 
   $form['settings_general']['upload_extensions_default'] = array(
@@ -293,12 +301,12 @@
 /**
  * Implementation of hook_file_download().
  */
-function upload_file_download($file) {
+function upload_file_download($filepath) {
   if (!user_access('view uploaded files')) {
     return -1;
   }
-  $file = file_create_path($file);
-  $result = db_query("SELECT f.* FROM {files} f INNER JOIN {upload} u ON f.fid = u.uid WHERE filepath = '%s'", $file);
+  $filepath = file_create_path($filepath);
+  $result = db_query("SELECT f.* FROM {files} f INNER JOIN {upload} u ON f.fid = u.uid WHERE filepath = '%s'", $filepath);
   if ($file = db_fetch_object($result)) {
     return array(
       'Content-Type: '. $file->filemime,
@@ -347,7 +355,7 @@
     $_SESSION['upload_files'][$file->fid] = $file;
   }
 
-  // attach session files to node.
+  // Attach session files to node.
   if (count($_SESSION['upload_files'])) {
     foreach($_SESSION['upload_files'] as $fid => $file) {
       $node->files[$fid] = $file;
@@ -413,6 +421,27 @@
 }
 
 /**
+ * Implementation of hook_file().
+ */
+function upload_file($op, &$file, $source = NULL) {
+  switch ($op) {
+    case 'load':
+      // Add the upload specific data into the file object. 
+      $values = db_fetch_array(db_query('SELECT vid, description, list FROM {upload} u WHERE u.fid = %d', $file->fid));
+      foreach ((array) $values as $key => $value) {
+        $file->{$key} = $value;
+      }
+      break;
+
+    case 'delete':
+      // Delete all information associated with the file.
+      db_query('DELETE FROM {upload} WHERE fid = %d', $file->fid);
+      break;
+  }
+}
+
+
+/**
  * Implementation of hook_nodeapi().
  */
 function upload_nodeapi(&$node, $op, $teaser) {
@@ -554,13 +583,12 @@
     // Create a new revision, or associate a new file needed.
     if (!empty($node->old_vid) || array_key_exists($fid, $_SESSION['upload_files'])) {
       db_query("INSERT INTO {upload} (fid, nid, vid, list, description) VALUES (%d, %d, %d, %d, '%s')", $file->fid, $node->nid, $node->vid, $file->list, $file->description);
-      file_set_status($file, FILE_STATUS_PERMANENT);
     }
     // Update existing revision.
     else {
       db_query("UPDATE {upload} SET list = %d, description = '%s' WHERE fid = %d AND vid = %d", $file->list, $file->description, $file->fid, $node->vid);
-      file_set_status($file, FILE_STATUS_PERMANENT);
     }
+    file_set_status($file, FILE_STATUS_PERMANENT);
   }
   // Empty the session storage after save. We use this variable to track files
   // that haven't been related to the node yet.
@@ -577,7 +605,7 @@
   foreach ($files as $fid => $file) {
     // Delete all files associated with the node
     db_query('DELETE FROM {files} WHERE fid = %d', $fid);
-    file_delete($file->filepath);
+    file_delete($file);
   }
 
   // Delete all file revision information associated with the node
@@ -593,7 +621,7 @@
       // if the file won't be used, delete it
       if ($count < 2) {
         db_query('DELETE FROM {files} WHERE fid = %d', $file->fid);
-        file_delete($file->filepath);
+        file_delete($file);
       }
     }
   }
@@ -610,10 +638,17 @@
   if (!empty($node->files) && is_array($node->files)) {
     $form['files']['#theme'] = 'upload_form_current';
     $form['files']['#tree'] = TRUE;
-    foreach ($node->files as $key => $file) {
-      $description = file_create_url($file->filepath);
-      $description = "<small>". check_plain($description) ."</small>";
-      $form['files'][$key]['description'] = array('#type' => 'textfield', '#default_value' => !empty($file->description) ? $file->description : $file->filename, '#maxlength' => 256, '#description' => $description );
+    foreach ($node->files as $file) {
+      $file = (object) $file;
+      $key = $file->fid;
+
+      $form['files'][$key]['description'] = array(
+        '#type' => 'textfield', 
+        '#default_value' => !empty($file->description) ? $file->description : $file->filename, 
+        '#maxlength' => 256, 
+        '#description' => '<small>'. file_create_url($file->filepath) .'</small>',
+      );
+
       $form['files'][$key]['size'] = array('#value' => 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);
@@ -639,11 +674,23 @@
       '#prefix' => '<div id="attach-hide">',
       '#suffix' => '</div>',
     );
+    $limit_description = t('The maximum size of file uploads is %filesize. ', array('%filesize' => format_size($limits['file_size'])));
+    if (isset($limits['resolution'])) {
+      if (image_get_toolkit()) {
+        $limit_description .= t('Images larger than %resolution will be resized. ', array('%resolution' => $limits['resolution']));
+      }
+      else {
+        $limit_description .= t('Images may not be larger than %resolution. ', array('%resolution' => $limits['resolution']));
+      }
+    }
+    if ($user->uid != 1) {
+      $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' => ($limits['resolution'] ? t('Images are larger than %resolution will be resized. ', array('%resolution' => $limits['resolution'])) : '') . t('The maximum upload size is %filesize. Only files with the following extensions may be uploaded: %extensions. ', array('%extensions' => $limits['extensions'], '%filesize' => format_size($limits['file_size']))),
+      '#description' => $limit_description,
     );
     $form['new']['attach'] = array(
       '#type' => 'submit',
@@ -693,9 +740,9 @@
   $files = array();
 
   if ($node->vid) {
-    $result = db_query('SELECT * FROM {files} f INNER JOIN {upload} r ON f.fid = r.fid WHERE r.vid = %d ORDER BY f.fid', $node->vid);
+    $result = db_query('SELECT fid FROM {upload} u WHERE u.vid = %d ORDER BY u.fid', $node->vid);
     while ($file = db_fetch_object($result)) {
-      $files[$file->fid] = $file;
+      $files[$file->fid] = file_load($file->fid);
     }
   }
 
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.815
diff -u -r1.815 user.module
--- modules/user/user.module	1 Jul 2007 19:49:19 -0000	1.815
+++ modules/user/user.module	1 Jul 2007 21:42:21 -0000
@@ -375,7 +375,7 @@
     // collector delete the original upload.
     $info = image_get_info($file->filepath);
     $destination = variable_get('user_picture_path', 'pictures') .'/picture-'. $form['#uid'] .'.'. $info['extension'];
-    if (file_copy($file, $destination, FILE_EXISTS_REPLACE)) {
+    if ($file = file_copy($file, $destination, FILE_EXISTS_REPLACE)) {
       $form_state['values']['picture'] = $file->filepath;
     }
     else {
@@ -1571,8 +1571,8 @@
   $user = user_load(array('uid' => $uid));
   // Delete picture if requested, and if no replacement picture was given.
   if (!empty($edit['picture_delete'])) {
-    if ($user->picture && file_exists($user->picture)) {
-      file_delete($user->picture);
+    if ($user->picture && $file = file_load($user->picture)) {
+      file_delete($file);
     }
     $edit['picture'] = '';
   }
