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 39 and 401]

   1  <?php
   2  
   3  namespace Box\Spout\Common\Helper;
   4  
   5  use Box\Spout\Common\Exception\IOException;
   6  
   7  /**
   8   * Class FileSystemHelper
   9   * This class provides helper functions to help with the file system operations
  10   * like files/folders creation & deletion
  11   */
  12  class FileSystemHelper implements FileSystemHelperInterface
  13  {
  14      /** @var string Real path of the base folder where all the I/O can occur */
  15      protected $baseFolderRealPath;
  16  
  17      /**
  18       * @param string $baseFolderPath The path of the base folder where all the I/O can occur
  19       */
  20      public function __construct(string $baseFolderPath)
  21      {
  22          $this->baseFolderRealPath = \realpath($baseFolderPath);
  23      }
  24  
  25      /**
  26       * Creates an empty folder with the given name under the given parent folder.
  27       *
  28       * @param string $parentFolderPath The parent folder path under which the folder is going to be created
  29       * @param string $folderName The name of the folder to create
  30       * @throws \Box\Spout\Common\Exception\IOException If unable to create the folder or if the folder path is not inside of the base folder
  31       * @return string Path of the created folder
  32       */
  33      public function createFolder($parentFolderPath, $folderName)
  34      {
  35          $this->throwIfOperationNotInBaseFolder($parentFolderPath);
  36  
  37          $folderPath = $parentFolderPath . '/' . $folderName;
  38  
  39          $wasCreationSuccessful = \mkdir($folderPath, 0777, true);
  40          if (!$wasCreationSuccessful) {
  41              throw new IOException("Unable to create folder: $folderPath");
  42          }
  43  
  44          return $folderPath;
  45      }
  46  
  47      /**
  48       * Creates a file with the given name and content in the given folder.
  49       * The parent folder must exist.
  50       *
  51       * @param string $parentFolderPath The parent folder path where the file is going to be created
  52       * @param string $fileName The name of the file to create
  53       * @param string $fileContents The contents of the file to create
  54       * @throws \Box\Spout\Common\Exception\IOException If unable to create the file or if the file path is not inside of the base folder
  55       * @return string Path of the created file
  56       */
  57      public function createFileWithContents($parentFolderPath, $fileName, $fileContents)
  58      {
  59          $this->throwIfOperationNotInBaseFolder($parentFolderPath);
  60  
  61          $filePath = $parentFolderPath . '/' . $fileName;
  62  
  63          $wasCreationSuccessful = \file_put_contents($filePath, $fileContents);
  64          if ($wasCreationSuccessful === false) {
  65              throw new IOException("Unable to create file: $filePath");
  66          }
  67  
  68          return $filePath;
  69      }
  70  
  71      /**
  72       * Delete the file at the given path
  73       *
  74       * @param string $filePath Path of the file to delete
  75       * @throws \Box\Spout\Common\Exception\IOException If the file path is not inside of the base folder
  76       * @return void
  77       */
  78      public function deleteFile($filePath)
  79      {
  80          $this->throwIfOperationNotInBaseFolder($filePath);
  81  
  82          if (\file_exists($filePath) && \is_file($filePath)) {
  83              \unlink($filePath);
  84          }
  85      }
  86  
  87      /**
  88       * Delete the folder at the given path as well as all its contents
  89       *
  90       * @param string $folderPath Path of the folder to delete
  91       * @throws \Box\Spout\Common\Exception\IOException If the folder path is not inside of the base folder
  92       * @return void
  93       */
  94      public function deleteFolderRecursively($folderPath)
  95      {
  96          $this->throwIfOperationNotInBaseFolder($folderPath);
  97  
  98          $itemIterator = new \RecursiveIteratorIterator(
  99              new \RecursiveDirectoryIterator($folderPath, \RecursiveDirectoryIterator::SKIP_DOTS),
 100              \RecursiveIteratorIterator::CHILD_FIRST
 101          );
 102  
 103          foreach ($itemIterator as $item) {
 104              if ($item->isDir()) {
 105                  \rmdir($item->getPathname());
 106              } else {
 107                  \unlink($item->getPathname());
 108              }
 109          }
 110  
 111          \rmdir($folderPath);
 112      }
 113  
 114      /**
 115       * All I/O operations must occur inside the base folder, for security reasons.
 116       * This function will throw an exception if the folder where the I/O operation
 117       * should occur is not inside the base folder.
 118       *
 119       * @param string $operationFolderPath The path of the folder where the I/O operation should occur
 120       * @throws \Box\Spout\Common\Exception\IOException If the folder where the I/O operation should occur
 121       * is not inside the base folder or the base folder does not exist
 122       * @return void
 123       */
 124      protected function throwIfOperationNotInBaseFolder(string $operationFolderPath)
 125      {
 126          $operationFolderRealPath = \realpath($operationFolderPath);
 127          if (!$this->baseFolderRealPath) {
 128              throw new IOException("The base folder path is invalid: {$this->baseFolderRealPath}");
 129          }
 130          $isInBaseFolder = (\strpos($operationFolderRealPath, $this->baseFolderRealPath) === 0);
 131          if (!$isInBaseFolder) {
 132              throw new IOException("Cannot perform I/O operation outside of the base folder: {$this->baseFolderRealPath}");
 133          }
 134      }
 135  }