Completed
Push — master ( 074443...b3b676 )
by Alexey
08:58 queued 11s
created

FilesUtil::symlink()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 3
c 0
b 0
f 0
nc 2
nop 3
dl 0
loc 7
ccs 0
cts 6
cp 0
crap 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 24
    public static function isEmptyDir($dir)
26
    {
27 24
        if (!is_readable($dir)) {
28
            return false;
29
        }
30
31 24
        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 36
    public static function removeDir($dir)
40
    {
41 36
        $files = new \RecursiveIteratorIterator(
42 36
            new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS),
43 36
            \RecursiveIteratorIterator::CHILD_FIRST
44
        );
45
46 36
        /** @var \SplFileInfo $fileInfo */
47 32
        foreach ($files as $fileInfo) {
48 32
            $function = ($fileInfo->isDir() ? 'rmdir' : 'unlink');
49
            $function($fileInfo->getPathname());
50 36
        }
51 36
        rmdir($dir);
52
    }
53
54
    /**
55
     * Convert glob pattern to regex pattern.
56
     *
57
     * @param string $globPattern
58
     *
59
     * @return string
60 1
     */
61
    public static function convertGlobToRegEx($globPattern)
62
    {
63 1
        // Remove beginning and ending * globs because they're useless
64 1
        $globPattern = trim($globPattern, '*');
65 1
        $escaping = false;
66 1
        $inCurrent = 0;
67 1
        $chars = str_split($globPattern);
68
        $regexPattern = '';
69 1
70 1
        foreach ($chars as $currentChar) {
71 1
            switch ($currentChar) {
72
                case '*':
73
                    $regexPattern .= ($escaping ? '\\*' : '.*');
74
                    $escaping = false;
75
                    break;
76 1
77
                case '?':
78
                    $regexPattern .= ($escaping ? '\\?' : '.');
79
                    $escaping = false;
80
                    break;
81 1
82 1
                case '.':
83 1
                case '(':
84 1
                case ')':
85 1
                case '+':
86 1
                case '|':
87 1
                case '^':
88 1
                case '$':
89 1
                case '@':
90 1
                case '%':
91 1
                    $regexPattern .= '\\' . $currentChar;
92 1
                    $escaping = false;
93
                    break;
94 1
95
                case '\\':
96
                    if ($escaping) {
97
                        $regexPattern .= '\\\\';
98
                        $escaping = false;
99
                    } else {
100
                        $escaping = true;
101
                    }
102
                    break;
103 1
104 1
                case '{':
105
                    if ($escaping) {
106
                        $regexPattern .= '\\{';
107 1
                    } else {
108 1
                        $regexPattern = '(';
109
                        $inCurrent++;
110 1
                    }
111 1
                    $escaping = false;
112
                    break;
113 1
114 1
                case '}':
115 1
                    if ($inCurrent > 0 && !$escaping) {
116 1
                        $regexPattern .= ')';
117
                        $inCurrent--;
118
                    } elseif ($escaping) {
119
                        $regexPattern = '\\}';
120
                    } else {
121
                        $regexPattern = '}';
122 1
                    }
123 1
                    $escaping = false;
124
                    break;
125 1
126 1
                case ',':
127 1
                    if ($inCurrent > 0 && !$escaping) {
128
                        $regexPattern .= '|';
129
                    } elseif ($escaping) {
130
                        $regexPattern .= '\\,';
131
                    } else {
132
                        $regexPattern = ',';
133 1
                    }
134
                    break;
135 1
                default:
136 1
                    $escaping = false;
137
                    $regexPattern .= $currentChar;
138
            }
139
        }
140 1
141
        return $regexPattern;
142
    }
143
144
    /**
145
     * Search files.
146
     *
147
     * @param string $inputDir
148
     * @param bool   $recursive
149
     * @param array  $ignoreFiles
150
     *
151
     * @return array Searched file list
152
     */
153
    public static function fileSearchWithIgnore($inputDir, $recursive = true, array $ignoreFiles = [])
154
    {
155
        if ($recursive) {
156
            $directoryIterator = new \RecursiveDirectoryIterator($inputDir);
157
158
            if (!empty($ignoreFiles)) {
159
                $directoryIterator = new IgnoreFilesRecursiveFilterIterator($directoryIterator, $ignoreFiles);
160
            }
161
            $iterator = new \RecursiveIteratorIterator($directoryIterator);
162
        } else {
163
            $directoryIterator = new \DirectoryIterator($inputDir);
164
165
            if (!empty($ignoreFiles)) {
166
                $directoryIterator = new IgnoreFilesFilterIterator($directoryIterator, $ignoreFiles);
167
            }
168
            $iterator = new \IteratorIterator($directoryIterator);
169
        }
170
171
        $fileList = [];
172
173
        foreach ($iterator as $file) {
174
            if ($file instanceof \SplFileInfo) {
175
                $fileList[] = $file->getPathname();
176
            }
177
        }
178
179
        return $fileList;
180
    }
181
182
    /**
183
     * Search files from glob pattern.
184
     *
185
     * @param string $globPattern
186
     * @param int    $flags
187
     * @param bool   $recursive
188
     *
189
     * @return array Searched file list
190 3
     */
191
    public static function globFileSearch($globPattern, $flags = 0, $recursive = true)
192 3
    {
193 3
        $flags = (int) $flags;
194 3
        $recursive = (bool) $recursive;
195
        $files = glob($globPattern, $flags);
196 3
197 1
        if (!$recursive) {
198
            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...
199
        }
200 2
201
        foreach (glob(\dirname($globPattern) . '/*', \GLOB_ONLYDIR | \GLOB_NOSORT) as $dir) {
202
            // Unpacking the argument via ... is supported starting from php 5.6 only
203 2
            /** @noinspection SlowArrayOperationsInLoopInspection */
204
            $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

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