Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 401 and 403]

   1  <?php
   2  
   3  namespace PhpOffice\PhpSpreadsheet\Shared;
   4  
   5  use PhpOffice\PhpSpreadsheet\Exception;
   6  use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
   7  use ZipArchive;
   8  
   9  class File
  10  {
  11      /**
  12       * Use Temp or File Upload Temp for temporary files.
  13       *
  14       * @var bool
  15       */
  16      protected static $useUploadTempDirectory = false;
  17  
  18      /**
  19       * Set the flag indicating whether the File Upload Temp directory should be used for temporary files.
  20       */
  21      public static function setUseUploadTempDirectory(bool $useUploadTempDir): void
  22      {
  23          self::$useUploadTempDirectory = (bool) $useUploadTempDir;
  24      }
  25  
  26      /**
  27       * Get the flag indicating whether the File Upload Temp directory should be used for temporary files.
  28       */
  29      public static function getUseUploadTempDirectory(): bool
  30      {
  31          return self::$useUploadTempDirectory;
  32      }
  33  
  34      // https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
  35      // Section 4.3.7
  36      // Looks like there might be endian-ness considerations
  37      private const ZIP_FIRST_4 = [
  38          "\x50\x4b\x03\x04", // what it looks like on my system
  39          "\x04\x03\x4b\x50", // what it says in documentation
  40      ];
  41  
  42      private static function validateZipFirst4(string $zipFile): bool
  43      {
  44          $contents = @file_get_contents($zipFile, false, null, 0, 4);
  45  
  46          return in_array($contents, self::ZIP_FIRST_4, true);
  47      }
  48  
  49      /**
  50       * Verify if a file exists.
  51       */
  52      public static function fileExists(string $filename): bool
  53      {
  54          // Sick construction, but it seems that
  55          // file_exists returns strange values when
  56          // doing the original file_exists on ZIP archives...
  57          if (strtolower(substr($filename, 0, 6)) == 'zip://') {
  58              // Open ZIP file and verify if the file exists
  59              $zipFile = substr($filename, 6, strrpos($filename, '#') - 6);
  60              $archiveFile = substr($filename, strrpos($filename, '#') + 1);
  61  
  62              if (self::validateZipFirst4($zipFile)) {
  63                  $zip = new ZipArchive();
  64                  $res = $zip->open($zipFile);
  65                  if ($res === true) {
  66                      $returnValue = ($zip->getFromName($archiveFile) !== false);
  67                      $zip->close();
  68  
  69                      return $returnValue;
  70                  }
  71              }
  72  
  73              return false;
  74          }
  75  
  76          return file_exists($filename);
  77      }
  78  
  79      /**
  80       * Returns canonicalized absolute pathname, also for ZIP archives.
  81       */
  82      public static function realpath(string $filename): string
  83      {
  84          // Returnvalue
  85          $returnValue = '';
  86  
  87          // Try using realpath()
  88          if (file_exists($filename)) {
  89              $returnValue = realpath($filename) ?: '';
  90          }
  91  
  92          // Found something?
  93          if ($returnValue === '') {
  94              $pathArray = explode('/', $filename);
  95              while (in_array('..', $pathArray) && $pathArray[0] != '..') {
  96                  $iMax = count($pathArray);
  97                  for ($i = 0; $i < $iMax; ++$i) {
  98                      if ($pathArray[$i] == '..' && $i > 0) {
  99                          unset($pathArray[$i], $pathArray[$i - 1]);
 100  
 101                          break;
 102                      }
 103                  }
 104              }
 105              $returnValue = implode('/', $pathArray);
 106          }
 107  
 108          // Return
 109          return $returnValue;
 110      }
 111  
 112      /**
 113       * Get the systems temporary directory.
 114       */
 115      public static function sysGetTempDir(): string
 116      {
 117          // Moodle hack!
 118          if (function_exists('make_temp_directory')) {
 119              $temp = make_temp_directory('phpspreadsheet');
 120              return realpath(dirname($temp));
 121          }
 122  
 123          $path = sys_get_temp_dir();
 124          if (self::$useUploadTempDirectory) {
 125              //  use upload-directory when defined to allow running on environments having very restricted
 126              //      open_basedir configs
 127              if (ini_get('upload_tmp_dir') !== false) {
 128                  if ($temp = ini_get('upload_tmp_dir')) {
 129                      if (file_exists($temp)) {
 130                          $path = $temp;
 131                      }
 132                  }
 133              }
 134          }
 135  
 136          return realpath($path) ?: '';
 137      }
 138  
 139      public static function temporaryFilename(): string
 140      {
 141          $filename = tempnam(self::sysGetTempDir(), 'phpspreadsheet');
 142          if ($filename === false) {
 143              throw new Exception('Could not create temporary file');
 144          }
 145  
 146          return $filename;
 147      }
 148  
 149      /**
 150       * Assert that given path is an existing file and is readable, otherwise throw exception.
 151       */
 152      public static function assertFile(string $filename, string $zipMember = ''): void
 153      {
 154          if (!is_file($filename)) {
 155              throw new ReaderException('File "' . $filename . '" does not exist.');
 156          }
 157  
 158          if (!is_readable($filename)) {
 159              throw new ReaderException('Could not open "' . $filename . '" for reading.');
 160          }
 161  
 162          if ($zipMember !== '') {
 163              $zipfile = "zip://$filename#$zipMember";
 164              if (!self::fileExists($zipfile)) {
 165                  throw new ReaderException("Could not find zip member $zipfile");
 166              }
 167          }
 168      }
 169  
 170      /**
 171       * Same as assertFile, except return true/false and don't throw Exception.
 172       */
 173      public static function testFileNoThrow(string $filename, ?string $zipMember = null): bool
 174      {
 175          if (!is_file($filename)) {
 176              return false;
 177          }
 178          if (!is_readable($filename)) {
 179              return false;
 180          }
 181          if ($zipMember === null) {
 182              return true;
 183          }
 184          // validate zip, but don't check specific member
 185          if ($zipMember === '') {
 186              return self::validateZipFirst4($filename);
 187          }
 188  
 189          return self::fileExists("zip://$filename#$zipMember");
 190      }
 191  }