Commit 26cf55bb authored by catch's avatar catch
Browse files

Issue #1377740 by jbrown, bvanmeurs, catch, anrikun, Jorrit, rpayanm, mvc,...

Issue #1377740 by jbrown, bvanmeurs, catch, anrikun, Jorrit, rpayanm, mvc, mikeytown2, chx: file_unmanaged_move() should issue rename() where possible instead of copy() & unlink()
parent 327f8e38
......@@ -412,7 +412,7 @@ function file_valid_uri($uri) {
}
/**
* Copies a file to a new location without invoking the file API.
* Copies a file to a new location without database changes or hook invocation.
*
* This is a powerful function that in many ways performs like an advanced
* version of copy().
......@@ -422,10 +422,9 @@ function file_valid_uri($uri) {
* - If the $source and $destination are equal, the behavior depends on the
* $replace parameter. FILE_EXISTS_REPLACE will error out. FILE_EXISTS_RENAME
* will rename the file until the $destination is unique.
* - Provides a fallback using realpaths if the move fails using stream
* wrappers. This can occur because PHP's copy() function does not properly
* support streams if open_basedir is enabled. See
* https://bugs.php.net/bug.php?id=60456
* - Works around a PHP bug where copy() does not properly support streams if
* safe_mode or open_basedir are enabled.
* @see https://bugs.php.net/bug.php?id=60456
*
* @param $source
* A string specifying the filepath or URI of the source file.
......@@ -446,18 +445,66 @@ function file_valid_uri($uri) {
* @see file_copy()
*/
function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
if (!file_unmanaged_prepare($source, $destination, $replace)) {
return FALSE;
}
// Attempt to resolve the URIs. This is necessary in certain configurations
// (see above).
$real_source = drupal_realpath($source) ?: $source;
$real_destination = drupal_realpath($destination) ?: $destination;
// Perform the copy operation.
if (!@copy($real_source, $real_destination)) {
\Drupal::logger('file')->error('The specified file %file could not be copied to %destination.', array('%file' => $source, '%destination' => $destination));
return FALSE;
}
// Set the permissions on the new file.
drupal_chmod($destination);
return $destination;
}
/**
* Internal function that prepares the destination for a file_unmanaged_copy or
* file_unmanaged_move operation.
*
* - Checks if $source and $destination are valid and readable/writable.
* - Checks that $source is not equal to $destination; if they are an error
* is reported.
* - If file already exists in $destination either the call will error out,
* replace the file or rename the file based on the $replace parameter.
*
* @param $source
* A string specifying the filepath or URI of the source file.
* @param $destination
* A URI containing the destination that $source should be moved/copied to.
* The URI may be a bare filepath (without a scheme) and in that case the
* default scheme (file://) will be used. If this value is omitted, Drupal's
* default files scheme will be used, usually "public://".
* @param $replace
* Replace behavior when the destination file already exists:
* - FILE_EXISTS_REPLACE - Replace the existing file.
* - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
* unique.
* - FILE_EXISTS_ERROR - Do nothing and return FALSE.
*
* @return
* TRUE, or FALSE in the event of an error.
*
* @see file_unmanaged_copy()
* @see file_unmanaged_move()
*/
function file_unmanaged_prepare($source, &$destination = NULL, $replace = FILE_EXISTS_RENAME) {
$original_source = $source;
$logger = \Drupal::logger('file');
// Assert that the source file actually exists.
if (!file_exists($source)) {
// @todo Replace drupal_set_message() calls with exceptions instead.
drupal_set_message(t('The specified file %file could not be copied because no file by that name exists. Please check that you supplied the correct filename.', array('%file' => $original_source)), 'error');
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.', array('%file' => $original_source)), 'error');
if (($realpath = drupal_realpath($original_source)) !== FALSE) {
$logger->notice('File %file (%realpath) could not be copied because it does not exist.', array('%file' => $original_source, '%realpath' => $realpath));
$logger->notice('File %file (%realpath) could not be moved/copied because it does not exist.', array('%file' => $original_source, '%realpath' => $realpath));
}
else {
$logger->notice('File %file could not be copied because it does not exist.', array('%file' => $original_source));
$logger->notice('File %file could not be moved/copied because it does not exist.', array('%file' => $original_source));
}
return FALSE;
}
......@@ -478,8 +525,8 @@ function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXIST
$dirname = drupal_dirname($destination);
if (!file_prepare_directory($dirname)) {
// The destination is not valid.
$logger->notice('File %file could not be copied because the destination directory %destination is not configured correctly.', array('%file' => $original_source, '%destination' => $dirname));
drupal_set_message(t('The specified file %file could not be 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.', array('%file' => $original_source)), 'error');
$logger->notice('File %file could not be moved/copied because the destination directory %destination is not configured correctly.', array('%file' => $original_source, '%destination' => $dirname));
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.', array('%file' => $original_source)), 'error');
return FALSE;
}
}
......@@ -487,8 +534,8 @@ function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXIST
// Determine whether we can perform this operation based on overwrite rules.
$destination = file_destination($destination, $replace);
if ($destination === FALSE) {
drupal_set_message(t('The file %file could not be copied because a file by that name already exists in the destination directory.', array('%file' => $original_source)), 'error');
$logger->notice('File %file could not be copied because a file by that name already exists in the destination directory (%destination)', array('%file' => $original_source, '%destination' => $destination));
drupal_set_message(t('The file %file could not be moved/copied because a file by that name already exists in the destination directory.', array('%file' => $original_source)), 'error');
$logger->notice('File %file could not be moved/copied because a file by that name already exists in the destination directory (%destination)', array('%file' => $original_source, '%destination' => $destination));
return FALSE;
}
......@@ -496,26 +543,13 @@ function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXIST
$real_source = drupal_realpath($source);
$real_destination = drupal_realpath($destination);
if ($source == $destination || ($real_source !== FALSE) && ($real_source == $real_destination)) {
drupal_set_message(t('The specified file %file was not copied because it would overwrite itself.', array('%file' => $source)), 'error');
$logger->notice('File %file could not be copied because it would overwrite itself.', array('%file' => $source));
drupal_set_message(t('The specified file %file was not moved/copied because it would overwrite itself.', array('%file' => $source)), 'error');
$logger->notice('File %file could not be moved/copied because it would overwrite itself.', array('%file' => $source));
return FALSE;
}
// Make sure the .htaccess files are present.
file_ensure_htaccess();
// Perform the copy operation.
if (!@copy($source, $destination)) {
// If the copy failed and realpaths exist, retry the operation using them
// instead.
if ($real_source === FALSE || $real_destination === FALSE || !@copy($real_source, $real_destination)) {
$logger->error('The specified file %file could not be copied to %destination.', array('%file' => $source, '%destination' => $destination));
return FALSE;
}
}
// Set the permissions on the new file.
drupal_chmod($destination);
return $destination;
return TRUE;
}
/**
......@@ -566,12 +600,24 @@ function file_destination($destination, $replace) {
/**
* Moves a file to a new location without database changes or hook invocation.
*
* This is a powerful function that in many ways performs like an advanced
* version of rename().
* - Checks if $source and $destination are valid and readable/writable.
* - Checks that $source is not equal to $destination; if they are an error
* is reported.
* - If file already exists in $destination either the call will error out,
* replace the file or rename the file based on the $replace parameter.
* - Works around a PHP bug where rename() does not properly support streams if
* safe_mode or open_basedir are enabled.
* @see https://bugs.php.net/bug.php?id=60456
*
* @param $source
* A string specifying the filepath or URI of the original file.
* A string specifying the filepath or URI of the source file.
* @param $destination
* A string containing the destination that $source should be moved to.
* This must be a stream wrapper URI. If this value is omitted, Drupal's
* default files scheme will be used, usually "public://".
* A URI containing the destination that $source should be moved to. The
* URI may be a bare filepath (without a scheme) and in that case the default
* scheme (file://) will be used. If this value is omitted, Drupal's default
* files scheme will be used, usually "public://".
* @param $replace
* Replace behavior when the destination file already exists:
* - FILE_EXISTS_REPLACE - Replace the existing file.
......@@ -580,16 +626,37 @@ function file_destination($destination, $replace) {
* - FILE_EXISTS_ERROR - Do nothing and return FALSE.
*
* @return
* The URI of the moved file, or FALSE in the event of an error.
* The path to the new file, or FALSE in the event of an error.
*
* @see file_move()
*/
function file_unmanaged_move($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
$filepath = file_unmanaged_copy($source, $destination, $replace);
if ($filepath == FALSE || file_unmanaged_delete($source) == FALSE) {
if (!file_unmanaged_prepare($source, $destination, $replace)) {
return FALSE;
}
return $filepath;
// Ensure compatibility with Windows.
// @see drupal_unlink()
if ((substr(PHP_OS, 0, 3) == 'WIN') && (!file_stream_wrapper_valid_scheme(file_uri_scheme($source)))) {
chmod($source, 0600);
}
// Attempt to resolve the URIs. This is necessary in certain configurations
// (see above) and can also permit fast moves across local schemes.
$real_source = drupal_realpath($source) ?: $source;
$real_destination = drupal_realpath($destination) ?: $destination;
// Perform the move operation.
if (!@rename($real_source, $real_destination)) {
// Fall back to slow copy and unlink procedure. This is necessary for
// renames across schemes that are not local, or where rename() has not been
// implemented. It's not necessary to use drupal_unlink() as the Windows
// issue has already been resolved above.
if (!@copy($real_source, $real_destination) || !@unlink($real_source)) {
\Drupal::logger('file')->error('The specified file %file could not be moved to %destination.', array('%file' => $source, '%destination' => $destination));
return FALSE;
}
}
// Set the permissions on the new file.
drupal_chmod($destination);
return $destination;
}
/**
......
......@@ -134,7 +134,8 @@ function drupal_theme_rebuild() {
*/
function drupal_find_theme_functions($cache, $prefixes) {
$implementations = [];
$grouped_functions = \Drupal::service('theme.registry')->getPrefixGroupedUserFunctions();
$functions = get_defined_functions();
$theme_functions = preg_grep('/^(' . implode(')|(', $prefixes) . ')_/', $functions['user']);
foreach ($cache as $hook => $info) {
foreach ($prefixes as $prefix) {
......@@ -150,10 +151,8 @@ function drupal_find_theme_functions($cache, $prefixes) {
// are found using the base hook's pattern, not a pattern from an
// intermediary suggestion.
$pattern = isset($info['pattern']) ? $info['pattern'] : ($hook . '__');
// Grep only the functions which are within the prefix group.
list($first_prefix,) = explode('_', $prefix, 2);
if (!isset($info['base hook']) && !empty($pattern) && isset($grouped_functions[$first_prefix])) {
$matches = preg_grep('/^' . $prefix . '_' . $pattern . '/', $grouped_functions[$first_prefix]);
if (!isset($info['base hook']) && !empty($pattern)) {
$matches = preg_grep('/^' . $prefix . '_' . $pattern . '/', $theme_functions);
if ($matches) {
foreach ($matches as $match) {
$new_hook = substr($match, strlen($prefix) + 1);
......
......@@ -185,8 +185,6 @@ function file_copy(FileInterface $source, $destination = NULL, $replace = FILE_E
/**
* Moves a file to a new location and update the file's database entry.
*
* Moving a file is performed by copying the file to the new location and then
* deleting the original.
* - Checks if $source and $destination are valid and readable/writable.
* - Performs a file move if $source is not equal to $destination.
* - If file already exists in $destination either the call will error out,
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment