ILIAS  release_7 Revision v7.30-3-g800a261c036
All Data Structures Namespaces Files Functions Variables Modules Pages
FileUploadImpl.php
Go to the documentation of this file.
1 <?php
2 
19 namespace ILIAS\FileUpload;
20 
37 
45 final class FileUploadImpl implements FileUpload
46 {
47 
55  private $filesystems;
63  private $processed;
67  private $moved;
71  private $uploadResult;
79  private $uploadStreams;
80 
81 
82 
83 
92  {
93  $this->processorManager = $processorManager;
94  $this->filesystems = $filesystems;
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(),
218  new ProcessingStatus(ProcessingStatus::REJECTED, $errorReason),
219  ''
220  );
221  }
222 
223 
235  private function selectFilesystem($location)
236  {
237  switch ($location) {
239  return $this->filesystems->customizing();
240  case Location::STORAGE:
241  return $this->filesystems->storage();
242  case Location::WEB:
243  return $this->filesystems->web();
244  case Location::TEMPORARY:
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(
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 }
rejectFailedUpload(UploadedFileInterface $file, Metadata $metadata)
Reject a failed upload with the given metadata.
Interface GlobalHttpState.
const REJECTED
Upload got rejected by a processor.
process()
Invokes all preprocessors for each uploaded file in the sequence they got registered.
$result
moveFilesTo($destination, $location=Location::STORAGE)
Class IOException Indicates general problems with the input or output operations. ...
Definition: IOException.php:12
const STORAGE
The filesystem outside of the ilias web root.
Definition: Location.php:28
$location
Definition: buildRTE.php:44
__construct(PreProcessorManager $processorManager, Filesystems $filesystems, GlobalHttpState $globalHttpState)
FileUploadImpl constructor.
regenerateUploadResultWithCopyError(UploadResult $result, $errorReason)
Creates a clone of the given result and set the status to rejected with the passed error message...
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...
getMimeType()
Client supplied mime type of the uploaded.
Definition: Metadata.php:118
selectFilesystem($location)
Selects the correct filesystem by the given Location constant.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getFilename()
The filename supplied by the browser.
Definition: Metadata.php:73
getUploadSize()
This is always the original file size which was determined by the http service.
Definition: Metadata.php:105
static filesystems()
Returns the loaded filesystems.
const CUSTOMIZING
The filesystem within the web root where all the skins and plugins are saved.
Definition: Location.php:33
const DENIED
Upload got denied by a processor, the upload will be removed immediately.
Class FileUpload.
Definition: FileUpload.php:21
regenerateUploadResultWithPath(UploadResult $result, $path)
Generate an exact copy of the result with the given path.
const TEMPORARY
The ILIAS temporary directory.
Definition: Location.php:38
Exercise XML Parser which completes/updates a given file by an xml string.
hardRemoveUpload(string $identifier)
This is the very last thing we can do if a preprocessor DENIEs an upload.
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:23
Class Filesystems The Filesystems interface defines the access methods which can be used to fetch the...
Definition: Filesystems.php:14
static ofPsr7Stream(StreamInterface $stream)
Create a FileStream from a Psr7 compliant stream.
Definition: Streams.php:58