From 70e477073bc4677c8dae9425cf4776ac9f6eadbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Tue, 8 Jul 2025 15:06:44 +0200 Subject: [PATCH] chore: remove deprecations --- deploy.php | 1 + src/Service/Pdf/ContractRenderer.php | 10 +- src/Service/Pdf/InvoiceApprover.php | 6 +- src/Service/Pdf/InvoiceRenderer.php | 16 +-- src/Service/Pdf/Pdf.php | 107 +++++++++++++++++++-- tests/Service/Pdf/ContractRendererTest.php | 3 +- tests/Service/Pdf/InvoiceRendererTest.php | 6 +- 7 files changed, 123 insertions(+), 26 deletions(-) diff --git a/deploy.php b/deploy.php index 5419cc6..ad7e1c9 100644 --- a/deploy.php +++ b/deploy.php @@ -20,6 +20,7 @@ add('shared_files', [ // Rsync options, mainly files/dirs to exclude $rsyncOptions = [ 'exclude' => [ + '.cursor', '.idea', '.vscode', '.DS_Store', diff --git a/src/Service/Pdf/ContractRenderer.php b/src/Service/Pdf/ContractRenderer.php index b092bbe..121dc5e 100644 --- a/src/Service/Pdf/ContractRenderer.php +++ b/src/Service/Pdf/ContractRenderer.php @@ -27,16 +27,16 @@ class ContractRenderer extends AbstractPdfRenderer // Current date $date = (new \DateTimeImmutable())->format('d.m.Y'); - $pdf->Text(164,43, utf8_decode($date)); + $pdf->Text(164,43, $pdf->encodeString($date)); // Name - $pdf->Text(45,67.75, utf8_decode($teamer)); + $pdf->Text(45,67.75, $pdf->encodeString($teamer)); // Address - $pdf->Text(45,77.75, utf8_decode($teamer->getAddress())); + $pdf->Text(45,77.75, $pdf->encodeString($teamer->getAddress())); // Jobprofile - $pdf->Text(21,104.5, utf8_decode($assignment->getJobProfile()->getName())); + $pdf->Text(21,104.5, $pdf->encodeString($assignment->getJobProfile()->getName())); // Period $dateFrom = $assignment->getEffectivePeriod()->start->format('d.m.Y'); @@ -45,7 +45,7 @@ class ContractRenderer extends AbstractPdfRenderer $pdf->Text(57,113, $period); // Destination - $pdf->Text(57,121.5, utf8_decode(sprintf('%s, %s', $destination->getProduct(), $destination->getHotel()))); + $pdf->Text(57,121.5, $pdf->encodeString(sprintf('%s, %s', $destination->getProduct(), $destination->getHotel()))); // Set pointer to last page to include all $pdf->setPage($numberOfPages); diff --git a/src/Service/Pdf/InvoiceApprover.php b/src/Service/Pdf/InvoiceApprover.php index 1e1fbed..ea93d8a 100644 --- a/src/Service/Pdf/InvoiceApprover.php +++ b/src/Service/Pdf/InvoiceApprover.php @@ -39,19 +39,19 @@ class InvoiceApprover extends AbstractPdfRenderer // Cost unit job profile if (null !== $costUnit = $assignment->getJobProfile()->getCostUnit()) { $pdf->SetFont('Arial', '', 11); - $pdf->Text(20,20, utf8_decode('Kostenstelle 1: '.$costUnit)); + $pdf->Text(20,20, $pdf->encodeString('Kostenstelle 1: '.$costUnit)); } // Cost unit destination/hotel if (null !== $costUnit = $assignment->getDestination()->getCostUnit()) { $pdf->SetFont('Arial', '', 11); - $pdf->Text(20,25, utf8_decode('Kostenstelle 2: '.$costUnit)); + $pdf->Text(20,25, $pdf->encodeString('Kostenstelle 2: '.$costUnit)); } // Approval $pdf->SetFont('Arial', '', 11); $pdf->SetTextColor(0, 128, 0); - $pdf->Text(20,30, utf8_decode('geprüft: '.$invoice->getApprovedBy())); + $pdf->Text(20,30, $pdf->encodeString('geprüft: '.$invoice->getApprovedBy())); $this->createBackup($filepath); diff --git a/src/Service/Pdf/InvoiceRenderer.php b/src/Service/Pdf/InvoiceRenderer.php index 9774061..5379252 100644 --- a/src/Service/Pdf/InvoiceRenderer.php +++ b/src/Service/Pdf/InvoiceRenderer.php @@ -26,15 +26,15 @@ class InvoiceRenderer extends AbstractPdfRenderer $assignment = $disposition->getAssignment(); // Name - $pdf->Text(135,28.5, utf8_decode($teamer->getFullName())); + $pdf->Text(135,28.5, $pdf->encodeString($teamer->getFullName())); // Address $address = $teamer->getAddress(); - $pdf->Text(135,39, utf8_decode($address->getStreet())); - $pdf->Text(135,51, utf8_decode($address->getPostCode(). ' '.$address->getCity())); + $pdf->Text(135,39, $pdf->encodeString($address->getStreet())); + $pdf->Text(135,51, $pdf->encodeString($address->getPostCode(). ' '.$address->getCity())); // Tax ID - $pdf->Text(144,62, utf8_decode($teamer->getTaxId())); + $pdf->Text(144,62, $pdf->encodeString($teamer->getTaxId())); // Invoice number $pdf->SetFont('Arial', '', 12); @@ -45,14 +45,14 @@ class InvoiceRenderer extends AbstractPdfRenderer // Bank account $bankAccount = $teamer->getBankAccount(); - $pdf->Text(36,233.5, utf8_decode($bankAccount->getIban())); - $pdf->Text(36,241, utf8_decode($bankAccount->getBic())); - $pdf->Text(36,248.5, utf8_decode($bankAccount->getBank())); + $pdf->Text(36,233.5, $pdf->encodeString($bankAccount->getIban())); + $pdf->Text(36,241, $pdf->encodeString($bankAccount->getBic())); + $pdf->Text(36,248.5, $pdf->encodeString($bankAccount->getBank())); // Current date $date = (new \DateTimeImmutable())->format('d.m.Y'); - $pdf->Text(38,264, utf8_decode($date)); + $pdf->Text(38,264, $pdf->encodeString($date)); return $pdf; } diff --git a/src/Service/Pdf/Pdf.php b/src/Service/Pdf/Pdf.php index 45eff45..186a574 100644 --- a/src/Service/Pdf/Pdf.php +++ b/src/Service/Pdf/Pdf.php @@ -7,13 +7,18 @@ use setasign\Fpdi\PdfParser\PdfParserException; use setasign\Fpdi\PdfReader\PdfReaderException; /** - * Functionality to center image on PDF page adapted from - * https://gist.github.com/benshimmin/4088493?permalink_comment_id=2398423#gistcomment-2398423. + * Extended PDF manipulation class providing image centering and PDF merging capabilities. * - * Functionality to append pages from external PDF file adapted from - * https://stackoverflow.com/questions/22404601/merging-pdf-files-with-php-fpdi/29552294#29552294 + * This class extends FPDI to provide enhanced PDF functionality including automatic + * image centering on pages, PDF file merging, and proper UTF-8 string encoding for + * FPDF compatibility. It handles image resizing to fit within specified containers + * while maintaining aspect ratios and provides error handling for PDF parsing issues. * - * Requires commercial FPDI PDF-Parser addon to parse compressed PDF files. + * The class implements functionality adapted from external sources for image centering + * and PDF merging, with additional error handling and UTF-8 support for German text. + * + * @see https://gist.github.com/benshimmin/4088493?permalink_comment_id=2398423#gistcomment-2398423 Image centering functionality + * @see https://stackoverflow.com/questions/22404601/merging-pdf-files-with-php-fpdi/29552294#29552294 PDF merging functionality */ class Pdf extends Fpdi { @@ -24,11 +29,33 @@ class Pdf extends Fpdi public const MARGIN_X = 10; public const MARGIN_Y = 10; + /** + * Sets the current page number for PDF operations. + * + * Allows manual control of the current page position within the PDF document. + * This is useful for positioning operations or when working with specific pages. + * + * @param int $pageNumber The page number to set as current + */ public function setPage(int $pageNumber): void { $this->page = $pageNumber; } + /** + * Appends all pages from an external PDF file to the current document. + * + * Imports and merges all pages from a source PDF file into the current document. + * Each page is imported with its original dimensions and orientation. If parsing + * fails, a fallback page with error information is added instead. + * + * @param string $sourceFile Path to the source PDF file to append + * + * @return int The number of pages successfully appended, or 1 if an error occurred + * + * @throws PdfParserException When the source PDF cannot be parsed + * @throws PdfReaderException When the source PDF cannot be read + */ public function appendPdf(string $sourceFile): int { try { @@ -50,6 +77,16 @@ class Pdf extends Fpdi } } + /** + * Adds a new page with a centered image and optional title. + * + * Creates a new page and places an image in the center with proper margins. + * The image is automatically resized to fit within the available space while + * maintaining its aspect ratio. An optional title can be added at the top. + * + * @param string $sourceFile Path to the image file to display + * @param string $pageTitle Optional title text to display at the top of the page + */ public function appendImage(string $sourceFile, string $pageTitle = ''): void { $this->AddPage(); @@ -65,10 +102,23 @@ class Pdf extends Fpdi if ($pageTitle) { $this->SetFont('Arial', '', 10); - $this->Text(self::MARGIN_X, self::MARGIN_Y, utf8_decode($pageTitle)); + $this->Text(self::MARGIN_X, self::MARGIN_Y, $this->encodeString($pageTitle)); } } + /** + * Centers an image within specified container dimensions. + * + * Calculates the optimal size for an image to fit within the given container + * while maintaining aspect ratio, then positions it in the center. Handles + * image loading errors gracefully by displaying an error message. + * + * @param string $imgPath Path to the image file + * @param int $x X-coordinate of the container's top-left corner + * @param int $y Y-coordinate of the container's top-left corner + * @param int $containerWidth Width of the container area + * @param int $containerHeight Height of the container area + */ public function centerImage( string $imgPath, int $x = 0, @@ -92,16 +142,43 @@ class Pdf extends Fpdi } } + /** + * Converts pixel values to millimeters based on DPI setting. + * + * @param mixed $val Pixel value to convert + * + * @return int Millimeter value rounded to nearest integer + */ protected function pixelsToMm($val): int { return (int) (round($val * $this::MM_IN_INCH / self::DPI)); } + /** + * Converts millimeter values to pixels based on DPI setting. + * + * @param mixed $val Millimeter value to convert + * + * @return int Pixel value rounded to nearest integer + */ protected function mmToPixels($val): int { - return (int) (round($this::DPI * $val / self::MM_IN_INCH)); + return (int) (round($this::DPI * $val / $this::MM_IN_INCH)); } + /** + * Calculates optimal image dimensions to fit within specified maximum bounds. + * + * Analyzes the original image dimensions and calculates the largest possible + * size that fits within the maximum width and height while preserving the + * aspect ratio. Returns dimensions in millimeters for PDF placement. + * + * @param string $imgPath Path to the image file + * @param int $maxWidth Maximum allowed width in millimeters + * @param int $maxHeight Maximum allowed height in millimeters + * + * @return array Array containing [width, height] in millimeters + */ protected function resizeToFit( string $imgPath, int $maxWidth = self::A4_WIDTH, @@ -117,4 +194,20 @@ class Pdf extends Fpdi $this->pixelsToMm($scale * $height), ]; } + + /** + * Converts UTF-8 string to ISO-8859-1 for FPDF compatibility. + * + * FPDF has limited Unicode support and works best with ISO-8859-1 encoding. + * This method ensures proper character encoding for German umlauts and other + * special characters when adding text to PDF documents. + * + * @param string $string UTF-8 encoded string to convert + * + * @return string ISO-8859-1 encoded string suitable for FPDF + */ + public function encodeString(string $string): string + { + return mb_convert_encoding($string, 'ISO-8859-1', 'UTF-8'); + } } diff --git a/tests/Service/Pdf/ContractRendererTest.php b/tests/Service/Pdf/ContractRendererTest.php index f166a47..4e41eaa 100644 --- a/tests/Service/Pdf/ContractRendererTest.php +++ b/tests/Service/Pdf/ContractRendererTest.php @@ -21,7 +21,8 @@ class ContractRendererTest extends WebTestCase public function testRendersContract(): void { $projectDir = static::getContainer()->getParameter('kernel.project_dir'); - $translator = $this->createMock(TranslatorInterface::class); + /** @var TranslatorInterface $translator */ + $translator = static::getContainer()->get(TranslatorInterface::class); $renderer = new ContractRenderer($translator, ['project_dir' => $projectDir]); $address = new Address(); diff --git a/tests/Service/Pdf/InvoiceRendererTest.php b/tests/Service/Pdf/InvoiceRendererTest.php index 86572f1..66269f1 100644 --- a/tests/Service/Pdf/InvoiceRendererTest.php +++ b/tests/Service/Pdf/InvoiceRendererTest.php @@ -25,6 +25,7 @@ class InvoiceRendererTest extends WebTestCase { $projectDir = static::getContainer()->getParameter('kernel.project_dir'); $translator = $this->createMock(TranslatorInterface::class); + /** @var TranslatorInterface $translator */ $renderer = new InvoiceRenderer($translator, ['project_dir' => $projectDir]); $address = new Address(); @@ -93,19 +94,20 @@ class InvoiceRendererTest extends WebTestCase $pdf = $renderer->render($disposition); $this->assertInstanceOf(Pdf::class, $pdf); - $pdf->Output('F', $projectDir.'/uploads/invoice/Honorarnote.pdf'); + $pdf->Output('F', $projectDir.'/uploads/invoice/a/abcdef0123456789.pdf'); $invoice = new Upload(); $invoice ->setType(Upload::TYPE_INVOICE) ->setMimeType('application/pdf') - ->setFilename('Honorarnote.pdf') + ->setFilename('abcdef0123456789.pdf') ->setApprovedBy('FOO') ->setApprovedAt(new \DateTimeImmutable()) ->setStatus(Upload::STATUS_PAID) ; $disposition->addDocument($invoice); + /** @var TranslatorInterface $translator */ $approver = new InvoiceApprover($translator, ['project_dir' => $projectDir]); $pdf = $approver->render($disposition); $this->assertInstanceOf(Pdf::class, $pdf);