ILIAS  release_7 Revision v7.30-3-g800a261c036
FileUploadImpl.php
Go to the documentation of this file.
1<?php
2
19namespace ILIAS\FileUpload;
20
34use Psr\Http\Message\UploadedFileInterface;
35use RecursiveArrayIterator;
36use RecursiveIteratorIterator;
37
45final class FileUploadImpl implements FileUpload
46{
47
55 private $filesystems;
63 private $processed;
67 private $moved;
80
81
82
83
92 {
93 $this->processorManager = $processorManager;
95 $this->globalHttpState = $globalHttpState;
96 $this->processed = false;
97 $this->moved = false;
98 $this->uploadResult = [];
99 $this->rejectedUploadResult = [];
100 }
101
106 private function hardRemoveUpload(string $identifier) : void
107 {
108 // we delete the file from the temporary directory and remove it from the global $_FILES array
109 $file_stream = $this->uploadStreams[$identifier];
110 $uri = $file_stream->getMetadata('uri');
111 $file_stream->close();
112 unlink($uri);
113 unset($this->uploadStreams[$identifier]);
114 unset($_FILES[$identifier]);
115 throw new IllegalStateException("File upload removed due to security reasons.");
116 }
117
121 public function moveOneFileTo(UploadResult $uploadResult, $destination, $location = Location::STORAGE, $file_name = '', $override_existing = false)
122 {
123 if ($this->processed === false) {
124 throw new \RuntimeException('Can not move unprocessed files.');
125 }
126 $filesystem = $this->selectFilesystem($location);
127 $tempResults = [];
128
129 if ($uploadResult->getStatus()->getCode() == ProcessingStatus::REJECTED) {
130 return false;
131 }
132
133 try {
134 $path = rtrim($destination, "/") . '/' . ($file_name == "" ? $uploadResult->getName() : $file_name);
135 if ($override_existing && $filesystem->has($path)) {
136 $filesystem->delete($path);
137 }
138 $filesystem->writeStream($path, Streams::ofPsr7Stream($this->uploadStreams[$uploadResult->getPath()]));
139 $tempResults[] = $this->regenerateUploadResultWithPath($uploadResult, $path);
140 } catch (IOException $ex) {
141 $this->regenerateUploadResultWithCopyError($uploadResult, $ex->getMessage());
142 }
143 }
144
145
149 public function moveFilesTo($destination, $location = Location::STORAGE)
150 {
151 if ($this->processed === false) {
152 throw new \RuntimeException('Can not move unprocessed files.');
153 }
154
155 if ($this->moved === true) {
156 throw new \RuntimeException('Can not move the files a second time.');
157 }
158
159 $filesystem = $this->selectFilesystem($location);
160 $tempResults = [];
161
162 foreach ($this->uploadResult as $key => $uploadResult) {
163 if ($uploadResult->getStatus()->getCode() == ProcessingStatus::REJECTED) {
164 continue;
165 }
166
167 try {
168 $path = $destination . '/' . $uploadResult->getName();
169 $filesystem->writeStream($path, Streams::ofPsr7Stream($this->uploadStreams[$key]));
170 $tempResults[] = $this->regenerateUploadResultWithPath($uploadResult, $path);
171 } catch (IOException $ex) {
172 $this->regenerateUploadResultWithCopyError($uploadResult, $ex->getMessage());
173 }
174 }
175
176 $this->uploadResult = $tempResults;
177 $this->uploadStreams = null;
178 $this->moved = true;
179 }
180
181
191 {
192 return new UploadResult(
193 $result->getName(),
194 $result->getSize(),
195 $result->getMimeType(),
196 $result->getMetaData(),
197 $result->getStatus(),
198 $path
199 );
200 }
201
202
212 {
213 return new UploadResult(
214 $result->getName(),
215 $result->getSize(),
216 $result->getMimeType(),
217 $result->getMetaData(),
219 ''
220 );
221 }
222
223
235 private function selectFilesystem($location)
236 {
237 switch ($location) {
239 return $this->filesystems->customizing();
241 return $this->filesystems->storage();
242 case Location::WEB:
243 return $this->filesystems->web();
245 return $this->filesystems->temp();
246 default:
247 throw new \InvalidArgumentException("No filesystem found for location code \"$location\"");
248 }
249 }
250
251
255 public function uploadSizeLimit()
256 {
257 return \ilUtil::getUploadSizeLimitBytes();
258 }
259
260
264 public function register(PreProcessor $preProcessor)
265 {
266 if ($this->processed === false) {
267 $this->processorManager->with($preProcessor);
268 } else {
269 throw new IllegalStateException('Can not register processor after the upload was processed.');
270 }
271 }
272
273
277 public function process()
278 {
279 if ($this->processed === true) {
280 throw new IllegalStateException('Can not reprocess the uploaded files.');
281 }
282
286 $uploadedFiles = $this->globalHttpState->request()->getUploadedFiles();
287 $collectFilesFromNestedFields = $this->flattenUploadedFiles($uploadedFiles);
288 foreach ($collectFilesFromNestedFields as $file) {
289 $metadata = new Metadata($file->getClientFilename(), $file->getSize(), $file->getClientMediaType());
290 try {
291 $stream = Streams::ofPsr7Stream($file->getStream());
292 } catch (\RuntimeException $e) {
293 $this->rejectFailedUpload($file, $metadata);
294 continue;
295 }
296
297 // we take the temporary file name as an identifier as it is the only unique attribute.
298 $identifier = $file->getStream()->getMetadata('uri');
299
300 $identifier = is_array($identifier) ? '' : $identifier;
301
302 $this->uploadStreams[$identifier] = $stream;
303
304 if ($file->getError() === UPLOAD_ERR_OK) {
305 $processingResult = $this->processorManager->process($stream, $metadata);
306
307 // we do discard if the result is a DENIED that there is no further pissibility to process the file.
308 if ($processingResult->getCode() === ProcessingStatus::DENIED) {
309 $this->hardRemoveUpload($identifier);
310 continue;
311 }
312
313 $result = new UploadResult(
314 $metadata->getFilename(),
315 $metadata->getUploadSize(),
316 $metadata->getMimeType(),
317 $metadata->additionalMetaData(),
318 $processingResult,
319 is_string($identifier)?$identifier:''
320 );
321 $this->uploadResult[$identifier] = $result;
322 } else {
323 $this->rejectFailedUpload($file, $metadata);
324 }
325 }
326
327 $this->processed = true;
328 }
329
330
339 private function rejectFailedUpload(UploadedFileInterface $file, Metadata $metadata)
340 {
341 //reject failed upload
342 $processingStatus = new ProcessingStatus(ProcessingStatus::REJECTED, 'Upload failed');
343 $extraMetadata = new ImmutableMapWrapper(new EntryLockingStringMap());
344 $result = new UploadResult(
345 $metadata->getFilename(),
346 $metadata->getUploadSize(),
347 $metadata->getMimeType(),
348 $extraMetadata,
349 $processingStatus,
350 ''
351 );
352
353 $this->rejectedUploadResult[] = $result;
354 }
355
356
360 public function getResults()
361 {
362 if ($this->processed) {
363 return array_merge($this->uploadResult, $this->rejectedUploadResult);
364 }
365
366 throw new IllegalStateException('Can not fetch results without processing the uploads.');
367 }
368
369
373 public function hasUploads()
374 {
375 if ($this->moved) {
376 return false;
377 }
378
379 $uploadedFiles = $this->flattenUploadedFiles($this->globalHttpState->request()->getUploadedFiles());
380
381 return (count($uploadedFiles) > 0);
382 }
383
384
390 protected function flattenUploadedFiles($uploadedFiles)
391 {
392 $recursiveIterator = new RecursiveIteratorIterator(
393 new RecursiveArrayIterator(
394 $uploadedFiles,
395 RecursiveArrayIterator::CHILD_ARRAYS_ONLY
396 ),
397 RecursiveIteratorIterator::LEAVES_ONLY
398 );
399
400 return iterator_to_array($recursiveIterator, false);
401 }
402
403
407 public function hasBeenProcessed()
408 {
409 return $this->processed;
410 }
411}
$result
$location
Definition: buildRTE.php:44
An exception for terminatinating execution or to throw for unit testing.
getFilename()
The filename supplied by the browser.
Definition: Metadata.php:73
getMimeType()
Client supplied mime type of the uploaded.
Definition: Metadata.php:118
getUploadSize()
This is always the original file size which was determined by the http service.
Definition: Metadata.php:105
const REJECTED
Upload got rejected by a processor.
const DENIED
Upload got denied by a processor, the upload will be removed immediately.
rejectFailedUpload(UploadedFileInterface $file, Metadata $metadata)
Reject a failed upload with the given metadata.
moveFilesTo($destination, $location=Location::STORAGE)
@inheritDoc
regenerateUploadResultWithPath(UploadResult $result, $path)
Generate an exact copy of the result with the given path.
regenerateUploadResultWithCopyError(UploadResult $result, $errorReason)
Creates a clone of the given result and set the status to rejected with the passed error message.
__construct(PreProcessorManager $processorManager, Filesystems $filesystems, GlobalHttpState $globalHttpState)
FileUploadImpl constructor.
hardRemoveUpload(string $identifier)
@description This is the very last thing we can do if a preprocessor DENIEs an upload.
selectFilesystem($location)
Selects the correct filesystem by the given Location constant.
moveOneFileTo(UploadResult $uploadResult, $destination, $location=Location::STORAGE, $file_name='', $override_existing=false)
Moves a single File (the attributes, metadata and upload-status of which are contained in UploadResul...
Class IOException Indicates general problems with the input or output operations.
Definition: IOException.php:13
Class Streams Stream factory which enables the user to create streams without the knowledge of the co...
Definition: Streams.php:17
static ofPsr7Stream(StreamInterface $stream)
Create a FileStream from a Psr7 compliant stream.
Definition: Streams.php:58
process()
Invokes all preprocessors for each uploaded file in the sequence they got registered.
const TEMPORARY
The ILIAS temporary directory.
Definition: Location.php:38
const CUSTOMIZING
The filesystem within the web root where all the skins and plugins are saved.
Definition: Location.php:33
const WEB
The filesystem within the ilias web root.
Definition: Location.php:23
const STORAGE
The filesystem outside of the ilias web root.
Definition: Location.php:28
Class Filesystems The Filesystems interface defines the access methods which can be used to fetch the...
Definition: Filesystems.php:15
Interface FileStream The base interface for all filesystem streams.
Definition: FileStream.php:18
Interface GlobalHttpState.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static filesystems()
Returns the loaded filesystems.