Unify the handling of the attachment context
authorAlexander Ebert <ebert@woltlab.com>
Sun, 7 Apr 2024 23:09:05 +0000 (01:09 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Sat, 8 Jun 2024 10:19:38 +0000 (12:19 +0200)
ts/WoltLabSuite/Core/Component/File/Upload.ts
wcfsetup/install/files/lib/system/file/processor/AttachmentFileProcessor.class.php
wcfsetup/install/files/lib/system/file/processor/FileProcessor.class.php
wcfsetup/install/files/lib/system/file/processor/FileProcessorPreflightResult.class.php

index 6369decf7472f6105806ff258771963c4f56a025..fd2573fd1c9721ae1bafc58562c2a7ffa721e4c0 100644 (file)
@@ -88,6 +88,12 @@ async function getSha256Hash(data: BufferSource): Promise<string> {
 export function setup(): void {
   wheneverFirstSeen("woltlab-core-file-upload", (element) => {
     element.addEventListener("upload", (event: CustomEvent<File>) => {
+      // TODO: Add some pipeline logic here.
+
+      // TODO: Add a canvas based resize to the pipeline.
+
+      // TODO: We need to pass around the file using some dedicated type of
+      //       data structure because it can be modified somehow.
       void upload(element, event.detail);
     });
   });
index a3847e976e5ef0e0209fd3a92a5c7ca4591bd69d..d1fe6a9ceec2303cbfece3ebb7e323a5f1d812e8 100644 (file)
@@ -2,10 +2,12 @@
 
 namespace wcf\system\file\processor;
 
+use CuyZ\Valinor\Mapper\MappingError;
 use wcf\data\attachment\Attachment;
 use wcf\data\attachment\AttachmentEditor;
 use wcf\data\file\File;
 use wcf\data\file\thumbnail\FileThumbnail;
+use wcf\http\Helper;
 use wcf\system\attachment\AttachmentHandler;
 use wcf\system\exception\NotImplementedException;
 
@@ -26,13 +28,10 @@ final class AttachmentFileProcessor implements IFileProcessor
     #[\Override]
     public function getAllowedFileExtensions(array $context): array
     {
-        // TODO: Properly validate the shape of `$context`.
-        $objectType = $context['objectType'] ?? '';
-        $objectID = \intval($context['objectID'] ?? 0);
-        $parentObjectID = \intval($context['parentObjectID'] ?? 0);
-        $tmpHash = $context['tmpHash'] ?? '';
-
-        $attachmentHandler = new AttachmentHandler($objectType, $objectID, $tmpHash, $parentObjectID);
+        $attachmentHandler = $this->getAttachmentHandlerFromContext($context);
+        if ($attachmentHandler === null) {
+            return FileProcessorPreflightResult::InvalidContext;
+        }
 
         return $attachmentHandler->getAllowedExtensions();
     }
@@ -40,20 +39,15 @@ final class AttachmentFileProcessor implements IFileProcessor
     #[\Override]
     public function adopt(File $file, array $context): void
     {
-        // TODO: Properly validate the shape of `$context`.
-        $objectType = $context['objectType'] ?? '';
-        $objectID = \intval($context['objectID'] ?? 0);
-        $parentObjectID = \intval($context['parentObjectID'] ?? 0);
-        $tmpHash = $context['tmpHash'] ?? '';
-
-        $attachmentHandler = new AttachmentHandler($objectType, $objectID, $tmpHash, $parentObjectID);
+        $attachmentHandler = $this->getAttachmentHandlerFromContext($context);
+        if ($attachmentHandler === null) {
+            return FileProcessorPreflightResult::InvalidContext;
+        }
 
-        // TODO: How do we want to create the attachments? Do we really want to
-        //       keep using the existing attachment table though?
         AttachmentEditor::fastCreate([
             'objectTypeID' => $attachmentHandler->getObjectType()->objectTypeID,
             'objectID' => $attachmentHandler->getObjectID(),
-            'tmpHash' => $tmpHash,
+            'tmpHash' => $attachmentHandler->getTmpHashes()[0] ?? '',
             'fileID' => $file->fileID,
         ]);
     }
@@ -61,13 +55,11 @@ final class AttachmentFileProcessor implements IFileProcessor
     #[\Override]
     public function acceptUpload(string $filename, int $fileSize, array $context): FileProcessorPreflightResult
     {
-        // TODO: Properly validate the shape of `$context`.
-        $objectType = $context['objectType'] ?? '';
-        $objectID = \intval($context['objectID'] ?? 0);
-        $parentObjectID = \intval($context['parentObjectID'] ?? 0);
-        $tmpHash = $context['tmpHash'] ?? '';
+        $attachmentHandler = $this->getAttachmentHandlerFromContext($context);
+        if ($attachmentHandler === null) {
+            return FileProcessorPreflightResult::InvalidContext;
+        }
 
-        $attachmentHandler = new AttachmentHandler($objectType, $objectID, $tmpHash, $parentObjectID);
         if (!$attachmentHandler->canUpload()) {
             return FileProcessorPreflightResult::InsufficientPermissions;
         }
@@ -175,8 +167,8 @@ final class AttachmentFileProcessor implements IFileProcessor
 
         $columnName = match ($thumbnail->identifier) {
             '' => 'thumbnailID',
-            'tiny'=>'tinyThumbnailID',
-            'default'=>throw new \RuntimeException('TODO'), // TODO
+            'tiny' => 'tinyThumbnailID',
+            'default' => throw new \RuntimeException('TODO'), // TODO
         };
 
         $attachmentEditor = new AttachmentEditor($attachment);
@@ -184,4 +176,40 @@ final class AttachmentFileProcessor implements IFileProcessor
             $columnName => $thumbnail->thumbnailID,
         ]);
     }
+
+    private function getAttachmentHandlerFromContext(array $context): ?AttachmentHandler
+    {
+        try {
+            $parameters = Helper::mapQueryParameters($context, AttachmentFileProcessorContext::class);
+        } catch (MappingError) {
+            return null;
+        }
+
+        \assert($parameters instanceof AttachmentFileProcessorContext);
+
+        return new AttachmentHandler(
+            $parameters->objectType,
+            $parameters->objectID,
+            $parameters->tmpHash,
+            $parameters->parentObjectID,
+        );
+    }
+}
+
+/** @internal */
+final class AttachmentFileProcessorContext
+{
+    public function __construct(
+        /** @var non-empty-string */
+        public readonly string $objectType,
+
+        /** @var positive-int */
+        public readonly int $objectID,
+
+        /** @var non-negative-int */
+        public readonly int $parentObjectID,
+
+        public readonly string $tmpHash,
+    ) {
+    }
 }
index b639589b566a586e9a73628e33a5627eb6dca064..3088e94787ffa4ae772086531949930e2097cc5f 100644 (file)
@@ -103,6 +103,9 @@ final class FileProcessor extends SingletonFactory
                 $imageAdapter->loadFile($file->getPath() . $file->getSourceFilename());
             }
 
+            // TODO: Thumbnails are currently created using the exact same file
+            //       type as the source file. It may make more sense to always
+            //       use an efficient format, like WebP, exclusively.
             assert($imageAdapter instanceof ImageAdapter);
             $image = $imageAdapter->createThumbnail($format->width, $format->height, $format->retainDimensions);
 
index 0e570c87a36d25becf68cc5f47a5a726e5f89ccc..55cd38bf5dca1c4c2bcde058ff90f271172c96b6 100644 (file)
@@ -15,6 +15,7 @@ enum FileProcessorPreflightResult
     case FileExtensionNotPermitted;
     case FileSizeTooLarge;
     case InsufficientPermissions;
+    case InvalidContext;
     case Passed;
 
     public function ok(): bool
@@ -31,6 +32,7 @@ enum FileProcessorPreflightResult
             self::FileExtensionNotPermitted => 'fileExtensionNotPermitted',
             self::FileSizeTooLarge => 'fileSizeTooLarge',
             self::InsufficientPermissions => 'insufficientPermissions',
+            self::InvalidContext => 'invalidContext',
             self::Passed => 'passed',
         };
     }