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