5 * API for handling file uploads and server file management.
8 use Drupal\Component\FileSystem\FileSystem as ComponentFileSystem;
9 use Drupal\Component\Utility\Unicode;
10 use Drupal\Component\Utility\UrlHelper;
11 use Drupal\Component\PhpStorage\FileStorage;
12 use Drupal\Component\Utility\Bytes;
13 use Drupal\Core\File\FileSystem;
14 use Drupal\Core\Site\Settings;
15 use Drupal\Core\StreamWrapper\PublicStream;
16 use Drupal\Core\StreamWrapper\PrivateStream;
19 * Default mode for new directories. See drupal_chmod().
21 * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
22 * Use \Drupal\Core\File\FileSystem::CHMOD_DIRECTORY.
24 * @see https://www.drupal.org/node/2418133
26 const FILE_CHMOD_DIRECTORY = FileSystem::CHMOD_DIRECTORY;
29 * Default mode for new files. See drupal_chmod().
31 * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
32 * Use \Drupal\Core\File\FileSystem::CHMOD_FILE.
34 * @see https://www.drupal.org/node/2418133
36 const FILE_CHMOD_FILE = FileSystem::CHMOD_FILE;
39 * @defgroup file File interface
41 * Common file handling functions.
45 * Flag used by file_prepare_directory() -- create directory if not present.
47 const FILE_CREATE_DIRECTORY = 1;
50 * Flag used by file_prepare_directory() -- file permissions may be changed.
52 const FILE_MODIFY_PERMISSIONS = 2;
55 * Flag for dealing with existing files: Appends number until name is unique.
57 const FILE_EXISTS_RENAME = 0;
60 * Flag for dealing with existing files: Replace the existing file.
62 const FILE_EXISTS_REPLACE = 1;
65 * Flag for dealing with existing files: Do nothing and return FALSE.
67 const FILE_EXISTS_ERROR = 2;
70 * Indicates that the file is permanent and should not be deleted.
72 * Temporary files older than the system.file.temporary_maximum_age
73 * configuration value will be, if clean-up not disabled, removed during cron
74 * runs, but permanent files will not be removed during the file garbage
77 const FILE_STATUS_PERMANENT = 1;
80 * Returns the scheme of a URI (e.g. a stream).
82 * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
83 * Use \Drupal\Core\File\FileSystem::uriScheme().
85 * @see https://www.drupal.org/node/2418133
87 function file_uri_scheme($uri) {
88 return \Drupal::service('file_system')->uriScheme($uri);
92 * Checks that the scheme of a stream URI is valid.
94 * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
95 * Use \Drupal\Core\File\FileSystem::validScheme().
97 * @see https://www.drupal.org/node/2418133
99 function file_stream_wrapper_valid_scheme($scheme) {
100 return \Drupal::service('file_system')->validScheme($scheme);
105 * Returns the part of a URI after the schema.
108 * A stream, referenced as "scheme://target" or "data:target".
110 * @return string|bool
111 * A string containing the target (path), or FALSE if none.
112 * For example, the URI "public://sample/test.txt" would return
115 * @see file_uri_scheme()
117 function file_uri_target($uri) {
118 // Remove the scheme from the URI and remove erroneous leading or trailing,
119 // forward-slashes and backslashes.
120 $target = trim(preg_replace('/^[\w\-]+:\/\/|^data:/', '', $uri), '\/');
122 // If nothing was replaced, the URI doesn't have a valid scheme.
123 return $target !== $uri ? $target : FALSE;
127 * Gets the default file stream implementation.
130 * 'public', 'private' or any other file scheme defined as the default.
132 function file_default_scheme() {
133 return \Drupal::config('system.file')->get('default_scheme');
137 * Normalizes a URI by making it syntactically correct.
139 * A stream is referenced as "scheme://target".
141 * The following actions are taken:
142 * - Remove trailing slashes from target
143 * - Trim erroneous leading slashes from target. e.g. ":///" becomes "://".
146 * String reference containing the URI to normalize.
149 * The normalized URI.
151 function file_stream_wrapper_uri_normalize($uri) {
152 $scheme = \Drupal::service('file_system')->uriScheme($uri);
154 if (file_stream_wrapper_valid_scheme($scheme)) {
155 $target = file_uri_target($uri);
157 if ($target !== FALSE) {
158 $uri = $scheme . '://' . $target;
166 * Creates a web-accessible URL for a stream to an external or local file.
168 * Compatibility: normal paths and stream wrappers.
170 * There are two kinds of local files:
171 * - "managed files", i.e. those stored by a Drupal-compatible stream wrapper.
172 * These are files that have either been uploaded by users or were generated
173 * automatically (for example through CSS aggregation).
174 * - "shipped files", i.e. those outside of the files directory, which ship as
175 * part of Drupal core or contributed modules or themes.
178 * The URI to a file for which we need an external URL, or the path to a
182 * A string containing a URL that may be used to access the file.
183 * If the provided string already contains a preceding 'http', 'https', or
184 * '/', nothing is done and the same string is returned. If a stream wrapper
185 * could not be found to generate an external URL, then FALSE is returned.
187 * @see https://www.drupal.org/node/515192
188 * @see file_url_transform_relative()
190 function file_create_url($uri) {
191 // Allow the URI to be altered, e.g. to serve a file from a CDN or static
193 \Drupal::moduleHandler()->alter('file_url', $uri);
195 $scheme = \Drupal::service('file_system')->uriScheme($uri);
199 // - root-relative URIs (e.g. /foo.jpg in http://example.com/foo.jpg)
200 // - protocol-relative URIs (e.g. //bar.jpg, which is expanded to
201 // http://example.com/bar.jpg by the browser when viewing a page over
202 // HTTP and to https://example.com/bar.jpg when viewing a HTTPS page)
203 // Both types of relative URIs are characterized by a leading slash, hence
204 // we can use a single check.
205 if (Unicode::substr($uri, 0, 1) == '/') {
209 // If this is not a properly formatted stream, then it is a shipped file.
210 // Therefore, return the urlencoded URI with the base URL prepended.
211 $options = UrlHelper::parse($uri);
212 $path = $GLOBALS['base_url'] . '/' . UrlHelper::encodePath($options['path']);
214 if ($options['query']) {
215 $path .= '?' . UrlHelper::buildQuery($options['query']);
219 if ($options['fragment']) {
220 $path .= '#' . $options['fragment'];
226 elseif ($scheme == 'http' || $scheme == 'https' || $scheme == 'data') {
227 // Check for HTTP and data URI-encoded URLs so that we don't have to
228 // implement getExternalUrl() for the HTTP and data schemes.
232 // Attempt to return an external URL using the appropriate wrapper.
233 if ($wrapper = \Drupal::service('stream_wrapper_manager')->getViaUri($uri)) {
234 return $wrapper->getExternalUrl();
243 * Transforms an absolute URL of a local file to a relative URL.
245 * May be useful to prevent problems on multisite set-ups and prevent mixed
246 * content errors when using HTTPS + HTTP.
248 * @param string $file_url
249 * A file URL of a local file as generated by file_create_url().
252 * If the file URL indeed pointed to a local file and was indeed absolute,
253 * then the transformed, relative URL to the local file. Otherwise: the
254 * original value of $file_url.
256 * @see file_create_url()
258 function file_url_transform_relative($file_url) {
259 // Unfortunately, we pretty much have to duplicate Symfony's
260 // Request::getHttpHost() method because Request::getPort() may return NULL
261 // instead of a port number.
262 $request = \Drupal::request();
263 $host = $request->getHost();
264 $scheme = $request->getScheme();
265 $port = $request->getPort() ?: 80;
266 if (('http' == $scheme && $port == 80) || ('https' == $scheme && $port == 443)) {
270 $http_host = $host . ':' . $port;
273 return preg_replace('|^https?://' . $http_host . '|', '', $file_url);
277 * Checks that the directory exists and is writable.
279 * Directories need to have execute permissions to be considered a directory by
283 * A string reference containing the name of a directory path or URI. A
284 * trailing slash will be trimmed from a path.
286 * A bitmask to indicate if the directory should be created if it does
287 * not exist (FILE_CREATE_DIRECTORY) or made writable if it is read-only
288 * (FILE_MODIFY_PERMISSIONS).
291 * TRUE if the directory exists (or was created) and is writable. FALSE
294 function file_prepare_directory(&$directory, $options = FILE_MODIFY_PERMISSIONS) {
295 if (!file_stream_wrapper_valid_scheme(\Drupal::service('file_system')->uriScheme($directory))) {
296 // Only trim if we're not dealing with a stream.
297 $directory = rtrim($directory, '/\\');
300 // Check if directory exists.
301 if (!is_dir($directory)) {
302 // Let mkdir() recursively create directories and use the default directory
304 if ($options & FILE_CREATE_DIRECTORY) {
305 return @drupal_mkdir($directory, NULL, TRUE);
309 // The directory exists, so check to see if it is writable.
310 $writable = is_writable($directory);
311 if (!$writable && ($options & FILE_MODIFY_PERMISSIONS)) {
312 return drupal_chmod($directory);
319 * Creates a .htaccess file in each Drupal files directory if it is missing.
321 function file_ensure_htaccess() {
322 file_save_htaccess('public://', FALSE);
323 $private_path = PrivateStream::basePath();
324 if (!empty($private_path)) {
325 file_save_htaccess('private://', TRUE);
327 file_save_htaccess('temporary://', TRUE);
329 // If a staging directory exists then it should contain a .htaccess file.
330 // @todo https://www.drupal.org/node/2696103 catch a more specific exception
331 // and simplify this code.
333 $staging = config_get_config_directory(CONFIG_SYNC_DIRECTORY);
335 catch (\Exception $e) {
339 // Note that we log an error here if we can't write the .htaccess file. This
340 // can occur if the staging directory is read-only. If it is then it is the
341 // user's responsibility to create the .htaccess file.
342 file_save_htaccess($staging, TRUE);
347 * Creates a .htaccess file in the given directory.
349 * @param string $directory
351 * @param bool $private
352 * (Optional) FALSE indicates that $directory should be a web-accessible
353 * directory. Defaults to TRUE which indicates a private directory.
354 * @param bool $force_overwrite
355 * (Optional) Set to TRUE to attempt to overwrite the existing .htaccess file
356 * if one is already present. Defaults to FALSE.
358 function file_save_htaccess($directory, $private = TRUE, $force_overwrite = FALSE) {
359 if (\Drupal::service('file_system')->uriScheme($directory)) {
360 $htaccess_path = file_stream_wrapper_uri_normalize($directory . '/.htaccess');
363 $directory = rtrim($directory, '/\\');
364 $htaccess_path = $directory . '/.htaccess';
367 if (file_exists($htaccess_path) && !$force_overwrite) {
368 // Short circuit if the .htaccess file already exists.
371 $htaccess_lines = FileStorage::htaccessLines($private);
373 // Write the .htaccess file.
374 if (file_exists($directory) && is_writable($directory) && file_put_contents($htaccess_path, $htaccess_lines)) {
375 return drupal_chmod($htaccess_path, 0444);
378 $variables = ['%directory' => $directory, '@htaccess' => $htaccess_lines];
379 \Drupal::logger('security')->error("Security warning: Couldn't write .htaccess file. Please create a .htaccess file in your %directory directory which contains the following lines: <pre><code>@htaccess</code></pre>", $variables);
385 * Returns the standard .htaccess lines that Drupal writes to file directories.
387 * @param bool $private
388 * (Optional) Set to FALSE to return the .htaccess lines for a web-accessible
389 * public directory. The default is TRUE, which returns the .htaccess lines
390 * for a private directory that should not be web-accessible.
393 * The desired contents of the .htaccess file.
395 * @deprecated in Drupal 8.0.x-dev and will be removed before Drupal 9.0.0.
396 * Use \Drupal\Component\PhpStorage\FileStorage::htaccessLines().
398 * @see https://www.drupal.org/node/2418133
400 function file_htaccess_lines($private = TRUE) {
401 return FileStorage::htaccessLines($private);
405 * Determines whether the URI has a valid scheme for file API operations.
407 * There must be a scheme and it must be a Drupal-provided scheme like
408 * 'public', 'private', 'temporary', or an extension provided with
409 * hook_stream_wrappers().
412 * The URI to be tested.
415 * TRUE if the URI is allowed.
417 function file_valid_uri($uri) {
418 // Assert that the URI has an allowed scheme. Bare paths are not allowed.
419 $uri_scheme = \Drupal::service('file_system')->uriScheme($uri);
420 if (!file_stream_wrapper_valid_scheme($uri_scheme)) {
427 * Copies a file to a new location without database changes or hook invocation.
429 * This is a powerful function that in many ways performs like an advanced
431 * - Checks if $source and $destination are valid and readable/writable.
432 * - If file already exists in $destination either the call will error out,
433 * replace the file or rename the file based on the $replace parameter.
434 * - If the $source and $destination are equal, the behavior depends on the
435 * $replace parameter. FILE_EXISTS_REPLACE will error out. FILE_EXISTS_RENAME
436 * will rename the file until the $destination is unique.
437 * - Works around a PHP bug where copy() does not properly support streams if
438 * safe_mode or open_basedir are enabled.
439 * @see https://bugs.php.net/bug.php?id=60456
442 * A string specifying the filepath or URI of the source file.
443 * @param $destination
444 * A URI containing the destination that $source should be copied to. The
445 * URI may be a bare filepath (without a scheme). If this value is omitted,
446 * Drupal's default files scheme will be used, usually "public://".
448 * Replace behavior when the destination file already exists:
449 * - FILE_EXISTS_REPLACE - Replace the existing file.
450 * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
452 * - FILE_EXISTS_ERROR - Do nothing and return FALSE.
455 * The path to the new file, or FALSE in the event of an error.
459 function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
460 if (!file_unmanaged_prepare($source, $destination, $replace)) {
463 // Attempt to resolve the URIs. This is necessary in certain configurations
465 $real_source = drupal_realpath($source) ?: $source;
466 $real_destination = drupal_realpath($destination) ?: $destination;
467 // Perform the copy operation.
468 if (!@copy($real_source, $real_destination)) {
469 \Drupal::logger('file')->error('The specified file %file could not be copied to %destination.', ['%file' => $source, '%destination' => $destination]);
472 // Set the permissions on the new file.
473 drupal_chmod($destination);
478 * Internal function that prepares the destination for a file_unmanaged_copy or
479 * file_unmanaged_move operation.
481 * - Checks if $source and $destination are valid and readable/writable.
482 * - Checks that $source is not equal to $destination; if they are an error
484 * - If file already exists in $destination either the call will error out,
485 * replace the file or rename the file based on the $replace parameter.
488 * A string specifying the filepath or URI of the source file.
489 * @param $destination
490 * A URI containing the destination that $source should be moved/copied to.
491 * The URI may be a bare filepath (without a scheme) and in that case the
492 * default scheme (file://) will be used. If this value is omitted, Drupal's
493 * default files scheme will be used, usually "public://".
495 * Replace behavior when the destination file already exists:
496 * - FILE_EXISTS_REPLACE - Replace the existing file.
497 * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
499 * - FILE_EXISTS_ERROR - Do nothing and return FALSE.
502 * TRUE, or FALSE in the event of an error.
504 * @see file_unmanaged_copy()
505 * @see file_unmanaged_move()
507 function file_unmanaged_prepare($source, &$destination = NULL, $replace = FILE_EXISTS_RENAME) {
508 $original_source = $source;
509 $logger = \Drupal::logger('file');
511 // Assert that the source file actually exists.
512 if (!file_exists($source)) {
513 // @todo Replace drupal_set_message() calls with exceptions instead.
514 drupal_set_message(t('The specified file %file could not be moved/copied because no file by that name exists. Please check that you supplied the correct filename.', ['%file' => $original_source]), 'error');
515 if (($realpath = drupal_realpath($original_source)) !== FALSE) {
516 $logger->notice('File %file (%realpath) could not be moved/copied because it does not exist.', ['%file' => $original_source, '%realpath' => $realpath]);
519 $logger->notice('File %file could not be moved/copied because it does not exist.', ['%file' => $original_source]);
524 // Build a destination URI if necessary.
525 if (!isset($destination)) {
526 $destination = file_build_uri(drupal_basename($source));
530 // Prepare the destination directory.
531 if (file_prepare_directory($destination)) {
532 // The destination is already a directory, so append the source basename.
533 $destination = file_stream_wrapper_uri_normalize($destination . '/' . drupal_basename($source));
536 // Perhaps $destination is a dir/file?
537 $dirname = drupal_dirname($destination);
538 if (!file_prepare_directory($dirname)) {
539 // The destination is not valid.
540 $logger->notice('File %file could not be moved/copied because the destination directory %destination is not configured correctly.', ['%file' => $original_source, '%destination' => $dirname]);
541 drupal_set_message(t('The specified file %file could not be moved/copied because the destination directory is not properly configured. This may be caused by a problem with file or directory permissions. More information is available in the system log.', ['%file' => $original_source]), 'error');
546 // Determine whether we can perform this operation based on overwrite rules.
547 $destination = file_destination($destination, $replace);
548 if ($destination === FALSE) {
549 drupal_set_message(t('The file %file could not be moved/copied because a file by that name already exists in the destination directory.', ['%file' => $original_source]), 'error');
550 $logger->notice('File %file could not be moved/copied because a file by that name already exists in the destination directory (%destination)', ['%file' => $original_source, '%destination' => $destination]);
554 // Assert that the source and destination filenames are not the same.
555 $real_source = drupal_realpath($source);
556 $real_destination = drupal_realpath($destination);
557 if ($source == $destination || ($real_source !== FALSE) && ($real_source == $real_destination)) {
558 drupal_set_message(t('The specified file %file was not moved/copied because it would overwrite itself.', ['%file' => $source]), 'error');
559 $logger->notice('File %file could not be moved/copied because it would overwrite itself.', ['%file' => $source]);
562 // Make sure the .htaccess files are present.
563 file_ensure_htaccess();
568 * Constructs a URI to Drupal's default files location given a relative path.
570 function file_build_uri($path) {
571 $uri = file_default_scheme() . '://' . $path;
572 return file_stream_wrapper_uri_normalize($uri);
576 * Determines the destination path for a file.
578 * @param $destination
579 * A string specifying the desired final URI or filepath.
581 * Replace behavior when the destination file already exists.
582 * - FILE_EXISTS_REPLACE - Replace the existing file.
583 * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
585 * - FILE_EXISTS_ERROR - Do nothing and return FALSE.
588 * The destination filepath, or FALSE if the file already exists
589 * and FILE_EXISTS_ERROR is specified.
591 function file_destination($destination, $replace) {
592 if (file_exists($destination)) {
594 case FILE_EXISTS_REPLACE:
595 // Do nothing here, we want to overwrite the existing file.
598 case FILE_EXISTS_RENAME:
599 $basename = drupal_basename($destination);
600 $directory = drupal_dirname($destination);
601 $destination = file_create_filename($basename, $directory);
604 case FILE_EXISTS_ERROR:
605 // Error reporting handled by calling function.
613 * Moves a file to a new location without database changes or hook invocation.
615 * This is a powerful function that in many ways performs like an advanced
616 * version of rename().
617 * - Checks if $source and $destination are valid and readable/writable.
618 * - Checks that $source is not equal to $destination; if they are an error
620 * - If file already exists in $destination either the call will error out,
621 * replace the file or rename the file based on the $replace parameter.
622 * - Works around a PHP bug where rename() does not properly support streams if
623 * safe_mode or open_basedir are enabled.
624 * @see https://bugs.php.net/bug.php?id=60456
627 * A string specifying the filepath or URI of the source file.
628 * @param $destination
629 * A URI containing the destination that $source should be moved to. The
630 * URI may be a bare filepath (without a scheme) and in that case the default
631 * scheme (file://) will be used. If this value is omitted, Drupal's default
632 * files scheme will be used, usually "public://".
634 * Replace behavior when the destination file already exists:
635 * - FILE_EXISTS_REPLACE - Replace the existing file.
636 * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
638 * - FILE_EXISTS_ERROR - Do nothing and return FALSE.
641 * The path to the new file, or FALSE in the event of an error.
645 function file_unmanaged_move($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
646 if (!file_unmanaged_prepare($source, $destination, $replace)) {
649 // Ensure compatibility with Windows.
650 // @see drupal_unlink()
651 if ((substr(PHP_OS, 0, 3) == 'WIN') && (!file_stream_wrapper_valid_scheme(file_uri_scheme($source)))) {
652 chmod($source, 0600);
654 // Attempt to resolve the URIs. This is necessary in certain configurations
655 // (see above) and can also permit fast moves across local schemes.
656 $real_source = drupal_realpath($source) ?: $source;
657 $real_destination = drupal_realpath($destination) ?: $destination;
658 // Perform the move operation.
659 if (!@rename($real_source, $real_destination)) {
660 // Fall back to slow copy and unlink procedure. This is necessary for
661 // renames across schemes that are not local, or where rename() has not been
662 // implemented. It's not necessary to use drupal_unlink() as the Windows
663 // issue has already been resolved above.
664 if (!@copy($real_source, $real_destination) || !@unlink($real_source)) {
665 \Drupal::logger('file')->error('The specified file %file could not be moved to %destination.', ['%file' => $source, '%destination' => $destination]);
669 // Set the permissions on the new file.
670 drupal_chmod($destination);
675 * Modifies a filename as needed for security purposes.
677 * Munging a file name prevents unknown file extensions from masking exploit
678 * files. When web servers such as Apache decide how to process a URL request,
679 * they use the file extension. If the extension is not recognized, Apache
680 * skips that extension and uses the previous file extension. For example, if
681 * the file being requested is exploit.php.pps, and Apache does not recognize
682 * the '.pps' extension, it treats the file as PHP and executes it. To make
683 * this file name safe for Apache and prevent it from executing as PHP, the
684 * .php extension is "munged" into .php_, making the safe file name
687 * Specifically, this function adds an underscore to all extensions that are
688 * between 2 and 5 characters in length, internal to the file name, and not
689 * included in $extensions.
691 * Function behavior is also controlled by the configuration
692 * 'system.file:allow_insecure_uploads'. If it evaluates to TRUE, no alterations
693 * will be made, if it evaluates to FALSE, the filename is 'munged'. *
695 * File name to modify.
697 * A space-separated list of extensions that should not be altered.
699 * If TRUE, drupal_set_message() will be called to display a message if the
700 * file name was changed.
703 * The potentially modified $filename.
705 function file_munge_filename($filename, $extensions, $alerts = TRUE) {
706 $original = $filename;
708 // Allow potentially insecure uploads for very savvy users and admin
709 if (!\Drupal::config('system.file')->get('allow_insecure_uploads')) {
710 // Remove any null bytes. See
711 // http://php.net/manual/security.filesystem.nullbytes.php
712 $filename = str_replace(chr(0), '', $filename);
714 $whitelist = array_unique(explode(' ', strtolower(trim($extensions))));
716 // Split the filename up by periods. The first part becomes the basename
717 // the last part the final extension.
718 $filename_parts = explode('.', $filename);
719 $new_filename = array_shift($filename_parts); // Remove file basename.
720 $final_extension = array_pop($filename_parts); // Remove final extension.
722 // Loop through the middle parts of the name and add an underscore to the
723 // end of each section that could be a file extension but isn't in the list
724 // of allowed extensions.
725 foreach ($filename_parts as $filename_part) {
726 $new_filename .= '.' . $filename_part;
727 if (!in_array(strtolower($filename_part), $whitelist) && preg_match("/^[a-zA-Z]{2,5}\d?$/", $filename_part)) {
728 $new_filename .= '_';
731 $filename = $new_filename . '.' . $final_extension;
733 if ($alerts && $original != $filename) {
734 drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', ['%filename' => $filename]));
742 * Undoes the effect of file_munge_filename().
745 * String with the filename to be unmunged.
748 * An unmunged filename string.
750 function file_unmunge_filename($filename) {
751 return str_replace('_.', '.', $filename);
755 * Creates a full file path from a directory and filename.
757 * If a file with the specified name already exists, an alternative will be
763 * String containing the directory or parent URI.
766 * File path consisting of $directory and a unique filename based off
769 function file_create_filename($basename, $directory) {
770 // Strip control characters (ASCII value < 32). Though these are allowed in
771 // some filesystems, not many applications handle them well.
772 $basename = preg_replace('/[\x00-\x1F]/u', '_', $basename);
773 if (substr(PHP_OS, 0, 3) == 'WIN') {
774 // These characters are not allowed in Windows filenames
775 $basename = str_replace([':', '*', '?', '"', '<', '>', '|'], '_', $basename);
778 // A URI or path may already have a trailing slash or look like "public://".
779 if (substr($directory, -1) == '/') {
786 $destination = $directory . $separator . $basename;
788 if (file_exists($destination)) {
789 // Destination file already exists, generate an alternative.
790 $pos = strrpos($basename, '.');
791 if ($pos !== FALSE) {
792 $name = substr($basename, 0, $pos);
793 $ext = substr($basename, $pos);
802 $destination = $directory . $separator . $name . '_' . $counter++ . $ext;
803 } while (file_exists($destination));
810 * Deletes a file and its database record.
812 * Instead of directly deleting a file, it is strongly recommended to delete
813 * file usages instead. That will automatically mark the file as temporary and
814 * remove it during cleanup.
819 * @see file_unmanaged_delete()
820 * @see \Drupal\file\FileUsage\FileUsageBase::delete()
822 function file_delete($fid) {
823 return file_delete_multiple([$fid]);
829 * Instead of directly deleting a file, it is strongly recommended to delete
830 * file usages instead. That will automatically mark the file as temporary and
831 * remove it during cleanup.
836 * @see file_unmanaged_delete()
837 * @see \Drupal\file\FileUsage\FileUsageBase::delete()
839 function file_delete_multiple(array $fids) {
840 entity_delete_multiple('file', $fids);
844 * Deletes a file without database changes or hook invocations.
846 * This function should be used when the file to be deleted does not have an
847 * entry recorded in the files table.
850 * A string containing a file path or (streamwrapper) URI.
853 * TRUE for success or path does not exist, or FALSE in the event of an
857 * @see file_unmanaged_delete_recursive()
859 function file_unmanaged_delete($path) {
860 if (is_file($path)) {
861 return drupal_unlink($path);
863 $logger = \Drupal::logger('file');
865 $logger->error('%path is a directory and cannot be removed using file_unmanaged_delete().', ['%path' => $path]);
868 // Return TRUE for non-existent file, but log that nothing was actually
869 // deleted, as the current state is the intended result.
870 if (!file_exists($path)) {
871 $logger->notice('The file %path was not deleted because it does not exist.', ['%path' => $path]);
874 // We cannot handle anything other than files and directories. Log an error
875 // for everything else (sockets, symbolic links, etc).
876 $logger->error('The file %path is not of a recognized type so it was not deleted.', ['%path' => $path]);
881 * Deletes all files and directories in the specified filepath recursively.
883 * If the specified path is a directory then the function will call itself
884 * recursively to process the contents. Once the contents have been removed the
885 * directory will also be removed.
887 * If the specified path is a file then it will be passed to
888 * file_unmanaged_delete().
890 * Note that this only deletes visible files with write permission.
893 * A string containing either an URI or a file or directory path.
895 * (optional) Callback function to run on each file prior to deleting it and
896 * on each directory prior to traversing it. For example, can be used to
897 * modify permissions.
900 * TRUE for success or if path does not exist, FALSE in the event of an
903 * @see file_unmanaged_delete()
905 function file_unmanaged_delete_recursive($path, $callback = NULL) {
906 if (isset($callback)) {
907 call_user_func($callback, $path);
911 while (($entry = $dir->read()) !== FALSE) {
912 if ($entry == '.' || $entry == '..') {
915 $entry_path = $path . '/' . $entry;
916 file_unmanaged_delete_recursive($entry_path, $callback);
920 return drupal_rmdir($path);
922 return file_unmanaged_delete($path);
928 * Moves an uploaded file to a new location.
930 * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
931 * Use \Drupal\Core\File\FileSystem::moveUploadedFile().
933 * @see https://www.drupal.org/node/2418133
935 function drupal_move_uploaded_file($filename, $uri) {
936 return \Drupal::service('file_system')->moveUploadedFile($filename, $uri);
940 * Saves a file to the specified destination without invoking file API.
942 * This function is identical to file_save_data() except the file will not be
943 * saved to the {file_managed} table and none of the file_* hooks will be
947 * A string containing the contents of the file.
948 * @param $destination
949 * A string containing the destination location. This must be a stream wrapper
950 * URI. If no value is provided, a randomized name will be generated and the
951 * file will be saved using Drupal's default files scheme, usually
954 * Replace behavior when the destination file already exists:
955 * - FILE_EXISTS_REPLACE - Replace the existing file.
956 * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
958 * - FILE_EXISTS_ERROR - Do nothing and return FALSE.
961 * A string with the path of the resulting file, or FALSE on error.
963 * @see file_save_data()
965 function file_unmanaged_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
966 // Write the data to a temporary file.
967 $temp_name = drupal_tempnam('temporary://', 'file');
968 if (file_put_contents($temp_name, $data) === FALSE) {
969 drupal_set_message(t('The file could not be created.'), 'error');
973 // Move the file to its final destination.
974 return file_unmanaged_move($temp_name, $destination, $replace);
978 * Finds all files that match a given mask in a given directory.
980 * Directories and files beginning with a dot are excluded; this prevents
981 * hidden files and directories (such as SVN working directories) from being
982 * scanned. Use the umask option to skip configuration directories to
983 * eliminate the possibility of accidentally exposing configuration
984 * information. Also, you can use the base directory, recurse, and min_depth
985 * options to improve performance by limiting how much of the filesystem has
989 * The base directory or URI to scan, without trailing slash.
991 * The preg_match() regular expression for files to be included.
993 * An associative array of additional options, with the following elements:
994 * - 'nomask': The preg_match() regular expression for files to be excluded.
995 * Defaults to the 'file_scan_ignore_directories' setting.
996 * - 'callback': The callback function to call for each match. There is no
998 * - 'recurse': When TRUE, the directory scan will recurse the entire tree
999 * starting at the provided directory. Defaults to TRUE.
1000 * - 'key': The key to be used for the returned associative array of files.
1001 * Possible values are 'uri', for the file's URI; 'filename', for the
1002 * basename of the file; and 'name' for the name of the file without the
1003 * extension. Defaults to 'uri'.
1004 * - 'min_depth': Minimum depth of directories to return files from. Defaults
1007 * The current depth of recursion. This parameter is only used internally and
1008 * should not be passed in.
1011 * An associative array (keyed on the chosen key) of objects with 'uri',
1012 * 'filename', and 'name' properties corresponding to the matched files.
1014 function file_scan_directory($dir, $mask, $options = [], $depth = 0) {
1015 // Merge in defaults.
1022 // Normalize $dir only once.
1024 $dir = file_stream_wrapper_uri_normalize($dir);
1025 $dir_has_slash = (substr($dir, -1) === '/');
1028 // Allow directories specified in settings.php to be ignored. You can use this
1029 // to not check for files in common special-purpose directories. For example,
1030 // node_modules and bower_components. Ignoring irrelevant directories is a
1031 // performance boost.
1032 if (!isset($options['nomask'])) {
1033 $ignore_directories = Settings::get('file_scan_ignore_directories', []);
1034 array_walk($ignore_directories, function(&$value) {
1035 $value = preg_quote($value, '/');
1037 $default_nomask = '/^' . implode('|', $ignore_directories) . '$/';
1040 $options['key'] = in_array($options['key'], ['uri', 'filename', 'name']) ? $options['key'] : 'uri';
1042 // Avoid warnings when opendir does not have the permissions to open a
1045 if ($handle = @opendir($dir)) {
1046 while (FALSE !== ($filename = readdir($handle))) {
1047 // Skip this file if it matches the nomask or starts with a dot.
1048 if ($filename[0] != '.'
1049 && !(isset($options['nomask']) && preg_match($options['nomask'], $filename))
1050 && !(!empty($default_nomask) && preg_match($default_nomask, $filename))
1052 if ($depth == 0 && $dir_has_slash) {
1053 $uri = "$dir$filename";
1056 $uri = "$dir/$filename";
1058 if ($options['recurse'] && is_dir($uri)) {
1059 // Give priority to files in this folder by merging them in after
1060 // any subdirectory files.
1061 $files = array_merge(file_scan_directory($uri, $mask, $options, $depth + 1), $files);
1063 elseif ($depth >= $options['min_depth'] && preg_match($mask, $filename)) {
1064 // Always use this match over anything already set in $files with
1065 // the same $options['key'].
1066 $file = new stdClass();
1068 $file->filename = $filename;
1069 $file->name = pathinfo($filename, PATHINFO_FILENAME);
1070 $key = $options['key'];
1071 $files[$file->$key] = $file;
1072 if ($options['callback']) {
1073 $options['callback']($uri);
1082 \Drupal::logger('file')->error('@dir can not be opened', ['@dir' => $dir]);
1090 * Determines the maximum file upload size by querying the PHP settings.
1093 * A file size limit in bytes based on the PHP upload_max_filesize and
1096 function file_upload_max_size() {
1097 static $max_size = -1;
1099 if ($max_size < 0) {
1100 // Start with post_max_size.
1101 $max_size = Bytes::toInt(ini_get('post_max_size'));
1103 // If upload_max_size is less, then reduce. Except if upload_max_size is
1104 // zero, which indicates no limit.
1105 $upload_max = Bytes::toInt(ini_get('upload_max_filesize'));
1106 if ($upload_max > 0 && $upload_max < $max_size) {
1107 $max_size = $upload_max;
1114 * Sets the permissions on a file or directory.
1116 * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
1117 * Use \Drupal\Core\File\FileSystem::chmod().
1119 * @see https://www.drupal.org/node/2418133
1121 function drupal_chmod($uri, $mode = NULL) {
1122 return \Drupal::service('file_system')->chmod($uri, $mode);
1128 * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
1129 * Use \Drupal\Core\File\FileSystem::unlink().
1131 * @see https://www.drupal.org/node/2418133
1133 function drupal_unlink($uri, $context = NULL) {
1134 return \Drupal::service('file_system')->unlink($uri, $context);
1138 * Resolves the absolute filepath of a local URI or filepath.
1140 * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
1141 * Use \Drupal\Core\File\FileSystem::realpath().
1143 * @see https://www.drupal.org/node/2418133
1145 function drupal_realpath($uri) {
1146 return \Drupal::service('file_system')->realpath($uri);
1150 * Gets the name of the directory from a given path.
1152 * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
1153 * Use \Drupal\Core\File\FileSystem::dirname().
1155 * @see https://www.drupal.org/node/2418133
1157 function drupal_dirname($uri) {
1158 return \Drupal::service('file_system')->dirname($uri);
1162 * Gets the filename from a given path.
1164 * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
1165 * Use \Drupal\Core\File\FileSystem::basename().
1167 * @see https://www.drupal.org/node/2418133
1169 function drupal_basename($uri, $suffix = NULL) {
1170 return \Drupal::service('file_system')->basename($uri, $suffix);
1174 * Creates a directory, optionally creating missing components in the path to
1177 * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
1178 * Use \Drupal\Core\File\FileSystem::mkdir().
1180 * @see https://www.drupal.org/node/2418133
1182 function drupal_mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) {
1183 return \Drupal::service('file_system')->mkdir($uri, $mode, $recursive, $context);
1187 * Removes a directory.
1189 * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
1190 * Use \Drupal\Core\File\FileSystem::rmdir().
1192 * @see https://www.drupal.org/node/2418133
1194 function drupal_rmdir($uri, $context = NULL) {
1195 return \Drupal::service('file_system')->rmdir($uri, $context);
1199 * Creates a file with a unique filename in the specified directory.
1201 * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
1202 * Use \Drupal\Core\File\FileSystem::tempnam().
1204 * @see https://www.drupal.org/node/2418133
1206 function drupal_tempnam($directory, $prefix) {
1207 return \Drupal::service('file_system')->tempnam($directory, $prefix);
1211 * Gets and sets the path of the configured temporary directory.
1213 * @return mixed|null
1214 * A string containing the path to the temporary directory.
1216 function file_directory_temp() {
1217 $temporary_directory = \Drupal::config('system.file')->get('path.temporary');
1218 if (empty($temporary_directory)) {
1220 $config = \Drupal::configFactory()->getEditable('system.file');
1221 $temporary_directory = ComponentFileSystem::getOsTemporaryDirectory();
1223 if (empty($temporary_directory)) {
1224 // If no directory has been found default to 'files/tmp'.
1225 $temporary_directory = PublicStream::basePath() . '/tmp';
1227 // Windows accepts paths with either slash (/) or backslash (\), but will
1228 // not accept a path which contains both a slash and a backslash. Since
1229 // the 'file_public_path' variable may have either format, we sanitize
1230 // everything to use slash which is supported on all platforms.
1231 $temporary_directory = str_replace('\\', '/', $temporary_directory);
1233 // Save the path of the discovered directory. Do not check config schema on
1235 $config->set('path.temporary', (string) $temporary_directory)->save(TRUE);
1238 return $temporary_directory;
1242 * Discovers a writable system-appropriate temporary directory.
1245 * A string containing the path to the temporary directory.
1247 * @deprecated in Drupal 8.3.x-dev, will be removed before Drupal 9.0.0.
1248 * Use \Drupal\Component\FileSystem\FileSystem::getOsTemporaryDirectory().
1250 * @see https://www.drupal.org/node/2418133
1252 function file_directory_os_temp() {
1253 return ComponentFileSystem::getOsTemporaryDirectory();
1257 * @} End of "defgroup file".