 core/includes/file.inc |  150 ++++++++++++++++++++++++++++++++++++------------
 1 files changed, 114 insertions(+), 36 deletions(-)

diff --git a/core/includes/file.inc b/core/includes/file.inc
index babf465..5651b98 100644
--- a/core/includes/file.inc
+++ b/core/includes/file.inc
@@ -828,7 +828,7 @@ function file_valid_uri($uri) {
  *   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.
- * - Provides a fallback using realpaths if the move fails using stream
+ * - Provides a fallback using realpaths if the copy fails using stream
  *   wrappers. This can occur because PHP's copy() function does not properly
  *   support streams if safe_mode or open_basedir are enabled. See
  *   https://bugs.php.net/bug.php?id=60456
@@ -853,18 +853,72 @@ 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;
+  }
+
+  // Perform the copy operation.
+  if (!@copy($source, $destination)) {
+    // If the copy failed and realpaths exist, retry the operation using them
+    // instead. Necessary in certain configurations.
+    // @see http://drupal.org/node/1240256
+    $real_source = drupal_realpath($source);
+    $real_destination = drupal_realpath($destination);
+    if ($real_source === FALSE || $real_destination === FALSE || !@copy($real_source, $real_destination)) {
+      watchdog('file', 'The specified file %file could not be copied to %destination.', array('%file' => $source, '%destination' => $destination), WATCHDOG_ERROR);
+      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;
   $original_destination = $destination;
 
   // 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) {
-      watchdog('file', 'File %file (%realpath) could not be copied because it does not exist.', array('%file' => $original_source, '%realpath' => $realpath));
+      watchdog('file', 'File %file (%realpath) could not be moved/copied because it does not exist.', array('%file' => $original_source, '%realpath' => $realpath));
     }
     else {
-      watchdog('file', 'File %file could not be copied because it does not exist.', array('%file' => $original_source));
+      watchdog('file', 'File %file could not be moved/copied because it does not exist.', array('%file' => $original_source));
     }
     return FALSE;
   }
@@ -885,8 +939,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.
-      watchdog('file', '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');
+      watchdog('file', '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;
     }
   }
@@ -894,8 +948,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');
-    watchdog('file', 'File %file could not be copied because a file by that name already exists in the destination directory (%directory)', 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');
+    watchdog('file', 'File %file could not be moved/copied because a file by that name already exists in the destination directory (%directory)', array('%file' => $original_source, '%destination' => $destination));
     return FALSE;
   }
 
@@ -903,26 +957,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');
-    watchdog('file', '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');
+    watchdog('file', '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)) {
-      watchdog('file', 'The specified file %file could not be copied to %destination.', array('%file' => $source, '%destination' => $destination), WATCHDOG_ERROR);
-      return FALSE;
-    }
-  }
-
-  // Set the permissions on the new file.
-  drupal_chmod($destination);
-
-  return $destination;
+  return TRUE;
 }
 
 /**
@@ -974,8 +1015,6 @@ function file_destination($destination, $replace) {
 /**
  * Move a file to a new location and update the file's database entry.
  *
- * Moving a file is performed by copying the file to the new location and then
- * deleting the original.
  * - Checks if $source and $destination are valid and readable/writable.
  * - Performs a file move if $source is not equal to $destination.
  * - If file already exists in $destination either the call will error out,
@@ -1052,15 +1091,27 @@ function file_move(stdClass $source, $destination = NULL, $replace = FILE_EXISTS
 }
 
 /**
- * Move a file to a new location without calling any hooks or making any
- * changes to the database.
+ * Moves a file to a new location without invoking the file API.
+ *
+ * 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.
+ * - Provides a fallback using realpaths if the move fails using stream
+ *   wrappers. This can occur because PHP's rename() function 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.
@@ -1069,16 +1120,43 @@ function file_move(stdClass $source, $destination = NULL, $replace = FILE_EXISTS
  *   - 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.
+  if (substr(PHP_OS, 0, 3) == 'WIN' && !file_stream_wrapper_valid_scheme(file_uri_scheme($source))) {
+    chmod($source, 0600);
+  }
+
+  // Perform the move operation.
+  if (!@rename($source, $destination)) {
+    // If the move failed and realpaths exist, retry the operation using them
+    // instead. This allows fast moves across schemes. Also necessary in certain
+    // configurations.
+    // @see http://drupal.org/node/1240256
+    $real_source = drupal_realpath($source);
+    $real_destination = drupal_realpath($destination);
+    if ($real_source === FALSE || $real_destination === FALSE || !@rename($real_source, $real_destination)) {
+      // Finally fall back to slow copy and delete procedure. This is necessary
+      // for remote schemes.
+      $filepath = @copy($source, $destination);
+      if ($filepath == FALSE || file_unmanaged_delete($source) == FALSE) {
+        watchdog('file', 'The specified file %file could not be moved to %destination.', array('%file' => $source, '%destination' => $destination), WATCHDOG_ERROR);
+        return FALSE;
+      }
+    }
+  }
+
+  // Set the permissions on the new file.
+  drupal_chmod($destination);
+
+  return $destination;
 }
 
 /**
