ILIAS  release_8 Revision v8.24
FileUploadImpl.php
Go to the documentation of this file.
1<?php
2
19namespace ILIAS\FileUpload;
20
35use Psr\Http\Message\UploadedFileInterface;
36use RecursiveArrayIterator;
37use RecursiveIteratorIterator;
39use ilFileUtils;
40
48final class FileUploadImpl implements FileUpload
49{
53 private bool $processed;
54 private bool $moved;
58 private array $uploadResult;
62 private array $rejectedUploadResult;
66 private ?array $uploadStreams = null;
67
68
69
70
79 {
80 $this->processorManager = $processorManager;
82 $this->globalHttpState = $globalHttpState;
83 $this->processed = false;
84 $this->moved = false;
85 $this->uploadResult = [];
86 $this->rejectedUploadResult = [];
87 }
88
93 private function hardRemoveUpload(string $identifier, string $rejection_message): void
94 {
95 // we delete the file from the temporary directory and remove it from the global $_FILES array
96 $file_stream = $this->uploadStreams[$identifier];
97 $uri = $file_stream->getMetadata('uri');
98 $file_stream->close();
99 unlink($uri);
100 unset($this->uploadStreams[$identifier]);
101 unset($_FILES[$identifier]);
102 throw new IllegalStateException($rejection_message);
103 }
104
108 public function moveOneFileTo(UploadResult $uploadResult, string $destination, int $location = Location::STORAGE, string $file_name = '', bool $override_existing = false): bool
109 {
110 if (!$this->processed) {
111 throw new \RuntimeException('Can not move unprocessed files.');
112 }
113 $filesystem = $this->selectFilesystem($location);
114 $tempResults = [];
115
116 if ($uploadResult->getStatus()->getCode() == ProcessingStatus::REJECTED) {
117 return false;
118 }
119
120 try {
121 $path = rtrim($destination, "/") . '/' . ($file_name == "" ? $uploadResult->getName() : $file_name);
122 if ($override_existing && $filesystem->has($path)) {
123 $filesystem->delete($path);
124 }
125 $filesystem->writeStream($path, Streams::ofPsr7Stream($this->uploadStreams[$uploadResult->getPath()]));
126 $tempResults[] = $this->regenerateUploadResultWithPath($uploadResult, $path);
127 } catch (IOException $ex) {
128 $this->regenerateUploadResultWithCopyError($uploadResult, $ex->getMessage());
129 }
130
131 return true;
132 }
133
134
138 public function moveFilesTo(string $destination, int $location = Location::STORAGE): void
139 {
140 if (!$this->processed) {
141 throw new \RuntimeException('Can not move unprocessed files.');
142 }
143
144 if ($this->moved) {
145 throw new \RuntimeException('Can not move the files a second time.');
146 }
147
148 $filesystem = $this->selectFilesystem($location);
149 $tempResults = [];
150
151 foreach ($this->uploadResult as $key => $uploadResult) {
152 if ($uploadResult->getStatus()->getCode() == ProcessingStatus::REJECTED) {
153 continue;
154 }
155
156 try {
157 $path = $destination . '/' . $uploadResult->getName();
158 $filesystem->writeStream($path, Streams::ofPsr7Stream($this->uploadStreams[$key]));
159 $tempResults[] = $this->regenerateUploadResultWithPath($uploadResult, $path);
160 } catch (IOException $ex) {
161 $this->regenerateUploadResultWithCopyError($uploadResult, $ex->getMessage());
162 }
163 }
164
165 $this->uploadResult = $tempResults;
166 $this->uploadStreams = null;
167 $this->moved = true;
168 }
169
170
180 {
181 return new UploadResult(
182 $result->getName(),
183 $result->getSize(),
184 $result->getMimeType(),
185 $result->getMetaData(),
186 $result->getStatus(),
187 $path
188 );
189 }
190
191
200 private function regenerateUploadResultWithCopyError(UploadResult $result, string $errorReason): UploadResult
201 {
202 return new UploadResult(
203 $result->getName(),
204 $result->getSize(),
205 $result->getMimeType(),
206 $result->getMetaData(),
208 ''
209 );
210 }
211
212
222 private function selectFilesystem(int $location): Filesystem
223 {
224 switch ($location) {
226 return $this->filesystems->customizing();
228 return $this->filesystems->storage();
229 case Location::WEB:
230 return $this->filesystems->web();
232 return $this->filesystems->temp();
233 default:
234 throw new \InvalidArgumentException("No filesystem found for location code \"$location\"");
235 }
236 }
237
238
242 public function uploadSizeLimit(): int
243 {
245 }
246
247
251 public function register(PreProcessor $preProcessor): void
252 {
253 if (!$this->processed) {
254 $this->processorManager->with($preProcessor);
255 } else {
256 throw new IllegalStateException('Can not register processor after the upload was processed.');
257 }
258 }
259
260
264 public function process(): void
265 {
266 if ($this->processed) {
267 throw new IllegalStateException('Can not reprocess the uploaded files.');
268 }
269
270 $uploadedFiles = $this->globalHttpState->request()->getUploadedFiles();
271 $collectFilesFromNestedFields = $this->flattenUploadedFiles($uploadedFiles);
272 foreach ($collectFilesFromNestedFields as $file) {
273 $metadata = new Metadata($file->getClientFilename(), $file->getSize(), $file->getClientMediaType());
274 try {
275 $stream = Streams::ofPsr7Stream($file->getStream());
276 } catch (\RuntimeException $e) {
277 $this->rejectFailedUpload($metadata);
278 continue;
279 }
280
281 // we take the temporary file name as an identifier as it is the only unique attribute.
282 $identifier = $file->getStream()->getMetadata('uri');
283
284 $identifier = is_array($identifier) ? '' : $identifier;
285
286 $this->uploadStreams[$identifier] = $stream;
287
288 if ($file->getError() === UPLOAD_ERR_OK) {
289 $processingResult = $this->processorManager->process($stream, $metadata);
290
291 // we do discard if the result is a DENIED that there is no further pissibility to process the file.
292 if ($processingResult->getCode() === ProcessingStatus::DENIED) {
293 $this->hardRemoveUpload($identifier, $processingResult->getMessage());
294 continue;
295 }
296
297 $result = new UploadResult(
298 $metadata->getFilename(),
299 $metadata->getUploadSize(),
300 $metadata->getMimeType(),
301 $metadata->additionalMetaData(),
302 $processingResult,
303 is_string($identifier) ? $identifier : ''
304 );
305 $this->uploadResult[$identifier] = $result;
306 } else {
307 $this->rejectFailedUpload($metadata);
308 }
309 }
310
311 $this->processed = true;
312 }
313
314
320 private function rejectFailedUpload(Metadata $metadata): void
321 {
322 //reject failed upload
323 $processingStatus = new ProcessingStatus(ProcessingStatus::REJECTED, 'Upload failed');
324 $extraMetadata = new ImmutableMapWrapper(new EntryLockingStringMap());
325 $result = new UploadResult(
326 $metadata->getFilename(),
327 $metadata->getUploadSize(),
328 $metadata->getMimeType(),
329 $extraMetadata,
330 $processingStatus,
331 ''
332 );
333
334 $this->rejectedUploadResult[] = $result;
335 }
336
337
341 public function getResults(): array
342 {
343 if ($this->processed) {
344 return array_merge($this->uploadResult, $this->rejectedUploadResult);
345 }
346
347 throw new IllegalStateException('Can not fetch results without processing the uploads.');
348 }
349
350
354 public function hasUploads(): bool
355 {
356 if ($this->moved) {
357 return false;
358 }
359
360 $uploadedFiles = $this->flattenUploadedFiles($this->globalHttpState->request()->getUploadedFiles());
361
362 return ($uploadedFiles !== []);
363 }
364
365
366 protected function flattenUploadedFiles(array $uploadedFiles): array
367 {
368 $recursiveIterator = new RecursiveIteratorIterator(
369 new RecursiveArrayIterator(
370 $uploadedFiles,
371 RecursiveArrayIterator::CHILD_ARRAYS_ONLY
372 ),
373 RecursiveIteratorIterator::LEAVES_ONLY
374 );
375
376 return iterator_to_array($recursiveIterator, false);
377 }
378
379
380 public function hasBeenProcessed(): bool
381 {
382 return $this->processed;
383 }
384}
$location
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: buildRTE.php:22
getFilename()
The filename supplied by the browser.
Definition: Metadata.php:60
getMimeType()
Client supplied mime type of the uploaded.
Definition: Metadata.php:102
getUploadSize()
This is always the original file size which was determined by the http service.
Definition: Metadata.php:90
const REJECTED
Upload got rejected by a processor.
const DENIED
Upload got denied by a processor, the upload will be removed immediately.
__construct(PreProcessorManager $processorManager, Filesystems $filesystems, GlobalHttpState $globalHttpState)
FileUploadImpl constructor.
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...
regenerateUploadResultWithCopyError(UploadResult $result, string $errorReason)
Creates a clone of the given result and set the status to rejected with the passed error message.
selectFilesystem(int $location)
Selects the correct filesystem by the given Location constant.
flattenUploadedFiles(array $uploadedFiles)
regenerateUploadResultWithPath(UploadResult $result, string $path)
Generate an exact copy of the result with the given path.
rejectFailedUpload(Metadata $metadata)
Reject a failed upload with the given metadata.
hasBeenProcessed()
Return (bool)true if the current upload has already been processed.
moveFilesTo(string $destination, int $location=Location::STORAGE)
@inheritDoc
hardRemoveUpload(string $identifier, string $rejection_message)
@description This is the very last thing we can do if a preprocessor DENIEs an upload.
PreProcessorManager $processorManager
static ofPsr7Stream(StreamInterface $stream)
Create a FileStream from a Psr7 compliant stream.
Definition: Streams.php:82
Class Services.
Definition: Services.php:38
Class ilFileUtils.
static getUploadSizeLimitBytes()
const TEMPORARY
The ILIAS temporary directory.
Definition: Location.php:50
const CUSTOMIZING
The filesystem within the web root where all the skins and plugins are saved.
Definition: Location.php:45
const WEB
The filesystem within the ilias web root.
Definition: Location.php:35
const STORAGE
The filesystem outside of the ilias web root.
Definition: Location.php:40
Interface Filesystem.
Definition: Filesystem.php:40
Interface GlobalHttpState.
$path
Definition: ltiservices.php:32
Class FlySystemFileAccessTest \Provider\FlySystem @runTestsInSeparateProcesses @preserveGlobalState d...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static filesystems()
Returns the loaded filesystems.
string $key
Consumer key/client ID value.
Definition: System.php:193