Browse Source

Merge branch 'V8-7323-servir-les-images-rognes'

Olivier Massot 6 months ago
parent
commit
a7549c6b88

+ 7 - 3
src/Entity/Core/File.php

@@ -7,6 +7,7 @@ namespace App\Entity\Core;
 use ApiPlatform\Metadata\ApiResource;
 use ApiPlatform\Metadata\Delete;
 use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\Patch;
 use ApiPlatform\Metadata\Post;
 use ApiPlatform\Metadata\Put;
 use App\Entity\AccessWish\DocumentWish;
@@ -28,6 +29,7 @@ use App\Enum\Core\FileTypeEnum;
 use App\Enum\Core\FileVisibilityEnum;
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use App\Repository\Core\FileRepository;
+use App\State\Processor\Core\FileProcessor;
 use Doctrine\Common\Collections\ArrayCollection;
 use Doctrine\Common\Collections\Collection;
 use Doctrine\ORM\Mapping as ORM;
@@ -45,9 +47,11 @@ use Symfony\Component\Serializer\Annotation\Groups;
     operations: [
         new Get(security: "is_granted('READ', object)"),
         new Put(security: "is_granted('EDIT', object)"),
+        new Patch(security: "is_granted('EDIT', object)"),
         new Post(security: "is_granted('CREATE', object)"),
         new Delete(security: "is_granted('DELETE', object)"),
-    ]
+    ],
+    processor: FileProcessor::class
 )]
 // #[Auditable]
 #[ORM\Entity(repositoryClass: FileRepository::class)]
@@ -559,12 +563,12 @@ class File
         return $this;
     }
 
-    public function getQrCode(): Parameters
+    public function getQrCode(): ?Parameters
     {
         return $this->qrCode;
     }
 
-    public function setQrCode(Parameters $qrCode): self
+    public function setQrCode(?Parameters $qrCode): self
     {
         $this->qrCode = $qrCode;
 

+ 3 - 1
src/Entity/Organization/Organization.php

@@ -53,6 +53,7 @@ use App\Enum\Organization\TypeEstablishmentDetailEnum;
 use App\Enum\Organization\TypeEstablishmentEnum;
 use App\Repository\Organization\OrganizationRepository;
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
+use App\State\Processor\Organization\OrganizationProcessor;
 use Doctrine\Common\Collections\ArrayCollection;
 use Doctrine\Common\Collections\Collection;
 use Doctrine\ORM\Mapping as ORM;
@@ -68,7 +69,8 @@ use JetBrains\PhpStorm\Pure;
 #[ApiResource(
     operations: [
         new Get(security: 'object.getId() == user.getOrganization().getId()'),
-    ]
+    ],
+    processor: OrganizationProcessor::class,
 )]
 // #[Auditable]
 #[ORM\Entity(repositoryClass: OrganizationRepository::class)]

+ 1 - 1
src/Entity/Organization/Parameters.php

@@ -149,7 +149,7 @@ class Parameters
     #[ORM\Column(options: ['default' => false])]
     private bool $studentsAreAdherents = false;
 
-    #[ORM\OneToOne(inversedBy: 'qrCode', targetEntity: File::class, cascade: ['persist'], fetch: 'EAGER')]
+    #[ORM\OneToOne(targetEntity: File::class, inversedBy: 'qrCode', cascade: ['persist'], fetch: 'EAGER')]
     #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
     private ?File $qrCode = null;
 

+ 2 - 2
src/Service/File/Factory/ImageFactory.php

@@ -27,9 +27,9 @@ class ImageFactory
      * @param File   $file       : File contenant l'image de base
      * @param string $filterName : nom du filtre liip à appliquer
      */
-    public function createImageContent(File $file, string $filterName): void
+    public function createImageContent(File $file, string $filterName, bool $uncropped = false): void
     {
-        $filters_options = $this->getCropFilterOptions($file->getConfig());
+        $filters_options = $uncropped ? [] : $this->getCropFilterOptions($file->getConfig());
         $path = $file->getSlug();
         $this->createAndStore($path, $filterName, $filters_options);
     }

+ 2 - 2
src/Service/File/FileManager.php

@@ -64,11 +64,11 @@ class FileManager
      *
      * @throws FileNotFoundException
      */
-    public function getImageUrl(File $file, string $size, bool $relativePath = false): string
+    public function getImageUrl(File $file, string $size, bool $relativePath = false, bool $uncropped = false): string
     {
         $storage = $this->getStorageFor($file);
 
-        return $storage->getImageUrl($file, $size, $relativePath);
+        return $storage->getImageUrl($file, $size, $relativePath, $uncropped);
     }
 
     /**

+ 5 - 1
src/Service/File/Storage/ApiLegacyStorage.php

@@ -42,10 +42,14 @@ class ApiLegacyStorage implements FileStorageInterface
     /**
      * Retoune l'URL de l'image, à la bonne taille, contenu dans la File.
      */
-    public function getImageUrl(File $file, string $size, bool $relativePath): string
+    public function getImageUrl(File $file, string $size, bool $relativePath, bool $uncropped = false): string
     {
         $url = sprintf('api/files/%s/download/%s?relativePath=1', $file->getId(), $size);
 
+        if ($uncropped) {
+            $url .= '&noCache=true';
+        }
+
         // L'url interne est l'équivalent d'un chemin relatif dans ce cas
         $baseUrl = $relativePath ? $this->legacyBaseUrl : $this->publicLegacyBaseUrl;
 

+ 1 - 1
src/Service/File/Storage/FileStorageInterface.php

@@ -14,7 +14,7 @@ interface FileStorageInterface
     public function read(File $file): string;
 
     // TODO: remplacer `string $size` par `FileSizeEnum $size`
-    public function getImageUrl(File $file, string $size, bool $relativePath): string;
+    public function getImageUrl(File $file, string $size, bool $relativePath, bool $uncropped = false): string;
 
     public function support(File $file): bool;
 

+ 17 - 8
src/Service/File/Storage/LocalStorage.php

@@ -93,20 +93,24 @@ class LocalStorage implements FileStorageInterface
     /**
      * Retourne l'URL de l'image, à la bonne taille, contenu dans le File.
      */
-    public function getImageUrl(File $file, string $size, bool $relativePath): string
+    public function getImageUrl(File $file, string $size, bool $relativePath, bool $uncropped = false): string
     {
-        $filterName = $this->getFilterFromSizeAndConfig($size, !empty($file->getConfig()));
+        $crop = !empty($file->getConfig()) && !$uncropped;
+
+        $filterName = $this->getFilterFromSizeAndConfig($size, $crop);
+
         $path = $file->getSlug();
 
         if (!$this->cacheManager->isStored($path, $filterName)) {
             try {
-                $this->imageFactory->createImageContent($file, $filterName);
+                $this->imageFactory->createImageContent($file, $filterName, $uncropped);
             } catch (\Exception $e) {
                 $path = 'images/missing-file.png';
 
                 return $relativePath ? $path : $this->urlBuilder->getAbsoluteUrl($path);
             }
         }
+
         $url = $this->cacheManager->resolve($path, $filterName);
 
         return $relativePath ? $this->urlBuilder->getRelativeUrl($url) : $url;
@@ -115,18 +119,18 @@ class LocalStorage implements FileStorageInterface
     /**
      * Retourne le filtre Liip correspondant à la taille désirée.
      */
-    protected function getFilterFromSizeAndConfig(string $size, bool $configExist): string
+    protected function getFilterFromSizeAndConfig(string $size, bool $crop = true): string
     {
         switch ($size) {
             case FileSizeEnum::SM->value :
-                $filter = $configExist ? self::CROP_SM : self::SM_FOLDER;
+                $filter = $crop ? self::CROP_SM : self::SM_FOLDER;
                 break;
             case FileSizeEnum::MD->value :
             default:
-                $filter = $configExist ? self::CROP_MD : self::MD_FOLDER;
+                $filter = $crop ? self::CROP_MD : self::MD_FOLDER;
                 break;
             case FileSizeEnum::LG->value :
-                $filter = $configExist ? self::CROP_LG : self::LG_FOLDER;
+                $filter = $crop ? self::CROP_LG : self::LG_FOLDER;
                 break;
         }
 
@@ -192,7 +196,12 @@ class LocalStorage implements FileStorageInterface
             throw new \RuntimeException('File has no filename');
         }
 
-        $isNewFile = $file->getSlug() === null;
+        try {
+            $isNewFile = $file->getSlug() === null;
+        } catch (\Throwable) {
+            $isNewFile = true; // Catch case where slud has not been initialized
+        }
+
         if ($isNewFile) {
             // Try to get the Access owner from the organization_id and person_id
             $access = null;

+ 39 - 0
src/Service/OnChange/Core/OnFileChange.php

@@ -0,0 +1,39 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Service\OnChange\Core;
+
+use App\Entity\Core\File;
+use App\Service\OnChange\OnChangeContext;
+use App\Service\OnChange\OnChangeDefault;
+use Liip\ImagineBundle\Imagine\Cache\CacheManager;
+
+/**
+ * Classe OnOrganizationChange qui comporte toutes les opérations automatiques se produisant lors de l'évolution d'une organisation.
+ */
+class OnFileChange extends OnChangeDefault
+{
+    public function __construct(
+        protected readonly CacheManager $cacheManager
+    ) {
+
+    }
+
+
+    public function beforeChange(mixed $file, OnChangeContext $context): void
+    {
+        if ($context->previousData() && $context->previousData()->getConfig() !== $file->getConfig()) {
+            $this->onConfigChange($file);
+        }
+    }
+
+    /**
+     * La configuration d'un fichier a changé (i.e. le cropping).
+     * On invalide le cache Liip.
+     */
+    public function onConfigChange(File $file): void
+    {
+        $this->cacheManager->remove($file->getSlug());
+    }
+}

+ 22 - 0
src/State/Processor/Core/FileProcessor.php

@@ -0,0 +1,22 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\State\Processor\Core;
+
+use App\Service\OnChange\Core\OnFileChange;
+use App\State\Processor\EntityProcessor;
+use JetBrains\PhpStorm\Pure;
+
+/**
+ * Classe OrganizationProcessor qui est un custom dataPersister gérant la resource Organization.
+ */
+class FileProcessor extends EntityProcessor
+{
+    #[Pure]
+    public function __construct(
+        OnFileChange $onChange,
+    ) {
+        parent::__construct($onChange);
+    }
+}

+ 7 - 3
src/State/Provider/Core/ImageProvider.php

@@ -39,13 +39,17 @@ final class ImageProvider implements ProviderInterface
             throw new \RuntimeException('not supported', 500);
         }
 
-        return $this->getImage($uriVariables['fileId'], $uriVariables['size']);
+        $uncropped =
+            isset($context['filters']['uncropped']) &&
+            ($context['filters']['uncropped'] === '1' || $context['filters']['uncropped'] === 'true');
+
+        return $this->getImage($uriVariables['fileId'], $uriVariables['size'], $uncropped);
     }
 
     /**
      * @throws FileNotFoundException
      */
-    protected function getImage(int $fileId, string $size): Response
+    protected function getImage(int $fileId, string $size, bool $uncropped = false): Response
     {
         $file = $this->fileRepository->find($fileId);
 
@@ -59,7 +63,7 @@ final class ImageProvider implements ProviderInterface
             throw new \RuntimeException('File '.$fileId.' is not an image.');
         }
 
-        $content = $this->fileManager->getImageUrl($file, $size);
+        $content = $this->fileManager->getImageUrl($file, $size, false, $uncropped);
 
         return new Response($content);
     }