DirectoryScanner   F
last analyzed

Complexity

Total Complexity 100

Size/Duplication

Total Lines 858
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 100
eloc 258
c 0
b 0
f 0
dl 0
loc 858
rs 2

37 Methods

Rating   Name   Duplication   Size   Complexity  
A setErrorOnMissingDir() 0 3 1
A removeDefaultExclude() 0 12 2
A setIncludes() 0 12 4
A __construct() 0 4 2
A getBasedir() 0 3 1
A matchPatternStart() 0 3 1
A resetDefaultExcludes() 0 3 1
A match() 0 3 1
A setCaseSensitive() 0 3 2
A matchPath() 0 3 1
A setExpandSymbolicLinks() 0 3 1
A addDefaultExclude() 0 10 2
A setBasedir() 0 4 1
A setExcludes() 0 12 4
A getDefaultExcludes() 0 3 1
A isExcluded() 0 9 3
A getDeselectedFiles() 0 5 1
D scandir() 0 79 24
A addDefaultExcludes() 0 8 2
A getIncludedDirectories() 0 9 2
A setSelectors() 0 3 1
A slowScan() 0 23 6
A isSelected() 0 17 5
A getIncludedFilesCount() 0 7 2
A getNotIncludedDirectories() 0 5 1
A isIncluded() 0 9 3
A getExcludedFiles() 0 5 1
B scan() 0 51 11
A getIncludedFiles() 0 9 2
A listDir() 0 3 1
A getIncludedDirectoriesCount() 0 7 2
A clearResults() 0 10 1
A getNotIncludedFiles() 0 5 1
A getExcludedDirectories() 0 5 1
A couldHoldIncluded() 0 9 3
A isEverythingIncluded() 0 3 1
A getDeselectedDirectories() 0 5 1

How to fix   Complexity   

Complex Class

Complex classes like DirectoryScanner often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DirectoryScanner, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
5
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
6
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
7
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
8
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
9
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
10
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
11
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
12
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
13
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
14
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
 *
16
 * This software consists of voluntary contributions made by many individuals
17
 * and is licensed under the LGPL. For more information please see
18
 * <http://phing.info>.
19
 */
20
21
namespace Phing\Io;
22
23
use Phing\Exception\BuildException;
24
use Phing\Type\Selector\FileSelector;
25
use Phing\Type\Selector\SelectorScanner;
26
use Phing\Type\Selector\SelectorUtils;
27
use Phing\Util\StringHelper;
28
use UnexpectedValueException;
29
30
/**
31
 * Class for scanning a directory for files/directories that match a certain
32
 * criteria.
33
 *
34
 * These criteria consist of a set of include and exclude patterns. With these
35
 * patterns, you can select which files you want to have included, and which
36
 * files you want to have excluded.
37
 *
38
 * The idea is simple. A given directory is recursively scanned for all files
39
 * and directories. Each file/directory is matched against a set of include
40
 * and exclude patterns. Only files/directories that match at least one
41
 * pattern of the include pattern list, and don't match a pattern of the
42
 * exclude pattern list will be placed in the list of files/directories found.
43
 *
44
 * When no list of include patterns is supplied, "**" will be used, which
45
 * means that everything will be matched. When no list of exclude patterns is
46
 * supplied, an empty list is used, such that nothing will be excluded.
47
 *
48
 * The pattern matching is done as follows:
49
 * The name to be matched is split up in path segments. A path segment is the
50
 * name of a directory or file, which is bounded by DIRECTORY_SEPARATOR
51
 * ('/' under UNIX, '\' under Windows).
52
 * E.g. "abc/def/ghi/xyz.php" is split up in the segments "abc", "def", "ghi"
53
 * and "xyz.php".
54
 * The same is done for the pattern against which should be matched.
55
 *
56
 * Then the segments of the name and the pattern will be matched against each
57
 * other. When '**' is used for a path segment in the pattern, then it matches
58
 * zero or more path segments of the name.
59
 *
60
 * There are special case regarding the use of DIRECTORY_SEPARATOR at
61
 * the beginning of the pattern and the string to match:
62
 * When a pattern starts with a DIRECTORY_SEPARATOR, the string
63
 * to match must also start with a DIRECTORY_SEPARATOR.
64
 * When a pattern does not start with a DIRECTORY_SEPARATOR, the
65
 * string to match may not start with a DIRECTORY_SEPARATOR.
66
 * When one of these rules is not obeyed, the string will not
67
 * match.
68
 *
69
 * When a name path segment is matched against a pattern path segment, the
70
 * following special characters can be used:
71
 *   '*' matches zero or more characters,
72
 *   '?' matches one character.
73
 *
74
 * Examples:
75
 *
76
 * "**\*.php" matches all .php files/dirs in a directory tree.
77
 *
78
 * "test\a??.php" matches all files/dirs which start with an 'a', then two
79
 * more characters and then ".php", in a directory called test.
80
 *
81
 * "**" matches everything in a directory tree.
82
 *
83
 * "**\test\**\XYZ*" matches all files/dirs that start with "XYZ" and where
84
 * there is a parent directory called test (e.g. "abc\test\def\ghi\XYZ123").
85
 *
86
 * Case sensitivity may be turned off if necessary.  By default, it is
87
 * turned on.
88
 *
89
 * Example of usage:
90
 *   $ds = new DirectroyScanner();
91
 *   $includes = array("**\*.php");
92
 *   $excludes = array("modules\*\**");
93
 *   $ds->SetIncludes($includes);
94
 *   $ds->SetExcludes($excludes);
95
 *   $ds->SetBasedir("test");
96
 *   $ds->SetCaseSensitive(true);
97
 *   $ds->Scan();
98
 *
99
 *   print("FILES:");
100
 *   $files = ds->GetIncludedFiles();
101
 *   for ($i = 0; $i < count($files);$i++) {
102
 *     println("$files[$i]\n");
103
 *   }
104
 *
105
 * This will scan a directory called test for .php files, but excludes all
106
 * .php files in all directories under a directory called "modules"
107
 *
108
 * This class is complete preg/ereg free port of the Java class
109
 * org.apache.tools.ant.DirectoryScanner. Even functions that use preg/ereg
110
 * internally (like split()) are not used. Only the _fast_ string functions
111
 * and comparison operators (=== !=== etc) are used for matching and tokenizing.
112
 *
113
 * @author Arnout J. Kuiper, [email protected]
114
 * @author Magesh Umasankar, [email protected]
115
 * @author Andreas Aderhold, [email protected]
116
 */
117
class DirectoryScanner implements FileScanner, SelectorScanner
118
{
119
    /**
120
     * default set of excludes.
121
     */
122
    protected static $DEFAULTEXCLUDES = [
123
        '**/*~',
124
        '**/#*#',
125
        '**/.#*',
126
        '**/%*%',
127
        '**/CVS',
128
        '**/CVS/**',
129
        '**/.cvsignore',
130
        '**/SCCS',
131
        '**/SCCS/**',
132
        '**/vssver.scc',
133
        '**/.svn',
134
        '**/.svn/**',
135
        '**/._*',
136
        '**/.DS_Store',
137
        '**/.darcs',
138
        '**/.darcs/**',
139
        '**/.git',
140
        '**/.git/**',
141
        '**/.gitattributes',
142
        '**/.gitignore',
143
        '**/.gitmodules',
144
        '**/.hg',
145
        '**/.hg/**',
146
        '**/.hgignore',
147
        '**/.hgsub',
148
        '**/.hgsubstate',
149
        '**/.hgtags',
150
        '**/.bzr',
151
        '**/.bzr/**',
152
        '**/.bzrignore',
153
    ];
154
155
    /**
156
     * The base directory which should be scanned.
157
     *
158
     * @var string
159
     */
160
    protected $basedir;
161
162
    /**
163
     * The patterns for the files that should be included.
164
     *
165
     * @var string[]
166
     */
167
    protected $includes;
168
169
    /**
170
     * The patterns for the files that should be excluded.
171
     *
172
     * @var string[]
173
     */
174
    protected $excludes;
175
176
    /**
177
     * Whether to expand/dereference symbolic links, default is false.
178
     *
179
     * @var bool
180
     */
181
    protected $expandSymbolicLinks = false;
182
183
    /**
184
     * The files that where found and matched at least one includes, and matched
185
     * no excludes.
186
     */
187
    protected $filesIncluded;
188
189
    /**
190
     * The files that where found and did not match any includes. Trie.
191
     */
192
    protected $filesNotIncluded;
193
194
    /**
195
     * The files that where found and matched at least one includes, and also
196
     * matched at least one excludes. Trie object.
197
     */
198
    protected $filesExcluded;
199
200
    /**
201
     * The directories that where found and matched at least one includes, and
202
     * matched no excludes.
203
     */
204
    protected $dirsIncluded;
205
206
    /**
207
     * The directories that where found and did not match any includes.
208
     */
209
    protected $dirsNotIncluded;
210
211
    /**
212
     * The files that where found and matched at least one includes, and also
213
     * matched at least one excludes.
214
     */
215
    protected $dirsExcluded;
216
217
    /**
218
     * Have the vars holding our results been built by a slow scan?
219
     */
220
    protected $haveSlowResults = false;
221
222
    /**
223
     * Should the file system be treated as a case sensitive one?
224
     */
225
    protected $isCaseSensitive = true;
226
    /**
227
     * Whether a missing base directory is an error.
228
     */
229
    protected $errorOnMissingDir = false;
230
231
    /**
232
     * @var FileSelector[] Selectors
233
     */
234
    protected $selectorsList;
235
236
    protected $filesDeselected;
237
    protected $dirsDeselected;
238
239
    /**
240
     * if there are no deselected files.
241
     */
242
    protected $everythingIncluded = true;
243
244
    private static $defaultExcludeList = [];
245
246
    public function __construct()
247
    {
248
        if (empty(self::$defaultExcludeList)) {
249
            self::$defaultExcludeList = self::$DEFAULTEXCLUDES;
250
        }
251
    }
252
253
    /**
254
     * Does the path match the start of this pattern up to the first "**".
255
     * This is a static mehtod and should always be called static.
256
     *
257
     * This is not a general purpose test and should only be used if you
258
     * can live with false positives.
259
     *
260
     * pattern=**\a and str=b will yield true.
261
     *
262
     * @param string $pattern         the pattern to match against
263
     * @param string $str             the string (path) to match
264
     * @param bool   $isCaseSensitive must matches be case sensitive?
265
     *
266
     * @return bool true if matches, otherwise false
267
     */
268
    public function matchPatternStart($pattern, $str, $isCaseSensitive = true)
269
    {
270
        return SelectorUtils::matchPatternStart($pattern, $str, $isCaseSensitive);
271
    }
272
273
    /**
274
     * Matches a path against a pattern.
275
     *
276
     * @param string $pattern         the (non-null) pattern to match against
277
     * @param string $str             the (non-null) string (path) to match
278
     * @param bool   $isCaseSensitive must a case sensitive match be done?
279
     *
280
     * @return bool true when the pattern matches against the string.
281
     *              false otherwise.
282
     */
283
    public function matchPath($pattern, $str, $isCaseSensitive = true)
284
    {
285
        return SelectorUtils::matchPath($pattern, $str, $isCaseSensitive);
286
    }
287
288
    /**
289
     * Matches a string against a pattern. The pattern contains two special
290
     * characters:
291
     * '*' which means zero or more characters,
292
     * '?' which means one and only one character.
293
     *
294
     * @param string $pattern         pattern to match against
295
     * @param string $str             string that must be matched against the
296
     *                                pattern
297
     * @param bool   $isCaseSensitive
298
     *
299
     * @return bool true when the string matches against the pattern,
300
     *              false otherwise
301
     */
302
    public function match($pattern, $str, $isCaseSensitive = true)
303
    {
304
        return SelectorUtils::match($pattern, $str, $isCaseSensitive);
305
    }
306
307
    /**
308
     * Get the list of patterns that should be excluded by default.
309
     *
310
     * @return string[] an array of <code>String</code> based on the current
311
     *                  contents of the <code>defaultExcludes</code>
312
     *                  <code>Set</code>
313
     */
314
    public static function getDefaultExcludes()
315
    {
316
        return self::$defaultExcludeList;
317
    }
318
319
    /**
320
     * Add a pattern to the default excludes unless it is already a
321
     * default exclude.
322
     *
323
     * @param string $s a string to add as an exclude pattern
324
     *
325
     * @return bool <code>true</code> if the string was added;
326
     *              <code>false</code> if it already existed
327
     */
328
    public static function addDefaultExclude($s)
329
    {
330
        if (!in_array($s, self::$defaultExcludeList)) {
331
            $return = true;
332
            self::$defaultExcludeList[] = $s;
333
        } else {
334
            $return = false;
335
        }
336
337
        return $return;
338
    }
339
340
    /**
341
     * Remove a string if it is a default exclude.
342
     *
343
     * @param string $s the string to attempt to remove
344
     *
345
     * @return bool <code>true</code> if <code>s</code> was a default
346
     *              exclude (and thus was removed);
347
     *              <code>false</code> if <code>s</code> was not
348
     *              in the default excludes list to begin with
349
     */
350
    public static function removeDefaultExclude($s)
351
    {
352
        $key = array_search($s, self::$defaultExcludeList);
353
354
        if (false !== $key) {
355
            unset(self::$defaultExcludeList[$key]);
356
            self::$defaultExcludeList = array_values(self::$defaultExcludeList);
357
358
            return true;
359
        }
360
361
        return false;
362
    }
363
364
    /**
365
     * Go back to the hardwired default exclude patterns.
366
     */
367
    public static function resetDefaultExcludes()
368
    {
369
        self::$defaultExcludeList = self::$DEFAULTEXCLUDES;
370
    }
371
372
    /**
373
     * Sets the basedir for scanning. This is the directory that is scanned
374
     * recursively. All '/' and '\' characters are replaced by
375
     * DIRECTORY_SEPARATOR.
376
     *
377
     * @param string $basedir the (non-null) basedir for scanning
378
     */
379
    public function setBasedir($basedir)
380
    {
381
        $basedir = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $basedir);
382
        $this->basedir = $basedir;
383
    }
384
385
    /**
386
     * Gets the basedir that is used for scanning. This is the directory that
387
     * is scanned recursively.
388
     *
389
     * @return string the basedir that is used for scanning
390
     */
391
    public function getBasedir()
392
    {
393
        return $this->basedir;
394
    }
395
396
    /**
397
     * Sets the case sensitivity of the file system.
398
     *
399
     * @param bool $isCaseSensitive specifies if the filesystem is case sensitive
400
     */
401
    public function setCaseSensitive($isCaseSensitive)
402
    {
403
        $this->isCaseSensitive = ($isCaseSensitive) ? true : false;
404
    }
405
406
    /**
407
     * Sets whether or not a missing base directory is an error.
408
     *
409
     * @param bool $errorOnMissingDir whether or not a missing base directory
410
     *                                is an error
411
     */
412
    public function setErrorOnMissingDir($errorOnMissingDir)
413
    {
414
        $this->errorOnMissingDir = $errorOnMissingDir;
415
    }
416
417
    /**
418
     * Sets the set of include patterns to use. All '/' and '\' characters are
419
     * replaced by DIRECTORY_SEPARATOR. So the separator used need
420
     * not match DIRECTORY_SEPARATOR.
421
     *
422
     * When a pattern ends with a '/' or '\', "**" is appended.
423
     *
424
     * @param array $includes list of include patterns
425
     */
426
    public function setIncludes($includes = [])
427
    {
428
        if (empty($includes)) {
429
            $this->includes = null;
430
        } else {
431
            $numIncludes = count($includes);
432
            for ($i = 0; $i < $numIncludes; ++$i) {
433
                $pattern = str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $includes[$i]);
434
                if (StringHelper::endsWith(DIRECTORY_SEPARATOR, $pattern)) {
435
                    $pattern .= '**';
436
                }
437
                $this->includes[] = $pattern;
438
            }
439
        }
440
    }
441
442
    /**
443
     * Sets the set of exclude patterns to use. All '/' and '\' characters are
444
     * replaced by <code>File.separatorChar</code>. So the separator used need
445
     * not match <code>File.separatorChar</code>.
446
     *
447
     * When a pattern ends with a '/' or '\', "**" is appended.
448
     *
449
     * @param array $excludes list of exclude patterns
450
     */
451
    public function setExcludes($excludes = [])
452
    {
453
        if (empty($excludes)) {
454
            $this->excludes = null;
455
        } else {
456
            $numExcludes = count($excludes);
457
            for ($i = 0; $i < $numExcludes; ++$i) {
458
                $pattern = str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $excludes[$i]);
459
                if (StringHelper::endsWith(DIRECTORY_SEPARATOR, $pattern)) {
460
                    $pattern .= '**';
461
                }
462
                $this->excludes[] = $pattern;
463
            }
464
        }
465
    }
466
467
    /**
468
     * Sets whether to expand/dereference symbolic links.
469
     *
470
     * @param bool $expandSymbolicLinks
471
     */
472
    public function setExpandSymbolicLinks($expandSymbolicLinks)
473
    {
474
        $this->expandSymbolicLinks = $expandSymbolicLinks;
475
    }
476
477
    /**
478
     * Scans the base directory for files that match at least one include
479
     * pattern, and don't match any exclude patterns.
480
     */
481
    public function scan()
482
    {
483
        $this->clearResults();
484
        $exception = null;
485
486
        if (empty($this->basedir)) {
487
            return false;
488
        }
489
490
        if (!@file_exists($this->basedir)) {
491
            if ($this->errorOnMissingDir) {
492
                $exception = new BuildException(
493
                    "basedir  {$this->basedir} does not exist."
494
                );
495
            } else {
496
                return false;
497
            }
498
        } elseif (!@is_dir($this->basedir)) {
499
            $exception = new BuildException(
500
                "basedir {$this->basedir} is not a directory."
501
            );
502
        }
503
        if (null !== $exception) {
504
            throw $exception;
505
        }
506
507
        if (null === $this->includes) {
508
            // No includes supplied, so set it to 'matches all'
509
            $this->includes = ['**'];
510
        }
511
        if (null === $this->excludes) {
512
            $this->excludes = [];
513
        }
514
515
        if ($this->isIncluded('')) {
516
            if (!$this->isExcluded('')) {
517
                if ($this->isSelected('', $this->basedir)) {
518
                    $this->dirsIncluded[] = '';
519
                } else {
520
                    $this->dirsDeselected[] = '';
521
                }
522
            } else {
523
                $this->dirsExcluded[] = '';
524
            }
525
        } else {
526
            $this->dirsNotIncluded[] = '';
527
        }
528
529
        $this->scandir($this->basedir, '', true);
530
531
        return true;
532
    }
533
534
    /**
535
     * Lists contents of a given directory and returns array with entries.
536
     *
537
     * @param string $_dir directory to list contents for
538
     *
539
     * @return array directory entries
540
     *
541
     * @author Albert Lash, [email protected]
542
     */
543
    public function listDir($_dir)
544
    {
545
        return (new File($_dir))->listDir();
546
    }
547
548
    /**
549
     * Return the count of included files.
550
     *
551
     * @throws UnexpectedValueException
552
     */
553
    public function getIncludedFilesCount(): int
554
    {
555
        if (null === $this->filesIncluded) {
556
            throw new UnexpectedValueException('Must call scan() first');
557
        }
558
559
        return count($this->filesIncluded);
560
    }
561
562
    /**
563
     * Get the names of the files that matched at least one of the include
564
     * patterns, and matched none of the exclude patterns.
565
     * The names are relative to the basedir.
566
     *
567
     * @throws UnexpectedValueException
568
     *
569
     * @return array names of the files
570
     */
571
    public function getIncludedFiles(): array
572
    {
573
        if (null === $this->filesIncluded) {
574
            throw new UnexpectedValueException('Must call scan() first');
575
        }
576
577
        sort($this->filesIncluded);
578
579
        return $this->filesIncluded;
580
    }
581
582
    /**
583
     * Get the names of the files that matched at none of the include patterns.
584
     * The names are relative to the basedir.
585
     *
586
     * @return array the names of the files
587
     */
588
    public function getNotIncludedFiles()
589
    {
590
        $this->slowScan();
591
592
        return $this->filesNotIncluded;
593
    }
594
595
    /**
596
     * Get the names of the files that matched at least one of the include
597
     * patterns, an matched also at least one of the exclude patterns.
598
     * The names are relative to the basedir.
599
     *
600
     * @return array the names of the files
601
     */
602
    public function getExcludedFiles()
603
    {
604
        $this->slowScan();
605
606
        return $this->filesExcluded;
607
    }
608
609
    /**
610
     * <p>Returns the names of the files which were selected out and
611
     * therefore not ultimately included.</p>.
612
     *
613
     * <p>The names are relative to the base directory. This involves
614
     * performing a slow scan if one has not already been completed.</p>
615
     *
616
     * @return array the names of the files which were deselected
617
     *
618
     * @see #slowScan
619
     */
620
    public function getDeselectedFiles()
621
    {
622
        $this->slowScan();
623
624
        return $this->filesDeselected;
625
    }
626
627
    /**
628
     * Get the names of the directories that matched at least one of the include
629
     * patterns, an matched none of the exclude patterns.
630
     * The names are relative to the basedir.
631
     *
632
     * @throws UnexpectedValueException
633
     *
634
     * @return array the names of the directories
635
     */
636
    public function getIncludedDirectories()
637
    {
638
        if (null === $this->dirsIncluded) {
639
            throw new UnexpectedValueException('Must call scan() first');
640
        }
641
642
        sort($this->dirsIncluded);
643
644
        return $this->dirsIncluded;
645
    }
646
647
    /**
648
     * Return the count of included directories.
649
     *
650
     * @throws UnexpectedValueException
651
     */
652
    public function getIncludedDirectoriesCount(): int
653
    {
654
        if (null === $this->dirsIncluded) {
655
            throw new UnexpectedValueException('Must call scan() first');
656
        }
657
658
        return count($this->dirsIncluded);
659
    }
660
661
    /**
662
     * Get the names of the directories that matched at none of the include
663
     * patterns.
664
     * The names are relative to the basedir.
665
     *
666
     * @return array the names of the directories
667
     */
668
    public function getNotIncludedDirectories()
669
    {
670
        $this->slowScan();
671
672
        return $this->dirsNotIncluded;
673
    }
674
675
    /**
676
     * <p>Returns the names of the directories which were selected out and
677
     * therefore not ultimately included.</p>.
678
     *
679
     * <p>The names are relative to the base directory. This involves
680
     * performing a slow scan if one has not already been completed.</p>
681
     *
682
     * @return array the names of the directories which were deselected
683
     *
684
     * @see #slowScan
685
     */
686
    public function getDeselectedDirectories()
687
    {
688
        $this->slowScan();
689
690
        return $this->dirsDeselected;
691
    }
692
693
    /**
694
     * Get the names of the directories that matched at least one of the include
695
     * patterns, an matched also at least one of the exclude patterns.
696
     * The names are relative to the basedir.
697
     *
698
     * @return array the names of the directories
699
     */
700
    public function getExcludedDirectories()
701
    {
702
        $this->slowScan();
703
704
        return $this->dirsExcluded;
705
    }
706
707
    /**
708
     * Adds the array with default exclusions to the current exclusions set.
709
     */
710
    public function addDefaultExcludes()
711
    {
712
        $defaultExcludesTemp = self::getDefaultExcludes();
713
        $newExcludes = [];
714
        foreach ($defaultExcludesTemp as $temp) {
715
            $newExcludes[] = str_replace(['\\', '/'], FileUtils::getSeparator(), $temp);
716
        }
717
        $this->excludes = array_merge((array) $this->excludes, $newExcludes);
718
    }
719
720
    /**
721
     * Sets the selectors that will select the filelist.
722
     *
723
     * @param array $selectors the selectors to be invoked on a scan
724
     */
725
    public function setSelectors($selectors)
726
    {
727
        $this->selectorsList = $selectors;
728
    }
729
730
    /**
731
     * Returns whether or not the scanner has included all the files or
732
     * directories it has come across so far.
733
     *
734
     * @return bool <code>true</code> if all files and directories which have
735
     */
736
    public function isEverythingIncluded()
737
    {
738
        return $this->everythingIncluded;
739
    }
740
741
    /**
742
     * Toplevel invocation for the scan.
743
     *
744
     * Returns immediately if a slow scan has already been requested.
745
     */
746
    protected function slowScan()
747
    {
748
        if ($this->haveSlowResults) {
749
            return;
750
        }
751
752
        // copy trie object add CopyInto() method
753
        $excl = $this->dirsExcluded;
754
        $notIncl = $this->dirsNotIncluded;
755
756
        for ($i = 0, $_i = count($excl); $i < $_i; ++$i) {
757
            if (!$this->couldHoldIncluded($excl[$i])) {
758
                $this->scandir($this->basedir . $excl[$i], $excl[$i] . DIRECTORY_SEPARATOR, false);
759
            }
760
        }
761
762
        for ($i = 0, $_i = count($notIncl); $i < $_i; ++$i) {
763
            if (!$this->couldHoldIncluded($notIncl[$i])) {
764
                $this->scandir($this->basedir . $notIncl[$i], $notIncl[$i] . DIRECTORY_SEPARATOR, false);
765
            }
766
        }
767
768
        $this->haveSlowResults = true;
769
    }
770
771
    /**
772
     * Tests whether a name matches against at least one include pattern.
773
     *
774
     * @param string $_name the name to match
775
     *
776
     * @return bool <code>true</code> when the name matches against at least one
777
     */
778
    protected function isIncluded($_name)
779
    {
780
        for ($i = 0, $_i = count($this->includes); $i < $_i; ++$i) {
781
            if ($this->matchPath($this->includes[$i], $_name, $this->isCaseSensitive)) {
782
                return true;
783
            }
784
        }
785
786
        return false;
787
    }
788
789
    /**
790
     * Tests whether a name matches the start of at least one include pattern.
791
     *
792
     * @param string $_name the name to match
793
     *
794
     * @return bool <code>true</code> when the name matches against at least one
795
     *              include pattern, <code>false</code> otherwise
796
     */
797
    protected function couldHoldIncluded($_name)
798
    {
799
        for ($i = 0, $includesCount = count($this->includes); $i < $includesCount; ++$i) {
800
            if ($this->matchPatternStart($this->includes[$i], $_name, $this->isCaseSensitive)) {
801
                return true;
802
            }
803
        }
804
805
        return false;
806
    }
807
808
    /**
809
     * Tests whether a name matches against at least one exclude pattern.
810
     *
811
     * @param string $_name the name to match
812
     *
813
     * @return bool <code>true</code> when the name matches against at least one
814
     *              exclude pattern, <code>false</code> otherwise
815
     */
816
    protected function isExcluded($_name)
817
    {
818
        for ($i = 0, $excludesCount = count($this->excludes); $i < $excludesCount; ++$i) {
819
            if ($this->matchPath($this->excludes[$i], $_name, $this->isCaseSensitive)) {
820
                return true;
821
            }
822
        }
823
824
        return false;
825
    }
826
827
    /**
828
     * Tests whether a name should be selected.
829
     *
830
     * @param string $name the filename to check for selecting
831
     * @param string $file the full file path
832
     *
833
     * @throws BuildException
834
     * @throws IOException
835
     * @throws \InvalidArgumentException
836
     *
837
     * @return bool false when the selectors says that the file
838
     *              should not be selected, True otherwise
839
     */
840
    protected function isSelected($name, $file)
841
    {
842
        if (null !== $this->selectorsList) {
843
            $basedir = new File($this->basedir);
844
            $file = new File($file);
845
            if (!$file->canRead()) {
846
                return false;
847
            }
848
849
            foreach ($this->selectorsList as $selector) {
850
                if (!$selector->isSelected($basedir, $name, $file)) {
851
                    return false;
852
                }
853
            }
854
        }
855
856
        return true;
857
    }
858
859
    /**
860
     * Scans the passed dir for files and directories. Found files and
861
     * directories are placed in their respective collections, based on the
862
     * matching of includes and excludes. When a directory is found, it is
863
     * scanned recursively.
864
     *
865
     * @param string $_rootdir the directory to scan
866
     * @param string $_vpath   the path relative to the basedir (needed to prevent
867
     *                         problems with an absolute path when using dir)
868
     * @param bool   $_fast
869
     *
870
     * @see #filesIncluded
871
     * @see #filesNotIncluded
872
     * @see #filesExcluded
873
     * @see #dirsIncluded
874
     * @see #dirsNotIncluded
875
     * @see #dirsExcluded
876
     */
877
    private function scandir($_rootdir, $_vpath, $_fast)
878
    {
879
        if (!is_readable($_rootdir)) {
880
            return;
881
        }
882
883
        $newfiles = $this->listDir($_rootdir);
884
885
        for ($i = 0, $_i = count($newfiles); $i < $_i; ++$i) {
886
            $file = $_rootdir . DIRECTORY_SEPARATOR . $newfiles[$i];
887
            $name = $_vpath . $newfiles[$i];
888
889
            if (@is_link($file) && !$this->expandSymbolicLinks) {
890
                if ($this->isIncluded($name)) {
891
                    if (!$this->isExcluded($name)) {
892
                        if ($this->isSelected($name, $file)) {
893
                            $this->filesIncluded[] = $name;
894
                        } else {
895
                            $this->everythingIncluded = false;
896
                            $this->filesDeselected[] = $name;
897
                        }
898
                    } else {
899
                        $this->everythingIncluded = false;
900
                        $this->filesExcluded[] = $name;
901
                    }
902
                } else {
903
                    $this->everythingIncluded = false;
904
                    $this->filesNotIncluded[] = $name;
905
                }
906
            } else {
907
                if (@is_dir($file)) {
908
                    if ($this->isIncluded($name)) {
909
                        if (!$this->isExcluded($name)) {
910
                            if ($this->isSelected($name, $file)) {
911
                                $this->dirsIncluded[] = $name;
912
                                if ($_fast) {
913
                                    $this->scandir($file, $name . DIRECTORY_SEPARATOR, $_fast);
914
                                }
915
                            } else {
916
                                $this->everythingIncluded = false;
917
                                $this->dirsDeselected[] = $name;
918
                                if ($_fast && $this->couldHoldIncluded($name)) {
919
                                    $this->scandir($file, $name . DIRECTORY_SEPARATOR, $_fast);
920
                                }
921
                            }
922
                        } else {
923
                            $this->everythingIncluded = false;
924
                            $this->dirsExcluded[] = $name;
925
                            if ($_fast && $this->couldHoldIncluded($name)) {
926
                                $this->scandir($file, $name . DIRECTORY_SEPARATOR, $_fast);
927
                            }
928
                        }
929
                    } else {
930
                        $this->everythingIncluded = false;
931
                        $this->dirsNotIncluded[] = $name;
932
                        if ($_fast && $this->couldHoldIncluded($name)) {
933
                            $this->scandir($file, $name . DIRECTORY_SEPARATOR, $_fast);
934
                        }
935
                    }
936
937
                    if (!$_fast) {
938
                        $this->scandir($file, $name . DIRECTORY_SEPARATOR, $_fast);
939
                    }
940
                } elseif (@is_file($file)) {
941
                    if ($this->isIncluded($name)) {
942
                        if (!$this->isExcluded($name)) {
943
                            if ($this->isSelected($name, $file)) {
944
                                $this->filesIncluded[] = $name;
945
                            } else {
946
                                $this->everythingIncluded = false;
947
                                $this->filesDeselected[] = $name;
948
                            }
949
                        } else {
950
                            $this->everythingIncluded = false;
951
                            $this->filesExcluded[] = $name;
952
                        }
953
                    } else {
954
                        $this->everythingIncluded = false;
955
                        $this->filesNotIncluded[] = $name;
956
                    }
957
                }
958
            }
959
        }
960
    }
961
962
    /**
963
     * @return void
964
     */
965
    private function clearResults(): void
966
    {
967
        $this->filesIncluded = [];
968
        $this->filesNotIncluded = [];
969
        $this->filesExcluded = [];
970
        $this->dirsIncluded = [];
971
        $this->dirsNotIncluded = [];
972
        $this->dirsExcluded = [];
973
        $this->dirsDeselected = [];
974
        $this->filesDeselected = [];
975
    }
976
}
977