Add UploadHandler to manage uploaded files
authorJoshua Rüsweg <josh@bastelstu.be>
Mon, 7 Jan 2019 21:34:35 +0000 (22:34 +0100)
committerJoshua Rüsweg <josh@bastelstu.be>
Mon, 7 Jan 2019 22:13:11 +0000 (23:13 +0100)
See #2825

wcfsetup/install/files/lib/system/file/upload/UploadHandler.class.php [new file with mode: 0644]

diff --git a/wcfsetup/install/files/lib/system/file/upload/UploadHandler.class.php b/wcfsetup/install/files/lib/system/file/upload/UploadHandler.class.php
new file mode 100644 (file)
index 0000000..18a0130
--- /dev/null
@@ -0,0 +1,366 @@
+<?php
+namespace wcf\system\file\upload;
+use wcf\system\exception\ImplementationException;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+
+/**
+ * Handles uploads for files.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\File\Upload
+ * @since       5.2
+ */
+class UploadHandler extends SingletonFactory {
+       /**
+        * Session variable name for the file storage.
+        * @var string
+        */
+       const UPLOAD_HANDLER_SESSION_VAR = 'file_upload_handler_storage';
+       
+       /**
+        * Contains the registered upload fields. 
+        * 
+        * @var UploadField[]
+        */
+       protected $fields = [];
+       
+       /**
+        * Registers a UploadField.
+        * 
+        * @param       UploadField     $field
+        */
+       public function registerUploadField(UploadField $field) {
+               if (isset($this->fields[$field->getFieldId()])) {
+                       throw new \InvalidArgumentException('UploadField with the id "'. $field->getFieldId() .'" is already registered.');
+               }
+               
+               // read internal identifier
+               if (!empty($_POST) && isset($_POST[$field->getFieldId()]) && $this->isValidInternalId($_POST[$field->getFieldId()])) {
+                       $field->setInternalId($_POST[$field->getFieldId()]);
+                       
+                       $this->fields[$field->getFieldId()] = $field;
+               }
+               else {
+                       do {
+                               $internalId = bin2hex(random_bytes(32));
+                       } 
+                       while (in_array($internalId, $this->getKnownInternalIds()));
+                       
+                       $field->setInternalId($internalId);
+                       
+                       $this->registerFieldInStorage($field);
+               }
+       }
+       
+       /**
+        * Returns the uploaded files for a specific fieldId.
+        * 
+        * @param       String          $fieldId
+        * @return      UploadFile[]
+        */
+       public function getFilesForFieldId($fieldId) {
+               if (!isset($this->fields[$fieldId])) {
+                       throw new \InvalidArgumentException('UploadField with the id "'. $fieldId .'" is unknown.');
+               }
+               
+               return $this->getFilesForInternalId($this->fields[$fieldId]->getInternalId());
+       }
+       
+       /**
+        * Returns the removed but previosly proccessed files for a specific fieldId.
+        *
+        * @param       String          $fieldId
+        * @param       boolean         $processFiles
+        * @return      UploadFile[]
+        */
+       public function getRemovedFiledForFieldId($fieldId, $processFiles = true) {
+               if (!isset($this->fields[$fieldId])) {
+                       throw new \InvalidArgumentException('UploadField with the id "'. $fieldId .'" is unknown.');
+               }
+               
+               return $this->getRemovedFiledForInternalId($this->fields[$fieldId]->getInternalId(), $processFiles);
+       }
+       
+       /**
+        * Returns the removed but previosly proccessed files for a specific internalId.
+        *
+        * @param       String          $internalId
+        * @param       boolean         $processFiles
+        * @return      UploadFile[]
+        */
+       public function getRemovedFiledForInternalId($internalId, $processFiles = true) {
+               if (isset($this->getStorage()[$internalId])) {
+                       $files = $this->getStorage()[$internalId]['removedFiles'];
+                       $removedFiles = [];
+                       
+                       /** @var UploadFile $file */
+                       foreach ($files as $file) {
+                               if (file_exists($file->getLocation())) {
+                                       $removedFiles[] = $file;
+                               }
+                       }
+                       
+                       if ($processFiles) $this->processRemovedFiles($this->getFieldForInternalId($internalId));
+                       
+                       return $removedFiles;
+               }
+               
+               return [];
+       }
+       
+       /**
+        * @param $internalId
+        * @param $uniqueFileId
+        */
+       public function removeFile($internalId, $uniqueFileId) {
+               if (!$this->isValidInternalId($internalId)) {
+                       throw new \InvalidArgumentException('InternalId "'. $internalId .'" is unknown.');
+               }
+               
+               $file = $this->getFileForUniqueFileId($internalId, $uniqueFileId);
+               
+               if ($file === null) {
+                       return; 
+               }               
+               
+               $storage = $this->getStorage();
+               
+               if ($file->isProcessed()) {
+                       $storage[$internalId]['removedFiles'] = array_unique(array_merge($storage[$internalId]['removedFiles'], [$file]));
+               }
+               else {
+                       @unlink($file->getLocation());
+               }
+               
+               /** @var UploadFile $storageFile */
+               foreach ($storage[$internalId]['files'] as $id => $storageFile) {
+                       if ($storageFile->getUniqueFileId() == $uniqueFileId) {
+                               unset($storage[$internalId]['files'][$id]);
+                               break;
+                       }
+               }
+               
+               WCF::getSession()->register(self::UPLOAD_HANDLER_SESSION_VAR, $storage);
+       }
+       
+       /**
+        * Renders the field with the given fieldId for the template.
+        * @param       String          $fieldId
+        * @return      String
+        */
+       public function renderField($fieldId) {
+               if (!isset($this->fields[$fieldId])) {
+                       throw new \InvalidArgumentException('UploadField with the id "'. $fieldId .'" is unknown.');
+               }
+               
+               return WCF::getTPL()->fetch('uploadFieldComponent', 'wcf', [
+                       'field' => $this->fields[$fieldId], 
+                       'fieldId' => $fieldId,
+                       'files' => $this->getFilesForFieldId($fieldId)
+               ]);
+       }
+       
+       /**
+        * Returns true, if the given internalId is valid.
+        * 
+        * @param       String          $internalId
+        * @return      boolean
+        */
+       public function isValidInternalId($internalId) {
+               return isset($this->getStorage()[$internalId]); 
+       }
+       
+       /**
+        * 
+        * @param       String        $internalId
+        * @param       String        $uniqueFileId
+        * @return      boolean
+        */
+       public function isValidUniqueFileId($internalId, $uniqueFileId) {
+               return $this->getFileForUniqueFileId($internalId, $uniqueFileId) !== null; 
+       }
+       
+       /**
+        *
+        * @param       String          $internalId
+        * @param       String          $uniqueFileId
+        * @return      UploadFile|null
+        */
+       public function getFileForUniqueFileId($internalId, $uniqueFileId) {
+               if (!$this->isValidInternalId($internalId)) {
+                       throw new \InvalidArgumentException('InternalId "'. $internalId .'" is unknown.');
+               }
+               
+               foreach ($this->getFilesForInternalId($internalId) as $file) {
+                       if ($file->getUniqueFileId() == $uniqueFileId) {
+                               return $file;
+                       }
+               }
+               
+               return null;
+       } 
+       
+       /**
+        * Add a file for an internalId. 
+        * 
+        * @param       String          $internalId
+        * @param       UploadFile      $file
+        */
+       public function addFileForInternalId($internalId, UploadFile $file) {
+               $this->registerFilesForInternalId($internalId, array_merge($this->getFilesForInternalId($internalId), [$file]));
+       }
+       
+       /**
+        * Registers files for the given internalId.
+        *
+        * HEADS UP: Deletes all uploaded files and overwrites them with
+        * the given files. If you want to add a file, use the addFileForInternalId method. 
+        * 
+        * @param       String          $internalId
+        * @param       UploadFile[]    $files
+        */
+       public function registerFilesForInternalId($internalId, array $files) {
+               if (!$this->isValidInternalId($internalId)) {
+                       throw new \InvalidArgumentException('InternalId "'. $internalId .'" is unknown.');
+               }
+               
+               foreach ($files as $file) {
+                       if (!($file instanceof UploadFile)) {
+                               throw new ImplementationException(get_class($file), UploadFile::class);
+                       }
+               }
+               
+               $storage = $this->getStorage();
+               $storage[$internalId]['files'] = $files;
+               
+               WCF::getSession()->register(self::UPLOAD_HANDLER_SESSION_VAR, $storage);
+       }
+       
+       /**
+        * Add a file for an upload field with the given fieldId.
+        * 
+        * @param       String          $fieldId
+        * @param       UploadFile      $file
+        */
+       public function addFileForField($fieldId, UploadFile $file) {
+               $this->registerFilesForField($fieldId, array_merge($this->getFilesForFieldId($fieldId), [$file]));
+       }
+       
+       /**
+        * Register files for the field with the given fieldId.
+        * 
+        * HEADS UP: Deletes all uploaded files and overwrites them with
+        * the given files. If you want to add a file, use the addFileForField method.
+        * 
+        * @param       String          $fieldId
+        * @param       UploadFile[]    $files
+        */
+       public function registerFilesForField($fieldId, array $files) {
+               if (!isset($this->fields[$fieldId])) {
+                       throw new \InvalidArgumentException('UploadField with the id "'. $fieldId .'" is unknown.');
+               }
+               
+               $this->registerFilesForInternalId($this->fields[$fieldId]->getInternalId(), $files);
+       }
+       
+       /**
+        * Returns the field for the internalId.
+        * 
+        * @param       String          $internalId
+        * @return      UploadField
+        */
+       public function getFieldForInternalId($internalId) {
+               if (!$this->isValidInternalId($internalId)) {
+                       throw new \InvalidArgumentException('InternalId "'. $internalId .'" is unknown.');
+               }
+               
+               return $this->getStorage()[$internalId]['field'];
+       }
+       
+       /**
+        * Returns the count of uploaded files for an internal id. 
+        * 
+        * @param       String          $internalId
+        * @return      int
+        */
+       public function getFilesCountForInternalId($internalId) {
+               return count($this->getFilesForInternalId($internalId));
+       }
+       
+       /**
+        * Returns the files for an internal identifier.
+        *
+        * @param       String          $internalId
+        * @return      UploadFile[]
+        */
+       private function getFilesForInternalId($internalId) {
+               if (isset($this->getStorage()[$internalId])) {
+                       $files = $this->getStorage()[$internalId]['files'];
+                       
+                       // check avaibility of the files 
+                       /** @var UploadFile $file */
+                       foreach ($files as $file) {
+                               if (!file_exists($file->getLocation())) {
+                                       $this->removeFile($internalId, $file->getUniqueFileId());
+                               }
+                       }
+                       
+                       return $files;
+               }
+               
+               return [];
+       }
+       
+       /**
+        * Returns the upload handler storage, located in the session var.
+        * 
+        * @return array
+        */
+       private function getStorage() {
+               if (!is_array(WCF::getSession()->getVar(self::UPLOAD_HANDLER_SESSION_VAR))) {
+                       return [];
+               }
+               
+               return WCF::getSession()->getVar(self::UPLOAD_HANDLER_SESSION_VAR);
+       }
+       
+       /**
+        * Registers an field in the storage. 
+        * 
+        * @param       UploadField     $field
+        */
+       private function registerFieldInStorage(UploadField $field) {
+               $storage = $this->getStorage();
+               $storage[$field->getInternalId()] = [
+                       'field' => $field, 
+                       'files' => [],
+                       'removedFiles' => []
+               ];
+               
+               $this->fields[$field->getFieldId()] = $field;
+               
+               WCF::getSession()->register(self::UPLOAD_HANDLER_SESSION_VAR, $storage);
+       }
+       
+       /**
+        * @param UploadField $field
+        */
+       private function processRemovedFiles(UploadField $field) {
+               $storage = $this->getStorage();
+               $storage[$field->getInternalId()]['removedFiles'] = [];
+               
+               WCF::getSession()->register(self::UPLOAD_HANDLER_SESSION_VAR, $storage);
+       }
+       
+       /**
+        * Returns the known internalIds. 
+        * 
+        * @return String[]
+        */
+       private function getKnownInternalIds() {
+               return array_keys($this->getStorage());
+       }
+}