Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.41% covered (success)
93.41%
85 / 91
62.50% covered (warning)
62.50%
5 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
FileUtils
93.41% covered (success)
93.41%
85 / 91
62.50% covered (warning)
62.50%
5 / 8
55.87
0.00% covered (danger)
0.00%
0 / 1
 __construct
n/a
0 / 0
n/a
0 / 0
1
 getExtensionType
96.15% covered (success)
96.15%
25 / 26
0.00% covered (danger)
0.00%
0 / 1
13
 absolute
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
10
 changeFileEncoding
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 permissionToOctal
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
6
 mb_pathinfo
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
15
 isAccessible
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
4.07
 isPermittedPath
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 makeSafeFileName
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Jingga
4 *
5 * PHP Version 8.1
6 *
7 * @package   phpOMS\System\File
8 * @copyright Dennis Eichhorn
9 * @license   OMS License 2.0
10 * @version   1.0.0
11 * @link      https://jingga.app
12 */
13declare(strict_types=1);
14
15namespace phpOMS\System\File;
16
17/**
18 * Path exception class.
19 *
20 * @package phpOMS\System\File
21 * @license OMS License 2.0
22 * @link    https://jingga.app
23 * @since   1.0.0
24 */
25final class FileUtils
26{
27    public const CODE_EXTENSION = ['cpp', 'c', 'h', 'hpp', 'cs', 'css', 'scss', 'htm', 'html', 'js', 'java', 'sh', 'vb', 'php', 'rb', 'rs', 'ts', 'swift', 'class', 'htaccess', 'sql', 'py', 'bat', 'xml'];
28
29    public const TEXT_EXTENSION = ['log', 'txt', 'md', 'csv', 'tex', 'latex', 'cfg', 'json', 'config', 'conf', 'ini', 'yaml', 'yml'];
30
31    public const WORD_EXTENSION = ['doc', 'docx', 'rtf', 'odt'];
32
33    public const PRESENTATION_EXTENSION = ['ppt', 'pptx', 'pps', 'odp', 'key'];
34
35    public const PDF_EXTENSION = ['pdf'];
36
37    public const ARCHIVE_EXTENSION = ['zip', '7z', 'rar', 'tar', 'gz', 'z', 'deb', 'rpm', 'pkg'];
38
39    public const AUDIO_EXTENSION = ['mp3', 'wav', 'wma', 'ogg'];
40
41    public const VIDEO_EXTENSION = ['mp4', 'flv', 'vob', 'wmv', 'swf', 'mpg', 'mpeg', 'mov', 'mkv', 'h264', 'avi'];
42
43    public const SPREADSHEET_EXTENSION = ['xls', 'xlsx', 'xlsm', 'xlr', 'ods'];
44
45    public const IMAGE_EXTENSION = ['png', 'gif', 'jpg', 'jpeg', 'tif', 'tiff', 'bmp', 'svg', 'ico'];
46
47    public const DIRECTORY = ['collection', '/'];
48
49    public const SYSTEM_EXTENSION = ['bak', 'dll', 'sys', 'tmp', 'msi', 'so', 'exe', 'bin', 'iso'];
50
51    public const REFERENCE = ['reference'];
52
53    /**
54     * Constructor.
55     *
56     * @since 1.0.0
57     * @codeCoverageIgnore
58     */
59    private function __construct()
60    {
61    }
62
63    /**
64     * Get file extension type.
65     *
66     * @param string $extension Extension string
67     *
68     * @return int Extension type
69     *
70     * @since 1.0.0
71     */
72    public static function getExtensionType(string $extension) : int
73    {
74        $extension = \strtolower($extension);
75
76        if (\in_array($extension, self::CODE_EXTENSION)) {
77            return ExtensionType::CODE;
78        } elseif (\in_array($extension, self::TEXT_EXTENSION)) {
79            return ExtensionType::TEXT;
80        } elseif (\in_array($extension, self::WORD_EXTENSION)) {
81            return ExtensionType::WORD;
82        } elseif (\in_array($extension, self::PRESENTATION_EXTENSION)) {
83            return ExtensionType::PRESENTATION;
84        } elseif (\in_array($extension, self::PDF_EXTENSION)) {
85            return ExtensionType::PDF;
86        } elseif (\in_array($extension, self::ARCHIVE_EXTENSION)) {
87            return ExtensionType::ARCHIVE;
88        } elseif (\in_array($extension, self::AUDIO_EXTENSION)) {
89            return ExtensionType::AUDIO;
90        } elseif (\in_array($extension, self::VIDEO_EXTENSION)) {
91            return ExtensionType::VIDEO;
92        } elseif (\in_array($extension, self::IMAGE_EXTENSION)) {
93            return ExtensionType::IMAGE;
94        } elseif (\in_array($extension, self::SPREADSHEET_EXTENSION)) {
95            return ExtensionType::SPREADSHEET;
96        } elseif (\in_array($extension, self::DIRECTORY)) {
97            return ExtensionType::DIRECTORY;
98        } elseif (\in_array($extension, self::REFERENCE)) {
99            return ExtensionType::REFERENCE;
100        }
101
102        return ExtensionType::UNKNOWN;
103    }
104
105    /**
106     * Make file path absolute
107     *
108     * @param string $origPath File path
109     *
110     * @return string
111     *
112     * @since 1.0.0
113     */
114    public static function absolute(string $origPath) : string
115    {
116        if (\file_exists($origPath)) {
117            $path = \realpath($origPath);
118
119            return $path === false ? '' : $path;
120        }
121
122        $startsWithSlash = \str_starts_with($origPath, '/') || \str_starts_with($origPath, '\\') ? '/' : '';
123
124        $path  = [];
125        $parts = \explode('/', $origPath);
126
127        foreach ($parts as $part) {
128            if (empty($part) || $part === '.') {
129                continue;
130            }
131
132            if ($part !== '..' || empty($path)) {
133                $path[] = $part;
134            } else {
135                \array_pop($path);
136            }
137        }
138
139        return $startsWithSlash . \implode('/', $path);
140    }
141
142    /**
143     * Change encoding of file
144     *
145     * @param string $input          Path to file which should be re-encoded
146     * @param string $output         Output file path
147     * @param string $outputEncoding New file encoding
148     * @param string $inputEncoding  Old file encoding
149     *
150     * @return void
151     *
152     * @since 1.0.0
153     */
154    public static function changeFileEncoding(string $input, string $output, string $outputEncoding, string $inputEncoding = '') : void
155    {
156        $content = \file_get_contents($input);
157
158        if ($content === false) {
159            return; // @codeCoverageIgnore
160        }
161
162        $detected = empty($inputEncoding) ? \mb_detect_encoding($content) : $inputEncoding;
163        \file_put_contents($output, \mb_convert_encoding($content, $outputEncoding, $detected === false ? \mb_list_encodings() : $detected));
164    }
165
166    /**
167     * Converts a string permisseion (rwx) to octal
168     *
169     * @param string $permission Permission string (e.g. rwx-w-r--)
170     *
171     * @return int
172     *
173     * @since 1.0.0
174     */
175    public static function permissionToOctal(string $permission) : int
176    {
177        $permissionLength = \strlen($permission);
178        $perm             = '';
179        $tempPermission   = 0;
180
181        for ($i = 0; $i < $permissionLength; ++$i) {
182            if ($permission[$i] === 'r') {
183                $tempPermission += 4;
184            } elseif ($permission[$i] === 'w') {
185                $tempPermission += 2;
186            } elseif ($permission[$i] === 'x') {
187                ++$tempPermission;
188            }
189
190            if (($i + 1) % 3 === 0) {
191                $perm          .= $tempPermission;
192                $tempPermission = 0;
193            }
194        }
195
196        return \intval($perm, 8);
197    }
198
199    /**
200     * Multi-byte-safe pathinfo.
201     *
202     * @param string          $path    Path
203     * @param null|int|string $options PATHINFO_* or specifier for the component
204     *
205     * @return string|array
206     *
207     * @since 1.0.0
208     */
209    public static function mb_pathinfo(string $path, int | string $options = null) : string | array
210    {
211        $ret      = ['dirname' => '', 'basename' => '', 'extension' => '', 'filename' => ''];
212        $pathinfo = [];
213
214        if (\preg_match('#^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^.\\\\/]+?)|))[\\\\/.]*$#m', $path, $pathinfo)) {
215            if (isset($pathinfo[1])) {
216                $ret['dirname'] = $pathinfo[1];
217            }
218            if (isset($pathinfo[2])) {
219                $ret['basename'] = $pathinfo[2];
220            }
221            if (isset($pathinfo[5])) {
222                $ret['extension'] = $pathinfo[5];
223            }
224            if (isset($pathinfo[3])) {
225                $ret['filename'] = $pathinfo[3];
226            }
227        }
228
229        switch ($options) {
230            case \PATHINFO_DIRNAME:
231            case 'dirname':
232                return $ret['dirname'];
233            case \PATHINFO_BASENAME:
234            case 'basename':
235                return $ret['basename'];
236            case \PATHINFO_EXTENSION:
237            case 'extension':
238                return $ret['extension'];
239            case \PATHINFO_FILENAME:
240            case 'filename':
241                return $ret['filename'];
242            default:
243                return $ret;
244        }
245    }
246
247    /**
248     * Check whether a file path is safe, accessible, and readable.
249     *
250     * @param string $path A relative or absolute path
251     *
252     * @return bool
253     *
254     * @since 1.0.0
255     */
256    public static function isAccessible(string $path) : bool
257    {
258        if (!self::isPermittedPath($path)) {
259            return false;
260        }
261
262        $readable = \is_file($path);
263        if (!\str_starts_with($path, '\\\\')) {
264            $readable = $readable && \is_readable($path);
265        }
266
267        return $readable;
268    }
269
270    /**
271     * Check whether a file path is of a permitted type.
272     *
273     * @param string $path Path
274     *
275     * @return bool
276     *
277     * @since 1.0.0
278     */
279    public static function isPermittedPath(string $path) : bool
280    {
281        return !\preg_match('#^[a-z][a-z\d+.-]*://#i', $path);
282    }
283
284    /**
285     * Turn a string into a safe file name (sanitize a string)
286     *
287     * @param string $name String to sanitize for file name usage
288     *
289     * @return string
290     *
291     * @since 1.0.0
292     */
293    public static function makeSafeFileName(string $name) : string
294    {
295        $name = \preg_replace("/[^A-Za-z0-9\-_.]/", '_', $name);
296        $name = \preg_replace("/_+/", '_', $name ?? '');
297        $name = \trim($name ?? '', '_');
298
299        return \strtolower($name);
300    }
301}