2003-12-26 23:03:21 +00:00
< ? php
2005-08-11 12:57:41 +00:00
// $Id$
2003-12-27 21:29:20 +00:00
2004-08-21 06:42:38 +00:00
/**
* @ file
* API for handling file uploads and server file management .
*/
2003-12-27 21:29:20 +00:00
/**
2004-01-06 19:52:14 +00:00
* @ defgroup file File interface
2003-12-27 21:29:20 +00:00
* @ {
2004-09-09 05:51:08 +00:00
* Common file handling functions .
2003-12-26 23:03:21 +00:00
*/
define ( 'FILE_DOWNLOADS_PUBLIC' , 1 );
define ( 'FILE_DOWNLOADS_PRIVATE' , 2 );
2004-08-17 21:35:26 +00:00
define ( 'FILE_CREATE_DIRECTORY' , 1 );
define ( 'FILE_MODIFY_PERMISSIONS' , 2 );
2004-09-17 18:18:04 +00:00
define ( 'FILE_EXISTS_RENAME' , 0 );
define ( 'FILE_EXISTS_REPLACE' , 1 );
define ( 'FILE_EXISTS_ERROR' , 2 );
2003-12-26 23:03:21 +00:00
2007-05-30 08:08:59 +00:00
/**
2007-07-02 14:41:37 +00:00
* A files status can be one of two values : temporary or permanent . The status
2007-05-30 08:08:59 +00:00
* for each file Drupal manages is stored in the { files } tables . If the status
* is temporary Drupal ' s file garbage collection will delete the file and
* remove it from the files table after a set period of time .
*
* If you wish to add custom statuses for use by contrib modules please expand as
* binary flags and consider the first 8 bits reserved . ( 0 , 1 , 2 , 4 , 8 , 16 , 32 , 64 , 128 )
*/
define ( 'FILE_STATUS_TEMPORARY' , 0 );
define ( 'FILE_STATUS_PERMANENT' , 1 );
2003-12-26 23:03:21 +00:00
/**
* Create the download path to a file .
2004-09-17 18:18:04 +00:00
*
2006-03-08 23:42:55 +00:00
* @ 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 .
2003-12-26 23:03:21 +00:00
*/
function file_create_url ( $path ) {
2006-04-17 20:48:26 +00:00
// Strip file_directory_path from $path. We only include relative paths in urls.
2007-04-13 08:56:59 +00:00
if ( strpos ( $path , file_directory_path () . '/' ) === 0 ) {
2005-11-12 09:23:50 +00:00
$path = trim ( substr ( $path , strlen ( file_directory_path ())), '\\/' );
2003-12-27 14:28:23 +00:00
}
2004-01-13 10:33:02 +00:00
switch ( variable_get ( 'file_downloads' , FILE_DOWNLOADS_PUBLIC )) {
2003-12-26 23:03:21 +00:00
case FILE_DOWNLOADS_PUBLIC :
2005-11-12 09:23:50 +00:00
return $GLOBALS [ 'base_url' ] . '/' . file_directory_path () . '/' . str_replace ( '\\' , '/' , $path );
2003-12-26 23:03:21 +00:00
case FILE_DOWNLOADS_PRIVATE :
2007-02-15 11:40:19 +00:00
return url ( 'system/files/' . $path , array ( 'absolute' => TRUE ));
2003-12-26 23:03:21 +00:00
}
}
/**
2006-03-08 23:42:55 +00:00
* Make sure the destination is a complete path and resides in the file system
* directory , if it is not prepend the file system directory .
2004-09-17 18:18:04 +00:00
*
2006-03-08 23:42:55 +00:00
* @ 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 ) .
2003-12-26 23:03:21 +00:00
*/
function file_create_path ( $dest = 0 ) {
2005-11-12 09:23:50 +00:00
$file_path = file_directory_path ();
2003-12-26 23:03:21 +00:00
if ( ! $dest ) {
2005-05-14 21:05:08 +00:00
return $file_path ;
2003-12-26 23:03:21 +00:00
}
2005-05-18 05:17:11 +00:00
// file_check_location() checks whether the destination is inside the Drupal files directory.
2005-05-14 21:05:08 +00:00
if ( file_check_location ( $dest , $file_path )) {
2003-12-26 23:03:21 +00:00
return $dest ;
}
2005-05-18 05:17:11 +00:00
// check if the destination is instead inside the Drupal temporary files directory.
2005-11-12 09:23:50 +00:00
else if ( file_check_location ( $dest , file_directory_temp ())) {
2005-05-18 05:17:11 +00:00
return $dest ;
}
2005-11-12 09:23:50 +00:00
// Not found, try again with prefixed directory path.
2007-04-13 08:56:59 +00:00
else if ( file_check_location ( $file_path . '/' . $dest , $file_path )) {
return $file_path . '/' . $dest ;
2005-05-14 21:05:08 +00:00
}
// File not found.
return FALSE ;
2003-12-26 23:03:21 +00:00
}
/**
2005-11-04 20:19:14 +00:00
* Check that the directory exists and is writable . Directories need to
* have execute permissions to be considered a directory by FTP servers , etc .
2003-12-26 23:03:21 +00:00
*
2006-03-08 23:42:55 +00:00
* @ 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
2006-04-11 11:33:15 +00:00
* require the user to specify a writable directory . If it can ' t be made to
2006-03-08 23:42:55 +00:00
* work , a form error will be set preventing them from saving the settings .
2006-07-05 11:45:51 +00:00
* @ return FALSE when directory not found , or TRUE when directory exists .
2003-12-26 23:03:21 +00:00
*/
2004-08-17 21:35:26 +00:00
function file_check_directory ( & $directory , $mode = 0 , $form_item = NULL ) {
2003-12-26 23:03:21 +00:00
$directory = rtrim ( $directory , '/\\' );
2004-08-17 21:35:26 +00:00
// Check if directory exists.
if ( ! is_dir ( $directory )) {
2005-11-04 20:19:14 +00:00
if (( $mode & FILE_CREATE_DIRECTORY ) && @ mkdir ( $directory )) {
2006-08-18 12:17:00 +00:00
drupal_set_message ( t ( 'The directory %directory has been created.' , array ( '%directory' => $directory )));
2005-11-04 20:19:14 +00:00
@ chmod ( $directory , 0775 ); // Necessary for non-webserver users.
2004-08-17 21:35:26 +00:00
}
else {
if ( $form_item ) {
2006-08-18 12:17:00 +00:00
form_set_error ( $form_item , t ( 'The directory %directory does not exist.' , array ( '%directory' => $directory )));
2004-08-17 21:35:26 +00:00
}
2006-07-05 11:45:51 +00:00
return FALSE ;
2004-08-17 21:35:26 +00:00
}
}
// Check to see if the directory is writable.
if ( ! is_writable ( $directory )) {
2005-11-04 20:19:14 +00:00
if (( $mode & FILE_MODIFY_PERMISSIONS ) && @ chmod ( $directory , 0775 )) {
2006-08-18 12:17:00 +00:00
drupal_set_message ( t ( 'The permissions of directory %directory have been changed to make it writable.' , array ( '%directory' => $directory )));
2004-08-17 21:35:26 +00:00
}
else {
2006-08-18 12:17:00 +00:00
form_set_error ( $form_item , t ( 'The directory %directory is not writable' , array ( '%directory' => $directory )));
2007-04-24 13:53:15 +00:00
watchdog ( 'file system' , 'The directory %directory is not writable, because it does not have the correct permissions set.' , array ( '%directory' => $directory ), WATCHDOG_ERROR );
2006-07-05 11:45:51 +00:00
return FALSE ;
2004-08-17 21:35:26 +00:00
}
}
2006-05-25 01:33:53 +00:00
if (( file_directory_path () == $directory || file_directory_temp () == $directory ) && ! is_file ( " $directory /.htaccess " )) {
2006-10-12 15:06:12 +00:00
$htaccess_lines = " SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006 \n Options None \n Options +FollowSymLinks " ;
2006-06-01 21:54:48 +00:00
if (( $fp = fopen ( " $directory /.htaccess " , 'w' )) && fputs ( $fp , $htaccess_lines )) {
2006-05-25 01:33:53 +00:00
fclose ( $fp );
2007-01-04 07:13:50 +00:00
chmod ( $directory . '/.htaccess' , 0664 );
2006-05-25 01:33:53 +00:00
}
else {
2007-04-24 13:53:15 +00:00
$variables = array ( '%directory' => $directory , '!htaccess' => '<br />' . nl2br ( check_plain ( $htaccess_lines )));
form_set_error ( $form_item , t ( " Security warning: Couldn't write .htaccess file. Please create a .htaccess file in your %directory directory which contains the following lines: <code>!htaccess</code> " , $variables ));
watchdog ( 'security' , " Security warning: Couldn't write .htaccess file. Please create a .htaccess file in your %directory directory which contains the following lines: <code>!htaccess</code> " , $variables , WATCHDOG_ERROR );
2006-05-25 01:33:53 +00:00
}
}
2006-07-05 11:45:51 +00:00
return TRUE ;
2003-12-26 23:03:21 +00:00
}
/**
* Checks path to see if it is a directory , or a dir / file .
*
2006-03-08 23:42:55 +00:00
* @ param $path A string containing a file path . This will be set to the
* directory ' s path .
2006-04-11 11:33:15 +00:00
* @ return If the directory is not in a Drupal writable directory , FALSE is
2006-03-08 23:42:55 +00:00
* returned . Otherwise , the base name of the path is returned .
2003-12-26 23:03:21 +00:00
*/
function file_check_path ( & $path ) {
// Check if path is a directory.
if ( file_check_directory ( $path )) {
return '' ;
}
// Check if path is a possible dir/file.
$filename = basename ( $path );
$path = dirname ( $path );
if ( file_check_directory ( $path )) {
return $filename ;
}
2006-07-05 11:45:51 +00:00
return FALSE ;
2003-12-26 23:03:21 +00:00
}
/**
* Check if a file is really located inside $directory . Should be used to make
* sure a file specified is really located within the directory to prevent
* exploits .
*
* @ code
2006-07-05 11:45:51 +00:00
* // Returns FALSE:
2003-12-26 23:03:21 +00:00
* 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 .
*/
2006-04-08 18:39:26 +00:00
function file_check_location ( $source , $directory = '' ) {
2005-05-17 20:49:54 +00:00
$check = realpath ( $source );
if ( $check ) {
$source = $check ;
}
else {
// This file does not yet exist
$source = realpath ( dirname ( $source )) . '/' . basename ( $source );
}
2004-01-29 11:39:22 +00:00
$directory = realpath ( $directory );
2003-12-26 23:03:21 +00:00
if ( $directory && strpos ( $source , $directory ) !== 0 ) {
return 0 ;
}
return $source ;
}
/**
2004-09-17 18:18:04 +00:00
* Copies a file to a new location . This is a powerful function that in many ways
2003-12-26 23:03:21 +00:00
* 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 .
2004-09-17 18:18:04 +00:00
* - If file already exists in $dest either the call will error out , replace the
* file or rename the file based on the $replace parameter .
2003-12-26 23:03:21 +00:00
*
* @ 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 .
2006-03-08 23:42:55 +00:00
* If this value is omitted , Drupal 's ' files ' directory will be used .
2004-09-17 18:18:04 +00:00
* @ 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
2006-07-05 11:45:51 +00:00
* - FILE_EXISTS_ERROR - Do nothing and return FALSE .
* @ return True for success , FALSE for failure .
2003-12-26 23:03:21 +00:00
*/
2004-09-17 18:18:04 +00:00
function file_copy ( & $source , $dest = 0 , $replace = FILE_EXISTS_RENAME ) {
2003-12-26 23:03:21 +00:00
$dest = file_create_path ( $dest );
$directory = $dest ;
$basename = file_check_path ( $directory );
// Make sure we at least have a valid directory.
2006-07-05 11:45:51 +00:00
if ( $basename === FALSE ) {
2006-03-26 15:02:51 +00:00
$source = is_object ( $source ) ? $source -> filepath : $source ;
2006-08-18 12:17:00 +00:00
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' );
2007-04-24 13:53:15 +00:00
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 );
2003-12-26 23:03:21 +00:00
return 0 ;
}
// Process a file upload object.
if ( is_object ( $source )) {
$file = $source ;
2004-08-17 21:35:26 +00:00
$source = $file -> filepath ;
2003-12-26 23:03:21 +00:00
if ( ! $basename ) {
2004-08-17 21:35:26 +00:00
$basename = $file -> filename ;
2003-12-26 23:03:21 +00:00
}
}
$source = realpath ( $source );
if ( ! file_exists ( $source )) {
2006-08-18 12:17:00 +00:00
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' );
2003-12-26 23:03:21 +00:00
return 0 ;
}
2005-05-05 22:22:46 +00:00
// If the destination file is not specified then use the filename of the source file.
2003-12-26 23:03:21 +00:00
$basename = $basename ? $basename : basename ( $source );
2004-08-24 19:21:30 +00:00
$dest = $directory . '/' . $basename ;
2003-12-26 23:03:21 +00:00
2004-08-17 21:35:26 +00:00
// Make sure source and destination filenames are not the same, makes no sense
// to copy it if they are. In fact copying the file will most likely result in
// a 0 byte file. Which is bad. Real bad.
if ( $source != realpath ( $dest )) {
2007-05-30 08:08:59 +00:00
if ( ! $dest = file_destination ( $dest , $replace )) {
drupal_set_message ( t ( 'The selected file %file could not be copied, because a file by that name already exists in the destination.' , array ( '%file' => $source )), 'error' );
return FALSE ;
2004-08-17 21:35:26 +00:00
}
2003-12-26 23:03:21 +00:00
2004-10-28 23:42:36 +00:00
if ( !@ copy ( $source , $dest )) {
2006-08-18 12:17:00 +00:00
drupal_set_message ( t ( 'The selected file %file could not be copied.' , array ( '%file' => $source )), 'error' );
2004-08-17 21:35:26 +00:00
return 0 ;
}
2005-11-04 20:19:14 +00:00
// Give everyone read access so that FTP'd users or
2007-05-30 08:08:59 +00:00
// non-webserver users can see/read these files,
2007-07-02 14:41:37 +00:00
// and give group write permissions so group members
2007-05-30 08:08:59 +00:00
// can alter files uploaded by the webserver.
2005-11-04 20:19:14 +00:00
@ chmod ( $dest , 0664 );
2003-12-26 23:03:21 +00:00
}
2007-03-27 05:13:55 +00:00
if ( isset ( $file ) && is_object ( $file )) {
2004-08-17 21:35:26 +00:00
$file -> filename = $basename ;
$file -> filepath = $dest ;
2003-12-26 23:03:21 +00:00
$source = $file ;
}
else {
$source = $dest ;
}
2004-08-17 21:35:26 +00:00
2003-12-26 23:03:21 +00:00
return 1 ; // Everything went ok.
}
2007-05-30 08:08:59 +00:00
/**
* 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 .
* - 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
* FILE_EXISTS_ERROR was specified .
*/
function file_destination ( $destination , $replace ) {
if ( file_exists ( $destination )) {
switch ( $replace ) {
case FILE_EXISTS_RENAME :
$basename = basename ( $destination );
$directory = dirname ( $destination );
$destination = file_create_filename ( $basename , $directory );
break ;
case FILE_EXISTS_ERROR :
drupal_set_message ( t ( 'The selected file %file could not be copied, because a file by that name already exists in the destination.' , array ( '%file' => $source )), 'error' );
return FALSE ;
}
}
return $destination ;
}
2004-09-17 18:18:04 +00:00
/**
* Moves a file to a new location .
* - 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 .
*
* @ 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 .
2006-03-08 23:42:55 +00:00
* If this value is omitted , Drupal 's ' files ' directory will be used .
2004-09-17 18:18:04 +00:00
* @ 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
2006-07-05 11:45:51 +00:00
* - FILE_EXISTS_ERROR - Do nothing and return FALSE .
* @ return True for success , FALSE for failure .
2004-09-17 18:18:04 +00:00
*/
function file_move ( & $source , $dest = 0 , $replace = FILE_EXISTS_RENAME ) {
2004-08-17 21:35:26 +00:00
$path_original = is_object ( $source ) ? $source -> filepath : $source ;
2003-12-26 23:03:21 +00:00
if ( file_copy ( $source , $dest , $replace )) {
2004-08-17 21:35:26 +00:00
$path_current = is_object ( $source ) ? $source -> filepath : $source ;
if ( $path_original == $path_current || file_delete ( $path_original )) {
2003-12-26 23:03:21 +00:00
return 1 ;
}
2006-08-18 12:17:00 +00:00
drupal_set_message ( t ( 'The removal of the original file %file has failed.' , array ( '%file' => $path_original )), 'error' );
2003-12-26 23:03:21 +00:00
}
return 0 ;
}
2007-05-30 08:08:59 +00:00
/**
* 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 .
*/
function file_munge_filename ( $filename , $extensions , $alerts = TRUE ) {
$original = $filename ;
// Allow potentially insecure uploads for very savvy users and admin
if ( ! variable_get ( 'allow_insecure_uploads' , 0 )) {
$whitelist = array_unique ( explode ( ' ' , trim ( $extensions )));
// Split the filename up by periods. The first part becomes the basename
// the last part the final extension.
$filename_parts = explode ( '.' , $filename );
$new_filename = array_shift ( $filename_parts ); // Remove file basename.
$final_extension = array_pop ( $filename_parts ); // Remove final extension.
// Loop through the middle parts of the name and add an underscore to the
// end of each section that could be a file extension but isn't in the list
// of allowed extensions.
foreach ( $filename_parts as $filename_part ) {
$new_filename .= '.' . $filename_part ;
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 ) {
drupal_set_message ( t ( 'For security reasons, your upload has been renamed to %filename.' , array ( '%filename' => $filename )));
}
}
return $filename ;
}
/**
* Undo the effect of upload_munge_filename () .
*
* @ param $filename string filename
* @ return string
*/
function file_unmunge_filename ( $filename ) {
return str_replace ( '_.' , '.' , $filename );
}
2006-03-08 23:42:55 +00:00
/**
* 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
* @ return
*/
2004-08-17 21:35:26 +00:00
function file_create_filename ( $basename , $directory ) {
2004-08-24 19:21:30 +00:00
$dest = $directory . '/' . $basename ;
2004-08-17 21:35:26 +00:00
if ( file_exists ( $dest )) {
// Destination file already exists, generate an alternative.
if ( $pos = strrpos ( $basename , '.' )) {
$name = substr ( $basename , 0 , $pos );
$ext = substr ( $basename , $pos );
}
else {
$name = $basename ;
}
$counter = 0 ;
do {
2004-08-24 19:21:30 +00:00
$dest = $directory . '/' . $name . '_' . $counter ++ . $ext ;
2004-08-17 21:35:26 +00:00
} while ( file_exists ( $dest ));
}
return $dest ;
}
2006-03-08 23:42:55 +00:00
/**
* Delete a file .
*
* @ param $path A string containing a file path .
2007-05-30 08:08:59 +00:00
* @ return TRUE for success , FALSE for failure .
2006-03-08 23:42:55 +00:00
*/
2003-12-27 19:21:48 +00:00
function file_delete ( $path ) {
if ( is_file ( $path )) {
2004-08-17 21:35:26 +00:00
return unlink ( $path );
2003-12-27 19:21:48 +00:00
}
2003-12-26 23:03:21 +00:00
}
2007-05-30 08:08:59 +00:00
/**
* 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 ));
}
return db_result ( db_query ( 'SELECT SUM(filesize) FROM {files}' ));
}
2003-12-26 23:03:21 +00:00
/**
* Saves a file upload to a new location . The source file is validated as a
* proper upload and handled as such .
*
2007-07-02 14:41:37 +00:00
* The file will be added to the files table as a temporary file . Temporary files
2007-05-30 08:08:59 +00:00
* are periodically cleaned . To make the file permanent file call
* file_set_status () to change it ' s status .
*
* @ param $source
* A string specifying the name of the upload field to save .
* @ param $dest
* A string containing the directory $source should be copied to . If this is
* not provided , the temporary directory will be used .
* @ param $validators
* An optional , associative array of callback functions used to validate the
* file . The keys are function names and the values arrays of callback
* parameters which will be passed in after the user and file objects . The
* functions should return an array of error messages , an empty array
* indicates that the file passed validation . The functions will be called in
* the order specified .
* @ param $replace
* A boolean indicating whether an existing file of the same name in the
* 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 .
2003-12-26 23:03:21 +00:00
*/
2007-05-30 08:08:59 +00:00
function file_save_upload ( $source , $validators = array (), $dest = FALSE , $replace = FILE_EXISTS_RENAME ) {
global $user ;
static $upload_cache ;
// Add in our check of the the file name length.
$validators [ 'file_validate_name_length' ] = array ();
// Return cached objects without processing since the file will have
// already been processed and the paths in _FILES will be invalid.
if ( isset ( $upload_cache [ $source ])) {
return $upload_cache [ $source ];
}
// 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.
switch ( $_FILES [ 'files' ][ 'error' ][ $source ]) {
// @see http://php.net/manual/en/features.file-upload.errors.php
case UPLOAD_ERR_OK :
break ;
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 ;
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 ;
// 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 ;
}
// Build the list of non-munged extensions.
// @todo: this should not be here. we need to figure out the right place.
$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 odt ods odp' ));
}
2006-03-07 19:14:30 +00:00
2007-05-30 08:08:59 +00:00
// Begin building file object.
$file = new stdClass ();
$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 ];
// 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' )) {
$file -> filemime = 'text/plain' ;
$file -> filepath .= '.txt' ;
$file -> filename .= '.txt' ;
}
// Create temporary name/path for newly uploaded files.
2003-12-27 19:21:48 +00:00
if ( ! $dest ) {
2007-05-30 08:08:59 +00:00
$dest = file_destination ( file_create_path ( $file -> filename ), FILE_EXISTS_RENAME );
}
$file -> source = $source ;
$file -> destination = $dest ;
$file -> filesize = $_FILES [ 'files' ][ 'size' ][ $source ];
// Call the validation functions.
$errors = array ();
foreach ( $validators as $function => $args ) {
array_unshift ( $args , $file );
$errors = array_merge ( $errors , call_user_func_array ( $function , $args ));
2003-12-26 23:03:21 +00:00
}
2007-05-30 08:08:59 +00:00
// Check for validation errors.
if ( ! empty ( $errors )) {
$message = t ( 'The selected file %name could not be uploaded. ' , array ( '%name' => $file -> filename ));
if ( count ( $errors ) > 1 ) {
$message .= '<ul><li>' . implode ( '</li><li>' , $errors ) . '</li></ul>' ;
}
else {
$message .= array_pop ( $errors );
2003-12-26 23:03:21 +00:00
}
2007-05-30 08:08:59 +00:00
form_set_error ( $source , $message );
return 0 ;
2003-12-26 23:03:21 +00:00
}
2007-05-30 08:08:59 +00:00
// 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 ;
}
// If we made it this far it's safe to record this file in the database.
2007-06-05 12:13:23 +00:00
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' );
2007-05-30 08:08:59 +00:00
// Add file to the cache.
$upload_cache [ $source ] = $file ;
return $file ;
2003-12-26 23:03:21 +00:00
}
return 0 ;
}
2007-05-30 08:08:59 +00:00
/**
* Check for files with names longer than we can store in the database .
*
* @ param $file
* A Drupal file object .
* @ return
* An array . If the file name is too long , it will contain an error message .
*/
function file_validate_name_length ( $file ) {
$errors = array ();
if ( strlen ( $file -> filename ) > 255 ) {
$errors [] = t ( 'Its name exceeds the 255 characters limit. Please rename the file and try again.' );
}
return $errors ;
}
/**
* Check that the filename ends with an allowed extension . This check is not
* enforced for the user #1.
*
* @ param $file
* A Drupal file object .
* @ param $extensions
* A string with a space separated
* @ return
* An array . If the file name is too long , it will contain an error message .
*/
function file_validate_extensions ( $file , $extensions ) {
global $user ;
$errors = array ();
// Bypass validation for uid = 1.
if ( $user -> uid != 1 ) {
$regex = '/\.(' . ereg_replace ( ' +' , '|' , preg_quote ( $extensions )) . ')$/i' ;
if ( ! preg_match ( $regex , $file -> filename )) {
$errors [] = t ( 'Only files with the following extensions are allowed: %files-allowed.' , array ( '%files-allowed' => $extensions ));
}
}
return $errors ;
}
/**
* Check that the file ' s size is below certain limits . This check is not
* enforced for the user #1.
*
* @ param $file
* A Drupal file object .
* @ param $file_limit
* An integer specifying the maximum file size in bytes . Zero indicates that
* no limit should be enforced .
* @ param $$user_limit
* An integer specifying the maximum number of bytes the user is allowed . Zero
* indicates that no limit should be enforced .
* @ return
* An array . If the file name is too long , it will contain an error message .
*/
function file_validate_size ( $file , $file_limit = 0 , $user_limit = 0 ) {
global $user ;
$errors = array ();
// Bypass validation for uid = 1.
if ( $user -> uid != 1 ) {
if ( $file_limit && $file -> filesize > $file_limit ) {
$errors [] = t ( 'The file is %filesize exceeding the maximum file size of %maxsize.' , array ( '%filesize' => format_size ( $file -> filesize ), '%maxsize' => format_size ( $file_limit )));
}
$total_size = file_space_used ( $user -> uid ) + $file -> filesize ;
if ( $user_limit && $total_size > $user_limit ) {
$errors [] = t ( 'The file is %filesize which would exceed your disk quota of %quota.' , array ( '%filesize' => format_size ( $file -> filesize ), '%quota' => format_size ( $user_limit )));
}
}
return $errors ;
}
/**
* Check that the file is recognized by image_get_info () as an image .
*
* @ param $file
* A Drupal file object .
* @ return
* An array . If the file is not an image , it will contain an error message .
*/
function file_validate_is_image ( & $file ) {
$errors = array ();
$info = image_get_info ( $file -> filepath );
if ( ! $info || empty ( $info [ 'extension' ])) {
$errors [] = t ( 'Only JPEG, PNG and GIF images are allowed.' );
}
return $errors ;
}
/**
* If the file is an image verify that its dimensions are within the specified
* maximum and minimum dimensions . Non - image files will be ignored .
*
* @ param $file
* A Drupal file object . This function may resize the file affecting its size .
* @ param $maximum_dimensions
* An optional string in the form WIDTHxHEIGHT e . g . '640x480' or '85x85' . If
* an image toolkit is installed the image will be resized down to these
* dimensions . A value of 0 indicates no restriction on size , so resizing
* will be attempted .
* @ param $minimum_dimensions
* An optional string in the form WIDTHxHEIGHT . This will check that the image
* meets a minimum size . A value of 0 indicates no restriction .
* @ return
* An array . If the file is an image and did not meet the requirements , it
* will contain an error message .
*/
function file_validate_image_resolution ( & $file , $maximum_dimensions = 0 , $minimum_dimensions = 0 ) {
$errors = array ();
// Check first that the file is an image.
if ( $info = image_get_info ( $file -> filepath )) {
if ( $maximum_dimensions ) {
// Check that it is smaller than the given dimensions.
list ( $width , $height ) = explode ( 'x' , $maximum_dimensions );
if ( $info [ 'width' ] > $width || $info [ 'height' ] > $height ) {
// Try to resize the image to fit the dimensions.
if ( image_get_toolkit () && image_scale ( $file -> filepath , $file -> filepath , $width , $height )) {
drupal_set_message ( t ( 'The image was resized to fit within the maximum allowed dimensions of %dimensions pixels.' , array ( '%dimensions' => $maximum_dimensions )));
// Clear the cached filesize and refresh the image information.
clearstatcache ();
$info = image_get_info ( $file -> filepath );
$file -> filesize = $info [ 'file_size' ];
}
else {
$errors [] = t ( 'The image is too large; the maximum dimensions are %dimensions pixels.' , array ( '%dimensions' => $maximum_dimensions ));
}
}
}
if ( $minimum_dimensions ) {
// Check that it is larger than the given dimensions.
list ( $width , $height ) = explode ( 'x' , $minimum_dimensions );
2007-07-25 17:41:27 +00:00
if ( $info [ 'width' ] < $width || $info [ 'height' ] < $height ) {
2007-05-30 08:08:59 +00:00
$errors [] = t ( 'The image is too small; the minimum dimensions are %dimensions pixels.' , array ( '%dimensions' => $minimum_dimensions ));
}
}
}
return $errors ;
}
2004-06-04 18:00:48 +00:00
/**
2006-03-08 23:42:55 +00:00
* Save a string to the specified destination .
2004-06-04 18:00:48 +00:00
*
2006-03-08 23:42:55 +00:00
* @ 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
2006-07-05 11:45:51 +00:00
* - FILE_EXISTS_ERROR - Do nothing and return FALSE .
2004-06-04 18:00:48 +00:00
*
* @ return A string containing the resulting filename or 0 on error
*/
2004-10-26 20:57:34 +00:00
function file_save_data ( $data , $dest , $replace = FILE_EXISTS_RENAME ) {
2005-11-12 09:23:50 +00:00
$temp = file_directory_temp ();
2004-06-04 18:00:48 +00:00
$file = tempnam ( $temp , 'file' );
2004-08-17 21:35:26 +00:00
if ( ! $fp = fopen ( $file , 'wb' )) {
2005-05-05 22:22:46 +00:00
drupal_set_message ( t ( 'The file could not be created.' ), 'error' );
2004-06-04 18:00:48 +00:00
return 0 ;
}
fwrite ( $fp , $data );
fclose ( $fp );
2004-09-13 23:30:58 +00:00
if ( ! file_move ( $file , $dest , $replace )) {
2004-06-04 18:00:48 +00:00
return 0 ;
}
return $file ;
}
2007-05-30 08:08:59 +00:00
/**
* 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 .
*/
function file_set_status ( & $file , $status ) {
if ( db_query ( 'UPDATE {files} SET status = %d WHERE fid = %d' , $status , $file -> fid )) {
$file -> status = $status ;
return TRUE ;
}
return FALSE ;
}
2003-12-26 23:03:21 +00:00
/**
* 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 .
*/
function file_transfer ( $source , $headers ) {
ob_end_clean ();
foreach ( $headers as $header ) {
2005-11-30 15:31:23 +00:00
// To prevent HTTP header injection, we delete new lines that are
// not followed by a space or a tab.
// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
$header = preg_replace ( '/\r?\n(?!\t| )/' , '' , $header );
2007-05-22 17:43:17 +00:00
drupal_set_header ( $header );
2003-12-26 23:03:21 +00:00
}
$source = file_create_path ( $source );
// Transfer file in 1024 byte chunks to save memory usage.
2005-05-06 09:31:45 +00:00
if ( $fd = fopen ( $source , 'rb' )) {
while ( ! feof ( $fd )) {
print fread ( $fd , 1024 );
}
fclose ( $fd );
}
else {
drupal_not_found ();
2003-12-26 23:03:21 +00:00
}
exit ();
}
/**
2006-03-08 23:42:55 +00:00
* Call modules that implement hook_file_download () to find out if a file is
2006-03-10 19:02:06 +00:00
* accessible and what headers it should be transferred with . If a module
* returns - 1 drupal_access_denied () will be returned . If one or more modules
* returned headers the download will start with the returned headers . If no
* modules respond drupal_not_found () will be returned .
2003-12-26 23:03:21 +00:00
*/
function file_download () {
2006-03-30 17:45:32 +00:00
// Merge remainder of arguments from GET['q'], into relative file path.
2006-03-29 06:49:25 +00:00
$args = func_get_args ();
$filepath = implode ( '/' , $args );
2006-04-11 11:33:15 +00:00
// Maintain compatibility with old ?file=paths saved in node bodies.
2006-03-29 06:49:25 +00:00
if ( isset ( $_GET [ 'file' ])) {
$filepath = $_GET [ 'file' ];
}
2006-03-10 19:02:06 +00:00
if ( file_exists ( file_create_path ( $filepath ))) {
$headers = module_invoke_all ( 'file_download' , $filepath );
if ( in_array ( - 1 , $headers )) {
2007-05-30 08:08:59 +00:00
return drupal_access_denied ();
2006-03-10 19:02:06 +00:00
}
if ( count ( $headers )) {
2007-05-30 08:08:59 +00:00
file_transfer ( $filepath , $headers );
2003-12-26 23:03:21 +00:00
}
}
2006-03-10 19:02:06 +00:00
return drupal_not_found ();
2003-12-26 23:03:21 +00:00
}
2006-03-10 19:02:06 +00:00
2003-12-26 23:03:21 +00:00
/**
2006-12-11 16:45:17 +00:00
* Finds all files that match a given mask in a given directory .
* Directories and files beginning with a period are excluded ; this
* prevents hidden files and directories ( such as SVN working directories )
* from being scanned .
2004-09-17 18:18:04 +00:00
*
2004-11-24 22:44:01 +00:00
* @ param $dir
2007-07-15 19:14:19 +00:00
* The base directory for the scan , without trailing slash .
2004-11-24 22:44:01 +00:00
* @ param $mask
* The regular expression of the files to find .
* @ param $nomask
* An array of files / directories to ignore .
* @ param $callback
* The callback function to call for each match .
* @ param $recurse
* When TRUE , the directory scan will recurse the entire tree
* starting at the provided directory .
* @ param $key
2006-05-07 00:08:36 +00:00
* The key to be used for the returned array of files . Possible
2004-11-24 22:44:01 +00:00
* values are " filename " , for the path starting with $dir ,
* " basename " , for the basename of the file , and " name " for the name
* of the file without an extension .
2005-03-08 22:06:11 +00:00
* @ param $min_depth
* Minimum depth of directories to return files from .
* @ param $depth
2005-03-31 21:18:08 +00:00
* Current depth of recursion . This parameter is only used internally and should not be passed .
2004-11-24 22:44:01 +00:00
*
* @ return
* An associative array ( keyed on the provided key ) of objects with
* " path " , " basename " , and " name " members corresponding to the
* matching files .
2003-12-26 23:03:21 +00:00
*/
2005-03-08 22:10:26 +00:00
function file_scan_directory ( $dir , $mask , $nomask = array ( '.' , '..' , 'CVS' ), $callback = 0 , $recurse = TRUE , $key = 'filename' , $min_depth = 0 , $depth = 0 ) {
2004-11-24 22:44:01 +00:00
$key = ( in_array ( $key , array ( 'filename' , 'basename' , 'name' )) ? $key : 'filename' );
2003-12-26 23:03:21 +00:00
$files = array ();
2004-11-24 22:44:01 +00:00
2003-12-26 23:03:21 +00:00
if ( is_dir ( $dir ) && $handle = opendir ( $dir )) {
while ( $file = readdir ( $handle )) {
2006-12-11 16:45:17 +00:00
if ( ! in_array ( $file , $nomask ) && $file [ 0 ] != '.' ) {
2004-09-17 18:18:04 +00:00
if ( is_dir ( " $dir / $file " ) && $recurse ) {
2005-03-08 22:10:26 +00:00
$files = array_merge ( $files , file_scan_directory ( " $dir / $file " , $mask , $nomask , $callback , $recurse , $key , $min_depth , $depth + 1 ));
2003-12-26 23:03:21 +00:00
}
2005-03-08 22:06:11 +00:00
elseif ( $depth >= $min_depth && ereg ( $mask , $file )) {
2004-11-24 22:44:01 +00:00
$filename = " $dir / $file " ;
$basename = basename ( $file );
$name = substr ( $basename , 0 , strrpos ( $basename , '.' ));
$files [ $$key ] = new stdClass ();
$files [ $$key ] -> filename = $filename ;
$files [ $$key ] -> basename = $basename ;
$files [ $$key ] -> name = $name ;
2003-12-26 23:03:21 +00:00
if ( $callback ) {
2004-11-24 22:44:01 +00:00
$callback ( $filename );
2003-12-26 23:03:21 +00:00
}
}
}
}
2004-11-24 22:44:01 +00:00
2003-12-26 23:03:21 +00:00
closedir ( $handle );
}
2004-11-24 22:44:01 +00:00
2003-12-26 23:03:21 +00:00
return $files ;
}
2005-11-12 09:23:50 +00:00
/**
* Determine the default temporary directory .
2006-03-08 23:42:55 +00:00
*
* @ return A string containing a temp directory .
2005-11-12 09:23:50 +00:00
*/
function file_directory_temp () {
$temporary_directory = variable_get ( 'file_directory_temp' , NULL );
if ( is_null ( $temporary_directory )) {
$directories = array ();
// Has PHP been set with an upload_tmp_dir?
if ( ini_get ( 'upload_tmp_dir' )) {
$directories [] = ini_get ( 'upload_tmp_dir' );
}
// Operating system specific dirs.
if ( substr ( PHP_OS , 0 , 3 ) == 'WIN' ) {
$directories [] = 'c:\\windows\\temp' ;
$directories [] = 'c:\\winnt\\temp' ;
$path_delimiter = '\\' ;
}
else {
$directories [] = '/tmp' ;
$path_delimiter = '/' ;
}
foreach ( $directories as $directory ) {
if ( ! $temporary_directory && is_dir ( $directory )) {
$temporary_directory = $directory ;
}
}
// if a directory has been found, use it, otherwise default to 'files/tmp' or 'files\\tmp';
2007-04-13 08:56:59 +00:00
$temporary_directory = $temporary_directory ? $temporary_directory : file_directory_path () . $path_delimiter . 'tmp' ;
2005-11-12 09:23:50 +00:00
variable_set ( 'file_directory_temp' , $temporary_directory );
}
return $temporary_directory ;
}
/**
* Determine the default 'files' directory .
2006-03-08 23:42:55 +00:00
*
* @ return A string containing the path to Drupal 's ' files ' directory .
2005-11-12 09:23:50 +00:00
*/
function file_directory_path () {
return variable_get ( 'file_directory_path' , 'files' );
}
2006-08-06 22:49:27 +00:00
/**
* Determine the maximum file upload size by querying the PHP settings .
*
* @ return
* A file size limit in MB based on the PHP upload_max_filesize and post_max_size
*/
function file_upload_max_size () {
static $max_size = - 1 ;
2005-08-25 21:14:17 +00:00
2006-08-06 22:49:27 +00:00
if ( $max_size < 0 ) {
2006-12-07 17:02:25 +00:00
$upload_max = parse_size ( ini_get ( 'upload_max_filesize' ));
2006-08-06 22:49:27 +00:00
// sanity check- a single upload should not be more than 50% the size limit of the total post
2006-12-07 17:02:25 +00:00
$post_max = parse_size ( ini_get ( 'post_max_size' )) / 2 ;
2006-08-06 22:49:27 +00:00
$max_size = ( $upload_max < $post_max ) ? $upload_max : $post_max ;
}
return $max_size ;
}