yf_dir class

Filesystem utils.

Benchmark results of the methods, from SLOW to FASTEST:
1) _class("dir")->scan() | time: 0.46 | mem: 175824 | peakmem: 4467496 | found: 8
2) _class("dir")->riterate() | time: 0.306 | mem: 2288 | peakmem: 4489568 | found: 8
3) _class("dir")->scan_fast() | time: 0.176 | mem: 2352 | peakmem: 4489568 | found: 8
4) _class("dir")->rglob() | time: 0.066 | mem: 2256 | peakmem: 4489568 | found: 8
5) _class("dir")->find() | time: 0.058 | mem: 2232 | peakmem: 4489568 | found: 8 = fastest so far

Fileclasses/yf_dir.class.php
Use_class('dir')

Public properties

A site sets them in its config with $PROJECT_CONF['dir']['NAME'] = ...;

PropertyDefaultDescription
$CHECK_IF_READABLE
bool
true
$CHECK_IF_WRITABLE
bool
true

Public methods 30

chmod

public function chmod($start_dir, $new_mode = 493, $pattern_include = '', $pattern_exclude = '')

Alias.

    public function chmod($start_dir, $new_mode = 0755, $pattern_include = '', $pattern_exclude = '')
    {
        return $this->chmod_dir($start_dir, $new_mode, $pattern_include, $pattern_exclude);
    }

chmod_dir

public function chmod_dir($start_dir, $new_mode = 493, $pattern_include = '', $pattern_exclude = '')

Recursively chmod directory structure (including subdirectories).

    public function chmod_dir($start_dir, $new_mode = 0755, $pattern_include = '', $pattern_exclude = '')
    {
        if ( ! $start_dir || ! file_exists($start_dir) || empty($new_mode)) {
            return false;
        }
        $func = __FUNCTION__;
        $start_dir = rtrim($start_dir, '/');

        $dh = opendir($start_dir);
        while (false !== ($f = readdir($dh))) {
            if ($f == '.' || $f == '..') {
                continue;
            }
            $item = $start_dir . '/' . $f;
            $_is_dir = is_dir($item);
            // Check patterns
            if ($this->_skip_by_pattern($item, $_is_dir, $pattern_include, $pattern_exclude)) {
                continue;
            }
            chmod($item, $new_mode);
            if ($_is_dir) {
                $this->$func($item, $new_mode);
            }
        }
    }

copy

public function copy($path1, $path2, $pattern_include = '', $pattern_exclude = '', $level = null)

Alias.

    public function copy($path1, $path2, $pattern_include = '', $pattern_exclude = '', $level = null)
    {
        return $this->copy_dir($path1, $path2, $pattern_include, $pattern_exclude, $level);
    }

copy_dir

public function copy_dir($path1, $path2, $pattern_include = '', $pattern_exclude = '', $level = null)

This function recursively copies contents of source directory to destination.

    public function copy_dir($path1, $path2, $pattern_include = '', $pattern_exclude = '', $level = null)
    {
        if ( ! $path1 || ! file_exists($path1)) {
            return false;
        }
        $func = __FUNCTION__;
        $path1 = rtrim(str_replace('\\', '/', realpath($path1)), '/');
        $path2 = rtrim(str_replace('\\', '/', realpath($path2)), '/');

        $dh = opendir($path1);
        $old_mask = umask(0);
        if ( ! file_exists($path2)) {
            $this->mkdir_m($path2);
        }
        while (false !== ($f = readdir($dh))) {
            if ($f == '.' || $f == '..') {
                continue;
            }
            $item_1 = $path1 . '/' . $f;
            $item_2 = $path2 . '/' . $f;
            $_is_dir = is_dir($item_1);
            // Check patterns
            if ($this->_skip_by_pattern($item_1, $_is_dir, $pattern_include, $pattern_exclude)) {
                continue;
            }
            if ($_is_dir) {
                if ( ! file_exists($item_2)) {
                    $this->mkdir_m($item_2);
                }
                if ($level === null || $level > 0) {
                    $this->$func($item_1, $item_2, $pattern_include, $pattern_exclude, $level === null ? $level : $level - 1);
                }
            } else {
                $this->_copy_file($item_1, $item_2);
            }
        }
        umask($old_mask);
    }

count_files

public function count_files($start_dir, $pattern_include = '', $pattern_exclude = '')

This function calculate number of files by mask inside given directory.

    public function count_files($start_dir, $pattern_include = '', $pattern_exclude = '')
    {
        if ( ! $start_dir || ! file_exists($start_dir)) {
            return false;
        }
        $start_dir = rtrim($start_dir, '/');

        $dh = opendir($start_dir);
        $num_files = 0;
        while (($f = readdir($dh)) !== false) {
            if ($f == '.' || $f == '..') {
                continue;
            }
            $path = $start_dir . '/' . $f;
            $_is_dir = is_dir($path);
            // Check patterns
            if ($this->_skip_by_pattern($path, $_is_dir, $pattern_include, $pattern_exclude)) {
                continue;
            }
            if ($_is_dir) {
                $num_files += $this->count_files($path . '/', $pattern_include, $pattern_exclude);
            } elseif (is_file($path)) {
                $num_files++;
            }
        }
        closedir($dh);
        return $num_files;
    }

delete

public function delete($start_dir, $delete_start_dir = false)

Alias.

    public function delete($start_dir, $delete_start_dir = false)
    {
        return $this->delete_dir($start_dir, $delete_start_dir);
    }

delete_dir

public function delete_dir($start_dir, $delete_start_dir = false)

Recursively delete directory structure (including subdirectories).

    public function delete_dir($start_dir, $delete_start_dir = false)
    {
        if ( ! $start_dir || ! file_exists($start_dir)) {
            return false;
        }
        $func = __FUNCTION__;
        $start_dir = rtrim($start_dir, '/');
        $sub_dirs_list = [];
        // Process folder contents
        $sub_dirs_list = [];
        $dh = opendir($start_dir);
        while (false !== ($f = readdir($dh))) {
            if ($f == '.' || $f == '..') {
                continue;
            }
            $item = str_replace('//', '/', $start_dir . '/' . $f);
            chmod($item, 0777);
            // Delete files immediatelly
            if (is_file($item)) {
                unlink($item);
                // Store folders to delete in stack and try to delete sub items
            } elseif (is_dir($item)) {
                $this->$func($item);
                $sub_dirs_list[] = $item;
            }
        }
        closedir($dh);
        // Now try to delete sub folders
        foreach ((array) $sub_dirs_list as $dir_name) {
            rmdir($dir_name);
        }
        // Do delete start dir if needed
        if ($delete_start_dir) {
            rmdir($start_dir);
        }
    }

delete_files

public function delete_files($start_dir, $pattern_include = '', $pattern_exclude = '')

Delete files in specified dir recursively using patterns.

    public function delete_files($start_dir, $pattern_include = '', $pattern_exclude = '')
    {
        foreach ((array) $this->scan_dir($start_dir, 1, $pattern_include, $pattern_exclude) as $file_path) {
            unlink($file_path);
        }
    }

dirsize

public function dirsize($start_dir, $pattern_include = '', $pattern_exclude = '')

This function calculate directory size.

    public function dirsize($start_dir, $pattern_include = '', $pattern_exclude = '')
    {
        if ( ! $start_dir || ! file_exists($start_dir)) {
            return false;
        }
        $start_dir = rtrim($start_dir, '/');

        $dh = opendir($start_dir);
        $size = 0;
        while (($f = readdir($dh)) !== false) {
            if ($f == '.' || $f == '..') {
                continue;
            }
            $path = $start_dir . '/' . $f;
            $_is_dir = is_dir($path);
            // Check patterns
            if ($this->_skip_by_pattern($path, $_is_dir, $pattern_include, $pattern_exclude)) {
                continue;
            }
            if ($_is_dir) {
                $size += $this->dirsize($path . '/', $pattern_include, $pattern_exclude);
            } elseif (is_file($path)) {
                $size += filesize($path);
            }
        }
        closedir($dh);
        return $size;
    }

find

public function find($folder, $pattern = '*')

Scan dir using shell find = so far, fastest method.

    public function find($folder, $pattern = '*')
    {
        return explode("\n", trim(shell_exec('find -L ' . escapeshellarg($folder) . ' -iname ' . escapeshellarg($pattern))));
    }

grep

public function grep($pattern_find, $start_dirs, $pattern_path = '*', $extra = [])
    public function grep($pattern_find, $start_dirs, $pattern_path = '*', $extra = [])
    {
        if ( ! $pattern_find) {
            return false;
        }
        if ( ! $start_dirs) {
            $start_dirs = APP_PATH;
        }
        if ( ! is_array($start_dirs)) {
            $start_dirs = [$start_dirs];
        }
        if ( ! is_array($pattern_find)) {
            $pattern_find = [$pattern_find];
        }
        $files = [];
        foreach ((array) $start_dirs as $start_dir) {
            $start_dir = rtrim($start_dir, '/');
            foreach ((array) $this->rglob($start_dir, $pattern_path) as $path) {
                $files[] = $path;
            }
        }
        $matched = [];
        foreach ((array) $files as $_id => $path) {
            if (isset($extra['exclude_paths']) && wildcard_compare($extra['exclude_paths'], $path)) {
                continue;
            }
            $contents = file_get_contents($path);
            foreach ((array) $pattern_find as $p_find) {
                if (preg_match_all($p_find, $contents, $m)) {
                    $matched[$files[$_id]] = $extra['return_match'] && isset($m[$extra['return_match']]) ? $m[$extra['return_match']] : $m[0];
                    continue;
                }
            }
        }
        return $matched;
    }

mkdir

public function mkdir($dir_name, $dir_mode = 493, $create_index_htmls = 0, $start_folder = '')

Alias.

    public function mkdir($dir_name, $dir_mode = 0755, $create_index_htmls = 0, $start_folder = '')
    {
        return $this->mkdir_m($dir_name, $dir_mode, $create_index_htmls, $start_folder);
    }

mkdir_m

public function mkdir_m($dir_name, $dir_mode = 493, $create_index_htmls = 0, $start_folder = '')

Create multiple dirs at one time (eg. mkdir_m('some_dir1/some_dir2/some_dir3')).

  • $dir_name string
  • $dir_mode octal
  • $create_index_htmls bool
  • $start_folder string

Returns int Status code

    public function mkdir_m($dir_name, $dir_mode = 0755, $create_index_htmls = 0, $start_folder = '')
    {
        if ( ! $dir_name || ! strlen($dir_name)) {
            return 0;
        }
        $dir_name = rtrim(str_replace(['\\', '//'], '/', $dir_name), '/');
        // Default dir mode
        if (empty($dir_mode)) {
            $dir_mode = 0777;
        }
        // Use native recursive function if applicable
        if ( ! $create_index_htmls) {
            if (file_exists($dir_name)) {
                return true;
            }
            return mkdir($dir_name, $dir_mode, true);
        }
        $old_mask = umask(0);
        // Default start folder to look at
        if ( ! strlen($start_folder)) {
            $start_folder = INCLUDE_PATH;
        }
        $start_folder = str_replace(['\\', '//'], '/', realpath($start_folder) . '/');
        // Process given file name
        if ( ! file_exists($dir_name)) {
            $base_path = OS_WINDOWS ? '' : '/';
            preg_match_all('/([^\/]+)\/?/i', $dir_name, $atmp);
            foreach ((array) $atmp[0] as $val) {
                $base_path = $base_path . $val;
                // Skip paths while we are out of base_folder
                if ( ! empty($start_folder) && false === strpos($base_path, $start_folder)) {
                    continue;
                }
                // Skip if already exists
                if (file_exists($base_path)) {
                    continue;
                } elseif ($this->CHECK_IF_WRITABLE && ! is_writable(dirname($base_path))) {
                    trigger_error('DIR: directory: ' . dirname($base_path) . ' is not writable', E_USER_WARNING);
                }
                // Try to create sub dir
                if ( ! mkdir($base_path, $dir_mode)) {
                    trigger_error('DIR: Cannot create: ' . $base_path, E_USER_WARNING);
                    return -1;
                }
                chmod($base_path, $dir_mode);
            }
        } elseif ( ! is_dir($dir_name)) {
            trigger_error('DIR: ' . $dir_name . ' exists and is not a directory', E_USER_WARNING);
            return -2;
        }
        // Create empty index.html in new folder if needed
        if ($create_index_htmls) {
            $index_file_path = $dir_name . '/index.html';
            if ( ! file_exists($index_file_path)) {
                file_put_contents($index_file_path, '');
            }
        }
        umask($old_mask);
        return 0;
    }

move

public function move($path1, $path2, $pattern_include = '', $pattern_exclude = '')

Alias.

    public function move($path1, $path2, $pattern_include = '', $pattern_exclude = '')
    {
        return $this->move_dir($path1, $path2, $pattern_include, $pattern_exclude);
    }

move_dir

public function move_dir($path1, $path2, $pattern_include = '', $pattern_exclude = '')

This function recursively move contents of source directory to destination.

    public function move_dir($path1, $path2, $pattern_include = '', $pattern_exclude = '')
    {
        if ( ! $path1 || ! file_exists($path1)) {
            return false;
        }
        $func = __FUNCTION__;
        $path1 = rtrim($path1, '/');
        $path2 = rtrim($path2, '/');

        $dh = opendir($path1);
        if ( ! file_exists($path2)) {
            mkdir($path2, 0777);
        }
        while (false !== ($f = readdir($dh))) {
            if ($f == '.' || $f == '..') {
                continue;
            }
            $item_1 = $path1 . '/' . $f;
            $item_2 = $path2 . '/' . $f;
            $_is_dir = is_dir($item_1);
            // Check patterns
            if ($this->_skip_by_pattern($item_1, $_is_dir, $pattern_include, $pattern_exclude)) {
                continue;
            }
            if ($_is_dir) {
                if ( ! file_exists($item_2)) {
                    mkdir($item_2, 0777);
                }
                $this->$func($item_1, $item_2, $pattern_include, $pattern_exclude);
            } else {
                $this->_copy_file($item_1, $item_2);
                unlink($item_1);
            }
        }
        rmdir($path1);
    }

replace

public function replace($start_dirs, $pattern_include = '', $pattern_exclude = '', $pattern_find = '', $pattern_replace = '')

This function searches given folder(folders) for provided text using paths include/exclude patterns and replaces by pattern.

WARNING! Be careful here, it really overwrites files matches $pattern_replace (if not null)
test first with $this-search() method

Returns array of processed files

    public function replace($start_dirs, $pattern_include = '', $pattern_exclude = '', $pattern_find = '', $pattern_replace = '')
    {
        $files = [];
        if ( ! is_array($start_dirs)) {
            $start_dirs = [$start_dirs];
        }
        if ( ! $pattern_find || ! isset($pattern_replace)) {
            return false;
        }
        if ( ! is_array($pattern_find)) {
            $pattern_find = [$pattern_find => $pattern_replace];
        }
        foreach ((array) $start_dirs as $_dir_name) {
            foreach ((array) $this->scan_dir($_dir_name, 1, $pattern_include, $pattern_exclude) as $_file_path) {
                $files[] = $_file_path;
            }
        }
        foreach ((array) $files as $_id => $_file_path) {
            $contents = file_get_contents($_file_path);
            $what = [];
            foreach ((array) $pattern_find as $p_find => $p_replace) {
                if (preg_match($p_find, $contents)) {
                    $what[$p_find] = $p_replace;
                }
            }
            // This needed to not log/touch/override files that have no matches
            if ( ! $what) {
                unset($files[$_id]);
                continue;
            }
            $contents_new = preg_replace(array_keys($what), array_values($what), $contents);
            if ($contents_new !== $contents) {
                file_put_contents($_file_path, $contents_new);
            }
        }
        return $files;
    }

rglob

public function rglob($folder, $pattern = '*')

Recursive glob(). Note that glob and rglob does not search hidden files (starting from dot on linux/unix).

    public function rglob($folder, $pattern = '*')
    {
        $folder = rtrim($folder, '/');
        if (false === strpos($pattern, '[')) {
            $pattern = $this->_pattern_regcase($pattern);
        }

        $files = (array) glob($folder . '/' . $pattern, GLOB_NOSORT);
        $dirs = array_merge(
            (array) glob($folder . '/*', GLOB_ONLYDIR | GLOB_NOSORT),
            array_filter((array) glob($folder . '/.*', GLOB_ONLYDIR | GLOB_NOSORT), function ($path) {
                $d = basename($path);
                return $d !== '.' && $d !== '..' && $d !== '.git' && $d !== '.svn';
            })
        );

        foreach ($dirs as $dir) {
            $files = array_merge($files, $this->rglob($dir, $pattern));
        }

        return $files;
    }

riterate

public function riterate($folder, $pattern = '~.+~')

Recursive folder search, based on RecursiveDirectoryIterator.

    public function riterate($folder, $pattern = '~.+~')
    {
        $out = [];
        $flags = FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::FOLLOW_SYMLINKS;
        foreach (new RegexIterator(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($folder, $flags)), $pattern, RegexIterator::GET_MATCH) as $path => $f) {
            $out[] = $path;
        }
        return $out;
    }

scan

public function scan($start_dir, $_tmp = true, $pattern_include = '', $pattern_exclude = '')

Alias.

    public function scan($start_dir, $_tmp = true, $pattern_include = '', $pattern_exclude = '')
    {
        return $this->scan_dir($start_dir, $_tmp, $pattern_include, $pattern_exclude);
    }

scan_dir

public function scan_dir($start_dir, $_tmp = 1, $pattern_include = '', $pattern_exclude = '')

Recursively scanning directory structure (including subdirectories) //.

    public function scan_dir($start_dir, $_tmp = 1, $pattern_include = '', $pattern_exclude = '')
    {
        $func = __FUNCTION__;
        // Here we accept several start folders, result will be merged
        if (is_array($start_dir)) {
            $_files = null;
            foreach ((array) $start_dir as $_dir_name) {
                foreach ((array) $this->$func($_dir_name, 1, $pattern_include, $pattern_exclude) as $_file_path) {
                    $_files[] = $_file_path;
                }
            }
            return $_files;
        }
        if ( ! $start_dir || ! file_exists($start_dir)) {
            return false;
        }
        $start_dir = rtrim($start_dir, '/');
        $files = [];
        $dh = opendir($start_dir);
        while (false !== ($f = readdir($dh))) {
            if ($f == '.' || $f == '..') {
                continue;
            }
            $item = $start_dir . '/' . $f;
            $_is_dir = is_dir($item);
            if ($this->_skip_by_pattern($item, $_is_dir, $pattern_include, $pattern_exclude)) {
                continue;
            }
            if ($_is_dir) {
                $files = array_merge($files, $this->$func($item, 1, $pattern_include, $pattern_exclude));
            } elseif (is_file($item)) {
                $files[] = $item;
            }
        }
        closedir($dh);
        return $files;
    }

scan_fast

public function scan_fast($start_dir, $pattern = '~.+~')

Fast implementation with old functions opendir/readdir.

    public function scan_fast($start_dir, $pattern = '~.+~')
    {
        $files = [];
        $dh = @opendir($start_dir);
        if ( ! $dh) {
            return $files;
        }
        $func = __FUNCTION__;
        while (false !== ($f = readdir($dh))) {
            if ($f === '.' || $f === '..') {
                continue;
            }
            $item = $start_dir . '/' . $f;
            if (is_dir($item)) {
                $files = array_merge($files, $this->$func($item, $pattern));
            } elseif (is_file($item) && ( ! $pattern || preg_match($pattern, $item))) {
                $files[] = $item;
            }
        }
        closedir($dh);
        return $files;
    }

size

public function size($start_dir, $pattern_include = '', $pattern_exclude = '')

Alias.

    public function size($start_dir, $pattern_include = '', $pattern_exclude = '')
    {
        return $this->dirsize($start_dir, $pattern_include, $pattern_exclude);
    }

tail

public function tail($file, $lines = 10)

Implementation of the UNIX 'tail' command on pure PHP, memory safe on huge files.

    public function tail($file, $lines = 10)
    {
        if ( ! $file || ! file_exists($file)) {
            return false;
        }
        $handle = fopen($file, 'r');
        $linecounter = $lines;
        $pos = -2;
        $beginning = false;
        $text = [];
        while ($linecounter > 0) {
            $t = ' ';
            while ($t != "\n") {
                if (fseek($handle, $pos, SEEK_END) == -1) {
                    $beginning = true;
                    break;
                }
                $t = fgetc($handle);
                $pos--;
            }
            $linecounter--;
            if ($beginning) {
                rewind($handle);
            }
            $text[$lines - $linecounter - 1] = fgets($handle);
            if ($beginning) {
                break;
            }
        }
        fclose($handle);
        return array_reverse($text);
    }

Methods starting with _ hooks and helpers; not routable as page actions

__call

public function __call($name, $args)

Catch missing method call.

    public function __call($name, $args)
    {
        return main()->extend_call($this, $name, $args);
    }

_copy_file

public function _copy_file($path_from = '', $path_to = '')

Try to copy file.

    public function _copy_file($path_from = '', $path_to = '')
    {
        if ( ! $path_from || ! $path_to) {
            return false;
        }
        $result = false;
        if ( ! file_exists($path_from)) {
            return false;
        }
        // Quick way
        $result = copy($path_from, $path_to);
        if ( ! $result) {
            $result = (bool) file_put_contents($path_to, file_get_contents($path_from));
        }
        return $result;
    }

_gen_dir_path

public function _gen_dir_path($id, $path = '', $make = false, $dir_mode = 493, $create_index_htmls = 1)

generate user upload path and if need make generated dirs.

  • $id user id
  • $path path to main dir
  • $make bool if create directories need
  • $dir_mode mode of the new dirs (octal)
  • $create_index_htmls bool Create index.html's in every new folder or not

Returns user uploads path

    public function _gen_dir_path($id, $path = '', $make = false, $dir_mode = 0755, $create_index_htmls = 1)
    {
        // Make 3-level dir path
        $dirs = sprintf('%09s', $id);
        $dir3 = substr($dirs, -3, 3);
        $dir2 = substr($dirs, -6, 3);
        $dir1 = substr($dirs, 0, -6);
        // 3-level path
        $mpath = $dir1 . '/' . $dir2 . '/' . $dir3 . '/';
        // Add path prefix to string
        if (strlen($path) > 0) {
            // if last char in $path not '\' or '/' add '/'
            if ((substr($path, -1, 1) !== '/') && (substr($path, -1, 1) !== '\\')) {
                $path .= '/';
            }
            $mpath = $path . $mpath;
        }
        // Do create subdirs (if needed)
        if ($make) {
            $this->mkdir_m($mpath, $dir_mode, $create_index_htmls);
        }
        return $mpath;
    }
public function _pattern_regcase($str)

Check if file or folder should be skipped by include/exclude patterns.

    public function _pattern_regcase($str)
    {
        $res = '';
        $chars = str_split($str);
        foreach ($chars as $char) {
            if (preg_match('/[A-Za-z]/', $char)) {
                $res .= '[' . mb_strtoupper($char, 'UTF-8') . mb_strtolower($char, 'UTF-8') . ']';
            } else {
                $res .= $char;
            }
        }
        return $res;
    }
public function _skip_by_pattern($path = '', $_is_dir = false, $pattern_include = '', $pattern_exclude = '')

Check if we need to skip current path according to given patterns (unified method for whole dir module).

    public function _skip_by_pattern($path = '', $_is_dir = false, $pattern_include = '', $pattern_exclude = '')
    {
        if ( ! $path) {
            return false;
        }
        if ( ! $pattern_include && ! $pattern_exclude) {
            return false;
        }
        $_path_clean = trim(str_replace('//', '/', str_replace('\\', '/', $path)));
        // Include files only if they match the mask
        $_index = $_is_dir ? 0 : 1;
        if ($_is_dir) {
            $_path_clean = rtrim($_path_clean, '/');
        }
        if (is_array($pattern_include)) {
            $pattern_include = $pattern_include[$_index];
        }
        if (is_array($pattern_exclude)) {
            $pattern_exclude = $pattern_exclude[$_index];
        }
        $modifier = '';
        $MATCHED = false;
        if ( ! empty($pattern_include) && is_string($pattern_include)) {
            // Examples: "-f /\.(jpg|png)$/", -d /some_dir/
            $try_modifier = substr($pattern_include, 0, 3);
            if (in_array($try_modifier, ['-f ', '-d '])) {
                $pattern_include = substr($pattern_include, 3);
                $modifier = $try_modifier;
            }
            if (strlen($pattern_include) == 2 && $pattern_include[0] == '-') {
                if ($pattern_include == '-d' && ! $_is_dir) {
                    $MATCHED = true;
                } elseif ($pattern_include == '-f' && $_is_dir) {
                    $MATCHED = true;
                }
            } else {
                $need_match = true;
                if ($modifier == '-f ' && $_is_dir) {
                    $need_match = false;
                } elseif ($modifier == '-d ' && ! $_is_dir) {
                    $need_match = false;
                }
                if ($need_match && ! preg_match($pattern_include . 'ims', $_path_clean)) {
                    $MATCHED = true;
                }
            }
        }
        // Exclude files from list by mask
        if ( ! empty($pattern_exclude) && is_string($pattern_exclude)) {
            // Examples: "-f /\.(jpg|png)$/", -d /some_dir/
            $try_modifier = substr($pattern_include, 0, 3);
            if (in_array($try_modifier, ['-f ', '-d '])) {
                $pattern_include = substr($pattern_include, 3);
                $modifier = $try_modifier;
            }
            if (strlen($pattern_exclude) == 2 && $pattern_exclude[0] == '-') {
                if ($pattern_exclude == '-d' && $_is_dir) {
                    $MATCHED = true;
                } elseif ($pattern_exclude == '-f' && ! $_is_dir) {
                    $MATCHED = true;
                }
            } else {
                $need_match = true;
                if ($modifier == '-f ' && $_is_dir) {
                    $need_match = false;
                } elseif ($modifier == '-d ' && ! $_is_dir) {
                    $need_match = false;
                }
                if ($need_match && preg_match($pattern_exclude . 'ims', $_path_clean)) {
                    $MATCHED = true;
                }
            }
        }
        return $MATCHED;
    }