Migrate attachments to the new file upload API
authorAlexander Ebert <ebert@woltlab.com>
Sat, 25 May 2024 13:12:42 +0000 (15:12 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Sat, 8 Jun 2024 10:19:40 +0000 (12:19 +0200)
wcfsetup/install/files/lib/data/file/FileEditor.class.php
wcfsetup/install/files/lib/system/worker/AttachmentRebuildDataWorker.class.php

index 44cd5ee69bb570a4cdf591321a3da1fa91fe2499..611aae7010256c6c5c5c565e68598a038ac38773 100644 (file)
@@ -6,6 +6,7 @@ use wcf\data\DatabaseObjectEditor;
 use wcf\data\file\temporary\FileTemporary;
 use wcf\data\file\thumbnail\FileThumbnailEditor;
 use wcf\data\file\thumbnail\FileThumbnailList;
+use wcf\system\file\processor\FileProcessor;
 use wcf\util\FileUtil;
 
 /**
@@ -102,4 +103,59 @@ class FileEditor extends DatabaseObjectEditor
 
         return $file;
     }
+
+    public static function createFromExistingFile(
+        string $pathname,
+        string $originalFilename,
+        string $objectTypeName
+    ): ?File {
+        if (!\is_readable($pathname)) {
+            return null;
+        }
+
+        $objectType = FileProcessor::getInstance()->getObjectType($objectTypeName);
+        if ($objectType === null) {
+            return new \RuntimeException("The object type '{$objectTypeName}' is not valid.");
+        }
+
+        $mimeType = FileUtil::getMimeType($pathname);
+        $isImage = match ($mimeType) {
+            'image/gif' => true,
+            'image/jpeg' => true,
+            'image/png' => true,
+            'image/webp' => true,
+            default => false,
+        };
+
+        $width = $height = null;
+        if ($isImage) {
+            [$width, $height] = \getimagesize($pathname);
+        }
+
+        $fileAction = new FileAction([], 'create', ['data' => [
+            'filename' => $originalFilename,
+            'fileSize' => \filesize($pathname),
+            'fileHash' => \hash_file('sha256', $pathname),
+            'fileExtension' => File::getSafeFileExtension($mimeType, $originalFilename),
+            'secret' => \bin2hex(\random_bytes(16)),
+            'objectTypeID' => $objectType->objectTypeID,
+            'mimeType' => $mimeType,
+            'width' => $width,
+            'height' => $height,
+        ]]);
+        $file = $fileAction->executeAction()['returnValues'];
+        \assert($file instanceof File);
+
+        $filePath = $file->getPath();
+        if (!\is_dir($filePath)) {
+            \mkdir($filePath, recursive: true);
+        }
+
+        \rename(
+            $pathname,
+            $filePath . $file->getSourceFilename()
+        );
+
+        return $file;
+    }
 }
index 1968d4d2f7ce6670e74c6b0ba7e29f27e930ecb9..78d256d056bd3d2275e445c41f16209f1d8eb4a4 100644 (file)
@@ -2,20 +2,27 @@
 
 namespace wcf\system\worker;
 
-use wcf\data\attachment\AttachmentAction;
+use wcf\data\attachment\Attachment;
+use wcf\data\attachment\AttachmentEditor;
 use wcf\data\attachment\AttachmentList;
+use wcf\data\file\FileEditor;
+use wcf\system\database\exception\DatabaseQueryException;
 use wcf\system\exception\SystemException;
+use wcf\system\database\exception\DatabaseQueryExecutionException;
+use wcf\system\database\exception\DatabaseTransactionException;
+use wcf\system\WCF;
 
 /**
  * Worker implementation for updating attachments.
  *
- * @author  Marcel Werk
- * @copyright   2001-2019 WoltLab GmbH
+ * @author Marcel Werk
+ * @copyright 2001-2024 WoltLab GmbH
  * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  *
  * @method  AttachmentList  getObjectList()
+ * @deprecated 6.1 Should be removed in 6.2 as its only purpose is to migrate to the new upload API.
  */
-class AttachmentRebuildDataWorker extends AbstractRebuildDataWorker
+class AttachmentRebuildDataWorker extends AbstractLinearRebuildDataWorker
 {
     /**
      * @inheritDoc
@@ -25,33 +32,79 @@ class AttachmentRebuildDataWorker extends AbstractRebuildDataWorker
     /**
      * @inheritDoc
      */
-    protected $limit = 10;
+    protected $limit = 100;
+
+    #[\Override]
+    public function execute()
+    {
+        parent::execute();
+
+        /** @var array<int,int> */
+        $attachmentToFileID = [];
+
+        /** @var list<int> */
+        $defunctAttachmentIDs = [];
+
+        foreach ($this->objectList as $attachment) {
+            \assert($attachment instanceof Attachment);
+
+            if ($attachment->fileID !== null) {
+                continue;
+            }
+
+            $attachment->migrateStorage();
+
+            $file = FileEditor::createFromExistingFile(
+                $attachment->getLocation(),
+                $attachment->filename,
+                'com.woltlab.wcf.attachment'
+            );
+
+            if ($file === null) {
+                $defunctAttachmentIDs[] = $attachment->attachmentID;
+                continue;
+            }
+
+            $attachmentToFileID[$attachment->attachmentID] = $file->fileID;
+        }
+
+        $this->setFileIDs($attachmentToFileID);
+        $this->removeDefunctAttachments($defunctAttachmentIDs);
+    }
 
     /**
-     * @inheritDoc
+     * @param array<int,int> $attachmentToFileID
      */
-    protected function initObjectList()
+    private function setFileIDs(array $attachmentToFileID): void
     {
-        parent::initObjectList();
+        if ($attachmentToFileID === []) {
+            return;
+        }
+
+        $sql = "UPDATE  wcf1_attachment
+                SET     fileID = ?
+                WHERE   attachmentID = ?";
+        $statement = WCF::getDB()->prepare($sql);
 
-        $this->objectList->sqlOrderBy = 'attachment.attachmentID';
+        WCF::getDB()->beginTransaction();
+        foreach ($attachmentToFileID as $attachmentID => $fileID) {
+            $statement->execute([
+                $fileID,
+                $attachmentID,
+            ]);
+        }
+        WCF::getDB()->commitTransaction();
     }
 
     /**
-     * @inheritDoc
+     * @param list<int> $attachmentIDs
      */
-    public function execute()
+    private function removeDefunctAttachments(array $attachmentIDs): void
     {
-        parent::execute();
-
-        /** @var \wcf\data\attachment\Attachment $attachment */
-        foreach ($this->objectList as $attachment) {
-            $attachment->migrateStorage();
-            try {
-                $action = new AttachmentAction([$attachment], 'generateThumbnails');
-                $action->executeAction();
-            } catch (SystemException $e) {
-            }
+        if ($attachmentIDs === []) {
+            return;
         }
+
+        AttachmentEditor::deleteAll($attachmentIDs);
     }
 }