? .bzr ? 142995_1.patch ? hook_file_142995_7.patch ? test.php ? modules/gd ? sites/default/files ? sites/default/modules ? sites/default/settings.php Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.669 diff -u -r1.669 common.inc --- includes/common.inc 2 Jul 2007 14:41:34 -0000 1.669 +++ includes/common.inc 2 Jul 2007 19:16:08 -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.102 diff -u -r1.102 file.inc --- includes/file.inc 2 Jul 2007 14:41:35 -0000 1.102 +++ includes/file.inc 2 Jul 2007 19:16:09 -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,43 @@ } /** + * Delete a file and its database record. + * + * @param $path + * A file object. + * @param $force + * Force File Deletion ignoring reference counting. + * @return mixed + * TRUE for success, Array for reference count block, or FALSE in the event of an error. + */ +function file_delete($file, $force = FALSE) { + // If any module returns a value from the reference hook, the + // file will not be deleted from drupal, but file_delete will + // return a populated array that tests as TRUE. + if (!$force && $references = module_invoke_all('file', 'reference', $file)) { + return $references; + } + + // 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 +520,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 +556,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 +574,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 +584,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 +607,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 +627,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 +648,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 +822,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 +915,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 +1042,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 +1084,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 +1108,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 2 Jul 2007 19:16:09 -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.506 diff -u -r1.506 system.module --- modules/system/system.module 2 Jul 2007 14:41:37 -0000 1.506 +++ modules/system/system.module 2 Jul 2007 19:16:12 -0000 @@ -2310,9 +2310,9 @@ // The image was saved using file_save_upload() and was added to the // files table as a temporary 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; } } @@ -2325,9 +2325,9 @@ // The image was saved using file_save_upload() and was added to the // files table as a temporary 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; } } @@ -2785,12 +2785,14 @@ if (file_exists($file->filepath)) { // If files that exist cannot be deleted, continue so the database remains // consistent. - 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.171 diff -u -r1.171 upload.module --- modules/upload/upload.module 2 Jul 2007 14:41:37 -0000 1.171 +++ modules/upload/upload.module 2 Jul 2007 19:16:13 -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 scaled down to fit using the %toolkit image toolkit.', array('%toolkit' => $toolkit)); + } + else { + $resize_message = t('There is no image toolkit 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 image toolkit is installed, files exceeding this value will be scaled 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' => ''. t('WIDTHxHEIGHT') .'' ); + + $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,36 @@ } /** + * 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 'reference': + // If upload.module is still using a file, do not let other modules delete it. + $count = db_result(db_query('SELECT count(*) FROM {upload} WHERE fid = %d', $file->fid)); + if ($count) { + // return the name of the module and how many references it has to the file. + return array('upload' => $count); + } + 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,52 +592,37 @@ // 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. unset($_SESSION['upload_files']); } + function upload_delete($node) { - $files = array(); - $result = db_query('SELECT DISTINCT f.* FROM upload u INNER JOIN files f ON u.fid = f.fid WHERE u.nid = %d', $node->nid); - while ($file = db_fetch_object($result)) { - $files[$file->fid] = $file; + db_query('DELETE FROM {upload} WHERE nid = %d', $node->nid); + if (!is_array($node->files)) { + return; } - - 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); + foreach($node->files as $file) { + file_delete($file); } - - // Delete all file revision information associated with the node - db_query('DELETE FROM {upload} WHERE nid = %d', $node->nid); } function upload_delete_revision($node) { - if (is_array($node->files)) { - foreach ($node->files as $file) { - // Check if the file will be used after this revision is deleted - $count = db_result(db_query('SELECT COUNT(fid) FROM {upload} WHERE fid = %d', $file->fid)); - - // 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); - } - } - } - - // delete the revision db_query('DELETE FROM {upload} WHERE vid = %d', $node->vid); + if (!is_array($node->files)) { + return; + } + foreach ($node->files as $file) { + file_delete($file); + } } function _upload_form($node) { @@ -610,10 +633,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 = "". check_plain($description) .""; - $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' => ''. file_create_url($file->filepath) .'', + ); + $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 +669,23 @@ '#prefix' => '
', '#suffix' => '
', ); + $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 +735,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.816 diff -u -r1.816 user.module --- modules/user/user.module 2 Jul 2007 14:41:37 -0000 1.816 +++ modules/user/user.module 2 Jul 2007 19:16: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'] = ''; }