WIP: Implement uploads
This commit is contained in:
1 parent
ce97017ac4
commit
d6687c3ca4
51 files changed
+1123
-168
No files matched your search
@@ -5,8 +5,10 @@ namespace App\Controller\Teamer;
|
||||
use App\BusProNet\ApiClient;
|
||||
use App\BusProNet\ApiClientException;
|
||||
use App\Entity\Teamer;
|
||||
use App\Entity\Upload;
|
||||
use App\Entity\User;
|
||||
use App\Form\TeamerProfileType;
|
||||
use App\Service\Upload\UploadHandler;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
@@ -18,7 +20,8 @@ class ProfileController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly ApiClient $apiClient
|
||||
private readonly ApiClient $apiClient,
|
||||
private readonly UploadHandler $uploadHandler
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -36,10 +39,22 @@ class ProfileController extends AbstractController
|
||||
$this->entityManager->persist($teamer);
|
||||
}
|
||||
|
||||
$form = $this->createForm(TeamerProfileType::class, $teamer);
|
||||
$uploadSession = $this->uploadHandler->getUploadSession();
|
||||
|
||||
$form = $this->createForm(TeamerProfileType::class, $teamer, ['upload_session' => $uploadSession]);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
if ($uploadSession->getCount() > 0) {
|
||||
if (null !== $existingUpload = $teamer->getPhoto()) {
|
||||
$this->entityManager->remove($existingUpload);
|
||||
}
|
||||
$upload = Upload::fromUploadDto($uploadSession->getUploads()->first(), $user, Upload::TYPE_PHOTO);
|
||||
$teamer->setPhoto($upload);
|
||||
$this->uploadHandler->moveUploadSessionFilesFromOrphanage('photo', $uploadSession);
|
||||
$this->uploadHandler->destroyUploadSession();
|
||||
}
|
||||
|
||||
try {
|
||||
$bpnPassword = $request->getSession()->get('bpn_password');
|
||||
$this->apiClient->updateProfile($user, $bpnPassword);
|
||||
@@ -47,6 +62,8 @@ class ProfileController extends AbstractController
|
||||
} catch (ApiClientException $e) {
|
||||
}
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->addFlash('success', 'Deine Daten wurden aktualisiert');
|
||||
|
||||
return $this->redirectToRoute('app_teamer_profile');
|
||||
@@ -54,6 +71,7 @@ class ProfileController extends AbstractController
|
||||
|
||||
return $this->render('teamer/profile.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'teamer' => $teamer,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Upload;
|
||||
|
||||
use App\Service\Upload\UploadHandler;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
class DeleteController extends AbstractController
|
||||
{
|
||||
public function __construct(private readonly UploadHandler $uploadHandler)
|
||||
{}
|
||||
|
||||
#[Route('/upload/delete', name: 'app_upload_delete', methods: ['DELETE'])]
|
||||
#[IsGranted('ROLE_USER')]
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$uuid = $request->get('uuid');
|
||||
|
||||
$this->uploadHandler->removeUploadFromSession($uuid);
|
||||
|
||||
return $this->json([
|
||||
'success' => true,
|
||||
'uuid' => $uuid,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace App\Entity;
|
||||
|
||||
use App\Entity\Traits\BlameableEntity;
|
||||
use App\Entity\Traits\TimestampableEntity;
|
||||
use App\Model\UploadDto;
|
||||
use App\Repository\UploadRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
@@ -14,6 +15,8 @@ class Upload implements BlameableEntityInterface, TimestampableEntityInterface
|
||||
use BlameableEntity;
|
||||
use TimestampableEntity;
|
||||
|
||||
public const TYPE_PHOTO = 'photo';
|
||||
|
||||
public const STATUS_NEW = 'new';
|
||||
public const STATUS_IN_PROCESS = 'in_process';
|
||||
public const STATUS_CHECKED = 'checked';
|
||||
@@ -52,6 +55,21 @@ class Upload implements BlameableEntityInterface, TimestampableEntityInterface
|
||||
$this->uuid = Uuid::v4();
|
||||
}
|
||||
|
||||
public static function fromUploadDto(UploadDto $uploadDto, User $owner, string $type): static
|
||||
{
|
||||
$instance = new static();
|
||||
$instance
|
||||
->setFilename($uploadDto->getFilename())
|
||||
->setOriginalFilename($uploadDto->getOriginalFilename())
|
||||
->setMimeType($uploadDto->getMimeType())
|
||||
->setSize($uploadDto->getSize())
|
||||
->setOwner($owner)
|
||||
->setType($type)
|
||||
;
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\EventListener;
|
||||
|
||||
use App\Entity\Upload;
|
||||
use App\Service\Upload\UploadHandler;
|
||||
use Doctrine\ORM\Event\PreRemoveEventArgs;
|
||||
use Liip\ImagineBundle\Imagine\Cache\CacheManager;
|
||||
|
||||
class DeleteUploadListener
|
||||
{
|
||||
public function __construct(
|
||||
private readonly UploadHandler $uploadHandler,
|
||||
private readonly CacheManager $cacheManager
|
||||
) {
|
||||
}
|
||||
|
||||
public function preRemove(PreRemoveEventArgs $eventArgs): void
|
||||
{
|
||||
$object = $eventArgs->getObject();
|
||||
|
||||
if (!$object instanceof Upload) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove uploaded original
|
||||
$this->uploadHandler->removeUploadedFile($object);
|
||||
|
||||
// Remove potential variants from public folders
|
||||
$this->cacheManager->remove($object->getFilename());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\EventListener;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Security\Http\Event\LogoutEvent;
|
||||
|
||||
class LogoutListener
|
||||
{
|
||||
public function __construct(private readonly LoggerInterface $authenticationLogger)
|
||||
{
|
||||
}
|
||||
|
||||
public function onLogout(LogoutEvent $event): void
|
||||
{
|
||||
$this->authenticationLogger->info('Logout', [
|
||||
'user' => $event->getToken()->getUserIdentifier(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\EventListener;
|
||||
|
||||
use App\Model\UploadDto;
|
||||
use App\Service\Upload\UploadHandler;
|
||||
use Oneup\UploaderBundle\Event\PostUploadEvent;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
|
||||
class UploadSessionListener
|
||||
{
|
||||
public function __construct(private readonly UploadHandler $uploadHandler)
|
||||
{
|
||||
}
|
||||
|
||||
public function onUpload(PostUploadEvent $event): void
|
||||
{
|
||||
$request = $event->getRequest();
|
||||
/** @var File $uploadedFile */
|
||||
$uploadedFile = $event->getFile();
|
||||
|
||||
// Get upload uuid from Dropzone request
|
||||
$uuid = $request->get('uuid');
|
||||
|
||||
// Get original filename from Dropzone request
|
||||
$originalFileName = $request->get('originalFilename');
|
||||
|
||||
$upload = new UploadDto(
|
||||
$uuid,
|
||||
$uploadedFile->getFilename(),
|
||||
$originalFileName,
|
||||
$uploadedFile->getMimeType(),
|
||||
$uploadedFile->getSize()
|
||||
);
|
||||
|
||||
$uploadSession = $this->uploadHandler->addUploadToSession($upload);
|
||||
|
||||
$response = $event->getResponse();
|
||||
$response['uploadedTotalSize'] = $uploadSession->getSize();
|
||||
$response['uploadedTotalFiles'] = $uploadSession->getCount();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\EventListener;
|
||||
|
||||
use App\Model\UploadSessionDto;
|
||||
use App\Service\Upload\UploadHandler;
|
||||
use Oneup\UploaderBundle\Event\ValidationEvent;
|
||||
use Oneup\UploaderBundle\Uploader\Exception\ValidationException;
|
||||
|
||||
class UploadSessionValidationListener
|
||||
{
|
||||
public function onValidate(ValidationEvent $event): void
|
||||
{
|
||||
if ('upload_collection' !== $event->getType()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$request = $event->getRequest();
|
||||
$session = $request->getSession();
|
||||
$uploadSessionId = $request->get(UploadHandler::SESSION_KEY);
|
||||
|
||||
if (null === $uploadSessionId || false === $session->has(UploadHandler::SESSION_KEY)) {
|
||||
throw new ValidationException('Invalid upload session');
|
||||
}
|
||||
|
||||
/** @var UploadSessionDto $uploadSession */
|
||||
$uploadSession = $session->get(UploadHandler::SESSION_KEY);
|
||||
|
||||
if (!$uploadSession instanceof UploadSessionDto || $uploadSession->getUid() !== $uploadSessionId) {
|
||||
throw new ValidationException('Invalid upload session');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,16 @@
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\Teamer;
|
||||
use App\Model\UploadSessionDto;
|
||||
use App\Service\Upload\UploadHandler;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class TeamerProfileType extends AbstractType
|
||||
@@ -77,10 +81,23 @@ class TeamerProfileType extends AbstractType
|
||||
;
|
||||
}
|
||||
|
||||
public function buildView(FormView $view, FormInterface $form, array $options): void
|
||||
{
|
||||
/** @var UploadSessionDto $uploadSession */
|
||||
$uploadSession = $options['upload_session'];
|
||||
$view->vars['upload_session_params'] = [
|
||||
UploadHandler::SESSION_KEY => $uploadSession->getUid(),
|
||||
];
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Teamer::class,
|
||||
]);
|
||||
$resolver
|
||||
->setDefaults([
|
||||
'data_class' => Teamer::class,
|
||||
])
|
||||
->setRequired(['upload_session'])
|
||||
->setAllowedTypes('upload_session', UploadSessionDto::class)
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
class UploadDto
|
||||
{
|
||||
private string $uuid;
|
||||
private string $filename;
|
||||
private string $originalFilename;
|
||||
private string $mimeType;
|
||||
private int $size;
|
||||
|
||||
public function __construct(
|
||||
string $uuid,
|
||||
string $filename,
|
||||
string $originalFilename,
|
||||
string $mimeType,
|
||||
int $size
|
||||
) {
|
||||
$this->uuid = $uuid;
|
||||
$this->filename = $filename;
|
||||
$this->originalFilename = $originalFilename;
|
||||
$this->mimeType = $mimeType;
|
||||
$this->size = $size;
|
||||
}
|
||||
|
||||
public function getUuid(): string
|
||||
{
|
||||
return $this->uuid;
|
||||
}
|
||||
|
||||
public function getFilename(): string
|
||||
{
|
||||
return $this->filename;
|
||||
}
|
||||
|
||||
public function getOriginalFilename(): string
|
||||
{
|
||||
return $this->originalFilename;
|
||||
}
|
||||
|
||||
public function getMimeType(): string
|
||||
{
|
||||
return $this->mimeType;
|
||||
}
|
||||
|
||||
public function getSize(): int
|
||||
{
|
||||
return $this->size;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
class UploadSessionDto
|
||||
{
|
||||
private string $uid;
|
||||
|
||||
#[Assert\Count(min: 1, minMessage: 'Bitte mindestens eine Datei hochladen')]
|
||||
private Collection $uploads;
|
||||
|
||||
public function __construct(string $uid = null)
|
||||
{
|
||||
$this->uid = $uid ?? Uuid::v4();
|
||||
$this->uploads = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getUid(): string
|
||||
{
|
||||
return $this->uid;
|
||||
}
|
||||
|
||||
public function addUpload(UploadDto $upload): static
|
||||
{
|
||||
if (false === $this->uploads->contains($upload)) {
|
||||
$this->uploads[] = $upload;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeUpload(string $uuid): static
|
||||
{
|
||||
$uploadToRemove = $this->uploads->filter(function (UploadDto $upload) use ($uuid) {
|
||||
return $uuid === $upload->getUuid();
|
||||
})->first();
|
||||
|
||||
if (null !== $uploadToRemove) {
|
||||
$this->uploads->removeElement($uploadToRemove);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUploads(): Collection
|
||||
{
|
||||
return $this->uploads;
|
||||
}
|
||||
|
||||
public function flushUploads(): static
|
||||
{
|
||||
$this->uploads->clear();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSize(): int
|
||||
{
|
||||
$size = 0;
|
||||
|
||||
foreach ($this->uploads as $upload) {
|
||||
$size += $upload->getSize();
|
||||
}
|
||||
|
||||
return $size;
|
||||
}
|
||||
|
||||
public function getCount(): int
|
||||
{
|
||||
return $this->uploads->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\Upload;
|
||||
|
||||
use App\Entity\Upload;
|
||||
use App\Model\UploadDto;
|
||||
use App\Model\UploadSessionDto;
|
||||
use Oneup\UploaderBundle\Uploader\Orphanage\OrphanageManager;
|
||||
use Symfony\Component\Filesystem\Exception\IOException;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
class UploadHandler
|
||||
{
|
||||
public const SESSION_KEY = 'myep_upload_session';
|
||||
|
||||
public function __construct(
|
||||
private readonly RequestStack $requestStack,
|
||||
private readonly OrphanageManager $orphanageManager,
|
||||
private readonly string $projectDir
|
||||
) {
|
||||
}
|
||||
|
||||
public function getUploadSession(): UploadSessionDto
|
||||
{
|
||||
$session = $this->requestStack->getSession();
|
||||
|
||||
if ($session->has(self::SESSION_KEY) && null !== $session->get(self::SESSION_KEY)) {
|
||||
/** @var UploadSessionDto $uploadSession */
|
||||
$uploadSession = $session->get(self::SESSION_KEY);
|
||||
} else {
|
||||
$uploadSession = new UploadSessionDto();
|
||||
$session->set(self::SESSION_KEY, $uploadSession);
|
||||
}
|
||||
|
||||
return $uploadSession;
|
||||
}
|
||||
|
||||
public function destroyUploadSession(): void
|
||||
{
|
||||
$this->requestStack->getSession()->remove(self::SESSION_KEY);
|
||||
}
|
||||
|
||||
public function flushUploadSession(UploadSessionDto $uploadSession): void
|
||||
{
|
||||
$uploadSession->flushUploads();
|
||||
$session = $this->requestStack->getSession();
|
||||
$session->set(self::SESSION_KEY, $uploadSession);
|
||||
}
|
||||
|
||||
public function addUploadToSession(UploadDto $upload): UploadSessionDto
|
||||
{
|
||||
$uploadSession = $this->getUploadSession();
|
||||
$uploadSession->addUpload($upload);
|
||||
|
||||
$session = $this->requestStack->getSession();
|
||||
$session->set(self::SESSION_KEY, $uploadSession);
|
||||
|
||||
return $uploadSession;
|
||||
}
|
||||
|
||||
public function removeUploadFromSession(string $filename): UploadSessionDto
|
||||
{
|
||||
$uploadSession = $this->getUploadSession();
|
||||
$uploadSession->removeUpload($filename);
|
||||
|
||||
$session = $this->requestStack->getSession();
|
||||
$session->set(self::SESSION_KEY, $uploadSession);
|
||||
|
||||
return $uploadSession;
|
||||
}
|
||||
|
||||
public function moveUploadSessionFilesFromOrphanage(string $uploader, UploadSessionDto $uploadSession): void
|
||||
{
|
||||
$uploadSessionFilenames = $this->getUploadSessionFilenames($uploadSession, true);
|
||||
$this->moveUploads($uploader, $uploadSessionFilenames);
|
||||
}
|
||||
|
||||
public function moveUploads(string $uploader, array $filenames = []): array
|
||||
{
|
||||
$manager = $this->orphanageManager->get($uploader);
|
||||
|
||||
if (0 === count($filenames)) {
|
||||
return $manager->uploadFiles();
|
||||
}
|
||||
|
||||
$files = $manager->getFiles();
|
||||
$files->files()->name($filenames);
|
||||
|
||||
return $manager->uploadFiles(iterator_to_array($files));
|
||||
}
|
||||
|
||||
public function removeUploadedFile(Upload $upload): void
|
||||
{
|
||||
$filepath = $this->getUploadFilepath($upload);
|
||||
|
||||
$filesystem = new Filesystem();
|
||||
|
||||
try {
|
||||
$filesystem->remove($filepath);
|
||||
} catch (IOException $e) {
|
||||
}
|
||||
}
|
||||
|
||||
public function getUploadSessionFilenames(UploadSessionDto $uploadSession, bool $basename = false): array
|
||||
{
|
||||
$uploadSessionId = $uploadSession->getUid();
|
||||
|
||||
return $uploadSession
|
||||
->getUploads()
|
||||
->map(function (UploadDto $uploadDto) use ($basename, $uploadSessionId) {
|
||||
return true === $basename ? $uploadDto->getFilename() : $this->getTempUploadFilepath($uploadDto, $uploadSessionId);
|
||||
})
|
||||
->toArray()
|
||||
;
|
||||
}
|
||||
|
||||
public function getUploadFilepath(Upload $upload): string
|
||||
{
|
||||
return sprintf('%s/uploads/%s/%s', $this->projectDir, $upload->getType(), $upload->getFilename());
|
||||
}
|
||||
|
||||
public function getTempUploadFilepath(UploadDto $uploadDto, string $uploadSessionId): string
|
||||
{
|
||||
$tempUploadDir = $this->getTempUploadDir($uploadSessionId);
|
||||
|
||||
return sprintf('%s/%s', $tempUploadDir, $uploadDto->getFilename());
|
||||
}
|
||||
|
||||
public function getTempUploadDir(string $uploadSessionId): string
|
||||
{
|
||||
return sprintf('%s/uploads/temp/%s', $this->projectDir, $uploadSessionId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\Upload;
|
||||
|
||||
use Oneup\UploaderBundle\Uploader\File\FileInterface;
|
||||
use Oneup\UploaderBundle\Uploader\Naming\NamerInterface;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
class UploadNamer implements NamerInterface
|
||||
{
|
||||
public function name(FileInterface $file): string
|
||||
{
|
||||
return sprintf('%s.%s', Uuid::v4(), strtolower($file->getExtension()));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user