1 | <?php |
||
2 | |||
3 | /** |
||
4 | * This file is part of Blitz PHP framework. |
||
5 | * |
||
6 | * (c) 2022 Dimitri Sitchet Tomkeu <[email protected]> |
||
7 | * |
||
8 | * For the full copyright and license information, please view |
||
9 | * the LICENSE file that was distributed with this source code. |
||
10 | */ |
||
11 | |||
12 | if (! function_exists('directory_map')) { |
||
13 | /** |
||
14 | * Créer une carte de répertoire |
||
15 | * |
||
16 | * Lit le répertoire spécifié et construit un tableau |
||
17 | * représentation de celui-ci. Les sous-dossiers contenus dans le répertoire seront également mappés. |
||
18 | * |
||
19 | * @param string $sourceDir Chemin d'accès à la source |
||
20 | * @param int $directoryDepth Profondeur des répertoires à parcourir |
||
21 | * (0 = entièrement récursif, 1 = répertoire actuel, etc.) |
||
22 | * @param bool $hidden Afficher ou non les fichiers cachés |
||
23 | */ |
||
24 | function directory_map(string $sourceDir, int $directoryDepth = 0, bool $hidden = false): array |
||
25 | { |
||
26 | try { |
||
27 | 6 | $fp = opendir($sourceDir); |
|
28 | |||
29 | 6 | $fileData = []; |
|
30 | 6 | $newDepth = $directoryDepth - 1; |
|
31 | 6 | $sourceDir = rtrim($sourceDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; |
|
32 | |||
33 | while (false !== ($file = readdir($fp))) { |
||
34 | // Remove '.', '..', and hidden files [optional] |
||
35 | if ($file === '.' || $file === '..' || ($hidden === false && $file[0] === '.')) { |
||
36 | 6 | continue; |
|
37 | } |
||
38 | |||
39 | if (is_dir($sourceDir . $file)) { |
||
40 | 6 | $file .= DIRECTORY_SEPARATOR; |
|
41 | } |
||
42 | |||
43 | if (($directoryDepth < 1 || $newDepth > 0) && is_dir($sourceDir . $file)) { |
||
44 | 6 | $fileData[$file] = directory_map($sourceDir . $file, $newDepth, $hidden); |
|
45 | } else { |
||
46 | 6 | $fileData[] = $file; |
|
47 | } |
||
48 | } |
||
49 | |||
50 | 6 | closedir($fp); |
|
51 | |||
52 | 6 | return $fileData; |
|
53 | } catch (Throwable) { |
||
54 | return []; |
||
55 | } |
||
56 | } |
||
57 | } |
||
58 | |||
59 | if (! function_exists('directory_mirror')) { |
||
60 | /** |
||
61 | * Copie récursivement les fichiers et répertoires du répertoire d'origine |
||
62 | * dans le répertoire cible, c'est-à-dire "miroir" son contenu. |
||
63 | * |
||
64 | * @param bool $overwrite Si les fichiers individuels sont écrasés en cas de collision |
||
65 | */ |
||
66 | function directory_mirror(string $originDir, string $targetDir, bool $overwrite = true): bool |
||
67 | { |
||
68 | return service('fs')->copyDirectory($originDir, $targetDir, $overwrite); |
||
69 | } |
||
70 | } |
||
71 | |||
72 | if (! function_exists('write_file')) { |
||
73 | /** |
||
74 | * Write File |
||
75 | * |
||
76 | * Writes data to the file specified in the path. |
||
77 | * Creates a new file if non-existent. |
||
78 | * |
||
79 | * @param string $path File path |
||
80 | * @param string $data Data to write |
||
81 | * @param string $mode fopen() mode (default: 'wb') |
||
82 | */ |
||
83 | function write_file(string $path, string $data, string $mode = 'wb'): bool |
||
84 | { |
||
85 | try { |
||
86 | 8 | $fp = fopen($path, $mode); |
|
87 | |||
88 | 8 | flock($fp, LOCK_EX); |
|
89 | |||
90 | 8 | for ($result = $written = 0, $length = strlen($data); $written < $length; $written += $result) { |
|
91 | if (($result = fwrite($fp, substr($data, $written))) === false) { |
||
92 | break; |
||
93 | } |
||
94 | } |
||
95 | |||
96 | 8 | flock($fp, LOCK_UN); |
|
97 | 8 | fclose($fp); |
|
98 | |||
99 | 8 | return is_int($result); |
|
100 | } catch (Throwable) { |
||
101 | return false; |
||
102 | } |
||
103 | } |
||
104 | } |
||
105 | |||
106 | if (! function_exists('delete_files')) { |
||
107 | /** |
||
108 | * Delete Files |
||
109 | * |
||
110 | * Deletes all files contained in the supplied directory path. |
||
111 | * Files must be writable or owned by the system in order to be deleted. |
||
112 | * If the second parameter is set to true, any directories contained |
||
113 | * within the supplied base directory will be nuked as well. |
||
114 | * |
||
115 | * @param string $path File path |
||
116 | * @param bool $delDir Whether to delete any directories found in the path |
||
117 | * @param bool $htdocs Whether to skip deleting .htaccess and index page files |
||
118 | * @param bool $hidden Whether to include hidden files (files beginning with a period) |
||
119 | */ |
||
120 | function delete_files(string $path, bool $delDir = false, bool $htdocs = false, bool $hidden = false): bool |
||
121 | { |
||
122 | 10 | $path = realpath($path) ?: $path; |
|
123 | 10 | $path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; |
|
124 | |||
125 | try { |
||
126 | foreach (new RecursiveIteratorIterator( |
||
127 | new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS), |
||
128 | RecursiveIteratorIterator::CHILD_FIRST |
||
129 | ) as $object) { |
||
130 | 10 | $filename = $object->getFilename(); |
|
131 | if (! $hidden && $filename[0] === '.') { |
||
132 | continue; |
||
133 | } |
||
134 | |||
135 | if (! $htdocs || ! preg_match('/^(\.htaccess|index\.(html|htm|php)|web\.config)$/i', $filename)) { |
||
136 | 10 | $isDir = $object->isDir(); |
|
137 | if ($isDir && $delDir) { |
||
138 | 10 | rmdir($object->getPathname()); |
|
139 | |||
140 | continue; |
||
141 | } |
||
142 | if (! $isDir) { |
||
143 | 10 | unlink($object->getPathname()); |
|
144 | } |
||
145 | } |
||
146 | } |
||
147 | |||
148 | 10 | return true; |
|
149 | } catch (Throwable) { |
||
150 | return false; |
||
151 | } |
||
152 | } |
||
153 | } |
||
154 | |||
155 | if (! function_exists('get_filenames')) { |
||
156 | /** |
||
157 | * Get Filenames |
||
158 | * |
||
159 | * Reads the specified directory and builds an array containing the filenames. |
||
160 | * Any sub-folders contained within the specified path are read as well. |
||
161 | * |
||
162 | * @param string $sourceDir Path to source |
||
163 | * @param bool|null $includePath Whether to include the path as part of the filename; false for no path, null for a relative path, true for full path |
||
164 | * @param bool $hidden Whether to include hidden files (files beginning with a period) |
||
165 | * @param bool $includeDir Whether to include directories |
||
166 | */ |
||
167 | function get_filenames( |
||
168 | string $sourceDir, |
||
169 | ?bool $includePath = false, |
||
170 | bool $hidden = false, |
||
171 | bool $includeDir = true |
||
172 | ): array { |
||
173 | $files = []; |
||
174 | |||
175 | $sourceDir = realpath($sourceDir) ?: $sourceDir; |
||
176 | $sourceDir = rtrim($sourceDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; |
||
177 | |||
178 | try { |
||
179 | foreach (new RecursiveIteratorIterator( |
||
180 | new RecursiveDirectoryIterator($sourceDir, RecursiveDirectoryIterator::SKIP_DOTS), |
||
181 | RecursiveIteratorIterator::SELF_FIRST |
||
182 | ) as $name => $object) { |
||
183 | $basename = pathinfo($name, PATHINFO_BASENAME); |
||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||
184 | if (! $hidden && $basename[0] === '.') { |
||
185 | continue; |
||
186 | } |
||
187 | |||
188 | if ($includeDir || ! $object->isDir()) { |
||
189 | if ($includePath === false) { |
||
190 | $files[] = $basename; |
||
191 | } elseif ($includePath === null) { |
||
192 | $files[] = str_replace($sourceDir, '', $name); |
||
193 | } else { |
||
194 | $files[] = $name; |
||
195 | } |
||
196 | } |
||
197 | } |
||
198 | } catch (Throwable) { |
||
199 | return []; |
||
200 | } |
||
201 | |||
202 | sort($files); |
||
203 | |||
204 | return $files; |
||
205 | } |
||
206 | } |
||
207 | |||
208 | if (! function_exists('get_dir_file_info')) { |
||
209 | /** |
||
210 | * Get Directory File Information |
||
211 | * |
||
212 | * Reads the specified directory and builds an array containing the filenames, |
||
213 | * filesize, dates, and permissions |
||
214 | * |
||
215 | * Any sub-folders contained within the specified path are read as well. |
||
216 | * |
||
217 | * @param string $sourceDir Path to source |
||
218 | * @param bool $topLevelOnly Look only at the top level directory specified? |
||
219 | * @param bool $recursion Internal variable to determine recursion status - do not use in calls |
||
220 | */ |
||
221 | function get_dir_file_info(string $sourceDir, bool $topLevelOnly = true, bool $recursion = false): array |
||
222 | { |
||
223 | static $fileData = []; |
||
224 | $relativePath = $sourceDir; |
||
225 | |||
226 | try { |
||
227 | $fp = opendir($sourceDir); |
||
228 | |||
229 | // reset the array and make sure $source_dir has a trailing slash on the initial call |
||
230 | if ($recursion === false) { |
||
231 | $fileData = []; |
||
232 | $sourceDir = rtrim(realpath($sourceDir), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; |
||
233 | } |
||
234 | |||
235 | // Used to be foreach (scandir($source_dir, 1) as $file), but scandir() is simply not as fast |
||
236 | while (false !== ($file = readdir($fp))) { |
||
237 | if (is_dir($sourceDir . $file) && $file[0] !== '.' && $topLevelOnly === false) { |
||
238 | get_dir_file_info($sourceDir . $file . DIRECTORY_SEPARATOR, $topLevelOnly, true); |
||
239 | } elseif ($file[0] !== '.') { |
||
240 | $fileData[$file] = get_file_info($sourceDir . $file); |
||
241 | $fileData[$file]['relative_path'] = $relativePath; |
||
242 | } |
||
243 | } |
||
244 | |||
245 | closedir($fp); |
||
246 | |||
247 | return $fileData; |
||
248 | } catch (Throwable) { |
||
249 | return []; |
||
250 | } |
||
251 | } |
||
252 | } |
||
253 | |||
254 | if (! function_exists('get_file_info')) { |
||
255 | /** |
||
256 | * Get File Info |
||
257 | * |
||
258 | * Given a file and path, returns the name, path, size, date modified |
||
259 | * Second parameter allows you to explicitly declare what information you want returned |
||
260 | * Options are: name, server_path, size, date, readable, writable, executable, fileperms |
||
261 | * Returns false if the file cannot be found. |
||
262 | * |
||
263 | * @param string $file Path to file |
||
264 | * @param mixed $returnedValues Array or comma separated string of information returned |
||
265 | * |
||
266 | * @return array|null |
||
267 | */ |
||
268 | function get_file_info(string $file, $returnedValues = ['name', 'server_path', 'size', 'date']) |
||
269 | { |
||
270 | if (! is_file($file)) { |
||
271 | return null; |
||
272 | } |
||
273 | |||
274 | $fileInfo = []; |
||
275 | |||
276 | if (is_string($returnedValues)) { |
||
277 | $returnedValues = explode(',', $returnedValues); |
||
278 | } |
||
279 | |||
280 | foreach ($returnedValues as $key) { |
||
281 | switch ($key) { |
||
282 | case 'name': |
||
283 | $fileInfo['name'] = basename($file); |
||
284 | break; |
||
285 | |||
286 | case 'server_path': |
||
287 | $fileInfo['server_path'] = $file; |
||
288 | break; |
||
289 | |||
290 | case 'size': |
||
291 | $fileInfo['size'] = filesize($file); |
||
292 | break; |
||
293 | |||
294 | case 'date': |
||
295 | $fileInfo['date'] = filemtime($file); |
||
296 | break; |
||
297 | |||
298 | case 'readable': |
||
299 | $fileInfo['readable'] = is_readable($file); |
||
300 | break; |
||
301 | |||
302 | case 'writable': |
||
303 | $fileInfo['writable'] = is_really_writable($file); |
||
304 | break; |
||
305 | |||
306 | case 'executable': |
||
307 | $fileInfo['executable'] = is_executable($file); |
||
308 | break; |
||
309 | |||
310 | case 'fileperms': |
||
311 | $fileInfo['fileperms'] = fileperms($file); |
||
312 | break; |
||
313 | } |
||
314 | } |
||
315 | |||
316 | return $fileInfo; |
||
317 | } |
||
318 | } |
||
319 | |||
320 | if (! function_exists('symbolic_permissions')) { |
||
321 | /** |
||
322 | * Symbolic Permissions |
||
323 | * |
||
324 | * Takes a numeric value representing a file's permissions and returns |
||
325 | * standard symbolic notation representing that value |
||
326 | * |
||
327 | * @param int $perms Permissions |
||
328 | */ |
||
329 | function symbolic_permissions(int $perms): string |
||
330 | { |
||
331 | if (($perms & 0xC000) === 0xC000) { |
||
332 | $symbolic = 's'; // Socket |
||
333 | } elseif (($perms & 0xA000) === 0xA000) { |
||
334 | $symbolic = 'l'; // Symbolic Link |
||
335 | } elseif (($perms & 0x8000) === 0x8000) { |
||
336 | $symbolic = '-'; // Regular |
||
337 | } elseif (($perms & 0x6000) === 0x6000) { |
||
338 | $symbolic = 'b'; // Block special |
||
339 | } elseif (($perms & 0x4000) === 0x4000) { |
||
340 | $symbolic = 'd'; // Directory |
||
341 | } elseif (($perms & 0x2000) === 0x2000) { |
||
342 | $symbolic = 'c'; // Character special |
||
343 | } elseif (($perms & 0x1000) === 0x1000) { |
||
344 | $symbolic = 'p'; // FIFO pipe |
||
345 | } else { |
||
346 | $symbolic = 'u'; // Unknown |
||
347 | } |
||
348 | |||
349 | // Owner |
||
350 | $symbolic .= ((($perms & 0x0100) !== 0) ? 'r' : '-') |
||
351 | . ((($perms & 0x0080) !== 0) ? 'w' : '-') |
||
352 | . ((($perms & 0x0040) !== 0) ? ((($perms & 0x0800) !== 0) ? 's' : 'x') : ((($perms & 0x0800) !== 0) ? 'S' : '-')); |
||
353 | |||
354 | // Group |
||
355 | $symbolic .= ((($perms & 0x0020) !== 0) ? 'r' : '-') |
||
356 | . ((($perms & 0x0010) !== 0) ? 'w' : '-') |
||
357 | . ((($perms & 0x0008) !== 0) ? ((($perms & 0x0400) !== 0) ? 's' : 'x') : ((($perms & 0x0400) !== 0) ? 'S' : '-')); |
||
358 | |||
359 | // World |
||
360 | $symbolic .= ((($perms & 0x0004) !== 0) ? 'r' : '-') |
||
361 | . ((($perms & 0x0002) !== 0) ? 'w' : '-') |
||
362 | . ((($perms & 0x0001) !== 0) ? ((($perms & 0x0200) !== 0) ? 't' : 'x') : ((($perms & 0x0200) !== 0) ? 'T' : '-')); |
||
363 | |||
364 | return $symbolic; |
||
365 | } |
||
366 | } |
||
367 | |||
368 | if (! function_exists('octal_permissions')) { |
||
369 | /** |
||
370 | * Octal Permissions |
||
371 | * |
||
372 | * Takes a numeric value representing a file's permissions and returns |
||
373 | * a three character string representing the file's octal permissions |
||
374 | * |
||
375 | * @param int $perms Permissions |
||
376 | */ |
||
377 | function octal_permissions(int $perms): string |
||
378 | { |
||
379 | return substr(sprintf('%o', $perms), -3); |
||
380 | } |
||
381 | } |
||
382 | |||
383 | if (! function_exists('same_file')) { |
||
384 | /** |
||
385 | * Checks if two files both exist and have identical hashes |
||
386 | * |
||
387 | * @return bool Same or not |
||
388 | */ |
||
389 | function same_file(string $file1, string $file2): bool |
||
390 | { |
||
391 | 2 | return is_file($file1) && is_file($file2) && md5_file($file1) === md5_file($file2); |
|
392 | } |
||
393 | } |
||
394 | |||
395 | if (! function_exists('set_realpath')) { |
||
396 | /** |
||
397 | * Set Realpath |
||
398 | * |
||
399 | * @param bool $checkExistence Checks to see if the path exists |
||
400 | */ |
||
401 | function set_realpath(string $path, bool $checkExistence = false): string |
||
402 | { |
||
403 | // Security check to make sure the path is NOT a URL. No remote file inclusion! |
||
404 | if (preg_match('#^(http:\/\/|https:\/\/|www\.|ftp)#i', $path) || filter_var($path, FILTER_VALIDATE_IP) === $path) { |
||
405 | 12 | throw new InvalidArgumentException('The path you submitted must be a local server path, not a URL'); |
|
406 | } |
||
407 | |||
408 | // Resolve the path |
||
409 | if (realpath($path) !== false) { |
||
410 | 12 | $path = realpath($path); |
|
411 | } elseif ($checkExistence && ! is_dir($path) && ! is_file($path)) { |
||
412 | 2 | throw new InvalidArgumentException('Not a valid path: ' . $path); |
|
413 | } |
||
414 | |||
415 | // Add a trailing slash, if this is a directory |
||
416 | 12 | return is_dir($path) ? rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $path; |
|
417 | } |
||
418 | } |
||
419 |