Completed
Push — master ( ccffa7...e085db )
by Lorenzo
02:27
created

DirHelper::copy()   D

Complexity

Conditions 9
Paths 14

Size

Total Lines 41
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 41
rs 4.909
c 0
b 0
f 0
cc 9
eloc 29
nc 14
nop 4
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 == '') {
108
            return false;
109
        }
110
        if ($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 == '') {
138
            return false;
139
        }
140
        if ($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 (self::isDotDir($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 string $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::isReadable($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 (self::isDotDir($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 string $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
418
     *
419
     * @return string
420
     * @see https://github.com/laradic/support/blob/master/src/Path.php
421
     */
422
    public static function join() : string
423
    {
424
        $paths = func_get_args();
425
        if (func_num_args() === 1 && is_array($paths[0])) {
426
            $paths = $paths[0];
427
        }
428
        foreach ($paths as $key => &$argument) {
429
            if (is_array($argument)) {
430
                $argument = self::join($argument);
431
            }
432
            $argument = self::removeFinalSlash($argument);
433
            if ($key > 0) {
434
                $argument = self::removeStartSlash($argument);
435
            }
436
        }
437
        return implode(DIRECTORY_SEPARATOR, $paths);
438
    }
439
440
    /**
441
     * Similar to the join() method, but also normalize()'s the result
442
     *
443
     * @param string|array
444
     *
445
     * @return string
446
     * @see https://github.com/laradic/support/blob/master/src/Path.php
447
     */
448
    public static function njoin() : string
449
    {
450
        return self::canonicalize(self::join(func_get_args()));
451
    }
452
453
    /**
454
     * Canonicalizes the given path.
455
     *
456
     * During normalization, all slashes are replaced by forward slashes ("/").
457
     * Furthermore, all "." and ".." segments are removed as far as possible.
458
     * ".." segments at the beginning of relative paths are not removed.
459
     *
460
     * ```php
461
     * echo DirHelper::canonicalize("\webmozart\puli\..\css\style.css");
462
     * // => /webmozart/style.css
463
     *
464
     * echo DirHelper::canonicalize("../css/./style.css");
465
     * // => ../css/style.css
466
     * ```
467
     *
468
     * This method is able to deal with both UNIX and Windows paths.
469
     *
470
     * @param string $path A path string
471
     *
472
     * @return string The canonical path
473
     * @see https://github.com/laradic/support/blob/master/src/Path.php
474
     */
475
    public static function canonicalize($path)
476
    {
477
        $path = (string)$path;
478
        if ('' === $path) {
479
            return '';
480
        }
481
        $path = str_replace('\\', '/', $path);
482
        list ($root, $path) = self::split($path);
483
        $parts = array_filter(explode('/', $path), 'strlen');
484
        $canonicalParts = [];
485
        // Collapse dot folder ., .., i f possible
486
        foreach ($parts as $part) {
487
            self::collapseDotFolder($root, $part, $canonicalParts);
488
        }
489
        // Add the root directory again
490
        return $root . implode('/', $canonicalParts);
491
    }
492
493
    /**
494
     * Collapse dot folder '.', '..', if possible
495
     * @param string $root
496
     * @param $part
497
     * @param $canonicalParts
498
     */
499
    protected static function collapseDotFolder($root, $part, &$canonicalParts)
500
    {
501
        if ('.' === $part) {
502
            return;
503
        }
504
        // Collapse ".." with the previous part, if one exists
505
        // Don't collapse ".." if the previous part is also ".."
506
        if ('..' === $part && count($canonicalParts) > 0
507
            && '..' !== $canonicalParts[count($canonicalParts) - 1]
508
        ) {
509
            array_pop($canonicalParts);
510
            return;
511
        }
512
        // Only add ".." prefixes for relative paths
513
        if ('..' !== $part || '' === $root) {
514
            $canonicalParts[] = $part;
515
        }
516
    }
517
518
    /**
519
     * Splits a part into its root directory and the remainder.
520
     *
521
     * If the path has no root directory, an empty root directory will be
522
     * returned.
523
     *
524
     * If the root directory is a Windows style partition, the resulting root
525
     * will always contain a trailing slash.
526
     *
527
     * list ($root, $path) = DirHelpersplit("C:/webmozart")
528
     * // => array("C:/", "webmozart")
529
     *
530
     * list ($root, $path) = DirHelpersplit("C:")
531
     * // => array("C:/", "")
532
     *
533
     * @param string $path The canonical path to split
534
     *
535
     * @return string[] An array with the root directory and the remaining relative
536
     *               path
537
     * @see https://github.com/laradic/support/blob/master/src/Path.php
538
     */
539
    private static function split($path)
540
    {
541
        if ('' === $path) {
542
            return ['', ''];
543
        }
544
        $root = '';
545
        $length = strlen($path);
546
        // Remove and remember root directory
547
        if ('/' === $path[0]) {
548
            $root = '/';
549
            $path = $length > 1 ? substr($path, 1) : '';
550
        } elseif ($length > 1 && ctype_alpha($path[0]) && ':' === $path[1]) {
551
            if (2 === $length) {
552
                // Windows special case: "C:"
553
                $root = $path . '/';
554
                $path = '';
555
            } elseif ('/' === $path[2]) {
556
                // Windows normal case: "C:/"..
557
                $root = substr($path, 0, 3);
558
                $path = $length > 3 ? substr($path, 3) : '';
559
            }
560
        }
561
        return [$root, $path];
562
    }
563
564
    /**
565
     * Check if a directory is empty in efficent way.
566
     * Check hidden files too.
567
     * @param string $path
568
     * @return bool
569
     */
570
    public static function isDirEmpty(string $path) : bool
571
    {
572
        //che if no such dir, not a dir, not readable
573
        if (!self::isReadable($path)) {
574
            return false;
575
        }
576
577
        $result = true;
578
        $handle = opendir($path);
579
        while (false !== ($entry = readdir($handle))) {
580
            if (!self::isDotDir($entry)) {
581
                $result = false;
582
                break;
583
            }
584
        }
585
        closedir($handle);
586
        return $result;
587
    }
588
589
    /**
590
     * Check if an antry is linux dot dir (i.e.: . or .. )
591
     * @param $entry
592
     * @return bool
593
     */
594
    public static function isDotDir($entry):bool
595
    {
596
        return $entry == "." || $entry == "..";
597
    }
598
599
    /**
600
     * Check if $path is a dir and is readable.
601
     * Return false is you pass a file.
602
     * @param string $path
603
     * @return bool
604
     */
605
    public static function isReadable(string $path):bool
606
    {
607
        return self::isDirSafe($path) && is_readable($path);
608
    }
609
}
610