From d0932e938cb48e305c1e6c3ae10d36d0038cf83b Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 27 Mar 2024 14:28:40 -0300 Subject: [PATCH 1/2] Replace TCPDI by TCPDF TCPDI have problems when manipulate the original PDF file. The approach to add footer at PDF files was replaced by create an overlay to each page and merge with original PDF file. Signed-off-by: Vitor Mattos --- composer.json | 11 +- composer.lock | 181 +----------------- lib/Db/File.php | 16 +- lib/Db/SignRequest.php | 2 +- lib/Db/SignRequestMapper.php | 3 - lib/Handler/Pkcs12Handler.php | 52 +++-- ...{TCPDILibresign.php => TCPDFLibresign.php} | 4 +- lib/Service/FileElementService.php | 14 +- lib/Service/FileService.php | 2 +- lib/Service/SignFileService.php | 2 +- tests/Unit/Handler/Pkcs12HandlerTest.php | 8 +- 11 files changed, 62 insertions(+), 233 deletions(-) rename lib/Handler/{TCPDILibresign.php => TCPDFLibresign.php} (96%) diff --git a/composer.json b/composer.json index 53da10bb0..2b48d6f68 100644 --- a/composer.json +++ b/composer.json @@ -5,13 +5,12 @@ "license": "AGPL", "require": { "endroid/qr-code": "^4.6", - "iio/libmergepdf": "dev-move-tcpdi-parser-to-package", "jsignpdf/jsignpdf-php": "^1.2", "mikehaertl/php-pdftk": "^0.13.0", "pagerfanta/pagerfanta": "^3.6", "smalot/pdfparser": "^2.4", "symfony/console": "^5.4", - "tecnickcom/tcpdf": "^6.4", + "tecnickcom/tcpdf": "^6.7", "wobeto/email-blur": "^1.0" }, "require-dev": { @@ -62,11 +61,5 @@ "psr-4": { "OCP\\": "vendor/nextcloud/ocp/OCP" } - }, - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/LibreCodeCoop/libmergepdf" - } - ] + } } diff --git a/composer.lock b/composer.lock index 8454f80b3..50730e44b 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": "e680dc2da1aee7d230b344aa0367ca37", + "content-hash": "3b8c5d2bdc7f7847621f5e16ffc8e7fc", "packages": [ { "name": "bacon/bacon-qr-code", @@ -185,65 +185,6 @@ ], "time": "2023-03-30T18:46:02+00:00" }, - { - "name": "iio/libmergepdf", - "version": "dev-move-tcpdi-parser-to-package", - "source": { - "type": "git", - "url": "https://github.com/LibreSign/libmergepdf.git", - "reference": "4304a115056e595725e6105a42e47cebe0416ed2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/LibreSign/libmergepdf/zipball/4304a115056e595725e6105a42e47cebe0416ed2", - "reference": "4304a115056e595725e6105a42e47cebe0416ed2", - "shasum": "" - }, - "require": { - "libresign/tcpdi_parser": "^0.4.0", - "php": "^7.1||^8.0", - "setasign/fpdi": "^2", - "tecnickcom/tcpdf": "^6.2.22" - }, - "conflict": { - "rafikhaceb/tcpdi": "*", - "setasign/fpdf": "*" - }, - "require-dev": { - "behat/behat": "^3.10", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.5", - "smalot/pdfparser": "~0.13" - }, - "type": "library", - "autoload": { - "psr-4": { - "iio\\libmergepdf\\": "src/" - }, - "classmap": [ - "tcpdi/" - ] - }, - "license": [ - "WTFPL" - ], - "authors": [ - { - "name": "Hannes Forsgård", - "email": "hannes.forsgard@fripost.org" - } - ], - "description": "Library for merging multiple PDFs", - "homepage": "https://github.com/hanneskod/libmergepdf", - "keywords": [ - "merge", - "pdf" - ], - "support": { - "source": "https://github.com/LibreSign/libmergepdf/tree/move-tcpdi-parser-to-package" - }, - "time": "2022-01-30T18:52:29+00:00" - }, { "name": "jsignpdf/jsignpdf-php", "version": "v1.2.3", @@ -297,45 +238,6 @@ }, "time": "2022-12-02T17:27:21+00:00" }, - { - "name": "libresign/tcpdi_parser", - "version": "v0.4", - "source": { - "type": "git", - "url": "https://github.com/LibreSign/tcpdi_parser.git", - "reference": "c958caddb089e8986a509dc538cda0c5e14f18a2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/LibreSign/tcpdi_parser/zipball/c958caddb089e8986a509dc538cda0c5e14f18a2", - "reference": "c958caddb089e8986a509dc538cda0c5e14f18a2", - "shasum": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "LibreSign\\TcpdiParser\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "LGPL-3.0-or-later" - ], - "authors": [ - { - "name": "Vitor Mattos", - "email": "vitor@php.rio" - }, - { - "name": "Paul Nicholls", - "homepage": "https://github.com/pauln" - } - ], - "support": { - "source": "https://github.com/LibreSign/tcpdi_parser/tree/v0.4" - }, - "time": "2022-01-30T18:39:05+00:00" - }, { "name": "mikehaertl/php-pdftk", "version": "0.13.1", @@ -631,78 +533,6 @@ }, "time": "2021-11-05T16:47:00+00:00" }, - { - "name": "setasign/fpdi", - "version": "v2.6.0", - "source": { - "type": "git", - "url": "https://github.com/Setasign/FPDI.git", - "reference": "a6db878129ec6c7e141316ee71872923e7f1b7ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Setasign/FPDI/zipball/a6db878129ec6c7e141316ee71872923e7f1b7ad", - "reference": "a6db878129ec6c7e141316ee71872923e7f1b7ad", - "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.6", - "setasign/tfpdf": "~1.33", - "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.6.0" - }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/setasign/fpdi", - "type": "tidelift" - } - ], - "time": "2023-12-11T16:03:32+00:00" - }, { "name": "smalot/pdfparser", "version": "v2.9.0", @@ -1567,12 +1397,12 @@ "source": { "type": "git", "url": "https://github.com/tecnickcom/TCPDF.git", - "reference": "f9fd21807cbb5d43ed62c685e2d6467515d31746" + "reference": "d4adef47ca21c90e6483d59dcb9e5b1023696937" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/f9fd21807cbb5d43ed62c685e2d6467515d31746", - "reference": "f9fd21807cbb5d43ed62c685e2d6467515d31746", + "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/d4adef47ca21c90e6483d59dcb9e5b1023696937", + "reference": "d4adef47ca21c90e6483d59dcb9e5b1023696937", "shasum": "" }, "require": { @@ -1631,7 +1461,7 @@ "type": "custom" } ], - "time": "2024-03-21T09:36:59+00:00" + "time": "2024-03-25T23:56:24+00:00" }, { "name": "wobeto/email-blur", @@ -2669,7 +2499,6 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": { - "iio/libmergepdf": 20, "nextcloud/ocp": 20, "roave/security-advisories": 20 }, diff --git a/lib/Db/File.php b/lib/Db/File.php index 2adf76b4d..dc44812ff 100644 --- a/lib/Db/File.php +++ b/lib/Db/File.php @@ -25,6 +25,7 @@ namespace OCA\Libresign\Db; use OCP\AppFramework\Db\Entity; +use stdClass; /** * @method void setId(int $id) @@ -45,8 +46,7 @@ * @method string getCallback() * @method void setStatus(int $status) * @method int getStatus() - * @method void setPages(int $pages) - * @method int getPages() + * @method string getMetadata() */ class File extends Entity { /** @var integer */ @@ -97,4 +97,16 @@ public function __construct() { $this->addType('status', 'integer'); $this->addType('metadata', 'string'); } + + public function setMetadata($metadata): void { + if (is_array($metadata)) { + $metadata = json_encode($metadata); + } + $this->metadata = (string) $metadata; + $this->markFieldUpdated('metadata'); + } + + public function getMetadataDecoded(): ?stdClass { + return json_decode($this->metadata); + } } diff --git a/lib/Db/SignRequest.php b/lib/Db/SignRequest.php index b722d1fb3..640dae2fc 100644 --- a/lib/Db/SignRequest.php +++ b/lib/Db/SignRequest.php @@ -43,7 +43,7 @@ * @method void setDisplayName(string $displayName) * @method string getDisplayName() * @method void setMetadata(array $metadata) - * @method string getMetadata() + * @method array getMetadata() */ class SignRequest extends Entity { /** @var integer */ diff --git a/lib/Db/SignRequestMapper.php b/lib/Db/SignRequestMapper.php index ee5c0ad43..04527a4de 100644 --- a/lib/Db/SignRequestMapper.php +++ b/lib/Db/SignRequestMapper.php @@ -69,9 +69,6 @@ public function incrementNotificationCounter(SignRequest $signRequest, string $m try { $fromDatabase = $this->getById($signRequest->getId()); $metadata = $fromDatabase->getMetadata(); - if (!empty($metadata)) { - $metadata = json_decode($metadata, true); - } if (!isset($metadata['notify'])) { $this->firstNotification = true; } diff --git a/lib/Handler/Pkcs12Handler.php b/lib/Handler/Pkcs12Handler.php index 755d3f60d..099f07867 100644 --- a/lib/Handler/Pkcs12Handler.php +++ b/lib/Handler/Pkcs12Handler.php @@ -33,14 +33,16 @@ use Endroid\QrCode\QrCode; use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeMargin; use OC\SystemConfig; +use OCA\Libresign\Db\File as FileEntity; use OCA\Libresign\Exception\LibresignException; use OCA\Libresign\Handler\CertificateEngine\Handler as CertificateEngineHandler; use OCA\Libresign\Service\FolderService; +use OCA\Libresign\Service\PdfParserService; use OCP\AppFramework\Services\IAppConfig; use OCP\Files\File; use OCP\IL10N; use OCP\IURLGenerator; -use TCPDI; +use TCPDF; use TypeError; class Pkcs12Handler extends SignEngineHandler { @@ -59,6 +61,7 @@ public function __construct( private CertificateEngineHandler $certificateEngineHandler, private IL10N $l10n, private JSignPdfHandler $jSignPdfHandler, + private PdfParserService $pdfParserService, ) { } @@ -159,42 +162,33 @@ public function sign(): File { /** * @psalm-suppress MixedReturnStatement */ - public function getFooter(File $file, string $uuid): string { + public function getFooter(File $file, FileEntity $fileEntity): string { $add_footer = (bool) $this->appConfig->getAppValue('add_footer', '1'); if (!$add_footer) { return ''; } + $metadata = $fileEntity->getMetadata(); + if (!is_array($metadata) || !isset($metadata['d'])) { + $metadata = $this->pdfParserService->getMetadata($file); + } $validation_site = $this->appConfig->getAppValue('validation_site'); if ($validation_site) { - $validation_site = rtrim($validation_site, '/').'/'.$uuid; + $validation_site = rtrim($validation_site, '/').'/'.$fileEntity->getUuid(); } else { - $validation_site = $this->urlGenerator->linkToRouteAbsolute('libresign.page.validationFileWithShortUrl', ['uuid' => $uuid]); + $validation_site = $this->urlGenerator->linkToRouteAbsolute('libresign.page.validationFileWithShortUrl', ['uuid' => $fileEntity->getUuid()]); } - $pdf = new TCPDILibresign(); - $pageCount = $pdf->setSourceData($file->getContent()); - - $dimensions = null; - for ($pageNo = 1; $pageNo <= $pageCount; $pageNo++) { - $pdf->importPage($pageNo); - - // Define dimensions of page - $tpl = $pdf->tpls[$pageNo]; - $dimensions['or'] = $tpl['w'] > $tpl['h'] ? 'L' : 'P'; - $pdf->setPageOrientation($dimensions['or']); - $dimensions = $pdf->getPageDimensions($pageNo - 1); - $dimensions['w'] = $tpl['w']; - $dimensions['h'] = $tpl['h']; - $dimensions['wk'] = $tpl['h']; - $dimensions['hk'] = $tpl['h']; - foreach (['MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox'] as $box) { - if (!isset($dimensions[$box])) { - continue; - } - $dimensions[$box]['urx'] = $tpl['h']; - $dimensions[$box]['ury'] = $tpl['w']; - } - $pdf->AddPage($dimensions['or'], $dimensions); + $pdf = new TCPDFLibresign(); + foreach ($metadata['d'] as $dimension) { + $orientation = $dimension['w'] > $dimension['h'] ? 'L' : 'P'; + $pdf->AddPage($orientation, [ + 'MediaBox' => [ + 'llx' => 0, + 'lly' => 0, + 'urx' => $dimension['w'], + 'ury' => $dimension['h'], + ] + ]); $pdf->SetFont('Helvetica'); $pdf->SetFontSize(8); @@ -232,7 +226,7 @@ public function getFooter(File $file, string $uuid): string { return $pdf->Output('', 'S'); } - private function writeQrCode(string $text, TCPDI $fpdf): void { + private function writeQrCode(string $text, TCPDF $fpdf): void { $this->qrCode = QrCode::create($text) ->setEncoding(new Encoding('UTF-8')) ->setErrorCorrectionLevel(new ErrorCorrectionLevelLow()) diff --git a/lib/Handler/TCPDILibresign.php b/lib/Handler/TCPDFLibresign.php similarity index 96% rename from lib/Handler/TCPDILibresign.php rename to lib/Handler/TCPDFLibresign.php index 4562729ef..ff3409bb2 100644 --- a/lib/Handler/TCPDILibresign.php +++ b/lib/Handler/TCPDFLibresign.php @@ -24,9 +24,9 @@ namespace OCA\Libresign\Handler; -use TCPDI; +use TCPDF; -class TCPDILibresign extends TCPDI { +class TCPDFLibresign extends TCPDF { /** * @var bool */ diff --git a/lib/Service/FileElementService.php b/lib/Service/FileElementService.php index 4fa7c8dac..04122c0ec 100644 --- a/lib/Service/FileElementService.php +++ b/lib/Service/FileElementService.php @@ -34,7 +34,7 @@ class FileElementService { public function __construct( private FileMapper $fileMapper, private FileElementMapper $fileElementMapper, - private ITimeFactory $timeFactory + private ITimeFactory $timeFactory, ) { } @@ -77,13 +77,13 @@ private function getVisibleElementFromProperties(array $properties, string $uuid private function translateCoordinatesToInternalNotation(array $properties, File $file): array { $translated['page'] = $properties['coordinates']['page'] ?? 1; - $metadata = json_decode($file->getMetadata(), true); - $dimension = $metadata['d'][$translated['page'] - 1]; + $metadata = $file->getMetadataDecoded(); + $dimension = $metadata->d[$translated['page'] - 1]; if (isset($properties['coordinates']['ury'])) { $translated['ury'] = $properties['coordinates']['ury']; } elseif (isset($properties['coordinates']['top'])) { - $translated['ury'] = $dimension['h'] - $properties['coordinates']['top']; + $translated['ury'] = $dimension->h - $properties['coordinates']['top']; } else { $translated['ury'] = 0; } @@ -131,12 +131,12 @@ private function translateCoordinatesToInternalNotation(array $properties, File } public function translateCoordinatesFromInternalNotation(array $properties, File $file): array { - $metadata = json_decode($file->getMetadata(), true); - $dimension = $metadata['d'][$properties['coordinates']['page'] - 1]; + $metadata = $file->getMetadataDecoded(); + $dimension = $metadata->d[$properties['coordinates']['page'] - 1]; $translated['left'] = $properties['coordinates']['llx']; $translated['height'] = abs($properties['coordinates']['ury'] - $properties['coordinates']['lly']); - $translated['top'] = $dimension['h'] - $properties['coordinates']['ury']; + $translated['top'] = $dimension->h - $properties['coordinates']['ury']; $translated['width'] = $properties['coordinates']['urx'] - $properties['coordinates']['llx']; return $translated; diff --git a/lib/Service/FileService.php b/lib/Service/FileService.php index 287ba03d7..25af403cd 100644 --- a/lib/Service/FileService.php +++ b/lib/Service/FileService.php @@ -259,7 +259,7 @@ private function getSigners(): array { private function getPages(): array { $return = []; - $metadata = json_decode($this->file->getMetadata()); + $metadata = $this->file->getMetadataDecoded(); for ($page = 1; $page <= $metadata->p; $page++) { $return[] = [ 'url' => $this->urlGenerator->linkToRoute('ocs.libresign.File.getPage', [ diff --git a/lib/Service/SignFileService.php b/lib/Service/SignFileService.php index 41789ec21..ed3da657c 100644 --- a/lib/Service/SignFileService.php +++ b/lib/Service/SignFileService.php @@ -535,7 +535,7 @@ private function getPdfToSign(FileEntity $fileData, File $originalFile): File { $originalFile->getPath() ); - $footer = $this->pkcs12Handler->getFooter($originalFile, $fileData->getUuid()); + $footer = $this->pkcs12Handler->getFooter($originalFile, $fileData); if ($footer) { $background = $this->tempManager->getTemporaryFile('signed.pdf'); file_put_contents($background, $footer); diff --git a/tests/Unit/Handler/Pkcs12HandlerTest.php b/tests/Unit/Handler/Pkcs12HandlerTest.php index 60b50e56e..5af879a0c 100644 --- a/tests/Unit/Handler/Pkcs12HandlerTest.php +++ b/tests/Unit/Handler/Pkcs12HandlerTest.php @@ -3,10 +3,10 @@ use OC\SystemConfig; use OCA\Libresign\Handler\CertificateEngine\CfsslHandler; use OCA\Libresign\Handler\CertificateEngine\Handler as CertificateEngineHandler; -use OCA\Libresign\Handler\CertificateEngine\OpenSslHandler; use OCA\Libresign\Handler\JSignPdfHandler; use OCA\Libresign\Handler\Pkcs12Handler; use OCA\Libresign\Service\FolderService; +use OCA\Libresign\Service\PdfParserService; use OCP\AppFramework\Services\IAppConfig; use OCP\IL10N; use OCP\IURLGenerator; @@ -21,7 +21,7 @@ final class Pkcs12HandlerTest extends \OCA\Libresign\Tests\Unit\TestCase { private CfsslHandler|MockObject $cfsslHandler; private IL10N|MockObject $l10n; private JSignPdfHandler|MockObject $jSignPdfHandler; - private OpenSslHandler|MockObject $openSslHandler; + private PdfParserService|MockObject $pdfParserService; private CertificateEngineHandler|MockObject $certificateEngineHandler; private array $cfsslHandlerBuffer = []; @@ -36,6 +36,7 @@ public function setUp(): void { ->method('t') ->will($this->returnArgument(0)); $this->jSignPdfHandler = $this->createMock(JSignPdfHandler::class); + $this->pdfParserService = $this->createMock(PdfParserService::class); $this->pkcs12Handler = new Pkcs12Handler( $this->folderService, $this->appConfig, @@ -44,6 +45,7 @@ public function setUp(): void { $this->certificateEngineHandler, $this->l10n, $this->jSignPdfHandler, + $this->pdfParserService, ); } @@ -102,6 +104,7 @@ public function testGetFooterWithoutValidationSite() { $this->certificateEngineHandler, $this->l10n, $this->jSignPdfHandler, + $this->pdfParserService, ); $file = $this->createMock(\OCP\Files\File::class); $actual = $this->pkcs12Handler->getFooter($file, 'uuid'); @@ -130,6 +133,7 @@ public function testGetFooterWithSuccess() { $this->certificateEngineHandler, $this->l10n, $this->jSignPdfHandler, + $this->pdfParserService, ); $file = $this->createMock(\OCP\Files\File::class); From c71d9170b5d7484691fc6e7d756ab510f37ac16c Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 27 Mar 2024 18:21:44 -0300 Subject: [PATCH 2/2] Fix unit test Signed-off-by: Vitor Mattos --- tests/Unit/Handler/Pkcs12HandlerTest.php | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/tests/Unit/Handler/Pkcs12HandlerTest.php b/tests/Unit/Handler/Pkcs12HandlerTest.php index 5af879a0c..561e79a91 100644 --- a/tests/Unit/Handler/Pkcs12HandlerTest.php +++ b/tests/Unit/Handler/Pkcs12HandlerTest.php @@ -107,7 +107,8 @@ public function testGetFooterWithoutValidationSite() { $this->pdfParserService, ); $file = $this->createMock(\OCP\Files\File::class); - $actual = $this->pkcs12Handler->getFooter($file, 'uuid'); + $libresignFile = $this->createMock(\OCA\Libresign\Db\File::class); + $actual = $this->pkcs12Handler->getFooter($file, $libresignFile); $this->assertEmpty($actual); } @@ -141,8 +142,24 @@ public function testGetFooterWithSuccess() { ->willReturn('small_valid.pdf'); $file->method('getContent') ->willReturn(file_get_contents(__DIR__ . '/../../fixtures/small_valid.pdf')); - $actual = $this->pkcs12Handler->getFooter($file, 'uuid'); - $this->assertEquals(18615, strlen($actual)); + $libresignFile = $this->createMock(\OCA\Libresign\Db\File::class); + $libresignFile + ->method('__call') + ->willReturnCallback(function ($key, $default) { + switch ($key) { + case 'getMetadata': return [ + 'd' => [ + [ + 'w' => 100, + 'h' => 100, + ], + ], + ]; + case 'getUuid': return 'uuid'; + } + }); + $actual = $this->pkcs12Handler->getFooter($file, $libresignFile); + $this->assertEquals(7655, strlen($actual)); } public function cfsslHandlerCallbackToGetSetArguments($functionName, $value = null) {