diff --git a/.gitignore b/.gitignore index 49dbaea..5fc020f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /vendor/ /.idea/ /composer.lock +*.iml diff --git a/.travis.yml b/.travis.yml index 41b9c3e..7b96d1d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,8 @@ language: php php: - - 5.6 - - 7.0 - 7.1 - 7.2 + - 7.3 matrix: allow_failures: - php: hhvm diff --git a/composer.json b/composer.json index 94fa329..a0ff4cc 100644 --- a/composer.json +++ b/composer.json @@ -15,15 +15,15 @@ } ], "require": { - "php": ">=5.6 || ~7.0.0 || ~7.1.0 || ~7.2.0", - "mpdf/mpdf": "~7.0.0" + "php": "~7.1.0 || ~7.2.0 || ~7.3.0", + "mpdf/mpdf": "~8.0.0" }, "require-dev": { - "nette/application": "~2.2", - "nette/tester": "~2.0", + "nette/application": "~3.0", + "nette/tester": "~2.2", "symfony/dom-crawler": "~2.5", "symfony/css-selector": "~2.5", - "latte/latte": "~2.2" + "latte/latte": "~2.5" }, "extra": { "branch-alias": { @@ -36,6 +36,8 @@ }, "license": "LGPL-3.0", "autoload": { - "classmap": ["src"] + "psr-4": { + "Joseki\\Application\\Responses\\": "src/" + } } } diff --git a/src/Joseki/Application/Responses/InvalidArgumentException.php b/src/InvalidArgumentException.php similarity index 100% rename from src/Joseki/Application/Responses/InvalidArgumentException.php rename to src/InvalidArgumentException.php diff --git a/src/Joseki/Application/Responses/InvalidStateException.php b/src/InvalidStateException.php similarity index 100% rename from src/Joseki/Application/Responses/InvalidStateException.php rename to src/InvalidStateException.php diff --git a/src/Joseki/Application/Responses/MissingServiceException.php b/src/MissingServiceException.php similarity index 100% rename from src/Joseki/Application/Responses/MissingServiceException.php rename to src/MissingServiceException.php diff --git a/src/Joseki/Application/Responses/PdfResponse.php b/src/PdfResponse.php similarity index 76% rename from src/Joseki/Application/Responses/PdfResponse.php rename to src/PdfResponse.php index 0c1bb3c..69160a6 100644 --- a/src/Joseki/Application/Responses/PdfResponse.php +++ b/src/PdfResponse.php @@ -2,14 +2,19 @@ namespace Joseki\Application\Responses; +use Mpdf\HTMLParserMode; use Mpdf\Mpdf; +use Mpdf\MpdfException; +use Mpdf\Output\Destination; use Nette; use Nette\Bridges\ApplicationLatte\Template; use Nette\FileNotFoundException; use Nette\Http\IRequest; use Nette\Http\IResponse; use Nette\Utils\Strings; +use setasign\Fpdi\PdfParser\PdfParserException as PdfParserExceptionAlias; use Symfony\Component\DomCrawler\Crawler; +use Throwable; /** * PdfResponse @@ -109,56 +114,52 @@ class PdfResponse implements Nette\Application\IResponse /** @var string margins: top, right, bottom, left, header, footer */ private $pageMargins = "16,15,16,15,9,9"; - /** @var Mpdf */ + /** @var Mpdf|null */ private $mPDF = null; - /** @var mPDF */ - private $generatedFile; + /** @var Mpdf|null */ + private $generatedFile = null; /************************************ properties **************************************/ /** * @return string */ - public function getDocumentAuthor() + public function getDocumentAuthor(): string { return $this->documentAuthor; } - /** * @param string $documentAuthor */ - public function setDocumentAuthor($documentAuthor) + public function setDocumentAuthor(string $documentAuthor): void { $this->documentAuthor = (string)$documentAuthor; } - /** * @return string */ - public function getDocumentTitle() + public function getDocumentTitle(): string { return $this->documentTitle; } - /** * @param string $documentTitle */ - public function setDocumentTitle($documentTitle) + public function setDocumentTitle(string $documentTitle): void { $this->documentTitle = (string)$documentTitle; } - /** - * @return string + * @return string|int */ public function getDisplayZoom() { @@ -166,22 +167,25 @@ public function getDisplayZoom() } - /** - * @param string $displayZoom + * @param string|int $displayZoom */ - public function setDisplayZoom($displayZoom) + public function setDisplayZoom($displayZoom): void { - if (!in_array($displayZoom, array(self::ZOOM_DEFAULT, self::ZOOM_FULLPAGE, self::ZOOM_FULLWIDTH, self::ZOOM_REAL)) && $displayZoom <= 0) { + if (!in_array($displayZoom, array( + self::ZOOM_DEFAULT, + self::ZOOM_FULLPAGE, + self::ZOOM_FULLWIDTH, + self::ZOOM_REAL + )) && $displayZoom <= 0) { throw new InvalidArgumentException("Invalid zoom '$displayZoom', use PdfResponse::ZOOM_* constants or o positive integer."); } $this->displayZoom = $displayZoom; } - /** - * @return mixed + * @return string */ public function getDisplayLayout() { @@ -189,15 +193,22 @@ public function getDisplayLayout() } - /** - * @param mixed $displayLayout + * @param string $displayLayout + * @throws InvalidArgumentException */ - public function setDisplayLayout($displayLayout) + public function setDisplayLayout(string $displayLayout): void { if (!in_array( $displayLayout, - array(self::LAYOUT_DEFAULT, self::LAYOUT_CONTINUOUS, self::LAYOUT_SINGLE, self::LAYOUT_TWO, self::LAYOUT_TWOLEFT, self::LAYOUT_TWORIGHT) + array( + self::LAYOUT_DEFAULT, + self::LAYOUT_CONTINUOUS, + self::LAYOUT_SINGLE, + self::LAYOUT_TWO, + self::LAYOUT_TWOLEFT, + self::LAYOUT_TWORIGHT + ) ) && $displayLayout <= 0 ) { throw new InvalidArgumentException("Invalid layout '$displayLayout', use PdfResponse::LAYOUT* constants."); @@ -206,57 +217,51 @@ public function setDisplayLayout($displayLayout) } - /** * @return boolean */ - public function isMultiLanguage() + public function isMultiLanguage(): bool { return $this->multiLanguage; } - /** * @param boolean $multiLanguage */ - public function setMultiLanguage($multiLanguage) + public function setMultiLanguage(bool $multiLanguage): void { $this->multiLanguage = (bool)$multiLanguage; } - /** * @return boolean */ - public function isIgnoreStylesInHTMLDocument() + public function isIgnoreStylesInHTMLDocument(): bool { return $this->ignoreStylesInHTMLDocument; } - /** * @param boolean $ignoreStylesInHTMLDocument */ - public function setIgnoreStylesInHTMLDocument($ignoreStylesInHTMLDocument) + public function setIgnoreStylesInHTMLDocument(bool $ignoreStylesInHTMLDocument): void { $this->ignoreStylesInHTMLDocument = (bool)$ignoreStylesInHTMLDocument; } - /** * @return string */ - public function getSaveMode() + public function getSaveMode(): string { return $this->saveMode; } - /** * To force download, use PdfResponse::DOWNLOAD * To show pdf in browser, use PdfResponse::INLINE @@ -264,7 +269,7 @@ public function getSaveMode() * @param string $saveMode * @throws InvalidArgumentException */ - public function setSaveMode($saveMode) + public function setSaveMode(string $saveMode): void { if (!in_array($saveMode, array(self::DOWNLOAD, self::INLINE))) { throw new InvalidArgumentException("Invalid mode '$saveMode', use PdfResponse::INLINE or PdfResponse::DOWNLOAD instead."); @@ -273,23 +278,21 @@ public function setSaveMode($saveMode) } - /** * @return string */ - public function getPageOrientation() + public function getPageOrientation(): string { return $this->pageOrientation; } - /** * @param string $pageOrientation * @throws InvalidStateException * @throws InvalidArgumentException */ - public function setPageOrientation($pageOrientation) + public function setPageOrientation(string $pageOrientation): void { if ($this->mPDF) { throw new InvalidStateException('mPDF instance already created. Set page orientation before calling getMPDF'); @@ -301,22 +304,20 @@ public function setPageOrientation($pageOrientation) } - /** * @return string */ - public function getPageFormat() + public function getPageFormat(): string { return $this->pageFormat; } - /** * @param string $pageFormat * @throws InvalidStateException */ - public function setPageFormat($pageFormat) + public function setPageFormat(string $pageFormat): void { if ($this->mPDF) { throw new InvalidStateException('mPDF instance already created. Set page format before calling getMPDF'); @@ -325,22 +326,20 @@ public function setPageFormat($pageFormat) } - /** * @return string */ - public function getPageMargins() + public function getPageMargins(): string { return $this->pageMargins; } - /** * Gets margins as array * @return array */ - public function getMargins() + public function getMargins(): array { $margins = explode(",", $this->pageMargins); @@ -355,13 +354,12 @@ public function getMargins() } - /** * @param string $pageMargins * @throws InvalidStateException * @throws InvalidArgumentException */ - public function setPageMargins($pageMargins) + public function setPageMargins(string $pageMargins): void { if ($this->mPDF) { throw new InvalidStateException('mPDF instance already created. Set page margins before calling getMPDF'); @@ -383,15 +381,15 @@ public function setPageMargins($pageMargins) } - /** * WARNING: internally creates mPDF instance, setting some properties after calling this method * may cause an Exception * * @param string $pathToBackgroundTemplate * @throws FileNotFoundException + * @throws PdfParserExceptionAlias */ - public function setBackgroundTemplate($pathToBackgroundTemplate) + public function setBackgroundTemplate(string $pathToBackgroundTemplate): void { if (!file_exists($pathToBackgroundTemplate)) { throw new FileNotFoundException("File '$pathToBackgroundTemplate' not found."); @@ -400,10 +398,9 @@ public function setBackgroundTemplate($pathToBackgroundTemplate) // if background exists, then add it as a background $mpdf = $this->getMPDF(); - $mpdf->SetImportUse(); - $pagecount = $mpdf->SetSourceFile($this->backgroundTemplate); + $pagecount = $mpdf->setSourceFile($this->backgroundTemplate); for ($i = 1; $i <= $pagecount; $i++) { - $tplId = $mpdf->ImportPage($i); + $tplId = $mpdf->importPage($i); $mpdf->UseTemplate($tplId); if ($i < $pagecount) { @@ -414,11 +411,10 @@ public function setBackgroundTemplate($pathToBackgroundTemplate) } - /** * @return array */ - protected function getMPDFConfig() + protected function getMPDFConfig(): array { $margins = $this->getMargins(); return [ @@ -435,16 +431,19 @@ protected function getMPDFConfig() } - /** - * @throws InvalidStateException * @return Mpdf + * @throws InvalidStateException */ - public function getMPDF() + public function getMPDF(): Mpdf { if (!$this->mPDF instanceof Mpdf) { - $mpdf = new Mpdf($this->getMPDFConfig()); + try { + $mpdf = new Mpdf($this->getMPDFConfig()); + } catch (MpdfException $e) { + throw new InvalidStateException("Unable to create Mpdf object", 0, $e); + } $mpdf->showImageErrors = true; @@ -483,12 +482,14 @@ public function __construct($source) * Builds final pdf * * @return mPDF - * @throws \Exception + * @throws InvalidStateException + * @throws MissingServiceException + * @throws MpdfException */ - private function build() + private function build(): Mpdf { if (empty($this->documentTitle)) { - throw new \Exception ("Var 'documentTitle' cannot be empty."); + throw new InvalidStateException("Var 'documentTitle' cannot be empty."); } if ($this->ignoreStylesInHTMLDocument) { if (!class_exists('Symfony\Component\DomCrawler\Crawler')) { @@ -508,14 +509,19 @@ private function build() } if ($this->source instanceof Template) { - $html = $this->source->__toString(); + try { + /** @noinspection PhpMethodParametersCountMismatchInspection */ + $html = $this->source->__toString(true); + } catch (Throwable $e) { + throw new InvalidStateException("Template rendering failed", 0, $e); + } } else { $html = $this->source; } // Fix: $html can't be empty (mPDF generates Fatal error) if (empty($html)) { - $html = ''; + $html = ''; } $mpdf = $this->getMPDF(); @@ -526,7 +532,7 @@ private function build() // Add styles if (!empty($this->styles)) { - $mpdf->WriteHTML($this->styles, 1); + $mpdf->WriteHTML($this->styles, HTMLParserMode::HEADER_CSS); } // copied from mPDF -> removes comments @@ -544,9 +550,9 @@ private function build() } $html = $crawler->html(); - $mode = 2; // If tags are found, all html outside these tags are discarded, and the rest is parsed as content for the document. If no tags are found, all html is parsed as content. Prior to mPDF 4.2 the default CSS was not parsed when using mode #2 + $mode = HTMLParserMode::HTML_BODY; // If tags are found, all html outside these tags are discarded, and the rest is parsed as content for the document. If no tags are found, all html is parsed as content. Prior to mPDF 4.2 the default CSS was not parsed when using mode #2 } else { - $mode = 0; // Parse all: HTML + CSS + $mode = HTMLParserMode::DEFAULT_MODE; // Parse all: HTML + CSS } // Add content @@ -569,26 +575,27 @@ private function build() * @param IRequest $httpRequest * @param IResponse $httpResponse * @return void + * @throws MpdfException */ - public function send(IRequest $httpRequest, IResponse $httpResponse) + public function send(IRequest $httpRequest, IResponse $httpResponse): void { $mpdf = $this->build(); $mpdf->Output(Strings::webalize($this->documentTitle) . ".pdf", $this->saveMode); } - /** * Save file to target location * Note: $name overrides property $documentTitle * * @param string $dir path to directory - * @param string $filename + * @param string|null $filename * @return string + * @throws MpdfException */ - public function save($dir, $filename = null) + public function save(string $dir, ?string $filename = null): string { - $content = $this->__toString(); + $content = $this->toString(); $filename = Strings::lower($filename ?: $this->documentTitle); if (Strings::endsWith($filename, ".pdf")) { @@ -603,6 +610,15 @@ public function save($dir, $filename = null) return $dir . $filename; } + /** + * @return string + * @throws MpdfException + */ + public function toString() + { + $pdf = $this->build(); + return $pdf->Output("", Destination::STRING_RETURN); + } /** @@ -612,8 +628,14 @@ public function save($dir, $filename = null) */ public function __toString() { - $pdf = $this->build(); - return $pdf->Output("", "S"); + $string = ""; + try { + $string = $this->toString(); + } catch (MpdfException $e) { + trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", + E_USER_ERROR); + } + return $string; } } diff --git a/tests/PdfResponse/PdfResponse.page.format.phpt b/tests/PdfResponse/PdfResponse.page.format.phpt index cd766ef..e872e0c 100644 --- a/tests/PdfResponse/PdfResponse.page.format.phpt +++ b/tests/PdfResponse/PdfResponse.page.format.phpt @@ -6,6 +6,7 @@ */ use Joseki\Application\Responses\PdfResponse; +use Joseki\Application\Responses\InvalidStateException; use Nette\Http; use Tester\Assert; @@ -41,7 +42,7 @@ test( function () use ($fileResponse) { $fileResponse->pageOrientation = PdfResponse::ORIENTATION_LANDSCAPE; }, - 'Joseki\Application\Responses\InvalidStateException', + InvalidStateException::class, 'mPDF instance already created. Set page orientation before calling getMPDF' ); } @@ -56,7 +57,7 @@ test( function () use ($fileResponse) { $fileResponse->pageFormat = 'A4-L'; }, - 'Joseki\Application\Responses\InvalidStateException', + InvalidStateException::class, 'mPDF instance already created. Set page format before calling getMPDF' ); } @@ -71,7 +72,7 @@ test( function () use ($fileResponse) { $fileResponse->pageMargins = $fileResponse->getPageMargins(); }, - 'Joseki\Application\Responses\InvalidStateException', + InvalidStateException::class, 'mPDF instance already created. Set page margins before calling getMPDF' ); } diff --git a/tests/PdfResponse/PdfResponse.setters.phpt b/tests/PdfResponse/PdfResponse.setters.phpt index 3ca08cd..c40ccd2 100644 --- a/tests/PdfResponse/PdfResponse.setters.phpt +++ b/tests/PdfResponse/PdfResponse.setters.phpt @@ -5,6 +5,7 @@ */ use Joseki\Application\Responses\PdfResponse; +use Joseki\Application\Responses\InvalidArgumentException; use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -21,7 +22,7 @@ test( function () use ($fileResponse) { $fileResponse->displayZoom = "invalid"; }, - 'Joseki\Application\Responses\InvalidArgumentException' + InvalidArgumentException::class ); // layout @@ -30,7 +31,7 @@ test( function () use ($fileResponse) { $fileResponse->displayLayout = "invalid"; }, - 'Joseki\Application\Responses\InvalidArgumentException' + InvalidArgumentException::class ); } ); diff --git a/tests/PdfResponse/expected/full.pdf b/tests/PdfResponse/expected/full.pdf index c3ede8d..1f7848d 100644 Binary files a/tests/PdfResponse/expected/full.pdf and b/tests/PdfResponse/expected/full.pdf differ diff --git a/tests/PdfResponse/expected/page.format.pdf b/tests/PdfResponse/expected/page.format.pdf index 22283b3..5b68185 100644 Binary files a/tests/PdfResponse/expected/page.format.pdf and b/tests/PdfResponse/expected/page.format.pdf differ diff --git a/tests/PdfResponse/expected/symfony.crawler.pdf b/tests/PdfResponse/expected/symfony.crawler.pdf index a724abb..3b2f979 100644 Binary files a/tests/PdfResponse/expected/symfony.crawler.pdf and b/tests/PdfResponse/expected/symfony.crawler.pdf differ