MDL-39442 Do not use native rename() when moving folders
authorDavid Mudrák <david@moodle.com>
Thu, 2 May 2013 11:44:47 +0000 (13:44 +0200)
committerDavid Mudrák <david@moodle.com>
Thu, 2 May 2013 11:58:43 +0000 (13:58 +0200)
The native rename() function does not support moving folders
cross-device. See https://bugs.php.net/bug.php?id=54097 for details. So
instead of trying to move the whole tree, the new installer's method
moves files recursively one by one.

This is consistent with what mdeploy.php already does.

admin/tool/installaddon/classes/installer.php
admin/tool/installaddon/deploy.php
admin/tool/installaddon/tests/installer_test.php
admin/tool/installaddon/version.php

index fa4babb..a882a03 100644 (file)
@@ -395,6 +395,52 @@ class tool_installaddon_installer {
         }
     }
 
+    /**
+     * Moves the given source into a new location recursively
+     *
+     * This is cross-device safe implementation to be used instead of the native rename() function.
+     * See https://bugs.php.net/bug.php?id=54097 for more details.
+     *
+     * @param string $source full path to the existing directory
+     * @param string $target full path to the new location of the directory
+     */
+    public function move_directory($source, $target) {
+
+        if (file_exists($target)) {
+            throw new tool_installaddon_installer_exception('err_folder_already_exists', array('path' => $target));
+        }
+
+        if (is_dir($source)) {
+            $handle = opendir($source);
+        } else {
+            throw new tool_installaddon_installer_exception('err_no_such_folder', array('path' => $source));
+        }
+
+        make_writable_directory($target);
+
+        while ($filename = readdir($handle)) {
+            $sourcepath = $source.'/'.$filename;
+            $targetpath = $target.'/'.$filename;
+
+            if ($filename === '.' or $filename === '..') {
+                continue;
+            }
+
+            if (is_dir($sourcepath)) {
+                $this->move_directory($sourcepath, $targetpath);
+
+            } else {
+                rename($sourcepath, $targetpath);
+            }
+        }
+
+        closedir($handle);
+
+        rmdir($source);
+
+        clearstatcache();
+    }
+
     //// End of external API ///////////////////////////////////////////////////
 
     /**
index 932e9f2..f68ebed 100644 (file)
@@ -71,6 +71,6 @@ if (file_exists($plugintypepath.'/'.$pluginname)) {
         get_string('invaliddata', 'core_error'));
 }
 
-rename($zipcontentpath.'/'.$pluginname, $plugintypepath.'/'.$pluginname);
+$installer->move_directory($zipcontentpath.'/'.$pluginname, $plugintypepath.'/'.$pluginname);
 fulldelete($CFG->tempdir.'/tool_installaddon/'.$jobid);
 redirect(new moodle_url('/admin'));
index fd8d1d1..4169719 100644 (file)
@@ -125,6 +125,20 @@ class tool_installaddon_installer_test extends advanced_testcase {
         )));
         $this->assertSame(false, $installer->testable_decode_remote_request($request));
     }
+
+    public function test_move_directory() {
+        $jobid = md5(rand().uniqid('test_', true));
+        $jobroot = make_temp_directory('tool_installaddon/'.$jobid);
+        $contentsdir = make_temp_directory('tool_installaddon/'.$jobid.'/contents/sub/folder');
+        file_put_contents($contentsdir.'/readme.txt', 'Hello world!');
+
+        $installer = tool_installaddon_installer::instance();
+        $installer->move_directory($jobroot.'/contents', $jobroot.'/moved');
+
+        $this->assertFalse(is_dir($jobroot.'/contents'));
+        $this->assertTrue(is_file($jobroot.'/moved/sub/folder/readme.txt'));
+        $this->assertSame('Hello world!', file_get_contents($jobroot.'/moved/sub/folder/readme.txt'));
+    }
 }
 
 
index 94ab2e5..db5c7cd 100644 (file)
@@ -26,4 +26,4 @@ defined('MOODLE_INTERNAL') || die();
 $plugin->component  = 'tool_installaddon';
 $plugin->version    = 2013031400;
 $plugin->requires   = 2013031400;
-$plugin->maturity   = MATURITY_BETA;
+$plugin->maturity   = MATURITY_STABLE;