Commit 7d630371 by Alexander Makarov

Merge pull request #4084 from resurtm/4080-file-helper-remove-directory-symlinks

Fixes #4080, FileHelper::removeDirectory() symlinks handling enhancements
parents a9275f94 fb9df787
......@@ -119,6 +119,7 @@ Yii Framework 2 Change Log
- Improved overall slug results.
- Added note about the fact that intl is required for non-latin languages to requirements checker.
- Enh #4028: Added ability to `yii\widgets\Menu` to encode each item's label separately (creocoder, umneeq)
- Enh #4080: Added proper handling and support of the symlinked directories in `FileHelper`, added $options parameter in `FileHelper::removeDirectory()` (resurtm)
- Enh: Added support for using sub-queries when building a DB query with `IN` condition (qiangxue)
- Enh: Supported adding a new response formatter without the need to reconfigure existing formatters (qiangxue)
- Enh: Added `yii\web\UrlManager::addRules()` to simplify adding new URL rules (qiangxue)
......
......@@ -269,9 +269,17 @@ class BaseFileHelper
/**
* Removes a directory (and all its content) recursively.
* @param string $dir the directory to be deleted recursively.
* @param array $options options for directory remove. Valid options are:
*
* - traverseSymlinks: boolean, whether symlinks to the directories should be traversed too.
* Defaults to `false`, meaning the content of the symlinked directory would not be deleted.
* Only symlink would be removed in that default case.
*/
public static function removeDirectory($dir)
public static function removeDirectory($dir, $options = [])
{
if (!isset($options['traverseSymlinks'])) {
$options['traverseSymlinks'] = false;
}
if (!is_dir($dir) || !($handle = opendir($dir))) {
return;
}
......@@ -280,15 +288,24 @@ class BaseFileHelper
continue;
}
$path = $dir . DIRECTORY_SEPARATOR . $file;
if (is_link($path)) {
if ($options['traverseSymlinks'] && is_dir($path)) {
static::removeDirectory($path, $options);
}
unlink($path);
} else {
if (is_file($path)) {
unlink($path);
} else {
static::removeDirectory($path);
static::removeDirectory($path, $options);
}
}
}
closedir($handle);
if (!is_link($dir)) {
rmdir($dir);
}
}
/**
* Returns the files found under the specified directory and subdirectories.
......
......@@ -87,8 +87,12 @@ class FileHelperTest extends TestCase
foreach ($items as $name => $content) {
$itemName = $basePath . DIRECTORY_SEPARATOR . $name;
if (is_array($content)) {
if (isset($content[0], $content[1]) && $content[0] == 'symlink') {
symlink($content[1], $itemName);
} else {
mkdir($itemName, 0777, true);
$this->createFileStructure($content, $itemName);
}
} else {
file_put_contents($itemName, $content);
}
......@@ -195,6 +199,92 @@ class FileHelperTest extends TestCase
FileHelper::removeDirectory($basePath . DIRECTORY_SEPARATOR . 'nonExisting');
}
public function testRemoveDirectorySymlinks1()
{
if (strtolower(substr(PHP_OS, 0, 3)) == 'win') {
$this->markTestSkipped('Cannot test this on MS Windows since symlinks are uncommon for it.');
}
$dirName = 'remove-directory-symlinks-1';
$this->createFileStructure([
$dirName => [
'file' => 'Symlinked file.',
'directory' => [
'standard-file-1' => 'Standard file 1.'
],
'symlinks' => [
'standard-file-2' => 'Standard file 2.',
'symlinked-file' => ['symlink', '..' . DIRECTORY_SEPARATOR . 'file'],
'symlinked-directory' => ['symlink', '..' . DIRECTORY_SEPARATOR . 'directory'],
],
],
]);
$basePath = $this->testFilePath . DIRECTORY_SEPARATOR . $dirName . DIRECTORY_SEPARATOR;
$this->assertFileExists($basePath . 'file');
$this->assertTrue(is_dir($basePath . 'directory'));
$this->assertFileExists($basePath . 'directory' . DIRECTORY_SEPARATOR . 'standard-file-1');
$this->assertTrue(is_dir($basePath . 'symlinks'));
$this->assertFileExists($basePath . 'symlinks' . DIRECTORY_SEPARATOR . 'standard-file-2');
$this->assertFileExists($basePath . 'symlinks' . DIRECTORY_SEPARATOR . 'symlinked-file');
$this->assertTrue(is_dir($basePath . 'symlinks' . DIRECTORY_SEPARATOR . 'symlinked-directory'));
$this->assertFileExists($basePath . 'symlinks' . DIRECTORY_SEPARATOR . 'symlinked-directory' . DIRECTORY_SEPARATOR . 'standard-file-1');
FileHelper::removeDirectory($basePath . 'symlinks');
$this->assertFileExists($basePath . 'file');
$this->assertTrue(is_dir($basePath . 'directory'));
$this->assertFileExists($basePath . 'directory' . DIRECTORY_SEPARATOR . 'standard-file-1'); // symlinked directory still have it's file
$this->assertFalse(is_dir($basePath . 'symlinks'));
$this->assertFileNotExists($basePath . 'symlinks' . DIRECTORY_SEPARATOR . 'standard-file-2');
$this->assertFileNotExists($basePath . 'symlinks' . DIRECTORY_SEPARATOR . 'symlinked-file');
$this->assertFalse(is_dir($basePath . 'symlinks' . DIRECTORY_SEPARATOR . 'symlinked-directory'));
$this->assertFileNotExists($basePath . 'symlinks' . DIRECTORY_SEPARATOR . 'symlinked-directory' . DIRECTORY_SEPARATOR . 'standard-file-1');
}
public function testRemoveDirectorySymlinks2()
{
if (strtolower(substr(PHP_OS, 0, 3)) == 'win') {
$this->markTestSkipped('Cannot test this on MS Windows since symlinks are uncommon for it.');
}
$dirName = 'remove-directory-symlinks-2';
$this->createFileStructure([
$dirName => [
'file' => 'Symlinked file.',
'directory' => [
'standard-file-1' => 'Standard file 1.'
],
'symlinks' => [
'standard-file-2' => 'Standard file 2.',
'symlinked-file' => ['symlink', '..' . DIRECTORY_SEPARATOR . 'file'],
'symlinked-directory' => ['symlink', '..' . DIRECTORY_SEPARATOR . 'directory'],
],
],
]);
$basePath = $this->testFilePath . DIRECTORY_SEPARATOR . $dirName . DIRECTORY_SEPARATOR;
$this->assertFileExists($basePath . 'file');
$this->assertTrue(is_dir($basePath . 'directory'));
$this->assertFileExists($basePath . 'directory' . DIRECTORY_SEPARATOR . 'standard-file-1');
$this->assertTrue(is_dir($basePath . 'symlinks'));
$this->assertFileExists($basePath . 'symlinks' . DIRECTORY_SEPARATOR . 'standard-file-2');
$this->assertFileExists($basePath . 'symlinks' . DIRECTORY_SEPARATOR . 'symlinked-file');
$this->assertTrue(is_dir($basePath . 'symlinks' . DIRECTORY_SEPARATOR . 'symlinked-directory'));
$this->assertFileExists($basePath . 'symlinks' . DIRECTORY_SEPARATOR . 'symlinked-directory' . DIRECTORY_SEPARATOR . 'standard-file-1');
FileHelper::removeDirectory($basePath . 'symlinks', ['traverseSymlinks' => true]);
$this->assertFileExists($basePath . 'file');
$this->assertTrue(is_dir($basePath . 'directory'));
$this->assertFileNotExists($basePath . 'directory' . DIRECTORY_SEPARATOR . 'standard-file-1'); // symlinked directory doesn't have it's file now
$this->assertFalse(is_dir($basePath . 'symlinks'));
$this->assertFileNotExists($basePath . 'symlinks' . DIRECTORY_SEPARATOR . 'standard-file-2');
$this->assertFileNotExists($basePath . 'symlinks' . DIRECTORY_SEPARATOR . 'symlinked-file');
$this->assertFalse(is_dir($basePath . 'symlinks' . DIRECTORY_SEPARATOR . 'symlinked-directory'));
$this->assertFileNotExists($basePath . 'symlinks' . DIRECTORY_SEPARATOR . 'symlinked-directory' . DIRECTORY_SEPARATOR . 'standard-file-1');
}
public function testFindFiles()
{
$dirName = 'test_dir';
......
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