diff --git a/framework/yii/helpers/base/FileHelper.php b/framework/yii/helpers/base/FileHelper.php index d76ebc7..27088a6 100644 --- a/framework/yii/helpers/base/FileHelper.php +++ b/framework/yii/helpers/base/FileHelper.php @@ -191,4 +191,101 @@ class FileHelper rmdir($directory); } } + + /** + * Returns the files found under the specified directory and subdirectories. + * @param string $dir the directory under which the files will be looked for. + * @param array $options options for file searching. Valid options are: + * + * @return array files found under the directory. The file list is sorted. + */ + public static function findFiles($dir, array $options = array()) + { + $fileTypes = array(); + $exclude = array(); + $level = -1; + extract($options); + $list = static::findFilesRecursive($dir, '', $fileTypes, $exclude, $level); + sort($list); + return $list; + } + + /** + * Returns the files found under the specified directory and subdirectories. + * This method is mainly used by [[findFiles]]. + * @param string $dir the source directory. + * @param string $base the path relative to the original source directory. + * @param array $fileTypes list of file name suffix (without dot). Only files with these suffixes will be returned. + * @param array $exclude list of directory and file exclusions. Each exclusion can be either a name or a path. + * If a file or directory name or path matches the exclusion, it will not be copied. For example, an exclusion of + * '.svn' will exclude all files and directories whose name is '.svn'. And an exclusion of '/a/b' will exclude + * file or directory '$src/a/b'. Note, that '/' should be used as separator regardless of the value of the DIRECTORY_SEPARATOR constant. + * @param integer $level recursion depth. It defaults to -1. + * Level -1 means searching for all directories and files under the directory; + * Level 0 means searching for only the files DIRECTLY under the directory; + * level N means searching for those directories that are within N levels. + * @return array files found under the directory. + */ + protected static function findFilesRecursive($dir, $base, $fileTypes, $exclude, $level) + { + $list = array(); + $handle = opendir($dir); + while (($file = readdir($handle)) !== false) { + if ($file === '.' || $file === '..') { + continue; + } + $path = $dir . DIRECTORY_SEPARATOR . $file; + $isFile = is_file($path); + if (static::validatePath($base, $file, $isFile, $fileTypes, $exclude)) { + if ($isFile) { + $list[] = $path; + } elseif ($level) { + $list = array_merge($list, static::findFilesRecursive($path, $base . DIRECTORY_SEPARATOR . $file, $fileTypes, $exclude, $level-1)); + } + } + } + closedir($handle); + return $list; + } + + /** + * Validates a file or directory, checking if it match given conditions. + * @param string $base the path relative to the original source directory + * @param string $file the file or directory name + * @param boolean $isFile whether this is a file + * @param array $fileTypes list of valid file name suffixes (without dot). + * @param array $exclude list of directory and file exclusions. Each exclusion can be either a name or a path. + * If a file or directory name or path matches the exclusion, false will be returned. For example, an exclusion of + * '.svn' will return false for all files and directories whose name is '.svn'. And an exclusion of '/a/b' will return false for + * file or directory '$src/a/b'. Note, that '/' should be used as separator regardless of the value of the DIRECTORY_SEPARATOR constant. + * @return boolean whether the file or directory is valid + */ + protected static function validatePath($base, $file, $isFile, $fileTypes, $exclude) + { + foreach ($exclude as $e) { + if ($file === $e || strpos($base . DIRECTORY_SEPARATOR . $file, $e) === 0) { + return false; + } + } + if (!$isFile || empty($fileTypes)) { + return true; + } + if (($type = pathinfo($file, PATHINFO_EXTENSION)) !== '') { + return in_array($type, $fileTypes); + } else { + return false; + } + } } diff --git a/tests/unit/framework/helpers/FileHelperTest.php b/tests/unit/framework/helpers/FileHelperTest.php index 2027510..262c3be 100644 --- a/tests/unit/framework/helpers/FileHelperTest.php +++ b/tests/unit/framework/helpers/FileHelperTest.php @@ -95,4 +95,33 @@ class FileHelperTest extends TestCase $this->assertFalse(file_exists($dirName), 'Unable to remove directory!'); } + + public function testFindFiles() + { + $basePath = $this->testFilePath; + $expectedFiles = array(); + + $dirName = $basePath . DIRECTORY_SEPARATOR . 'test_dir_for_remove'; + mkdir($dirName, 0777, true); + $files = array( + 'file1.txt' => 'file 1 content', + 'file2.txt' => 'file 2 content', + ); + foreach ($files as $name => $content) { + $fileName = $dirName . DIRECTORY_SEPARATOR . $name; + file_put_contents($fileName, $content); + $expectedFiles[] = $fileName; + } + $subDirName = $dirName . DIRECTORY_SEPARATOR . 'test_sub_dir'; + mkdir($subDirName, 0777, true); + foreach ($files as $name => $content) { + $fileName = $subDirName . DIRECTORY_SEPARATOR . $name; + file_put_contents($fileName, $content); + $expectedFiles[] = $fileName; + } + + $foundFiles = FileHelper::findFiles($dirName); + sort($expectedFiles); + $this->assertEquals($expectedFiles, $foundFiles); + } } \ No newline at end of file