Passed
Push — master ( 3b7697...25d5dd )
by Alexey
03:48 queued 29s
created

FilesUtil::removeDir()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
c 0
b 0
f 0
nc 3
nop 1
dl 0
loc 12
rs 10
1
<?php
2
3
namespace PhpZip\Util;
4
5
use PhpZip\Util\Iterator\IgnoreFilesFilterIterator;
6
use PhpZip\Util\Iterator\IgnoreFilesRecursiveFilterIterator;
7
8
/**
9
 * Files util.
10
 *
11
 * @author Ne-Lexa [email protected]
12
 * @license MIT
13
 *
14
 * @internal
15
 */
16
final class FilesUtil
17
{
18
    /**
19
     * Is empty directory.
20
     *
21
     * @param string $dir Directory
22
     *
23
     * @return bool
24
     */
25
    public static function isEmptyDir($dir)
26
    {
27
        if (!is_readable($dir)) {
28
            return false;
29
        }
30
31
        return \count(scandir($dir)) === 2;
0 ignored issues
show
Bug introduced by
It seems like scandir($dir) can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

31
        return \count(/** @scrutinizer ignore-type */ scandir($dir)) === 2;
Loading history...
32
    }
33
34
    /**
35
     * Remove recursive directory.
36
     *
37
     * @param string $dir directory path
38
     */
39
    public static function removeDir($dir)
40
    {
41
        $files = new \RecursiveIteratorIterator(
42
            new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS),
43
            \RecursiveIteratorIterator::CHILD_FIRST
44
        );
45
46
        foreach ($files as $fileInfo) {
47
            $function = ($fileInfo->isDir() ? 'rmdir' : 'unlink');
48
            $function($fileInfo->getRealPath());
49
        }
50
        rmdir($dir);
51
    }
52
53
    /**
54
     * Convert glob pattern to regex pattern.
55
     *
56
     * @param string $globPattern
57
     *
58
     * @return string
59
     */
60
    public static function convertGlobToRegEx($globPattern)
61
    {
62
        // Remove beginning and ending * globs because they're useless
63
        $globPattern = trim($globPattern, '*');
64
        $escaping = false;
65
        $inCurrent = 0;
66
        $chars = str_split($globPattern);
67
        $regexPattern = '';
68
69
        foreach ($chars as $currentChar) {
70
            switch ($currentChar) {
71
                case '*':
72
                    $regexPattern .= ($escaping ? '\\*' : '.*');
73
                    $escaping = false;
74
                    break;
75
76
                case '?':
77
                    $regexPattern .= ($escaping ? '\\?' : '.');
78
                    $escaping = false;
79
                    break;
80
81
                case '.':
82
                case '(':
83
                case ')':
84
                case '+':
85
                case '|':
86
                case '^':
87
                case '$':
88
                case '@':
89
                case '%':
90
                    $regexPattern .= '\\' . $currentChar;
91
                    $escaping = false;
92
                    break;
93
94
                case '\\':
95
                    if ($escaping) {
96
                        $regexPattern .= '\\\\';
97
                        $escaping = false;
98
                    } else {
99
                        $escaping = true;
100
                    }
101
                    break;
102
103
                case '{':
104
                    if ($escaping) {
105
                        $regexPattern .= '\\{';
106
                    } else {
107
                        $regexPattern = '(';
108
                        $inCurrent++;
109
                    }
110
                    $escaping = false;
111
                    break;
112
113
                case '}':
114
                    if ($inCurrent > 0 && !$escaping) {
115
                        $regexPattern .= ')';
116
                        $inCurrent--;
117
                    } elseif ($escaping) {
118
                        $regexPattern = '\\}';
119
                    } else {
120
                        $regexPattern = '}';
121
                    }
122
                    $escaping = false;
123
                    break;
124
125
                case ',':
126
                    if ($inCurrent > 0 && !$escaping) {
127
                        $regexPattern .= '|';
128
                    } elseif ($escaping) {
129
                        $regexPattern .= '\\,';
130
                    } else {
131
                        $regexPattern = ',';
132
                    }
133
                    break;
134
                default:
135
                    $escaping = false;
136
                    $regexPattern .= $currentChar;
137
            }
138
        }
139
140
        return $regexPattern;
141
    }
142
143
    /**
144
     * Search files.
145
     *
146
     * @param string $inputDir
147
     * @param bool   $recursive
148
     * @param array  $ignoreFiles
149
     *
150
     * @return array Searched file list
151
     */
152
    public static function fileSearchWithIgnore($inputDir, $recursive = true, array $ignoreFiles = [])
153
    {
154
        if ($recursive) {
155
            $directoryIterator = new \RecursiveDirectoryIterator($inputDir);
156
157
            if (!empty($ignoreFiles)) {
158
                $directoryIterator = new IgnoreFilesRecursiveFilterIterator($directoryIterator, $ignoreFiles);
159
            }
160
            $iterator = new \RecursiveIteratorIterator($directoryIterator);
161
        } else {
162
            $directoryIterator = new \DirectoryIterator($inputDir);
163
164
            if (!empty($ignoreFiles)) {
165
                $directoryIterator = new IgnoreFilesFilterIterator($directoryIterator, $ignoreFiles);
166
            }
167
            $iterator = new \IteratorIterator($directoryIterator);
168
        }
169
170
        $fileList = [];
171
172
        foreach ($iterator as $file) {
173
            if ($file instanceof \SplFileInfo) {
174
                $fileList[] = $file->getPathname();
175
            }
176
        }
177
178
        return $fileList;
179
    }
180
181
    /**
182
     * Search files from glob pattern.
183
     *
184
     * @param string $globPattern
185
     * @param int    $flags
186
     * @param bool   $recursive
187
     *
188
     * @return array Searched file list
189
     */
190
    public static function globFileSearch($globPattern, $flags = 0, $recursive = true)
191
    {
192
        $flags = (int) $flags;
193
        $recursive = (bool) $recursive;
194
        $files = glob($globPattern, $flags);
195
196
        if (!$recursive) {
197
            return $files;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $files could also return false which is incompatible with the documented return type array. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
198
        }
199
200
        foreach (glob(\dirname($globPattern) . '/*', \GLOB_ONLYDIR | \GLOB_NOSORT) as $dir) {
201
            // Unpacking the argument via ... is supported starting from php 5.6 only
202
            /** @noinspection SlowArrayOperationsInLoopInspection */
203
            $files = array_merge($files, self::globFileSearch($dir . '/' . basename($globPattern), $flags, $recursive));
0 ignored issues
show
Bug introduced by
It seems like $files can also be of type false; however, parameter $array1 of array_merge() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

203
            $files = array_merge(/** @scrutinizer ignore-type */ $files, self::globFileSearch($dir . '/' . basename($globPattern), $flags, $recursive));
Loading history...
204
        }
205
206
        return $files;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $files could also return false which is incompatible with the documented return type array. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
207
    }
208
209
    /**
210
     * Search files from regex pattern.
211
     *
212
     * @param string $folder
213
     * @param string $pattern
214
     * @param bool   $recursive
215
     *
216
     * @return array Searched file list
217
     */
218
    public static function regexFileSearch($folder, $pattern, $recursive = true)
219
    {
220
        if ($recursive) {
221
            $directoryIterator = new \RecursiveDirectoryIterator($folder);
222
            $iterator = new \RecursiveIteratorIterator($directoryIterator);
223
        } else {
224
            $directoryIterator = new \DirectoryIterator($folder);
225
            $iterator = new \IteratorIterator($directoryIterator);
226
        }
227
228
        $regexIterator = new \RegexIterator($iterator, $pattern, \RegexIterator::MATCH);
229
        $fileList = [];
230
231
        foreach ($regexIterator as $file) {
232
            if ($file instanceof \SplFileInfo) {
233
                $fileList[] = $file->getPathname();
234
            }
235
        }
236
237
        return $fileList;
238
    }
239
240
    /**
241
     * Convert bytes to human size.
242
     *
243
     * @param int         $size Size bytes
244
     * @param string|null $unit Unit support 'GB', 'MB', 'KB'
245
     *
246
     * @return string
247
     */
248
    public static function humanSize($size, $unit = null)
249
    {
250
        if (($unit === null && $size >= 1 << 30) || $unit === 'GB') {
251
            return number_format($size / (1 << 30), 2) . 'GB';
252
        }
253
254
        if (($unit === null && $size >= 1 << 20) || $unit === 'MB') {
255
            return number_format($size / (1 << 20), 2) . 'MB';
256
        }
257
258
        if (($unit === null && $size >= 1 << 10) || $unit === 'KB') {
259
            return number_format($size / (1 << 10), 2) . 'KB';
260
        }
261
262
        return number_format($size) . ' bytes';
263
    }
264
265
    /**
266
     * Normalizes zip path.
267
     *
268
     * @param string $path Zip path
269
     *
270
     * @return string
271
     */
272
    public static function normalizeZipPath($path)
273
    {
274
        return implode(
275
            '/',
276
            array_filter(
277
                explode('/', (string) $path),
278
                static function ($part) {
279
                    return $part !== '.' && $part !== '..';
280
                }
281
            )
282
        );
283
    }
284
285
    /**
286
     * Returns whether the file path is an absolute path.
287
     *
288
     * @param string $file A file path
289
     *
290
     * @return bool
291
     *
292
     * @see source symfony filesystem component
293
     */
294
    public static function isAbsolutePath($file)
295
    {
296
        return strspn($file, '/\\', 0, 1)
297
            || (
298
                \strlen($file) > 3 && ctype_alpha($file[0])
299
                && $file[1] === ':'
300
                && strspn($file, '/\\', 2, 1)
301
            )
302
            || parse_url($file, \PHP_URL_SCHEME) !== null;
303
    }
304
305
    /**
306
     * @param string $linkPath
307
     * @param string $target
308
     *
309
     * @return bool
310
     */
311
    public static function symlink($target, $linkPath)
312
    {
313
        if (\DIRECTORY_SEPARATOR === '\\') {
314
            $linkPath = str_replace('/', '\\', $linkPath);
315
            $target = str_replace('/', '\\', $target);
316
            $abs = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $abs is dead and can be removed.
Loading history...
317
318
            if (!self::isAbsolutePath($target)) {
319
                $abs = realpath(\dirname($linkPath) . \DIRECTORY_SEPARATOR . $target);
320
321
                if (\is_string($abs)) {
0 ignored issues
show
introduced by
The condition is_string($abs) is always true.
Loading history...
322
                    $target = $abs;
323
                }
324
            }
325
        }
326
327
        if (!symlink($target, $linkPath)) {
328
            if (\DIRECTORY_SEPARATOR === '\\' && is_file($target)) {
329
                return copy($target, $linkPath);
330
            }
331
332
            return false;
333
        }
334
335
        return true;
336
    }
337
338
    /**
339
     * @param string $file
340
     *
341
     * @return bool
342
     */
343
    public static function isBadCompressionFile($file)
344
    {
345
        $badCompressFileExt = [
346
            'dic',
347
            'dng',
348
            'f4v',
349
            'flipchart',
350
            'h264',
351
            'lrf',
352
            'mobi',
353
            'mts',
354
            'nef',
355
            'pspimage',
356
        ];
357
358
        $ext = strtolower(pathinfo($file, \PATHINFO_EXTENSION));
359
360
        if (\in_array($ext, $badCompressFileExt, true)) {
361
            return true;
362
        }
363
364
        $mimeType = self::getMimeTypeFromFile($file);
365
366
        return self::isBadCompressionMimeType($mimeType);
367
    }
368
369
    /**
370
     * @param string $mimeType
371
     *
372
     * @return bool
373
     */
374
    public static function isBadCompressionMimeType($mimeType)
375
    {
376
        static $badDeflateCompMimeTypes = [
377
            'application/epub+zip',
378
            'application/gzip',
379
            'application/vnd.debian.binary-package',
380
            'application/vnd.oasis.opendocument.graphics',
381
            'application/vnd.oasis.opendocument.presentation',
382
            'application/vnd.oasis.opendocument.text',
383
            'application/vnd.oasis.opendocument.text-master',
384
            'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
385
            'application/vnd.rn-realmedia',
386
            'application/x-7z-compressed',
387
            'application/x-arj',
388
            'application/x-bzip2',
389
            'application/x-hwp',
390
            'application/x-lzip',
391
            'application/x-lzma',
392
            'application/x-ms-reader',
393
            'application/x-rar',
394
            'application/x-rpm',
395
            'application/x-stuffit',
396
            'application/x-tar',
397
            'application/x-xz',
398
            'application/zip',
399
            'application/zlib',
400
            'audio/flac',
401
            'audio/mpeg',
402
            'audio/ogg',
403
            'audio/vnd.dolby.dd-raw',
404
            'audio/webm',
405
            'audio/x-ape',
406
            'audio/x-hx-aac-adts',
407
            'audio/x-m4a',
408
            'audio/x-m4a',
409
            'audio/x-wav',
410
            'image/gif',
411
            'image/heic',
412
            'image/jp2',
413
            'image/jpeg',
414
            'image/png',
415
            'image/vnd.djvu',
416
            'image/webp',
417
            'image/x-canon-cr2',
418
            'video/ogg',
419
            'video/webm',
420
            'video/x-matroska',
421
            'video/x-ms-asf',
422
            'x-epoc/x-sisx-app',
423
        ];
424
425
        if (\in_array($mimeType, $badDeflateCompMimeTypes, true)) {
426
            return true;
427
        }
428
429
        return false;
430
    }
431
432
    /**
433
     * @param string $file
434
     *
435
     * @return string
436
     *
437
     * @noinspection PhpComposerExtensionStubsInspection
438
     */
439
    public static function getMimeTypeFromFile($file)
440
    {
441
        if (\function_exists('mime_content_type')) {
442
            return mime_content_type($file);
443
        }
444
445
        return 'application/octet-stream';
446
    }
447
448
    /**
449
     * @param string $contents
450
     *
451
     * @return string
452
     * @noinspection PhpComposerExtensionStubsInspection
453
     */
454
    public static function getMimeTypeFromString($contents)
455
    {
456
        $contents = (string) $contents;
457
        $finfo = new \finfo(\FILEINFO_MIME);
458
        $mimeType = $finfo->buffer($contents);
459
460
        if ($mimeType === false) {
461
            $mimeType = 'application/octet-stream';
462
        }
463
464
        return explode(';', $mimeType)[0];
465
    }
466
}
467