mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-21 09:35:49 +02:00
Merge branch 'upload_overhaul'
This commit is contained in:
commit
f5bbb8bdd6
11 changed files with 300 additions and 54 deletions
|
@ -26,6 +26,7 @@
|
||||||
"florianv/swap": "^4.0",
|
"florianv/swap": "^4.0",
|
||||||
"florianv/swap-bundle": "dev-master",
|
"florianv/swap-bundle": "dev-master",
|
||||||
"gregwar/captcha-bundle": "^2.1.0",
|
"gregwar/captcha-bundle": "^2.1.0",
|
||||||
|
"hshn/base64-encoded-file": "^5.0",
|
||||||
"jbtronics/2fa-webauthn": "^v2.2.0",
|
"jbtronics/2fa-webauthn": "^v2.2.0",
|
||||||
"jbtronics/dompdf-font-loader-bundle": "^1.0.0",
|
"jbtronics/dompdf-font-loader-bundle": "^1.0.0",
|
||||||
"jfcherng/php-diff": "^6.14",
|
"jfcherng/php-diff": "^6.14",
|
||||||
|
|
64
composer.lock
generated
64
composer.lock
generated
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "6c3d6e309f579d6683344fead9a86d50",
|
"content-hash": "d262b7af88fd38fff57c486ce7f61cbe",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "api-platform/core",
|
"name": "api-platform/core",
|
||||||
|
@ -2904,6 +2904,68 @@
|
||||||
],
|
],
|
||||||
"time": "2023-12-03T20:05:35+00:00"
|
"time": "2023-12-03T20:05:35+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "hshn/base64-encoded-file",
|
||||||
|
"version": "v5.0.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/hshn/base64-encoded-file.git",
|
||||||
|
"reference": "54fa81461ba4fbf5b67ed71d22b43ea5cc8c8748"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/hshn/base64-encoded-file/zipball/54fa81461ba4fbf5b67ed71d22b43ea5cc8c8748",
|
||||||
|
"reference": "54fa81461ba4fbf5b67ed71d22b43ea5cc8c8748",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1.0",
|
||||||
|
"symfony/http-foundation": "^5.4 || ^6.0 || ^7.0",
|
||||||
|
"symfony/mime": "^5.4 || ^6.0 || ^7.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^9.0.0",
|
||||||
|
"symfony/config": "^5.4 || ^6.0 || ^7.0",
|
||||||
|
"symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0",
|
||||||
|
"symfony/form": "^5.4 || ^6.0 || ^7.0",
|
||||||
|
"symfony/http-kernel": "^5.4 || ^6.0 || ^7.0",
|
||||||
|
"symfony/serializer": "^5.4 || ^6.0 || ^7.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"symfony/config": "to use the bundle in a Symfony project",
|
||||||
|
"symfony/dependency-injection": "to use the bundle in a Symfony project",
|
||||||
|
"symfony/form": "to use base64_encoded_file type",
|
||||||
|
"symfony/http-kernel": "to use the bundle in a Symfony project",
|
||||||
|
"symfony/serializer": "to convert a base64 string to a Base64EncodedFile object"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "5.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Hshn\\Base64EncodedFile\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Shota Hoshino",
|
||||||
|
"email": "sht.hshn@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Provides handling base64 encoded files, and the integration of symfony/form",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/hshn/base64-encoded-file/issues",
|
||||||
|
"source": "https://github.com/hshn/base64-encoded-file/tree/v5.0.1"
|
||||||
|
},
|
||||||
|
"time": "2023-12-24T07:23:07+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "imagine/imagine",
|
"name": "imagine/imagine",
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
|
|
69
src/ApiPlatform/HandleAttachmentsUploadsProcessor.php
Normal file
69
src/ApiPlatform/HandleAttachmentsUploadsProcessor.php
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 - 2024 Jan Böhmer (https://github.com/jbtronics)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
|
* by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
|
||||||
|
namespace App\ApiPlatform;
|
||||||
|
|
||||||
|
use ApiPlatform\Metadata\DeleteOperationInterface;
|
||||||
|
use ApiPlatform\Metadata\Operation;
|
||||||
|
use ApiPlatform\State\ProcessorInterface;
|
||||||
|
use App\Entity\Attachments\Attachment;
|
||||||
|
use App\Services\Attachments\AttachmentSubmitHandler;
|
||||||
|
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This state processor handles the upload property set on the deserialized attachment entity and
|
||||||
|
* calls the upload handler service to handle the upload.
|
||||||
|
*/
|
||||||
|
final class HandleAttachmentsUploadsProcessor implements ProcessorInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
#[Autowire(service: 'api_platform.doctrine.orm.state.persist_processor')]
|
||||||
|
private readonly ProcessorInterface $persistProcessor,
|
||||||
|
#[Autowire(service: 'api_platform.doctrine.orm.state.remove_processor')]
|
||||||
|
private readonly ProcessorInterface $removeProcessor,
|
||||||
|
private readonly AttachmentSubmitHandler $attachmentSubmitHandler
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = [])
|
||||||
|
{
|
||||||
|
if ($operation instanceof DeleteOperationInterface) {
|
||||||
|
return $this->removeProcessor->process($data, $operation, $uriVariables, $context);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if the attachment has any upload data we need to handle
|
||||||
|
//This have to happen before the persist processor is called, because the changes on the entity must be saved!
|
||||||
|
if ($data instanceof Attachment && $data->getUpload()) {
|
||||||
|
$upload = $data->getUpload();
|
||||||
|
//Reset the upload data
|
||||||
|
$data->setUpload(null);
|
||||||
|
|
||||||
|
$this->attachmentSubmitHandler->handleUpload($data, $upload);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->persistProcessor->process($data, $operation, $uriVariables, $context);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,7 @@ namespace App\Controller\AdminPages;
|
||||||
|
|
||||||
use App\DataTables\LogDataTable;
|
use App\DataTables\LogDataTable;
|
||||||
use App\Entity\Attachments\Attachment;
|
use App\Entity\Attachments\Attachment;
|
||||||
|
use App\Entity\Attachments\AttachmentUpload;
|
||||||
use App\Entity\Base\AbstractDBElement;
|
use App\Entity\Base\AbstractDBElement;
|
||||||
use App\Entity\Base\AbstractNamedDBElement;
|
use App\Entity\Base\AbstractNamedDBElement;
|
||||||
use App\Entity\Base\AbstractPartsContainingDBElement;
|
use App\Entity\Base\AbstractPartsContainingDBElement;
|
||||||
|
@ -175,16 +176,10 @@ abstract class BaseAdminController extends AbstractController
|
||||||
$attachments = $form['attachments'];
|
$attachments = $form['attachments'];
|
||||||
foreach ($attachments as $attachment) {
|
foreach ($attachments as $attachment) {
|
||||||
/** @var FormInterface $attachment */
|
/** @var FormInterface $attachment */
|
||||||
$options = [
|
|
||||||
'secure_attachment' => $attachment['secureFile']->getData(),
|
|
||||||
'download_url' => $attachment['downloadURL']->getData(),
|
|
||||||
];
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->attachmentSubmitHandler->handleFormSubmit(
|
$this->attachmentSubmitHandler->handleUpload(
|
||||||
$attachment->getData(),
|
$attachment->getData(),
|
||||||
$attachment['file']->getData(),
|
AttachmentUpload::fromAttachmentForm($attachment)
|
||||||
$options
|
|
||||||
);
|
);
|
||||||
} catch (AttachmentDownloadException $attachmentDownloadException) {
|
} catch (AttachmentDownloadException $attachmentDownloadException) {
|
||||||
$this->addFlash(
|
$this->addFlash(
|
||||||
|
@ -270,10 +265,9 @@ abstract class BaseAdminController extends AbstractController
|
||||||
];
|
];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->attachmentSubmitHandler->handleFormSubmit(
|
$this->attachmentSubmitHandler->handleUpload(
|
||||||
$attachment->getData(),
|
$attachment->getData(),
|
||||||
$attachment['file']->getData(),
|
AttachmentUpload::fromAttachmentForm($attachment)
|
||||||
$options
|
|
||||||
);
|
);
|
||||||
} catch (AttachmentDownloadException $attachmentDownloadException) {
|
} catch (AttachmentDownloadException $attachmentDownloadException) {
|
||||||
$this->addFlash(
|
$this->addFlash(
|
||||||
|
|
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use App\DataTables\LogDataTable;
|
use App\DataTables\LogDataTable;
|
||||||
|
use App\Entity\Attachments\AttachmentUpload;
|
||||||
use App\Entity\Parts\Category;
|
use App\Entity\Parts\Category;
|
||||||
use App\Entity\Parts\Footprint;
|
use App\Entity\Parts\Footprint;
|
||||||
use App\Entity\Parts\Manufacturer;
|
use App\Entity\Parts\Manufacturer;
|
||||||
|
@ -301,13 +302,9 @@ class PartController extends AbstractController
|
||||||
$attachments = $form['attachments'];
|
$attachments = $form['attachments'];
|
||||||
foreach ($attachments as $attachment) {
|
foreach ($attachments as $attachment) {
|
||||||
/** @var FormInterface $attachment */
|
/** @var FormInterface $attachment */
|
||||||
$options = [
|
|
||||||
'secure_attachment' => $attachment['secureFile']->getData(),
|
|
||||||
'download_url' => $attachment['downloadURL']->getData(),
|
|
||||||
];
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->attachmentSubmitHandler->handleFormSubmit($attachment->getData(), $attachment['file']->getData(), $options);
|
$this->attachmentSubmitHandler->handleUpload($attachment->getData(), AttachmentUpload::fromAttachmentForm($attachment));
|
||||||
} catch (AttachmentDownloadException $attachmentDownloadException) {
|
} catch (AttachmentDownloadException $attachmentDownloadException) {
|
||||||
$this->addFlash(
|
$this->addFlash(
|
||||||
'error',
|
'error',
|
||||||
|
|
|
@ -35,6 +35,7 @@ use ApiPlatform\Metadata\Post;
|
||||||
use App\ApiPlatform\DocumentedAPIProperty;
|
use App\ApiPlatform\DocumentedAPIProperty;
|
||||||
use App\ApiPlatform\Filter\EntityFilter;
|
use App\ApiPlatform\Filter\EntityFilter;
|
||||||
use App\ApiPlatform\Filter\LikeFilter;
|
use App\ApiPlatform\Filter\LikeFilter;
|
||||||
|
use App\ApiPlatform\HandleAttachmentsUploadsProcessor;
|
||||||
use App\Repository\AttachmentRepository;
|
use App\Repository\AttachmentRepository;
|
||||||
use App\EntityListeners\AttachmentDeleteListener;
|
use App\EntityListeners\AttachmentDeleteListener;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
|
@ -68,12 +69,13 @@ use LogicException;
|
||||||
operations: [
|
operations: [
|
||||||
new Get(security: 'is_granted("read", object)'),
|
new Get(security: 'is_granted("read", object)'),
|
||||||
new GetCollection(security: 'is_granted("@attachments.list_attachments")'),
|
new GetCollection(security: 'is_granted("@attachments.list_attachments")'),
|
||||||
new Post(securityPostDenormalize: 'is_granted("create", object)'),
|
new Post(securityPostDenormalize: 'is_granted("create", object)', ),
|
||||||
new Patch(security: 'is_granted("edit", object)'),
|
new Patch(security: 'is_granted("edit", object)'),
|
||||||
new Delete(security: 'is_granted("delete", object)'),
|
new Delete(security: 'is_granted("delete", object)'),
|
||||||
],
|
],
|
||||||
normalizationContext: ['groups' => ['attachment:read', 'attachment:read:standalone', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
|
normalizationContext: ['groups' => ['attachment:read', 'attachment:read:standalone', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
|
||||||
denormalizationContext: ['groups' => ['attachment:write', 'attachment:write:standalone', 'api:basic:write'], 'openapi_definition_name' => 'Write'],
|
denormalizationContext: ['groups' => ['attachment:write', 'attachment:write:standalone', 'api:basic:write'], 'openapi_definition_name' => 'Write'],
|
||||||
|
processor: HandleAttachmentsUploadsProcessor::class,
|
||||||
)]
|
)]
|
||||||
#[DocumentedAPIProperty(schemaName: 'Attachment-Read', property: 'media_url', type: 'string', nullable: true,
|
#[DocumentedAPIProperty(schemaName: 'Attachment-Read', property: 'media_url', type: 'string', nullable: true,
|
||||||
description: 'The URL to the file, where the attachment file can be downloaded. This can be an internal or external URL.',
|
description: 'The URL to the file, where the attachment file can be downloaded. This can be an internal or external URL.',
|
||||||
|
@ -132,6 +134,14 @@ abstract class Attachment extends AbstractNamedDBElement
|
||||||
*/
|
*/
|
||||||
protected const ALLOWED_ELEMENT_CLASS = AttachmentContainingDBElement::class;
|
protected const ALLOWED_ELEMENT_CLASS = AttachmentContainingDBElement::class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var AttachmentUpload|null The options used for uploading a file to this attachment or modify it.
|
||||||
|
* This value is not persisted in the database, but is just used to pass options to the upload manager.
|
||||||
|
* If it is null, no upload process is started.
|
||||||
|
*/
|
||||||
|
#[Groups(['attachment:write'])]
|
||||||
|
protected ?AttachmentUpload $upload = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string|null the original filename the file had, when the user uploaded it
|
* @var string|null the original filename the file had, when the user uploaded it
|
||||||
*/
|
*/
|
||||||
|
@ -192,6 +202,31 @@ abstract class Attachment extends AbstractNamedDBElement
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the upload currently associated with this attachment.
|
||||||
|
* This is only temporary and not persisted directly in the database.
|
||||||
|
* @internal This function should only be used by the Attachment Submit handler service
|
||||||
|
* @return AttachmentUpload|null
|
||||||
|
*/
|
||||||
|
public function getUpload(): ?AttachmentUpload
|
||||||
|
{
|
||||||
|
return $this->upload;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current upload for this attachment.
|
||||||
|
* It will be processed as the attachment is persisted/flushed.
|
||||||
|
* @param AttachmentUpload|null $upload
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setUpload(?AttachmentUpload $upload): Attachment
|
||||||
|
{
|
||||||
|
$this->upload = $upload;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/***********************************************************
|
/***********************************************************
|
||||||
* Various function
|
* Various function
|
||||||
***********************************************************/
|
***********************************************************/
|
||||||
|
|
77
src/Entity/Attachments/AttachmentUpload.php
Normal file
77
src/Entity/Attachments/AttachmentUpload.php
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 - 2024 Jan Böhmer (https://github.com/jbtronics)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
|
* by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
|
||||||
|
namespace App\Entity\Attachments;
|
||||||
|
|
||||||
|
use Symfony\Component\Form\FormInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||||
|
use Symfony\Component\Serializer\Attribute\Groups;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a DTO representing a file upload for an attachment and which is used to pass data to the Attachment
|
||||||
|
* submit handler service.
|
||||||
|
*/
|
||||||
|
class AttachmentUpload
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
/** @var UploadedFile|null The file which was uploaded, or null if the file should not be changed */
|
||||||
|
public readonly ?UploadedFile $file,
|
||||||
|
/** @var string|null The base64 encoded data of the file which should be uploaded. */
|
||||||
|
#[Groups(['attachment:write'])]
|
||||||
|
public readonly ?string $data = null,
|
||||||
|
/** @vaar string|null The original filename of the file passed in data. */
|
||||||
|
#[Groups(['attachment:write'])]
|
||||||
|
public readonly ?string $filename = null,
|
||||||
|
/** @var bool True, if the URL in the attachment should be downloaded by Part-DB */
|
||||||
|
#[Groups(['attachment:write'])]
|
||||||
|
public readonly bool $downloadUrl = false,
|
||||||
|
/** @var bool If true the file will be moved to private attachment storage,
|
||||||
|
* if false it will be moved to public attachment storage. On null file is not moved
|
||||||
|
*/
|
||||||
|
#[Groups(['attachment:write'])]
|
||||||
|
public readonly ?bool $private = null,
|
||||||
|
/** @var bool If true and no preview image was set yet, the new uploaded file will become the preview image */
|
||||||
|
#[Groups(['attachment:write'])]
|
||||||
|
public readonly ?bool $becomePreviewIfEmpty = true,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an AttachmentUpload object from an Attachment FormInterface
|
||||||
|
* @param FormInterface $form
|
||||||
|
* @return AttachmentUpload
|
||||||
|
*/
|
||||||
|
public static function fromAttachmentForm(FormInterface $form): AttachmentUpload
|
||||||
|
{
|
||||||
|
if (!$form->has('file')) {
|
||||||
|
throw new \InvalidArgumentException('The form does not have a file field. Is it an attachment form?');
|
||||||
|
}
|
||||||
|
|
||||||
|
return new self(
|
||||||
|
file: $form->get('file')->getData(),
|
||||||
|
downloadUrl: $form->get('downloadURL')->getData(),
|
||||||
|
private: $form->get('secureFile')->getData()
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -48,6 +48,8 @@ class DetermineTypeFromElementIRIDenormalizer implements DenormalizerInterface,
|
||||||
|
|
||||||
use DenormalizerAwareTrait;
|
use DenormalizerAwareTrait;
|
||||||
|
|
||||||
|
private const ALREADY_CALLED = self::class . '::ALREADY_CALLED';
|
||||||
|
|
||||||
public function __construct(private readonly IriConverterInterface $iriConverter, private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory)
|
public function __construct(private readonly IriConverterInterface $iriConverter, private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -94,13 +96,18 @@ class DetermineTypeFromElementIRIDenormalizer implements DenormalizerInterface,
|
||||||
$data = $this->addTypeDiscriminatorIfNecessary($data, $context['operation']);
|
$data = $this->addTypeDiscriminatorIfNecessary($data, $context['operation']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$context[self::ALREADY_CALLED] = true;
|
||||||
|
|
||||||
return $this->denormalizer->denormalize($data, $type, $format, $context);
|
return $this->denormalizer->denormalize($data, $type, $format, $context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function supportsDenormalization(mixed $data, string $type, ?string $format = null)
|
public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool
|
||||||
{
|
{
|
||||||
//Only denormalize if the _type discriminator is not set and the class is supported
|
//Only denormalize if the _type discriminator is not set and the class is supported and we not have already called this function
|
||||||
return is_array($data) && !isset($data['_type']) && in_array($type, self::SUPPORTED_CLASSES, true);
|
return !isset($context[self::ALREADY_CALLED])
|
||||||
|
&& is_array($data)
|
||||||
|
&& !isset($data['_type'])
|
||||||
|
&& in_array($type, self::SUPPORTED_CLASSES, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSupportedTypes(?string $format): array
|
public function getSupportedTypes(?string $format): array
|
||||||
|
|
|
@ -38,7 +38,7 @@ class OverrideClassDenormalizer implements DenormalizerInterface, DenormalizerAw
|
||||||
|
|
||||||
public const CONTEXT_KEY = '__override_type__';
|
public const CONTEXT_KEY = '__override_type__';
|
||||||
|
|
||||||
public function denormalize(mixed $data, string $type, ?string $format = null, array $context = [])
|
public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed
|
||||||
{
|
{
|
||||||
//Deserialize the data with the overridden type
|
//Deserialize the data with the overridden type
|
||||||
$overrideType = $context[self::CONTEXT_KEY];
|
$overrideType = $context[self::CONTEXT_KEY];
|
||||||
|
|
|
@ -26,6 +26,7 @@ use App\Entity\Attachments\Attachment;
|
||||||
use App\Entity\Attachments\AttachmentContainingDBElement;
|
use App\Entity\Attachments\AttachmentContainingDBElement;
|
||||||
use App\Entity\Attachments\AttachmentType;
|
use App\Entity\Attachments\AttachmentType;
|
||||||
use App\Entity\Attachments\AttachmentTypeAttachment;
|
use App\Entity\Attachments\AttachmentTypeAttachment;
|
||||||
|
use App\Entity\Attachments\AttachmentUpload;
|
||||||
use App\Entity\Attachments\CategoryAttachment;
|
use App\Entity\Attachments\CategoryAttachment;
|
||||||
use App\Entity\Attachments\CurrencyAttachment;
|
use App\Entity\Attachments\CurrencyAttachment;
|
||||||
use App\Entity\Attachments\LabelAttachment;
|
use App\Entity\Attachments\LabelAttachment;
|
||||||
|
@ -39,6 +40,8 @@ use App\Entity\Attachments\StorageLocationAttachment;
|
||||||
use App\Entity\Attachments\SupplierAttachment;
|
use App\Entity\Attachments\SupplierAttachment;
|
||||||
use App\Entity\Attachments\UserAttachment;
|
use App\Entity\Attachments\UserAttachment;
|
||||||
use App\Exceptions\AttachmentDownloadException;
|
use App\Exceptions\AttachmentDownloadException;
|
||||||
|
use Hshn\Base64EncodedFile\HttpFoundation\File\Base64EncodedFile;
|
||||||
|
use Hshn\Base64EncodedFile\HttpFoundation\File\UploadedBase64EncodedFile;
|
||||||
use const DIRECTORY_SEPARATOR;
|
use const DIRECTORY_SEPARATOR;
|
||||||
use function get_class;
|
use function get_class;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
|
@ -179,27 +182,39 @@ class AttachmentSubmitHandler
|
||||||
* This function will move the uploaded file or download the URL file to server, if needed.
|
* This function will move the uploaded file or download the URL file to server, if needed.
|
||||||
*
|
*
|
||||||
* @param Attachment $attachment the attachment that should be used for handling
|
* @param Attachment $attachment the attachment that should be used for handling
|
||||||
* @param UploadedFile|null $file If given, that file will be moved to the right location
|
* @param AttachmentUpload|null $upload The upload options DTO. If it is null, it will be tried to get from the attachment option
|
||||||
* @param array $options The options to use with the upload. Here you can specify that a URL should be downloaded,
|
|
||||||
* or an file should be moved to a secure location.
|
|
||||||
*
|
*
|
||||||
* @return Attachment The attachment with the new filename (same instance as passed $attachment)
|
* @return Attachment The attachment with the new filename (same instance as passed $attachment)
|
||||||
*/
|
*/
|
||||||
public function handleFormSubmit(Attachment $attachment, ?UploadedFile $file, array $options = []): Attachment
|
public function handleUpload(Attachment $attachment, ?AttachmentUpload $upload): Attachment
|
||||||
{
|
{
|
||||||
$resolver = new OptionsResolver();
|
if ($upload === null) {
|
||||||
$this->configureOptions($resolver);
|
$upload = $attachment->getUpload();
|
||||||
$options = $resolver->resolve($options);
|
if ($upload === null) {
|
||||||
|
throw new InvalidArgumentException('No upload options given and no upload options set in attachment!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = $upload->file;
|
||||||
|
|
||||||
|
//If no file was uploaded, but we have base64 encoded data, create a file from it
|
||||||
|
if (!$file && $upload->data !== null) {
|
||||||
|
$file = new UploadedBase64EncodedFile(new Base64EncodedFile($upload->data), $upload->filename ?? 'base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
//By default we assume a public upload
|
||||||
|
$secure_attachment = $upload->private ?? false;
|
||||||
|
|
||||||
//When a file is given then upload it, otherwise check if we need to download the URL
|
//When a file is given then upload it, otherwise check if we need to download the URL
|
||||||
if ($file instanceof UploadedFile) {
|
if ($file instanceof UploadedFile) {
|
||||||
$this->upload($attachment, $file, $options);
|
|
||||||
} elseif ($options['download_url'] && $attachment->isExternal()) {
|
$this->upload($attachment, $file, $secure_attachment);
|
||||||
$this->downloadURL($attachment, $options);
|
} elseif ($upload->downloadUrl && $attachment->isExternal()) {
|
||||||
|
$this->downloadURL($attachment, $secure_attachment);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Move the attachment files to secure location (and back) if needed
|
//Move the attachment files to secure location (and back) if needed
|
||||||
$this->moveFile($attachment, $options['secure_attachment']);
|
$this->moveFile($attachment, $secure_attachment);
|
||||||
|
|
||||||
//Rename blacklisted (unsecure) files to a better extension
|
//Rename blacklisted (unsecure) files to a better extension
|
||||||
$this->renameBlacklistedExtensions($attachment);
|
$this->renameBlacklistedExtensions($attachment);
|
||||||
|
@ -208,7 +223,7 @@ class AttachmentSubmitHandler
|
||||||
$element = $attachment->getElement();
|
$element = $attachment->getElement();
|
||||||
if ($element instanceof AttachmentContainingDBElement) {
|
if ($element instanceof AttachmentContainingDBElement) {
|
||||||
//Make this attachment the master picture if needed and this was requested
|
//Make this attachment the master picture if needed and this was requested
|
||||||
if ($options['become_preview_if_empty']
|
if ($upload->becomePreviewIfEmpty
|
||||||
&& $element->getMasterPictureAttachment() === null //Element must not have an preview image yet
|
&& $element->getMasterPictureAttachment() === null //Element must not have an preview image yet
|
||||||
&& null === $attachment->getID() //Attachment must be null
|
&& null === $attachment->getID() //Attachment must be null
|
||||||
&& $attachment->isPicture() //Attachment must be a picture
|
&& $attachment->isPicture() //Attachment must be a picture
|
||||||
|
@ -261,17 +276,6 @@ class AttachmentSubmitHandler
|
||||||
return $attachment;
|
return $attachment;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function configureOptions(OptionsResolver $resolver): void
|
|
||||||
{
|
|
||||||
$resolver->setDefaults([
|
|
||||||
//If no preview image was set yet, the new uploaded file will become the preview image
|
|
||||||
'become_preview_if_empty' => true,
|
|
||||||
//When a URL is given download the URL
|
|
||||||
'download_url' => false,
|
|
||||||
'secure_attachment' => false,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move the given attachment to secure location (or back to public folder) if needed.
|
* Move the given attachment to secure location (or back to public folder) if needed.
|
||||||
*
|
*
|
||||||
|
@ -325,11 +329,11 @@ class AttachmentSubmitHandler
|
||||||
/**
|
/**
|
||||||
* Download the URL set in the attachment and save it on the server.
|
* Download the URL set in the attachment and save it on the server.
|
||||||
*
|
*
|
||||||
* @param array $options The options from the handleFormSubmit function
|
* @param bool $secureAttachment True if the file should be moved to the secure attachment storage
|
||||||
*
|
*
|
||||||
* @return Attachment The attachment with the new filepath
|
* @return Attachment The attachment with the new filepath
|
||||||
*/
|
*/
|
||||||
protected function downloadURL(Attachment $attachment, array $options): Attachment
|
protected function downloadURL(Attachment $attachment, bool $secureAttachment): Attachment
|
||||||
{
|
{
|
||||||
//Check if we are allowed to download files
|
//Check if we are allowed to download files
|
||||||
if (!$this->allow_attachments_downloads) {
|
if (!$this->allow_attachments_downloads) {
|
||||||
|
@ -339,7 +343,7 @@ class AttachmentSubmitHandler
|
||||||
$url = $attachment->getURL();
|
$url = $attachment->getURL();
|
||||||
|
|
||||||
$fs = new Filesystem();
|
$fs = new Filesystem();
|
||||||
$attachment_folder = $this->generateAttachmentPath($attachment, $options['secure_attachment']);
|
$attachment_folder = $this->generateAttachmentPath($attachment, $secureAttachment);
|
||||||
$tmp_path = $attachment_folder.DIRECTORY_SEPARATOR.$this->generateAttachmentFilename($attachment, 'tmp');
|
$tmp_path = $attachment_folder.DIRECTORY_SEPARATOR.$this->generateAttachmentFilename($attachment, 'tmp');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -408,15 +412,15 @@ class AttachmentSubmitHandler
|
||||||
*
|
*
|
||||||
* @param Attachment $attachment The attachment in which the file should be saved
|
* @param Attachment $attachment The attachment in which the file should be saved
|
||||||
* @param UploadedFile $file The file which was uploaded
|
* @param UploadedFile $file The file which was uploaded
|
||||||
* @param array $options The options from the handleFormSubmit function
|
* @param bool $secureAttachment True if the file should be moved to the secure attachment storage
|
||||||
*
|
*
|
||||||
* @return Attachment The attachment with the new filepath
|
* @return Attachment The attachment with the new filepath
|
||||||
*/
|
*/
|
||||||
protected function upload(Attachment $attachment, UploadedFile $file, array $options): Attachment
|
protected function upload(Attachment $attachment, UploadedFile $file, bool $secureAttachment): Attachment
|
||||||
{
|
{
|
||||||
//Move our temporay attachment to its final location
|
//Move our temporary attachment to its final location
|
||||||
$file_path = $file->move(
|
$file_path = $file->move(
|
||||||
$this->generateAttachmentPath($attachment, $options['secure_attachment']),
|
$this->generateAttachmentPath($attachment, $secureAttachment),
|
||||||
$this->generateAttachmentFilename($attachment, $file->getClientOriginalExtension())
|
$this->generateAttachmentFilename($attachment, $file->getClientOriginalExtension())
|
||||||
)->getRealPath();
|
)->getRealPath();
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ namespace App\Services\UserSystem;
|
||||||
|
|
||||||
use App\Entity\Attachments\Attachment;
|
use App\Entity\Attachments\Attachment;
|
||||||
use App\Entity\Attachments\AttachmentType;
|
use App\Entity\Attachments\AttachmentType;
|
||||||
|
use App\Entity\Attachments\AttachmentUpload;
|
||||||
use App\Entity\Attachments\UserAttachment;
|
use App\Entity\Attachments\UserAttachment;
|
||||||
use App\Entity\UserSystem\User;
|
use App\Entity\UserSystem\User;
|
||||||
use App\Services\Attachments\AttachmentSubmitHandler;
|
use App\Services\Attachments\AttachmentSubmitHandler;
|
||||||
|
@ -156,11 +157,10 @@ class UserAvatarHelper
|
||||||
}
|
}
|
||||||
|
|
||||||
$attachment->setAttachmentType($attachment_type);
|
$attachment->setAttachmentType($attachment_type);
|
||||||
//$user->setMasterPictureAttachment($attachment);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Handle the upload
|
//Handle the upload
|
||||||
$this->submitHandler->handleFormSubmit($attachment, $file);
|
$this->submitHandler->handleUpload($attachment, new AttachmentUpload(file: $file));
|
||||||
|
|
||||||
//Set attachment as master picture
|
//Set attachment as master picture
|
||||||
$user->setMasterPictureAttachment($attachment);
|
$user->setMasterPictureAttachment($attachment);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue