Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.
   1  <?php
   2  
   3  declare(strict_types=1);
   4  
   5  namespace GuzzleHttp\Psr7;
   6  
   7  use InvalidArgumentException;
   8  use Psr\Http\Message\StreamInterface;
   9  use Psr\Http\Message\UploadedFileInterface;
  10  use RuntimeException;
  11  
  12  class UploadedFile implements UploadedFileInterface
  13  {
  14      private const ERRORS = [
  15          UPLOAD_ERR_OK,
  16          UPLOAD_ERR_INI_SIZE,
  17          UPLOAD_ERR_FORM_SIZE,
  18          UPLOAD_ERR_PARTIAL,
  19          UPLOAD_ERR_NO_FILE,
  20          UPLOAD_ERR_NO_TMP_DIR,
  21          UPLOAD_ERR_CANT_WRITE,
  22          UPLOAD_ERR_EXTENSION,
  23      ];
  24  
  25      /**
  26       * @var string|null
  27       */
  28      private $clientFilename;
  29  
  30      /**
  31       * @var string|null
  32       */
  33      private $clientMediaType;
  34  
  35      /**
  36       * @var int
  37       */
  38      private $error;
  39  
  40      /**
  41       * @var string|null
  42       */
  43      private $file;
  44  
  45      /**
  46       * @var bool
  47       */
  48      private $moved = false;
  49  
  50      /**
  51       * @var int|null
  52       */
  53      private $size;
  54  
  55      /**
  56       * @var StreamInterface|null
  57       */
  58      private $stream;
  59  
  60      /**
  61       * @param StreamInterface|string|resource $streamOrFile
  62       */
  63      public function __construct(
  64          $streamOrFile,
  65          ?int $size,
  66          int $errorStatus,
  67          string $clientFilename = null,
  68          string $clientMediaType = null
  69      ) {
  70          $this->setError($errorStatus);
  71          $this->size = $size;
  72          $this->clientFilename = $clientFilename;
  73          $this->clientMediaType = $clientMediaType;
  74  
  75          if ($this->isOk()) {
  76              $this->setStreamOrFile($streamOrFile);
  77          }
  78      }
  79  
  80      /**
  81       * Depending on the value set file or stream variable
  82       *
  83       * @param StreamInterface|string|resource $streamOrFile
  84       *
  85       * @throws InvalidArgumentException
  86       */
  87      private function setStreamOrFile($streamOrFile): void
  88      {
  89          if (is_string($streamOrFile)) {
  90              $this->file = $streamOrFile;
  91          } elseif (is_resource($streamOrFile)) {
  92              $this->stream = new Stream($streamOrFile);
  93          } elseif ($streamOrFile instanceof StreamInterface) {
  94              $this->stream = $streamOrFile;
  95          } else {
  96              throw new InvalidArgumentException(
  97                  'Invalid stream or file provided for UploadedFile'
  98              );
  99          }
 100      }
 101  
 102      /**
 103       * @throws InvalidArgumentException
 104       */
 105      private function setError(int $error): void
 106      {
 107          if (false === in_array($error, UploadedFile::ERRORS, true)) {
 108              throw new InvalidArgumentException(
 109                  'Invalid error status for UploadedFile'
 110              );
 111          }
 112  
 113          $this->error = $error;
 114      }
 115  
 116      private function isStringNotEmpty($param): bool
 117      {
 118          return is_string($param) && false === empty($param);
 119      }
 120  
 121      /**
 122       * Return true if there is no upload error
 123       */
 124      private function isOk(): bool
 125      {
 126          return $this->error === UPLOAD_ERR_OK;
 127      }
 128  
 129      public function isMoved(): bool
 130      {
 131          return $this->moved;
 132      }
 133  
 134      /**
 135       * @throws RuntimeException if is moved or not ok
 136       */
 137      private function validateActive(): void
 138      {
 139          if (false === $this->isOk()) {
 140              throw new RuntimeException('Cannot retrieve stream due to upload error');
 141          }
 142  
 143          if ($this->isMoved()) {
 144              throw new RuntimeException('Cannot retrieve stream after it has already been moved');
 145          }
 146      }
 147  
 148      public function getStream(): StreamInterface
 149      {
 150          $this->validateActive();
 151  
 152          if ($this->stream instanceof StreamInterface) {
 153              return $this->stream;
 154          }
 155  
 156          /** @var string $file */
 157          $file = $this->file;
 158  
 159          return new LazyOpenStream($file, 'r+');
 160      }
 161  
 162      public function moveTo($targetPath): void
 163      {
 164          $this->validateActive();
 165  
 166          if (false === $this->isStringNotEmpty($targetPath)) {
 167              throw new InvalidArgumentException(
 168                  'Invalid path provided for move operation; must be a non-empty string'
 169              );
 170          }
 171  
 172          if ($this->file) {
 173              $this->moved = PHP_SAPI === 'cli'
 174                  ? rename($this->file, $targetPath)
 175                  : move_uploaded_file($this->file, $targetPath);
 176          } else {
 177              Utils::copyToStream(
 178                  $this->getStream(),
 179                  new LazyOpenStream($targetPath, 'w')
 180              );
 181  
 182              $this->moved = true;
 183          }
 184  
 185          if (false === $this->moved) {
 186              throw new RuntimeException(
 187                  sprintf('Uploaded file could not be moved to %s', $targetPath)
 188              );
 189          }
 190      }
 191  
 192      public function getSize(): ?int
 193      {
 194          return $this->size;
 195      }
 196  
 197      public function getError(): int
 198      {
 199          return $this->error;
 200      }
 201  
 202      public function getClientFilename(): ?string
 203      {
 204          return $this->clientFilename;
 205      }
 206  
 207      public function getClientMediaType(): ?string
 208      {
 209          return $this->clientMediaType;
 210      }
 211  }