Skip to content

Commit

Permalink
Added filter options to FileHelper::clearDirectory()
Browse files Browse the repository at this point in the history
  • Loading branch information
brandonkelly committed May 1, 2017
1 parent 6d66d5e commit e66b5a2
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG-v3.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Craft CMS 3.0 Working Changelog
- Renamed `craft\helpers\Image::isImageManipulatable()` to `canManipulateAsImage()`.
- Craft now checks if the current installation can manipulate an image instead of checking against a predefined list. ([#1648](https://github.com/craftcms/cms/issues/1648), [#1545](https://github.com/craftcms/cms/issues/1545))
- The old `Craft\DateTime` methods from Craft 2 no longer cause PHP errors when called from a template. A deprecation error will be logged instead.
- `craft\helpers\FileHelper::clearDirectory()` now supports `filter`, `except`, and `only` options.

### Removed
- Removed `craft\base\Field::isValueEmpty()`.
Expand Down
114 changes: 109 additions & 5 deletions src/helpers/FileHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,9 @@ public static function removeFile(string $file)
* - `traverseSymlinks`: bool, 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.
* - `filter`: callback (see [[findFiles()]])
* - `except`: array (see [[findFiles()]])
* - `only`: array (see [[findFiles()]])
*
* @return void
* @throws InvalidParamException if the dir is invalid
Expand All @@ -322,19 +325,27 @@ public static function clearDirectory(string $dir, array $options = [])
throw new InvalidParamException("The dir argument must be a directory: $dir");
}

// Copied from [[removeDirectory()]] minus the root directory removal at the end
// Adapted from [[removeDirectory()]], plus addition of filters, and minus the root directory removal at the end
if (!($handle = opendir($dir))) {
return;
}

if (!isset($options['basePath'])) {
$options['basePath'] = realpath($dir);
$options = self::_normalizeOptions($options);
}

while (($file = readdir($handle)) !== false) {
if ($file === '.' || $file === '..') {
continue;
}
$path = $dir.DIRECTORY_SEPARATOR.$file;
if (is_dir($path)) {
static::removeDirectory($path, $options);
} else {
static::removeFile($path);
if (static::filterPath($path, $options)) {
if (is_dir($path)) {
static::removeDirectory($path, $options);
} else {
static::removeFile($path);
}
}
}
closedir($handle);
Expand Down Expand Up @@ -412,4 +423,97 @@ public static function useFileLocks(): bool

return self::$_useFileLocks;
}

/**
* @param array $options raw options
* @return array normalized options
* @todo Remove if Yii makes the parent method protected (https://github.com/yiisoft/yii2/pull/14098)
*/
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;
}

/**
* Processes the pattern, stripping special characters like / and ! from the beginning and settings flags instead.
* @param string $pattern
* @param bool $caseSensitive
* @throws \yii\base\InvalidParamException
* @return array with keys: (string) pattern, (int) flags, (int|bool) firstWildcard
* @todo Remove if Yii makes the parent method protected (https://github.com/yiisoft/yii2/pull/14098)
*/
private static function _parseExcludePattern($pattern, $caseSensitive)
{
if (!is_string($pattern)) {
throw new InvalidParamException('Exclude/include pattern must be a string.');
}

$result = [
'pattern' => $pattern,
'flags' => 0,
'firstWildcard' => false,
];

if (!$caseSensitive) {
$result['flags'] |= self::PATTERN_CASE_INSENSITIVE;
}

if (!isset($pattern[0])) {
return $result;
}

if ($pattern[0] === '!') {
$result['flags'] |= self::PATTERN_NEGATIVE;
$pattern = StringHelper::byteSubstr($pattern, 1, StringHelper::byteLength($pattern));
}
if (StringHelper::byteLength($pattern) && StringHelper::byteSubstr($pattern, -1, 1) === '/') {
$pattern = StringHelper::byteSubstr($pattern, 0, -1);
$result['flags'] |= self::PATTERN_MUSTBEDIR;
}
if (strpos($pattern, '/') === false) {
$result['flags'] |= self::PATTERN_NODIR;
}
$result['firstWildcard'] = self::_firstWildcardInPattern($pattern);
if ($pattern[0] === '*' && self::_firstWildcardInPattern(StringHelper::byteSubstr($pattern, 1, StringHelper::byteLength($pattern))) === false) {
$result['flags'] |= self::PATTERN_ENDSWITH;
}
$result['pattern'] = $pattern;

return $result;
}

/**
* Searches for the first wildcard character in the pattern.
* @param string $pattern the pattern to search in
* @return int|bool position of first wildcard character or false if not found
* @todo Remove if Yii makes the parent method protected (https://github.com/yiisoft/yii2/pull/14098)
*/
private static function _firstWildcardInPattern($pattern)
{
$wildcards = ['*', '?', '[', '\\'];
$wildcardSearch = function ($r, $c) use ($pattern) {
$p = strpos($pattern, $c);

return $r === false ? $p : ($p === false ? $r : min($r, $p));
};

return array_reduce($wildcards, $wildcardSearch, false);
}
}

0 comments on commit e66b5a2

Please sign in to comment.