diff --git a/formwork/src/App.php b/formwork/src/App.php index 15151aaa..df9c619c 100644 --- a/formwork/src/App.php +++ b/formwork/src/App.php @@ -9,6 +9,7 @@ use Formwork\Fields\Dynamic\DynamicFieldValue; use Formwork\Files\FileFactory; use Formwork\Files\FileUriGenerator; +use Formwork\Files\Services\FileUploader; use Formwork\Http\Request; use Formwork\Http\Response; use Formwork\Images\ImageFactory; @@ -222,6 +223,8 @@ protected function loadServices(Container $container): void $container->define(ImageFactory::class); $container->define(FileUriGenerator::class); + + $container->define(FileUploader::class); } /** diff --git a/formwork/src/Files/FileUploader.php b/formwork/src/Files/FileUploader.php deleted file mode 100644 index f141d24d..00000000 --- a/formwork/src/Files/FileUploader.php +++ /dev/null @@ -1,42 +0,0 @@ - $allowedMimeTypes - */ - public function __construct(protected array $allowedMimeTypes) - { - } - - /** - * @return array - */ - public function allowedMimeTypes(): array - { - return $this->allowedMimeTypes; - } - - public function upload(UploadedFile $uploadedFile, string $destinationPath, ?string $name = null, bool $overwrite = false): File - { - $mimeType = MimeType::fromFile($uploadedFile->tempPath()); - - if (!in_array($mimeType, $this->allowedMimeTypes, true)) { - throw new TranslatedException(sprintf('Invalid mime type %s for file uploads', $mimeType), 'upload.error.mimeType'); - } - - $filename = Str::slug($name ?? pathinfo($uploadedFile->clientName(), PATHINFO_FILENAME)) . '.' . MimeType::toExtension($mimeType); - - $uploadedFile->move($destinationPath, $filename, $overwrite); - - return new File(FileSystem::joinPaths($destinationPath, $filename)); - } -} diff --git a/formwork/src/Files/Services/FileUploader.php b/formwork/src/Files/Services/FileUploader.php new file mode 100644 index 00000000..ebfa7e07 --- /dev/null +++ b/formwork/src/Files/Services/FileUploader.php @@ -0,0 +1,78 @@ + + */ + protected array $allowedMimeTypes; + + public function __construct(protected Config $config, protected FileFactory $fileFactory) + { + $this->allowedMimeTypes = Arr::map($this->config->get('system.files.allowedExtensions'), fn (string $ext) => MimeType::fromExtension($ext)); + } + + /** + * @return list + */ + public function allowedMimeTypes(): array + { + return $this->allowedMimeTypes; + } + + /** + * @param ?list $allowedMimeTypes + */ + public function upload(UploadedFile $uploadedFile, string $destinationPath, ?string $name = null, bool $overwrite = false, ?array $allowedMimeTypes = null): File + { + $mimeType = MimeType::fromFile($uploadedFile->tempPath()); + + $allowedMimeTypes ??= $this->allowedMimeTypes; + + if (!in_array($mimeType, $allowedMimeTypes, true)) { + throw new TranslatedException(sprintf('Invalid mime type %s for file uploads', $mimeType), 'upload.error.mimeType'); + } + + $filename = Str::slug($name ?? pathinfo($uploadedFile->clientName(), PATHINFO_FILENAME)) . '.' . MimeType::toExtension($mimeType); + + $uploadedFile->move($destinationPath, $filename, $overwrite); + + $file = $this->fileFactory->make(FileSystem::joinPaths($destinationPath, $filename)); + + if ($file instanceof Image) { + switch ($file->mimeType()) { + case 'image/jpeg': + case 'image/png': + case 'image/webp': + // Process JPEG, PNG and WebP images according to system options (e.g. quality) + if ($this->config->get('system.uploads.processImages')) { + $file->save(); + } + break; + + case 'image/svg+xml': + // Sanitize SVG images + $svgSanitizer = new SvgSanitizer(); + $data = FileSystem::read($file->path()); + FileSystem::write($file->path(), $svgSanitizer->sanitize($data)); + break; + } + } + + return $file; + } +} diff --git a/formwork/src/Http/Files/UploadedFile.php b/formwork/src/Http/Files/UploadedFile.php index c2eaf0dc..ffb78a03 100644 --- a/formwork/src/Http/Files/UploadedFile.php +++ b/formwork/src/Http/Files/UploadedFile.php @@ -116,6 +116,10 @@ public function getErrorTranslationString(): string public function move(string $destination, string $filename, bool $overwrite = false): bool { + if ($this->error !== UPLOAD_ERR_OK) { + throw new TranslatedException(sprintf('Cannot upload file "%s": %s', $this->fieldName(), $this->getErrorMessage()), $this->getErrorTranslationString()); + } + if (strlen($filename) > FileSystem::MAX_NAME_LENGTH) { throw new TranslatedException('File name too long', 'upload.error.fileNameTooLong'); } diff --git a/formwork/src/Images/Handler/AbstractHandler.php b/formwork/src/Images/Handler/AbstractHandler.php index c69bdf97..842f296b 100644 --- a/formwork/src/Images/Handler/AbstractHandler.php +++ b/formwork/src/Images/Handler/AbstractHandler.php @@ -30,9 +30,12 @@ public function __construct(protected string $data, array $options = []) $this->options = [...$this->defaults(), ...$options]; } - public static function fromPath(string $path): static + /** + * @param array $options + */ + public static function fromPath(string $path, array $options = []): static { - return new static(FileSystem::read($path)); + return new static(FileSystem::read($path), $options); } public static function fromGdImage(GdImage $gdImage, array $options = []): static diff --git a/formwork/src/Images/Image.php b/formwork/src/Images/Image.php index a1beb46d..50bf1c2b 100644 --- a/formwork/src/Images/Image.php +++ b/formwork/src/Images/Image.php @@ -417,11 +417,11 @@ protected function handler(): AbstractHandler protected function getHandler(): AbstractHandler { return match ($this->mimeType()) { - 'image/jpeg' => JpegHandler::fromPath($this->path), - 'image/png' => PngHandler::fromPath($this->path), - 'image/gif' => GifHandler::fromPath($this->path), - 'image/webp' => WebpHandler::fromPath($this->path), - 'image/svg+xml' => SvgHandler::fromPath($this->path), + 'image/jpeg' => JpegHandler::fromPath($this->path, $this->options), + 'image/png' => PngHandler::fromPath($this->path, $this->options), + 'image/gif' => GifHandler::fromPath($this->path, $this->options), + 'image/webp' => WebpHandler::fromPath($this->path, $this->options), + 'image/svg+xml' => SvgHandler::fromPath($this->path, $this->options), default => throw new RuntimeException('Unsupported image type'), }; } diff --git a/formwork/src/Panel/Controllers/PagesController.php b/formwork/src/Panel/Controllers/PagesController.php index 2d92574b..5fa7998d 100644 --- a/formwork/src/Panel/Controllers/PagesController.php +++ b/formwork/src/Panel/Controllers/PagesController.php @@ -7,7 +7,7 @@ use Formwork\Fields\FieldCollection; use Formwork\Files\File; use Formwork\Files\FileCollection; -use Formwork\Files\FileUploader; +use Formwork\Files\Services\FileUploader; use Formwork\Http\Files\UploadedFile; use Formwork\Http\JsonResponse; use Formwork\Http\RedirectResponse; @@ -15,7 +15,6 @@ use Formwork\Http\RequestData; use Formwork\Http\RequestMethod; use Formwork\Http\Response; -use Formwork\Images\Image; use Formwork\Pages\Page; use Formwork\Panel\ContentHistory\ContentHistory; use Formwork\Panel\ContentHistory\ContentHistoryEvent; @@ -26,7 +25,6 @@ use Formwork\Utils\Constraint; use Formwork\Utils\Date; use Formwork\Utils\FileSystem; -use Formwork\Utils\MimeType; use Formwork\Utils\Str; use Formwork\Utils\Uri; use RuntimeException; @@ -792,28 +790,19 @@ protected function updatePage(Page $page, RequestData $requestData, FieldCollect /** * Process page uploads * - * @param array $files - * @param list $mimeTypes + * @param list $files + * @param list $mimeTypes */ protected function processPageUploads(array $files, Page $page, ?array $mimeTypes = null, ?string $name = null, bool $overwrite = false): void { - $mimeTypes ??= Arr::map($this->config->get('system.files.allowedExtensions'), fn (string $ext) => MimeType::fromExtension($ext)); + $fileUploader = $this->app->getService(FileUploader::class); - $fileUploader = new FileUploader($mimeTypes); + if ($page->contentPath() === null) { + throw new UnexpectedValueException('Unexpected missing page path'); + } foreach ($files as $file) { - if (!$file->isUploaded()) { - throw new TranslatedException(sprintf('Cannot upload file "%s"', $file->fieldName()), $file->getErrorTranslationString()); - } - if ($page->contentPath() === null) { - throw new UnexpectedValueException('Unexpected missing page path'); - } - $uploadedFile = $fileUploader->upload($file, $page->contentPath(), $name, $overwrite); - // Process JPEG and PNG images according to system options (e.g. quality) - if ($this->config->get('system.uploads.processImages') && in_array($uploadedFile->mimeType(), ['image/jpeg', 'image/png'], true)) { - $image = new Image($uploadedFile->path(), $this->config->get('system.images')); - $image->save(); - } + $fileUploader->upload($file, $page->contentPath(), $name, overwrite: $overwrite, allowedMimeTypes: $mimeTypes); } $page->reload(); diff --git a/formwork/src/Panel/Controllers/UsersController.php b/formwork/src/Panel/Controllers/UsersController.php index 91a16b20..89232c72 100644 --- a/formwork/src/Panel/Controllers/UsersController.php +++ b/formwork/src/Panel/Controllers/UsersController.php @@ -5,7 +5,7 @@ use Formwork\Exceptions\TranslatedException; use Formwork\Fields\Exceptions\ValidationException; use Formwork\Fields\FieldCollection; -use Formwork\Files\FileUploader; +use Formwork\Files\Services\FileUploader; use Formwork\Http\FileResponse; use Formwork\Http\Files\UploadedFile; use Formwork\Http\RedirectResponse; @@ -267,31 +267,31 @@ protected function updateUser(User $user, FieldCollection $fieldCollection): voi * * @param array $mimeTypes */ - protected function uploadUserImage(User $user, UploadedFile $file, array $mimeTypes): ?string + protected function uploadUserImage(User $user, UploadedFile $uploadedFile, array $mimeTypes): ?string { $imagesPath = FileSystem::joinPaths($this->config->get('system.users.paths.images')); - $fileUploader = new FileUploader($mimeTypes); + $fileUploader = $this->app->getService(FileUploader::class); - $uploadedFile = $fileUploader->upload($file, $imagesPath, FileSystem::randomName()); + $file = $fileUploader->upload($uploadedFile, $imagesPath, FileSystem::randomName(), allowedMimeTypes: $mimeTypes); - if ($uploadedFile->type() === 'image') { - $userImageSize = $this->config->get('system.panel.userImageSize'); + if (!($file instanceof Image)) { + return null; + } - // Square off uploaded image - $image = new Image($uploadedFile->path(), $this->config->get('system.images')); - $image->square($userImageSize)->save(); + $userImageSize = $this->config->get('system.panel.userImageSize'); - // Delete old image - if (!$user->hasDefaultImage()) { - $this->deleteUserImage($user); - } + // Square off uploaded image + $file->square($userImageSize)->save(); - $this->panel()->notify($this->translate('panel.user.image.uploaded'), 'success'); - return $uploadedFile->name(); + // Delete old image + if (!$user->hasDefaultImage()) { + $this->deleteUserImage($user); } - return null; + $this->panel()->notify($this->translate('panel.user.image.uploaded'), 'success'); + + return $file->name(); } /**