From adad3afbdceb9b096b68816e291daa77e9e95b94 Mon Sep 17 00:00:00 2001 From: Matthias Devlamynck Date: Tue, 30 Jan 2024 10:30:35 +0100 Subject: [PATCH] Switch from fpdi/fpdf to pdftk to be able to use more recent/secure pdf protection algorithm --- composer.json | 2 +- composer.lock | 314 +++++++++----------- src/Domain/Pdf/Trait/PdfMergerTrait.php | 33 +- src/Domain/Pdf/Trait/PdfProtectionTrait.php | 41 ++- src/Domain/Pdf/Trait/PdfTempFileTrait.php | 41 +++ 5 files changed, 226 insertions(+), 205 deletions(-) create mode 100644 src/Domain/Pdf/Trait/PdfTempFileTrait.php diff --git a/composer.json b/composer.json index d785e32..5a24490 100644 --- a/composer.json +++ b/composer.json @@ -21,9 +21,9 @@ "doctrine/migrations": "^3.0", "doctrine/orm": "^2.7", "knplabs/knp-gaufrette-bundle": "^0.7.2", + "mikehaertl/php-pdftk": "^0.13.1", "richcongress/bundle-toolbox": "^1.1", "sensio/framework-extra-bundle": "^5.4|^6.0", - "setasign/fpdi-protection": "^2.0", "symfony/translation": "^5.4", "symfony/twig-bundle": "^5.4" }, diff --git a/composer.lock b/composer.lock index e737dc3..5ea71ef 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9027f2f2bfa384deb208d8985aad11f5", + "content-hash": "0a52db3bd55996528b07597d09173b94", "packages": [ { "name": "chrome-php/chrome", @@ -1589,6 +1589,145 @@ ], "time": "2022-07-28T22:46:52+00:00" }, + { + "name": "mikehaertl/php-pdftk", + "version": "0.13.1", + "source": { + "type": "git", + "url": "https://github.com/mikehaertl/php-pdftk.git", + "reference": "3851b08c1027489e48387d7c14c27bc295d98239" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mikehaertl/php-pdftk/zipball/3851b08c1027489e48387d7c14c27bc295d98239", + "reference": "3851b08c1027489e48387d7c14c27bc295d98239", + "shasum": "" + }, + "require": { + "mikehaertl/php-shellcommand": "^1.6.3", + "mikehaertl/php-tmpfile": "^1.1.0", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": ">4.0 <9.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "mikehaertl\\pdftk\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Haertl", + "email": "haertl.mike@gmail.com" + } + ], + "description": "A PDF conversion and form utility based on pdftk.", + "keywords": [ + "pdf", + "pdftk" + ], + "support": { + "issues": "https://github.com/mikehaertl/php-pdftk/issues", + "source": "https://github.com/mikehaertl/php-pdftk/tree/0.13.1" + }, + "time": "2023-11-03T16:06:08+00:00" + }, + { + "name": "mikehaertl/php-shellcommand", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/mikehaertl/php-shellcommand.git", + "reference": "e79ea528be155ffdec6f3bf1a4a46307bb49e545" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mikehaertl/php-shellcommand/zipball/e79ea528be155ffdec6f3bf1a4a46307bb49e545", + "reference": "e79ea528be155ffdec6f3bf1a4a46307bb49e545", + "shasum": "" + }, + "require": { + "php": ">= 5.3.0" + }, + "require-dev": { + "phpunit/phpunit": ">4.0 <=9.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "mikehaertl\\shellcommand\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Härtl", + "email": "haertl.mike@gmail.com" + } + ], + "description": "An object oriented interface to shell commands", + "keywords": [ + "shell" + ], + "support": { + "issues": "https://github.com/mikehaertl/php-shellcommand/issues", + "source": "https://github.com/mikehaertl/php-shellcommand/tree/1.7.0" + }, + "time": "2023-04-19T08:25:22+00:00" + }, + { + "name": "mikehaertl/php-tmpfile", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/mikehaertl/php-tmpfile.git", + "reference": "70a5b70b17bc0d9666388e6a551ecc93d0b40a10" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mikehaertl/php-tmpfile/zipball/70a5b70b17bc0d9666388e6a551ecc93d0b40a10", + "reference": "70a5b70b17bc0d9666388e6a551ecc93d0b40a10", + "shasum": "" + }, + "require-dev": { + "php": ">=5.3.0", + "phpunit/phpunit": ">4.0 <=9.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "mikehaertl\\tmp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Härtl", + "email": "haertl.mike@gmail.com" + } + ], + "description": "A convenience class for temporary files", + "keywords": [ + "files" + ], + "support": { + "issues": "https://github.com/mikehaertl/php-tmpfile/issues", + "source": "https://github.com/mikehaertl/php-tmpfile/tree/1.2.1" + }, + "time": "2021-03-01T18:26:25+00:00" + }, { "name": "monolog/monolog", "version": "2.8.0", @@ -2015,179 +2154,6 @@ }, "time": "2022-09-05T16:44:56+00:00" }, - { - "name": "setasign/fpdf", - "version": "1.8.4", - "source": { - "type": "git", - "url": "https://github.com/Setasign/FPDF.git", - "reference": "b0ddd9c5b98ced8230ef38534f6f3c17308a7974" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Setasign/FPDF/zipball/b0ddd9c5b98ced8230ef38534f6f3c17308a7974", - "reference": "b0ddd9c5b98ced8230ef38534f6f3c17308a7974", - "shasum": "" - }, - "require": { - "ext-gd": "*", - "ext-zlib": "*" - }, - "type": "library", - "autoload": { - "classmap": [ - "fpdf.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Olivier Plathey", - "email": "oliver@fpdf.org", - "homepage": "http://fpdf.org/" - } - ], - "description": "FPDF is a PHP class which allows to generate PDF files with pure PHP. F from FPDF stands for Free: you may use it for any kind of usage and modify it to suit your needs.", - "homepage": "http://www.fpdf.org", - "keywords": [ - "fpdf", - "pdf" - ], - "support": { - "source": "https://github.com/Setasign/FPDF/tree/1.8.4" - }, - "time": "2021-08-30T07:50:06+00:00" - }, - { - "name": "setasign/fpdi", - "version": "v2.3.6", - "source": { - "type": "git", - "url": "https://github.com/Setasign/FPDI.git", - "reference": "6231e315f73e4f62d72b73f3d6d78ff0eed93c31" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Setasign/FPDI/zipball/6231e315f73e4f62d72b73f3d6d78ff0eed93c31", - "reference": "6231e315f73e4f62d72b73f3d6d78ff0eed93c31", - "shasum": "" - }, - "require": { - "ext-zlib": "*", - "php": "^5.6 || ^7.0 || ^8.0" - }, - "conflict": { - "setasign/tfpdf": "<1.31" - }, - "require-dev": { - "phpunit/phpunit": "~5.7", - "setasign/fpdf": "~1.8", - "setasign/tfpdf": "1.31", - "squizlabs/php_codesniffer": "^3.5", - "tecnickcom/tcpdf": "~6.2" - }, - "suggest": { - "setasign/fpdf": "FPDI will extend this class but as it is also possible to use TCPDF or tFPDF as an alternative. There's no fixed dependency configured." - }, - "type": "library", - "autoload": { - "psr-4": { - "setasign\\Fpdi\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jan Slabon", - "email": "jan.slabon@setasign.com", - "homepage": "https://www.setasign.com" - }, - { - "name": "Maximilian Kresse", - "email": "maximilian.kresse@setasign.com", - "homepage": "https://www.setasign.com" - } - ], - "description": "FPDI is a collection of PHP classes facilitating developers to read pages from existing PDF documents and use them as templates in FPDF. Because it is also possible to use FPDI with TCPDF, there are no fixed dependencies defined. Please see suggestions for packages which evaluates the dependencies automatically.", - "homepage": "https://www.setasign.com/fpdi", - "keywords": [ - "fpdf", - "fpdi", - "pdf" - ], - "support": { - "issues": "https://github.com/Setasign/FPDI/issues", - "source": "https://github.com/Setasign/FPDI/tree/v2.3.6" - }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/setasign/fpdi", - "type": "tidelift" - } - ], - "time": "2021-02-11T11:37:01+00:00" - }, - { - "name": "setasign/fpdi-protection", - "version": "v2.0.2", - "source": { - "type": "git", - "url": "https://github.com/Setasign/fpdi-protection.git", - "reference": "c619d22b4a71d0f198e7809cd78d6a61f5bcb1bd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Setasign/fpdi-protection/zipball/c619d22b4a71d0f198e7809cd78d6a61f5bcb1bd", - "reference": "c619d22b4a71d0f198e7809cd78d6a61f5bcb1bd", - "shasum": "" - }, - "require": { - "ext-openssl": "*", - "php": "^5.6 || ^7.0 || ^8.0", - "setasign/fpdf": "^1.8.4", - "setasign/fpdi": "^2.0" - }, - "require-dev": { - "phpunit/phpunit": "~5.7" - }, - "type": "library", - "autoload": { - "psr-4": { - "setasign\\FpdiProtection\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jan Slabon", - "email": "jan.slabon@setasign.com", - "homepage": "https://www.setasign.com" - } - ], - "description": "A FPDI compatible version of the FPDF_Protection script.", - "homepage": "https://github.com/Setasign/fpdi-protection", - "keywords": [ - "fpdf", - "fpdi", - "password", - "pdf", - "protection" - ], - "support": { - "issues": "https://github.com/Setasign/fpdi-protection/issues", - "source": "https://github.com/Setasign/fpdi-protection/tree/v2.0.2" - }, - "time": "2021-08-30T09:05:15+00:00" - }, { "name": "symfony/cache", "version": "v6.0.11", diff --git a/src/Domain/Pdf/Trait/PdfMergerTrait.php b/src/Domain/Pdf/Trait/PdfMergerTrait.php index 9fb7691..1b1e0d6 100644 --- a/src/Domain/Pdf/Trait/PdfMergerTrait.php +++ b/src/Domain/Pdf/Trait/PdfMergerTrait.php @@ -4,31 +4,30 @@ namespace RichId\PdfTemplateBundle\Domain\Pdf\Trait; -use setasign\Fpdi\Fpdi; -use setasign\Fpdi\PdfParser\StreamReader; +use mikehaertl\pdftk\Pdf; trait PdfMergerTrait { - /** @param string[] $pdfs */ - private function mergePdfs(array $pdfs): string - { - $encoder = new Fpdi(); + use PdfTempFileTrait; - foreach ($pdfs as $pdf) { - $pageCount = $encoder->setSourceFile(StreamReader::createByString($pdf)); + /** @param string[] $sources */ + private function mergePdfs(array $sources): string + { + return $this->withTempDir(function (string $tempDir) use ($sources) { + $pdf = new Pdf(); + $pdf->ignoreWarnings = true; - for ($i = 1; $i <= $pageCount; $i++) { - $tplidx = $encoder->importPage($i); - $specs = $encoder->getTemplateSize($tplidx); + foreach ($sources as $index => $source) { + $pdf->addFile($this->copySource($source, $tempDir, $index + 1)); + } - if (\is_array($specs)) { - $encoder->addPage($specs['orientation'], [$specs['width'], $specs['height']]); - } + $result = $pdf->toString(); - $encoder->useTemplate($tplidx); + if (\is_bool($result)) { + throw new \Exception('Failed to generate pdf file'); } - } - return $encoder->Output('S'); + return $result; + }); } } diff --git a/src/Domain/Pdf/Trait/PdfProtectionTrait.php b/src/Domain/Pdf/Trait/PdfProtectionTrait.php index 2740e22..99a4d96 100644 --- a/src/Domain/Pdf/Trait/PdfProtectionTrait.php +++ b/src/Domain/Pdf/Trait/PdfProtectionTrait.php @@ -4,28 +4,43 @@ namespace RichId\PdfTemplateBundle\Domain\Pdf\Trait; -use setasign\Fpdi\PdfParser\StreamReader; -use setasign\FpdiProtection\FpdiProtection; +use mikehaertl\pdftk\Pdf; trait PdfProtectionTrait { - private function internalProtectPdf(string $pdf): string + use PdfTempFileTrait; + + private function internalProtectPdf(string $source): string { - $encoder = new FpdiProtection(); - $pageCount = $encoder->setSourceFile(StreamReader::createByString($pdf)); + return $this->withTempDir(function (string $tempDir) use ($source) { + $pdf = new Pdf(); + $pdf->ignoreWarnings = true; + + $pdf->addFile($this->copySource($source, $tempDir, 0)); - for ($i = 1; $i <= $pageCount; $i++) { - $tplidx = $encoder->importPage($i); - $specs = $encoder->getTemplateSize($tplidx); + $pdf->setPassword(self::randomPassword()); + $pdf->allow('Printing DegradedPrinting'); - if (\is_array($specs)) { - $encoder->addPage($specs['orientation'], [$specs['width'], $specs['height']]); + $result = $pdf->toString(); + + if (\is_bool($result)) { + throw new \Exception('Failed to generate pdf file'); } - $encoder->useTemplate($tplidx); + return $result; + }); + } + + private static function randomPassword(int $length = 32): string + { + $string = ''; + + while (($len = strlen($string)) < $length) { + $size = $length - $len; + $bytes = random_bytes($size); + $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size); } - $encoder->setProtection([FpdiProtection::PERM_PRINT, FpdiProtection::PERM_DIGITAL_PRINT]); - return $encoder->Output('S'); + return $string; } } diff --git a/src/Domain/Pdf/Trait/PdfTempFileTrait.php b/src/Domain/Pdf/Trait/PdfTempFileTrait.php new file mode 100644 index 0000000..533d1ed --- /dev/null +++ b/src/Domain/Pdf/Trait/PdfTempFileTrait.php @@ -0,0 +1,41 @@ +filesystem->tempnam(\sys_get_temp_dir(), 'pdf-merger-'); + $this->filesystem->remove($tempDir); + + $this->filesystem->mkdir($tempDir); + $result = $f($tempDir); + $this->filesystem->remove($tempDir); + + return $result; + } + + private function copySource(string $source, string $tempDir, int $index): string + { + $tempFilePath = \sprintf('%s/pdf-merger-file-%d', $tempDir, $index); + $result = \file_put_contents($tempFilePath, $source); + + if (\is_bool($result)) { + throw new \Exception('An error occurred when create temporary file.'); + } + + return $tempFilePath; + } +}