From ba6e7e85dbac8d9434b80b1f73776b35539e57b2 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sat, 1 Jun 2024 12:02:23 +0200 Subject: [PATCH] Merge #28264 to get a simpler code --- htdocs/core/lib/files.lib.php | 17 +- test/phpunit/AllTests.php | 2 + test/phpunit/FilesLibMoveDirTest.php | 283 +++++++++++++++++++++++++++ 3 files changed, 299 insertions(+), 3 deletions(-) create mode 100644 test/phpunit/FilesLibMoveDirTest.php diff --git a/htdocs/core/lib/files.lib.php b/htdocs/core/lib/files.lib.php index a6beaff3c00..6c082d59be3 100644 --- a/htdocs/core/lib/files.lib.php +++ b/htdocs/core/lib/files.lib.php @@ -1167,7 +1167,7 @@ function dol_move($srcfile, $destfile, $newmask = '0', $overwriteifexists = 1, $ * * @param string $srcdir Source directory * @param string $destdir Destination directory - * @param int $overwriteifexists Overwrite directory if exists (1 by default) + * @param int $overwriteifexists Overwrite directory if it already exists (1 by default) * @param int $indexdatabase Index new name of files into database. * @param int $renamedircontent Also rename contents inside srcdir after the move to match new destination name. * @return boolean True if OK, false if KO @@ -1182,7 +1182,7 @@ function dol_move_dir($srcdir, $destdir, $overwriteifexists = 1, $indexdatabase $destexists = dol_is_dir($destdir); if (!$srcexists) { - dol_syslog("files.lib.php::dol_move_dir srcdir does not exists. we ignore the move request."); + dol_syslog("files.lib.php::dol_move_dir srcdir does not exists. Move fails"); return false; } @@ -1190,9 +1190,20 @@ function dol_move_dir($srcdir, $destdir, $overwriteifexists = 1, $indexdatabase $newpathofsrcdir = dol_osencode($srcdir); $newpathofdestdir = dol_osencode($destdir); + // On windows, if destination directory exists and is empty, command fails. So if overwrite is on, we first remove destination directory. + // On linux, if destination directory exists and is empty, command succeed. So no need to delete di destination directory first. + // Note: If dir exists and is not empty, it will and must fail on both linux and windows even, if option $overwriteifexists is on. + if ($overwriteifexists) { + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + if (is_dir($newpathofdestdir)) { + rmdir($newpathofdestdir); + } + } + } + $result = @rename($newpathofsrcdir, $newpathofdestdir); - // Now we rename also contents inside dir after the move to match new destination name + // Now rename contents in the directory after the move to match the new destination if ($result && $renamedircontent) { if (file_exists($newpathofdestdir)) { $destbasename = basename($newpathofdestdir); diff --git a/test/phpunit/AllTests.php b/test/phpunit/AllTests.php index 3f1a3409918..c96380bdcc5 100644 --- a/test/phpunit/AllTests.php +++ b/test/phpunit/AllTests.php @@ -93,6 +93,8 @@ class AllTests //$suite->addTestSuite('DateLibTzFranceTest'); require_once dirname(__FILE__).'/MarginsLibTest.php'; $suite->addTestSuite('MarginsLibTest'); + require_once dirname(__FILE__).'/FilesLibMoveDirTest.php'; + $suite->addTestSuite('FilesLibMoveDirTest'); require_once dirname(__FILE__).'/FilesLibTest.php'; $suite->addTestSuite('FilesLibTest'); require_once dirname(__FILE__).'/GetUrlLibTest.php'; diff --git a/test/phpunit/FilesLibMoveDirTest.php b/test/phpunit/FilesLibMoveDirTest.php new file mode 100644 index 00000000000..d3654689474 --- /dev/null +++ b/test/phpunit/FilesLibMoveDirTest.php @@ -0,0 +1,283 @@ + + * Copyright (C) 2012 Regis Houssin + * Copyright (C) 2023 Alexandre Janniaux + * Copyright (C) 2024 MDW + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * or see https://www.gnu.org/ + */ + +/** + * \file test/phpunit/FilesLibRenameTest.php + * \ingroup test + * \brief PHPUnit test + * \remarks To run this script as CLI: phpunit test/phpunit/FilesLibRenameTest.php + */ + +global $conf,$user,$langs,$db; +//define('TEST_DB_FORCE_TYPE','mysql'); // This is to force using mysql driver +//require_once 'PHPUnit/Autoload.php'; +require_once dirname(__FILE__).'/../../htdocs/master.inc.php'; +require_once dirname(__FILE__).'/../../htdocs/core/lib/files.lib.php'; +require_once dirname(__FILE__).'/CommonClassTest.class.php'; + +if (empty($user->id)) { + print "Load permissions for admin user nb 1\n"; + $user->fetch(1); + $user->getrights(); +} +$conf->global->MAIN_DISABLE_ALL_MAILS = 1; + + +/** + * Class for PHPUnit tests + * + * @backupGlobals disabled + * @backupStaticAttributes enabled + * @remarks backupGlobals must be disabled to have db,conf,user and lang not erased. + */ +class FilesLibRenameTest extends CommonClassTest +{ + protected $sourceDir; + protected $destinationDir; + + /** + * Setup test case + * + * @return void + */ + protected function setUp(): void + { + parent::setup(); + + global $conf; + $this->sourceDir = $conf->admin->dir_temp."/source Dir"; + $this->destinationDir = $conf->admin->dir_temp.'/dest dir'; + + $this->cleanupDirectories(); + mkdir($this->sourceDir, 0777, true); + file_put_contents($this->sourceDir . '/file1.txt', 'Test file 1 content'); + file_put_contents($this->sourceDir . '/file2.txt', 'Test file 2 content'); + + // Create a temporary destination directory + mkdir($this->destinationDir); + + $this->nbLinesToShow = 0; // Nothing useful in the dolibarr log for debugging. + } + + /** + * Tear down test case + * + * Cleanup files that were created. + * + * @return void + */ + protected function tearDown(): void + { + parent::tearDown(); + $this->cleanupDirectories(); + } + + /** + * Clean up the test directories + * + * @return bool True if success + */ + protected function cleanUpDirectories() + { + if (is_dir($this->sourceDir)) { + $this->removeDirectory($this->sourceDir); + } + if (is_dir($this->destinationDir)) { + $this->removeDirectory($this->destinationDir); + } + } + + /** + * Remove a directory (not a test) + * + * @param $dir Directory to test for emptyness + * + * @return bool True if success + */ + protected function removeDirectory($dir) + { + $files = array_diff(scandir($dir), array('.', '..')); + foreach ($files as $file) { + (is_dir("$dir/$file")) ? $this->removeDirectory("$dir/$file") : unlink("$dir/$file"); + } + return rmdir($dir); + } + + + /** + * Assert a directory is empty + * + * @param $dir string Directory to test for emptyness + * @param $message string Message in case of failure + * + * @return bool True if success + */ + protected function assertDirectoryIsEmpty($dir, $message = '') + { + // Check if the destination directory is empty + $filesInDirectory = array_diff(scandir($dir), ['.', '..']); + $this->assertDirectoryExists($dir, $message); + //var_dump($filesInDirectory); exit; + $this->assertEmpty($filesInDirectory, $message."(".implode(",", $filesInDirectory).")"); + } + + + + /** + * Test "renaming" a non empty directory to an empty one + * + * @return void + */ + public function testRenameNonEmptyDirToEmptyDir() + { + // Compute and store MD5 checksums of source files + $file1MD5 = md5_file($this->sourceDir . '/file1.txt'); + $file2MD5 = md5_file($this->sourceDir . '/file2.txt'); + + $this->assertTrue(dol_move_dir($this->sourceDir, $this->destinationDir), "Failed renaming '{$this->sourceDir}' to '{$this->destinationDir}'"); + $this->assertDirectoryExists($this->destinationDir); + $this->assertFileNotExistsCompat($this->sourceDir . '/file1.txt', "file1.txt must not exist in source"); + $this->assertFileNotExistsCompat($this->sourceDir . '/file2.txt', "file2.txt must not exist in source"); + $this->assertFileExists($this->destinationDir . '/file1.txt', "file1.txt must exist in destination"); + $this->assertFileExists($this->destinationDir . '/file2.txt', "file2.txt must exist in destination"); + // Validate MD5 checksums + $this->assertEquals(md5_file($this->destinationDir . '/file1.txt'), $file1MD5, "file1.txt was not moved correctly"); + $this->assertEquals(md5_file($this->destinationDir . '/file2.txt'), $file2MD5, "file2.txt was not moved correctly"); + } + + /** + * Test "renaming" an empty directory to an non empty one + * + * @return void + * + * @depends testRenameNonEmptyDirToEmptyDir + * The depends says test is run only if previous is ok + */ + public function testRenameEmptyDirToNonEmptyDir() + { + mkdir($src = $this->sourceDir . '/rename_empty_directory'); + mkdir($dst = $this->destinationDir . '/dest_nonempty_directory'); + + // Create some files in the destination directory + file_put_contents($dst . '/file2.txt', 'Dest Test file 2 content'); + file_put_contents($dst . '/file3.txt', 'Dest Test file 3 content'); + $file2MD5 = md5_file($dst . '/file2.txt'); + $file3MD5 = md5_file($dst . '/file3.txt'); + + $this->assertFalse(dol_move_dir($src, $dst), "Rename empty to non empty directory must fail"); + $this->assertDirectoryExists($src, "Empty source directory must still exist"); + $this->assertDirectoryExists($dst, "Destination directory does not exist"); + $this->assertFileExists($dst . '/file2.txt', "File2 should still exist"); + $this->assertFileExists($dst . '/file3.txt', "File3 should still exist"); + // Validate MD5 checksums + $this->assertEquals($file2MD5, md5_file($dst . '/file2.txt'), "File2 does not have the expected contents"); + $this->assertEquals($file3MD5, md5_file($dst . '/file3.txt'), "File3 does not have the expected contents"); + } + + /** + * Test "renaming" an empty directory to an existing non empty one + * + * @return void + * + * @depends testRenameEmptyDirToNonEmptyDir + * The depends says test is run only if previous is ok + */ + public function testRenameEmptyDirToExistingEmptyDir() + { + // Create an empty directory + mkdir($src = $this->sourceDir . '/empty_directory'); + mkdir($dst = $this->destinationDir . '/destEmpty_directory'); + + $this->assertDirectoryIsEmpty($src, 'Source directory is not empty before rename.'); + $this->assertDirectoryIsEmpty($dst, 'Destination directory is not empty before rename.'); + $this->assertTrue(dol_move_dir($src, $dst), "Renaming the empty directory to an empty one did not succeed"); + + $this->assertDirectoryNotExistsCompat($src); + $this->assertDirectoryExists($dst, "The destination directory does not exist"); + $this->assertDirectoryIsEmpty($dst, 'Destination directory is not empty after rename.'); + } + + /** + * Test "renaming" an non empty directory to an non empty one + * + * @return void + * + * @depends testRenameEmptyDirToExistingEmptyDir + * The depends says test is run only if previous is ok + */ + public function testRenameNonEmptyDirToNonEmptyDir() + { + // The source directory and destination directory also have same file names. + mkdir($src = $this->sourceDir . '/src_directory'); + file_put_contents($src . '/file1.txt', 'Src Test file 1 content'); + file_put_contents($src . '/file3.txt', 'Src Test file 3 content'); + $srcFile1MD5 = md5_file($src . '/file1.txt'); + $srcFile3MD5 = md5_file($src . '/file3.txt'); + + // Create some files in the destination directory + mkdir($dst = $this->destinationDir . '/destNonEmpty_directory'); + file_put_contents($dst . '/file2.txt', 'Dest Test file 2 content'); + file_put_contents($dst . '/file3.txt', 'Dest Test file 3 content'); + $dstFile2MD5 = md5_file($dst . '/file2.txt'); + $dstFile3MD5 = md5_file($dst . '/file3.txt'); + + // Try to rename + $this->assertFalse(dol_move_dir($src, $dst), "Rename non empty directory to non empty directory must fail"); + + // Validate result + $this->assertDirectoryExists($src, "Source directory must still exist"); + $this->assertDirectoryExists($dst, "Destination directory must still exist"); + $this->assertFileExists($dst . '/file2.txt', "File2 does not exist"); + $this->assertFileExists($dst . '/file3.txt', "File3 does not exist"); + // Validate MD5 checksums + $this->assertEquals(md5_file($src . '/file1.txt'), $srcFile1MD5, "SrcFile1 does not have the expected contents"); + $this->assertEquals(md5_file($src . '/file3.txt'), $srcFile3MD5, "SrcFile3 does not have the expected contents"); + $this->assertEquals(md5_file($dst . '/file2.txt'), $dstFile2MD5, "DstFile1 does not have the expected contents"); + $this->assertEquals(md5_file($dst . '/file3.txt'), $dstFile3MD5, "DstFile3 does not have the expected contents"); + } + + + /** + * Test "renaming" an directory with depth and contents to an non existing one + * + * @return void + * + * @depends testRenameNonEmptyDirToNonEmptyDir + * The depends says test is run only if previous is ok + */ + public function testRenameDirectoryDepthOfTwo() + { + // Create a directory with depth of 2 + mkdir($srcDir = $this->sourceDir . '/subdirectory'); + $dstDir = $this->destinationDir . '/subdirectory'; + file_put_contents($srcDir.'/file3.txt', 'Test file 3 content'); + + // Compute and store MD5 checksums of source files + $file3MD5 = md5_file($srcDir.'/file3.txt'); + + $this->assertTrue(dol_move_dir($srcDir, $dstDir), "Rename did not succeed"); + $this->assertDirectoryExists($dstDir, "Destination directory does not exist"); + $this->assertDirectoryExists($dstDir, "Destination subdirectory does not exist"); + $this->assertFileExists($dstDir . '/file3.txt', "Destination file does not exist"); + // Validate MD5 checksum + $this->assertEquals($file3MD5, md5_file($dstDir . '/file3.txt'), "The destination file's contents is not correct"); + } +}