Newer
Older
Dries Buytaert
committed
// $Id$
/**
* @file
* API for handling file uploads and server file management.
*/
Dries Buytaert
committed
/**
* Stream wrapper code is included here because there are cases where
* File API is needed before a bootstrap, or in an alternate order (e.g.
* maintenance theme).
*/
require_once DRUPAL_ROOT . '/includes/stream_wrappers.inc';
Angie Byron
committed
*
* Fields on the file object:
* - fid - File ID
Angie Byron
committed
* - uid - The {users}.uid of the user who is associated with the file.
* - filename - Name of the file with no path components. This may differ from
* the basename of the filepath if the file is renamed to avoid overwriting
* an existing file.
* - uri - URI of the file.
Angie Byron
committed
* - filemime - The file's MIME type.
* - filesize - The size of the file in bytes.
Dries Buytaert
committed
* - status - A bitmapped field indicating the status of the file. The first 8
* bits are reserved for Drupal core. The least sigifigant bit indicates
* temporary (0) or permanent (1). Temporary files older than
* DRUPAL_MAXIMUM_TEMP_FILE_AGE will be removed during cron runs.
Angie Byron
committed
* - timestamp - UNIX timestamp for the date the file was added to the database.
* Flag used by file_prepare_directory() -- create directory if not present.
* Flag used by file_prepare_directory() -- file permissions may be changed.
Angie Byron
committed
* Flag for dealing with existing files: Appends number until name is unique.
/**
* Flag for dealing with existing files: Replace the existing file.
*/
/**
* Flag for dealing with existing files: Do nothing and return FALSE.
*/
/**
Dries Buytaert
committed
* File status -- This bit in the status indicates that the file is permanent
* and should not be deleted during file garbage collection process. Temporary
* files older than DRUPAL_MAXIMUM_TEMP_FILE_AGE will be removed during cron
* runs.
define('FILE_STATUS_PERMANENT', 1);
Dries Buytaert
committed
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
/**
* Methods to manage a registry of stream wrappers.
*/
/**
* Drupal stream wrapper registry.
*
* A stream wrapper is an abstraction of a file system that allows Drupal to
* use the same set of methods to access both local files and remote resources.
*
* Provide a facility for managing and querying user-defined stream wrappers
* in PHP. PHP's internal stream_get_wrappers() doesn't return the class
* registered to handle a stream, which we need to be able to find the handler
* for class instantiation.
*
* If a module registers a scheme that is already registered with PHP, the
* existing scheme will be unregistered and replaced with the specified class.
*
* A stream is referenced as "scheme://target".
*
* @return
* Returns the entire Drupal stream wrapper registry.
* @see hook_stream_wrappers()
* @see hook_stream_wrappers_alter()
*/
function file_get_stream_wrappers() {
$wrappers = &drupal_static(__FUNCTION__);
if (!isset($wrappers)) {
$wrappers = module_invoke_all('stream_wrappers');
drupal_alter('stream_wrappers', $wrappers);
$existing = stream_get_wrappers();
foreach ($wrappers as $scheme => $info) {
// We only register classes that implement our interface.
if (in_array('DrupalStreamWrapperInterface', class_implements($info['class']), TRUE)) {
// Record whether we are overriding an existing scheme.
if (in_array($scheme, $existing, TRUE)) {
$wrappers[$scheme]['override'] = TRUE;
stream_wrapper_unregister($scheme);
}
else {
$wrappers[$scheme]['override'] = FALSE;
}
stream_wrapper_register($scheme, $info['class']);
}
}
}
return $wrappers;
}
/**
* Returns the stream wrapper class name for a given scheme.
*
* @param $scheme
* Stream scheme.
* @return
* Return string if a scheme has a registered handler, or FALSE.
*/
function file_stream_wrapper_get_class($scheme) {
$wrappers = file_get_stream_wrappers();
return empty($wrappers[$scheme]) ? FALSE : $wrappers[$scheme]['class'];
}
/**
* Returns the scheme of a URI (e.g. a stream).
*
* @param $uri
* A stream, referenced as "scheme://target".
* @return
* A string containing the name of the scheme, or FALSE if none. For example,
* the URI "public://example.txt" would return "public".
*/
function file_uri_scheme($uri) {
$data = explode('://', $uri, 2);
return count($data) == 2 ? $data[0] : FALSE;
}
/**
* Check that the scheme of a stream URI is valid.
*
* Confirms that there is a registered stream handler for the provided scheme
* and that it is callable. This is useful if you want to confirm a valid
* scheme without creating a new instance of the registered handler.
*
* @param $scheme
* A URI scheme, a stream is referenced as "scheme://target".
* @return
* Returns TRUE if the string is the name of a validated stream,
* or FALSE if the scheme does not have a registered handler.
*/
function file_stream_wrapper_valid_scheme($scheme) {
// Does the scheme have a registered handler that is callable?
$class = file_stream_wrapper_get_class($scheme);
if (class_exists($class)) {
return TRUE;
}
else {
return FALSE;
}
}
/**
* Returns the target of a URI (e.g. a stream).
*
* @param $uri
* A stream, referenced as "scheme://target".
* @return
* A string containing the target (path), or FALSE if none.
* For example, the URI "public://sample/test.txt" would return
* "sample/test.txt".
*/
function file_uri_target($uri) {
$data = explode('://', $uri, 2);
if (count($data) != 2) {
return FALSE;
}
// Remove erroneous beginning forward slash.
$data[1] = ltrim($data[1], '\/');
return $data[1];
}
/**
* Normalizes a URI by making it syntactically correct.
*
* A stream is referenced as "scheme://target".
*
* The following actions are taken:
* - Remove all occurrences of the wrapper's directory path
* - Remove trailing slashes from target
* - Trim erroneous leading slashes from target. e.g. ":///" becomes "://".
*
* @param $uri
* String reference containing the URI to normalize.
* @return
* The normalized URI.
Dries Buytaert
committed
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
*/
function file_stream_wrapper_uri_normalize($uri) {
$scheme = file_uri_scheme($uri);
if ($scheme && file_stream_wrapper_valid_scheme($scheme)) {
$target = file_uri_target($uri);
// Remove all occurrences of the wrapper's directory path.
$directory_path = file_stream_wrapper_get_instance_by_scheme($scheme)->getDirectoryPath();
$target = str_replace($directory_path, '', $target);
// Trim trailing slashes from target.
$target = rtrim($target, '/');
// Trim erroneous leading slashes from target.
$uri = $scheme . '://' . ltrim($target, '/');
}
return $uri;
}
/**
* Returns a reference to the stream wrapper class responsible for a given URI (stream).
*
* The scheme determines the stream wrapper class that should be
* used by consulting the stream wrapper registry.
*
* @param $uri
* A stream, referenced as "scheme://target".
* @return
* Returns a new stream wrapper object appropriate for the given URI or FALSE
* if no registered handler could be found. For example, a URI of
* "private://example.txt" would return a new private stream wrapper object
* (DrupalPrivateStreamWrapper).
*/
function file_stream_wrapper_get_instance_by_uri($uri) {
$scheme = file_uri_scheme($uri);
$class = file_stream_wrapper_get_class($scheme);
if (class_exists($class)) {
$instance = new $class;
$instance->setUri($uri);
return $instance;
}
else {
return FALSE;
}
}
/**
* Returns a reference to the stream wrapper class responsible for a given scheme.
*
* This helper method returns a stream instance using a scheme. That is, the
* passed string does not contain a "://". For example, "public" is a scheme
* but "public://" is a URI (stream). This is because the later contains both
* a scheme and target despite target being empty.
*
* Note: the instance URI will be initialized to "scheme://" so that you can
* make the customary method calls as if you had retrieved an instance by URI.
*
* @param $scheme
* If the stream was "public://target", "public" would be the scheme.
* @return
* Returns a new stream wrapper object appropriate for the given $scheme.
* For example, for the public scheme a stream wrapper object
* (DrupalPublicStreamWrapper).
* FALSE is returned if no registered handler could be found.
*/
function file_stream_wrapper_get_instance_by_scheme($scheme) {
$class = file_stream_wrapper_get_class($scheme);
if (class_exists($class)) {
$instance = new $class;
$instance->setUri($scheme . '://');
return $instance;
}
else {
return FALSE;
}
}
* Creates the web accessible URL to a stream.
* Compatibility: normal paths and stream wrappers.
* @see http://drupal.org/node/515192
* @param $uri
* The URI to for which we need an external URL.
* A string containing a URL that may be used to access the file.
* If the provided string already contains a preceding 'http', nothing is done
* and the same string is returned. If a valid stream wrapper could not be
* found to generate an external URL, then FALSE will be returned.
function file_create_url($uri) {
$scheme = file_uri_scheme($uri);
if (!$scheme) {
// If this is not a properly formatted stream return the URI with the base
// url prepended.
return $GLOBALS['base_url'] . '/' . $uri;
}
elseif ($scheme == 'http' || $scheme == 'https') {
// Check for http so that we don't have to implement getExternalUrl() for
// the http wrapper.
return $uri;
}
else {
// Attempt to return an external URL using the appropriate wrapper.
if ($wrapper = file_stream_wrapper_get_instance_by_uri($uri)) {
return $wrapper->getExternalUrl();
}
else {
return FALSE;
}
}
// @todo Implement CDN integration hook stuff in this function.
// @see http://drupal.org/node/499156
* 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 reference containing the name of a directory path or URI. A
* trailing slash will be trimmed from a path.
* @param $options
Dries Buytaert
committed
* A bitmask to indicate if the directory should be created if it does
* not exist (FILE_CREATE_DIRECTORY) or made writable if it is read-only
* (FILE_MODIFY_PERMISSIONS).
* TRUE if the directory exists (or was created) and is writable. FALSE
* otherwise.
function file_prepare_directory(&$directory, $options = FILE_MODIFY_PERMISSIONS) {
if (!file_stream_wrapper_valid_scheme(file_uri_scheme($directory))) {
// Only trim if we're not dealing with a stream.
$directory = rtrim($directory, '/\\');
}
Angie Byron
committed
// Let mkdir() recursively create directories and use the default directory
// permissions.
if (($options & FILE_CREATE_DIRECTORY) && @drupal_mkdir($directory, NULL, TRUE)) {
return drupal_chmod($directory);
return FALSE;
// The directory exists, so check to see if it is writable.
$writable = is_writable($directory);
if (!$writable && ($options & FILE_MODIFY_PERMISSIONS)) {
return drupal_chmod($directory);
return $writable;
* If missing, create a .htaccess file in each Drupal files directory.
function file_ensure_htaccess() {
file_create_htaccess('public://', FALSE);
file_create_htaccess('private://', TRUE);
file_create_htaccess('temporary://', TRUE);
* Creates an .htaccess file in the given directory.
* @param $directory
* The directory.
* @param $private
* FALSE indicates that $directory should be an open and public directory.
* The default is TRUE which indicates a private and protected directory.
function file_create_htaccess($directory, $private = TRUE) {
if (file_uri_scheme($directory)) {
$directory = file_stream_wrapper_uri_normalize($directory);
}
else {
$directory = rtrim($directory, '/\\');
$htaccess_path = $directory . '/.htaccess';
if (file_exists($htaccess_path)) {
// Short circuit if the .htaccess file already exists.
return;
}
if ($private) {
// Private .htaccess file.
$htaccess_lines = "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006\nDeny from all\nOptions None\nOptions +FollowSymLinks";
}
else {
// Public .htaccess file.
$htaccess_lines = "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006\nOptions None\nOptions +FollowSymLinks";
}
// Write the .htaccess file.
if (file_put_contents($htaccess_path, $htaccess_lines)) {
drupal_chmod($htaccess_path, 0444);
}
else {
$variables = array('%directory' => $directory, '!htaccess' => '<br />' . nl2br(check_plain($htaccess_lines)));
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);
Dries Buytaert
committed
* Load file objects from the database.
Dries Buytaert
committed
* @param $fids
* An array of file IDs.
* @param $conditions
* An array of conditions to match against the {files} table. These
* should be supplied in the form array('field_name' => 'field_value').
* @return
Dries Buytaert
committed
* An array of file objects, indexed by fid.
*
* @see hook_file_load()
Dries Buytaert
committed
* @see file_load()
*/
Dries Buytaert
committed
function file_load_multiple($fids = array(), $conditions = array()) {
Dries Buytaert
committed
// If they don't provide any criteria return nothing rather than all files.
if (!$fids && !$conditions) {
return array();
}
$query = db_select('file', 'f')->fields('f');
Dries Buytaert
committed
// If the $fids array is populated, add those to the query.
if ($fids) {
$query->condition('f.fid', $fids, 'IN');
}
Dries Buytaert
committed
// If the conditions array is populated, add those to the query.
if ($conditions) {
foreach ($conditions as $field => $value) {
$query->condition('f.' . $field, $value);
}
}
Dries Buytaert
committed
$files = $query->execute()->fetchAllAssoc('fid');
Dries Buytaert
committed
// Invoke hook_file_load() on the terms loaded from the database
// and add them to the static cache.
if (!empty($files)) {
foreach (module_implements('file_load') as $module) {
$function = $module . '_file_load';
$function($files);
}
Dries Buytaert
committed
return $files;
}
Dries Buytaert
committed
/**
* Load a file object from the database.
*
* @param $fid
* A file ID.
* @return
* A file object.
*
* @see hook_file_load()
* @see file_load_multiple()
*/
function file_load($fid) {
$files = file_load_multiple(array($fid), array());
return reset($files);
}
/**
* 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 returned by file_load().
* @return
* The updated file object.
* @see hook_file_insert()
* @see hook_file_update()
*/
function file_save($file) {
$file = (object)$file;
$file->timestamp = REQUEST_TIME;
$file->filesize = filesize($file->uri);
if (empty($file->fid)) {
drupal_write_record('file', $file);
// Inform modules about the newly added file.
module_invoke_all('file_insert', $file);
}
else {
drupal_write_record('file', $file, 'fid');
// Inform modules that the file has been updated.
module_invoke_all('file_update', $file);
}
return $file;
}
/**
* Copy a file to a new location and adds a file record to the database.
*
* This function should be used when manipulating files that have records
* stored in the database. This is a powerful function that in many ways
* performs like an advanced version of copy().
* - Checks if $source and $destination are valid and readable/writable.
* - Checks that $source is not equal to $destination; if they are an error
* is reported.
* - If file already exists in $destination 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. If the source file is a
* temporary file, the resulting file will also be a temporary file.
* @see file_save_upload() for details on temporary files.
*
* @param $source
* A file object.
* @param $destination
* A string containing the destination that $source should be copied to.
* This should be a stream wrapper URI. If this value is omitted, Drupal's
* public files scheme will be used, "public://".
* @param $replace
* Replace behavior when the destination file already exists:
* - FILE_EXISTS_REPLACE - Replace the existing file. If a managed file with
* the destination name exists then its database entry will be updated. If
* no database entry is found then a new one will be created.
* - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
* unique.
* - FILE_EXISTS_ERROR - Do nothing and return FALSE.
* @return
* File object if the copy is successful, or FALSE in the event of an error.
* @see file_unmanaged_copy()
* @see hook_file_copy()
*/
function file_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
$source = (object)$source;
if ($uri = file_unmanaged_copy($source->uri, $destination, $replace)) {
$file = clone $source;
$file->fid = NULL;
$file->uri = $uri;
$file->filename = basename($uri);
// If we are replacing an existing file re-use its database record.
if ($replace == FILE_EXISTS_REPLACE) {
$existing_files = file_load_multiple(array(), array('uri' => $uri));
if (count($existing_files)) {
$existing = reset($existing_files);
$file->fid = $existing->fid;
$file->filename = $existing->filename;
}
// If we are renaming around an existing file (rather than a directory),
// use its basename for the filename.
elseif ($replace == FILE_EXISTS_RENAME && is_file($destination)) {
$file->filename = basename($destination);
}
$file = file_save($file);
// Inform modules that the file has been copied.
module_invoke_all('file_copy', $file, $source);
return $file;
}
return FALSE;
}
/**
* Copy a file to a new location without calling any hooks or making any
* changes to the database.
Angie Byron
committed
* This is a powerful function that in many ways performs like an advanced
* version of copy().
* - Checks if $source and $destination are valid and readable/writable.
* - Checks that $source is not equal to $destination; if they are an error
* is reported.
* - If file already exists in $destination either the call will error out,
* replace the file or rename the file based on the $replace parameter.
*
* @param $source
* A string specifying the filepath or URI of the original file.
Angie Byron
committed
* @param $destination
* A URI containing the destination that $source should be copied to. If
* NULL the default scheme will be used as the destination.
Angie Byron
committed
* @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.
Angie Byron
committed
* @return
* The path to the new file, or FALSE in the event of an error.
* @see file_copy()
function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
Dries Buytaert
committed
$original_source = $source;
$original_destination = $destination;
// Assert that the source file actually exists.
$source = drupal_realpath($source);
Angie Byron
committed
if (!file_exists($source)) {
// @todo Replace drupal_set_message() calls with exceptions instead.
Dries Buytaert
committed
drupal_set_message(t('The specified file %file could not be copied, because no file by that name exists. Please check that you supplied the correct filename.', array('%file' => $original_source)), 'error');
Angie Byron
committed
return FALSE;
}
// Build a destination URI if necessary.
if (!isset($destination)) {
$destination = file_build_uri(basename($source));
}
// Assert that the destination contains a valid stream.
$destination_scheme = file_uri_scheme($destination);
if (!$destination_scheme || !file_stream_wrapper_valid_scheme($destination_scheme)) {
drupal_set_message(t('The specified file %file could not be copied, because the destination %destination is invalid. This is often caused by improper use of file_unmanaged_copy() or a missing stream wrapper.', array('%file' => $original_source, '%destination' => $destination)), 'error');
// Prepare the destination directory.
if (file_prepare_directory($destination)) {
// The destination is already a directory, so append the source basename.
$destination = file_stream_wrapper_uri_normalize($destination . '/' . basename($source));
}
else {
// Perhaps $destination is a dir/file?
$dirname = drupal_dirname($destination);
if (!file_prepare_directory($dirname)) {
// The destination is not valid.
drupal_set_message(t('The specified file %file could not be copied, because the destination %directory is not properly configured. This is often caused by a problem with file or directory permissions.', array('%file' => $original_source, '%directory' => $destination)), 'error');
return FALSE;
}
}
// Determine whether we can perform this operation based on overwrite rules.
$destination = file_destination($destination, $replace);
Angie Byron
committed
if ($destination === FALSE) {
drupal_set_message(t('The file %file could not be copied because a file by that name already exists in the destination directory (%directory)', array('%file' => $source, '%directory' => $destination)), 'error');
Angie Byron
committed
return FALSE;
// Assert that the source and destination filenames are not the same.
if (drupal_realpath($source) == drupal_realpath($destination)) {
Angie Byron
committed
drupal_set_message(t('The specified file %file was not copied because it would overwrite itself.', array('%file' => $source)), 'error');
return FALSE;
// Make sure the .htaccess files are present.
file_ensure_htaccess();
// Perform the copy operation.
Angie Byron
committed
if (!@copy($source, $destination)) {
drupal_set_message(t('The specified file %file could not be copied.', array('%file' => $source)), 'error');
return FALSE;
// Set the permissions on the new file.
drupal_chmod($destination);
Angie Byron
committed
return $destination;
/**
* Given a relative path, construct a URI into Drupal's default files location.
*/
function file_build_uri($path) {
$uri = variable_get('file_scheme_default', 'public') . '://' . $path;
return file_stream_wrapper_uri_normalize($uri);
}
/**
* Determines the destination path for a file depending on how replacement of
* existing files should be handled.
*
* @param $destination
* A string specifying the desired final URI or filepath.
* @param $replace
* Replace behavior when the destination file already exists.
Angie Byron
committed
* - 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.
* The destination filepath, or FALSE if the file already exists
* and FILE_EXISTS_ERROR is specified.
*/
function file_destination($destination, $replace) {
if (file_exists($destination)) {
switch ($replace) {
Angie Byron
committed
case FILE_EXISTS_REPLACE:
// Do nothing here, we want to overwrite the existing file.
break;
case FILE_EXISTS_RENAME:
$basename = basename($destination);
$directory = drupal_dirname($destination);
$destination = file_create_filename($basename, $directory);
break;
case FILE_EXISTS_ERROR:
// Error reporting handled by calling function.
return FALSE;
}
}
return $destination;
}
* Move a file to a new location and update the file's database entry.
*
* Moving a file is performed by copying the file to the new location and then
* deleting the original.
* - Checks if $source and $destination are valid and readable/writable.
* - Performs a file move if $source is not equal to $destination.
* - If file already exists in $destination 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 file object.
* @param $destination
* A string containing the destination that $source should be moved to. This
* must be a URI matching a Drupal stream wrapper. 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. If a managed file with
* the destination name exists then its database entry will be updated and
* file_delete() called on the source file after hook_file_move is called.
* If no database entry is found then the source files record will be
* updated.
* - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
* unique.
* - FILE_EXISTS_ERROR - Do nothing and return FALSE.
* @return
* Resulting file object for success, or FALSE in the event of an error.
* @see file_unmanaged_move()
* @see hook_file_move()
*/
function file_move($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
$source = (object)$source;
if ($uri = file_unmanaged_move($source->uri, $destination, $replace)) {
$delete_source = FALSE;
$file = clone $source;
$file->uri = $uri;
// If we are replacing an existing file re-use its database record.
if ($replace == FILE_EXISTS_REPLACE) {
$existing_files = file_load_multiple(array(), array('uri' => $uri));
if (count($existing_files)) {
$existing = reset($existing_files);
$delete_source = TRUE;
$file->fid = $existing->fid;
}
}
// If we are renaming around an existing file (rather than a directory),
// use its basename for the filename.
elseif ($replace == FILE_EXISTS_RENAME && is_file($destination)) {
$file->filename = basename($destination);
$file = file_save($file);
// Inform modules that the file has been moved.
module_invoke_all('file_move', $file, $source);
if ($delete_source) {
// Try a soft delete to remove original if it's not in use elsewhere.
file_delete($source);
}
return $file;
}
return FALSE;
}
/**
* Move a file to a new location without calling any hooks or making any
* changes to the database.
Angie Byron
committed
* @param $source
* A string specifying the filepath or URI of the original file.
Angie Byron
committed
* @param $destination
* A string containing the destination that $source should be moved to. This
* must be a URI matching a Drupal stream wrapper. If this value is omitted,
* Drupal's 'files' directory will be used.
Angie Byron
committed
* @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.
Angie Byron
committed
* @return
* The URI of the moved file, or FALSE in the event of an error.
* @see file_move()
function file_unmanaged_move($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
$filepath = file_unmanaged_copy($source, $destination, $replace);
if ($filepath == FALSE || file_unmanaged_delete($source) == FALSE) {
Angie Byron
committed
return FALSE;
Angie Byron
committed
return $filepath;
/**
* 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 with the filename to be unmunged.
* @return
* An unmunged filename string.
*/
function file_unmunge_filename($filename) {
return str_replace('_.', '.', $filename);
}
/**
* 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 containing the directory or parent URI.
* @return
* File path consisting of $directory and a unique filename based off
* of $basename.
*/
// A URI or path may already have a trailing slash or look like "public://".
if (substr($directory, -1) == '/') {
$separator = '';
}
else {
$separator = '/';
}
$destination = $directory . $separator . $basename;
Angie Byron
committed
if (file_exists($destination)) {
Angie Byron
committed
$pos = strrpos($basename, '.');
if ($pos !== FALSE) {
$name = substr($basename, 0, $pos);
$ext = substr($basename, $pos);
}
else {
$name = $basename;
Angie Byron
committed
$ext = '';
$destination = $directory . $separator . $name . '_' . $counter++ . $ext;
Angie Byron
committed
} while (file_exists($destination));
Angie Byron
committed
return $destination;
/**
* Delete a file and its database record.
*
* If the $force parameter is not TRUE hook_file_references() will be called
* to determine if the file is being used by any modules. If the file is being
* used is the delete will be canceled.
*
* @param $file
* A file object.
* @param $force
* Boolean indicating that the file should be deleted even if
* hook_file_references() reports that the file is in use.
* @return mixed
* TRUE for success, FALSE in the event of an error, or an array if the file
* is being used by another module. The array keys are the module's name and
* the values are the number of references.
* @see file_unmanaged_delete()
* @see hook_file_references()
* @see hook_file_delete()
*/
function file_delete($file, $force = FALSE) {
$file = (object)$file;
// 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_references', $file))) {
return $references;
}
// Let other modules clean up any references to the deleted file.
module_invoke_all('file_delete', $file);
// Make sure the file is deleted before removing its row from the
// database, so UIs can still find the file in the database.
if (file_unmanaged_delete($file->uri)) {
db_delete('file')->condition('fid', $file->fid)->execute();
return TRUE;
}
return FALSE;
}
/**
* Delete a file without calling any hooks or making any changes to the
* database.
*
* This function should be used when the file to be deleted does not have an
* entry recorded in the files table.
*
Angie Byron
committed
* @param $path
* A string containing a filepath or URI.
Angie Byron
committed
* @return
* TRUE for success or path does not exist, or FALSE in the event of an
* error.
* @see file_delete()
Angie Byron
committed
* @see file_unmanaged_delete_recursive()
*/
function file_unmanaged_delete($path) {
Angie Byron
committed
if (is_dir($path)) {
watchdog('file', '%path is a directory and cannot be removed using file_unmanaged_delete().', array('%path' => $path), WATCHDOG_ERROR);
Angie Byron
committed
return FALSE;
}
if (is_file($path)) {
// Return TRUE for non-existent file, but log that nothing was actually
Angie Byron
committed
// deleted, as the current state is the indended result.
if (!file_exists($path)) {
watchdog('file', 'The file %path was not deleted, because it does not exist.', array('%path' => $path), WATCHDOG_NOTICE);
Angie Byron
committed
return TRUE;
}
Angie Byron
committed
// We cannot handle anything other than files and directories. Log an error
// for everything else (sockets, symbolic links, etc).
watchdog('file', 'The file %path is not of a recognized type so it was not deleted.', array('%path' => $path), WATCHDOG_ERROR);
Angie Byron
committed
return FALSE;