Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php

namespace wcf\command\file;

use wcf\data\file\File;
use wcf\data\file\FileBuilder;
use wcf\system\file\processor\FileProcessor;
use wcf\util\ExifUtil;
use wcf\util\FileUtil;
use wcf\util\ImageUtil;

/**
* Promotes a completely uploaded temporary file into a regular file.
*
* @author Alexander Ebert, Marcel Werk
* @copyright 2001-2026 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @since 6.3
*/
final class CreateFileFromExistingFile
{
/**
* @param null|array<string, array<string, mixed>> $exifData
*/
public function __construct(
private readonly string $pathname,
private readonly string $originalFilename,
private readonly string $objectTypeName,
private readonly bool $copy = false,
private readonly ?int $uploadTime = null,
private readonly ?array $exifData = null,
) {}

public function __invoke(): ?File
{
if (!\is_readable($this->pathname)) {
return null;
}

$objectType = FileProcessor::getInstance()->getObjectType($this->objectTypeName);
if ($objectType === null) {
throw new \RuntimeException("The object type '{$this->objectTypeName}' is not valid.");
}

$mimeType = FileUtil::getMimeType($this->pathname);
$isImage = ImageUtil::isImageMimeType($mimeType);

if ($this->exifData === null) {
$exifData = ExifUtil::getExifData($this->pathname);

// Remove the `FILE` and `COMPUTED` section because those contain
// garbled data anyway and we do not need them in the first place.
unset($exifData['FILE'], $exifData['COMPUTED']);

// We can also discard the `THUMBNAIL` section because it is a
// pointless feature and we’re not extracting it either.
unset($exifData['THUMBNAIL']);

if ($exifData === []) {
$exifData = null;
}
} else {
$exifData = $this->exifData;
}

$width = $height = null;
if ($isImage) {
try {
[$width, $height] = \getimagesize($this->pathname);
} catch (\Throwable) {
return null;
}

$imageWasModified = false;
try {
$imageWasModified = ExifUtil::normalizeImageRotation(
$this->pathname,
$width,
$height,
$mimeType,
$exifData,
);

if ($imageWasModified && $exifData !== null) {
unset($exifData['IFD0']['Orientation']);
}
} catch (\Throwable) {
}

if ($imageWasModified) {
[$width, $height] = \getimagesize($this->pathname);
}
}

$file = FileBuilder::forCreate()
->setFilename($this->originalFilename)
->setFileSize(\filesize($this->pathname))
->setFileHash(\hash_file('sha256', $this->pathname))
->setFileExtension(File::getSafeFileExtension($mimeType, $this->originalFilename))
->setObjectType($objectType)
->setMimeType($mimeType)
->setDimensions($width, $height)
->setUploadTime($this->uploadTime)
->setExifData($exifData)
->create();

$filePath = $file->getPath();
if (!\is_dir($filePath)) {
\mkdir($filePath, recursive: true);
}

if ($this->copy) {
\copy($this->pathname, $filePath . $file->getSourceFilename());
} else {
\rename($this->pathname, $filePath . $file->getSourceFilename());
}

return $file;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php

namespace wcf\command\file;

use wcf\data\file\File;
use wcf\data\file\FileBuilder;
use wcf\data\file\temporary\FileTemporary;
use wcf\event\file\UploadCompleted;
use wcf\system\event\EventHandler;
use wcf\util\ExifUtil;
use wcf\util\FileUtil;
use wcf\util\ImageUtil;

/**
* Promotes a completely uploaded temporary file into a regular file.
*
* @author Alexander Ebert, Marcel Werk
* @copyright 2001-2026 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @since 6.3
*/
final class CreateFileFromTemporary
{
public function __construct(
private readonly FileTemporary $fileTemporary,
) {}

public function __invoke(): File
{
$pathname = $this->fileTemporary->getPathname();
$mimeType = FileUtil::getMimeType($pathname);
$isImage = ImageUtil::isImageMimeType($mimeType);

$width = $height = null;
if ($isImage) {
[$width, $height] = \getimagesize($pathname);
}

$exifData = $this->fileTemporary->exifData;
if ($exifData !== null) {
$exifData = \unserialize($exifData);
}

$fileSize = $this->fileTemporary->fileSize;
$fileHash = $this->fileTemporary->fileHash;
if ($isImage) {
$imageWasModified = false;
try {
$imageWasModified = ExifUtil::normalizeImageRotation(
$pathname,
$width,
$height,
$mimeType,
$exifData,
);

if ($imageWasModified && $exifData !== null) {
unset($exifData['IFD0']['Orientation']);
}
} catch (\Throwable) {
}

if ($imageWasModified) {
$fileSize = \filesize($pathname);
$fileHash = \hash_file('sha256', $pathname);
[$width, $height] = \getimagesize($pathname);
}
}

$file = FileBuilder::forCreate()
->setFilename($this->fileTemporary->filename)
->setFileSize($fileSize)
->setFileHash($fileHash)
->setFileExtension(File::getSafeFileExtension($mimeType, $this->fileTemporary->filename))
->setObjectTypeID($this->fileTemporary->objectTypeID)
->setMimeType($mimeType)
->setDimensions($width, $height)
->setUploadTime(\TIME_NOW)
->setExifData($exifData)
->create();

$filePath = $file->getPath();
if (!\is_dir($filePath)) {
\mkdir($filePath, recursive: true);
}

\rename(
$pathname,
$filePath . $file->getSourceFilename()
);

$event = new UploadCompleted($file);
EventHandler::getInstance()->fire($event);

return $event->getFile();
}
}
25 changes: 25 additions & 0 deletions wcfsetup/install/files/lib/command/file/DeleteFile.class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace wcf\command\file;

use wcf\data\file\File;

/**
* Deletes a file permanently.
*
* @author Marcel Werk
* @copyright 2001-2026 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @since 6.3
*/
final class DeleteFile
{
public function __construct(
private readonly File $file,
) {}

public function __invoke(): void
{
new DeleteFiles([$this->file])();
}
}
45 changes: 45 additions & 0 deletions wcfsetup/install/files/lib/command/file/DeleteFiles.class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace wcf\command\file;

use wcf\data\file\File;
use wcf\data\file\FileBuilder;
use wcf\event\file\FileDeleted;
use wcf\system\event\EventHandler;
use wcf\system\file\processor\FileProcessor;

/**
* Deletes files permanently.
*
* @author Marcel Werk
* @copyright 2001-2026 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @since 6.3
*/
final class DeleteFiles
{
/**
* @param non-empty-list<File> $files
*/
public function __construct(
private readonly array $files,
) {}

public function __invoke(): void
{
FileProcessor::getInstance()->delete(
$this->files
);

FileBuilder::deleteAll(\array_map(
static fn($object) => $object->fileID,
$this->files
));

foreach ($this->files as $file) {
EventHandler::getInstance()->fire(
new FileDeleted($file)
);
}
}
}
43 changes: 18 additions & 25 deletions wcfsetup/install/files/lib/command/file/ReplaceFileSource.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace wcf\command\file;

use wcf\data\file\File;
use wcf\data\file\FileEditor;
use wcf\data\file\FileBuilder;
use wcf\system\file\processor\exception\DamagedImage;
use wcf\system\file\processor\FileProcessor;
use wcf\system\WCF;
Expand Down Expand Up @@ -101,17 +101,14 @@ private function replaceSource(): File
$statement = WCF::getDB()->prepare($sql);
$statement->execute([$this->file->fileID]);

(new FileEditor($this->file))->update([
'filename' => $filename,
'fileSize' => $fileSize,
'fileHash' => $fileHash,
'fileExtension' => $fileExtension,
'mimeType' => $mimeType,
'width' => $width,
'height' => $height,
]);

$updatedFile = new File($this->file->fileID);
$updatedFile = FileBuilder::forUpdate($this->file)
->setFilename($filename)
->setFileSize($fileSize)
->setFileHash($fileHash)
->setFileExtension($fileExtension)
->setMimeType($mimeType)
->setDimensions($width, $height)
->update();

$path = \dirname($updatedFile->getPathname());
FileUtil::makePath($path);
Expand All @@ -135,10 +132,9 @@ private function replaceSource(): File
// original version with it.
@\unlink($webpVariant);

(new FileEditor($updatedFile))->update([
'fileHashWebp' => null,
]);
$updatedFile = new File($updatedFile->fileID);
$updatedFile = FileBuilder::forUpdate($updatedFile)
->setFileHashWebp(null)
->update();
} else {
if (\file_exists($webpVariant)) {
$newWebpVariant = $updatedFile->getPathnameWebp();
Expand All @@ -151,10 +147,9 @@ private function replaceSource(): File
} else {
// The variant file is missing on disk, clear the
// stale reference.
(new FileEditor($updatedFile))->update([
'fileHashWebp' => null,
]);
$updatedFile = new File($updatedFile->fileID);
$updatedFile = FileBuilder::forUpdate($updatedFile)
->setFileHashWebp(null)
->update();
}
}
}
Expand Down Expand Up @@ -185,11 +180,9 @@ private function discardWebpVariantOfWebpFile(File $file): File
\unlink($file->getPathnameWebp());
}

(new FileEditor($file))->update([
'fileHashWebp' => null,
]);

return new File($file->fileID);
return FileBuilder::forUpdate($file)
->setFileHashWebp(null)
->update();
}

private function regenerateExistingThumbnails(File $file): File
Expand Down
Loading
Loading