ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.ilFileUtils.php
Go to the documentation of this file.
1<?php
2
27
36{
46 public static function recursive_dirscan(string $dir, array &$arr): void
47 {
48 global $DIC;
49
50 $lng = $DIC->language();
51
52 $dirlist = opendir($dir);
53 while (false !== ($file = readdir($dirlist))) {
54 if (!is_file($dir . "/" . $file) && !is_dir($dir . "/" . $file)) {
55 throw new ilFileUtilsException(
56 $lng->txt("filenames_not_supported"),
58 );
59 }
60
61 if ($file !== '.' && $file !== '..') {
62 $newpath = $dir . '/' . $file;
63 $level = explode('/', $newpath);
64 if (is_dir($newpath)) {
65 ilFileUtils::recursive_dirscan($newpath, $arr);
66 } else {
67 $arr["path"][] = $dir . "/";
68 $arr["file"][] = end($level);
69 }
70 }
71 }
72 closedir($dirlist);
73 }
74
78 public static function utf8_encode(string $string): string
79 {
80 global $DIC;
81 return $DIC->refinery()->string()->encoding()->latin1ToUtf8()->transform($string);
82 }
83
87 public static function getValidFilename(string $a_filename): string
88 {
89 global $DIC;
90 $sanitizer = new ilFileServicesFilenameSanitizer(
91 $DIC->fileServiceSettings()
92 );
93
94 return $sanitizer->sanitize($a_filename);
95 }
96
100 public static function rename(string $a_source, string $a_target): bool
101 {
102 $pi = pathinfo($a_target);
103 global $DIC;
104 $sanitizer = new ilFileServicesFilenameSanitizer(
105 $DIC->fileServiceSettings()
106 );
107
108 if (!$sanitizer->isClean($a_target)) {
109 throw new ilFileUtilsException("Invalid target file");
110 }
111
112 return rename($a_source, $a_target);
113 }
114
132 public static function rCopy(
133 string $a_sdir,
134 string $a_tdir,
135 bool $preserveTimeAttributes = false
136 ): bool {
137 $sourceFS = LegacyPathHelper::deriveFilesystemFrom($a_sdir);
138 $targetFS = LegacyPathHelper::deriveFilesystemFrom($a_tdir);
139
140 $sourceDir = LegacyPathHelper::createRelativePath($a_sdir);
141 $targetDir = LegacyPathHelper::createRelativePath($a_tdir);
142
143 // check if arguments are directories
144 if (!$sourceFS->hasDir($sourceDir)) {
145 return false;
146 }
147
148 $sourceList = $sourceFS->listContents($sourceDir, true);
149
150 foreach ($sourceList as $item) {
151 if ($item->isDir()) {
152 continue;
153 }
154 try {
155 $itemPath = $targetDir . '/' . substr(
156 $item->getPath(),
157 strlen($sourceDir)
158 );
159 $stream = $sourceFS->readStream($item->getPath());
160 $targetFS->writeStream($itemPath, $stream);
162 // Do nothing with that type of exception
163 }
164 }
165
166 return true;
167 }
168
188 public static function makeDirParents(string $a_dir): bool
189 {
190 $dirs = [$a_dir];
191 $a_dir = dirname($a_dir);
192 $last_dirname = '';
193 while ($last_dirname !== $a_dir) {
194 array_unshift($dirs, $a_dir);
195 $last_dirname = $a_dir;
196 $a_dir = dirname($a_dir);
197 }
198
199 // find the first existing dir
200 $reverse_paths = array_reverse($dirs, true);
201 $found_index = -1;
202 foreach ($reverse_paths as $key => $value) {
203 if ($found_index != -1) {
204 continue;
205 }
206 if (!is_dir($value)) {
207 continue;
208 }
209 $found_index = $key;
210 }
211
212 $old_mask = umask(0000);
213 foreach ($dirs as $dirindex => $dir) {
214 // starting with the longest existing path
215 if ($dirindex >= $found_index) {
216 if (!file_exists($dir)) {
217 if (strcmp(substr($dir, strlen($dir) - 1, 1), "/") == 0) {
218 // on some systems there is an error when there is a slash
219 // at the end of a directory in mkdir, see Mantis #2554
220 $dir = substr($dir, 0, strlen($dir) - 1);
221 }
222 if (!mkdir($dir)) {
223 error_log("Can't make directory: $dir");
224 return false;
225 }
226 } elseif (!is_dir($dir)) {
227 error_log("$dir is not a directory");
228 return false;
229 } else {
230 // get umask of the last existing parent directory
231 $umask = fileperms($dir);
232 }
233 }
234 }
235 umask($old_mask);
236
237 return true;
238 }
239
250 public static function getDataDir(): string
251 {
252 return CLIENT_DATA_DIR;
253 }
254
263 public static function dirsize(string $directory): int
264 {
265 $size = 0;
266 if (!is_dir($directory)) {
267 // dirsize of non-existing directory
268 $size = @filesize($directory);
269 return ($size === false) ? -1 : $size;
270 }
271 if ($DIR = opendir($directory)) {
272 while (($dirfile = readdir($DIR)) !== false) {
273 if (is_link(
274 $directory . DIRECTORY_SEPARATOR . $dirfile
275 )) {
276 continue;
277 }
278 if ($dirfile === '.') {
279 continue;
280 }
281 if ($dirfile === '..') {
282 continue;
283 }
284 if (is_file($directory . DIRECTORY_SEPARATOR . $dirfile)) {
285 $size += filesize(
286 $directory . DIRECTORY_SEPARATOR . $dirfile
287 );
288 } elseif (is_dir($directory . DIRECTORY_SEPARATOR . $dirfile)) {
289 $dirSize = ilFileUtils::dirsize(
290 $directory . DIRECTORY_SEPARATOR . $dirfile
291 );
292 if ($dirSize >= 0) {
293 $size += $dirSize;
294 } else {
295 return -1;
296 }
297 }
298 }
299 closedir($DIR);
300 }
301 return $size;
302 }
303
322 public static function makeDir(string $a_dir): bool
323 {
324 $a_dir = trim($a_dir);
325
326 // remove trailing slash (bugfix for php 4.2.x)
327 if (str_ends_with($a_dir, "/")) {
328 $a_dir = substr($a_dir, 0, -1);
329 }
330
331 // check if a_dir comes with a path
332 if (($path = substr(
333 $a_dir,
334 0,
335 strrpos($a_dir, "/") - strlen($a_dir)
336 )) === '' || ($path = substr(
337 $a_dir,
338 0,
339 strrpos($a_dir, "/") - strlen($a_dir)
340 )) === '0') {
341 $path = ".";
342 }
343
344 // create directory with file permissions of parent directory
345 if (is_dir($a_dir)) {
346 return true;
347 }
348 $old_mask = umask(0000);
349 $result = @mkdir($a_dir, fileperms($path));
350 umask($old_mask);
351
352 return $result;
353 }
354
355 protected static function sanitateTargetPath(string $a_target): array
356 {
357 $target_file_system = match (true) {
358 str_starts_with($a_target, 'public/' . ILIAS_WEB_DIR . '/' . CLIENT_ID),
359 str_starts_with($a_target, './public/' . ILIAS_WEB_DIR . '/' . CLIENT_ID),
360 str_starts_with($a_target, '/' . ILIAS_WEB_DIR . '/' . CLIENT_ID),
361 str_starts_with($a_target, './' . ILIAS_WEB_DIR . '/' . CLIENT_ID),
362 str_starts_with($a_target, CLIENT_WEB_DIR) => Location::WEB,
363
364 str_starts_with($a_target, CLIENT_DATA_DIR . "/temp") => Location::TEMPORARY,
365 str_starts_with($a_target, CLIENT_DATA_DIR) => Location::STORAGE,
366
367 str_starts_with($a_target, ILIAS_ABSOLUTE_PATH . '/public/Customizing') => Location::CUSTOMIZING,
368 default => throw new InvalidArgumentException(
369 "Can not move files to \"$a_target\" because path can not be mapped to web, storage or customizing location."
370 ),
371 };
372
373 $absolute_target_dir = dirname($a_target);
374 $target_dir = LegacyPathHelper::createRelativePath($absolute_target_dir);
375
376 return [$target_file_system, $target_dir];
377 }
378
398 public static function moveUploadedFile(
399 string $a_file,
400 string $a_name,
401 string $a_target,
402 bool $a_raise_errors = true,
403 string $a_mode = "move_uploaded"
404 ): bool {
405 global $DIC;
406 $main_tpl = $DIC->ui()->mainTemplate();
407 $target_filename = basename($a_target);
408
409 $target_filename = ilFileUtils::getValidFilename($target_filename);
410
411 // Make sure the target is in a valid subfolder. (e.g. no uploads to ilias/setup_/....)
412 [$target_filesystem, $target_dir] = self::sanitateTargetPath($a_target);
413
414 $upload = $DIC->upload();
415
416 // If the upload has not yet been processed make sure he gets processed now.
417 if (!$upload->hasBeenProcessed()) {
418 $upload->process();
419 }
420
421 try {
422 if (!$upload->hasUploads()) {
423 throw new ilException(
424 $DIC->language()->txt("upload_error_file_not_found")
425 );
426 }
427 $upload_result = $upload->getResults()[$a_file] ?? null;
428 if ($upload_result instanceof UploadResult) {
429 if (!$upload_result->isOK()) {
430 throw new ilException($upload_result->getStatus()->getMessage());
431 }
432 } else {
433 return false;
434 }
435 } catch (ilException $e) {
436 if (!$a_raise_errors) {
437 $main_tpl->setOnScreenMessage('failure', $e->getMessage(), true);
438 } else {
439 throw $e;
440 }
441
442 return false;
443 }
444
445 $upload->moveOneFileTo(
446 $upload_result,
447 $target_dir,
448 $target_filesystem,
449 $target_filename,
450 true
451 );
452
453 return true;
454 }
455
459 public static function zip(
460 string $a_dir,
461 string $a_file,
462 bool $compress_content = false
463 ): bool {
464 global $DIC;
465 // ensure top directory should be the same behaviour as before, if you need it to be different, you should legacyArchives directly
466 return $DIC->legacyArchives()->zip($a_dir, $a_file, true);
467 }
468
484 public static function delDir(string $a_dir, bool $a_clean_only = false): void
485 {
486 if (!is_dir($a_dir) || is_int(strpos($a_dir, ".."))) {
487 return;
488 }
489
490 $current_dir = opendir($a_dir);
491
492 $files = [];
493
494 // this extra loop has been necessary because of a strange bug
495 // at least on MacOS X. A looped readdir() didn't work
496 // correctly with larger directories
497 // when an unlink happened inside the loop. Getting all files
498 // into the memory first solved the problem.
499 while ($entryname = readdir($current_dir)) {
500 $files[] = $entryname;
501 }
502
503 foreach ($files as $file) {
504 if (is_dir(
505 $a_dir . "/" . $file
506 ) && ($file !== "." && $file !== "..")) {
507 ilFileUtils::delDir($a_dir . "/" . $file);
508 } elseif ($file !== "." && $file !== "..") {
509 unlink($a_dir . "/" . $file);
510 }
511 }
512
513 closedir($current_dir);
514 if (!$a_clean_only) {
515 @rmdir($a_dir);
516 }
517 }
518
519 public static function getSafeFilename(string $a_initial_filename): string
520 {
521 $file_peaces = explode('.', $a_initial_filename);
522
523 $file_extension = array_pop($file_peaces);
524
525 if (SUFFIX_REPL_ADDITIONAL) {
526 $string_extensions = SUFFIX_REPL_DEFAULT . "," . SUFFIX_REPL_ADDITIONAL;
527 } else {
528 $string_extensions = SUFFIX_REPL_DEFAULT;
529 }
530
531 $sufixes = explode(",", $string_extensions);
532
533 if (in_array($file_extension, $sufixes)) {
534 $file_extension = "sec";
535 }
536
537 $file_peaces[] = $file_extension;
538
539 $safe_filename = "";
540 foreach ($file_peaces as $piece) {
541 $safe_filename .= "$piece";
542 if ($piece != end($file_peaces)) {
543 $safe_filename .= ".";
544 }
545 }
546
547 return $safe_filename;
548 }
549
565 public static function getDir(
566 string $a_dir,
567 bool $a_rec = false,
568 ?string $a_sub_dir = ""
569 ): array {
570 $current_dir = opendir($a_dir . $a_sub_dir);
571
572 $dirs = [];
573 $files = [];
574 $subitems = [];
575 while ($entry = readdir($current_dir)) {
576 if (is_dir($a_dir . "/" . $entry)) {
577 $dirs[$entry] = [
578 "type" => "dir",
579 "entry" => $entry,
580 "subdir" => $a_sub_dir
581 ];
582 if ($a_rec && $entry !== "." && $entry !== "..") {
584 $a_dir,
585 true,
586 $a_sub_dir . "/" . $entry
587 );
588 $subitems = array_merge($subitems, $si);
589 }
590 } elseif ($entry !== "." && $entry !== "..") {
591 $size = filesize($a_dir . $a_sub_dir . "/" . $entry);
592 $files[$entry] = [
593 "type" => "file",
594 "entry" => $entry,
595 "size" => $size,
596 "subdir" => $a_sub_dir
597 ];
598 }
599 }
600 ksort($dirs);
601 ksort($files);
602
603 return array_merge($dirs, $files, $subitems);
604 }
605
621 public static function getWebspaceDir(string $mode = "filesystem"): string
622 {
623 if ($mode === "filesystem") {
624 return "./" . ILIAS_WEB_DIR . "/" . CLIENT_ID;
625 }
626 if (defined("ILIAS_MODULE")) {
627 return "../" . ILIAS_WEB_DIR . "/" . CLIENT_ID;
628 }
629 return "./" . ILIAS_WEB_DIR . "/" . CLIENT_ID;
630 }
631
644 public static function createDirectory(string $a_dir, int $a_mod = 0755): void
645 {
646 ilFileUtils::makeDir($a_dir);
647 }
648
649 public static function getFileSizeInfo(): string
650 {
651 global $DIC;
652 $size = new DataSize(self::getPhpUploadSizeLimitInBytes(), DataSize::MB);
653 $max_filesize = $size->__toString();
654 $lng = $DIC->language();
655
656 return $lng->txt("file_notice") . " $max_filesize.";
657 }
658
662 public static function getASCIIFilename(string $a_filename): string
663 {
664 global $DIC;
665 $policy = new ilFileServicesPolicy($DIC->fileServiceSettings());
666 return $policy->ascii($a_filename);
667 }
668
676 public static function ilTempnam(?string $a_temp_path = null): string
677 {
678 $temp_path = $a_temp_path ?? ilFileUtils::getDataDir() . "/temp";
679
680 if (!is_dir($temp_path)) {
682 }
683 $temp_name = $temp_path . "/" . uniqid("tmp");
684
685 return $temp_name;
686 }
687
696 public static function unzip(string $a_file, bool $overwrite = false, bool $a_flat = false): bool
697 {
698 if (defined('DEVMODE') && DEVMODE) {
699 trigger_error('Deprecated method called: ' . __METHOD__, E_USER_DEPRECATED);
700 }
701
702 global $DIC;
703 return $DIC->legacyArchives()->unzip(
704 $a_file,
705 null,
706 $overwrite,
707 $a_flat,
708 false
709 );
710 }
711
715 public static function renameExecutables(string $a_dir): void
716 {
717 $def_arr = explode(",", SUFFIX_REPL_DEFAULT);
718 foreach ($def_arr as $def) {
719 self::rRenameSuffix($a_dir, trim($def), "sec");
720 }
721
722 $def_arr = explode(",", SUFFIX_REPL_ADDITIONAL);
723 foreach ($def_arr as $def) {
724 self::rRenameSuffix($a_dir, trim($def), "sec");
725 }
726 }
727
734 public static function rRenameSuffix(string $a_dir, string $a_old_suffix, string $a_new_suffix): bool
735 {
736 if ($a_dir === "/" || $a_dir === "" || is_int(strpos($a_dir, ".."))
737 || trim($a_old_suffix) === "") {
738 return false;
739 }
740
741 // check if argument is directory
742 if (!@is_dir($a_dir)) {
743 return false;
744 }
745
746 // read a_dir
747 $dir = opendir($a_dir);
748
749 while ($file = readdir($dir)) {
750 if ($file !== "." && $file !== "..") {
751 // triple dot is not allowed in filenames
752 if ($file === '...') {
753 unlink($a_dir . "/" . $file);
754 continue;
755 }
756 // directories
757 if (@is_dir($a_dir . "/" . $file)) {
758 ilFileUtils::rRenameSuffix($a_dir . "/" . $file, $a_old_suffix, $a_new_suffix);
759 }
760
761 // files
762 if (@is_file($a_dir . "/" . $file)) {
763 // first check for files with trailing dot
764 if (strrpos($file, '.') == (strlen($file) - 1)) {
765 try {
766 rename($a_dir . '/' . $file, substr($a_dir . '/' . $file, 0, -1));
767 } catch (Throwable) {
768 // to avoid exploits we do delete this file and continue renaming
769 unlink($a_dir . '/' . $file);
770 continue;
771 }
772 $file = substr($file, 0, -1);
773 }
774
775 $path_info = pathinfo($a_dir . "/" . $file);
776
777 if (strtolower($path_info["extension"] ?? '') === strtolower($a_old_suffix)) {
778 $pos = strrpos($a_dir . "/" . $file, ".");
779 $new_name = substr($a_dir . "/" . $file, 0, $pos) . "." . $a_new_suffix;
780 // check if file exists
781 if (file_exists($new_name)) {
782 if (is_dir($new_name)) {
783 ilFileUtils::delDir($new_name);
784 } else {
785 unlink($new_name);
786 }
787 }
788 rename($a_dir . "/" . $file, $new_name);
789 }
790 }
791 }
792 }
793 return true;
794 }
795
796 public static function removeTrailingPathSeparators(string $path): string
797 {
798 $path = preg_replace("/[\/\\\]+$/", "", $path);
799 return (string) $path;
800 }
801
805 public static function getPhpUploadSizeLimitInBytes(): string
806 {
807 $convertPhpIniSizeValueToBytes = function ($phpIniSizeValue) {
808 if (is_numeric($phpIniSizeValue)) {
809 return $phpIniSizeValue;
810 }
811
812 $suffix = substr($phpIniSizeValue, -1);
813 $value = substr($phpIniSizeValue, 0, -1);
814
815 switch (strtoupper($suffix)) {
816 case 'P':
817 $value *= 1024;
818 // no break
819 case 'T':
820 $value *= 1024;
821 // no break
822 case 'G':
823 $value *= 1024;
824 // no break
825 case 'M':
826 $value *= 1024;
827 // no break
828 case 'K':
829 $value *= 1024;
830 break;
831 }
832
833 return $value;
834 };
835
836 $uploadSizeLimitBytes = min(
837 $convertPhpIniSizeValueToBytes(ini_get('post_max_size')),
838 $convertPhpIniSizeValueToBytes(ini_get('upload_max_filesize'))
839 );
840
841 return $uploadSizeLimitBytes;
842 }
843
844 public static function _sanitizeFilemame(string $a_filename): string
845 {
846 return strip_tags(ilUtil::stripSlashes($a_filename));
847 }
848}
This class provides the data size with additional information to remove the work to calculate the siz...
Definition: DataSize.php:31
Indicates that the directory is missing or not found.
Indicates that a file is missing or not found.
Indicates general problems with the input or output operations.
Definition: IOException.php:28
The legacy path helper provides convenient functions for the integration of the filesystem service wi...
Base class for ILIAS Exception handling.
Class ilFileServicesFilenameSanitizer.
Class ilFileServicesPolicy.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Class ilFileUtils.
static utf8_encode(string $string)
static makeDirParents(string $a_dir)
Create a new directory and all parent directories.
static getFileSizeInfo()
static getWebspaceDir(string $mode="filesystem")
get webspace directory
static getASCIIFilename(string $a_filename)
static getDir(string $a_dir, bool $a_rec=false, ?string $a_sub_dir="")
get directory
static recursive_dirscan(string $dir, array &$arr)
Recursively scans a given directory and writes path and filename into referenced array.
static zip(string $a_dir, string $a_file, bool $compress_content=false)
static makeDir(string $a_dir)
creates a new directory and inherits all filesystem permissions of the parent directory You may pass ...
static ilTempnam(?string $a_temp_path=null)
Returns a unique and non existing Path for e temporary file or directory.
static rename(string $a_source, string $a_target)
static delDir(string $a_dir, bool $a_clean_only=false)
removes a dir and all its content (subdirs and files) recursively
static unzip(string $a_file, bool $overwrite=false, bool $a_flat=false)
unzip file
static sanitateTargetPath(string $a_target)
static getDataDir()
get data directory (outside webspace)
static dirsize(string $directory)
get size of a directory or a file.
static renameExecutables(string $a_dir)
static getSafeFilename(string $a_initial_filename)
static createDirectory(string $a_dir, int $a_mod=0755)
create directory
static getValidFilename(string $a_filename)
static rRenameSuffix(string $a_dir, string $a_old_suffix, string $a_new_suffix)
Renames all files with certain suffix and gives them a new suffix.
static _sanitizeFilemame(string $a_filename)
static getPhpUploadSizeLimitInBytes()
static removeTrailingPathSeparators(string $path)
static moveUploadedFile(string $a_file, string $a_name, string $a_target, bool $a_raise_errors=true, string $a_mode="move_uploaded")
move uploaded file
static rCopy(string $a_sdir, string $a_tdir, bool $preserveTimeAttributes=false)
Copies content of a directory $a_sdir recursively to a directory $a_tdir.
static stripSlashes(string $a_str, bool $a_strip_html=true, string $a_allow="")
const CLIENT_ID
Definition: constants.php:41
const ILIAS_WEB_DIR
Definition: constants.php:45
const CLIENT_WEB_DIR
Definition: constants.php:47
const CLIENT_DATA_DIR
Definition: constants.php:46
Interface Location.
Definition: Location.php:33
$path
Definition: ltiservices.php:30
global $lng
Definition: privfeed.php:31
global $DIC
Definition: shib_login.php:26