From 8988ae7620eef8697646efb3d4816ead13e0722c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Wed, 18 Feb 2026 16:55:49 +0100 Subject: [PATCH] feat: dummy replacement pdf upload for environments other than prod --- .env | 2 ++ assets/pdf/chicken.pdf | Bin 0 -> 51500 bytes config/services.yaml | 2 ++ .../Document/BatchDownloadController.php | 2 +- src/Controller/Common/DownloadController.php | 2 +- src/EventListener/DatevEmailSubscriber.php | 2 +- src/Service/Pdf/InvoiceApprover.php | 34 +++++++++++++----- src/Service/Upload/UploadHandler.php | 23 ++++++++++++ .../DatevEmailSubscriberTest.php | 9 +++-- tests/Resources/.gitignore | 1 + tests/Service/Pdf/InvoiceApproverTest.php | 9 ++++- tests/Service/Pdf/InvoiceRendererTest.php | 8 ++++- 12 files changed, 78 insertions(+), 16 deletions(-) create mode 100644 assets/pdf/chicken.pdf create mode 100644 tests/Resources/.gitignore diff --git a/.env b/.env index 708b98e..76c46ee 100644 --- a/.env +++ b/.env @@ -73,3 +73,5 @@ DATEV_EMAIL_SENDER=honorar@ep-reisen.de FEATURE_SANITIZE_UPLOADS=false FEATURE_STAMP_INVOICES=false + +UPLOAD_REPLACEMENT_FILE=assets/pdf/chicken.pdf diff --git a/assets/pdf/chicken.pdf b/assets/pdf/chicken.pdf new file mode 100644 index 0000000000000000000000000000000000000000..09ac53096b26ea560c90dfea5dbf2ed1ed255cc0 GIT binary patch literal 51500 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 literal 0 HcmV?d00001 diff --git a/config/services.yaml b/config/services.yaml index 6c746ed..0fb7c9a 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -135,6 +135,8 @@ services: arguments: $orphanageManager: '@oneup_uploader.orphanage_manager' $projectDir: '%kernel.project_dir%' + $environment: '%kernel.environment%' + $uploadReplacementFile: '%env(default::UPLOAD_REPLACEMENT_FILE)%' App\Service\Pdf\Sanitizer: arguments: diff --git a/src/Controller/Administrative/Document/BatchDownloadController.php b/src/Controller/Administrative/Document/BatchDownloadController.php index 1f94aa8..f35437d 100644 --- a/src/Controller/Administrative/Document/BatchDownloadController.php +++ b/src/Controller/Administrative/Document/BatchDownloadController.php @@ -64,7 +64,7 @@ class BatchDownloadController extends AbstractController /** @var Upload $upload */ $teamer = $upload->getOwner()->getTeamer(); $filename = $namer->nameForTeamer($upload, $teamer); - $filepath = $this->uploadHandler->getUploadFilepath($upload); + $filepath = $this->uploadHandler->getResolvedUploadFilepath($upload); try { $zip->addFileFromPath(fileName: $filename, path: $filepath); diff --git a/src/Controller/Common/DownloadController.php b/src/Controller/Common/DownloadController.php index b835c3b..fc0e219 100644 --- a/src/Controller/Common/DownloadController.php +++ b/src/Controller/Common/DownloadController.php @@ -29,7 +29,7 @@ class DownloadController extends AbstractController #[IsGranted('DOWNLOAD', subject: 'upload')] public function index(Upload $upload, Request $request): Response { - $path = $this->uploadHandler->getUploadFilepath($upload); + $path = $this->uploadHandler->getResolvedUploadFilepath($upload); if (true === in_array($upload->getType(), [Upload::TYPE_INVOICE, Upload::TYPE_CONTRACT])) { $namer = new DownloadNamer(); diff --git a/src/EventListener/DatevEmailSubscriber.php b/src/EventListener/DatevEmailSubscriber.php index 65550b3..ae8693f 100644 --- a/src/EventListener/DatevEmailSubscriber.php +++ b/src/EventListener/DatevEmailSubscriber.php @@ -65,7 +65,7 @@ class DatevEmailSubscriber implements EventSubscriberInterface return; } - $path = $this->uploadHandler->getUploadFilepath($document); + $path = $this->uploadHandler->getResolvedUploadFilepath($document); if (false === file_exists($path)) { $this->logger->error('DATEV email skipped: attachment file not found', [ diff --git a/src/Service/Pdf/InvoiceApprover.php b/src/Service/Pdf/InvoiceApprover.php index c3fe76d..edb769d 100644 --- a/src/Service/Pdf/InvoiceApprover.php +++ b/src/Service/Pdf/InvoiceApprover.php @@ -4,10 +4,20 @@ namespace App\Service\Pdf; use App\Entity\Disposition; use App\Entity\Upload; +use App\Service\Upload\UploadHandler; use Symfony\Component\Filesystem\Filesystem; +use Symfony\Contracts\Translation\TranslatorInterface; class InvoiceApprover extends AbstractPdfRenderer { + public function __construct( + TranslatorInterface $translator, + array $options, + private readonly UploadHandler $uploadHandler, + ) { + parent::__construct($translator, $options); + } + /** * @throws \InvalidArgumentException */ @@ -16,24 +26,23 @@ class InvoiceApprover extends AbstractPdfRenderer $teamer = $disposition->getTeamer(); $assignment = $disposition->getAssignment(); $invoice = $disposition->getDocumentByType(Upload::TYPE_INVOICE); - $originalFilename = $invoice->getFilename(); if ('application/pdf' !== $invoice->getMimeType()) { throw new \InvalidArgumentException('Expected PDF but got '.$invoice->getMimeType()); } - // Load template - $folder = substr($originalFilename, 0, 1); - $filepath = sprintf('%s/uploads/invoice/%s/%s', $this->config['project_dir'], $folder, $originalFilename); - if (false === file_exists($filepath)) { - throw new \InvalidArgumentException('Uploaded document not found at '.$filepath); + $sourcePath = $this->uploadHandler->getResolvedUploadFilepath($invoice); + $targetPath = $this->uploadHandler->getUploadFilepath($invoice); + + if (false === file_exists($sourcePath)) { + throw new \InvalidArgumentException('Uploaded document not found at '.$sourcePath); } $pdf = new Pdf(); $pdf->SetAuthor($teamer->getFullName(), true); $pdf->SetCreator($teamer->getFullName(), true); - $pdf->appendPdf($filepath); + $pdf->appendPdf($sourcePath); $pdf->setPage(1); // Cost unit job profile @@ -53,9 +62,16 @@ class InvoiceApprover extends AbstractPdfRenderer $pdf->SetTextColor(0, 128, 0); $pdf->Text(20, 30, $pdf->encodeString('geprüft: '.$invoice->getApprovedBy())); - $this->createBackup($filepath); + $filesystem = new Filesystem(); + $targetDir = \dirname($targetPath); - $pdf->Output('F', $filepath); + if (false === $filesystem->exists($targetDir)) { + $filesystem->mkdir($targetDir); + } + + $this->createBackup($targetPath); + + $pdf->Output('F', $targetPath); return $pdf; } diff --git a/src/Service/Upload/UploadHandler.php b/src/Service/Upload/UploadHandler.php index 5c95904..eca0bb2 100644 --- a/src/Service/Upload/UploadHandler.php +++ b/src/Service/Upload/UploadHandler.php @@ -22,6 +22,8 @@ class UploadHandler private readonly Sanitizer $sanitizer, private readonly LoggerInterface $logger, private readonly string $projectDir, + private readonly string $environment, + private readonly string $uploadReplacementFile, ) { } @@ -143,6 +145,27 @@ class UploadHandler return true === $absolute ? sprintf('%s/%s', $this->projectDir, $relativePath) : $relativePath; } + /** + * Returns the upload file path, substituting a replacement file in non-production environments + * when configured. Use this for read-only access (downloads, email attachments). + */ + public function getResolvedUploadFilepath(Upload $upload): string + { + $originalPath = $this->getUploadFilepath($upload); + + if ('' === $this->uploadReplacementFile || 'prod' === $this->environment) { + return $originalPath; + } + + $replacementPath = sprintf('%s/%s', $this->projectDir, $this->uploadReplacementFile); + + if (false === file_exists($replacementPath)) { + return $originalPath; + } + + return $replacementPath; + } + public function getTempUploadFilepath(UploadDto $uploadDto, string $uploadSessionId): string { $tempUploadDir = $this->getTempUploadDir($uploadSessionId); diff --git a/tests/EventListener/DatevEmailSubscriberTest.php b/tests/EventListener/DatevEmailSubscriberTest.php index 3fb6bd5..da53a72 100644 --- a/tests/EventListener/DatevEmailSubscriberTest.php +++ b/tests/EventListener/DatevEmailSubscriberTest.php @@ -12,6 +12,7 @@ use App\Event\DocumentConfirmedEvent; use App\EventListener\DatevEmailSubscriber; use App\Model\EmailPathAttachmentDto; use App\Service\Upload\UploadHandler; +use Flagception\Manager\FeatureManagerInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; @@ -21,6 +22,7 @@ class DatevEmailSubscriberTest extends TestCase private Mailer&MockObject $mailer; private UploadHandler&MockObject $uploadHandler; private LoggerInterface&MockObject $logger; + private FeatureManagerInterface&MockObject $featureManager; private DatevEmailSubscriber $subscriber; protected function setUp(): void @@ -28,6 +30,8 @@ class DatevEmailSubscriberTest extends TestCase $this->mailer = $this->createMock(Mailer::class); $this->uploadHandler = $this->createMock(UploadHandler::class); $this->logger = $this->createMock(LoggerInterface::class); + $this->featureManager = $this->createMock(FeatureManagerInterface::class); + $this->featureManager->method('isActive')->with('datev_email')->willReturn(true); $this->subscriber = new DatevEmailSubscriber( $this->mailer, @@ -35,6 +39,7 @@ class DatevEmailSubscriberTest extends TestCase $this->logger, 'datev@example.com', 'sender@example.com', + $this->featureManager, ); } @@ -118,7 +123,7 @@ class DatevEmailSubscriberTest extends TestCase $nonExistentPath = '/tmp/non_existent_file_'.uniqid().'.pdf'; $this->uploadHandler - ->method('getUploadFilepath') + ->method('getResolvedUploadFilepath') ->with($document) ->willReturn($nonExistentPath); @@ -160,7 +165,7 @@ class DatevEmailSubscriberTest extends TestCase try { $this->uploadHandler ->expects($this->once()) - ->method('getUploadFilepath') + ->method('getResolvedUploadFilepath') ->with($document) ->willReturn($tmpFile); diff --git a/tests/Resources/.gitignore b/tests/Resources/.gitignore new file mode 100644 index 0000000..8b8a8c4 --- /dev/null +++ b/tests/Resources/.gitignore @@ -0,0 +1 @@ +upload.orig.pdf diff --git a/tests/Service/Pdf/InvoiceApproverTest.php b/tests/Service/Pdf/InvoiceApproverTest.php index f0f1d74..5d16be8 100644 --- a/tests/Service/Pdf/InvoiceApproverTest.php +++ b/tests/Service/Pdf/InvoiceApproverTest.php @@ -14,6 +14,8 @@ use App\Entity\Upload; use App\Service\Pdf\InvoiceApprover; use App\Service\Pdf\InvoiceRenderer; use App\Service\Pdf\Pdf; +use App\Service\Upload\UploadHandler; +use PHPUnit\Framework\MockObject\MockObject; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\Filesystem\Filesystem; use Symfony\Contracts\Translation\TranslatorInterface; @@ -25,6 +27,7 @@ class InvoiceApproverTest extends WebTestCase private string $pdfPath; private string $origPath; private Filesystem $filesystem; + private UploadHandler&MockObject $uploadHandler; protected function setUp(): void { @@ -35,6 +38,7 @@ class InvoiceApproverTest extends WebTestCase $this->pdfPath = $this->invoiceDir.'/test_approver_backup.pdf'; $this->origPath = $this->invoiceDir.'/test_approver_backup.orig.pdf'; $this->filesystem = new Filesystem(); + $this->uploadHandler = $this->createMock(UploadHandler::class); $this->filesystem->mkdir($this->invoiceDir); } @@ -80,7 +84,7 @@ class InvoiceApproverTest extends WebTestCase $translator = $this->createMock(TranslatorInterface::class); /** @var TranslatorInterface $translator */ - return new InvoiceApprover($translator, ['project_dir' => $this->projectDir]); + return new InvoiceApprover($translator, ['project_dir' => $this->projectDir], $this->uploadHandler); } private function createDispositionWithInvoicePdf(): Disposition @@ -130,6 +134,9 @@ class InvoiceApproverTest extends WebTestCase ; $disposition->addDocument($invoice); + $this->uploadHandler->method('getResolvedUploadFilepath')->with($invoice)->willReturn($this->pdfPath); + $this->uploadHandler->method('getUploadFilepath')->with($invoice)->willReturn($this->pdfPath); + return $disposition; } } diff --git a/tests/Service/Pdf/InvoiceRendererTest.php b/tests/Service/Pdf/InvoiceRendererTest.php index 0f79c6d..4608e5f 100644 --- a/tests/Service/Pdf/InvoiceRendererTest.php +++ b/tests/Service/Pdf/InvoiceRendererTest.php @@ -16,6 +16,7 @@ use App\Entity\User; use App\Service\Pdf\InvoiceApprover; use App\Service\Pdf\InvoiceRenderer; use App\Service\Pdf\Pdf; +use App\Service\Upload\UploadHandler; use Carbon\CarbonPeriod; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Contracts\Translation\TranslatorInterface; @@ -108,8 +109,13 @@ class InvoiceRendererTest extends WebTestCase ; $disposition->addDocument($invoice); + $invoicePath = $projectDir.'/uploads/invoice/a/abcdef0123456789.pdf'; + $uploadHandler = $this->createMock(UploadHandler::class); + $uploadHandler->method('getResolvedUploadFilepath')->with($invoice)->willReturn($invoicePath); + $uploadHandler->method('getUploadFilepath')->with($invoice)->willReturn($invoicePath); + /** @var TranslatorInterface $translator */ - $approver = new InvoiceApprover($translator, ['project_dir' => $projectDir]); + $approver = new InvoiceApprover($translator, ['project_dir' => $projectDir], $uploadHandler); $pdf = $approver->render($disposition); $this->assertInstanceOf(Pdf::class, $pdf); }