Passed
Pull Request — master (#14)
by Sergei
01:27
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 18
            if (is_link($directory)) {
183 2
                self::unlink($directory);
184
            } else {
185 18
                rmdir($directory);
186
            }
187
        }
188
    }
189
190
    /**
191
     * Clear all directory content.
192
     *
193
     * @param string $directory the directory to be cleared.
194
     * @param array $options options for directory clear ({@see removeDirectory()}).
195
     *
196
     * @return void
197
     */
198 1
    public static function clearDirectory(string $directory, array $options = []): void
199
    {
200 1
        self::removeDirectory($directory, array_merge($options, [
201 1
            'keepRootDirectory' => true,
202
        ]));
203
    }
204
205
    /**
206
     * Removes a file or symlink in a cross-platform way.
207
     *
208
     * @param string $path
209
     *
210
     * @return bool
211
     */
212 11
    public static function unlink(string $path): bool
213
    {
214 11
        $isWindows = DIRECTORY_SEPARATOR === '\\';
215
216 11
        if (!$isWindows) {
217 11
            return unlink($path);
218
        }
219
220
        if (is_link($path) && is_dir($path)) {
221
            return rmdir($path);
222
        }
223
224
        return unlink($path);
225
    }
226
227
    /**
228
     * Copies a whole directory as another one.
229
     *
230
     * The files and sub-directories will also be copied over.
231
     *
232
     * @param string $source the source directory.
233
     * @param string $destination the destination directory.
234
     * @param array $options options for directory copy. Valid options are:
235
     *
236
     * - dirMode: integer, the permission to be set for newly copied directories. Defaults to 0775.
237
     * - fileMode:  integer, the permission to be set for newly copied files. Defaults to the current environment
238
     *   setting.
239
     * - filter: callback, a PHP callback that is called for each directory or file.
240
     *   The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered.
241
     *   The callback can return one of the following values:
242
     *
243
     *   * true: the directory or file will be copied (the "only" and "except" options will be ignored).
244
     *   * false: the directory or file will NOT be copied (the "only" and "except" options will be ignored).
245
     *   * null: the "only" and "except" options will determine whether the directory or file should be copied.
246
     *
247
     * - only: array, list of patterns that the file paths should match if they want to be copied. A path matches a
248
     *   pattern if it contains the pattern string at its end. For example, '.php' matches all file paths ending with
249
     *   '.php'.
250
     *   Note, the '/' characters in a pattern matches both '/' and '\' in the paths. If a file path matches a pattern
251
     *   in both "only" and "except", it will NOT be copied.
252
     * - except: array, list of patterns that the files or directories should match if they want to be excluded from
253
     *   being copied. A path matches a pattern if it contains the pattern string at its end. Patterns ending with '/'
254
     *   apply to directory paths only, and patterns not ending with '/' apply to file paths only. For example, '/a/b'
255
     *   matches all file paths ending with '/a/b'; and '.svn/' matches directory paths ending with '.svn'. Note, the
256
     *   '/' characters in a pattern matches both '/' and '\' in the paths.
257
     * - caseSensitive: boolean, whether patterns specified at "only" or "except" should be case sensitive. Defaults to
258
     *   true.
259
     * - recursive: boolean, whether the files under the subdirectories should also be copied. Defaults to true.
260
     * - beforeCopy: callback, a PHP callback that is called before copying each sub-directory or file. If the callback
261
     *   returns false, the copy operation for the sub-directory or file will be cancelled. The signature of the
262
     *   callback should be: `function ($from, $to)`, where `$from` is the sub-directory or file to be copied from,
263
     *   while `$to` is the copy target.
264
     * - afterCopy: callback, a PHP callback that is called after each sub-directory or file is successfully copied.
265
     *   The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or file
266
     *   copied from, while `$to` is the copy target.
267
     * - copyEmptyDirectories: boolean, whether to copy empty directories. Set this to false to avoid creating
268
     *   directories that do not contain files. This affects directories that do not contain files initially as well as
269
     *   directories that do not contain files at the target destination because files have been filtered via `only` or
270
     *   `except`. Defaults to true.
271
     *
272
     * @throws \InvalidArgumentException if unable to open directory
273
     * @throws \Exception
274
     *
275
     * @return void
276
     */
277 11
    public static function copyDirectory(string $source, string $destination, array $options = []): void
278
    {
279 11
        $source = static::normalizePath($source);
280 11
        $destination = static::normalizePath($destination);
281
282 11
        static::isSelfDirectory($source, $destination);
283
284 9
        $destinationExists = static::setDestination($destination, $options);
285
286 9
        $handle = static::isSourceDirectory($source);
287
288 9
        $options = static::setBasePath($source, $options);
289
290 9
        while (($file = readdir($handle)) !== false) {
291 9
            if ($file === '.' || $file === '..') {
292 9
                continue;
293
            }
294
295 7
            $from = $source . '/' . $file;
296 7
            $to = $destination . '/' . $file;
297
298 7
            if (static::filterPath($from, $options)) {
299 7
                if (is_file($from)) {
300 7
                    if (!$destinationExists) {
301 2
                        static::createDirectory($destination, $options['dirMode'] ?? 0775);
302 2
                        $destinationExists = true;
303
                    }
304 7
                    copy($from, $to);
305 7
                    if (isset($options['fileMode'])) {
306 7
                        static::chmod($to, $options['fileMode']);
307
                    }
308 6
                } elseif (!isset($options['recursive']) || $options['recursive']) {
309 5
                    static::copyDirectory($from, $to, $options);
310
                }
311
            }
312
        }
313
314 9
        closedir($handle);
315
    }
316
317
    /**
318
     * Check copy it self directory.
319
     *
320
     * @param string $source
321
     * @param string $destination
322
     *
323
     * @throws \InvalidArgumentException
324
     *
325
     * @return boolean
326
     */
327 11
    private static function isSelfDirectory(string $source, string $destination)
328
    {
329 11
        if ($source === $destination || strpos($destination, $source . '/') === 0) {
330 2
            throw new \InvalidArgumentException('Trying to copy a directory to itself or a subdirectory.');
331
        }
332
    }
333
334
    /**
335
     * Check if exist source directory.
336
     *
337
     * @param string $source
338
     *
339
     * @throws \InvalidArgumentException
340
     *
341
     * @return resource
342
     */
343 9
    private static function isSourceDirectory(string $source)
344
    {
345 9
        $handle = @opendir($source);
346
347 9
        if ($handle === false) {
348
            throw new \InvalidArgumentException("Unable to open directory: $source");
349
        }
350
351 9
        return $handle;
352
    }
353
354
    /**
355
     * Set base path directory.
356
     *
357
     * @param string $source
358
     * @param array $options
359
     *
360
     * @return array
361
     */
362 9
    private static function setBasePath(string $source, array $options): array
363
    {
364 9
        if (!isset($options['basePath'])) {
365
            // this should be done only once
366 9
            $options['basePath'] = realpath($source);
367 9
            $options = static::normalizeOptions($options);
368
        }
369
370 9
        return $options;
371
    }
372
373
    /**
374
     * Set destination directory.
375
     *
376
     * @param string $destination
377
     * @param array $options
378
     *
379
     * @return bool
380
     */
381 9
    private static function setDestination(string $destination, array $options): bool
382
    {
383 9
        $destinationExists = is_dir($destination);
384
385 9
        if (!$destinationExists && (!isset($options['copyEmptyDirectories']) || $options['copyEmptyDirectories'])) {
386 5
            static::createDirectory($destination, $options['dirMode'] ?? 0775);
387 5
            $destinationExists = true;
388
        }
389
390 9
        return $destinationExists;
391
    }
392
393
    /**
394
     * Normalize options.
395
     *
396
     * @param array $options raw options.
397
     *
398
     * @return array normalized options.
399
     */
400 9
    protected static function normalizeOptions(array $options): array
401
    {
402 9
        $options = static::setCaseSensitive($options);
403 9
        $options = static::setExcept($options);
404 9
        $options = static::setOnly($options);
405
406 9
        return $options;
407
    }
408
409
    /**
410
     * Set options case sensitive.
411
     *
412
     * @param array $options
413
     *
414
     * @return array
415
     */
416 9
    private static function setCaseSensitive(array $options): array
417
    {
418 9
        if (!array_key_exists('caseSensitive', $options)) {
419 9
            $options['caseSensitive'] = true;
420
        }
421
422 9
        return $options;
423
    }
424
425
    /**
426
     * Set options except.
427
     *
428
     * @param array $options
429
     *
430
     * @return array
431
     */
432 9
    private static function setExcept(array $options): array
433
    {
434 9
        if (isset($options['except'])) {
435 1
            foreach ($options['except'] as $key => $value) {
436 1
                if (\is_string($value)) {
437 1
                    $options['except'][$key] = self::parseExcludePattern($value, $options['caseSensitive']);
438
                }
439
            }
440
        }
441
442 9
        return $options;
443
    }
444
445
    /**
446
     * Set options only.
447
     *
448
     * @param array $options
449
     *
450
     * @return array
451
     */
452 9
    private static function setOnly(array $options): array
453
    {
454 9
        if (isset($options['only'])) {
455 2
            foreach ($options['only'] as $key => $value) {
456 2
                if (\is_string($value)) {
457 2
                    $options['only'][$key] = self::parseExcludePattern($value, $options['caseSensitive']);
458
                }
459
            }
460
        }
461
462 9
        return $options;
463
    }
464
465
    /**
466
     * Checks if the given file path satisfies the filtering options.
467
     *
468
     * @param string $path the path of the file or directory to be checked.
469
     * @param array $options the filtering options.
470
     *
471
     * @return bool whether the file or directory satisfies the filtering options.
472
     */
473 7
    public static function filterPath(string $path, array $options): bool
474
    {
475 7
        $path = str_replace('\\', '/', $path);
476
477 7
        if (!empty($options['except'])) {
478 1
            if ((self::lastExcludeMatchingFromList($options['basePath'], $path, $options['except'])) !== null) {
479 1
                return false;
480
            }
481
        }
482
483 7
        if (!empty($options['only']) && !is_dir($path)) {
484
            // don't check PATTERN_NEGATIVE since those entries are not prefixed with !
485 2
            return self::lastExcludeMatchingFromList($options['basePath'], $path, $options['only']) !== null;
486
        }
487
488 7
        return true;
489
    }
490
491
    /**
492
     * Searches for the first wildcard character in the pattern.
493
     *
494
     * @param string $pattern the pattern to search in.
495
     *
496
     * @return int|bool position of first wildcard character or false if not found.
497
     */
498 2
    private static function firstWildcardInPattern(string $pattern)
499
    {
500 2
        $wildcards = ['*', '?', '[', '\\'];
501 2
        $wildcardSearch = function ($carry, $item) use ($pattern) {
502 2
            $position = strpos($pattern, $item);
503 2
            if ($position === false) {
504 2
                return $carry === false ? $position : $carry;
505
            }
506 2
            return $carry === false ? $position : min($carry, $position);
507 2
        };
508 2
        return array_reduce($wildcards, $wildcardSearch, false);
509
    }
510
511
512
    /**
513
     * Scan the given exclude list in reverse to see whether pathname should be ignored.
514
     *
515
     * The first match (i.e. the last on the list), if any, determines the fate.  Returns the element which matched,
516
     * or null for undecided.
517
     *
518
     * Based on last_exclude_matching_from_list() from dir.c of git 1.8.5.3 sources.
519
     *
520
     * @param string $basePath.
521
     * @param string $path.
522
     * @param array $excludes list of patterns to match $path against.
523
     *
524
     * @return null|array null or one of $excludes item as an array with keys: 'pattern', 'flags'.
525
     *
526
     * @throws \InvalidArgumentException if any of the exclude patterns is not a string or an array with keys: pattern,
527
     *                                   flags, firstWildcard.
528
     */
529 2
    private static function lastExcludeMatchingFromList(string $basePath, string $path, array $excludes): ?array
530
    {
531 2
        foreach (array_reverse($excludes) as $exclude) {
532 2
            if (\is_string($exclude)) {
533
                $exclude = self::parseExcludePattern($exclude, false);
534
            }
535
536 2
            if (!isset($exclude['pattern'], $exclude['flags'], $exclude['firstWildcard'])) {
537
                throw new \InvalidArgumentException(
538
                    'If exclude/include pattern is an array it must contain the pattern, flags and firstWildcard keys.'
539
                );
540
            }
541
542 2
            if (($exclude['flags'] & self::PATTERN_MUST_BE_DIR) && !is_dir($path)) {
543
                continue;
544
            }
545
546 2
            if ($exclude['flags'] & self::PATTERN_NO_DIR) {
547
                if (self::matchBasename(basename($path), $exclude['pattern'], $exclude['firstWildcard'], $exclude['flags'])) {
548
                    return $exclude;
549
                }
550
                continue;
551
            }
552
553 2
            if (self::matchPathname($path, $basePath, $exclude['pattern'], $exclude['firstWildcard'], $exclude['flags'])) {
554 2
                return $exclude;
555
            }
556
        }
557
558 2
        return null;
559
    }
560
561
    /**
562
     * Performs a simple comparison of file or directory names.
563
     *
564
     * Based on match_basename() from dir.c of git 1.8.5.3 sources.
565
     *
566
     * @param string $baseName file or directory name to compare with the pattern.
567
     * @param string $pattern the pattern that $baseName will be compared against.
568
     * @param int|bool $firstWildcard location of first wildcard character in the $pattern.
569
     * @param int $flags pattern flags
570
     *
571
     * @return bool whether the name matches against pattern
572
     */
573
    private static function matchBasename(string $baseName, string $pattern, $firstWildcard, int $flags): bool
574
    {
575
        if ($firstWildcard === false) {
576
            if ($pattern === $baseName) {
577
                return true;
578
            }
579
        } elseif ($flags & self::PATTERN_ENDS_WITH) {
580
            /* "*literal" matching against "fooliteral" */
581
            $n = StringHelper::byteLength($pattern);
582
            if (StringHelper::byteSubstring($pattern, 1, $n) === StringHelper::byteSubstring($baseName, -$n, $n)) {
583
                return true;
584
            }
585
        }
586
587
588
        $wildcardPattern = new WildcardPattern($pattern);
589
590
        if ($flags & self::PATTERN_CASE_INSENSITIVE) {
591
            $wildcardPattern = $wildcardPattern->ignoreCase();
592
        }
593
594
        return $wildcardPattern->match($baseName);
595
    }
596
597
    /**
598
     * Compares a path part against a pattern with optional wildcards.
599
     *
600
     * Based on match_pathname() from dir.c of git 1.8.5.3 sources.
601
     *
602
     * @param string $path full path to compare
603
     * @param string $basePath base of path that will not be compared
604
     * @param string $pattern the pattern that path part will be compared against
605
     * @param int|bool $firstWildcard location of first wildcard character in the $pattern
606
     * @param int $flags pattern flags
607
     *
608
     * @return bool whether the path part matches against pattern
609
     */
610 2
    private static function matchPathname(string $path, string $basePath, string $pattern, $firstWildcard, int $flags): bool
611
    {
612
        // match with FNM_PATHNAME; the pattern has base implicitly in front of it.
613 2
        if (strpos($pattern, '/') === 0) {
614
            $pattern = StringHelper::byteSubstring($pattern, 1, StringHelper::byteLength($pattern));
615
            if ($firstWildcard !== false && $firstWildcard !== 0) {
616
                $firstWildcard--;
617
            }
618
        }
619
620 2
        $namelen = StringHelper::byteLength($path) - (empty($basePath) ? 0 : StringHelper::byteLength($basePath) + 1);
621 2
        $name = StringHelper::byteSubstring($path, -$namelen, $namelen);
622
623 2
        if ($firstWildcard !== 0) {
624 2
            if ($firstWildcard === false) {
625 1
                $firstWildcard = StringHelper::byteLength($pattern);
626
            }
627
628
            // if the non-wildcard part is longer than the remaining pathname, surely it cannot match.
629 2
            if ($firstWildcard > $namelen) {
630 1
                return false;
631
            }
632
633 2
            if (strncmp($pattern, $name, (int) $firstWildcard)) {
634 2
                return false;
635
            }
636
637 2
            $pattern = StringHelper::byteSubstring($pattern, (int) $firstWildcard, StringHelper::byteLength($pattern));
638 2
            $name = StringHelper::byteSubstring($name, (int) $firstWildcard, $namelen);
639
640
            // 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.
641 2
            if (empty($pattern) && empty($name)) {
642 1
                return true;
643
            }
644
        }
645
646 2
        $wildcardPattern = (new WildcardPattern($pattern))
647 2
            ->withExactSlashes();
648
649 2
        if ($flags & self::PATTERN_CASE_INSENSITIVE) {
650
            $wildcardPattern = $wildcardPattern->ignoreCase();
651
        }
652
653 2
        return $wildcardPattern->match($name);
654
    }
655
656
    /**
657
     * Processes the pattern, stripping special characters like / and ! from the beginning and settings flags instead.
658
     *
659
     * @param string $pattern
660
     * @param bool $caseSensitive
661
     *
662
     * @return array with keys: (string) pattern, (int) flags, (int|bool) firstWildcard
663
     */
664 2
    private static function parseExcludePattern(string $pattern, bool $caseSensitive): array
665
    {
666
        $result = [
667 2
            'pattern' => $pattern,
668 2
            'flags' => 0,
669
            'firstWildcard' => false,
670
        ];
671
672 2
        $result = static::isCaseInsensitive($caseSensitive, $result);
673
674 2
        if (!isset($pattern[0])) {
675
            return $result;
676
        }
677
678 2
        if (strpos($pattern, '!') === 0) {
679
            $result['flags'] |= self::PATTERN_NEGATIVE;
680
            $pattern = StringHelper::byteSubstring($pattern, 1, StringHelper::byteLength($pattern));
681
        }
682
683 2
        if (StringHelper::byteLength($pattern) && StringHelper::byteSubstring($pattern, -1, 1) === '/') {
684
            $pattern = StringHelper::byteSubstring($pattern, 0, -1);
685
            $result['flags'] |= self::PATTERN_MUST_BE_DIR;
686
        }
687
688 2
        $result = static::isPatternNoDir($pattern, $result);
689
690 2
        $result['firstWildcard'] = self::firstWildcardInPattern($pattern);
691
692 2
        $result = static::isPatternEndsWith($pattern, $result);
693
694 2
        $result['pattern'] = $pattern;
695
696 2
        return $result;
697
    }
698
699
    /**
700
     * Check isCaseInsensitive.
701
     *
702
     * @param boolean $caseSensitive
703
     * @param array $result
704
     *
705
     * @return array
706
     */
707 2
    private static function isCaseInsensitive(bool $caseSensitive, array $result): array
708
    {
709 2
        if (!$caseSensitive) {
710
            $result['flags'] |= self::PATTERN_CASE_INSENSITIVE;
711
        }
712
713 2
        return $result;
714
    }
715
716
    /**
717
     * Check pattern no directory.
718
     *
719
     * @param string $pattern
720
     * @param array $result
721
     *
722
     * @return array
723
     */
724 2
    private static function isPatternNoDir(string $pattern, array $result): array
725
    {
726 2
        if (strpos($pattern, '/') === false) {
727
            $result['flags'] |= self::PATTERN_NO_DIR;
728
        }
729
730 2
        return $result;
731
    }
732
733
    /**
734
     * Check pattern ends with
735
     *
736
     * @param string $pattern
737
     * @param array $result
738
     *
739
     * @return array
740
     */
741 2
    private static function isPatternEndsWith(string $pattern, array $result): array
742
    {
743 2
        if (strpos($pattern, '*') === 0 && self::firstWildcardInPattern(StringHelper::byteSubstring($pattern, 1, StringHelper::byteLength($pattern))) === false) {
744
            $result['flags'] |= self::PATTERN_ENDS_WITH;
745
        }
746
747 2
        return $result;
748
    }
749
}
750