Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
93.41% |
85 / 91 |
|
62.50% |
5 / 8 |
CRAP | |
0.00% |
0 / 1 |
FileUtils | |
93.41% |
85 / 91 |
|
62.50% |
5 / 8 |
55.87 | |
0.00% |
0 / 1 |
__construct | n/a |
0 / 0 |
n/a |
0 / 0 |
1 | |||||
getExtensionType | |
96.15% |
25 / 26 |
|
0.00% |
0 / 1 |
13 | |||
absolute | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
10 | |||
changeFileEncoding | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
4 | |||
permissionToOctal | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
6 | |||
mb_pathinfo | |
100.00% |
23 / 23 |
|
100.00% |
1 / 1 |
15 | |||
isAccessible | |
83.33% |
5 / 6 |
|
0.00% |
0 / 1 |
4.07 | |||
isPermittedPath | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
makeSafeFileName | |
0.00% |
0 / 4 |
|
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 | */ |
13 | declare(strict_types=1); |
14 | |
15 | namespace 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 | */ |
25 | final 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 | } |