ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
class.ilFileUtils.php
Go to the documentation of this file.
1 <?php
2 
22 
31 {
41  public static function recursive_dirscan(string $dir, array &$arr): void
42  {
43  global $DIC;
44 
45  $lng = $DIC->language();
46 
47  $dirlist = opendir($dir);
48  while (false !== ($file = readdir($dirlist))) {
49  if (!is_file($dir . "/" . $file) && !is_dir($dir . "/" . $file)) {
50  throw new ilFileUtilsException(
51  $lng->txt("filenames_not_supported"),
53  );
54  }
55 
56  if ($file != '.' && $file != '..') {
57  $newpath = $dir . '/' . $file;
58  $level = explode('/', $newpath);
59  if (is_dir($newpath)) {
60  ilFileUtils::recursive_dirscan($newpath, $arr);
61  } else {
62  $arr["path"][] = $dir . "/";
63  $arr["file"][] = end($level);
64  }
65  }
66  }
67  closedir($dirlist);
68  }
69 
73  public static function utf8_encode(string $string): string
74  {
75  global $DIC;
76  return $DIC->refinery()->string()->encoding()->latin1ToUtf8()->transform($string);
77  }
78 
82  public static function getValidFilename(string $a_filename): string
83  {
84  global $DIC;
85  $sanitizer = new ilFileServicesFilenameSanitizer(
86  $DIC->fileServiceSettings()
87  );
88 
89  return $sanitizer->sanitize($a_filename);
90  }
91 
95  public static function rename(string $a_source, string $a_target): bool
96  {
97  $pi = pathinfo($a_target);
98  global $DIC;
99  $sanitizer = new ilFileServicesFilenameSanitizer(
100  $DIC->fileServiceSettings()
101  );
102 
103  if (!$sanitizer->isClean($a_target)) {
104  throw new ilFileUtilsException("Invalid target file");
105  }
106 
107  return rename($a_source, $a_target);
108  }
109 
127  public static function rCopy(
128  string $a_sdir,
129  string $a_tdir,
130  bool $preserveTimeAttributes = false
131  ): bool {
132  $sourceFS = LegacyPathHelper::deriveFilesystemFrom($a_sdir);
133  $targetFS = LegacyPathHelper::deriveFilesystemFrom($a_tdir);
134 
135  $sourceDir = LegacyPathHelper::createRelativePath($a_sdir);
136  $targetDir = LegacyPathHelper::createRelativePath($a_tdir);
137 
138  // check if arguments are directories
139  if (!$sourceFS->hasDir($sourceDir)) {
140  return false;
141  }
142 
143  $sourceList = $sourceFS->listContents($sourceDir, true);
144 
145  foreach ($sourceList as $item) {
146  if ($item->isDir()) {
147  continue;
148  }
149  try {
150  $itemPath = $targetDir . '/' . substr(
151  $item->getPath(),
152  strlen($sourceDir)
153  );
154  $stream = $sourceFS->readStream($item->getPath());
155  $targetFS->writeStream($itemPath, $stream);
156  } catch (\ILIAS\Filesystem\Exception\FileAlreadyExistsException $e) {
157  // Do nothing with that type of exception
158  }
159  }
160 
161  return true;
162  }
163 
183  public static function makeDirParents(string $a_dir): bool
184  {
185  $dirs = [$a_dir];
186  $a_dir = dirname($a_dir);
187  $last_dirname = '';
188  while ($last_dirname != $a_dir) {
189  array_unshift($dirs, $a_dir);
190  $last_dirname = $a_dir;
191  $a_dir = dirname($a_dir);
192  }
193 
194  // find the first existing dir
195  $reverse_paths = array_reverse($dirs, true);
196  $found_index = -1;
197  foreach ($reverse_paths as $key => $value) {
198  if ($found_index == -1) {
199  if (is_dir($value)) {
200  $found_index = $key;
201  }
202  }
203  }
204 
205  $old_mask = umask(0000);
206  foreach ($dirs as $dirindex => $dir) {
207  // starting with the longest existing path
208  if ($dirindex >= $found_index) {
209  if (!file_exists($dir)) {
210  if (strcmp(substr($dir, strlen($dir) - 1, 1), "/") == 0) {
211  // on some systems there is an error when there is a slash
212  // at the end of a directory in mkdir, see Mantis #2554
213  $dir = substr($dir, 0, strlen($dir) - 1);
214  }
215  if (!mkdir($dir)) {
216  error_log("Can't make directory: $dir");
217  return false;
218  }
219  } elseif (!is_dir($dir)) {
220  error_log("$dir is not a directory");
221  return false;
222  } else {
223  // get umask of the last existing parent directory
224  $umask = fileperms($dir);
225  }
226  }
227  }
228  umask($old_mask);
229 
230  return true;
231  }
232 
243  public static function getDataDir(): string
244  {
245  return CLIENT_DATA_DIR;
246  }
247 
256  public static function dirsize(string $directory): int
257  {
258  $size = 0;
259  if (!is_dir($directory)) {
260  // dirsize of non-existing directory
261  $size = @filesize($directory);
262  return ($size === false) ? -1 : $size;
263  }
264  if ($DIR = opendir($directory)) {
265  while (($dirfile = readdir($DIR)) !== false) {
266  if (is_link(
267  $directory . DIRECTORY_SEPARATOR . $dirfile
268  ) || $dirfile == '.' || $dirfile == '..') {
269  continue;
270  }
271  if (is_file($directory . DIRECTORY_SEPARATOR . $dirfile)) {
272  $size += filesize(
273  $directory . DIRECTORY_SEPARATOR . $dirfile
274  );
275  } elseif (is_dir($directory . DIRECTORY_SEPARATOR . $dirfile)) {
276  $dirSize = ilFileUtils::dirsize(
277  $directory . DIRECTORY_SEPARATOR . $dirfile
278  );
279  if ($dirSize >= 0) {
280  $size += $dirSize;
281  } else {
282  return -1;
283  }
284  }
285  }
286  closedir($DIR);
287  }
288  return $size;
289  }
290 
309  public static function makeDir(string $a_dir): bool
310  {
311  $a_dir = trim($a_dir);
312 
313  // remove trailing slash (bugfix for php 4.2.x)
314  if (substr($a_dir, -1) == "/") {
315  $a_dir = substr($a_dir, 0, -1);
316  }
317 
318  // check if a_dir comes with a path
319  if (!($path = substr(
320  $a_dir,
321  0,
322  strrpos($a_dir, "/") - strlen($a_dir)
323  ))) {
324  $path = ".";
325  }
326 
327  // create directory with file permissions of parent directory
328  if (is_dir($a_dir)) {
329  return true;
330  }
331  $old_mask = umask(0000);
332  $result = @mkdir($a_dir, fileperms($path));
333  umask($old_mask);
334 
335  return $result;
336  }
337 
338  protected static function sanitateTargetPath(string $a_target): array
339  {
340  switch (true) {
341  case strpos($a_target, ILIAS_WEB_DIR . '/' . CLIENT_ID) === 0:
342  case strpos(
343  $a_target,
344  './' . ILIAS_WEB_DIR . '/' . CLIENT_ID
345  ) === 0:
346  case strpos($a_target, CLIENT_WEB_DIR) === 0:
347  $targetFilesystem = \ILIAS\FileUpload\Location::WEB;
348  break;
349  case strpos($a_target, CLIENT_DATA_DIR . "/temp") === 0:
350  $targetFilesystem = \ILIAS\FileUpload\Location::TEMPORARY;
351  break;
352  case strpos($a_target, CLIENT_DATA_DIR) === 0:
353  $targetFilesystem = \ILIAS\FileUpload\Location::STORAGE;
354  break;
355  case strpos($a_target, ILIAS_ABSOLUTE_PATH . '/Customizing') === 0:
356  $targetFilesystem = \ILIAS\FileUpload\Location::CUSTOMIZING;
357  break;
358  default:
359  throw new InvalidArgumentException(
360  "Can not move files to \"$a_target\" because path can not be mapped to web, storage or customizing location."
361  );
362  }
363 
364  $absTargetDir = dirname($a_target);
365  $targetDir = LegacyPathHelper::createRelativePath($absTargetDir);
366 
367  return [$targetFilesystem, $targetDir];
368  }
369 
389  public static function moveUploadedFile(
390  string $a_file,
391  string $a_name,
392  string $a_target,
393  bool $a_raise_errors = true,
394  string $a_mode = "move_uploaded"
395  ): bool {
396  global $DIC;
397  $main_tpl = $DIC->ui()->mainTemplate();
398  $target_filename = basename($a_target);
399 
400  $target_filename = ilFileUtils::getValidFilename($target_filename);
401 
402  // Make sure the target is in a valid subfolder. (e.g. no uploads to ilias/setup/....)
403  [$target_filesystem, $target_dir] = self::sanitateTargetPath($a_target);
404 
405  $upload = $DIC->upload();
406 
407  // If the upload has not yet been processed make sure he gets processed now.
408  if (!$upload->hasBeenProcessed()) {
409  $upload->process();
410  }
411 
412  try {
413  if (!$upload->hasUploads()) {
414  throw new ilException(
415  $DIC->language()->txt("upload_error_file_not_found")
416  );
417  }
418  $upload_result = $upload->getResults()[$a_file] ?? null;
419  if ($upload_result instanceof UploadResult) {
420  if (!$upload_result->isOK()) {
421  throw new ilException($upload_result->getStatus()->getMessage());
422  }
423  } else {
424  return false;
425  }
426  } catch (ilException $e) {
427  if (!$a_raise_errors) {
428  $main_tpl->setOnScreenMessage('failure', $e->getMessage(), true);
429  } else {
430  throw $e;
431  }
432 
433  return false;
434  }
435 
436  $upload->moveOneFileTo(
437  $upload_result,
438  $target_dir,
439  $target_filesystem,
440  $target_filename,
441  true
442  );
443 
444  return true;
445  }
446 
450  public static function zip(
451  string $a_dir,
452  string $a_file,
453  bool $compress_content = false
454  ): bool {
455  global $DIC;
456  // ensure top directory should be the same behaviour as before, if you need it to be different, you should legacyArchives directly
457  return $DIC->legacyArchives()->zip($a_dir, $a_file, true);
458  }
459 
475  public static function delDir(string $a_dir, bool $a_clean_only = false): void
476  {
477  if (!is_dir($a_dir) || is_int(strpos($a_dir, ".."))) {
478  return;
479  }
480 
481  $current_dir = opendir($a_dir);
482 
483  $files = [];
484 
485  // this extra loop has been necessary because of a strange bug
486  // at least on MacOS X. A looped readdir() didn't work
487  // correctly with larger directories
488  // when an unlink happened inside the loop. Getting all files
489  // into the memory first solved the problem.
490  while ($entryname = readdir($current_dir)) {
491  $files[] = $entryname;
492  }
493 
494  foreach ($files as $file) {
495  if (is_dir(
496  $a_dir . "/" . $file
497  ) and ($file != "." and $file != "..")) {
498  ilFileUtils::delDir($a_dir . "/" . $file);
499  } elseif ($file != "." and $file != "..") {
500  unlink($a_dir . "/" . $file);
501  }
502  }
503 
504  closedir($current_dir);
505  if (!$a_clean_only) {
506  @rmdir($a_dir);
507  }
508  }
509 
510  public static function getSafeFilename(string $a_initial_filename): string
511  {
512  $file_peaces = explode('.', $a_initial_filename);
513 
514  $file_extension = array_pop($file_peaces);
515 
516  if (SUFFIX_REPL_ADDITIONAL) {
517  $string_extensions = SUFFIX_REPL_DEFAULT . "," . SUFFIX_REPL_ADDITIONAL;
518  } else {
519  $string_extensions = SUFFIX_REPL_DEFAULT;
520  }
521 
522  $sufixes = explode(",", $string_extensions);
523 
524  if (in_array($file_extension, $sufixes)) {
525  $file_extension = "sec";
526  }
527 
528  $file_peaces[] = $file_extension;
529 
530  $safe_filename = "";
531  foreach ($file_peaces as $piece) {
532  $safe_filename .= "$piece";
533  if ($piece != end($file_peaces)) {
534  $safe_filename .= ".";
535  }
536  }
537 
538  return $safe_filename;
539  }
540 
556  public static function getDir(
557  string $a_dir,
558  bool $a_rec = false,
559  ?string $a_sub_dir = ""
560  ): array {
561  $current_dir = opendir($a_dir . $a_sub_dir);
562 
563  $dirs = [];
564  $files = [];
565  $subitems = [];
566  while ($entry = readdir($current_dir)) {
567  if (is_dir($a_dir . "/" . $entry)) {
568  $dirs[$entry] = ["type" => "dir",
569  "entry" => $entry,
570  "subdir" => $a_sub_dir
571  ];
572  if ($a_rec && $entry != "." && $entry != "..") {
573  $si = ilFileUtils::getDir(
574  $a_dir,
575  true,
576  $a_sub_dir . "/" . $entry
577  );
578  $subitems = array_merge($subitems, $si);
579  }
580  } else {
581  if ($entry != "." && $entry != "..") {
582  $size = filesize($a_dir . $a_sub_dir . "/" . $entry);
583  $files[$entry] = ["type" => "file",
584  "entry" => $entry,
585  "size" => $size,
586  "subdir" => $a_sub_dir
587  ];
588  }
589  }
590  }
591  ksort($dirs);
592  ksort($files);
593 
594  return array_merge($dirs, $files, $subitems);
595  }
596 
612  public static function getWebspaceDir(string $mode = "filesystem"): string
613  {
614  if ($mode === "filesystem") {
615  return "./" . ILIAS_WEB_DIR . "/" . CLIENT_ID;
616  } else {
617  if (defined("ILIAS_MODULE")) {
618  return "../" . ILIAS_WEB_DIR . "/" . CLIENT_ID;
619  } else {
620  return "./" . ILIAS_WEB_DIR . "/" . CLIENT_ID;
621  }
622  }
623  }
624 
637  public static function createDirectory(string $a_dir, int $a_mod = 0755): void
638  {
639  ilFileUtils::makeDir($a_dir);
640  }
641 
642  public static function getFileSizeInfo(): string
643  {
644  global $DIC;
645  $size = new DataSize(self::getPhpUploadSizeLimitInBytes(), DataSize::MB);
646  $max_filesize = $size->__toString();
647  $lng = $DIC->language();
648 
649  return $lng->txt("file_notice") . " $max_filesize.";
650  }
651 
655  public static function getASCIIFilename(string $a_filename): string
656  {
657  global $DIC;
658  $policy = new ilFileServicesPolicy($DIC->fileServiceSettings());
659  return $policy->ascii($a_filename);
660  }
661 
669  public static function ilTempnam(?string $a_temp_path = null): string
670  {
671  if ($a_temp_path === null) {
672  $temp_path = ilFileUtils::getDataDir() . "/temp";
673  } else {
674  $temp_path = $a_temp_path;
675  }
676 
677  if (!is_dir($temp_path)) {
678  ilFileUtils::createDirectory($temp_path);
679  }
680  $temp_name = $temp_path . "/" . uniqid("tmp");
681 
682  return $temp_name;
683  }
684 
693  public static function unzip(string $a_file, bool $overwrite = false, bool $a_flat = false): bool
694  {
695  if(defined('DEVMODE') && DEVMODE) {
696  trigger_error('Deprecated method called: ' . __METHOD__, E_USER_DEPRECATED);
697  }
698 
699  global $DIC;
700  return $DIC->legacyArchives()->unzip(
701  $a_file,
702  null,
703  $overwrite,
704  $a_flat,
705  false
706  );
707  }
708 
712  public static function renameExecutables(string $a_dir): void
713  {
714  $def_arr = explode(",", SUFFIX_REPL_DEFAULT);
715  foreach ($def_arr as $def) {
716  self::rRenameSuffix($a_dir, trim($def), "sec");
717  }
718 
719  $def_arr = explode(",", SUFFIX_REPL_ADDITIONAL);
720  foreach ($def_arr as $def) {
721  self::rRenameSuffix($a_dir, trim($def), "sec");
722  }
723  }
724 
731  public static function rRenameSuffix(string $a_dir, string $a_old_suffix, string $a_new_suffix): bool
732  {
733  if ($a_dir == "/" || $a_dir == "" || is_int(strpos($a_dir, ".."))
734  || trim($a_old_suffix) == "") {
735  return false;
736  }
737 
738  // check if argument is directory
739  if (!@is_dir($a_dir)) {
740  return false;
741  }
742 
743  // read a_dir
744  $dir = opendir($a_dir);
745 
746  while ($file = readdir($dir)) {
747  if ($file !== "." && $file !== "..") {
748  // triple dot is not allowed in filenames
749  if ($file === '...') {
750  unlink($a_dir . "/" . $file);
751  continue;
752  }
753  // directories
754  if (@is_dir($a_dir . "/" . $file)) {
755  ilFileUtils::rRenameSuffix($a_dir . "/" . $file, $a_old_suffix, $a_new_suffix);
756  }
757 
758  // files
759  if (@is_file($a_dir . "/" . $file)) {
760  // first check for files with trailing dot
761  if (strrpos($file, '.') == (strlen($file) - 1)) {
762  try {
763  rename($a_dir . '/' . $file, substr($a_dir . '/' . $file, 0, -1));
764  } catch (Throwable $t) {
765  // to avoid exploits we do delete this file and continue renaming
766  unlink($a_dir . '/' . $file);
767  continue;
768  }
769  $file = substr($file, 0, -1);
770  }
771 
772  $path_info = pathinfo($a_dir . "/" . $file);
773 
774  if (strtolower($path_info["extension"] ?? '') === strtolower($a_old_suffix)) {
775  $pos = strrpos($a_dir . "/" . $file, ".");
776  $new_name = substr($a_dir . "/" . $file, 0, $pos) . "." . $a_new_suffix;
777  // check if file exists
778  if (file_exists($new_name)) {
779  if (is_dir($new_name)) {
780  ilFileUtils::delDir($new_name);
781  } else {
782  unlink($new_name);
783  }
784  }
785  rename($a_dir . "/" . $file, $new_name);
786  }
787  }
788  }
789  }
790  return true;
791  }
792 
793  public static function removeTrailingPathSeparators(string $path): string
794  {
795  $path = preg_replace("/[\/\\\]+$/", "", $path);
796  return (string) $path;
797  }
798 
802  public static function getPhpUploadSizeLimitInBytes(): string
803  {
804  $convertPhpIniSizeValueToBytes = function ($phpIniSizeValue) {
805  if (is_numeric($phpIniSizeValue)) {
806  return $phpIniSizeValue;
807  }
808 
809  $suffix = substr($phpIniSizeValue, -1);
810  $value = substr($phpIniSizeValue, 0, -1);
811 
812  switch (strtoupper($suffix)) {
813  case 'P':
814  $value *= 1024;
815  // no break
816  case 'T':
817  $value *= 1024;
818  // no break
819  case 'G':
820  $value *= 1024;
821  // no break
822  case 'M':
823  $value *= 1024;
824  // no break
825  case 'K':
826  $value *= 1024;
827  break;
828  }
829 
830  return $value;
831  };
832 
833 
834  $uploadSizeLimitBytes = min(
835  $convertPhpIniSizeValueToBytes(ini_get('post_max_size')),
836  $convertPhpIniSizeValueToBytes(ini_get('upload_max_filesize'))
837  );
838 
839  return $uploadSizeLimitBytes;
840  }
841 
842  public static function _sanitizeFilemame(string $a_filename): string
843  {
844  return strip_tags(ilUtil::stripSlashes($a_filename));
845  }
846 }
static getWebspaceDir(string $mode="filesystem")
get webspace directory
Class ilFileServicesPolicy.
const STORAGE
The filesystem outside of the ilias web root.
Definition: Location.php:40
static utf8_encode(string $string)
Class ChatMainBarProvider .
static stripSlashes(string $a_str, bool $a_strip_html=true, string $a_allow="")
This class provides the data size with additional information to remove the work to calculate the siz...
Definition: DataSize.php:30
static unzip(string $a_file, bool $overwrite=false, bool $a_flat=false)
unzip 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 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 makeDirParents(string $a_dir)
Create a new directory and all parent directories.
$path
Definition: ltiservices.php:32
static removeTrailingPathSeparators(string $path)
static getSafeFilename(string $a_initial_filename)
static getASCIIFilename(string $a_filename)
global $DIC
Definition: feed.php:28
static renameExecutables(string $a_dir)
const CLIENT_DATA_DIR
Definition: constants.php:46
static recursive_dirscan(string $dir, array &$arr)
Recursively scans a given directory and writes path and filename into referenced array.
static dirsize(string $directory)
get size of a directory or a file.
$lng
static delDir(string $a_dir, bool $a_clean_only=false)
removes a dir and all its content (subdirs and files) recursively
const CLIENT_ID
Definition: constants.php:41
static createDirectory(string $a_dir, int $a_mod=0755)
create directory
string $key
Consumer key/client ID value.
Definition: System.php:193
const CUSTOMIZING
The filesystem within the web root where all the skins and plugins are saved.
Definition: Location.php:45
static getDir(string $a_dir, bool $a_rec=false, ?string $a_sub_dir="")
get directory
const CLIENT_WEB_DIR
Definition: constants.php:47
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 getDataDir()
get data directory (outside webspace)
static getFileSizeInfo()
static sanitateTargetPath(string $a_target)
static _sanitizeFilemame(string $a_filename)
Class ilFileServicesFilenameSanitizer.
static ilTempnam(?string $a_temp_path=null)
Returns a unique and non existing Path for e temporary file or directory.
static zip(string $a_dir, string $a_file, bool $compress_content=false)
const TEMPORARY
The ILIAS temporary directory.
Definition: Location.php:50
static getPhpUploadSizeLimitInBytes()
static rename(string $a_source, string $a_target)
const WEB
The filesystem within the ilias web root.
Definition: Location.php:35
static makeDir(string $a_dir)
creates a new directory and inherits all filesystem permissions of the parent directory You may pass ...
const ILIAS_WEB_DIR
Definition: constants.php:45