1<?php
2declare(strict_types=1);
3
4namespace ILIAS\Filesystem\Finder\Iterator;
5
6use ILIAS\Filesystem\DTO\Metadata;
7
8/**
9 * Class ExcludeDirectoryFilterIterator
10 * @package ILIAS\Filesystem\Finder\Iterator
11 * @author  Michael Jansen <mjansen@databay.de>
12 */
13class ExcludeDirectoryFilterIterator extends \FilterIterator implements \RecursiveIterator
14{
15    /** @var \Iterator|\RecursiveIterator */
16    private $iterator;
17
18    /** @var bool */
19    private $isRecursive = false;
20
21    /** @var string[] */
22    private $excludedDirs = [];
23
24    /** @var string */
25    private $excludedPattern = '';
26
27    /**
28     * @param \Iterator $iterator The Iterator to filter
29     * @param string[] $directories An array of directories to exclude
30     */
31    public function __construct(\Iterator $iterator, array $directories)
32    {
33        array_walk($directories, function ($directory) {
34            if (!is_string($directory)) {
35                if (is_object($directory)) {
36                    throw new \InvalidArgumentException(sprintf('Invalid directory given: %s', get_class($directory)));
37                }
38
39                throw new \InvalidArgumentException(sprintf('Invalid directory given: %s', gettype($directory)));
40            }
41        });
42
43        $this->iterator = $iterator;
44        $this->isRecursive = $iterator instanceof \RecursiveIterator;
45
46        $patterns = [];
47        foreach ($directories as $directory) {
48            $directory = rtrim($directory, '/');
49            if (!$this->isRecursive || false !== strpos($directory, '/')) {
50                $patterns[] = preg_quote($directory, '#');
51            } else {
52                $this->excludedDirs[$directory] = true;
53            }
54        }
55
56        if ($patterns) {
57            $this->excludedPattern = '#(?:^|/)(?:' . implode('|', $patterns) . ')(?:/|$)#';
58        }
59
60        parent::__construct($iterator);
61    }
62
63    /**
64     * @inheritdoc
65     */
66    public function accept()
67    {
68        /** @var Metadata $metadata */
69        $metadata = $this->current();
70
71        if ($this->isRecursive && isset($this->excludedDirs[$metadata->getPath()]) && $metadata->isDir()) {
72            return false;
73        }
74
75        if ($this->excludedPattern) {
76            $path = $metadata->getPath();
77            $path = str_replace('\\', '/', $path);
78
79            return !preg_match($this->excludedPattern, $path);
80        }
81
82        return true;
83    }
84
85    /**
86     * @inheritdoc
87     */
88    public function hasChildren()
89    {
90        return $this->isRecursive && $this->iterator->hasChildren();
91    }
92
93    /**
94     * @inheritdoc
95     */
96    public function getChildren()
97    {
98        $children = new self($this->iterator->getChildren(), []);
99        $children->excludedDirs = $this->excludedDirs;
100        $children->excludedPattern = $this->excludedPattern;
101
102        return $children;
103    }
104}
105