Completed
Push — master ( 074806...efe083 )
by Lorenzo
02:33
created

DirHelper::canonicalize()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 11
nc 3
nop 1
1
<?php
2
3
namespace Padosoft\Io;
4
5
/**
6
 * Helper Class DirHelper
7
 * @package Padosoft\Io
8
 */
9
class DirHelper
10
{
11
    /**
12
     * Check if passed path exists or not.
13
     * @param string $filePath
14
     * @return bool
15
     */
16
    public static function isDirSafe(string $filePath) : bool
17
    {
18
        if (!$filePath) {
19
            return false;
20
        }
21
22
        return is_dir($filePath);
23
    }
24
25
    /**
26
     * Check if passed path exists or try to create it.
27
     * Return false if it fails to create it or if a file (and not a dir) passed as argument.
28
     * @param string $filePath
29
     * @param string $modeMask default '0755'
30
     * @return bool
31
     */
32
    public static function checkDirExistOrCreate(string $filePath, string $modeMask = '0755') : bool
33
    {
34
        if (self::isDirSafe($filePath)) {
35
            return true;
36
        }
37
38
        //controllo adesso che non sia un file
39
        if (FileHelper::fileExistsSafe($filePath)) {
40
            return false;
41
        }
42
43
        return mkdir($filePath, $modeMask, true) && self::isDirSafe($filePath);
44
    }
45
46
    /**
47
     * If dir passed, check if finishes with '/' otherwise append a slash to path.
48
     * If wrong or empty string passed, return '/'.
49
     * @param string $path
50
     * @return string
51
     */
52
    public static function addFinalSlash(string $path) : string
53
    {
54
        if ($path === null || $path == '') {
55
            return '/';
56
        }
57
58
        $quoted = preg_quote('/', '/');
59
        $path = preg_replace('/(?:' . $quoted . ')+$/', '', $path) . '/';
60
61
        return $path;
62
    }
63
64
    /**
65
     * for each dir passed in array, check if it finishes with '/' otherwise append a slash to path.
66
     * If not dir, leave the element untouched.
67
     * @param array $paths
68
     * @return array
69
     */
70
    public static function addFinalSlashToAllPaths(array $paths) : array
71
    {
72
        if (empty($paths)) {
73
            return [];
74
        }
75
76
        return array_map('self::addFinalSlash', $paths);
77
    }
78
79
    /**
80
     * Check if path ends with slash '/'
81
     * @param string $paths
82
     * @return bool
83
     */
84
    public static function endsWithSlash(string $paths) : bool
85
    {
86
        return self::endsWith($paths, '/');
87
    }
88
89
    /**
90
     * Check if path ends with star '*'
91
     * @param string $paths
92
     * @return bool
93
     */
94
    public static function endsWithStar(string $paths) : bool
95
    {
96
        return self::endsWith($paths, '*');
97
    }
98
99
    /**
100
     * Check if path ends with $needle
101
     * @param string $paths
102
     * @param string $needle
103
     * @return bool
104
     */
105
    public static function endsWith(string $paths, string $needle) : bool
106
    {
107
        if ($paths === null || $paths == '') {
108
            return false;
109
        }
110
        if ($needle === null || $needle == '') {
111
            return false;
112
        }
113
114
        // search forward starting from end minus needle length characters
115
        // see: http://stackoverflow.com/questions/834303/startswith-and-endswith-functions-in-php
116
        return (($temp = strlen($paths) - strlen($needle)) >= 0 && strpos($paths, $needle, $temp) !== false);
117
    }
118
119
    /**
120
     * Check if path starts with slash '/'
121
     * @param string $paths
122
     * @return bool
123
     */
124
    public static function startsWithSlash(string $paths) : bool
125
    {
126
        return self::startsWith($paths, '/');
127
    }
128
129
    /**
130
     * Check if path starts with slash $needle
131
     * @param string $paths
132
     * @param string $needle
133
     * @return bool
134
     */
135
    public static function startsWith(string $paths, string $needle) : bool
136
    {
137
        if ($paths === null || $paths == '') {
138
            return false;
139
        }
140
        if ($needle === null || $needle == '') {
141
            return false;
142
        }
143
144
        // search backwards starting from haystack length characters from the end
145
        // see: http://stackoverflow.com/questions/834303/startswith-and-endswith-functions-in-php
146
        return strrpos($paths, $needle, -strlen($paths)) !== false;
147
    }
148
149
    /**
150
     * Find dirs matching a pattern (recursive with subdirs).
151
     * Returns an array containing the matched dirs (full path and not files),
152
     * an empty array if no dir matched or on error.
153
     * @param string $pathPattern if is null it set to base_path()/* if exists otherwise __DIR__/*. It support glob() string pattern.
154
     * @return array of dirs
155
     */
156
    public static function findDirs(string $pathPattern)
157
    {
158
        if (($pathPattern === null || $pathPattern == '') && function_exists('base_path')) {
159
            $pathPattern = base_path() . '/*';
160
        } elseif ($pathPattern === null || $pathPattern == '') {
161
            $pathPattern = __DIR__ . '/*';
162
        } elseif (!self::endsWithStar($pathPattern)) {
163
            $pathPattern = DirHelper::addFinalSlash($pathPattern);
164
        }
165
166
        $files = glob($pathPattern, GLOB_ONLYDIR);
167
168
        foreach (glob(dirname($pathPattern) . '/*', GLOB_ONLYDIR | GLOB_NOSORT) as $dir) {
169
            $files = array_merge($files, self::findDirs($dir . '/' . basename($pathPattern)));
170
        }
171
172
        return $files;
173
    }
174
175
    /**
176
     * Dir::Delete()
177
     * get a dir path and remove all files and subdir in this dir.
178
     * if $not_remove_dir==TRUE then when finish DO NOT REMOVE THE $directory dir.
179
     * @param string $directory directory to empty
180
     * @param bool $not_remove_dir TRUE if DO NOT REMOVE THE $directory dir but only files.
181
     * @return bool true if success, otherwise false
182
     **/
183
    public static function delete($directory, bool $not_remove_dir = false) : bool
184
    {
185
        $directory = self::removeFinalSlash($directory);
186
187
        if (!self::isDirSafe($directory) || !is_readable($directory)) {
188
            return false;
189
        }
190
191
        $directoryHandle = opendir($directory);
192
        while (false !== ($contents = readdir($directoryHandle))) {
193
            if ($contents == '.' || $contents == '..') {
194
                continue;
195
            }
196
            $path = $directory . "/" . $contents;
197
198
            if (is_dir($path)) {
199
                self::delete($path, $not_remove_dir);
200
            } else {
201
                unlink($path);
202
            }
203
        }
204
        closedir($directoryHandle);
205
206
        if (!$not_remove_dir) {
207
            return true;
208
        }
209
        return rmdir($directory);
210
    }
211
212
    /**
213
     * Remove final slash ('/') char in dir if ends with slash.
214
     * @param $directory
215
     * @return string
216
     */
217
    public static function removeFinalSlash($directory) : string
218
    {
219
        if (self::endsWithSlash($directory)) {
220
            $directory = substr($directory, 0, -1);
221
        }
222
        return $directory;
223
    }
224
225
    /**
226
     * For each dir passed in array, check if not finishes with '/' otherwise remove a slash to path.
227
     * If not dir, leave the element untouched.
228
     * @param array $paths
229
     * @return array
230
     */
231
    public static function removeFinalSlashToAllPaths(array $paths) : array
232
    {
233
        if (empty($paths)) {
234
            return [];
235
        }
236
237
        return array_map('self::removeFinalSlash', $paths);
238
    }
239
240
    /**
241
     * Remove start slash ('/') char in dir if starts with slash.
242
     * @param $directory
243
     * @return string
244
     */
245
    public static function removeStartSlash($directory) : string
246
    {
247
        if (self::startsWithSlash($directory)) {
248
            $directory = substr($directory, 1);
249
        }
250
        return $directory;
251
    }
252
253
    /**
254
     * For each dir passed in array, check if not started with '/' otherwise remove a slash to path.
255
     * If not dir, leave the element untouched.
256
     * @param array $paths
257
     * @return array
258
     */
259
    public static function removeStartSlashToAllPaths(array $paths) : array
260
    {
261
        if (empty($paths)) {
262
            return [];
263
        }
264
265
        return array_map('self::removeStartSlash', $paths);
266
    }
267
268
    /**
269
     * Dir::copy()
270
     * Copy a source directory (files and all subdirectories) to destination directory.
271
     * If Destination directory doesn't exists try to create it.
272
     * @param $directorySource
273
     * @param $directoryDestination
274
     * @param array $excludedDirectory array of path to be escluded (i.e. it will not copied to destination folder)
275
     * @param \Closure|null $copied a function with two arguments  ($directorySource,$directoryDestination).
276
     * @return bool true if success, otherwise false
277
     */
278
    public static function copy(
279
        $directorySource,
280
        $directoryDestination,
281
        array $excludedDirectory = [],
282
        \Closure $copied = null
283
    ) : bool
284
    {
285
        $directorySource = self::removeFinalSlash($directorySource);
286
        if (!self::isDirSafe($directorySource) || !is_readable($directorySource)) {
287
            return false;
288
        }
289
290
        $directoryDestination = self::removeFinalSlash($directoryDestination);
291
        if (!self::checkDirExistOrCreate($directoryDestination)) {
292
            return false;
293
        }
294
        is_callable($copied) ? $copied($directorySource, $directoryDestination) : '';
295
296
        $excludedDirectory = self::removeFinalSlashToAllPaths($excludedDirectory);
297
298
        $directorySourceHandle = opendir($directorySource);
299
        while (false !== ($contents = readdir($directorySourceHandle))) {
300
            if ($contents == '.' || $contents == '..') {
301
                continue;
302
            }
303
            $path = $directorySource . "/" . $contents;
304
            if (in_array(DirHelper::removeFinalSlash($path), $excludedDirectory)) {
305
                continue;
306
            }
307
            $pathDest = $directoryDestination . "/" . $contents;
308
309
            if (is_dir($path)) {
310
                self::copy($path, $pathDest, $excludedDirectory);
311
            } else {
312
                copy($path, $pathDest);
313
                is_callable($copied) ? $copied($path, $pathDest) : '';
314
            }
315
        }
316
        closedir($directorySourceHandle);
317
        return true;
318
    }
319
320
    /**
321
     * Returns whether the given path is on the local filesystem.
322
     *
323
     * @param string $path A path string
324
     *
325
     * @return boolean Returns true if the path is local, false for a URL
326
     * @see https://github.com/laradic/support/blob/master/src/Path.php
327
     */
328
    public static function isLocal($path)
329
    {
330
        return is_string($path) && '' !== $path && false === strpos($path, '://');
331
    }
332
333
    /**
334
     * Returns whether a path is absolute Unix path.
335
     *
336
     * @param string $path A path string
337
     *
338
     * @return boolean Returns true if the path is absolute unix path, false if it is
339
     *                 relative or empty
340
     */
341
    public static function isAbsoluteUnix($path)
342
    {
343
        return '' !== $path && '/' === $path[0];
344
    }
345
346
    /**
347
     * Returns whether a path is absolute Windows Path.
348
     *
349
     * @param string $path A path string
350
     *
351
     * @return boolean Returns true if the path is absolute, false if it is
352
     *                 relative or empty
353
     * @see https://github.com/laradic/support/blob/master/src/Path.php
354
     */
355
    public static function isAbsoluteWindows($path)
356
    {
357
        if ('' === $path) {
358
            return false;
359
        }
360
        if ('\\' === $path[0]) {
361
            return true;
362
        }
363
        // Windows root
364
        return self::isAbsoluteWindowsRoot($path);
365
    }
366
367
    /**
368
     * Check if win special drive C: or Normal win drive C:/  C:\
369
     * @param $path
370
     * @return bool
371
     */
372
    protected static function isAbsoluteWindowsRoot($path):bool
373
    {
374
        if (strlen($path) > 1 && ctype_alpha($path[0]) && ':' === $path[1]) {
375
            // Special win drive C:
376
            if (2 === strlen($path)) {
377
                return true;
378
            }
379
            // Normal win drive C:/  C:\
380
            if ('/' === $path[2] || '\\' === $path[2]) {
381
                return true;
382
            }
383
        }
384
        return false;
385
    }
386
387
    /**
388
     * Returns whether a path is absolute.
389
     *
390
     * @param string $path A path string
391
     *
392
     * @return boolean Returns true if the path is absolute, false if it is
393
     *                 relative or empty
394
     */
395
    public static function isAbsolute($path)
396
    {
397
        return self::isAbsoluteUnix($path) || self::isAbsoluteWindows($path);
398
    }
399
400
    /**
401
     * Returns whether a path is relative.
402
     *
403
     * @param string $path A path string
404
     *
405
     * @return boolean Returns true if the path is relative or empty, false if
406
     *                 it is absolute
407
     * @see https://github.com/laradic/support/blob/master/src/Path.php
408
     */
409
    public static function isRelative($path)
410
    {
411
        return !self::isAbsolute($path);
412
    }
413
414
    /**
415
     * Joins a split file system path.
416
     *
417
     * @param  array|string $paths
418
     *
419
     * @return string
420
     * @see https://github.com/laradic/support/blob/master/src/Path.php
421
     */
422
    public static function join(...$paths) : string
423
    {
424
        foreach ($paths as $key => &$argument) {
425
            if (is_array($argument)) {
426
                $argument = self::join($argument);
427
            }
428
            $argument = self::removeFinalSlash($argument);
429
            if ($key > 0) {
430
                $argument = self::removeStartSlash($argument);
431
            }
432
            $paths[$key] = $argument;
433
        }
434
        return implode(DIRECTORY_SEPARATOR, $paths);
435
    }
436
437
    /**
438
     * Similar to the join() method, but also normalize()'s the result
439
     *
440
     * @param string|array ...$paths
441
     *
442
     * @return string
443
     * @see https://github.com/laradic/support/blob/master/src/Path.php
444
     */
445
    public static function njoin(...$paths) : string
446
    {
447
        return self::canonicalize(self::join($paths));
448
    }
449
450
    /**
451
     * Canonicalizes the given path.
452
     *
453
     * During normalization, all slashes are replaced by forward slashes ("/").
454
     * Furthermore, all "." and ".." segments are removed as far as possible.
455
     * ".." segments at the beginning of relative paths are not removed.
456
     *
457
     * ```php
458
     * echo DirHelper::canonicalize("\webmozart\puli\..\css\style.css");
459
     * // => /webmozart/style.css
460
     *
461
     * echo DirHelper::canonicalize("../css/./style.css");
462
     * // => ../css/style.css
463
     * ```
464
     *
465
     * This method is able to deal with both UNIX and Windows paths.
466
     *
467
     * @param string $path A path string
468
     *
469
     * @return string The canonical path
470
     * @see https://github.com/laradic/support/blob/master/src/Path.php
471
     */
472
    public static function canonicalize($path)
473
    {
474
        $path = (string)$path;
475
        if ('' === $path) {
476
            return '';
477
        }
478
        $path = str_replace('\\', '/', $path);
479
        list ($root, $path) = self::split($path);
480
        $parts = array_filter(explode('/', $path), 'strlen');
481
        $canonicalParts = [];
482
        // Collapse dot folder ., .., i f possible
483
        foreach ($parts as $part) {
484
            self::collapseDotFolder($root, $part, $canonicalParts);
485
        }
486
        // Add the root directory again
487
        return $root . implode('/', $canonicalParts);
488
    }
489
490
    /**
491
     * Collapse dot folder '.', '..', if possible
492
     * @param $root
493
     * @param $part
494
     * @param $canonicalParts
495
     */
496
    protected static function collapseDotFolder($root, $part, &$canonicalParts)
497
    {
498
        if ('.' === $part) {
499
            return;
500
        }
501
        // Collapse ".." with the previous part, if one exists
502
        // Don't collapse ".." if the previous part is also ".."
503
        if ('..' === $part && count($canonicalParts) > 0
504
            && '..' !== $canonicalParts[count($canonicalParts) - 1]
505
        ) {
506
            array_pop($canonicalParts);
507
            return;
508
        }
509
        // Only add ".." prefixes for relative paths
510
        if ('..' !== $part || '' === $root) {
511
            $canonicalParts[] = $part;
512
        }
513
    }
514
515
    /**
516
     * Splits a part into its root directory and the remainder.
517
     *
518
     * If the path has no root directory, an empty root directory will be
519
     * returned.
520
     *
521
     * If the root directory is a Windows style partition, the resulting root
522
     * will always contain a trailing slash.
523
     *
524
     * list ($root, $path) = DirHelpersplit("C:/webmozart")
525
     * // => array("C:/", "webmozart")
526
     *
527
     * list ($root, $path) = DirHelpersplit("C:")
528
     * // => array("C:/", "")
529
     *
530
     * @param string $path The canonical path to split
531
     *
532
     * @return array An array with the root directory and the remaining relative
533
     *               path
534
     * @see https://github.com/laradic/support/blob/master/src/Path.php
535
     */
536
    private static function split($path)
537
    {
538
        if ('' === $path) {
539
            return ['', ''];
540
        }
541
        $root = '';
542
        $length = strlen($path);
543
        // Remove and remember root directory
544
        if ('/' === $path[0]) {
545
            $root = '/';
546
            $path = $length > 1 ? substr($path, 1) : '';
547
        } elseif ($length > 1 && ctype_alpha($path[0]) && ':' === $path[1]) {
548
            if (2 === $length) {
549
                // Windows special case: "C:"
550
                $root = $path . '/';
551
                $path = '';
552
            } elseif ('/' === $path[2]) {
553
                // Windows normal case: "C:/"..
554
                $root = substr($path, 0, 3);
555
                $path = $length > 3 ? substr($path, 3) : '';
556
            }
557
        }
558
        return [$root, $path];
559
    }
560
}
561