Passed
Push — master ( 893165...fc69ee )
by Sergey
02:08
created

Filesystem::getMimeType()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 23
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 10
c 0
b 0
f 0
nc 3
nop 2
dl 0
loc 23
rs 9.9332
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Filesystem Component
7
 * Founded by Sergey Romanenko and maintained by Community.
8
 */
9
10
namespace Flextype\Component\Filesystem;
11
12
use FilesystemIterator;
13
use RecursiveDirectoryIterator;
14
use RecursiveIteratorIterator;
15
use SplFileInfo;
16
use const FILEINFO_MIME_TYPE;
17
use const PATHINFO_EXTENSION;
18
use function array_filter;
19
use function chmod;
20
use function clearstatcache;
21
use function fileperms;
22
use function filetype;
23
use function finfo_close;
24
use function finfo_file;
25
use function finfo_open;
26
use function function_exists;
27
use function is_dir;
28
use function octdec;
29
use function pathinfo;
30
use function preg_match;
31
use function scandir;
32
use function sprintf;
33
use function substr;
34
use function unlink;
35
use function reset;
36
37
class Filesystem
38
{
39
    /**
40
     * Permissions
41
     *
42
     * @var array
43
     */
44
    protected static $permissions = [
45
        'file' => [
46
            'public'  => 0644,
47
            'private' => 0600,
48
        ],
49
        'dir'  => [
50
            'public'  => 0755,
51
            'private' => 0700,
52
        ],
53
    ];
54
55
    /**
56
     * Mime type list
57
     *
58
     * @var array
59
     */
60
    public static $mime_types = [
61
        'aac'        => 'audio/aac',
62
        'atom'       => 'application/atom+xml',
63
        'avi'        => 'video/avi',
64
        'bmp'        => 'image/x-ms-bmp',
65
        'c'          => 'text/x-c',
66
        'class'      => 'application/octet-stream',
67
        'css'        => 'text/css',
68
        'csv'        => 'text/csv',
69
        'deb'        => 'application/x-deb',
70
        'dll'        => 'application/x-msdownload',
71
        'dmg'        => 'application/x-apple-diskimage',
72
        'doc'        => 'application/msword',
73
        'docx'       => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
74
        'exe'        => 'application/octet-stream',
75
        'flv'        => 'video/x-flv',
76
        'gif'        => 'image/gif',
77
        'gz'         => 'application/x-gzip',
78
        'h'          => 'text/x-c',
79
        'htm'        => 'text/html',
80
        'html'       => 'text/html',
81
        'ini'        => 'text/plain',
82
        'jar'        => 'application/java-archive',
83
        'java'       => 'text/x-java',
84
        'jpeg'       => 'image/jpeg',
85
        'jpg'        => 'image/jpeg',
86
        'js'         => 'text/javascript',
87
        'json'       => 'application/json',
88
        'mid'        => 'audio/midi',
89
        'midi'       => 'audio/midi',
90
        'mka'        => 'audio/x-matroska',
91
        'mkv'        => 'video/x-matroska',
92
        'mp3'        => 'audio/mpeg',
93
        'mp4'        => 'application/mp4',
94
        'mpeg'       => 'video/mpeg',
95
        'mpg'        => 'video/mpeg',
96
        'odt'        => 'application/vnd.oasis.opendocument.text',
97
        'ogg'        => 'audio/ogg',
98
        'pdf'        => 'application/pdf',
99
        'php'        => 'text/x-php',
100
        'png'        => 'image/png',
101
        'psd'        => 'image/vnd.adobe.photoshop',
102
        'py'         => 'application/x-python',
103
        'ra'         => 'audio/vnd.rn-realaudio',
104
        'ram'        => 'audio/vnd.rn-realaudio',
105
        'rar'        => 'application/x-rar-compressed',
106
        'rss'        => 'application/rss+xml',
107
        'safariextz' => 'application/x-safari-extension',
108
        'sh'         => 'text/x-shellscript',
109
        'shtml'      => 'text/html',
110
        'swf'        => 'application/x-shockwave-flash',
111
        'tar'        => 'application/x-tar',
112
        'tif'        => 'image/tiff',
113
        'tiff'       => 'image/tiff',
114
        'torrent'    => 'application/x-bittorrent',
115
        'txt'        => 'text/plain',
116
        'wav'        => 'audio/wav',
117
        'webp'       => 'image/webp',
118
        'wma'        => 'audio/x-ms-wma',
119
        'xls'        => 'application/vnd.ms-excel',
120
        'xml'        => 'text/xml',
121
        'zip'        => 'application/zip',
122
    ];
123
124
    /**
125
     * List contents of a directory.
126
     *
127
     * @param string $directory The directory to list.
128
     * @param bool   $recursive Whether to list recursively.
129
     *
130
     * @return array A list of file metadata.
131
     */
132
    public static function listContents(string $directory = '', bool $recursive = false) : array
133
    {
134
        $result = [];
135
136
        if (! is_dir($directory)) {
137
            return [];
138
        }
139
140
        $iterator = $recursive ? self::getRecursiveDirectoryIterator($directory) : self::getDirectoryIterator($directory);
141
142
        foreach ($iterator as $file) {
143
            $path = self::getFilePath($file);
144
145
            if (preg_match('#(^|/|\\\\)\.{1,2}$#', $path)) {
146
                continue;
147
            }
148
149
            $result[] = self::normalizeFileInfo($file);
150
        }
151
152
        return array_filter($result);
153
    }
154
155
    /**
156
     * Get directory timestamp
157
     *
158
     * @param string $directory The directory
159
     *
160
     * @return int directory timestamp
161
     */
162
    public static function getDirTimestamp(string $directory) : int
163
    {
164
        $_directory  = new RecursiveDirectoryIterator(
165
            $directory,
166
            FilesystemIterator::KEY_AS_PATHNAME |
167
            FilesystemIterator::CURRENT_AS_FILEINFO |
168
            FilesystemIterator::SKIP_DOTS
169
        );
170
        $_iterator   = new RecursiveIteratorIterator(
171
            $_directory,
172
            RecursiveIteratorIterator::SELF_FIRST
173
        );
174
        $_resultFile = $_iterator->current();
175
        foreach ($_iterator as $file) {
176
            if ($file->getMtime() <= $_resultFile->getMtime()) {
177
                continue;
178
            }
179
180
            $_resultFile = $file;
181
        }
182
183
        return $_resultFile->getMtime();
184
    }
185
186
    /**
187
     * Returns the mime type of a file. Returns false if the mime type is not found.
188
     *
189
     * @param  string $file  Full path to the file
190
     * @param  bool   $guess Set to false to disable mime type guessing
191
     *
192
     * @return mixed
193
     */
194
    public static function getMimeType(string $file, bool $guess = true)
195
    {
196
        // Get mime using the file information functions
197
        if (function_exists('finfo_open')) {
198
            $info = finfo_open(FILEINFO_MIME_TYPE);
199
200
            $mime = finfo_file($info, $file);
201
202
            finfo_close($info);
203
204
            return $mime;
205
        }
206
207
        // Just guess mime by using the file extension
208
        if ($guess === true) {
209
            $mime_types = self::$mime_types;
210
211
            $extension = pathinfo($file, PATHINFO_EXTENSION);
212
213
            return $mime_types[$extension] ?? false;
214
        }
215
216
        return false;
217
    }
218
219
    /**
220
     * Get a file's timestamp.
221
     *
222
     * @param string $path The path to the file.
223
     *
224
     * @return string|false The timestamp or false on failure.
225
     */
226
    public static function getTimestamp(string $path)
227
    {
228
        return self::getMetadata($path)['timestamp'];
229
    }
230
231
    /**
232
     * Get a file's size.
233
     *
234
     * @param string $path The path to the file.
235
     *
236
     * @return int|false The file size or false on failure.
237
     */
238
    public static function getSize(string $path)
239
    {
240
        return self::getMetadata($path)['size'];
241
    }
242
243
    /**
244
     * Get a file's metadata.
245
     *
246
     * @param string $path The path to the file.
247
     *
248
     * @return array|false The file metadata or false on failure.
249
     */
250
    public static function getMetadata(string $path)
251
    {
252
        $info = new SplFileInfo($path);
253
254
        return self::normalizeFileInfo($info);
255
    }
256
257
    /**
258
     * Get a file's visibility.
259
     *
260
     * @param string $path The path to the file.
261
     *
262
     * @return string|false The visibility (public|private) or false on failure.
263
     */
264
    public static function getVisibility(string $path)
265
    {
266
        clearstatcache(false, $path);
267
        $permissions = octdec(substr(sprintf('%o', fileperms($path)), -4));
268
269
        return $permissions & 0044 ? 'public' : 'private';
270
    }
271
272
    /**
273
     * Set the visibility for a file.
274
     *
275
     * @param string $path       The path to the file.
276
     * @param string $visibility One of 'public' or 'private'.
277
     *
278
     * @return bool True on success, false on failure.
279
     */
280
    public static function setVisibility(string $path, string $visibility) : bool
281
    {
282
        $type    = is_dir($path) ? 'dir' : 'file';
283
        $success = chmod($path, self::$permissions[$type][$visibility]);
284
285
        return $success !== false;
286
    }
287
288
    /**
289
     * Delete a file.
290
     *
291
     * @return bool True on success, false on failure.
292
     */
293
    public static function delete(string $path) : bool
294
    {
295
        return @unlink($path);
296
    }
297
298
    /**
299
     * Delete a directory.
300
     *
301
     * @return bool True on success, false on failure.
302
     */
303
    public static function deleteDir(string $dirname) : bool
304
    {
305
        if (! is_dir($dirname)) {
306
            return false;
307
        }
308
309
        // Delete dir
310
        if (is_dir($dirname)) {
311
            $ob = scandir($dirname);
312
            foreach ($ob as $o) {
313
                if ($o === '.' || $o === '..') {
314
                    continue;
315
                }
316
317
                if (filetype($dirname . '/' . $o) === 'dir') {
318
                    self::deleteDir($dirname . '/' . $o);
319
                } else {
320
                    unlink($dirname . '/' . $o);
321
                }
322
            }
323
        }
324
325
        reset($ob);
326
        rmdir($dirname);
327
328
        return true;
329
    }
330
331
    /**
332
     * Create a directory.
333
     *
334
     * @param string $dirname    The name of the new directory.
335
     * @param string $visibility Visibility
336
     *
337
     * @return bool True on success, false on failure.
338
     */
339
    public static function createDir(string $dirname, string $visibility = 'public') : bool
340
    {
341
        $umask = umask(0);
342
343
        if (! is_dir($dirname) && ! mkdir($dirname, self::$permissions['dir'][$visibility], true)) {
344
            return false;
345
        }
346
347
        umask($umask);
348
349
        return true;
350
    }
351
352
    /**
353
     * Copy a file(s).
354
     *
355
     * @param string $path      Path to the existing file.
356
     * @param string $newpath   The new path of the file.
357
     * @param bool   $recursive Recursive copy files.
358
     *
359
     * @return bool True on success, false on failure.
360
     */
361
    public static function copy(string $path, string $newpath, bool $recursive = false) : bool
362
    {
363
        if (! $recursive) {
364
            return copy($path, $newpath);
365
        }
366
367
        if (! self::has($newpath)) {
368
            mkdir($newpath);
369
        }
370
371
        $splFileInfoArr = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::SELF_FIRST);
372
373
        foreach ($splFileInfoArr as $fullPath => $splFileinfo) {
374
            //skip . ..
375
            if (in_array($splFileinfo->getBasename(), ['.', '..'])) {
376
                continue;
377
            }
378
379
            //get relative path of source file or folder
380
            $_path = str_replace($path, '', $splFileinfo->getPathname());
381
382
            if ($splFileinfo->isDir()) {
383
                mkdir($newpath . '/' . $_path);
384
            } else {
385
                copy($fullPath, $newpath . '/' . $_path);
386
            }
387
        }
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return boolean. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
388
    }
389
390
    /**
391
     * Rename a file.
392
     *
393
     * @param string $path    Path to the existing file.
394
     * @param string $newpath The new path of the file.
395
     *
396
     * @return bool True on success, false on failure.
397
     */
398
    public static function rename(string $path, string $newpath) : bool
399
    {
400
        return rename($path, $newpath);
401
    }
402
403
    /**
404
     * Write a file.
405
     *
406
     * @param string $path       The path of the new file.
407
     * @param string $contents   The file contents.
408
     * @param string $visibility An optional configuration array.
409
     * @param int    $flags      Flags
410
     *
411
     * @return bool True on success, false on failure.
412
     */
413
    public static function write(string $path, string $contents, string $visibility = 'public', int $flags = LOCK_EX) : bool
414
    {
415
        if (file_put_contents($path, $contents, $flags) === false) {
416
            return false;
417
        }
418
419
        self::setVisibility($path, $visibility);
420
421
        return true;
422
    }
423
424
    /**
425
     * Check whether a file exists.
426
     */
427
    public static function has(string $path) : bool
428
    {
429
        return file_exists($path);
430
    }
431
432
    /**
433
     * Read a file.
434
     *
435
     * @param string $path The path to the file.
436
     *
437
     * @return string|false The file contents or false on failure.
438
     */
439
    public static function read(string $path)
440
    {
441
        $contents = file_get_contents($path);
442
443
        if ($contents === false) {
444
            return false;
445
        }
446
447
        return $contents;
448
    }
449
450
    /**
451
     * Normalize the file info.
452
     *
453
     * @return array|void
454
     */
455
    protected static function normalizeFileInfo(SplFileInfo $file)
456
    {
457
        return self::mapFileInfo($file);
458
    }
459
460
    /**
461
     * @return array
462
     */
463
    protected static function mapFileInfo(SplFileInfo $file) : array
464
    {
465
        $normalized = [
466
            'type' => $file->getType(),
467
            'path' => self::getFilePath($file),
468
        ];
469
470
        $normalized['timestamp'] = $file->getMTime();
471
472
        if ($normalized['type'] === 'file') {
473
            $normalized['size']      = $file->getSize();
474
            $normalized['filename']  = $file->getFilename();
475
            $normalized['basename']  = $file->getBasename('.' . $file->getExtension());
476
            $normalized['extension'] = $file->getExtension();
477
        }
478
479
        if ($normalized['type'] === 'dir') {
480
            $normalized['dirname'] = $file->getFilename();
481
        }
482
483
        return $normalized;
484
    }
485
486
    /**
487
     * Get the normalized path from a SplFileInfo object.
488
     */
489
    protected static function getFilePath(SplFileInfo $file) : string
490
    {
491
        $path = $file->getPathname();
492
493
        $path = trim(str_replace('\\', '/', $path));
494
495
        return $path;
496
    }
497
498
    protected static function getRecursiveDirectoryIterator(string $path, int $mode = RecursiveIteratorIterator::SELF_FIRST) : RecursiveIteratorIterator
499
    {
500
        return new RecursiveIteratorIterator(
501
            new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
502
            $mode
503
        );
504
    }
505
506
    protected static function getDirectoryIterator(string $path) : \DirectoryIterator
507
    {
508
        return new \DirectoryIterator($path);
509
    }
510
}
511