annotate core/includes/file.inc @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 /**
Chris@0 4 * @file
Chris@0 5 * API for handling file uploads and server file management.
Chris@0 6 */
Chris@0 7
Chris@0 8 use Drupal\Component\FileSystem\FileSystem as ComponentFileSystem;
Chris@18 9 use Drupal\Component\PhpStorage\FileStorage;
Chris@18 10 use Drupal\Component\Utility\Environment;
Chris@0 11 use Drupal\Component\Utility\UrlHelper;
Chris@18 12 use Drupal\Core\File\Exception\FileException;
Chris@18 13 use Drupal\Core\File\Exception\FileWriteException;
Chris@0 14 use Drupal\Core\File\FileSystem;
Chris@18 15 use Drupal\Core\File\FileSystemInterface;
Chris@0 16 use Drupal\Core\Site\Settings;
Chris@18 17 use Drupal\Core\StreamWrapper\PrivateStream;
Chris@0 18 use Drupal\Core\StreamWrapper\PublicStream;
Chris@0 19
Chris@0 20 /**
Chris@18 21 * Default mode for new directories.
Chris@0 22 *
Chris@0 23 * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
Chris@0 24 * Use \Drupal\Core\File\FileSystem::CHMOD_DIRECTORY.
Chris@0 25 *
Chris@18 26 * @see \Drupal\Core\File\FileSystemInterface::chmod()
Chris@0 27 * @see https://www.drupal.org/node/2418133
Chris@0 28 */
Chris@0 29 const FILE_CHMOD_DIRECTORY = FileSystem::CHMOD_DIRECTORY;
Chris@0 30
Chris@0 31 /**
Chris@18 32 * Default mode for new files.
Chris@0 33 *
Chris@0 34 * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
Chris@0 35 * Use \Drupal\Core\File\FileSystem::CHMOD_FILE.
Chris@0 36 *
Chris@18 37 * @see \Drupal\Core\File\FileSystemInterface::chmod()
Chris@0 38 * @see https://www.drupal.org/node/2418133
Chris@0 39 */
Chris@0 40 const FILE_CHMOD_FILE = FileSystem::CHMOD_FILE;
Chris@0 41
Chris@0 42 /**
Chris@0 43 * @defgroup file File interface
Chris@0 44 * @{
Chris@0 45 * Common file handling functions.
Chris@0 46 */
Chris@0 47
Chris@0 48 /**
Chris@18 49 * Flag used to create a directory if not present.
Chris@18 50 *
Chris@18 51 * @deprecated in Drupal 8.7.0, will be removed before Drupal 9.0.0.
Chris@18 52 * Use \Drupal\Core\File\FileSystemInterface::CREATE_DIRECTORY.
Chris@0 53 */
Chris@18 54 const FILE_CREATE_DIRECTORY = FileSystemInterface::CREATE_DIRECTORY;
Chris@0 55
Chris@0 56 /**
Chris@18 57 * Flag used to indicate file permissions may be changed.
Chris@18 58 *
Chris@18 59 * @deprecated in Drupal 8.7.0, will be removed before Drupal 9.0.0.
Chris@18 60 * Use \Drupal\Core\File\FileSystemInterface::MODIFY_PERMISSIONS.
Chris@0 61 */
Chris@18 62 const FILE_MODIFY_PERMISSIONS = FileSystemInterface::MODIFY_PERMISSIONS;
Chris@0 63
Chris@0 64 /**
Chris@0 65 * Flag for dealing with existing files: Appends number until name is unique.
Chris@18 66 *
Chris@18 67 * @deprecated in Drupal 8.7.0, will be removed before Drupal 9.0.0.
Chris@18 68 * Use \Drupal\Core\File\FileSystemInterface::EXISTS_RENAME.
Chris@0 69 */
Chris@18 70 const FILE_EXISTS_RENAME = FileSystemInterface::EXISTS_RENAME;
Chris@0 71
Chris@0 72 /**
Chris@0 73 * Flag for dealing with existing files: Replace the existing file.
Chris@18 74 *
Chris@18 75 * @deprecated in Drupal 8.7.0, will be removed before Drupal 9.0.0.
Chris@18 76 * Use \Drupal\Core\File\FileSystemInterface::EXISTS_REPLACE.
Chris@0 77 */
Chris@18 78 const FILE_EXISTS_REPLACE = FileSystemInterface::EXISTS_REPLACE;
Chris@0 79
Chris@0 80 /**
Chris@0 81 * Flag for dealing with existing files: Do nothing and return FALSE.
Chris@18 82 *
Chris@18 83 * @deprecated in Drupal 8.7.0, will be removed before Drupal 9.0.0.
Chris@18 84 * Use \Drupal\Core\File\FileSystemInterface::EXISTS_ERROR.
Chris@0 85 */
Chris@18 86 const FILE_EXISTS_ERROR = FileSystemInterface::EXISTS_ERROR;
Chris@0 87
Chris@0 88 /**
Chris@0 89 * Indicates that the file is permanent and should not be deleted.
Chris@0 90 *
Chris@0 91 * Temporary files older than the system.file.temporary_maximum_age
Chris@0 92 * configuration value will be, if clean-up not disabled, removed during cron
Chris@0 93 * runs, but permanent files will not be removed during the file garbage
Chris@0 94 * collection process.
Chris@0 95 */
Chris@0 96 const FILE_STATUS_PERMANENT = 1;
Chris@0 97
Chris@0 98 /**
Chris@0 99 * Returns the scheme of a URI (e.g. a stream).
Chris@0 100 *
Chris@0 101 * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
Chris@0 102 * Use \Drupal\Core\File\FileSystem::uriScheme().
Chris@0 103 *
Chris@0 104 * @see https://www.drupal.org/node/2418133
Chris@0 105 */
Chris@0 106 function file_uri_scheme($uri) {
Chris@0 107 return \Drupal::service('file_system')->uriScheme($uri);
Chris@0 108 }
Chris@0 109
Chris@0 110 /**
Chris@0 111 * Checks that the scheme of a stream URI is valid.
Chris@0 112 *
Chris@0 113 * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
Chris@0 114 * Use \Drupal\Core\File\FileSystem::validScheme().
Chris@0 115 *
Chris@0 116 * @see https://www.drupal.org/node/2418133
Chris@0 117 */
Chris@0 118 function file_stream_wrapper_valid_scheme($scheme) {
Chris@0 119 return \Drupal::service('file_system')->validScheme($scheme);
Chris@0 120 }
Chris@0 121
Chris@0 122 /**
Chris@0 123 * Returns the part of a URI after the schema.
Chris@0 124 *
Chris@0 125 * @param string $uri
Chris@0 126 * A stream, referenced as "scheme://target" or "data:target".
Chris@0 127 *
Chris@0 128 * @return string|bool
Chris@0 129 * A string containing the target (path), or FALSE if none.
Chris@0 130 * For example, the URI "public://sample/test.txt" would return
Chris@0 131 * "sample/test.txt".
Chris@0 132 *
Chris@0 133 * @see file_uri_scheme()
Chris@0 134 */
Chris@0 135 function file_uri_target($uri) {
Chris@0 136 // Remove the scheme from the URI and remove erroneous leading or trailing,
Chris@0 137 // forward-slashes and backslashes.
Chris@0 138 $target = trim(preg_replace('/^[\w\-]+:\/\/|^data:/', '', $uri), '\/');
Chris@0 139
Chris@0 140 // If nothing was replaced, the URI doesn't have a valid scheme.
Chris@0 141 return $target !== $uri ? $target : FALSE;
Chris@0 142 }
Chris@0 143
Chris@0 144 /**
Chris@0 145 * Gets the default file stream implementation.
Chris@0 146 *
Chris@0 147 * @return string
Chris@0 148 * 'public', 'private' or any other file scheme defined as the default.
Chris@0 149 */
Chris@0 150 function file_default_scheme() {
Chris@0 151 return \Drupal::config('system.file')->get('default_scheme');
Chris@0 152 }
Chris@0 153
Chris@0 154 /**
Chris@0 155 * Normalizes a URI by making it syntactically correct.
Chris@0 156 *
Chris@0 157 * A stream is referenced as "scheme://target".
Chris@0 158 *
Chris@0 159 * The following actions are taken:
Chris@0 160 * - Remove trailing slashes from target
Chris@0 161 * - Trim erroneous leading slashes from target. e.g. ":///" becomes "://".
Chris@0 162 *
Chris@0 163 * @param string $uri
Chris@0 164 * String reference containing the URI to normalize.
Chris@0 165 *
Chris@0 166 * @return string
Chris@0 167 * The normalized URI.
Chris@0 168 */
Chris@0 169 function file_stream_wrapper_uri_normalize($uri) {
Chris@0 170 $scheme = \Drupal::service('file_system')->uriScheme($uri);
Chris@0 171
Chris@0 172 if (file_stream_wrapper_valid_scheme($scheme)) {
Chris@0 173 $target = file_uri_target($uri);
Chris@0 174
Chris@0 175 if ($target !== FALSE) {
Chris@0 176 $uri = $scheme . '://' . $target;
Chris@0 177 }
Chris@0 178 }
Chris@0 179
Chris@0 180 return $uri;
Chris@0 181 }
Chris@0 182
Chris@0 183 /**
Chris@0 184 * Creates a web-accessible URL for a stream to an external or local file.
Chris@0 185 *
Chris@0 186 * Compatibility: normal paths and stream wrappers.
Chris@0 187 *
Chris@0 188 * There are two kinds of local files:
Chris@0 189 * - "managed files", i.e. those stored by a Drupal-compatible stream wrapper.
Chris@0 190 * These are files that have either been uploaded by users or were generated
Chris@0 191 * automatically (for example through CSS aggregation).
Chris@0 192 * - "shipped files", i.e. those outside of the files directory, which ship as
Chris@0 193 * part of Drupal core or contributed modules or themes.
Chris@0 194 *
Chris@0 195 * @param string $uri
Chris@0 196 * The URI to a file for which we need an external URL, or the path to a
Chris@0 197 * shipped file.
Chris@0 198 *
Chris@0 199 * @return string
Chris@0 200 * A string containing a URL that may be used to access the file.
Chris@0 201 * If the provided string already contains a preceding 'http', 'https', or
Chris@0 202 * '/', nothing is done and the same string is returned. If a stream wrapper
Chris@0 203 * could not be found to generate an external URL, then FALSE is returned.
Chris@0 204 *
Chris@0 205 * @see https://www.drupal.org/node/515192
Chris@0 206 * @see file_url_transform_relative()
Chris@0 207 */
Chris@0 208 function file_create_url($uri) {
Chris@0 209 // Allow the URI to be altered, e.g. to serve a file from a CDN or static
Chris@0 210 // file server.
Chris@0 211 \Drupal::moduleHandler()->alter('file_url', $uri);
Chris@0 212
Chris@0 213 $scheme = \Drupal::service('file_system')->uriScheme($uri);
Chris@0 214
Chris@0 215 if (!$scheme) {
Chris@0 216 // Allow for:
Chris@0 217 // - root-relative URIs (e.g. /foo.jpg in http://example.com/foo.jpg)
Chris@0 218 // - protocol-relative URIs (e.g. //bar.jpg, which is expanded to
Chris@0 219 // http://example.com/bar.jpg by the browser when viewing a page over
Chris@0 220 // HTTP and to https://example.com/bar.jpg when viewing a HTTPS page)
Chris@0 221 // Both types of relative URIs are characterized by a leading slash, hence
Chris@0 222 // we can use a single check.
Chris@17 223 if (mb_substr($uri, 0, 1) == '/') {
Chris@0 224 return $uri;
Chris@0 225 }
Chris@0 226 else {
Chris@0 227 // If this is not a properly formatted stream, then it is a shipped file.
Chris@0 228 // Therefore, return the urlencoded URI with the base URL prepended.
Chris@0 229 $options = UrlHelper::parse($uri);
Chris@0 230 $path = $GLOBALS['base_url'] . '/' . UrlHelper::encodePath($options['path']);
Chris@0 231 // Append the query.
Chris@0 232 if ($options['query']) {
Chris@0 233 $path .= '?' . UrlHelper::buildQuery($options['query']);
Chris@0 234 }
Chris@0 235
Chris@0 236 // Append fragment.
Chris@0 237 if ($options['fragment']) {
Chris@0 238 $path .= '#' . $options['fragment'];
Chris@0 239 }
Chris@0 240
Chris@0 241 return $path;
Chris@0 242 }
Chris@0 243 }
Chris@0 244 elseif ($scheme == 'http' || $scheme == 'https' || $scheme == 'data') {
Chris@0 245 // Check for HTTP and data URI-encoded URLs so that we don't have to
Chris@0 246 // implement getExternalUrl() for the HTTP and data schemes.
Chris@0 247 return $uri;
Chris@0 248 }
Chris@0 249 else {
Chris@0 250 // Attempt to return an external URL using the appropriate wrapper.
Chris@0 251 if ($wrapper = \Drupal::service('stream_wrapper_manager')->getViaUri($uri)) {
Chris@0 252 return $wrapper->getExternalUrl();
Chris@0 253 }
Chris@0 254 else {
Chris@0 255 return FALSE;
Chris@0 256 }
Chris@0 257 }
Chris@0 258 }
Chris@0 259
Chris@0 260 /**
Chris@0 261 * Transforms an absolute URL of a local file to a relative URL.
Chris@0 262 *
Chris@0 263 * May be useful to prevent problems on multisite set-ups and prevent mixed
Chris@0 264 * content errors when using HTTPS + HTTP.
Chris@0 265 *
Chris@0 266 * @param string $file_url
Chris@0 267 * A file URL of a local file as generated by file_create_url().
Chris@0 268 *
Chris@0 269 * @return string
Chris@0 270 * If the file URL indeed pointed to a local file and was indeed absolute,
Chris@0 271 * then the transformed, relative URL to the local file. Otherwise: the
Chris@0 272 * original value of $file_url.
Chris@0 273 *
Chris@0 274 * @see file_create_url()
Chris@0 275 */
Chris@0 276 function file_url_transform_relative($file_url) {
Chris@0 277 // Unfortunately, we pretty much have to duplicate Symfony's
Chris@0 278 // Request::getHttpHost() method because Request::getPort() may return NULL
Chris@0 279 // instead of a port number.
Chris@0 280 $request = \Drupal::request();
Chris@0 281 $host = $request->getHost();
Chris@0 282 $scheme = $request->getScheme();
Chris@0 283 $port = $request->getPort() ?: 80;
Chris@0 284 if (('http' == $scheme && $port == 80) || ('https' == $scheme && $port == 443)) {
Chris@0 285 $http_host = $host;
Chris@0 286 }
Chris@0 287 else {
Chris@0 288 $http_host = $host . ':' . $port;
Chris@0 289 }
Chris@0 290
Chris@16 291 return preg_replace('|^https?://' . preg_quote($http_host, '|') . '|', '', $file_url);
Chris@0 292 }
Chris@0 293
Chris@0 294 /**
Chris@0 295 * Checks that the directory exists and is writable.
Chris@0 296 *
Chris@0 297 * Directories need to have execute permissions to be considered a directory by
Chris@0 298 * FTP servers, etc.
Chris@0 299 *
Chris@0 300 * @param $directory
Chris@0 301 * A string reference containing the name of a directory path or URI. A
Chris@0 302 * trailing slash will be trimmed from a path.
Chris@0 303 * @param $options
Chris@0 304 * A bitmask to indicate if the directory should be created if it does
Chris@0 305 * not exist (FILE_CREATE_DIRECTORY) or made writable if it is read-only
Chris@0 306 * (FILE_MODIFY_PERMISSIONS).
Chris@0 307 *
Chris@0 308 * @return
Chris@0 309 * TRUE if the directory exists (or was created) and is writable. FALSE
Chris@0 310 * otherwise.
Chris@18 311 *
Chris@18 312 * @deprecated in Drupal 8.7.0, will be removed before Drupal 9.0.0.
Chris@18 313 * Use \Drupal\Core\File\FileSystemInterface::prepareDirectory().
Chris@0 314 */
Chris@18 315 function file_prepare_directory(&$directory, $options = FileSystemInterface::MODIFY_PERMISSIONS) {
Chris@18 316 @trigger_error('file_prepare_directory() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\File\FileSystemInterface::prepareDirectory(). See https://www.drupal.org/node/3006851.', E_USER_DEPRECATED);
Chris@18 317 return \Drupal::service('file_system')->prepareDirectory($directory, $options);
Chris@0 318 }
Chris@0 319
Chris@0 320 /**
Chris@0 321 * Creates a .htaccess file in each Drupal files directory if it is missing.
Chris@0 322 */
Chris@0 323 function file_ensure_htaccess() {
Chris@0 324 file_save_htaccess('public://', FALSE);
Chris@0 325 $private_path = PrivateStream::basePath();
Chris@0 326 if (!empty($private_path)) {
Chris@0 327 file_save_htaccess('private://', TRUE);
Chris@0 328 }
Chris@0 329 file_save_htaccess('temporary://', TRUE);
Chris@0 330
Chris@0 331 // If a staging directory exists then it should contain a .htaccess file.
Chris@0 332 // @todo https://www.drupal.org/node/2696103 catch a more specific exception
Chris@0 333 // and simplify this code.
Chris@0 334 try {
Chris@0 335 $staging = config_get_config_directory(CONFIG_SYNC_DIRECTORY);
Chris@0 336 }
Chris@0 337 catch (\Exception $e) {
Chris@0 338 $staging = FALSE;
Chris@0 339 }
Chris@0 340 if ($staging) {
Chris@0 341 // Note that we log an error here if we can't write the .htaccess file. This
Chris@0 342 // can occur if the staging directory is read-only. If it is then it is the
Chris@0 343 // user's responsibility to create the .htaccess file.
Chris@0 344 file_save_htaccess($staging, TRUE);
Chris@0 345 }
Chris@0 346 }
Chris@0 347
Chris@0 348 /**
Chris@0 349 * Creates a .htaccess file in the given directory.
Chris@0 350 *
Chris@0 351 * @param string $directory
Chris@0 352 * The directory.
Chris@0 353 * @param bool $private
Chris@0 354 * (Optional) FALSE indicates that $directory should be a web-accessible
Chris@0 355 * directory. Defaults to TRUE which indicates a private directory.
Chris@0 356 * @param bool $force_overwrite
Chris@0 357 * (Optional) Set to TRUE to attempt to overwrite the existing .htaccess file
Chris@0 358 * if one is already present. Defaults to FALSE.
Chris@0 359 */
Chris@0 360 function file_save_htaccess($directory, $private = TRUE, $force_overwrite = FALSE) {
Chris@0 361 if (\Drupal::service('file_system')->uriScheme($directory)) {
Chris@0 362 $htaccess_path = file_stream_wrapper_uri_normalize($directory . '/.htaccess');
Chris@0 363 }
Chris@0 364 else {
Chris@0 365 $directory = rtrim($directory, '/\\');
Chris@0 366 $htaccess_path = $directory . '/.htaccess';
Chris@0 367 }
Chris@0 368
Chris@0 369 if (file_exists($htaccess_path) && !$force_overwrite) {
Chris@0 370 // Short circuit if the .htaccess file already exists.
Chris@0 371 return TRUE;
Chris@0 372 }
Chris@0 373 $htaccess_lines = FileStorage::htaccessLines($private);
Chris@0 374
Chris@0 375 // Write the .htaccess file.
Chris@0 376 if (file_exists($directory) && is_writable($directory) && file_put_contents($htaccess_path, $htaccess_lines)) {
Chris@18 377 return \Drupal::service('file_system')->chmod($htaccess_path, 0444);
Chris@0 378 }
Chris@0 379 else {
Chris@0 380 $variables = ['%directory' => $directory, '@htaccess' => $htaccess_lines];
Chris@0 381 \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);
Chris@0 382 return FALSE;
Chris@0 383 }
Chris@0 384 }
Chris@0 385
Chris@0 386 /**
Chris@0 387 * Returns the standard .htaccess lines that Drupal writes to file directories.
Chris@0 388 *
Chris@0 389 * @param bool $private
Chris@0 390 * (Optional) Set to FALSE to return the .htaccess lines for a web-accessible
Chris@0 391 * public directory. The default is TRUE, which returns the .htaccess lines
Chris@0 392 * for a private directory that should not be web-accessible.
Chris@0 393 *
Chris@0 394 * @return string
Chris@0 395 * The desired contents of the .htaccess file.
Chris@0 396 *
Chris@0 397 * @deprecated in Drupal 8.0.x-dev and will be removed before Drupal 9.0.0.
Chris@0 398 * Use \Drupal\Component\PhpStorage\FileStorage::htaccessLines().
Chris@0 399 *
Chris@0 400 * @see https://www.drupal.org/node/2418133
Chris@0 401 */
Chris@0 402 function file_htaccess_lines($private = TRUE) {
Chris@0 403 return FileStorage::htaccessLines($private);
Chris@0 404 }
Chris@0 405
Chris@0 406 /**
Chris@0 407 * Determines whether the URI has a valid scheme for file API operations.
Chris@0 408 *
Chris@0 409 * There must be a scheme and it must be a Drupal-provided scheme like
Chris@0 410 * 'public', 'private', 'temporary', or an extension provided with
Chris@0 411 * hook_stream_wrappers().
Chris@0 412 *
Chris@0 413 * @param $uri
Chris@0 414 * The URI to be tested.
Chris@0 415 *
Chris@0 416 * @return
Chris@0 417 * TRUE if the URI is allowed.
Chris@0 418 */
Chris@0 419 function file_valid_uri($uri) {
Chris@0 420 // Assert that the URI has an allowed scheme. Bare paths are not allowed.
Chris@0 421 $uri_scheme = \Drupal::service('file_system')->uriScheme($uri);
Chris@0 422 if (!file_stream_wrapper_valid_scheme($uri_scheme)) {
Chris@0 423 return FALSE;
Chris@0 424 }
Chris@0 425 return TRUE;
Chris@0 426 }
Chris@0 427
Chris@0 428 /**
Chris@0 429 * Copies a file to a new location without database changes or hook invocation.
Chris@0 430 *
Chris@0 431 * This is a powerful function that in many ways performs like an advanced
Chris@0 432 * version of copy().
Chris@0 433 * - Checks if $source and $destination are valid and readable/writable.
Chris@0 434 * - If file already exists in $destination either the call will error out,
Chris@0 435 * replace the file or rename the file based on the $replace parameter.
Chris@0 436 * - If the $source and $destination are equal, the behavior depends on the
Chris@0 437 * $replace parameter. FILE_EXISTS_REPLACE will error out. FILE_EXISTS_RENAME
Chris@0 438 * will rename the file until the $destination is unique.
Chris@0 439 * - Works around a PHP bug where copy() does not properly support streams if
Chris@0 440 * safe_mode or open_basedir are enabled.
Chris@0 441 * @see https://bugs.php.net/bug.php?id=60456
Chris@0 442 *
Chris@0 443 * @param $source
Chris@0 444 * A string specifying the filepath or URI of the source file.
Chris@0 445 * @param $destination
Chris@0 446 * A URI containing the destination that $source should be copied to. The
Chris@0 447 * URI may be a bare filepath (without a scheme). If this value is omitted,
Chris@0 448 * Drupal's default files scheme will be used, usually "public://".
Chris@0 449 * @param $replace
Chris@0 450 * Replace behavior when the destination file already exists:
Chris@0 451 * - FILE_EXISTS_REPLACE - Replace the existing file.
Chris@0 452 * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
Chris@0 453 * unique.
Chris@0 454 * - FILE_EXISTS_ERROR - Do nothing and return FALSE.
Chris@0 455 *
Chris@0 456 * @return
Chris@0 457 * The path to the new file, or FALSE in the event of an error.
Chris@0 458 *
Chris@18 459 * @deprecated in Drupal 8.7.0, will be removed before Drupal 9.0.0.
Chris@18 460 * Use \Drupal\Core\File\FileSystemInterface::copy().
Chris@18 461 *
Chris@0 462 * @see file_copy()
Chris@18 463 * @see https://www.drupal.org/node/3006851
Chris@0 464 */
Chris@0 465 function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
Chris@18 466 @trigger_error('file_unmanaged_copy() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\File\FileSystemInterface::copy(). See https://www.drupal.org/node/3006851.', E_USER_DEPRECATED);
Chris@18 467 try {
Chris@18 468 $file_system = \Drupal::service('file_system');
Chris@18 469
Chris@18 470 // Build a destination URI if necessary.
Chris@18 471 if (!isset($destination)) {
Chris@18 472 $destination = file_build_uri($file_system->basename($source));
Chris@18 473 }
Chris@18 474 return $file_system->copy($source, $destination, $replace);
Chris@18 475 }
Chris@18 476 catch (FileException $e) {
Chris@0 477 return FALSE;
Chris@0 478 }
Chris@0 479 }
Chris@0 480
Chris@0 481 /**
Chris@0 482 * Internal function that prepares the destination for a file_unmanaged_copy or
Chris@0 483 * file_unmanaged_move operation.
Chris@0 484 *
Chris@0 485 * - Checks if $source and $destination are valid and readable/writable.
Chris@0 486 * - Checks that $source is not equal to $destination; if they are an error
Chris@0 487 * is reported.
Chris@0 488 * - If file already exists in $destination either the call will error out,
Chris@0 489 * replace the file or rename the file based on the $replace parameter.
Chris@0 490 *
Chris@0 491 * @param $source
Chris@0 492 * A string specifying the filepath or URI of the source file.
Chris@0 493 * @param $destination
Chris@0 494 * A URI containing the destination that $source should be moved/copied to.
Chris@0 495 * The URI may be a bare filepath (without a scheme) and in that case the
Chris@0 496 * default scheme (file://) will be used. If this value is omitted, Drupal's
Chris@0 497 * default files scheme will be used, usually "public://".
Chris@0 498 * @param $replace
Chris@0 499 * Replace behavior when the destination file already exists:
Chris@0 500 * - FILE_EXISTS_REPLACE - Replace the existing file.
Chris@0 501 * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
Chris@0 502 * unique.
Chris@0 503 * - FILE_EXISTS_ERROR - Do nothing and return FALSE.
Chris@0 504 *
Chris@0 505 * @return
Chris@0 506 * TRUE, or FALSE in the event of an error.
Chris@0 507 *
Chris@18 508 * @deprecated in Drupal 8.7.0, will be removed before Drupal 9.0.0.
Chris@18 509 * Use \Drupal\Core\File\FileSystemInterface::getDestinationFilename() instead.
Chris@18 510 *
Chris@0 511 * @see file_unmanaged_copy()
Chris@0 512 * @see file_unmanaged_move()
Chris@18 513 * @see https://www.drupal.org/node/3006851
Chris@0 514 */
Chris@0 515 function file_unmanaged_prepare($source, &$destination = NULL, $replace = FILE_EXISTS_RENAME) {
Chris@18 516 @trigger_error('file_unmanaged_prepare() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\File\FileSystemInterface::getDestinationFilename() instead. See https://www.drupal.org/node/3006851.', E_USER_DEPRECATED);
Chris@0 517 $original_source = $source;
Chris@0 518 $logger = \Drupal::logger('file');
Chris@18 519 /** @var \Drupal\Core\File\FileSystemInterface $file_system */
Chris@14 520 $file_system = \Drupal::service('file_system');
Chris@0 521
Chris@0 522 // Assert that the source file actually exists.
Chris@0 523 if (!file_exists($source)) {
Chris@17 524 // @todo Replace \Drupal::messenger()->addError() calls with exceptions
Chris@17 525 // instead.
Chris@17 526 \Drupal::messenger()->addError(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]));
Chris@14 527 if (($realpath = $file_system->realpath($original_source)) !== FALSE) {
Chris@0 528 $logger->notice('File %file (%realpath) could not be moved/copied because it does not exist.', ['%file' => $original_source, '%realpath' => $realpath]);
Chris@0 529 }
Chris@0 530 else {
Chris@0 531 $logger->notice('File %file could not be moved/copied because it does not exist.', ['%file' => $original_source]);
Chris@0 532 }
Chris@0 533 return FALSE;
Chris@0 534 }
Chris@0 535
Chris@0 536 // Build a destination URI if necessary.
Chris@0 537 if (!isset($destination)) {
Chris@18 538 $destination = file_build_uri($file_system->basename($source));
Chris@0 539 }
Chris@0 540
Chris@0 541 // Prepare the destination directory.
Chris@0 542 if (file_prepare_directory($destination)) {
Chris@0 543 // The destination is already a directory, so append the source basename.
Chris@18 544 $destination = file_stream_wrapper_uri_normalize($destination . '/' . $file_system->basename($source));
Chris@0 545 }
Chris@0 546 else {
Chris@0 547 // Perhaps $destination is a dir/file?
Chris@18 548 $dirname = $file_system->dirname($destination);
Chris@0 549 if (!file_prepare_directory($dirname)) {
Chris@0 550 // The destination is not valid.
Chris@0 551 $logger->notice('File %file could not be moved/copied because the destination directory %destination is not configured correctly.', ['%file' => $original_source, '%destination' => $dirname]);
Chris@17 552 \Drupal::messenger()->addError(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]));
Chris@0 553 return FALSE;
Chris@0 554 }
Chris@0 555 }
Chris@0 556
Chris@0 557 // Determine whether we can perform this operation based on overwrite rules.
Chris@0 558 $destination = file_destination($destination, $replace);
Chris@0 559 if ($destination === FALSE) {
Chris@17 560 \Drupal::messenger()->addError(t('The file %file could not be moved/copied because a file by that name already exists in the destination directory.', ['%file' => $original_source]));
Chris@0 561 $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]);
Chris@0 562 return FALSE;
Chris@0 563 }
Chris@0 564
Chris@0 565 // Assert that the source and destination filenames are not the same.
Chris@14 566 $real_source = $file_system->realpath($source);
Chris@14 567 $real_destination = $file_system->realpath($destination);
Chris@0 568 if ($source == $destination || ($real_source !== FALSE) && ($real_source == $real_destination)) {
Chris@17 569 \Drupal::messenger()->addError(t('The specified file %file was not moved/copied because it would overwrite itself.', ['%file' => $source]));
Chris@0 570 $logger->notice('File %file could not be moved/copied because it would overwrite itself.', ['%file' => $source]);
Chris@0 571 return FALSE;
Chris@0 572 }
Chris@0 573 // Make sure the .htaccess files are present.
Chris@0 574 file_ensure_htaccess();
Chris@0 575 return TRUE;
Chris@0 576 }
Chris@0 577
Chris@0 578 /**
Chris@0 579 * Constructs a URI to Drupal's default files location given a relative path.
Chris@0 580 */
Chris@0 581 function file_build_uri($path) {
Chris@0 582 $uri = file_default_scheme() . '://' . $path;
Chris@0 583 return file_stream_wrapper_uri_normalize($uri);
Chris@0 584 }
Chris@0 585
Chris@0 586 /**
Chris@0 587 * Determines the destination path for a file.
Chris@0 588 *
Chris@0 589 * @param $destination
Chris@0 590 * A string specifying the desired final URI or filepath.
Chris@0 591 * @param $replace
Chris@0 592 * Replace behavior when the destination file already exists.
Chris@0 593 * - FILE_EXISTS_REPLACE - Replace the existing file.
Chris@0 594 * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
Chris@0 595 * unique.
Chris@0 596 * - FILE_EXISTS_ERROR - Do nothing and return FALSE.
Chris@0 597 *
Chris@0 598 * @return
Chris@0 599 * The destination filepath, or FALSE if the file already exists
Chris@0 600 * and FILE_EXISTS_ERROR is specified.
Chris@18 601 *
Chris@18 602 * @deprecated in Drupal 8.7.0, will be removed before Drupal 9.0.0.
Chris@18 603 * Use \Drupal\Core\File\FileSystemInterface::getDestinationFilename().
Chris@18 604 *
Chris@18 605 * @see https://www.drupal.org/node/3006851
Chris@0 606 */
Chris@0 607 function file_destination($destination, $replace) {
Chris@18 608 @trigger_error('file_destination() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\File\FileSystemInterface::getDestinationFilename(). See https://www.drupal.org/node/3006851.', E_USER_DEPRECATED);
Chris@18 609 return \Drupal::service('file_system')->getDestinationFilename($destination, $replace);
Chris@0 610 }
Chris@0 611
Chris@0 612 /**
Chris@0 613 * Moves a file to a new location without database changes or hook invocation.
Chris@0 614 *
Chris@0 615 * This is a powerful function that in many ways performs like an advanced
Chris@0 616 * version of rename().
Chris@0 617 * - Checks if $source and $destination are valid and readable/writable.
Chris@0 618 * - Checks that $source is not equal to $destination; if they are an error
Chris@0 619 * is reported.
Chris@0 620 * - If file already exists in $destination either the call will error out,
Chris@0 621 * replace the file or rename the file based on the $replace parameter.
Chris@0 622 * - Works around a PHP bug where rename() does not properly support streams if
Chris@0 623 * safe_mode or open_basedir are enabled.
Chris@0 624 * @see https://bugs.php.net/bug.php?id=60456
Chris@0 625 *
Chris@0 626 * @param $source
Chris@0 627 * A string specifying the filepath or URI of the source file.
Chris@0 628 * @param $destination
Chris@0 629 * A URI containing the destination that $source should be moved to. The
Chris@0 630 * URI may be a bare filepath (without a scheme) and in that case the default
Chris@0 631 * scheme (file://) will be used. If this value is omitted, Drupal's default
Chris@0 632 * files scheme will be used, usually "public://".
Chris@0 633 * @param $replace
Chris@0 634 * Replace behavior when the destination file already exists:
Chris@0 635 * - FILE_EXISTS_REPLACE - Replace the existing file.
Chris@0 636 * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
Chris@0 637 * unique.
Chris@0 638 * - FILE_EXISTS_ERROR - Do nothing and return FALSE.
Chris@0 639 *
Chris@0 640 * @return
Chris@0 641 * The path to the new file, or FALSE in the event of an error.
Chris@0 642 *
Chris@18 643 * @deprecated in Drupal 8.7.0, will be removed before Drupal 9.0.0.
Chris@18 644 * Use \Drupal\Core\File\FileSystemInterface::move().
Chris@18 645 *
Chris@0 646 * @see file_move()
Chris@18 647 * @see https://www.drupal.org/node/3006851
Chris@0 648 */
Chris@0 649 function file_unmanaged_move($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
Chris@18 650 @trigger_error('file_unmanaged_move() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\File\FileSystemInterface::move(). See https://www.drupal.org/node/3006851.', E_USER_DEPRECATED);
Chris@18 651 try {
Chris@18 652 $file_system = \Drupal::service('file_system');
Chris@18 653
Chris@18 654 // Build a destination URI if necessary.
Chris@18 655 if (!isset($destination)) {
Chris@18 656 $destination = file_build_uri($file_system->basename($source));
Chris@18 657 }
Chris@18 658 return $file_system->move($source, $destination, $replace);
Chris@18 659 }
Chris@18 660 catch (FileException $e) {
Chris@0 661 return FALSE;
Chris@0 662 }
Chris@0 663 }
Chris@0 664
Chris@0 665 /**
Chris@0 666 * Modifies a filename as needed for security purposes.
Chris@0 667 *
Chris@0 668 * Munging a file name prevents unknown file extensions from masking exploit
Chris@0 669 * files. When web servers such as Apache decide how to process a URL request,
Chris@0 670 * they use the file extension. If the extension is not recognized, Apache
Chris@0 671 * skips that extension and uses the previous file extension. For example, if
Chris@0 672 * the file being requested is exploit.php.pps, and Apache does not recognize
Chris@0 673 * the '.pps' extension, it treats the file as PHP and executes it. To make
Chris@0 674 * this file name safe for Apache and prevent it from executing as PHP, the
Chris@0 675 * .php extension is "munged" into .php_, making the safe file name
Chris@0 676 * exploit.php_.pps.
Chris@0 677 *
Chris@0 678 * Specifically, this function adds an underscore to all extensions that are
Chris@0 679 * between 2 and 5 characters in length, internal to the file name, and not
Chris@0 680 * included in $extensions.
Chris@0 681 *
Chris@0 682 * Function behavior is also controlled by the configuration
Chris@0 683 * 'system.file:allow_insecure_uploads'. If it evaluates to TRUE, no alterations
Chris@0 684 * will be made, if it evaluates to FALSE, the filename is 'munged'. *
Chris@0 685 * @param $filename
Chris@0 686 * File name to modify.
Chris@0 687 * @param $extensions
Chris@0 688 * A space-separated list of extensions that should not be altered.
Chris@0 689 * @param $alerts
Chris@17 690 * If TRUE, \Drupal::messenger()->addStatus() will be called to display
Chris@17 691 * a message if the file name was changed.
Chris@0 692 *
Chris@0 693 * @return string
Chris@0 694 * The potentially modified $filename.
Chris@0 695 */
Chris@0 696 function file_munge_filename($filename, $extensions, $alerts = TRUE) {
Chris@0 697 $original = $filename;
Chris@0 698
Chris@0 699 // Allow potentially insecure uploads for very savvy users and admin
Chris@0 700 if (!\Drupal::config('system.file')->get('allow_insecure_uploads')) {
Chris@0 701 // Remove any null bytes. See
Chris@0 702 // http://php.net/manual/security.filesystem.nullbytes.php
Chris@0 703 $filename = str_replace(chr(0), '', $filename);
Chris@0 704
Chris@0 705 $whitelist = array_unique(explode(' ', strtolower(trim($extensions))));
Chris@0 706
Chris@0 707 // Split the filename up by periods. The first part becomes the basename
Chris@0 708 // the last part the final extension.
Chris@0 709 $filename_parts = explode('.', $filename);
Chris@0 710 // Remove file basename.
Chris@0 711 $new_filename = array_shift($filename_parts);
Chris@0 712 // Remove final extension.
Chris@0 713 $final_extension = array_pop($filename_parts);
Chris@0 714
Chris@0 715 // Loop through the middle parts of the name and add an underscore to the
Chris@0 716 // end of each section that could be a file extension but isn't in the list
Chris@0 717 // of allowed extensions.
Chris@0 718 foreach ($filename_parts as $filename_part) {
Chris@0 719 $new_filename .= '.' . $filename_part;
Chris@0 720 if (!in_array(strtolower($filename_part), $whitelist) && preg_match("/^[a-zA-Z]{2,5}\d?$/", $filename_part)) {
Chris@0 721 $new_filename .= '_';
Chris@0 722 }
Chris@0 723 }
Chris@0 724 $filename = $new_filename . '.' . $final_extension;
Chris@0 725
Chris@0 726 if ($alerts && $original != $filename) {
Chris@17 727 \Drupal::messenger()->addStatus(t('For security reasons, your upload has been renamed to %filename.', ['%filename' => $filename]));
Chris@0 728 }
Chris@0 729 }
Chris@0 730
Chris@0 731 return $filename;
Chris@0 732 }
Chris@0 733
Chris@0 734 /**
Chris@0 735 * Undoes the effect of file_munge_filename().
Chris@0 736 *
Chris@0 737 * @param $filename
Chris@0 738 * String with the filename to be unmunged.
Chris@0 739 *
Chris@0 740 * @return
Chris@0 741 * An unmunged filename string.
Chris@0 742 */
Chris@0 743 function file_unmunge_filename($filename) {
Chris@0 744 return str_replace('_.', '.', $filename);
Chris@0 745 }
Chris@0 746
Chris@0 747 /**
Chris@0 748 * Creates a full file path from a directory and filename.
Chris@0 749 *
Chris@0 750 * If a file with the specified name already exists, an alternative will be
Chris@0 751 * used.
Chris@0 752 *
Chris@0 753 * @param $basename
Chris@0 754 * String filename
Chris@0 755 * @param $directory
Chris@0 756 * String containing the directory or parent URI.
Chris@0 757 *
Chris@0 758 * @return
Chris@0 759 * File path consisting of $directory and a unique filename based off
Chris@0 760 * of $basename.
Chris@18 761 *
Chris@18 762 * @deprecated in Drupal 8.7.0, will be removed before Drupal 9.0.0.
Chris@18 763 * Use \Drupal\Core\File\FileSystemInterface::createFilename().
Chris@18 764 *
Chris@18 765 * @see https://www.drupal.org/node/3006851
Chris@0 766 */
Chris@0 767 function file_create_filename($basename, $directory) {
Chris@18 768 @trigger_error('file_create_filename() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\File\FileSystemInterface::createFilename(). See https://www.drupal.org/node/3006851.', E_USER_DEPRECATED);
Chris@18 769 return \Drupal::service('file_system')->createFilename($basename, $directory);
Chris@0 770 }
Chris@0 771
Chris@0 772 /**
Chris@0 773 * Deletes a file and its database record.
Chris@0 774 *
Chris@0 775 * Instead of directly deleting a file, it is strongly recommended to delete
Chris@0 776 * file usages instead. That will automatically mark the file as temporary and
Chris@0 777 * remove it during cleanup.
Chris@0 778 *
Chris@0 779 * @param $fid
Chris@0 780 * The file id.
Chris@0 781 *
Chris@18 782 * @deprecated in Drupal 8.7.0, will be removed before Drupal 9.0.0.
Chris@18 783 * Use \Drupal\Core\Entity\EntityStorageInterface::delete() instead.
Chris@18 784 *
Chris@0 785 * @see file_unmanaged_delete()
Chris@0 786 * @see \Drupal\file\FileUsage\FileUsageBase::delete()
Chris@18 787 * @see \Drupal\Core\Entity\EntityStorageInterface::delete()
Chris@18 788 * @see https://www.drupal.org/node/3021663
Chris@0 789 */
Chris@0 790 function file_delete($fid) {
Chris@18 791 @trigger_error('file_delete() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\Entity\EntityStorageInterface::delete() instead. See https://www.drupal.org/node/3021663.', E_USER_DEPRECATED);
Chris@0 792 return file_delete_multiple([$fid]);
Chris@0 793 }
Chris@0 794
Chris@0 795 /**
Chris@0 796 * Deletes files.
Chris@0 797 *
Chris@0 798 * Instead of directly deleting a file, it is strongly recommended to delete
Chris@0 799 * file usages instead. That will automatically mark the file as temporary and
Chris@0 800 * remove it during cleanup.
Chris@0 801 *
Chris@18 802 * @param $fids
Chris@18 803 * An array of file ids.
Chris@18 804 *
Chris@18 805 * @deprecated in Drupal 8.7.0, will be removed before Drupal 9.0.0.
Chris@18 806 * Use \Drupal\Core\Entity\EntityStorageInterface::delete() instead.
Chris@0 807 *
Chris@0 808 * @see file_unmanaged_delete()
Chris@0 809 * @see \Drupal\file\FileUsage\FileUsageBase::delete()
Chris@18 810 * @see \Drupal\Core\Entity\EntityStorageInterface::delete()
Chris@18 811 * @see https://www.drupal.org/node/3021663
Chris@0 812 */
Chris@0 813 function file_delete_multiple(array $fids) {
Chris@18 814 @trigger_error('file_delete_multiple() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\Entity\EntityStorageInterface::delete() instead. See https://www.drupal.org/node/3021663.', E_USER_DEPRECATED);
Chris@18 815 $storage = \Drupal::entityTypeManager()->getStorage('file');
Chris@18 816 $entities = $storage->loadMultiple($fids);
Chris@18 817 $storage->delete($entities);
Chris@0 818 }
Chris@0 819
Chris@0 820 /**
Chris@0 821 * Deletes a file without database changes or hook invocations.
Chris@0 822 *
Chris@0 823 * This function should be used when the file to be deleted does not have an
Chris@0 824 * entry recorded in the files table.
Chris@0 825 *
Chris@0 826 * @param $path
Chris@0 827 * A string containing a file path or (streamwrapper) URI.
Chris@0 828 *
Chris@0 829 * @return
Chris@0 830 * TRUE for success or path does not exist, or FALSE in the event of an
Chris@0 831 * error.
Chris@0 832 *
Chris@18 833 * @deprecated in Drupal 8.7.0, will be removed before Drupal 9.0.0.
Chris@18 834 * Use \Drupal\Core\File\FileSystemInterface::delete().
Chris@18 835 *
Chris@0 836 * @see file_delete()
Chris@0 837 * @see file_unmanaged_delete_recursive()
Chris@18 838 * @see https://www.drupal.org/node/3006851
Chris@0 839 */
Chris@0 840 function file_unmanaged_delete($path) {
Chris@18 841 @trigger_error('file_unmanaged_delete() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\File\FileSystemInterface::delete(). See https://www.drupal.org/node/3006851.', E_USER_DEPRECATED);
Chris@18 842 try {
Chris@18 843 return \Drupal::service('file_system')->delete($path);
Chris@0 844 }
Chris@18 845 catch (FileException $e) {
Chris@0 846 return FALSE;
Chris@0 847 }
Chris@0 848 }
Chris@0 849
Chris@0 850 /**
Chris@0 851 * Deletes all files and directories in the specified filepath recursively.
Chris@0 852 *
Chris@0 853 * If the specified path is a directory then the function will call itself
Chris@0 854 * recursively to process the contents. Once the contents have been removed the
Chris@0 855 * directory will also be removed.
Chris@0 856 *
Chris@0 857 * If the specified path is a file then it will be passed to
Chris@0 858 * file_unmanaged_delete().
Chris@0 859 *
Chris@0 860 * Note that this only deletes visible files with write permission.
Chris@0 861 *
Chris@0 862 * @param $path
Chris@0 863 * A string containing either an URI or a file or directory path.
Chris@14 864 * @param callable $callback
Chris@0 865 * (optional) Callback function to run on each file prior to deleting it and
Chris@0 866 * on each directory prior to traversing it. For example, can be used to
Chris@0 867 * modify permissions.
Chris@0 868 *
Chris@0 869 * @return
Chris@0 870 * TRUE for success or if path does not exist, FALSE in the event of an
Chris@0 871 * error.
Chris@0 872 *
Chris@18 873 * @deprecated in Drupal 8.7.0, will be removed before Drupal 9.0.0.
Chris@18 874 * Use \Drupal\Core\File\FileSystemInterface::deleteRecursive().
Chris@18 875 *
Chris@0 876 * @see file_unmanaged_delete()
Chris@18 877 * @see https://www.drupal.org/node/3006851
Chris@0 878 */
Chris@0 879 function file_unmanaged_delete_recursive($path, $callback = NULL) {
Chris@18 880 @trigger_error('file_unmanaged_delete_recursive() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\File\FileSystemInterface::deleteRecursive(). See https://www.drupal.org/node/3006851.', E_USER_DEPRECATED);
Chris@18 881 $callback = is_callable($callback) ? $callback : NULL;
Chris@18 882 try {
Chris@18 883 return \Drupal::service('file_system')->deleteRecursive($path, $callback);
Chris@0 884 }
Chris@18 885 catch (FileException $e) {
Chris@18 886 return FALSE;
Chris@0 887 }
Chris@0 888 }
Chris@0 889
Chris@0 890 /**
Chris@0 891 * Moves an uploaded file to a new location.
Chris@0 892 *
Chris@0 893 * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
Chris@0 894 * Use \Drupal\Core\File\FileSystem::moveUploadedFile().
Chris@0 895 *
Chris@0 896 * @see https://www.drupal.org/node/2418133
Chris@0 897 */
Chris@0 898 function drupal_move_uploaded_file($filename, $uri) {
Chris@18 899 @trigger_error('drupal_move_uploaded_file() is deprecated in Drupal 8.0.x-dev and will be removed before Drupal 9.0.0. Use \Drupal\Core\File\FileSystemInterface::moveUploadedFile(). See https://www.drupal.org/node/2418133.', E_USER_DEPRECATED);
Chris@0 900 return \Drupal::service('file_system')->moveUploadedFile($filename, $uri);
Chris@0 901 }
Chris@0 902
Chris@0 903 /**
Chris@0 904 * Saves a file to the specified destination without invoking file API.
Chris@0 905 *
Chris@0 906 * This function is identical to file_save_data() except the file will not be
Chris@0 907 * saved to the {file_managed} table and none of the file_* hooks will be
Chris@0 908 * called.
Chris@0 909 *
Chris@0 910 * @param $data
Chris@0 911 * A string containing the contents of the file.
Chris@0 912 * @param $destination
Chris@0 913 * A string containing the destination location. This must be a stream wrapper
Chris@0 914 * URI. If no value is provided, a randomized name will be generated and the
Chris@0 915 * file will be saved using Drupal's default files scheme, usually
Chris@0 916 * "public://".
Chris@0 917 * @param $replace
Chris@0 918 * Replace behavior when the destination file already exists:
Chris@0 919 * - FILE_EXISTS_REPLACE - Replace the existing file.
Chris@0 920 * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
Chris@0 921 * unique.
Chris@0 922 * - FILE_EXISTS_ERROR - Do nothing and return FALSE.
Chris@0 923 *
Chris@0 924 * @return
Chris@0 925 * A string with the path of the resulting file, or FALSE on error.
Chris@0 926 *
Chris@18 927 * @deprecated in Drupal 8.7.0, will be removed before Drupal 9.0.0.
Chris@18 928 * Use \Drupal\Core\File\FileSystemInterface::saveData().
Chris@18 929 *
Chris@0 930 * @see file_save_data()
Chris@18 931 * @see https://www.drupal.org/node/3006851
Chris@0 932 */
Chris@0 933 function file_unmanaged_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
Chris@18 934 @trigger_error('file_unmanaged_save_data() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\File\FileSystemInterface::saveData(). See https://www.drupal.org/node/3006851.', E_USER_DEPRECATED);
Chris@18 935 try {
Chris@18 936 // Build a destination URI if necessary.
Chris@18 937 if (!isset($destination)) {
Chris@18 938 $destination = file_default_scheme() . '://';
Chris@18 939 }
Chris@18 940 return \Drupal::service('file_system')->saveData($data, $destination, $replace);
Chris@18 941 }
Chris@18 942 catch (FileWriteException $e) {
Chris@17 943 \Drupal::messenger()->addError(t('The file could not be created.'));
Chris@0 944 return FALSE;
Chris@0 945 }
Chris@18 946 catch (FileException $e) {
Chris@18 947 return FALSE;
Chris@18 948 }
Chris@0 949 }
Chris@0 950
Chris@0 951 /**
Chris@0 952 * Finds all files that match a given mask in a given directory.
Chris@0 953 *
Chris@0 954 * Directories and files beginning with a dot are excluded; this prevents
Chris@0 955 * hidden files and directories (such as SVN working directories) from being
Chris@0 956 * scanned. Use the umask option to skip configuration directories to
Chris@0 957 * eliminate the possibility of accidentally exposing configuration
Chris@0 958 * information. Also, you can use the base directory, recurse, and min_depth
Chris@0 959 * options to improve performance by limiting how much of the filesystem has
Chris@0 960 * to be traversed.
Chris@0 961 *
Chris@0 962 * @param $dir
Chris@0 963 * The base directory or URI to scan, without trailing slash.
Chris@0 964 * @param $mask
Chris@0 965 * The preg_match() regular expression for files to be included.
Chris@0 966 * @param $options
Chris@0 967 * An associative array of additional options, with the following elements:
Chris@0 968 * - 'nomask': The preg_match() regular expression for files to be excluded.
Chris@0 969 * Defaults to the 'file_scan_ignore_directories' setting.
Chris@0 970 * - 'callback': The callback function to call for each match. There is no
Chris@0 971 * default callback.
Chris@0 972 * - 'recurse': When TRUE, the directory scan will recurse the entire tree
Chris@0 973 * starting at the provided directory. Defaults to TRUE.
Chris@0 974 * - 'key': The key to be used for the returned associative array of files.
Chris@0 975 * Possible values are 'uri', for the file's URI; 'filename', for the
Chris@0 976 * basename of the file; and 'name' for the name of the file without the
Chris@0 977 * extension. Defaults to 'uri'.
Chris@0 978 * - 'min_depth': Minimum depth of directories to return files from. Defaults
Chris@0 979 * to 0.
Chris@0 980 * @param $depth
Chris@0 981 * The current depth of recursion. This parameter is only used internally and
Chris@0 982 * should not be passed in.
Chris@0 983 *
Chris@0 984 * @return
Chris@0 985 * An associative array (keyed on the chosen key) of objects with 'uri',
Chris@0 986 * 'filename', and 'name' properties corresponding to the matched files.
Chris@0 987 */
Chris@0 988 function file_scan_directory($dir, $mask, $options = [], $depth = 0) {
Chris@0 989 // Merge in defaults.
Chris@0 990 $options += [
Chris@0 991 'callback' => 0,
Chris@0 992 'recurse' => TRUE,
Chris@0 993 'key' => 'uri',
Chris@0 994 'min_depth' => 0,
Chris@0 995 ];
Chris@0 996 // Normalize $dir only once.
Chris@0 997 if ($depth == 0) {
Chris@0 998 $dir = file_stream_wrapper_uri_normalize($dir);
Chris@0 999 $dir_has_slash = (substr($dir, -1) === '/');
Chris@0 1000 }
Chris@0 1001
Chris@0 1002 // Allow directories specified in settings.php to be ignored. You can use this
Chris@0 1003 // to not check for files in common special-purpose directories. For example,
Chris@0 1004 // node_modules and bower_components. Ignoring irrelevant directories is a
Chris@0 1005 // performance boost.
Chris@0 1006 if (!isset($options['nomask'])) {
Chris@0 1007 $ignore_directories = Settings::get('file_scan_ignore_directories', []);
Chris@0 1008 array_walk($ignore_directories, function (&$value) {
Chris@0 1009 $value = preg_quote($value, '/');
Chris@0 1010 });
Chris@0 1011 $default_nomask = '/^' . implode('|', $ignore_directories) . '$/';
Chris@0 1012 }
Chris@0 1013
Chris@0 1014 $options['key'] = in_array($options['key'], ['uri', 'filename', 'name']) ? $options['key'] : 'uri';
Chris@0 1015 $files = [];
Chris@0 1016 // Avoid warnings when opendir does not have the permissions to open a
Chris@0 1017 // directory.
Chris@0 1018 if (is_dir($dir)) {
Chris@0 1019 if ($handle = @opendir($dir)) {
Chris@0 1020 while (FALSE !== ($filename = readdir($handle))) {
Chris@0 1021 // Skip this file if it matches the nomask or starts with a dot.
Chris@0 1022 if ($filename[0] != '.'
Chris@0 1023 && !(isset($options['nomask']) && preg_match($options['nomask'], $filename))
Chris@0 1024 && !(!empty($default_nomask) && preg_match($default_nomask, $filename))
Chris@0 1025 ) {
Chris@0 1026 if ($depth == 0 && $dir_has_slash) {
Chris@0 1027 $uri = "$dir$filename";
Chris@0 1028 }
Chris@0 1029 else {
Chris@0 1030 $uri = "$dir/$filename";
Chris@0 1031 }
Chris@0 1032 if ($options['recurse'] && is_dir($uri)) {
Chris@0 1033 // Give priority to files in this folder by merging them in after
Chris@0 1034 // any subdirectory files.
Chris@0 1035 $files = array_merge(file_scan_directory($uri, $mask, $options, $depth + 1), $files);
Chris@0 1036 }
Chris@0 1037 elseif ($depth >= $options['min_depth'] && preg_match($mask, $filename)) {
Chris@0 1038 // Always use this match over anything already set in $files with
Chris@0 1039 // the same $options['key'].
Chris@0 1040 $file = new stdClass();
Chris@0 1041 $file->uri = $uri;
Chris@0 1042 $file->filename = $filename;
Chris@0 1043 $file->name = pathinfo($filename, PATHINFO_FILENAME);
Chris@0 1044 $key = $options['key'];
Chris@0 1045 $files[$file->$key] = $file;
Chris@0 1046 if ($options['callback']) {
Chris@0 1047 $options['callback']($uri);
Chris@0 1048 }
Chris@0 1049 }
Chris@0 1050 }
Chris@0 1051 }
Chris@0 1052
Chris@0 1053 closedir($handle);
Chris@0 1054 }
Chris@0 1055 else {
Chris@0 1056 \Drupal::logger('file')->error('@dir can not be opened', ['@dir' => $dir]);
Chris@0 1057 }
Chris@0 1058 }
Chris@0 1059
Chris@0 1060 return $files;
Chris@0 1061 }
Chris@0 1062
Chris@0 1063 /**
Chris@0 1064 * Determines the maximum file upload size by querying the PHP settings.
Chris@0 1065 *
Chris@0 1066 * @return
Chris@0 1067 * A file size limit in bytes based on the PHP upload_max_filesize and
Chris@0 1068 * post_max_size
Chris@18 1069 *
Chris@18 1070 * @deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0.
Chris@18 1071 * Use \Drupal\Component\Utility\Environment::getUploadMaxSize() instead.
Chris@0 1072 */
Chris@0 1073 function file_upload_max_size() {
Chris@18 1074 @trigger_error('file_upload_max_size() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use \Drupal\Component\Utility\Environment::getUploadMaxSize() instead. See https://www.drupal.org/node/3000058.', E_USER_DEPRECATED);
Chris@18 1075 return Environment::getUploadMaxSize();
Chris@0 1076 }
Chris@0 1077
Chris@0 1078 /**
Chris@0 1079 * Sets the permissions on a file or directory.
Chris@0 1080 *
Chris@18 1081 * @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0.
Chris@0 1082 * Use \Drupal\Core\File\FileSystem::chmod().
Chris@0 1083 *
Chris@0 1084 * @see https://www.drupal.org/node/2418133
Chris@0 1085 */
Chris@0 1086 function drupal_chmod($uri, $mode = NULL) {
Chris@18 1087 @trigger_error('drupal_chmod() is deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\File\FileSystemInterface::chmod(). See https://www.drupal.org/node/2418133.', E_USER_DEPRECATED);
Chris@0 1088 return \Drupal::service('file_system')->chmod($uri, $mode);
Chris@0 1089 }
Chris@0 1090
Chris@0 1091 /**
Chris@0 1092 * Deletes a file.
Chris@0 1093 *
Chris@0 1094 * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
Chris@0 1095 * Use \Drupal\Core\File\FileSystem::unlink().
Chris@0 1096 *
Chris@0 1097 * @see https://www.drupal.org/node/2418133
Chris@0 1098 */
Chris@0 1099 function drupal_unlink($uri, $context = NULL) {
Chris@0 1100 return \Drupal::service('file_system')->unlink($uri, $context);
Chris@0 1101 }
Chris@0 1102
Chris@0 1103 /**
Chris@0 1104 * Resolves the absolute filepath of a local URI or filepath.
Chris@0 1105 *
Chris@0 1106 * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
Chris@0 1107 * Use \Drupal\Core\File\FileSystem::realpath().
Chris@0 1108 *
Chris@0 1109 * @see https://www.drupal.org/node/2418133
Chris@0 1110 */
Chris@0 1111 function drupal_realpath($uri) {
Chris@0 1112 return \Drupal::service('file_system')->realpath($uri);
Chris@0 1113 }
Chris@0 1114
Chris@0 1115 /**
Chris@0 1116 * Gets the name of the directory from a given path.
Chris@0 1117 *
Chris@18 1118 * @deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0.
Chris@0 1119 * Use \Drupal\Core\File\FileSystem::dirname().
Chris@0 1120 *
Chris@0 1121 * @see https://www.drupal.org/node/2418133
Chris@0 1122 */
Chris@0 1123 function drupal_dirname($uri) {
Chris@18 1124 @trigger_error('drupal_dirname() is deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\File\FileSystemInterface::dirname(). See https://www.drupal.org/node/2418133.', E_USER_DEPRECATED);
Chris@0 1125 return \Drupal::service('file_system')->dirname($uri);
Chris@0 1126 }
Chris@0 1127
Chris@0 1128 /**
Chris@0 1129 * Gets the filename from a given path.
Chris@0 1130 *
Chris@18 1131 * @deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0.
Chris@0 1132 * Use \Drupal\Core\File\FileSystem::basename().
Chris@0 1133 *
Chris@0 1134 * @see https://www.drupal.org/node/2418133
Chris@0 1135 */
Chris@0 1136 function drupal_basename($uri, $suffix = NULL) {
Chris@18 1137 @trigger_error('drupal_basename() is deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\File\FileSystemInterface::basename(). See https://www.drupal.org/node/2418133.', E_USER_DEPRECATED);
Chris@0 1138 return \Drupal::service('file_system')->basename($uri, $suffix);
Chris@0 1139 }
Chris@0 1140
Chris@0 1141 /**
Chris@0 1142 * Creates a directory, optionally creating missing components in the path to
Chris@0 1143 * the directory.
Chris@0 1144 *
Chris@18 1145 * @deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0.
Chris@0 1146 * Use \Drupal\Core\File\FileSystem::mkdir().
Chris@0 1147 *
Chris@0 1148 * @see https://www.drupal.org/node/2418133
Chris@0 1149 */
Chris@0 1150 function drupal_mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) {
Chris@18 1151 @trigger_error('drupal_mkdir() is deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\File\FileSystemInterface::mkdir(). See https://www.drupal.org/node/2418133.', E_USER_DEPRECATED);
Chris@0 1152 return \Drupal::service('file_system')->mkdir($uri, $mode, $recursive, $context);
Chris@0 1153 }
Chris@0 1154
Chris@0 1155 /**
Chris@0 1156 * Removes a directory.
Chris@0 1157 *
Chris@18 1158 * @deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0.
Chris@0 1159 * Use \Drupal\Core\File\FileSystem::rmdir().
Chris@0 1160 *
Chris@0 1161 * @see https://www.drupal.org/node/2418133
Chris@0 1162 */
Chris@0 1163 function drupal_rmdir($uri, $context = NULL) {
Chris@18 1164 @trigger_error('drupal_rmdir() is deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\File\FileSystemInterface::rmdir(). See https://www.drupal.org/node/2418133.', E_USER_DEPRECATED);
Chris@0 1165 return \Drupal::service('file_system')->rmdir($uri, $context);
Chris@0 1166 }
Chris@0 1167
Chris@0 1168 /**
Chris@0 1169 * Creates a file with a unique filename in the specified directory.
Chris@0 1170 *
Chris@18 1171 * @deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0.
Chris@0 1172 * Use \Drupal\Core\File\FileSystem::tempnam().
Chris@0 1173 *
Chris@0 1174 * @see https://www.drupal.org/node/2418133
Chris@0 1175 */
Chris@0 1176 function drupal_tempnam($directory, $prefix) {
Chris@18 1177 @trigger_error('tempnam() is deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\File\FileSystemInterface::tempnam(). See https://www.drupal.org/node/2418133.', E_USER_DEPRECATED);
Chris@0 1178 return \Drupal::service('file_system')->tempnam($directory, $prefix);
Chris@0 1179 }
Chris@0 1180
Chris@0 1181 /**
Chris@0 1182 * Gets and sets the path of the configured temporary directory.
Chris@0 1183 *
Chris@0 1184 * @return mixed|null
Chris@0 1185 * A string containing the path to the temporary directory.
Chris@0 1186 */
Chris@0 1187 function file_directory_temp() {
Chris@0 1188 $temporary_directory = \Drupal::config('system.file')->get('path.temporary');
Chris@0 1189 if (empty($temporary_directory)) {
Chris@0 1190 // Needs set up.
Chris@0 1191 $config = \Drupal::configFactory()->getEditable('system.file');
Chris@0 1192 $temporary_directory = ComponentFileSystem::getOsTemporaryDirectory();
Chris@0 1193
Chris@0 1194 if (empty($temporary_directory)) {
Chris@0 1195 // If no directory has been found default to 'files/tmp'.
Chris@0 1196 $temporary_directory = PublicStream::basePath() . '/tmp';
Chris@0 1197
Chris@0 1198 // Windows accepts paths with either slash (/) or backslash (\), but will
Chris@0 1199 // not accept a path which contains both a slash and a backslash. Since
Chris@0 1200 // the 'file_public_path' variable may have either format, we sanitize
Chris@0 1201 // everything to use slash which is supported on all platforms.
Chris@0 1202 $temporary_directory = str_replace('\\', '/', $temporary_directory);
Chris@0 1203 }
Chris@0 1204 // Save the path of the discovered directory. Do not check config schema on
Chris@0 1205 // save.
Chris@0 1206 $config->set('path.temporary', (string) $temporary_directory)->save(TRUE);
Chris@0 1207 }
Chris@0 1208
Chris@0 1209 return $temporary_directory;
Chris@0 1210 }
Chris@0 1211
Chris@0 1212 /**
Chris@0 1213 * Discovers a writable system-appropriate temporary directory.
Chris@0 1214 *
Chris@0 1215 * @return mixed
Chris@0 1216 * A string containing the path to the temporary directory.
Chris@0 1217 *
Chris@0 1218 * @deprecated in Drupal 8.3.x-dev, will be removed before Drupal 9.0.0.
Chris@0 1219 * Use \Drupal\Component\FileSystem\FileSystem::getOsTemporaryDirectory().
Chris@0 1220 *
Chris@0 1221 * @see https://www.drupal.org/node/2418133
Chris@0 1222 */
Chris@0 1223 function file_directory_os_temp() {
Chris@0 1224 return ComponentFileSystem::getOsTemporaryDirectory();
Chris@0 1225 }
Chris@0 1226
Chris@0 1227 /**
Chris@0 1228 * @} End of "defgroup file".
Chris@0 1229 */