Commit ab3d0114 by Qiang Xue

Merge pull request #4752 from klimov-paul/4062-find-files-case-sensative

Enh #4062 : Added 'caseless' option to `yii\helpers\BaseFileHelper::findFiles()`
parents a3e7a33a 0bf16497
...@@ -187,6 +187,7 @@ Yii Framework 2 Change Log ...@@ -187,6 +187,7 @@ Yii Framework 2 Change Log
- Enh #4636: Added `yii\web\Response::setDownloadHeaders()` (pawzar) - Enh #4636: Added `yii\web\Response::setDownloadHeaders()` (pawzar)
- Enh #4644: Added `yii\db\Schema::createColumnSchema()` to be able to customize column schema used (mcd-php) - Enh #4644: Added `yii\db\Schema::createColumnSchema()` to be able to customize column schema used (mcd-php)
- Enh #4656: HtmlPurifier helper config can now be a closure to change the purifier config object after it was created (Alex-Code) - Enh #4656: HtmlPurifier helper config can now be a closure to change the purifier config object after it was created (Alex-Code)
- Enh #4062: Added 'caseSensitive' option to `yii\helpers\BaseFileHelper::findFiles()` (klimov-paul)
- Enh #4691: Encoding on `ActiveForm` and `ActiveField` validation errors is now configurable (Alex-Code) - Enh #4691: Encoding on `ActiveForm` and `ActiveField` validation errors is now configurable (Alex-Code)
- Enh #4740: Added `yii\web\Session::addFlash()` (restyler) - Enh #4740: Added `yii\web\Session::addFlash()` (restyler)
- Enh: Added support for using sub-queries when building a DB query with `IN` condition (qiangxue) - Enh: Added support for using sub-queries when building a DB query with `IN` condition (qiangxue)
......
...@@ -26,6 +26,7 @@ class BaseFileHelper ...@@ -26,6 +26,7 @@ class BaseFileHelper
const PATTERN_ENDSWITH = 4; const PATTERN_ENDSWITH = 4;
const PATTERN_MUSTBEDIR = 8; const PATTERN_MUSTBEDIR = 8;
const PATTERN_NEGATIVE = 16; const PATTERN_NEGATIVE = 16;
const PATTERN_CASE_INSENSITIVE = 32;
/** /**
...@@ -227,6 +228,7 @@ class BaseFileHelper ...@@ -227,6 +228,7 @@ class BaseFileHelper
* apply to file paths only. For example, '/a/b' matches all file paths ending with '/a/b'; * apply to file paths only. For example, '/a/b' matches all file paths ending with '/a/b';
* and '.svn/' matches directory paths ending with '.svn'. Note, the '/' characters in a pattern matches * and '.svn/' matches directory paths ending with '.svn'. Note, the '/' characters in a pattern matches
* both '/' and '\' in the paths. * both '/' and '\' in the paths.
* - caseSensitive: boolean, whether patterns specified at "only" or "except" should be case sensitive. Defaults to true.
* - recursive: boolean, whether the files under the subdirectories should also be copied. Defaults to true. * - recursive: boolean, whether the files under the subdirectories should also be copied. Defaults to true.
* - beforeCopy: callback, a PHP callback that is called before copying each sub-directory or file. * - beforeCopy: callback, a PHP callback that is called before copying each sub-directory or file.
* If the callback returns false, the copy operation for the sub-directory or file will be cancelled. * If the callback returns false, the copy operation for the sub-directory or file will be cancelled.
...@@ -248,7 +250,9 @@ class BaseFileHelper ...@@ -248,7 +250,9 @@ class BaseFileHelper
throw new InvalidParamException('Unable to open directory: ' . $src); throw new InvalidParamException('Unable to open directory: ' . $src);
} }
if (!isset($options['basePath'])) { if (!isset($options['basePath'])) {
// this should be done only once
$options['basePath'] = realpath($src); $options['basePath'] = realpath($src);
$options = self::normalizeOptions($options);
} }
while (($file = readdir($handle)) !== false) { while (($file = readdir($handle)) !== false) {
if ($file === '.' || $file === '..') { if ($file === '.' || $file === '..') {
...@@ -342,6 +346,7 @@ class BaseFileHelper ...@@ -342,6 +346,7 @@ class BaseFileHelper
* - only: array, list of patterns that the file paths should match if they are to be returned. Directory paths are not checked against them. * - only: array, list of patterns that the file paths should match if they are to be returned. Directory paths are not checked against them.
* Same pattern matching rules as in the "except" option are used. * Same pattern matching rules as in the "except" option are used.
* If a file path matches a pattern in both "only" and "except", it will NOT be returned. * If a file path matches a pattern in both "only" and "except", it will NOT be returned.
* - caseSensitive: boolean, whether patterns specified at "only" or "except" should be case sensitive. Defaults to true.
* - recursive: boolean, whether the files under the subdirectories should also be looked for. Defaults to true. * - recursive: boolean, whether the files under the subdirectories should also be looked for. Defaults to true.
* @return array files found under the directory. The file list is sorted. * @return array files found under the directory. The file list is sorted.
* @throws InvalidParamException if the dir is invalid. * @throws InvalidParamException if the dir is invalid.
...@@ -353,22 +358,9 @@ class BaseFileHelper ...@@ -353,22 +358,9 @@ class BaseFileHelper
} }
$dir = rtrim($dir, DIRECTORY_SEPARATOR); $dir = rtrim($dir, DIRECTORY_SEPARATOR);
if (!isset($options['basePath'])) { if (!isset($options['basePath'])) {
// this should be done only once
$options['basePath'] = realpath($dir); $options['basePath'] = realpath($dir);
// this should also be done only once $options = self::normalizeOptions($options);
if (isset($options['except'])) {
foreach ($options['except'] as $key => $value) {
if (is_string($value)) {
$options['except'][$key] = self::parseExcludePattern($value);
}
}
}
if (isset($options['only'])) {
foreach ($options['only'] as $key => $value) {
if (is_string($value)) {
$options['only'][$key] = self::parseExcludePattern($value);
}
}
}
} }
$list = []; $list = [];
$handle = opendir($dir); $handle = opendir($dir);
...@@ -485,7 +477,12 @@ class BaseFileHelper ...@@ -485,7 +477,12 @@ class BaseFileHelper
} }
} }
return fnmatch($pattern, $baseName, 0); $fnmatchFlags = 0;
if ($flags & self::PATTERN_CASE_INSENSITIVE) {
$fnmatchFlags |= FNM_CASEFOLD;
}
return fnmatch($pattern, $baseName, $fnmatchFlags);
} }
/** /**
...@@ -534,7 +531,12 @@ class BaseFileHelper ...@@ -534,7 +531,12 @@ class BaseFileHelper
} }
} }
return fnmatch($pattern, $name, FNM_PATHNAME); $fnmatchFlags = FNM_PATHNAME;
if ($flags & self::PATTERN_CASE_INSENSITIVE) {
$fnmatchFlags |= FNM_CASEFOLD;
}
return fnmatch($pattern, $name, $fnmatchFlags);
} }
/** /**
...@@ -555,7 +557,7 @@ class BaseFileHelper ...@@ -555,7 +557,7 @@ class BaseFileHelper
{ {
foreach (array_reverse($excludes) as $exclude) { foreach (array_reverse($excludes) as $exclude) {
if (is_string($exclude)) { if (is_string($exclude)) {
$exclude = self::parseExcludePattern($exclude); $exclude = self::parseExcludePattern($exclude, false);
} }
if (!isset($exclude['pattern']) || !isset($exclude['flags']) || !isset($exclude['firstWildcard'])) { if (!isset($exclude['pattern']) || !isset($exclude['flags']) || !isset($exclude['firstWildcard'])) {
throw new InvalidParamException('If exclude/include pattern is an array it must contain the pattern, flags and firstWildcard keys.'); throw new InvalidParamException('If exclude/include pattern is an array it must contain the pattern, flags and firstWildcard keys.');
...@@ -582,19 +584,26 @@ class BaseFileHelper ...@@ -582,19 +584,26 @@ class BaseFileHelper
/** /**
* Processes the pattern, stripping special characters like / and ! from the beginning and settings flags instead. * Processes the pattern, stripping special characters like / and ! from the beginning and settings flags instead.
* @param string $pattern * @param string $pattern
* @param boolean $caseSensitive
* @throws \yii\base\InvalidParamException
* @return array with keys: (string) pattern, (int) flags, (int|boolean)firstWildcard * @return array with keys: (string) pattern, (int) flags, (int|boolean)firstWildcard
* @throws InvalidParamException if the pattern is not a string.
*/ */
private static function parseExcludePattern($pattern) private static function parseExcludePattern($pattern, $caseSensitive)
{ {
if (!is_string($pattern)) { if (!is_string($pattern)) {
throw new InvalidParamException('Exclude/include pattern must be a string.'); throw new InvalidParamException('Exclude/include pattern must be a string.');
} }
$result = [ $result = [
'pattern' => $pattern, 'pattern' => $pattern,
'flags' => 0, 'flags' => 0,
'firstWildcard' => false, 'firstWildcard' => false,
]; ];
if (!$caseSensitive) {
$result['flags'] |= self::PATTERN_CASE_INSENSITIVE;
}
if (!isset($pattern[0])) { if (!isset($pattern[0])) {
return $result; return $result;
} }
...@@ -635,4 +644,30 @@ class BaseFileHelper ...@@ -635,4 +644,30 @@ class BaseFileHelper
return array_reduce($wildcards, $wildcardSearch, false); return array_reduce($wildcards, $wildcardSearch, false);
} }
/**
* @param array $options raw options
* @return array normalized options
*/
private static function normalizeOptions(array $options)
{
if (!array_key_exists('caseSensitive', $options)) {
$options['caseSensitive'] = true;
}
if (isset($options['except'])) {
foreach ($options['except'] as $key => $value) {
if (is_string($value)) {
$options['except'][$key] = self::parseExcludePattern($value, $options['caseSensitive']);
}
}
}
if (isset($options['only'])) {
foreach ($options['only'] as $key => $value) {
if (is_string($value)) {
$options['only'][$key] = self::parseExcludePattern($value, $options['caseSensitive']);
}
}
}
return $options;
}
} }
...@@ -407,6 +407,36 @@ class FileHelperTest extends TestCase ...@@ -407,6 +407,36 @@ class FileHelperTest extends TestCase
$this->assertEquals($expect, $foundFiles); $this->assertEquals($expect, $foundFiles);
} }
/**
* @depends testFindFilesExclude
*/
public function testFindFilesCaseSensitive()
{
$dirName = 'test_dir';
$this->createFileStructure([
$dirName => [
'lower.txt' => 'lower case filename',
'upper.TXT' => 'upper case filename',
],
]);
$basePath = $this->testFilePath;
$dirName = $basePath . DIRECTORY_SEPARATOR . $dirName;
$options = [
'except' => ['*.txt'],
'caseSensitive' => false
];
$foundFiles = FileHelper::findFiles($dirName, $options);
$this->assertCount(0, $foundFiles);
$options = [
'only' => ['*.txt'],
'caseSensitive' => false
];
$foundFiles = FileHelper::findFiles($dirName, $options);
$this->assertCount(2, $foundFiles);
}
public function testCreateDirectory() public function testCreateDirectory()
{ {
$basePath = $this->testFilePath; $basePath = $this->testFilePath;
......
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