Passed
Pull Request — 4 (#8209)
by Ingo
09:07
created

ManifestFileFinder::acceptDir()   B

Complexity

Conditions 8
Paths 9

Size

Total Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
nc 9
nop 3
dl 0
loc 34
rs 8.1315
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Core\Manifest;
4
5
use SilverStripe\Assets\FileFinder;
6
7
/**
8
 * An extension to the default file finder with some extra filters to faciliate
9
 * autoload and template manifest generation:
10
 *   - Only modules with _config.php files are scanned.
11
 *   - If a _manifest_exclude file is present inside a directory it is ignored.
12
 *   - Assets and module language directories are ignored.
13
 *   - Module tests directories are skipped if the ignore_tests option is not
14
 *     set to false.
15
 */
16
class ManifestFileFinder extends FileFinder
17
{
18
19
    const CONFIG_FILE = '_config.php';
20
    const CONFIG_DIR = '_config';
21
    const EXCLUDE_FILE = '_manifest_exclude';
22
    const LANG_DIR = 'lang';
23
    const TESTS_DIR = 'tests';
24
    const VENDOR_DIR = 'vendor';
25
    const RESOURCES_DIR = 'resources';
26
27
    protected static $default_options = array(
28
        'include_themes' => false,
29
        'ignore_tests' => true,
30
        'min_depth' => 1,
31
        'ignore_dirs' => ['node_modules']
32
    );
33
34
    public function acceptDir($basename, $pathname, $depth)
35
    {
36
        // Skip if ignored
37
        if ($this->isInsideIgnored($basename, $pathname, $depth)) {
38
            return false;
39
        }
40
41
        // Keep searching inside vendor
42
        $inVendor = $this->isInsideVendor($basename, $pathname, $depth);
43
        if ($inVendor) {
44
            // Keep searching if we could have a subdir module
45
            if ($depth < 3) {
46
                return true;
47
            }
48
49
            // Stop searching if we are in a non-module library
50
            $libraryPath = $this->upLevels($pathname, $depth - 3);
51
            $libraryBase = basename($libraryPath);
52
            if (!$this->isDirectoryModule($libraryBase, $libraryPath, 3)) {
53
                return false;
54
            }
55
        }
56
57
        // Include themes
58
        if ($this->getOption('include_themes') && $this->isInsideThemes($basename, $pathname, $depth)) {
59
            return true;
60
        }
61
62
        // Skip if not in module
63
        if (!$this->isInsideModule($basename, $pathname, $depth)) {
64
            return false;
65
        }
66
67
        return parent::acceptDir($basename, $pathname, $depth);
68
    }
69
70
    /**
71
     * Check if the given dir is, or is inside the vendor folder
72
     *
73
     * @param string $basename
74
     * @param string $pathname
75
     * @param int $depth
76
     * @return bool
77
     */
78
    public function isInsideVendor($basename, $pathname, $depth)
79
    {
80
        $base = basename($this->upLevels($pathname, $depth - 1));
81
        return $base === self::VENDOR_DIR;
82
    }
83
84
    /**
85
     * Check if the given dir is, or is inside the themes folder
86
     *
87
     * @param string $basename
88
     * @param string $pathname
89
     * @param int $depth
90
     * @return bool
91
     */
92
    public function isInsideThemes($basename, $pathname, $depth)
93
    {
94
        $base = basename($this->upLevels($pathname, $depth - 1));
95
        return $base === THEMES_DIR;
96
    }
97
98
    /**
99
     * Check if this folder or any parent is ignored
100
     *
101
     * @param string $basename
102
     * @param string $pathname
103
     * @param int $depth
104
     * @return bool
105
     */
106
    public function isInsideIgnored($basename, $pathname, $depth)
107
    {
108
        return $this->anyParents($basename, $pathname, $depth, function ($basename, $pathname, $depth) {
109
            return $this->isDirectoryIgnored($basename, $pathname, $depth);
110
        });
111
    }
112
113
    /**
114
     * Check if this folder is inside any module
115
     *
116
     * @param string $basename
117
     * @param string $pathname
118
     * @param int $depth
119
     * @return bool
120
     */
121
    public function isInsideModule($basename, $pathname, $depth)
122
    {
123
        return $this->anyParents($basename, $pathname, $depth, function ($basename, $pathname, $depth) {
124
            return $this->isDirectoryModule($basename, $pathname, $depth);
125
        });
126
    }
127
128
    /**
129
     * Check if any parents match the given callback
130
     *
131
     * @param string $basename
132
     * @param string $pathname
133
     * @param int $depth
134
     * @param callable $callback
135
     * @return bool
136
     */
137
    protected function anyParents($basename, $pathname, $depth, $callback)
138
    {
139
        // Check all ignored dir up the path
140
        while ($depth >= 0) {
141
            $ignored = $callback($basename, $pathname, $depth);
142
            if ($ignored) {
143
                return true;
144
            }
145
            $pathname = dirname($pathname);
146
            $basename = basename($pathname);
147
            $depth--;
148
        }
149
        return false;
150
    }
151
152
    /**
153
     * Check if the given dir is a module root (not a subdir)
154
     *
155
     * @param string $basename
156
     * @param string $pathname
157
     * @param string $depth
158
     * @return bool
159
     */
160
    public function isDirectoryModule($basename, $pathname, $depth)
161
    {
162
        // Depth can either be 0, 1, or 3 (if and only if inside vendor)
163
        $inVendor = $this->isInsideVendor($basename, $pathname, $depth);
0 ignored issues
show
Bug introduced by
$depth of type string is incompatible with the type integer expected by parameter $depth of SilverStripe\Core\Manife...inder::isInsideVendor(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

163
        $inVendor = $this->isInsideVendor($basename, $pathname, /** @scrutinizer ignore-type */ $depth);
Loading history...
164
        if ($depth > 0 && $depth !== ($inVendor ? 3 : 1)) {
165
            return false;
166
        }
167
168
        // True if config file exists
169
        if (file_exists($pathname . '/' . self::CONFIG_FILE)) {
170
            return true;
171
        }
172
173
        // True if config dir exists
174
        if (file_exists($pathname . '/' . self::CONFIG_DIR)) {
175
            return true;
176
        }
177
178
        return false;
179
    }
180
181
    /**
182
     * Get a parent path the given levels above
183
     *
184
     * @param string $pathname
185
     * @param int $depth Number of parents to rise
186
     * @return string
187
     */
188
    protected function upLevels($pathname, $depth)
189
    {
190
        if ($depth < 0) {
191
            return null;
192
        }
193
        while ($depth--) {
194
            $pathname = dirname($pathname);
195
        }
196
        return $pathname;
197
    }
198
199
    /**
200
     * Get all ignored directories
201
     *
202
     * @return array
203
     */
204
    protected function getIgnoredDirs()
205
    {
206
        $ignored = [self::LANG_DIR, 'node_modules'];
207
        if ($this->getOption('ignore_tests')) {
208
            $ignored[] = self::TESTS_DIR;
209
        }
210
        return $ignored;
211
    }
212
213
    /**
214
     * Check if the given directory is ignored
215
     * @param string $basename
216
     * @param string $pathname
217
     * @param string $depth
218
     * @return bool
219
     */
220
    public function isDirectoryIgnored($basename, $pathname, $depth)
221
    {
222
        // Don't ignore root
223
        if ($depth === 0) {
0 ignored issues
show
introduced by
The condition $depth === 0 is always false.
Loading history...
224
            return false;
225
        }
226
227
        // Check if manifest-ignored is present
228
        if (file_exists($pathname . '/' . self::EXCLUDE_FILE)) {
229
            return true;
230
        }
231
232
        // Check if directory name is ignored
233
        $ignored = $this->getIgnoredDirs();
234
        if (in_array($basename, $ignored)) {
235
            return true;
236
        }
237
238
        // Ignore these dirs in the root only
239
        if ($depth === 1 && in_array($basename, [ASSETS_DIR, self::RESOURCES_DIR])) {
0 ignored issues
show
introduced by
The condition $depth === 1 is always false.
Loading history...
240
            return true;
241
        }
242
243
        return false;
244
    }
245
}
246