Passed
Pull Request — master (#14)
by Sergei
01:14
created

FileHelper::matchPathname()   C

Complexity

Conditions 12
Paths 72

Size

Total Lines 44
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 12.7571

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 22
c 1
b 0
f 0
nc 72
nop 5
dl 0
loc 44
ccs 19
cts 23
cp 0.8261
crap 12.7571
rs 6.9666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Files;
6
7
use Yiisoft\Strings\StringHelper;
8
use Yiisoft\Strings\WildcardPattern;
9
10
/**
11
 * FileHelper provides useful methods to manage files and directories
12
 */
13
class FileHelper
14
{
15
    /**
16
     * @var int PATTERN_NO_DIR
17
     */
18
    private const PATTERN_NO_DIR = 1;
19
20
    /**
21
     * @var int PATTERN_ENDS_WITH
22
     */
23
    private const PATTERN_ENDS_WITH = 4;
24
25
    /**
26
     * @var int PATTERN_MUST_BE_DIR
27
     */
28
    private const PATTERN_MUST_BE_DIR = 8;
29
30
    /**
31
     * @var int PATTERN_NEGATIVE
32
     */
33
    private const PATTERN_NEGATIVE = 16;
34
35
    /**
36
     * @var int PATTERN_CASE_INSENSITIVE
37
     */
38
    private const PATTERN_CASE_INSENSITIVE = 32;
39
40
    /**
41
     * Creates a new directory.
42
     *
43
     * This method is similar to the PHP `mkdir()` function except that it uses `chmod()` to set the permission of the
44
     * created directory in order to avoid the impact of the `umask` setting.
45
     *
46
     * @param string $path path of the directory to be created.
47
     * @param int $mode the permission to be set for the created directory.
48
     *
49
     * @return bool whether the directory is created successfully.
50
     */
51 18
    public static function createDirectory(string $path, int $mode = 0775): bool
52
    {
53 18
        $path = static::normalizePath($path);
54
55
        try {
56 18
            if (!mkdir($path, $mode, true) && !is_dir($path)) {
57 18
                return false;
58
            }
59 1
        } catch (\Exception $e) {
60 1
            if (!is_dir($path)) {
61
                throw new \RuntimeException(
62
                    "Failed to create directory \"$path\": " . $e->getMessage(),
63
                    $e->getCode(),
64
                    $e
65
                );
66
            }
67
        }
68
69 18
        return static::chmod($path, $mode);
70
    }
71
72
    /**
73
     * Set permissions directory.
74
     *
75
     * @param string $path
76
     * @param integer $mode
77
     *
78
     * @throws \RuntimeException
79
     *
80
     * @return boolean|null
81
     */
82 18
    private static function chmod(string $path, int $mode): ?bool
83
    {
84
        try {
85 18
            return chmod($path, $mode);
86
        } catch (\Exception $e) {
87
            throw new \RuntimeException(
88
                "Failed to change permissions for directory \"$path\": " . $e->getMessage(),
89
                $e->getCode(),
90
                $e
91
            );
92
        }
93
    }
94
95
    /**
96
     * Normalizes a file/directory path.
97
     *
98
     * The normalization does the following work:
99
     *
100
     * - Convert all directory separators into `/` (e.g. "\a/b\c" becomes "/a/b/c")
101
     * - Remove trailing directory separators (e.g. "/a/b/c/" becomes "/a/b/c")
102
     * - Turn multiple consecutive slashes into a single one (e.g. "/a///b/c" becomes "/a/b/c")
103
     * - Remove ".." and "." based on their meanings (e.g. "/a/./b/../c" becomes "/a/c")
104
     *
105
     * @param string $path the file/directory path to be normalized
106
     *
107
     * @return string the normalized file/directory path
108
     */
109 18
    public static function normalizePath(string $path): string
110
    {
111 18
        $isWindowsShare = strpos($path, '\\\\') === 0;
112
113 18
        if ($isWindowsShare) {
114 1
            $path = substr($path, 2);
115
        }
116
117 18
        $path = rtrim(strtr($path, '/\\', '//'), '/');
118
119 18
        if (strpos('/' . $path, '/.') === false && strpos($path, '//') === false) {
120 18
            return $isWindowsShare ? "\\\\$path" : $path;
121
        }
122
123 1
        $parts = [];
124
125 1
        foreach (explode('/', $path) as $part) {
126 1
            if ($part === '..' && !empty($parts) && end($parts) !== '..') {
127 1
                array_pop($parts);
128 1
            } elseif ($part !== '.' && ($part !== '' || empty($parts))) {
129 1
                $parts[] = $part;
130
            }
131
        }
132
133 1
        $path = implode('/', $parts);
134
135 1
        if ($isWindowsShare) {
136 1
            $path = '\\\\' . $path;
137
        }
138
139 1
        return $path === '' ? '.' : $path;
140
    }
141
142
    /**
143
     * Removes a directory (and all its content) recursively.
144
     *
145
     * @param string $directory the directory to be deleted recursively.
146
     * @param array $options options for directory remove. Valid options are:
147
     *
148
     * - traverseSymlinks: boolean, whether symlinks to the directories should be traversed too.
149
     *   Defaults to `false`, meaning the content of the symlinked directory would not be deleted.
150
     *   Only symlink would be removed in that default case.
151
     *
152
     * - keepRootDirectory: boolean, whether only clear directory.
153
     *
154
     * @return void
155
     */
156 18
    public static function removeDirectory(string $directory, array $options = []): void
157
    {
158 18
        $keepRootDirectory = !empty($options['keepRootDirectory']);
159 18
        unset($options['keepRootDirectory']);
160
161 18
        if (!empty($options['traverseSymlinks']) || !is_link($directory)) {
162 18
            if (!($handle = @opendir($directory))) {
163 1
                return;
164
            }
165
166 18
            while (($file = readdir($handle)) !== false) {
167 18
                if ($file === '.' || $file === '..') {
168 18
                    continue;
169
                }
170 17
                $path = $directory . '/' . $file;
171 17
                if (is_dir($path)) {
172 17
                    self::removeDirectory($path, $options);
173
                } else {
174 11
                    self::unlink($path);
175
                }
176
            }
177
178 18
            closedir($handle);
179
        }
180
181 18
        if ($keepRootDirectory) {
182 1
            return;
183
        }
184
185 18
        if (is_link($directory)) {
186 2
            self::unlink($directory);
187
        } else {
188 18
            rmdir($directory);
189
        }
190
    }
191
192
    /**
193
     * Clear all directory content.
194
     *
195
     * @param string $directory the directory to be cleared.
196
     * @param array $options options for directory clear ({@see removeDirectory()}).
197
     *
198
     * @return void
199
     */
200 1
    public static function clearDirectory(string $directory, array $options = []): void
201
    {
202 1
        self::removeDirectory($directory, array_merge($options, [
203 1
            'keepRootDirectory' => true,
204
        ]));
205
    }
206
207
    /**
208
     * Removes a file or symlink in a cross-platform way.
209
     *
210
     * @param string $path
211
     *
212
     * @return bool
213
     */
214 11
    public static function unlink(string $path): bool
215
    {
216 11
        $isWindows = DIRECTORY_SEPARATOR === '\\';
217
218 11
        if (!$isWindows) {
219 11
            return unlink($path);
220
        }
221
222
        if (is_link($path) && is_dir($path)) {
223
            return rmdir($path);
224
        }
225
226
        return unlink($path);
227
    }
228
229
    /**
230
     * Copies a whole directory as another one.
231
     *
232
     * The files and sub-directories will also be copied over.
233
     *
234
     * @param string $source the source directory.
235
     * @param string $destination the destination directory.
236
     * @param array $options options for directory copy. Valid options are:
237
     *
238
     * - dirMode: integer, the permission to be set for newly copied directories. Defaults to 0775.
239
     * - fileMode:  integer, the permission to be set for newly copied files. Defaults to the current environment
240
     *   setting.
241
     * - filter: callback, a PHP callback that is called for each directory or file.
242
     *   The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered.
243
     *   The callback can return one of the following values:
244
     *
245
     *   * true: the directory or file will be copied (the "only" and "except" options will be ignored).
246
     *   * false: the directory or file will NOT be copied (the "only" and "except" options will be ignored).
247
     *   * null: the "only" and "except" options will determine whether the directory or file should be copied.
248
     *
249
     * - only: array, list of patterns that the file paths should match if they want to be copied. A path matches a
250
     *   pattern if it contains the pattern string at its end. For example, '.php' matches all file paths ending with
251
     *   '.php'.
252
     *   Note, the '/' characters in a pattern matches both '/' and '\' in the paths. If a file path matches a pattern
253
     *   in both "only" and "except", it will NOT be copied.
254
     * - except: array, list of patterns that the files or directories should match if they want to be excluded from
255
     *   being copied. A path matches a pattern if it contains the pattern string at its end. Patterns ending with '/'
256
     *   apply to directory paths only, and patterns not ending with '/' apply to file paths only. For example, '/a/b'
257
     *   matches all file paths ending with '/a/b'; and '.svn/' matches directory paths ending with '.svn'. Note, the
258
     *   '/' characters in a pattern matches both '/' and '\' in the paths.
259
     * - caseSensitive: boolean, whether patterns specified at "only" or "except" should be case sensitive. Defaults to
260
     *   true.
261
     * - recursive: boolean, whether the files under the subdirectories should also be copied. Defaults to true.
262
     * - beforeCopy: callback, a PHP callback that is called before copying each sub-directory or file. If the callback
263
     *   returns false, the copy operation for the sub-directory or file will be cancelled. The signature of the
264
     *   callback should be: `function ($from, $to)`, where `$from` is the sub-directory or file to be copied from,
265
     *   while `$to` is the copy target.
266
     * - afterCopy: callback, a PHP callback that is called after each sub-directory or file is successfully copied.
267
     *   The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or file
268
     *   copied from, while `$to` is the copy target.
269
     * - copyEmptyDirectories: boolean, whether to copy empty directories. Set this to false to avoid creating
270
     *   directories that do not contain files. This affects directories that do not contain files initially as well as
271
     *   directories that do not contain files at the target destination because files have been filtered via `only` or
272
     *   `except`. Defaults to true.
273
     *
274
     * @throws \InvalidArgumentException if unable to open directory
275
     * @throws \Exception
276
     *
277
     * @return void
278
     */
279 11
    public static function copyDirectory(string $source, string $destination, array $options = []): void
280
    {
281 11
        $source = static::normalizePath($source);
282 11
        $destination = static::normalizePath($destination);
283
284 11
        static::isSelfDirectory($source, $destination);
285
286 9
        $destinationExists = static::setDestination($destination, $options);
287
288 9
        $handle = static::isSourceDirectory($source);
289
290 9
        $options = static::setBasePath($source, $options);
291
292 9
        while (($file = readdir($handle)) !== false) {
293 9
            if ($file === '.' || $file === '..') {
294 9
                continue;
295
            }
296
297 7
            $from = $source . '/' . $file;
298 7
            $to = $destination . '/' . $file;
299
300 7
            if (static::filterPath($from, $options)) {
301 7
                if (is_file($from)) {
302 7
                    if (!$destinationExists) {
303 2
                        static::createDirectory($destination, $options['dirMode'] ?? 0775);
304 2
                        $destinationExists = true;
305
                    }
306 7
                    copy($from, $to);
307 7
                    if (isset($options['fileMode'])) {
308 7
                        static::chmod($to, $options['fileMode']);
309
                    }
310 6
                } elseif (!isset($options['recursive']) || $options['recursive']) {
311 5
                    static::copyDirectory($from, $to, $options);
312
                }
313
            }
314
        }
315
316 9
        closedir($handle);
317
    }
318
319
    /**
320
     * Check copy it self directory.
321
     *
322
     * @param string $source
323
     * @param string $destination
324
     *
325
     * @throws \InvalidArgumentException
326
     *
327
     * @return boolean
328
     */
329 11
    private static function isSelfDirectory(string $source, string $destination)
330
    {
331 11
        if ($source === $destination || strpos($destination, $source . '/') === 0) {
332 2
            throw new \InvalidArgumentException('Trying to copy a directory to itself or a subdirectory.');
333
        }
334
    }
335
336
    /**
337
     * Check if exist source directory.
338
     *
339
     * @param string $source
340
     *
341
     * @throws \InvalidArgumentException
342
     *
343
     * @return resource
344
     */
345 9
    private static function isSourceDirectory(string $source)
346
    {
347 9
        $handle = @opendir($source);
348
349 9
        if ($handle === false) {
350
            throw new \InvalidArgumentException("Unable to open directory: $source");
351
        }
352
353 9
        return $handle;
354
    }
355
356
    /**
357
     * Set base path directory.
358
     *
359
     * @param string $source
360
     * @param array $options
361
     *
362
     * @return array
363
     */
364 9
    private static function setBasePath(string $source, array $options): array
365
    {
366 9
        if (!isset($options['basePath'])) {
367
            // this should be done only once
368 9
            $options['basePath'] = realpath($source);
369 9
            $options = static::normalizeOptions($options);
370
        }
371
372 9
        return $options;
373
    }
374
375
    /**
376
     * Set destination directory.
377
     *
378
     * @param string $destination
379
     * @param array $options
380
     *
381
     * @return bool
382
     */
383 9
    private static function setDestination(string $destination, array $options): bool
384
    {
385 9
        $destinationExists = is_dir($destination);
386
387 9
        if (!$destinationExists && (!isset($options['copyEmptyDirectories']) || $options['copyEmptyDirectories'])) {
388 5
            static::createDirectory($destination, $options['dirMode'] ?? 0775);
389 5
            $destinationExists = true;
390
        }
391
392 9
        return $destinationExists;
393
    }
394
395
    /**
396
     * Normalize options.
397
     *
398
     * @param array $options raw options.
399
     *
400
     * @return array normalized options.
401
     */
402 9
    protected static function normalizeOptions(array $options): array
403
    {
404 9
        $options = static::setCaseSensitive($options);
405 9
        $options = static::setExcept($options);
406 9
        $options = static::setOnly($options);
407
408 9
        return $options;
409
    }
410
411
    /**
412
     * Set options case sensitive.
413
     *
414
     * @param array $options
415
     *
416
     * @return array
417
     */
418 9
    private static function setCaseSensitive(array $options): array
419
    {
420 9
        if (!array_key_exists('caseSensitive', $options)) {
421 9
            $options['caseSensitive'] = true;
422
        }
423
424 9
        return $options;
425
    }
426
427
    /**
428
     * Set options except.
429
     *
430
     * @param array $options
431
     *
432
     * @return array
433
     */
434 9
    private static function setExcept(array $options): array
435
    {
436 9
        if (isset($options['except'])) {
437 1
            foreach ($options['except'] as $key => $value) {
438 1
                if (\is_string($value)) {
439 1
                    $options['except'][$key] = self::parseExcludePattern($value, $options['caseSensitive']);
440
                }
441
            }
442
        }
443
444 9
        return $options;
445
    }
446
447
    /**
448
     * Set options only.
449
     *
450
     * @param array $options
451
     *
452
     * @return array
453
     */
454 9
    private static function setOnly(array $options): array
455
    {
456 9
        if (isset($options['only'])) {
457 2
            foreach ($options['only'] as $key => $value) {
458 2
                if (\is_string($value)) {
459 2
                    $options['only'][$key] = self::parseExcludePattern($value, $options['caseSensitive']);
460
                }
461
            }
462
        }
463
464 9
        return $options;
465
    }
466
467
    /**
468
     * Checks if the given file path satisfies the filtering options.
469
     *
470
     * @param string $path the path of the file or directory to be checked.
471
     * @param array $options the filtering options.
472
     *
473
     * @return bool whether the file or directory satisfies the filtering options.
474
     */
475 7
    public static function filterPath(string $path, array $options): bool
476
    {
477 7
        $path = str_replace('\\', '/', $path);
478
479 7
        if (!empty($options['except'])) {
480 1
            if ((self::lastExcludeMatchingFromList($options['basePath'], $path, $options['except'])) !== null) {
481 1
                return false;
482
            }
483
        }
484
485 7
        if (!empty($options['only']) && !is_dir($path)) {
486
            // don't check PATTERN_NEGATIVE since those entries are not prefixed with !
487 2
            return self::lastExcludeMatchingFromList($options['basePath'], $path, $options['only']) !== null;
488
        }
489
490 7
        return true;
491
    }
492
493
    /**
494
     * Searches for the first wildcard character in the pattern.
495
     *
496
     * @param string $pattern the pattern to search in.
497
     *
498
     * @return int|bool position of first wildcard character or false if not found.
499
     */
500 2
    private static function firstWildcardInPattern(string $pattern)
501
    {
502 2
        $wildcards = ['*', '?', '[', '\\'];
503 2
        $wildcardSearch = function ($carry, $item) use ($pattern) {
504 2
            $position = strpos($pattern, $item);
505 2
            if ($position === false) {
506 2
                return $carry === false ? $position : $carry;
507
            }
508 2
            return $carry === false ? $position : min($carry, $position);
509 2
        };
510 2
        return array_reduce($wildcards, $wildcardSearch, false);
511
    }
512
513
514
    /**
515
     * Scan the given exclude list in reverse to see whether pathname should be ignored.
516
     *
517
     * The first match (i.e. the last on the list), if any, determines the fate.  Returns the element which matched,
518
     * or null for undecided.
519
     *
520
     * Based on last_exclude_matching_from_list() from dir.c of git 1.8.5.3 sources.
521
     *
522
     * @param string $basePath.
523
     * @param string $path.
524
     * @param array $excludes list of patterns to match $path against.
525
     *
526
     * @return null|array null or one of $excludes item as an array with keys: 'pattern', 'flags'.
527
     *
528
     * @throws \InvalidArgumentException if any of the exclude patterns is not a string or an array with keys: pattern,
529
     *                                   flags, firstWildcard.
530
     */
531 2
    private static function lastExcludeMatchingFromList(string $basePath, string $path, array $excludes): ?array
532
    {
533 2
        foreach (array_reverse($excludes) as $exclude) {
534 2
            if (\is_string($exclude)) {
535
                $exclude = self::parseExcludePattern($exclude, false);
536
            }
537
538 2
            if (!isset($exclude['pattern'], $exclude['flags'], $exclude['firstWildcard'])) {
539
                throw new \InvalidArgumentException(
540
                    'If exclude/include pattern is an array it must contain the pattern, flags and firstWildcard keys.'
541
                );
542
            }
543
544 2
            if (($exclude['flags'] & self::PATTERN_MUST_BE_DIR) && !is_dir($path)) {
545
                continue;
546
            }
547
548 2
            if ($exclude['flags'] & self::PATTERN_NO_DIR) {
549
                if (self::matchBasename(basename($path), $exclude['pattern'], $exclude['firstWildcard'], $exclude['flags'])) {
550
                    return $exclude;
551
                }
552
                continue;
553
            }
554
555 2
            if (self::matchPathname($path, $basePath, $exclude['pattern'], $exclude['firstWildcard'], $exclude['flags'])) {
556 2
                return $exclude;
557
            }
558
        }
559
560 2
        return null;
561
    }
562
563
    /**
564
     * Performs a simple comparison of file or directory names.
565
     *
566
     * Based on match_basename() from dir.c of git 1.8.5.3 sources.
567
     *
568
     * @param string $baseName file or directory name to compare with the pattern.
569
     * @param string $pattern the pattern that $baseName will be compared against.
570
     * @param int|bool $firstWildcard location of first wildcard character in the $pattern.
571
     * @param int $flags pattern flags
572
     *
573
     * @return bool whether the name matches against pattern
574
     */
575
    private static function matchBasename(string $baseName, string $pattern, $firstWildcard, int $flags): bool
576
    {
577
        if ($firstWildcard === false) {
578
            if ($pattern === $baseName) {
579
                return true;
580
            }
581
        } elseif ($flags & self::PATTERN_ENDS_WITH) {
582
            /* "*literal" matching against "fooliteral" */
583
            $n = StringHelper::byteLength($pattern);
584
            if (StringHelper::byteSubstring($pattern, 1, $n) === StringHelper::byteSubstring($baseName, -$n, $n)) {
585
                return true;
586
            }
587
        }
588
589
590
        $wildcardPattern = new WildcardPattern($pattern);
591
592
        if ($flags & self::PATTERN_CASE_INSENSITIVE) {
593
            $wildcardPattern = $wildcardPattern->ignoreCase();
594
        }
595
596
        return $wildcardPattern->match($baseName);
597
    }
598
599
    /**
600
     * Compares a path part against a pattern with optional wildcards.
601
     *
602
     * Based on match_pathname() from dir.c of git 1.8.5.3 sources.
603
     *
604
     * @param string $path full path to compare
605
     * @param string $basePath base of path that will not be compared
606
     * @param string $pattern the pattern that path part will be compared against
607
     * @param int|bool $firstWildcard location of first wildcard character in the $pattern
608
     * @param int $flags pattern flags
609
     *
610
     * @return bool whether the path part matches against pattern
611
     */
612 2
    private static function matchPathname(string $path, string $basePath, string $pattern, $firstWildcard, int $flags): bool
613
    {
614
        // match with FNM_PATHNAME; the pattern has base implicitly in front of it.
615 2
        if (strpos($pattern, '/') === 0) {
616
            $pattern = StringHelper::byteSubstring($pattern, 1, StringHelper::byteLength($pattern));
617
            if ($firstWildcard !== false && $firstWildcard !== 0) {
618
                $firstWildcard--;
619
            }
620
        }
621
622 2
        $namelen = StringHelper::byteLength($path) - (empty($basePath) ? 0 : StringHelper::byteLength($basePath) + 1);
623 2
        $name = StringHelper::byteSubstring($path, -$namelen, $namelen);
624
625 2
        if ($firstWildcard !== 0) {
626 2
            if ($firstWildcard === false) {
627 1
                $firstWildcard = StringHelper::byteLength($pattern);
628
            }
629
630
            // if the non-wildcard part is longer than the remaining pathname, surely it cannot match.
631 2
            if ($firstWildcard > $namelen) {
632 1
                return false;
633
            }
634
635 2
            if (strncmp($pattern, $name, (int) $firstWildcard)) {
636 2
                return false;
637
            }
638
639 2
            $pattern = StringHelper::byteSubstring($pattern, (int) $firstWildcard, StringHelper::byteLength($pattern));
640 2
            $name = StringHelper::byteSubstring($name, (int) $firstWildcard, $namelen);
641
642
            // If the whole pattern did not have a wildcard, then our prefix match is all we need; we do not need to call fnmatch at all.
643 2
            if (empty($pattern) && empty($name)) {
644 1
                return true;
645
            }
646
        }
647
648 2
        $wildcardPattern = (new WildcardPattern($pattern))
649 2
            ->withExactSlashes();
650
651 2
        if ($flags & self::PATTERN_CASE_INSENSITIVE) {
652
            $wildcardPattern = $wildcardPattern->ignoreCase();
653
        }
654
655 2
        return $wildcardPattern->match($name);
656
    }
657
658
    /**
659
     * Processes the pattern, stripping special characters like / and ! from the beginning and settings flags instead.
660
     *
661
     * @param string $pattern
662
     * @param bool $caseSensitive
663
     *
664
     * @return array with keys: (string) pattern, (int) flags, (int|bool) firstWildcard
665
     */
666 2
    private static function parseExcludePattern(string $pattern, bool $caseSensitive): array
667
    {
668
        $result = [
669 2
            'pattern' => $pattern,
670 2
            'flags' => 0,
671
            'firstWildcard' => false,
672
        ];
673
674 2
        $result = static::isCaseInsensitive($caseSensitive, $result);
675
676 2
        if (!isset($pattern[0])) {
677
            return $result;
678
        }
679
680 2
        if (strpos($pattern, '!') === 0) {
681
            $result['flags'] |= self::PATTERN_NEGATIVE;
682
            $pattern = StringHelper::byteSubstring($pattern, 1, StringHelper::byteLength($pattern));
683
        }
684
685 2
        if (StringHelper::byteLength($pattern) && StringHelper::byteSubstring($pattern, -1, 1) === '/') {
686
            $pattern = StringHelper::byteSubstring($pattern, 0, -1);
687
            $result['flags'] |= self::PATTERN_MUST_BE_DIR;
688
        }
689
690 2
        $result = static::isPatternNoDir($pattern, $result);
691
692 2
        $result['firstWildcard'] = self::firstWildcardInPattern($pattern);
693
694 2
        $result = static::isPatternEndsWith($pattern, $result);
695
696 2
        $result['pattern'] = $pattern;
697
698 2
        return $result;
699
    }
700
701
    /**
702
     * Check isCaseInsensitive.
703
     *
704
     * @param boolean $caseSensitive
705
     * @param array $result
706
     *
707
     * @return array
708
     */
709 2
    private static function isCaseInsensitive(bool $caseSensitive, array $result): array
710
    {
711 2
        if (!$caseSensitive) {
712
            $result['flags'] |= self::PATTERN_CASE_INSENSITIVE;
713
        }
714
715 2
        return $result;
716
    }
717
718
    /**
719
     * Check pattern no directory.
720
     *
721
     * @param string $pattern
722
     * @param array $result
723
     *
724
     * @return array
725
     */
726 2
    private static function isPatternNoDir(string $pattern, array $result): array
727
    {
728 2
        if (strpos($pattern, '/') === false) {
729
            $result['flags'] |= self::PATTERN_NO_DIR;
730
        }
731
732 2
        return $result;
733
    }
734
735
    /**
736
     * Check pattern ends with
737
     *
738
     * @param string $pattern
739
     * @param array $result
740
     *
741
     * @return array
742
     */
743 2
    private static function isPatternEndsWith(string $pattern, array $result): array
744
    {
745 2
        if (strpos($pattern, '*') === 0 && self::firstWildcardInPattern(StringHelper::byteSubstring($pattern, 1, StringHelper::byteLength($pattern))) === false) {
746
            $result['flags'] |= self::PATTERN_ENDS_WITH;
747
        }
748
749 2
        return $result;
750
    }
751
}
752