Browse Source

Merge branch 'feature/V8-7432_finaliser_export_licence_cmf' into release/2.6

Olivier Massot 4 months ago
parent
commit
8276d05a61

+ 5 - 1
config/packages/messenger.yaml

@@ -5,7 +5,10 @@ framework:
 
         transports:
             # https://symfony.com/doc/current/messenger.html#transport-configuration
-            async: '%env(MESSENGER_TRANSPORT_DSN)%'
+            async:
+                dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
+                retry_strategy:
+                    max_retries: 0
             failed: '%env(MESSENGER_TRANSPORT_DSN)%?queue_name=failed'
             sync: 'sync://'
 
@@ -18,3 +21,4 @@ framework:
             'App\Message\Message\Typo3\Typo3Undelete': async
             'App\Message\Message\OrganizationCreation': async
             'App\Message\Message\OrganizationDeletion': async
+

+ 4 - 4
env/.env.prod

@@ -6,13 +6,13 @@ ADMIN_BASE_URL=https://admin.opentalent.fr
 ###
 
 ###> api v1 ###
-API_LEG_BASE_URL=https://api.opentalent.fr/api
-PUBLIC_API_LEG_BASE_URL=https://api.opentalent.fr/api
+API_LEG_BASE_URL=https://api.opentalent.fr
+PUBLIC_API_LEG_BASE_URL=https://api.opentalent.fr
 ###
 
 ###> url v2 ###
-API_BASE_URL=https://ap2i.opentalent.fr/api
-PUBLIC_API_BASE_URL=https://ap2i.opentalent.fr/api
+API_BASE_URL=https://ap2i.opentalent.fr
+PUBLIC_API_BASE_URL=https://ap2i.opentalent.fr
 ###
 
 ###> typo3 client ###

+ 1 - 1
env/.env.staging

@@ -18,7 +18,7 @@ PUBLIC_API_LEG_BASE_URL=https://none
 ###< api v1 ###
 
 ###> api v2 ###
-API_BASE_URL=https://ap2i.ci.opentalent.fr/api
+API_BASE_URL=https://ap2i.ci.opentalent.fr
 PUBLIC_API_BASE_URL=https://ap2i.ci.opentalent.fr
 ###< api v2 ###
 

+ 4 - 4
env/.env.test

@@ -8,13 +8,13 @@ ADMIN_BASE_URL=https://admin.test.opentalent.fr
 ###
 
 ###> api v1 ###
-API_LEG_BASE_URL=https://api.test.opentalent.fr/api
-PUBLIC_API_LEG_BASE_URL=https://api.test.opentalent.fr/api
+API_LEG_BASE_URL=https://api.test.opentalent.fr
+PUBLIC_API_LEG_BASE_URL=https://api.test.opentalent.fr
 ###< api v1 ###
 
 ###> api v2 ###
-API_BASE_URL=https://ap2i.test.opentalent.fr/api
-PUBLIC_API_BASE_URL=https://ap2i.test.opentalent.fr/api
+API_BASE_URL=https://ap2i.test.opentalent.fr
+PUBLIC_API_BASE_URL=https://ap2i.test.opentalent.fr
 ###< api v2 ###
 
 ###> typo3 client ###

+ 4 - 4
env/.env.test1

@@ -8,13 +8,13 @@ ADMIN_BASE_URL=https://admin.test1.opentalent.fr
 ###
 
 ###> api v1 ###
-API_LEG_BASE_URL=https://api.test1.opentalent.fr/api
-PUBLIC_API_LEG_BASE_URL=https://api.test1.opentalent.fr/api
+API_LEG_BASE_URL=https://api.test1.opentalent.fr
+PUBLIC_API_LEG_BASE_URL=https://api.test1.opentalent.fr
 ###< api v1 ###
 
 ###> api v2 ###
-API_BASE_URL=https://ap2i.test1.opentalent.fr/api
-PUBLIC_API_BASE_URL=https://ap2i.test1.opentalent.fr/api
+API_BASE_URL=https://ap2i.test1.opentalent.fr
+PUBLIC_API_BASE_URL=https://ap2i.test1.opentalent.fr
 ###< api v2 ###
 
 ###> typo3 client ###

+ 4 - 4
env/.env.test2

@@ -8,13 +8,13 @@ ADMIN_BASE_URL=https://admin.test2.opentalent.fr
 ###
 
 ###> api v1 ###
-API_LEG_BASE_URL=https://api.test2.opentalent.fr/api
-PUBLIC_API_LEG_BASE_URL=https://api.test2.opentalent.fr/api
+API_LEG_BASE_URL=https://api.test2.opentalent.fr
+PUBLIC_API_LEG_BASE_URL=https://api.test2.opentalent.fr
 ###< api v1 ###
 
 ###> api v2 ###
-API_BASE_URL=https://ap2i.test2.opentalent.fr/api
-PUBLIC_API_BASE_URL=https://ap2i.test2.opentalent.fr/api
+API_BASE_URL=https://ap2i.test2.opentalent.fr
+PUBLIC_API_BASE_URL=https://ap2i.test2.opentalent.fr
 ###< api v2 ###
 
 ###> typo3 client ###

+ 4 - 4
env/.env.test3

@@ -8,13 +8,13 @@ ADMIN_BASE_URL=https://admin.test3.opentalent.fr
 ###
 
 ###> api v1 ###
-API_LEG_BASE_URL=https://api.test3.opentalent.fr/api
-PUBLIC_API_LEG_BASE_URL=https://api.test3.opentalent.fr/api
+API_LEG_BASE_URL=https://api.test3.opentalent.fr
+PUBLIC_API_LEG_BASE_URL=https://api.test3.opentalent.fr
 ###< api v1 ###
 
 ###> api v2 ###
-API_BASE_URL=https://ap2i.test3.opentalent.fr/api
-PUBLIC_API_BASE_URL=https://ap2i.test3.opentalent.fr/api
+API_BASE_URL=https://ap2i.test3.opentalent.fr
+PUBLIC_API_BASE_URL=https://ap2i.test3.opentalent.fr
 ###< api v2 ###
 
 ###> typo3 client ###

+ 4 - 4
env/.env.test4

@@ -8,13 +8,13 @@ ADMIN_BASE_URL=https://admin.test4.opentalent.fr
 ###
 
 ###> api v1 ###
-API_LEG_BASE_URL=https://api.test4.opentalent.fr/api
-PUBLIC_API_LEG_BASE_URL=https://api.test4.opentalent.fr/api
+API_LEG_BASE_URL=https://api.test4.opentalent.fr
+PUBLIC_API_LEG_BASE_URL=https://api.test4.opentalent.fr
 ###< api v1 ###
 
 ###> api v2 ###
-API_BASE_URL=https://ap2i.test4.opentalent.fr/api
-PUBLIC_API_BASE_URL=https://ap2i.test4.opentalent.fr/api
+API_BASE_URL=https://ap2i.test4.opentalent.fr
+PUBLIC_API_BASE_URL=https://ap2i.test4.opentalent.fr
 ###< api v2 ###
 
 ###> typo3 client ###

+ 4 - 4
env/.env.test5

@@ -8,13 +8,13 @@ ADMIN_BASE_URL=https://admin.test5.opentalent.fr
 ###
 
 ###> api v1 ###
-API_LEG_BASE_URL=https://api.test5.opentalent.fr/api
-PUBLIC_API_LEG_BASE_URL=https://api.test5.opentalent.fr/api
+API_LEG_BASE_URL=https://api.test5.opentalent.fr
+PUBLIC_API_LEG_BASE_URL=https://api.test5.opentalent.fr
 ###< api v1 ###
 
 ###> api v2 ###
-API_BASE_URL=https://ap2i.test5.opentalent.fr/api
-PUBLIC_API_BASE_URL=https://ap2i.test5.opentalent.fr/api
+API_BASE_URL=https://ap2i.test5.opentalent.fr
+PUBLIC_API_BASE_URL=https://ap2i.test5.opentalent.fr
 ###< api v2 ###
 
 ###> typo3 client ###

+ 4 - 4
env/.env.test6

@@ -8,13 +8,13 @@ ADMIN_BASE_URL=https://admin.test6.opentalent.fr
 ###
 
 ###> api v1 ###
-API_LEG_BASE_URL=https://api.test6.opentalent.fr/api
-PUBLIC_API_LEG_BASE_URL=https://api.test6.opentalent.fr/api
+API_LEG_BASE_URL=https://api.test6.opentalent.fr
+PUBLIC_API_LEG_BASE_URL=https://api.test6.opentalent.fr
 ###< api v1 ###
 
 ###> api v2 ###
-API_BASE_URL=https://ap2i.test6.opentalent.fr/api
-PUBLIC_API_BASE_URL=https://ap2i.test6.opentalent.fr/api
+API_BASE_URL=https://ap2i.test6.opentalent.fr
+PUBLIC_API_BASE_URL=https://ap2i.test6.opentalent.fr
 ###< api v2 ###
 
 ###> typo3 client ###

+ 4 - 4
env/.env.test7

@@ -8,13 +8,13 @@ ADMIN_BASE_URL=https://admin.test7.opentalent.fr
 ###
 
 ###> api v1 ###
-API_LEG_BASE_URL=https://api.test7.opentalent.fr/api
-PUBLIC_API_LEG_BASE_URL=https://api.test7.opentalent.fr/api
+API_LEG_BASE_URL=https://api.test7.opentalent.fr
+PUBLIC_API_LEG_BASE_URL=https://api.test7.opentalent.fr
 ###< api v1 ###
 
 ###> api v2 ###
-API_BASE_URL=https://ap2i.test7.opentalent.fr/api
-PUBLIC_API_BASE_URL=https://ap2i.test7.opentalent.fr/api
+API_BASE_URL=https://ap2i.test7.opentalent.fr
+PUBLIC_API_BASE_URL=https://ap2i.test7.opentalent.fr
 ###< api v2 ###
 
 ###> typo3 client ###

+ 4 - 4
env/.env.test8

@@ -8,13 +8,13 @@ ADMIN_BASE_URL=https://admin.test8.opentalent.fr
 ###
 
 ###> api v1 ###
-API_LEG_BASE_URL=https://api.test8.opentalent.fr/api
-PUBLIC_API_LEG_BASE_URL=https://api.test8.opentalent.fr/api
+API_LEG_BASE_URL=https://api.test8.opentalent.fr
+PUBLIC_API_LEG_BASE_URL=https://api.test8.opentalent.fr
 ###< api v1 ###
 
 ###> api v2 ###
-API_BASE_URL=https://ap2i.test8.opentalent.fr/api
-PUBLIC_API_BASE_URL=https://ap2i.test8.opentalent.fr/api
+API_BASE_URL=https://ap2i.test8.opentalent.fr
+PUBLIC_API_BASE_URL=https://ap2i.test8.opentalent.fr
 ###< api v2 ###
 
 ###> typo3 client ###

+ 4 - 4
env/.env.test9

@@ -8,13 +8,13 @@ ADMIN_BASE_URL=https://admin.test9.opentalent.fr
 ###
 
 ###> api v1 ###
-API_LEG_BASE_URL=https://api.test9.opentalent.fr/api
-PUBLIC_API_LEG_BASE_URL=https://api.test9.opentalent.fr/api
+API_LEG_BASE_URL=https://api.test9.opentalent.fr
+PUBLIC_API_LEG_BASE_URL=https://api.test9.opentalent.fr
 ###< api v1 ###
 
 ###> api v2 ###
-API_BASE_URL=https://ap2i.test9.opentalent.fr/api
-PUBLIC_API_BASE_URL=https://ap2i.test9.opentalent.fr/api
+API_BASE_URL=https://ap2i.test9.opentalent.fr
+PUBLIC_API_BASE_URL=https://ap2i.test9.opentalent.fr
 ###< api v2 ###
 
 ###> typo3 client ###

+ 0 - 1
src/Service/ApiLegacy/ApiLegacyRequestService.php

@@ -45,7 +45,6 @@ class ApiLegacyRequestService extends ApiRequestService
         $headers = [
             'Accept' => '*/*',
             'Charset' => 'UTF-8',
-            'Accept-Encoding' => 'gzip, deflate, br',
             'Content-Type' => 'application/ld+json',
         ];
 

+ 14 - 2
src/Service/Doctrine/FiltersConfigurationService.php

@@ -26,6 +26,8 @@ class FiltersConfigurationService
      */
     protected ?bool $previousTimeConstraintState = null;
 
+    protected bool $filtersConfigured = false;
+
     public function __construct(
         private EntityManagerInterface $entityManager,
         private DateTimeConstraint $dateTimeConstraint,
@@ -55,6 +57,8 @@ class FiltersConfigurationService
         $activityYearFilter = $this->getFilters()->enable('activity_year_filter');
         $activityYearFilter->setAccessId($accessId);
         $activityYearFilter->setTimeConstraint($this->activityYearConstraint);
+
+        $this->filtersConfigured = true;
     }
 
     /**
@@ -65,8 +69,12 @@ class FiltersConfigurationService
      */
     public function suspendTimeConstraintFilters(): void
     {
+        if (!$this->filtersConfigured) {
+            return;
+        }
+
         if ($this->previousTimeConstraintState !== null) {
-            throw new \RuntimeException('time constraints is already suspended');
+            throw new \RuntimeException('The time constraints are already suspended');
         }
 
         if ($this->timeFiltersAlreadyDisabled()) {
@@ -112,8 +120,12 @@ class FiltersConfigurationService
      */
     public function restoreTimeConstraintFilters(): void
     {
+        if (!$this->filtersConfigured) {
+            return;
+        }
+
         if ($this->previousTimeConstraintState === null) {
-            throw new \RuntimeException('time constraints has not been suspended, can not be restored');
+            throw new \RuntimeException('The time constraints have not been suspended, can not be restored');
         }
 
         if ($this->previousTimeConstraintState === false) {

+ 20 - 16
src/Service/Export/Encoder/PdfEncoder.php

@@ -14,18 +14,19 @@ use Dompdf\Options;
  */
 class PdfEncoder implements EncoderInterface
 {
-    protected Options $domPdfOptions;
-    protected Dompdf $dompdf;
+    public function support(string $format): bool
+    {
+        return $format === ExportFormatEnum::PDF->value;
+    }
 
-    public function __construct()
+    protected function getDomPdf(): Dompdf
     {
-        $this->domPdfOptions = new Options();
-        $this->dompdf = new Dompdf();
+        return new Dompdf();
     }
 
-    public function support(string $format): bool
+    protected function getDomPdfOptions(): Options
     {
-        return $format === ExportFormatEnum::PDF->value;
+        return new Options();
     }
 
     /**
@@ -40,16 +41,19 @@ class PdfEncoder implements EncoderInterface
      */
     public function encode(string $html, array $options = []): string
     {
-        $this->domPdfOptions->setIsRemoteEnabled(true);
-        $this->domPdfOptions->setChroot(PathUtils::getProjectDir().'/public');
-        $this->domPdfOptions->setDefaultPaperOrientation('portrait');
-        $this->domPdfOptions->setDefaultPaperSize('A4');
-        $this->domPdfOptions->set($options);
+        $domPdfOptions = $this->getDomPdfOptions();
+        $dompdf = $this->getDomPdf();
+
+        $domPdfOptions->setIsRemoteEnabled(true);
+        $domPdfOptions->setChroot(PathUtils::getProjectDir().'/public');
+        $domPdfOptions->setDefaultPaperOrientation('portrait');
+        $domPdfOptions->setDefaultPaperSize('A4');
+        $domPdfOptions->set($options);
 
-        $this->dompdf->setOptions($this->domPdfOptions);
-        $this->dompdf->loadHtml($html);
-        $this->dompdf->render();
+        $dompdf->setOptions($domPdfOptions);
+        $dompdf->loadHtml($html);
+        $dompdf->render();
 
-        return $this->dompdf->output();
+        return $dompdf->output();
     }
 }

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

@@ -44,7 +44,7 @@ class ApiLegacyStorage implements FileStorageInterface
      */
     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);
+        $url = sprintf('api/public/files/%s/download/%s?relativePath=1', $file->getId(), $size);
 
         if ($uncropped) {
             $url .= '&noCache=true';

+ 25 - 0
src/Service/Twig/AssetsExtension.php

@@ -19,9 +19,13 @@ use Twig\TwigFunction;
  */
 class AssetsExtension extends AbstractExtension
 {
+    private string $publicDir;
+
     public function __construct(
+        string $projectDir,
         private readonly FileManager $fileManager,
     ) {
+        $this->publicDir = $projectDir.'/public';
     }
 
     public function getFunctions(): array
@@ -29,6 +33,8 @@ class AssetsExtension extends AbstractExtension
         return [
             new TwigFunction('absPath', [$this, 'absPath']),
             new TwigFunction('fileImagePath', [$this, 'fileImagePath']),
+            new TwigFunction('asset_absolute', [$this, 'getAssetAbsolutePath']),
+            new TwigFunction('asset_base64', [$this, 'getAssetBase64']),
         ];
     }
 
@@ -59,4 +65,23 @@ class AssetsExtension extends AbstractExtension
     {
         return ltrim($this->fileManager->getImageUrl($file, $size, true), '/');
     }
+
+    public function getAssetAbsolutePath(string $path): string
+    {
+        return $this->publicDir.'/'.ltrim($path, '/');
+    }
+
+    public function getAssetBase64(string $path): string
+    {
+        $absolutePath = $this->getAssetAbsolutePath($path);
+
+        if (!file_exists($absolutePath)) {
+            throw new \RuntimeException("Asset not found: {$absolutePath}");
+        }
+
+        $imageData = file_get_contents($absolutePath);
+        $mimeType = mime_content_type($absolutePath);
+
+        return 'data:'.$mimeType.';base64,'.base64_encode($imageData);
+    }
 }

+ 20 - 0
src/Service/Utils/DebugUtils.php

@@ -0,0 +1,20 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Service\Utils;
+
+use Path\Path;
+
+class DebugUtils
+{
+    public static function dumpToFile(mixed $var, bool $append = true): void
+    {
+        $debugFile = (new Path(PathUtils::getProjectDir()))->append('var', 'dump.log');
+
+        $datetime = date('Y-m-d H:i:s');
+        $content = json_encode($var, JSON_PRETTY_PRINT);
+
+        $debugFile->putContent("$datetime - $content\n", $append);
+    }
+}

+ 1 - 1
src/State/Provider/Core/DownloadProvider.php

@@ -52,7 +52,7 @@ final class DownloadProvider implements ProviderInterface
             throw new \RuntimeException('File '.$fileId.' does not exist; abort.');
         }
         if ($file->getStatus() !== FileStatusEnum::READY) {
-            throw new \RuntimeException('File '.$fileId.' has '.$file->getStatus().' status; abort.');
+            throw new \RuntimeException('File '.$fileId.' has '.$file->getStatus()->value.' status; abort.');
         }
 
         $content = $this->fileManager->read($file);

+ 5 - 5
templates/export/licence_cmf.html.twig

@@ -203,7 +203,7 @@
                             <tbody>
                             <tr>
                                 <td width="250" class="relative">
-                                    <img src="{{ 'images/cmf_licence.png' }}"
+                                    <img src="{{ asset_base64('images/cmf_licence.png') }}"
                                             width="170" height="86"/>
                                     <span id="year_head">
                                         {{ licence.year }}
@@ -211,7 +211,7 @@
                                 </td>
                                 <td width="250">
                                     <div align="right">
-                                        <img src="{{ 'images/cmf-reseau.png' }}"
+                                        <img src="{{ asset_base64('images/cmf-reseau.png') }}"
                                                 width="200" height="86"/>
                                     </div>
                                 </td>
@@ -268,7 +268,7 @@
                                 <td height="62" width="62" id="avatar">
                                     <div align="center">
                                         {% if(licence.logo is null) %}
-                                            <img src="{{ 'images/picto_face.png' }}"
+                                            <img src="{{ asset_base64('images/picto_face.png') }}"
                                                  height="62"/>
                                         {% else %}
                                             <img src="{{ fileImagePath(licence.logo, 'sm') }}"
@@ -290,7 +290,7 @@
                                     <div align="center">
                                         {% if(licence.personAvatar is null) %}
                                             <img
-                                                    src="{{ 'images/picto_face.png' }}"
+                                                    src="{{ asset_base64('images/picto_face.png') }}"
                                                     height="62"/>
                                         {% else %}
                                             <img class="avatar"
@@ -314,7 +314,7 @@
                             <td height="54" width="84" valign="middle"
                                 style="vertical-align: bottom;">
                                 <div align="center">
-                                    <img src="{{ 'images/cmf_licence.png' }}"
+                                    <img src="{{ asset_base64('images/cmf_licence.png') }}"
                                          height="35"/>
                                     <span id="year_card">{{ licence.year }}</span>
                                 </div>

+ 0 - 2
tests/Unit/Service/ApiLegacy/ApiLegacyRequestServiceTest.php

@@ -65,7 +65,6 @@ class ApiLegacyRequestServiceTest extends TestCase
             'authorization' => 'BEARER XYZ',
             'Accept' => '*/*',
             'Charset' => 'UTF-8',
-            'Accept-Encoding' => 'gzip, deflate, br',
             'Content-Type' => 'application/ld+json',
             'x-accessid' => '1',
             'internal-requests-token' => self::internalRequestsToken,
@@ -120,7 +119,6 @@ class ApiLegacyRequestServiceTest extends TestCase
             'authorization' => 'BEARER 123',
             'Accept' => '*/*',
             'Charset' => 'UTF-8',
-            'Accept-Encoding' => 'gzip, deflate, br',
             'Content-Type' => 'application/ld+json',
             'x-accessid' => '20',
             'x-switch-access' => '10',

+ 42 - 3
tests/Unit/Service/Doctrine/FiltersConfigurationServiceTest.php

@@ -44,6 +44,11 @@ class TestableFiltersConfigurationService extends FiltersConfigurationService
     {
         return $this->previousTimeConstraintState;
     }
+
+    public function setFiltersConfigured(bool $value): void
+    {
+        $this->filtersConfigured = $value;
+    }
 }
 
 class FiltersConfigurationServiceTest extends TestCase
@@ -64,7 +69,7 @@ class FiltersConfigurationServiceTest extends TestCase
         return $this
             ->getMockBuilder(TestableFiltersConfigurationService::class)
             ->setConstructorArgs([$this->em, $this->dateTimeConstraint, $this->activityYearConstraint])
-            ->setMethodsExcept([$methodName, 'getPreviousTimeConstraintState', 'setPreviousTimeConstraintState'])
+            ->setMethodsExcept([$methodName, 'getPreviousTimeConstraintState', 'setPreviousTimeConstraintState', 'setFiltersConfigured'])
             ->getMock();
     }
 
@@ -117,6 +122,7 @@ class FiltersConfigurationServiceTest extends TestCase
     public function testSuspendTimeConstraintFilters(): void
     {
         $filterConfigurationService = $this->getFiltersConfigurationServiceMockFor('suspendTimeConstraintFilters');
+        $filterConfigurationService->setFiltersConfigured(true);
 
         $filterConfigurationService->expects(self::once())->method('timeFiltersAlreadyDisabled')->willReturn(false);
         $filterConfigurationService->expects(self::exactly(2))->method('disableFilter')->withConsecutive(['date_time_filter'], ['activity_year_filter']);
@@ -132,11 +138,12 @@ class FiltersConfigurationServiceTest extends TestCase
     public function testSuspendTimeConstraintFiltersAlreadySuspended(): void
     {
         $filterConfigurationService = $this->getFiltersConfigurationServiceMockFor('suspendTimeConstraintFilters');
+        $filterConfigurationService->setFiltersConfigured(true);
 
         $filterConfigurationService->setPreviousTimeConstraintState(true);
 
         $this->expectException(\RuntimeException::class);
-        $this->expectExceptionMessage('time constraints is already suspended');
+        $this->expectExceptionMessage('The time constraints are already suspended');
 
         $filterConfigurationService->suspendTimeConstraintFilters();
     }
@@ -144,6 +151,7 @@ class FiltersConfigurationServiceTest extends TestCase
     public function testSuspendTimeConstraintFiltersAlreadyDisabled(): void
     {
         $filterConfigurationService = $this->getFiltersConfigurationServiceMockFor('suspendTimeConstraintFilters');
+        $filterConfigurationService->setFiltersConfigured(true);
 
         $filterConfigurationService->expects(self::once())->method('timeFiltersAlreadyDisabled')->willReturn(true);
 
@@ -237,6 +245,7 @@ class FiltersConfigurationServiceTest extends TestCase
     public function testRestoreTimeConstraintFilters(): void
     {
         $filterConfigurationService = $this->getFiltersConfigurationServiceMockFor('restoreTimeConstraintFilters');
+        $filterConfigurationService->setFiltersConfigured(true);
 
         $filterConfigurationService->setPreviousTimeConstraintState(true);
 
@@ -253,10 +262,40 @@ class FiltersConfigurationServiceTest extends TestCase
     public function testRestoreTimeConstraintFiltersNotAlreadySuspended(): void
     {
         $filterConfigurationService = $this->getFiltersConfigurationServiceMockFor('restoreTimeConstraintFilters');
+        $filterConfigurationService->setFiltersConfigured(true);
 
         $this->expectException(\RuntimeException::class);
-        $this->expectExceptionMessage('time constraints has not been suspended, can not be restored');
+        $this->expectExceptionMessage('The time constraints have not been suspended, can not be restored');
 
         $filterConfigurationService->restoreTimeConstraintFilters();
     }
+
+    public function testSuspendTimeConstraintFiltersWhenFiltersNotConfigured(): void
+    {
+        $filterConfigurationService = $this->getFiltersConfigurationServiceMockFor('suspendTimeConstraintFilters');
+        $filterConfigurationService->setFiltersConfigured(false);
+
+        // No methods should be called when filtersConfigured is false
+        $filterConfigurationService->expects(self::never())->method('timeFiltersAlreadyDisabled');
+        $filterConfigurationService->expects(self::never())->method('disableFilter');
+
+        $filterConfigurationService->suspendTimeConstraintFilters();
+
+        // previousTimeConstraintState should remain null
+        $this->assertNull($filterConfigurationService->getPreviousTimeConstraintState());
+    }
+
+    public function testRestoreTimeConstraintFiltersWhenFiltersNotConfigured(): void
+    {
+        $filterConfigurationService = $this->getFiltersConfigurationServiceMockFor('restoreTimeConstraintFilters');
+        $filterConfigurationService->setFiltersConfigured(false);
+
+        // No methods should be called when filtersConfigured is false
+        $filterConfigurationService->expects(self::never())->method('enableFilter');
+
+        $filterConfigurationService->restoreTimeConstraintFilters();
+
+        // previousTimeConstraintState should remain null
+        $this->assertNull($filterConfigurationService->getPreviousTimeConstraintState());
+    }
 }

+ 41 - 14
tests/Unit/Service/Export/Encoder/PdfEncoderTest.php

@@ -10,47 +10,74 @@ use PHPUnit\Framework\TestCase;
 
 class TestablePdfEncoder extends PdfEncoder
 {
-    public function setDomPdfOptions(Options $options): void
+    public function getDomPdf(): Dompdf
     {
-        $this->domPdfOptions = $options;
+        return parent::getDomPdf();
     }
 
-    public function setDomPdf(Dompdf $dompdf): void
+    public function getDomPdfOptions(): Options
     {
-        $this->dompdf = $dompdf;
+        return parent::getDomPdfOptions();
     }
 }
 
 class PdfEncoderTest extends TestCase
 {
+    private function getPdfEncoderMockFor(string $method): TestablePdfEncoder
+    {
+        return $this->getMockBuilder(TestablePdfEncoder::class)
+            ->disableOriginalConstructor()
+            ->setMethodsExcept([$method])
+            ->getMock();
+    }
+
     /**
      * @see PdfEncoder::support()
      */
     public function testSupport(): void
     {
-        $encoder = $this->getMockBuilder(PdfEncoder::class)
-            ->disableOriginalConstructor()
-            ->setMethodsExcept(['support'])
-            ->getMock();
+        $encoder = $this->getPdfEncoderMockFor('support');
 
         $this->assertTrue($encoder->support('pdf'));
         $this->assertFalse($encoder->support('txt'));
     }
 
+    /**
+     * @see PdfEncoder::getDomPdf()
+     */
+    public function testGetDomPdf(): void
+    {
+        $encoder = $this->getPdfEncoderMockFor('getDomPdf');
+
+        $dompdf = $encoder->getDomPdf();
+
+        $this->assertInstanceOf(Dompdf::class, $dompdf);
+    }
+
+    /**
+     * @see PdfEncoder::getDomPdfOptions()
+     */
+    public function testGetDomPdfOptions(): void
+    {
+        $encoder = $this->getPdfEncoderMockFor('getDomPdfOptions');
+
+        $options = $encoder->getDomPdfOptions();
+
+        $this->assertInstanceOf(Options::class, $options);
+    }
+
     /**
      * @see PdfEncoder::encode()
      */
     public function testEncode(): void
     {
-        $encoder = $this->getMockBuilder(TestablePdfEncoder::class)
-            ->disableOriginalConstructor()
-            ->setMethodsExcept(['encode', 'setDomPdfOptions', 'setDomPdf'])
-            ->getMock();
+        $encoder = $this->getPdfEncoderMockFor('encode');
 
         $domPdfOptions = $this->getMockBuilder(Options::class)->disableOriginalConstructor()->getMock();
         $domPdf = $this->getMockBuilder(Dompdf::class)->disableOriginalConstructor()->getMock();
-        $encoder->setDomPdfOptions($domPdfOptions);
-        $encoder->setDomPdf($domPdf);
+
+        $encoder->expects(self::once())->method('getDomPdfOptions')->willReturn($domPdfOptions);
+        $encoder->expects(self::once())->method('getDomPdf')->willReturn($domPdf);
 
         $domPdfOptions->expects(self::once())->method('setIsRemoteEnabled')->with(true);
         $domPdfOptions->expects(self::once())->method('setChroot')->with(PathUtils::getProjectDir().'/public');

+ 2 - 2
tests/Unit/Service/File/Storage/ApiLegacyStorageTest.php

@@ -75,7 +75,7 @@ class ApiLegacyStorageTest extends TestCase
         $this->apiLegacyRequestService
             ->expects(self::once())
             ->method('getContent')
-            ->with('api/files/123/download/md?relativePath=1')
+            ->with('api/public/files/123/download/md?relativePath=1')
             ->willReturn('xyz');
 
         $this->assertEquals(
@@ -94,7 +94,7 @@ class ApiLegacyStorageTest extends TestCase
         $this->apiLegacyRequestService
             ->expects(self::once())
             ->method('getContent')
-            ->with('api/files/123/download/lg?relativePath=1')
+            ->with('api/public/files/123/download/lg?relativePath=1')
             ->willReturn('xyz');
 
         $this->assertEquals(

+ 110 - 3
tests/Unit/Service/Twig/AssetsExtensionTest.php

@@ -2,6 +2,7 @@
 
 namespace App\Tests\Unit\Service\Twig;
 
+use App\Entity\Core\File;
 use App\Service\File\FileManager;
 use App\Service\Twig\AssetsExtension;
 use App\Service\Utils\PathUtils;
@@ -10,9 +11,11 @@ use PHPUnit\Framework\TestCase;
 class AssetsExtensionTest extends TestCase
 {
     private FileManager $fileManager;
+    private string $projectDir;
 
     public function setUp(): void
     {
+        $this->projectDir = PathUtils::getProjectDir();
         $this->fileManager = $this->getMockBuilder(FileManager::class)->disableOriginalConstructor()->getMock();
     }
 
@@ -20,23 +23,25 @@ class AssetsExtensionTest extends TestCase
     {
         $assetsExtension = $this
             ->getMockBuilder(AssetsExtension::class)
-            ->setConstructorArgs([$this->fileManager])
+            ->setConstructorArgs([$this->projectDir, $this->fileManager])
             ->setMethodsExcept(['getFunctions'])
             ->getMock();
 
         $functions = $assetsExtension->getFunctions();
 
-        $this->assertCount(2, $functions);
+        $this->assertCount(4, $functions);
 
         $this->assertEquals('absPath', $functions[0]->getName());
         $this->assertEquals('fileImagePath', $functions[1]->getName());
+        $this->assertEquals('asset_absolute', $functions[2]->getName());
+        $this->assertEquals('asset_base64', $functions[3]->getName());
     }
 
     public function testAbsPath(): void
     {
         $assetsExtension = $this
             ->getMockBuilder(AssetsExtension::class)
-            ->setConstructorArgs([$this->fileManager])
+            ->setConstructorArgs([$this->projectDir, $this->fileManager])
             ->setMethodsExcept(['absPath'])
             ->getMock();
 
@@ -57,4 +62,106 @@ class AssetsExtensionTest extends TestCase
             $assetsExtension->absPath('')
         );
     }
+
+    public function testFileImagePath(): void
+    {
+        $file = $this->getMockBuilder(File::class)->disableOriginalConstructor()->getMock();
+        $size = 'sm';
+        $expectedUrl = '/path/to/image.jpg';
+
+        $this->fileManager
+            ->expects($this->once())
+            ->method('getImageUrl')
+            ->with($file, $size, true)
+            ->willReturn($expectedUrl);
+
+        $assetsExtension = new AssetsExtension($this->projectDir, $this->fileManager);
+
+        $this->assertEquals(
+            ltrim($expectedUrl, '/'),
+            $assetsExtension->fileImagePath($file, $size)
+        );
+    }
+
+    public function testGetAssetAbsolutePath(): void
+    {
+        $assetsExtension = new AssetsExtension($this->projectDir, $this->fileManager);
+        $publicDir = $this->projectDir.'/public';
+
+        // Test with path starting with /
+        $this->assertEquals(
+            $publicDir.'/images/logo.png',
+            $assetsExtension->getAssetAbsolutePath('/images/logo.png')
+        );
+
+        // Test with path not starting with /
+        $this->assertEquals(
+            $publicDir.'/images/logo.png',
+            $assetsExtension->getAssetAbsolutePath('images/logo.png')
+        );
+
+        // Test with empty path
+        $this->assertEquals(
+            $publicDir.'/',
+            $assetsExtension->getAssetAbsolutePath('')
+        );
+    }
+
+    public function testGetAssetBase64(): void
+    {
+        // Create a mock for AssetsExtension with partial methods
+        $assetsExtension = $this->getMockBuilder(AssetsExtension::class)
+            ->setConstructorArgs([$this->projectDir, $this->fileManager])
+            ->onlyMethods(['getAssetAbsolutePath'])
+            ->getMock();
+
+        // Set up the mock to return a specific path
+        $testFilePath = sys_get_temp_dir().'/test_image.png';
+        $assetsExtension
+            ->method('getAssetAbsolutePath')
+            ->willReturn($testFilePath);
+
+        // Create a test file
+        $imageContent = base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==');
+        file_put_contents($testFilePath, $imageContent);
+
+        try {
+            // Test the method
+            $result = $assetsExtension->getAssetBase64('test_image.png');
+
+            // Check the result
+            $this->assertStringStartsWith('data:image/png;base64,', $result);
+            $this->assertStringEndsWith(base64_encode($imageContent), $result);
+        } finally {
+            // Clean up
+            if (file_exists($testFilePath)) {
+                unlink($testFilePath);
+            }
+        }
+    }
+
+    public function testGetAssetBase64WithNonExistentFile(): void
+    {
+        // Create a mock for AssetsExtension with partial methods
+        $assetsExtension = $this->getMockBuilder(AssetsExtension::class)
+            ->setConstructorArgs([$this->projectDir, $this->fileManager])
+            ->onlyMethods(['getAssetAbsolutePath'])
+            ->getMock();
+
+        // Set up the mock to return a non-existent file path
+        $nonExistentFilePath = sys_get_temp_dir().'/non_existent_file.png';
+        $assetsExtension->method('getAssetAbsolutePath')
+            ->willReturn($nonExistentFilePath);
+
+        // Make sure the file doesn't exist
+        if (file_exists($nonExistentFilePath)) {
+            unlink($nonExistentFilePath);
+        }
+
+        // Test that an exception is thrown
+        $this->expectException(\RuntimeException::class);
+        $this->expectExceptionMessage("Asset not found: {$nonExistentFilePath}");
+
+        $assetsExtension->getAssetBase64('non_existent_file.png');
+    }
 }