ILIAS  trunk Revision v11.0_alpha-1723-g8e69f309bab
All Data Structures Namespaces Files Functions Variables Enumerations Enumerator Modules Pages
FileUploadImpl.php
Go to the documentation of this file.
1 <?php
2 
19 namespace ILIAS\FileUpload;
20 
37 use ilFileUtils;
38 
46 final class FileUploadImpl implements FileUpload
47 {
48  private bool $processed = false;
49  private bool $moved = false;
53  private array $uploadResult = [];
57  private array $rejectedUploadResult = [];
61  private ?array $uploadStreams = null;
62 
63 
64 
65 
73  public function __construct(private PreProcessorManager $processorManager, private Filesystems $filesystems, private GlobalHttpState $globalHttpState)
74  {
75  }
76 
81  private function hardRemoveUpload(string $identifier, ProcessingStatus $status): never
82  {
83  // we delete the file from the temporary directory and remove it from the global $_FILES array
84  $file_stream = $this->uploadStreams[$identifier];
85  $uri = $file_stream->getMetadata('uri');
86  $file_stream->close();
87  unlink($uri);
88  unset($this->uploadStreams[$identifier]);
89  unset($_FILES[$identifier]);
90  throw new IllegalStateException($status->getMessage());
91  }
92 
96  public function moveOneFileTo(UploadResult $uploadResult, string $destination, int $location = Location::STORAGE, string $file_name = '', bool $override_existing = false): bool
97  {
98  if (!$this->processed) {
99  throw new \RuntimeException('Can not move unprocessed files.');
100  }
101  $filesystem = $this->selectFilesystem($location);
102  $tempResults = [];
103 
104  if ($uploadResult->getStatus()->getCode() === ProcessingStatus::REJECTED) {
105  return false;
106  }
107 
108  try {
109  $path = rtrim($destination, "/") . '/' . ($file_name === "" ? $uploadResult->getName() : $file_name);
110  if ($override_existing && $filesystem->has($path)) {
111  $filesystem->delete($path);
112  }
113  $filesystem->writeStream($path, Streams::ofPsr7Stream($this->uploadStreams[$uploadResult->getPath()]));
114  $tempResults[] = $this->regenerateUploadResultWithPath($uploadResult, $path);
115  } catch (IOException $ex) {
116  $this->regenerateUploadResultWithCopyError($uploadResult, $ex->getMessage());
117  }
118 
119  return true;
120  }
121 
122 
126  public function moveFilesTo(string $destination, int $location = Location::STORAGE): void
127  {
128  if (!$this->processed) {
129  throw new \RuntimeException('Can not move unprocessed files.');
130  }
131 
132  if ($this->moved) {
133  throw new \RuntimeException('Can not move the files a second time.');
134  }
135 
136  $filesystem = $this->selectFilesystem($location);
137  $tempResults = [];
138 
139  foreach ($this->uploadResult as $key => $uploadResult) {
140  if ($uploadResult->getStatus()->getCode() === ProcessingStatus::REJECTED) {
141  continue;
142  }
143 
144  try {
145  $path = $destination . '/' . $uploadResult->getName();
146  $filesystem->writeStream($path, Streams::ofPsr7Stream($this->uploadStreams[$key]));
147  $tempResults[] = $this->regenerateUploadResultWithPath($uploadResult, $path);
148  } catch (IOException $ex) {
149  $this->regenerateUploadResultWithCopyError($uploadResult, $ex->getMessage());
150  }
151  }
152 
153  $this->uploadResult = $tempResults;
154  $this->uploadStreams = null;
155  $this->moved = true;
156  }
157 
158 
167  private function regenerateUploadResultWithPath(UploadResult $result, string $path): UploadResult
168  {
169  return new UploadResult(
170  $result->getName(),
171  $result->getSize(),
172  $result->getMimeType(),
173  $result->getMetaData(),
174  $result->getStatus(),
175  $path
176  );
177  }
178 
179 
188  private function regenerateUploadResultWithCopyError(UploadResult $result, string $errorReason): UploadResult
189  {
190  return new UploadResult(
191  $result->getName(),
192  $result->getSize(),
193  $result->getMimeType(),
194  $result->getMetaData(),
195  new ProcessingStatus(ProcessingStatus::REJECTED, $errorReason),
196  ''
197  );
198  }
199 
200 
210  private function selectFilesystem(int $location): Filesystem
211  {
212  return match ($location) {
213  Location::CUSTOMIZING => $this->filesystems->customizing(),
214  Location::STORAGE => $this->filesystems->storage(),
215  Location::WEB => $this->filesystems->web(),
216  Location::TEMPORARY => $this->filesystems->temp(),
217  default => throw new \InvalidArgumentException("No filesystem found for location code \"$location\""),
218  };
219  }
220 
221 
225  public function uploadSizeLimit(): int
226  {
228  }
229 
230 
234  public function register(PreProcessor $preProcessor): void
235  {
236  if (!$this->processed) {
237  $this->processorManager->with($preProcessor);
238  } else {
239  throw new IllegalStateException('Can not register processor after the upload was processed.');
240  }
241  }
242 
243 
247  public function process(): void
248  {
249  if ($this->processed) {
250  throw new IllegalStateException('Can not reprocess the uploaded files.');
251  }
252 
253  $uploadedFiles = $this->globalHttpState->request()->getUploadedFiles();
254  $collectFilesFromNestedFields = $this->flattenUploadedFiles($uploadedFiles);
255  foreach ($collectFilesFromNestedFields as $file) {
256  $metadata = new Metadata($file->getClientFilename(), $file->getSize(), $file->getClientMediaType());
257  try {
258  $stream = Streams::ofPsr7Stream($file->getStream());
259  } catch (\RuntimeException) {
260  $this->rejectFailedUpload($metadata);
261  continue;
262  }
263 
264  // we take the temporary file name as an identifier as it is the only unique attribute.
265  $identifier = $file->getStream()->getMetadata('uri');
266 
267  $identifier = is_array($identifier) ? '' : $identifier;
268 
269  $this->uploadStreams[$identifier] = $stream;
270 
271  if ($file->getError() === UPLOAD_ERR_OK) {
272  $processingResult = $this->processorManager->process($stream, $metadata);
273 
274  // we do discard if the result is a DENIED that there is no further pissibility to process the file.
275  if ($processingResult->getCode() === ProcessingStatus::DENIED) {
276  $this->hardRemoveUpload($identifier, $processingResult);
277  continue;
278  }
279 
280  $result = new UploadResult(
281  $metadata->getFilename(),
282  $metadata->getUploadSize(),
283  $metadata->getMimeType(),
284  $metadata->additionalMetaData(),
285  $processingResult,
286  is_string($identifier) ? $identifier : ''
287  );
288  $this->uploadResult[$identifier] = $result;
289  } else {
290  $this->rejectFailedUpload($metadata);
291  }
292  }
293 
294  $this->processed = true;
295  }
296 
297 
303  private function rejectFailedUpload(Metadata $metadata): void
304  {
305  //reject failed upload
306  $processingStatus = new ProcessingStatus(ProcessingStatus::REJECTED, 'Upload failed');
307  $extraMetadata = new ImmutableMapWrapper(new EntryLockingStringMap());
308  $result = new UploadResult(
309  $metadata->getFilename(),
310  $metadata->getUploadSize(),
311  $metadata->getMimeType(),
312  $extraMetadata,
313  $processingStatus,
314  ''
315  );
316 
317  $this->rejectedUploadResult[] = $result;
318  }
319 
320 
324  public function getResults(): array
325  {
326  if ($this->processed) {
327  return array_merge($this->uploadResult, $this->rejectedUploadResult);
328  }
329 
330  throw new IllegalStateException('Can not fetch results without processing the uploads.');
331  }
332 
333 
337  public function hasUploads(): bool
338  {
339  if ($this->moved) {
340  return false;
341  }
342 
343  $uploadedFiles = $this->flattenUploadedFiles($this->globalHttpState->request()->getUploadedFiles());
344 
345  return ($uploadedFiles !== []);
346  }
347 
348 
349  protected function flattenUploadedFiles(array $uploadedFiles): array
350  {
351  $recursiveIterator = new RecursiveIteratorIterator(
353  $uploadedFiles,
354  RecursiveArrayIterator::CHILD_ARRAYS_ONLY
355  ),
356  RecursiveIteratorIterator::LEAVES_ONLY
357  );
358 
359  return iterator_to_array($recursiveIterator, false);
360  }
361 
362 
363  public function hasBeenProcessed(): bool
364  {
365  return $this->processed;
366  }
367 }
const STORAGE
The filesystem outside of the ilias web root.
Definition: Location.php:43
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
regenerateUploadResultWithCopyError(UploadResult $result, string $errorReason)
Creates a clone of the given result and set the status to rejected with the passed error message...
$location
Definition: buildRTE.php:22
selectFilesystem(int $location)
Selects the correct filesystem by the given Location constant.
flattenUploadedFiles(array $uploadedFiles)
$path
Definition: ltiservices.php:29
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
moveFilesTo(string $destination, int $location=Location::STORAGE)
static filesystems()
Returns the loaded filesystems.
__construct(private PreProcessorManager $processorManager, private Filesystems $filesystems, private GlobalHttpState $globalHttpState)
FileUploadImpl constructor.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
rejectFailedUpload(Metadata $metadata)
Reject a failed upload with the given metadata.
moveOneFileTo(UploadResult $uploadResult, string $destination, int $location=Location::STORAGE, string $file_name='', bool $override_existing=false)
Moves a single File (the attributes, metadata and upload-status of which are contained in UploadResul...
const CUSTOMIZING
The filesystem within the web root where all the skins and plugins are saved.
Definition: Location.php:48
hasBeenProcessed()
Return (bool)true if the current upload has already been processed.
Class FileUpload.
Definition: FileUpload.php:37
regenerateUploadResultWithPath(UploadResult $result, string $path)
Generate an exact copy of the result with the given path.
const TEMPORARY
The ILIAS temporary directory.
Definition: Location.php:53
static getPhpUploadSizeLimitInBytes()
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
const WEB
The filesystem within the ilias web root.
Definition: Location.php:38
hardRemoveUpload(string $identifier, ProcessingStatus $status)
This is the very last thing we can do if a preprocessor DENIEs an upload.
static ofPsr7Stream(StreamInterface $stream)
Create a FileStream from a Psr7 compliant stream.
Definition: Streams.php:113