├── Adapter ├── WkPdfEngine.php ├── WkPdfException.php └── WkPdfFile.php ├── Api ├── Medium.php ├── Options.php ├── PdfEngine.php ├── PdfFile.php ├── SourceDocument.php └── TableOfContents.php ├── Block └── PdfTemplate.php ├── Jenkinsfile ├── Model ├── Cache.php ├── CachedPdfFile.php ├── Config.php ├── PdfFactory.php ├── PdfResponse.php └── View │ ├── PageResultWithoutHttp.php │ └── PdfResult.php ├── README.md ├── Service ├── FakePdfEngine.php ├── FakePdfFile.php ├── FakeSourceDocument.php ├── Pdf.php ├── PdfAppendContent.php ├── PdfCover.php ├── PdfOptions.php └── PdfTableOfContents.php ├── Test ├── Integration │ ├── ConfigTest.php │ ├── PdfResultTest.php │ ├── PdfTemplateTest.php │ └── WkPdfEngineTest.php └── Unit │ ├── Model │ ├── PdfFactoryTest.php │ └── PdfResultTest.php │ └── Service │ ├── FakePdfFileTest.php │ ├── FakeSourceDocumentTest.php │ ├── PdfAppendContentTest.php │ ├── PdfCoverTest.php │ ├── PdfOptionsTest.php │ ├── PdfTableOfContentsTest.php │ └── PdfTest.php ├── composer.json ├── docs └── img │ └── config.png ├── etc ├── acl.xml ├── adminhtml │ └── system.xml ├── config.xml ├── di.xml └── module.xml ├── grumphp.yml ├── phpunit.xml.dist └── registration.php /Adapter/WkPdfEngine.php: -------------------------------------------------------------------------------- 1 | wkPdf = $wkPdf; 21 | } 22 | 23 | public function __clone() 24 | { 25 | $this->wkPdf = clone $this->wkPdf; 26 | } 27 | 28 | public function addPage($html, Options $options) 29 | { 30 | $this->wkPdf->addPage($this->html($html), $options->asArray()); 31 | } 32 | 33 | public function addCover($html, Options $options) 34 | { 35 | $this->wkPdf->addCover($this->html($html), $options->asArray()); 36 | } 37 | 38 | public function addTableOfContents(Options $options) 39 | { 40 | $this->wkPdf->addToc($options->asArray()); 41 | } 42 | 43 | /** 44 | * @param Options $globalOptions 45 | * @return PdfFile 46 | */ 47 | public function generatePdf(Options $globalOptions) 48 | { 49 | /* 50 | * No factory here, WkPdfEngine and WkPdfFile are tightly coupled and should only be exchanged together 51 | */ 52 | $wkPdf = clone $this->wkPdf; 53 | $wkPdf->setOptions($globalOptions->asArray()); 54 | return new WkPdfFile($wkPdf); 55 | } 56 | 57 | /** 58 | * Make sure, string is recognized as HTML, not file or URL 59 | * 60 | * @param $html 61 | * @return string 62 | */ 63 | private function html($html) 64 | { 65 | if (!preg_match(Pdf::REGEX_HTML, $html) && !preg_match(Pdf::REGEX_XML, $html)) { 66 | return sprintf('%s', $html); 67 | } 68 | return $html; 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /Adapter/WkPdfException.php: -------------------------------------------------------------------------------- 1 | wkPdf = $wkPdf; 18 | } 19 | 20 | public function saveAs($path) 21 | { 22 | if (false === $this->wkPdf->saveAs($path)) { 23 | throw new WkPdfException($this->wkPdf->getError()); 24 | } 25 | } 26 | 27 | public function send() 28 | { 29 | if (false === $this->wkPdf->send()) { 30 | throw new WkPdfException($this->wkPdf->getError()); 31 | } 32 | } 33 | 34 | public function toString() 35 | { 36 | $result = $this->wkPdf->toString(); 37 | if ($result === false) { 38 | throw new WkPdfException($this->wkPdf->getError()); 39 | } 40 | return $result; 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /Api/Medium.php: -------------------------------------------------------------------------------- 1 | pdfOptions = $optionsFactory->create(); 33 | parent::__construct($context, $data); 34 | } 35 | 36 | public function printTo(Medium $medium) 37 | { 38 | $medium->printHtml($this->toHtml(), $this->pdfOptions); 39 | } 40 | } -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | @Library('staempfli/jenkins-shared-library') _ 2 | magento2ModulePipeline {} 3 | -------------------------------------------------------------------------------- /Model/Cache.php: -------------------------------------------------------------------------------- 1 | pdfEngine = $pdfEngine; 26 | $this->scopeConfig = $scopeConfig; 27 | } 28 | 29 | public function create() 30 | { 31 | $pdf = new Pdf($this->pdfEngine); 32 | $pdf->setOptions($this->optionsFromConfig()); 33 | return $pdf; 34 | } 35 | 36 | private function optionsFromConfig() 37 | { 38 | $config = function ($xpath) { 39 | return $this->scopeConfig->getValue($xpath); 40 | }; 41 | $withoutNull = function (array $array) { 42 | return array_filter($array, function ($value) { 43 | return ! is_null($value); //@codingStandardsIgnoreLine; 44 | }); 45 | }; 46 | return new PdfOptions( 47 | $withoutNull( 48 | [ 49 | PdfOptions::KEY_GLOBAL_BINARY => $config(Config::XML_PATH_BINARY), 50 | PdfOptions::KEY_GLOBAL_VERSION9 => $config(Config::XML_PATH_VERSION9), 51 | PdfOptions::KEY_GLOBAL_TMP_DIR => $config(Config::XML_PATH_TMP_DIR), 52 | PdfOptions::KEY_GLOBAL_CLI_OPTIONS => $withoutNull([ 53 | PdfOptions::KEY_CLI_OPTIONS_ESCAPE_ARGS => $config(Config::XML_PATH_ESCAPE_ARGS), 54 | PdfOptions::KEY_CLI_OPTIONS_USE_EXEC => $config(Config::XML_PATH_USE_EXEC), 55 | PdfOptions::KEY_CLI_OPTIONS_XVFB_RUN_OPTIONS => $config(Config::XML_PATH_XVFB_RUN_OPTIONS), 56 | PdfOptions::KEY_CLI_OPTIONS_XVFB_RUN_BINARY => $config(Config::XML_PATH_XVFB_RUN_BINARY), 57 | PdfOptions::KEY_CLI_OPTIONS_USE_XVFB_RUN => $config(Config::XML_PATH_USE_XVFB_RUN), 58 | ]) 59 | ] 60 | ) 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Model/PdfResponse.php: -------------------------------------------------------------------------------- 1 | pdfOptions = $options; 27 | } 28 | 29 | /** 30 | * We don't actually send a HTTP response, the PdfResponse instance is sent to the Pdf service instead 31 | * 32 | * @return void 33 | */ 34 | public function sendResponse() 35 | { 36 | return; 37 | } 38 | 39 | /** 40 | * Not explicitly part of the Response interface but implicitly required by \Magento\Framework\View\Result\Page 41 | * 42 | * @param string $value 43 | * @return $this 44 | */ 45 | public function appendBody($value) 46 | { 47 | $this->body .= $value; 48 | return $this; 49 | } 50 | 51 | /** 52 | * @param Medium $medium 53 | */ 54 | public function printTo(Medium $medium) 55 | { 56 | $medium->printHtml($this->body, $this->pdfOptions); 57 | } 58 | 59 | /** 60 | * Set HTTP response code 61 | * 62 | * @param int $code 63 | * @return void 64 | */ 65 | public function setHttpResponseCode($code) 66 | { 67 | } 68 | 69 | /** 70 | * Get HTTP response code 71 | * 72 | * @return int 73 | */ 74 | public function getHttpResponseCode() 75 | { 76 | return 200; 77 | } 78 | 79 | /** 80 | * Set a header 81 | * 82 | * If $replace is true, replaces any headers already defined with that $name. 83 | * 84 | * @param string $name 85 | * @param string $value 86 | * @param boolean $replace 87 | * @return self 88 | */ 89 | public function setHeader($name, $value, $replace = false) 90 | { 91 | return $this; 92 | } 93 | 94 | /** 95 | * Get header value by name 96 | * 97 | * Returns first found header by passed name. 98 | * If header with specified name was not found returns false. 99 | * 100 | * @param string $name 101 | * @return \Zend\Http\Header\HeaderInterface|bool 102 | */ 103 | public function getHeader($name) 104 | { 105 | return false; 106 | } 107 | 108 | /** 109 | * Remove header by name from header stack 110 | * 111 | * @param string $name 112 | * @return self 113 | */ 114 | public function clearHeader($name) 115 | { 116 | return $this; 117 | } 118 | 119 | /** 120 | * Allow granular setting of HTTP response status code, version and phrase 121 | * 122 | * For example, a HTTP response as the following: 123 | * HTTP 200 1.1 Your response has been served 124 | * Can be set with the arguments 125 | * $httpCode = 200 126 | * $version = 1.1 127 | * $phrase = 'Your response has been served' 128 | * 129 | * @param int|string $httpCode 130 | * @param null|int|string $version 131 | * @param null|string $phrase 132 | * @return self 133 | */ 134 | public function setStatusHeader($httpCode, $version = null, $phrase = null) 135 | { 136 | return $this; 137 | } 138 | 139 | /** 140 | * Set the response body to the given value 141 | * 142 | * Any previously set contents will be replaced by the new content. 143 | * 144 | * @param string $value 145 | * @return self 146 | */ 147 | public function setBody($value) 148 | { 149 | return $this; 150 | } 151 | 152 | /** 153 | * Set redirect URL 154 | * 155 | * Sets Location header and response code. Forces replacement of any prior redirects. 156 | * 157 | * @param string $url 158 | * @param int $code 159 | * @return self 160 | */ 161 | public function setRedirect($url, $code = 302) 162 | { 163 | return $this; 164 | } 165 | } -------------------------------------------------------------------------------- /Model/View/PageResultWithoutHttp.php: -------------------------------------------------------------------------------- 1 | render($response); 22 | 23 | $this->eventManager->dispatch('layout_render_before'); 24 | \Magento\Framework\Profiler::stop('layout_render'); 25 | \Magento\Framework\Profiler::stop('LAYOUT'); 26 | return $this; 27 | } 28 | 29 | /** 30 | * @todo replace $this->pageConfigRenderer with specialized renderer to replace http:// URLs with file:// URLs 31 | * @todo for optimized rendering of local resources 32 | * @todo (similar to \Magento\Developer\Model\View\Page\Config\ClientSideLessCompilation\Renderer) 33 | */ 34 | protected function initPageConfigReader() 35 | { 36 | parent::initPageConfigReader(); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /Model/View/PdfResult.php: -------------------------------------------------------------------------------- 1 | pdfResponseFactory = $pdfResponseFactory; 40 | $this->pdfFactory = $pdfFactory; 41 | $this->pdfGlobalOptions = $pdfOptionsFactory->create(); 42 | $this->pdfPageOptions = $pdfOptionsFactory->create(); 43 | $this->pageResult = $pageResult; 44 | } 45 | 46 | /** 47 | * Set filename for download. If null, Content-Disposition header is not sent, 48 | * i.e. download will not be forced and PDF may be displayed in browser 49 | * 50 | * @param $filename 51 | */ 52 | public function setFilename($filename) 53 | { 54 | $this->filename = $filename; 55 | } 56 | 57 | public function addGlobalOptions(Options $options) 58 | { 59 | $this->pdfGlobalOptions = $this->pdfGlobalOptions->merge($options); 60 | } 61 | 62 | public function addPageOptions(Options $options) 63 | { 64 | $this->pdfPageOptions = $this->pdfPageOptions->merge($options); 65 | } 66 | 67 | /** 68 | * Renders directly to HTTP response 69 | * 70 | * @param HttpResponseInterface $response 71 | * @return $this 72 | */ 73 | protected function render(HttpResponseInterface $response) 74 | { 75 | $this->preparePdfResponse($response, $this->renderPdf()); 76 | 77 | return $this; 78 | } 79 | 80 | /** 81 | * @return PdfResponse 82 | */ 83 | public function renderSourceDocument() 84 | { 85 | /** @var PdfResponse $pdfResponse */ 86 | $pdfResponse = $this->pdfResponseFactory->create([ 87 | PdfResponse::PARAM_OPTIONS => $this->pdfPageOptions 88 | ]); 89 | /* 90 | * As of Magento 2.1, addDefaultHandle() must be called after instantiating a layout result, 91 | * see \Magento\Framework\Controller\ResultFactory::create() 92 | * 93 | * But since it is marked as a temporary solution, this might change in a later Magento release 94 | */ 95 | $this->pageResult->addDefaultHandle(); 96 | 97 | $this->pageResult->renderNonHttpResult($pdfResponse); 98 | return $pdfResponse; 99 | } 100 | 101 | /** 102 | * @return string 103 | */ 104 | protected function renderPdf() 105 | { 106 | $pdf = $this->pdfFactory->create(); 107 | $pdf->addOptions($this->pdfGlobalOptions); 108 | $pdfResponse = $this->renderSourceDocument(); 109 | $pdf->appendContent($pdfResponse); 110 | $body = $pdf->file()->toString(); 111 | return $body; 112 | } 113 | 114 | /** 115 | * @param Framework\App\Response\Http $response 116 | * @param $body 117 | * @return void 118 | */ 119 | protected function preparePdfResponse(Framework\App\Response\Http $response, $body) 120 | { 121 | $response->setHeader('Content-type', 'application/pdf', true); 122 | $response->setHeader('Content-Length', \strlen($body)); 123 | $response->setHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0', true); 124 | $response->setHeader('Last-Modified', \date('r'), true); 125 | if (null !== $this->filename) { 126 | $response->setHeader('Content-Disposition', 'attachment; filename="' . $this->filename . '"', true); 127 | } 128 | $response->appendBody($body); 129 | } 130 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Magento 2 PDF generator 2 | 3 | [![Project Status: Abandoned – Initial development has started, but there has not yet been a stable, usable release; the project has been abandoned and the author(s) do not intend on continuing development.](http://www.repostatus.org/badges/latest/abandoned.svg)](http://www.repostatus.org/#abandoned) 4 | 5 | Magento 2 module to ease the pdf generation using [wkhtmltopdf](https://wkhtmltopdf.org/) features 6 | 7 | ## Installation 8 | 9 | ``` 10 | composer require "staempfli/magento2-module-pdf":"~1.0" 11 | ``` 12 | 13 | ## Setup 14 | 15 | ### Install wkhtmltopdf 16 | This module needs [wkhtmltopdf](https://wkhtmltopdf.org/) installed on your computer. You can download and install it from here: 17 | 18 | * [https://wkhtmltopdf.org/downloads.html](https://wkhtmltopdf.org/downloads.html) 19 | 20 | NOTE: Do not install it using `apt-get` on Linux systems. See [troubleshooting](#troubleshooting) section for more info. 21 | 22 | ### Module configuration 23 | 24 | `Stores > Configuration > Advanced PDF Generation` 25 | 26 | ![Admin Configuration](docs/img/config.png) 27 | 28 | 29 | ## Usage 30 | 31 | This module can generate a PDF from any `frontControllerAction` 32 | 33 | 1. Create you Controller path with the correspinding Blocks and `.phtml` templates 34 | 35 | ``` 36 | 37 | 38 | 39 | 40 | 41 | 42 | ``` 43 | 44 | 2. Return pdf instance on Controller 45 | 46 | ``` 47 | renderPdfResult(); 59 | } 60 | 61 | protected function renderPdfResult() 62 | { 63 | /** @var PdfResult $result */ 64 | $result = $this->resultFactory->create(PdfResult::TYPE); 65 | $result->addGlobalOptions( 66 | new PdfOptions( 67 | [ 68 | PdfOptions::KEY_GLOBAL_TITLE => __('Return PDF'), 69 | PdfOptions::KEY_PAGE_ENCODING => PdfOptions::ENCODING_UTF_8, 70 | PdfOptions::KEY_GLOBAL_ORIENTATION => PdfOptions::ORIENTATION_PORTRAIT, 71 | PdfOptions::FLAG_PAGE_PRINT_MEDIA_TYPE, 72 | ] 73 | ) 74 | ); 75 | $result->addPageOptions( 76 | new PdfOptions( 77 | [ 78 | PdfOptions::KEY_PAGE_COOKIES => ${'_COOKIE'}, 79 | ] 80 | ) 81 | ); 82 | return $result; 83 | } 84 | } 85 | ``` 86 | 87 | ### Header and Footer 88 | 89 | Header and footer html can be added as follows: 90 | 91 | 1. Create your `header.html` and `footer.html` files inside your Module template dir 92 | 93 | ``` 94 | 95 | 96 | 97 | 98 | Header text 99 | 100 | 101 | ``` 102 | ``` 103 | 104 | 105 | 106 | 107 | Footer text 108 | 109 | 110 | ``` 111 | 112 | 2. Attach those files to the Controller pdf generation 113 | 114 | ``` 115 | templateResolver = $templateResolver; 136 | } 137 | 138 | public function execute() 139 | { 140 | return $this->renderPdfResult(); 141 | } 142 | 143 | protected function renderPdfResult() 144 | { 145 | /** @var PdfResult $result */ 146 | $result = $this->resultFactory->create(PdfResult::TYPE); 147 | $result->addGlobalOptions( 148 | new PdfOptions( 149 | [ 150 | PdfOptions::KEY_GLOBAL_TITLE => __('Return PDF'), 151 | PdfOptions::KEY_PAGE_ENCODING => PdfOptions::ENCODING_UTF_8, 152 | PdfOptions::KEY_GLOBAL_ORIENTATION => PdfOptions::ORIENTATION_PORTRAIT, 153 | PdfOptions::FLAG_PAGE_PRINT_MEDIA_TYPE, 154 | PdfOptions::KEY_PAGE_HEADER_SPACING => "10", 155 | ] 156 | ) 157 | ); 158 | $result->addPageOptions( 159 | new PdfOptions( 160 | [ 161 | PdfOptions::KEY_PAGE_COOKIES => ${'_COOKIE'}, 162 | PdfOptions::KEY_PAGE_HEADER_HTML_URL => $this->templateResolver 163 | ->getTemplateFileName('Vendor_Package::pdf/header.html'), 164 | PdfOptions::KEY_PAGE_FOOTER_HTML_URL => $this->templateResolver 165 | ->getTemplateFileName('Vendor_ Package::pdf/footer.html'), 166 | ] 167 | ) 168 | ); 169 | return $result; 170 | } 171 | } 172 | ``` 173 | 174 | **NOTE:** Only the header or only the footer as `html` is not possible, either both or none. Otherwise top and bottom margins are ignored. 175 | 176 | ## Troubleshooting 177 | 178 | ### The switch --print-media, is not support using unpatched qt: 179 | 180 | * `wkhtmltopdf` should not be installed via apt-get. See: 181 | * http://stackoverflow.com/questions/18758589/wkhtmltopdf-installation-error-on-ubuntu 182 | 183 | ### Tiny or very small output on Mac: 184 | 185 | * It seems to be a bug on `wkhtmltopdf` version 0.12.4. It can be fixed by installing 0.12.3 186 | * https://stackoverflow.com/questions/40814680/wkhtmltopdf-generates-tiny-output-on-mac 187 | 188 | ## Requirements 189 | - PHP >= 7.0.* 190 | - Magento >= 2.1.* 191 | 192 | ## Support 193 | If you have any issues with this extension, open an issue on [GitHub](https://github.com/staempfli/magento2-module-pdf/issues). 194 | 195 | ## Contribution 196 | Any contribution is highly appreciated. The best way to contribute code is to open a [pull request on GitHub](https://help.github.com/articles/using-pull-requests). 197 | 198 | ## Developer 199 | Staempfli Webteam, [Fabian Schmengler, integer_net](https://github.com/schmengler) and all other [contributors](https://github.com/staempfli/magento2-module-pdf/contributors) 200 | 201 | ## License 202 | [Open Software License ("OSL") v. 3.0](https://opensource.org/licenses/OSL-3.0) 203 | 204 | ## Copyright 205 | (c) 2018, Stämpfli AG 206 | -------------------------------------------------------------------------------- /Service/FakePdfEngine.php: -------------------------------------------------------------------------------- 1 | fakePdfFile = new FakePdfFile(); 35 | } 36 | 37 | public function addPage($html, Options $options) 38 | { 39 | $this->pages[] = [$html, $options]; 40 | } 41 | 42 | public function addCover($html, Options $options) 43 | { 44 | $this->cover = [$html, $options]; 45 | } 46 | 47 | public function addTableOfContents(Options $options) 48 | { 49 | $this->tableOfContents = $options; 50 | } 51 | 52 | /** 53 | * @param Options $globalOptions 54 | * @param Medium $cover 55 | * @param Medium[] ...$pages 56 | * @return FakePdfFile 57 | */ 58 | public function generatePdf(Options $globalOptions) 59 | { 60 | $this->globalOptions = $globalOptions; 61 | $this->fakePdfFile->isGenerated = true; 62 | return $this->fakePdfFile; 63 | } 64 | } -------------------------------------------------------------------------------- /Service/FakePdfFile.php: -------------------------------------------------------------------------------- 1 | contents = $contents; 23 | } 24 | 25 | public function saveAs($path) 26 | { 27 | \file_put_contents($path, $this->contents); 28 | } 29 | 30 | public function send() 31 | { 32 | $this->isSent = true; 33 | } 34 | 35 | public function toString() 36 | { 37 | return $this->contents; 38 | } 39 | 40 | /** 41 | * @return bool 42 | */ 43 | public function isSent() 44 | { 45 | return $this->isSent; 46 | } 47 | } -------------------------------------------------------------------------------- /Service/FakeSourceDocument.php: -------------------------------------------------------------------------------- 1 | html = $html; 24 | $this->options = $options; 25 | } 26 | 27 | /** 28 | * @param Medium $medium 29 | * @return void 30 | */ 31 | public function printTo(Medium $medium) 32 | { 33 | $medium->printHtml($this->html, $this->options); 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /Service/Pdf.php: -------------------------------------------------------------------------------- 1 | engine = $engine; 26 | $this->options = new PdfOptions(); 27 | } 28 | 29 | /** 30 | * Replace options 31 | * 32 | * @param Options $options 33 | */ 34 | public function setOptions(Options $options) 35 | { 36 | $this->options = $options; 37 | } 38 | 39 | /** 40 | * Add/override options 41 | * 42 | * @param Options $options 43 | */ 44 | public function addOptions(Options $options) 45 | { 46 | $this->options = $this->options->merge($options); 47 | } 48 | 49 | /** 50 | * All options that can be specified for a page object can also be specified for 51 | * a toc, further more the options from the TOC Options section can also be 52 | * applied. The table of content is generated via XSLT which means that it can be 53 | * styled to look however you want it to look. To get an aide of how to do this 54 | * you can dump the default xslt document by supplying the 55 | * --dump-default-toc-xsl, and the outline it works on by supplying 56 | * --dump-outline, see the Outline Options section. 57 | * 58 | * @see http://wkhtmltopdf.org/usage/wkhtmltopdf.txt 59 | * @param Options $tocOptions 60 | */ 61 | public function appendTableOfContents(Options $tocOptions) 62 | { 63 | $toc = new PdfTableOfContents($this->engine); 64 | $toc->printToc($tocOptions); 65 | } 66 | 67 | /** 68 | * A page object puts the content of a singe webpage into the output document. 69 | * 70 | * @see http://wkhtmltopdf.org/usage/wkhtmltopdf.txt 71 | * @param SourceDocument $source 72 | */ 73 | public function appendContent(SourceDocument $source) 74 | { 75 | $source->printTo(new PdfAppendContent($this->engine)); 76 | } 77 | 78 | /** 79 | * A cover object puts the content of a singe webpage into the output document, 80 | * the page does not appear in the table of content, and does not have headers 81 | * and footers. 82 | * 83 | * @see http://wkhtmltopdf.org/usage/wkhtmltopdf.txt 84 | * @param SourceDocument $source 85 | */ 86 | public function appendCover(SourceDocument $source) 87 | { 88 | $source->printTo(new PdfCover($this->engine)); 89 | } 90 | /** 91 | * @return PdfFile 92 | */ 93 | public function file() 94 | { 95 | return $this->engine->generatePdf($this->options); 96 | } 97 | } -------------------------------------------------------------------------------- /Service/PdfAppendContent.php: -------------------------------------------------------------------------------- 1 | pdfEngine = $pdfEngine; 22 | } 23 | 24 | public function printHtml($html, Options $options) 25 | { 26 | $this->pdfEngine->addPage($html, $options); 27 | } 28 | } -------------------------------------------------------------------------------- /Service/PdfCover.php: -------------------------------------------------------------------------------- 1 | pdfEngine = $pdfEngine; 22 | } 23 | 24 | public function printHtml($html, Options $options) 25 | { 26 | $this->pdfEngine->addCover($html, $options); 27 | } 28 | } -------------------------------------------------------------------------------- /Service/PdfOptions.php: -------------------------------------------------------------------------------- 1 | value pair. Example: 16 | * 17 | * [ 18 | * PdfOptions::FLAG_PAGE_PRINT_MEDIA_TYPE, 19 | * PdfOptions::KEY_PAGE_NUMBER_OFFSET => -1, 20 | * ] 21 | * 22 | */ 23 | final class PdfOptions extends ArrayObject implements Options 24 | { 25 | // global options 26 | const KEY_GLOBAL_BINARY = 'binary'; 27 | const KEY_GLOBAL_VERSION9 = 'version9'; 28 | const KEY_GLOBAL_TMP_DIR = 'tmpDir'; 29 | const KEY_GLOBAL_IGNORE_WARNINGS = 'ignoreWarnings'; 30 | /** 31 | * Additional CLI options, as array. See KEY_CLI_OPTIONS_* constants 32 | * 33 | * For more CLI options, refer to the public properties of 34 | * 35 | * \mikehaertl\shellcommand\Command 36 | */ 37 | const KEY_GLOBAL_CLI_OPTIONS = 'commandOptions'; 38 | 39 | const KEY_CLI_OPTIONS_ESCAPE_ARGS = 'escapeArgs'; 40 | const KEY_CLI_OPTIONS_USE_EXEC = 'useExec'; 41 | const KEY_CLI_OPTIONS_USE_XVFB_RUN = 'enableXvfb'; 42 | const KEY_CLI_OPTIONS_XVFB_RUN_BINARY = 'xvfbRunBinary'; 43 | const KEY_CLI_OPTIONS_XVFB_RUN_OPTIONS = 'xvfbRunOptions'; 44 | 45 | /** 46 | * The title of the generated pdf file (The title of the first document is used if not specified) 47 | */ 48 | const KEY_GLOBAL_TITLE = 'title'; 49 | /** 50 | * Set paper size to: A4, Letter, etc. (default A4) 51 | */ 52 | const KEY_GLOBAL_PAGE_SIZE = 'page-size'; 53 | /** 54 | * Page height in mm, can be used instead of PAGE_SIZE for more control 55 | */ 56 | const KEY_GLOBAL_PAGE_HEIGHT = 'page-height'; 57 | /** 58 | * Page width in mm, can be used instead of PAGE_SIZE for more control 59 | */ 60 | const KEY_GLOBAL_PAGE_WIDTH = 'page-width'; 61 | /** 62 | * Set orientation to Landscape or Portrait (default Portrait) 63 | */ 64 | const KEY_GLOBAL_ORIENTATION = 'orientation'; 65 | /** 66 | * Bottom margin in mm (default 10) 67 | */ 68 | const KEY_GLOBAL_MARGIN_BOTTOM = 'margin-bottom'; 69 | /** 70 | * Left margin in mm (default 10) 71 | */ 72 | const KEY_GLOBAL_MARGIN_LEFT = 'margin-left'; 73 | /** 74 | * Right margin in mm (default 10) 75 | */ 76 | const KEY_GLOBAL_MARGIN_RIGHT = 'margin-right'; 77 | /** 78 | * Top margin in mm (default 10) 79 | */ 80 | const KEY_GLOBAL_MARGIN_TOP = 'margin-top'; 81 | /** 82 | * Put an outline (bookmarks) into the pdf (default) 83 | */ 84 | const FLAG_GLOBAL_OUTLINE = 'outline'; 85 | /** 86 | * Do not put an outline (bookmarks) into the pdf 87 | */ 88 | const FLAG_GLOBAL_NO_OUTLINE = 'no-outline'; 89 | /** 90 | * Set the depth of the outline (default 4) 91 | */ 92 | const KEY_GLOBAL_OUTLINE_DEPTH = 'outline-depth'; 93 | /** 94 | * Collate when printing multiple copies (default) 95 | */ 96 | const FLAG_GLOBAL_COLLATE = 'collate'; 97 | /** 98 | * Do not collate when printing multiple copies 99 | */ 100 | const FLAG_GLOBAL_NO_COLLATE = 'no-collate'; 101 | /** 102 | * Read and write cookies from and to the supplied cookie jar file 103 | */ 104 | const KEY_GLOBAL_COOKIE_JAR = 'cookie-jar'; 105 | /** 106 | * Number of copies to print into the pdf file (default 1) 107 | */ 108 | const KEY_GLOBAL_COPIES = 'copies'; 109 | /** 110 | * PDF will be generated in grayscale 111 | */ 112 | const FLAG_GLOBAL_GRAYSCALE = 'grayscale'; 113 | /** 114 | * When embedding images scale them down to this dpi (default 600) 115 | */ 116 | const KEY_GLOBAL_IMAGE_DPI = 'image-dpi'; 117 | /** 118 | * When jpeg compressing images use this quality (default 94) 119 | */ 120 | const KEY_GLOBAL_IMAGE_QUALITY = 'image-quality'; 121 | /** 122 | * Generates lower quality pdf/ps. Useful to shrink the result document space 123 | */ 124 | const FLAG_GLOBAL_LOWQUALITY = 'lowquality'; 125 | /** 126 | * Do not use lossless compression on pdf objects 127 | */ 128 | const FLAG_GLOBAL_NO_PDF_COMPRESSION = 'no-pdf-compression'; 129 | 130 | // page options 131 | /** 132 | * HTML header, should be URL or filename 133 | */ 134 | const KEY_PAGE_HEADER_HTML_URL = 'header-html'; 135 | /** 136 | * Header text printed in the top margin (not visible if margin is 0) 137 | */ 138 | const KEY_PAGE_HEADER_TEXT_LEFT = 'header-left'; 139 | /** 140 | * Header text printed in the top margin (not visible if margin is 0) 141 | */ 142 | const KEY_PAGE_HEADER_TEXT_RIGHT = 'header-right'; 143 | /** 144 | * Spacing between header and content in mm (default 0) 145 | */ 146 | const KEY_PAGE_HEADER_SPACING = 'header-spacing'; 147 | /** 148 | * Display line below the header 149 | */ 150 | const FLAG_PAGE_HEADER_LINE = 'header-line'; 151 | /** 152 | * Do not display line below the header (default) 153 | */ 154 | const FLAG_PAGE_NO_HEADER_LINE = 'no-header-line'; 155 | /** 156 | * Centered header text 157 | */ 158 | const KEY_PAGE_HEADER_CENTER = 'header-center'; 159 | /** 160 | * Set header font name (default Arial) 161 | */ 162 | const KEY_PAGE_HEADER_FONT_NAME = 'header-font-name'; 163 | /** 164 | * Set header font size (default 12) 165 | */ 166 | const KEY_PAGE_HEADER_FONT_SIZE = 'header-font-size'; 167 | /** 168 | * HTML footer, should be URL or filename 169 | */ 170 | const KEY_PAGE_FOOTER_HTML_URL = 'footer-html'; 171 | /** 172 | * Footer text printed in the bottom margin (not visible if margin is 0) 173 | */ 174 | const KEY_PAGE_FOOTER_TEXT_LEFT = 'footer-left'; 175 | /** 176 | * Footer text printed in the bottom margin (not visible if margin is 0) 177 | */ 178 | const KEY_PAGE_FOOTER_TEXT_RIGHT = 'footer-right'; 179 | /** 180 | * Spacing between footer and content in mm (default 0) 181 | */ 182 | const KEY_PAGE_FOOTER_SPACING = 'footer-spacing'; 183 | /** 184 | * Display line above the footer 185 | */ 186 | const FLAG_PAGE_FOOTER_LINE = 'footer-line'; 187 | /** 188 | * Do not display line above the footer (default 189 | */ 190 | const FLAG_PAGE_NO_FOOTER_LINE = 'no-footer-line'; 191 | /** 192 | * Centered footer text 193 | */ 194 | const KEY_PAGE_FOOTER_CENTER = 'footer-center'; 195 | /** 196 | * Set footer font name (default Arial) 197 | */ 198 | const KEY_PAGE_FOOTER_FONT_NAME = 'footer-font-name'; 199 | /** 200 | * Set footer font size (default 12) 201 | */ 202 | const KEY_PAGE_FOOTER_FONT_SIZE = 'footer-font-size'; 203 | /** 204 | * Array of additional replacements. ["foo" => "bar"] will replace "[foo]" with "bar" 205 | */ 206 | const KEY_PAGE_HEADER_FOOTER_REPLACE_MAP = 'replace'; 207 | /** 208 | * Run these additional javascript after the page is done loading (array of JS files) 209 | */ 210 | const KEY_PAGE_RUN_SCRIPTS = 'run-script'; 211 | /** 212 | * Set additional cookies (array of URL encoded values) 213 | */ 214 | const KEY_PAGE_COOKIES = 'cookie'; 215 | /** 216 | * Set the default text encoding 217 | */ 218 | const KEY_PAGE_ENCODING = 'encoding'; 219 | /** 220 | * Use print media-type instead of screen 221 | */ 222 | const FLAG_PAGE_PRINT_MEDIA_TYPE = 'print-media-type'; 223 | /** 224 | * Do not use print media-type instead of screen (default) 225 | */ 226 | const FLAG_PAGE_NO_PRINT_MEDIA_TYPE = 'no-print-media-type'; 227 | /** 228 | * HTTP Authentication username 229 | */ 230 | const KEY_PAGE_HTTP_AUTH_USER = 'username'; 231 | /** 232 | * HTTP Authentication password 233 | */ 234 | const KEY_PAGE_HTTP_AUTH_PASSWORD = 'password'; 235 | /** 236 | * Allowed conversion of a local file to read in other local files. (default) 237 | */ 238 | const FLAG_PAGE_ENABLE_LOCAL_FILE_ACCESS = 'enable-local-file-access'; 239 | /** 240 | * Do not allow conversion of a local file to read in other local files, 241 | * unless explicitly allowed with FLAG_PAGE_ALLOW 242 | */ 243 | const FLAG_PAGE_DISABLE_LOCAL_FILE_ACCESS = 'disable-local-file-access'; 244 | /** 245 | * Allow the file or files from the specified folder to be loaded (array of paths) 246 | */ 247 | const FLAG_PAGE_ALLOW = 'allow'; 248 | /** 249 | * Adjust the starting page number (default 0) 250 | */ 251 | const KEY_PAGE_NUMBER_OFFSET = 'page-offset'; 252 | 253 | /** 254 | * Do print background (default) 255 | */ 256 | const FLAG_PAGE_BACKGROUND = 'background'; 257 | /** 258 | * Do not print background 259 | */ 260 | const FLAG_PAGE_NO_BACKGROUND = 'no-background'; 261 | /** 262 | * Web cache directory 263 | */ 264 | const KEY_PAGE_CACHE_DIR = 'cache-dir'; 265 | /** 266 | * Use this SVG file when rendering checked checkboxes 267 | */ 268 | const KEY_PAGE_CHECKBOX_CHECKED_SVG = 'checkbox-checked-svg'; 269 | /** 270 | * Use this SVG file when rendering unchecked checkboxes 271 | */ 272 | const KEY_PAGE_CHECKBOX_SVG = 'checkbox-svg'; 273 | /** 274 | * Set additional HTTP headers (array with names as keys and values as values) 275 | */ 276 | const KEY_PAGE_CUSTOM_HEADER = 'custom-header'; 277 | /** 278 | * Add HTTP headers specified by KEY_PAGE_CUSTOM_HEADER for each resource request. 279 | */ 280 | const FLAG_PAGE_CUSTOM_HEADER_PROPAGATION = 'custom-header-propagation'; 281 | /** 282 | * Do not add HTTP headers specified by KEY_PAGE_CUSTOM_HEADER for each resource request. 283 | */ 284 | const FLAG_PAGE_NO_CUSTOM_HEADER_PROPAGATION = 'no-custom-header-propagation'; 285 | /** 286 | * Show javascript debugging output 287 | */ 288 | const FLAG_PAGE_DEBUG_JAVASCRIPT = 'debug-javascript'; 289 | /** 290 | * Do not show javascript debugging output (default) 291 | */ 292 | const FLAG_PAGE_NO_DEBUG_JAVASCRIPT = 'no-debug-javascript'; 293 | /** 294 | * Add a default header, with the name of the page to the left, and the page number to the right, this is short for: 295 | * --header-left='[webpage]' 296 | * --header-right='[page]/[toPage]' 297 | * --top 2cm 298 | * --header-line 299 | */ 300 | const FLAG_PAGE_DEFAULT_HEADER = 'default-header'; 301 | /** 302 | * Do not make links to remote web pages 303 | */ 304 | const FLAG_PAGE_DISABLE_EXTERNAL_LINKS = 'disable-external-links'; 305 | /** 306 | * Make links to remote web pages (default) 307 | */ 308 | const FLAG_PAGE_ENABLE_EXTERNAL_LINKS = 'enable-external-links'; 309 | /** 310 | * Do not turn HTML form fields into pdf form fields (default) 311 | */ 312 | const FLAG_PAGE_DISABLE_FORMS = 'disable-forms'; 313 | /** 314 | * Turn HTML form fields into pdf form fields 315 | */ 316 | const FLAG_PAGE_ENABLE_FORMS = 'enable-forms'; 317 | /** 318 | * Do load or print images (default) 319 | */ 320 | const FLAG_PAGE_IMAGES = 'images'; 321 | /** 322 | * Do not load or print images 323 | */ 324 | const FLAG_PAGE_NO_IMAGES = 'no-images'; 325 | /** 326 | * Do not make local links 327 | */ 328 | const FLAG_PAGE_DISABLE_INTERNAL_LINKS = 'disable-internal-links'; 329 | /** 330 | * Make local links (default) 331 | */ 332 | const FLAG_PAGE_ENABLE_INTERNAL_LINKS = 'enable-internal-links'; 333 | /** 334 | * Do not allow web pages to run javascript 335 | */ 336 | const FLAG_PAGE_DISABLE_JAVASCRIPT = 'disable-javascript'; 337 | /** 338 | * Do allow web pages to run javascript (default) 339 | */ 340 | const FLAG_PAGE_ENABLE_JAVASCRIPT = 'enable-javascript'; 341 | /** 342 | * Wait some milliseconds for javascript finish (default 200) 343 | */ 344 | const KEY_PAGE_JAVASCRIPT_DELAY = 'javascript-delay'; 345 | /** 346 | * Specify how to handle pages that fail to load: abort, ignore or skip (default abort) 347 | */ 348 | const KEY_PAGE_LOAD_ERROR_HANDLING = 'load-error-handling'; 349 | /** 350 | * Specify how to handle media files that fail to load: abort, ignore or skip (default ignore) 351 | */ 352 | const KEY_PAGE_LOAD_MEDIA_ERROR_HANDLING = 'load-media-error-handling'; 353 | /** 354 | * Minimum font size 355 | */ 356 | const KEY_PAGE_MINIMUM_FONT_SIZE = 'minimum-font-size'; 357 | /** 358 | * Do not include the page in the table of contents and outlines 359 | */ 360 | const FLAG_PAGE_EXCLUDE_FROM_OUTLINE = 'exclude-from-outline'; 361 | /** 362 | * Include the page in the table of contents and outlines (default) 363 | */ 364 | const FLAG_PAGE_INCLUDE_IN_OUTLINE = 'include-in-outline'; 365 | /** 366 | * Disable installed plugins (default) 367 | */ 368 | const FLAG_PAGE_DISABLE_PLUGINS = 'disable-plugins'; 369 | /** 370 | * Enable installed plugins (plugins will likely not work) 371 | */ 372 | const FLAG_PAGE_ENABLE_PLUGINS = 'enable-plugins'; 373 | /** 374 | * Add additional post fields (array) 375 | */ 376 | const KEY_PAGE_POST = 'post'; 377 | /** 378 | * Post additional files (array of paths) 379 | */ 380 | const KEY_PAGE_POST_FILE = 'post-file'; 381 | /** 382 | * Use a proxy 383 | */ 384 | const KEY_PAGE_PROXY = 'proxy'; 385 | /** 386 | * Use this SVG file when rendering checked radiobuttons 387 | */ 388 | const KEY_PAGE_RADIOBUTTON_CHECKED_SVG = 'radiobutton-checked-svg'; 389 | /** 390 | * Use this SVG file when rendering unchecked radiobuttons 391 | */ 392 | const KEY_PAGE_RADIOBUTTON_SVG = 'radiobutton-svg'; 393 | /** 394 | * Disable the intelligent shrinking strategy used by WebKit that makes the pixel/dpi ratio none constant 395 | */ 396 | const FLAG_PAGE_DISABLE_SMART_SHRINKING = 'disable-smart-shrinking'; 397 | /** 398 | * Enable the intelligent shrinking strategy used by WebKit that makes the pixel/dpi ratio none constant (default) 399 | */ 400 | const FLAG_PAGE_ENABLE_SMART_SHRINKING = 'enable-smart-shrinking'; 401 | /** 402 | * Stop slow running javascripts (default) 403 | */ 404 | const FLAG_PAGE_STOP_SLOW_SCRIPTS = 'stop-slow-scripts'; 405 | /** 406 | * Do not Stop slow running javascripts 407 | */ 408 | const FLAG_PAGE_NO_STOP_SLOW_SCRIPTS = 'no-stop-slow-scripts'; 409 | /** 410 | * Do not link from section header to toc (default) 411 | */ 412 | const FLAG_PAGE_DISABLE_TOC_BACK_LINKS = 'disable-toc-back-links'; 413 | /** 414 | * Link from section header to toc 415 | */ 416 | const FLAG_PAGE_ENABLE_TOC_BACK_LINKS = 'enable-toc-back-links'; 417 | /** 418 | * Specify a user style sheet, to load with every page 419 | */ 420 | const KEY_PAGE_USER_STYLE_SHEET = 'user-style-sheet'; 421 | /** 422 | * Set viewport size if you have custom scrollbars or css attribute overflow to emulate window size 423 | */ 424 | const KEY_PAGE_VIEWPORT_SIZE = 'viewport-size'; 425 | /** 426 | * Wait until window.status is equal to this string before rendering page 427 | */ 428 | const KEY_PAGE_WINDOW_STATUS = 'window-status'; 429 | /** 430 | * Use this zoom factor (default 1) 431 | */ 432 | const KEY_PAGE_ZOOM = 'zoom'; 433 | 434 | // toc options 435 | /** 436 | * The header text of the toc (default "Table of Contents") 437 | */ 438 | const KEY_TOC_HEADER_TEXT = 'toc-header-text'; 439 | /** 440 | * Use the supplied xsl style sheet for printing the table of content 441 | */ 442 | const KEY_TOC_XSL_STYLE_SHEET = 'xsl-style-sheet'; 443 | /** 444 | * For each level of headings in the toc indent by this length (default 1em) 445 | */ 446 | const FLAG_TOC_LEVEL_INDENTATION = 'toc-level-indentation'; 447 | /** 448 | * Do not use dotted lines in the toc 449 | */ 450 | const FLAG_TOC_DISABLE_DOTTED_LINES = 'disable-dotted-lines'; 451 | /** 452 | * Do not link from toc to sections 453 | */ 454 | const FLAG_TOC_DISABLE_LINKS = 'disable-toc-links'; 455 | 456 | // option values 457 | const PAGE_SIZE_DIN_A4 = 'A4'; 458 | const PAGE_SIZE_LETTER = 'letter'; 459 | const ORIENTATION_PORTRAIT = 'Portrait'; 460 | const ORIENTATION_LANDSCAPE = 'Landscape'; 461 | const ENCODING_UTF_8 = 'UTF-8'; 462 | const LOAD_ON_ERROR_ABORT = 'abort'; 463 | const LOAD_ON_ERROR_IGNORE = 'ignore'; 464 | const LOAD_ON_ERROR_SKIP = 'skip'; 465 | /** 466 | * PdfOptions constructor. 467 | * 468 | * Overridden from ArrayObject for default input parameter. This prevents error 469 | * 470 | * Passed variable is not an array or object, using empty array instead 471 | * 472 | * while used with Magento 2 object manager 473 | * 474 | * @param array $input 475 | * @param int $flags 476 | * @param string $iteratorClass 477 | */ 478 | public function __construct($input = [], $flags = 0, $iteratorClass = \ArrayIterator::class) 479 | { 480 | parent::__construct($input, $flags, $iteratorClass); 481 | } 482 | 483 | /** 484 | * @param Options $newOptions 485 | * @return PdfOptions 486 | */ 487 | public function merge(Options $newOptions) 488 | { 489 | $merged = clone $this; 490 | foreach ($newOptions as $key => $value) { 491 | $merged[$key] = $value; 492 | } 493 | return $merged; 494 | } 495 | 496 | public function asArray() 497 | { 498 | return (array) $this; 499 | } 500 | 501 | } -------------------------------------------------------------------------------- /Service/PdfTableOfContents.php: -------------------------------------------------------------------------------- 1 | pdfEngine = $pdfEngine; 21 | } 22 | 23 | public function printToc(Options $options) 24 | { 25 | $this->pdfEngine->addTableOfContents($options); 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /Test/Integration/ConfigTest.php: -------------------------------------------------------------------------------- 1 | uri = 'backend/admin/system_config/edit'; 23 | $this->resource = 'Staempfli_Pdf::config_staempfli_pdf'; 24 | $this->getRequest()->setParam('section', 'staempfli_pdf'); 25 | } 26 | 27 | protected function setUp() 28 | { 29 | parent::setUp(); 30 | $this->objectManager = ObjectManager::getInstance(); 31 | $this->setUpRequest(); 32 | } 33 | /** 34 | * Overridden to make depends annotation work 35 | */ 36 | public function testAclHasAccess() 37 | { 38 | parent::testAclHasAccess(); 39 | // we do not pass the response as parameter to dependent test because PHPUnit would take ages 40 | // to go through the object tree, looking for mock objects. 41 | self::$lastResponse = $this->getResponse(); 42 | } 43 | /** 44 | * Overridden to check for redirect instead of "Forbidden" response 45 | */ 46 | public function testAclNoAccess() 47 | { 48 | if ($this->resource === null) { 49 | $this->markTestIncomplete('Acl test is not complete'); 50 | } 51 | $this->_objectManager->get('Magento\Framework\Acl\Builder') 52 | ->getAcl() 53 | ->deny(null, $this->resource); 54 | $this->dispatch($this->uri); 55 | $this->assertSame(302, $this->getResponse()->getHttpResponseCode()); 56 | $this->assertContains('/index.php/backend/admin/system_config/index/', $this->getResponse()->getHeader('location')->toString()); 57 | } 58 | /** 59 | * @depends testAclHasAccess 60 | */ 61 | public function testConfigSectionLoads() 62 | { 63 | $response = self::$lastResponse; 64 | $this->assertEquals(200, $response->getStatusCode(), 'HTTP Status Code'); 65 | } 66 | } -------------------------------------------------------------------------------- /Test/Integration/PdfResultTest.php: -------------------------------------------------------------------------------- 1 | objectManager = ObjectManager::getInstance(); 19 | $this->pdfResult = $this->objectManager->create(PdfResult::class); 20 | } 21 | 22 | /** 23 | * @magentoAppIsolation enabled 24 | * @magentoAppArea frontend 25 | */ 26 | public function testRenderPdfResponse() 27 | { 28 | /** @var HttpResponse $response */ 29 | $response = $this->objectManager->create(HttpResponse::class); 30 | $this->objectManager->get(Page::class)->addDefaultHandle(); 31 | $this->pdfResult->setFilename('testpdf.pdf'); 32 | $this->pdfResult->renderResult($response); 33 | 34 | $this->assertResponseHeader($response, 'Content-type', $this->equalTo('application/pdf')); 35 | $this->assertResponseHeader($response, 'Content-length', $this->greaterThan(1000), 'Content-length header should be at least 1K'); 36 | $this->assertResponseHeader($response, 'Cache-Control', $this->equalTo('must-revalidate, post-check=0, pre-check=0')); 37 | $this->assertResponseHeader($response, 'Last-Modified', $this->callback(function($lastModified) { 38 | return \strtotime($lastModified) > \time() - 10; 39 | }), 'Last-Modified header should be about right now'); 40 | $this->assertResponseHeader($response, 'Content-Disposition', $this->equalTo('attachment; filename="testpdf.pdf"')); 41 | $this->assertStringStartsWith('%PDF-', $response->getBody(), 'body should contain the PDF header'); 42 | } 43 | 44 | /** 45 | * @param HttpResponse $response 46 | * @param string $field 47 | * @param \PHPUnit_Framework_Constraint $constraint 48 | */ 49 | private function assertResponseHeader(HttpResponse $response, $field, \PHPUnit_Framework_Constraint $constraint, $message = '') 50 | { 51 | $this->assertNotFalse($response->getHeader($field), "{$field} header should be present"); 52 | $this->assertThat($response->getHeader($field)->getFieldValue(), $constraint, $message); 53 | } 54 | } -------------------------------------------------------------------------------- /Test/Integration/PdfTemplateTest.php: -------------------------------------------------------------------------------- 1 | objectManager = ObjectManager::getInstance(); 24 | $this->layout = $this->objectManager->get(Layout::class); 25 | } 26 | 27 | /** 28 | * @magentoAppIsolation enabled 29 | * @magentoAppArea frontend 30 | */ 31 | public function testPrint() 32 | { 33 | $fakePdfEngine = new FakePdfEngine(); 34 | /** @var PdfTemplate $pdfTemplate */ 35 | $pdfTemplate = $this->layout->createBlock(PdfTemplate::class, 'pdftest-container'); 36 | $pdfTemplate->addChild('pdftest-element', Text::class, ['text' => 'test']); 37 | $pdfTemplate->printTo(new PdfAppendContent($fakePdfEngine)); 38 | 39 | $this->assertEquals( 40 | [['test', new PdfOptions([])]], 41 | $fakePdfEngine->pages 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Test/Integration/WkPdfEngineTest.php: -------------------------------------------------------------------------------- 1 | objectManager = ObjectManager::getInstance(); 22 | $this->pdfFactory = $this->objectManager->create(PdfFactory::class); 23 | 24 | $wkhtmltopdfBinary = $this->getWkhtmltopdfBinary(); 25 | if (! $this->isExecutable($wkhtmltopdfBinary)) { 26 | $this->markTestSkipped( 27 | sprintf( 28 | 'wkhtmltopdf binary not found at %s. If it is installed in a different location, '. 29 | 'please adjust the configuration.', 30 | $wkhtmltopdfBinary 31 | ) 32 | ); 33 | } 34 | } 35 | public function testGeneratePdf() 36 | { 37 | $pdf = $this->pdfFactory->create(); 38 | $pdf->appendCover(new FakeSourceDocument('BIG COVER', new PdfOptions([]))); 39 | $pdf->appendTableOfContents(new PdfOptions([])); 40 | $pdf->appendContent(new FakeSourceDocument('print me!', new PdfOptions([]))); 41 | $this->assertStringStartsWith('%PDF-', $pdf->file()->toString(), 'binary string should contain the PDF header'); 42 | } 43 | 44 | public function testExceptionOnToString() 45 | { 46 | $this->setExpectedException(WkPdfException::class); 47 | $this->createInvalidPdf()->file()->toString(); 48 | } 49 | 50 | public function testExceptionOnSend() 51 | { 52 | $this->setExpectedException(WkPdfException::class); 53 | $this->createInvalidPdf()->file()->send(); 54 | } 55 | 56 | public function testExceptionOnSave() 57 | { 58 | $this->setExpectedException(WkPdfException::class); 59 | $this->createInvalidPdf()->file()->saveAs(\tempnam(\sys_get_temp_dir(), 'pdftest')); 60 | } 61 | 62 | /** 63 | * This test saves the generated file in tmp/staempfli_pdf.pdf for manual inspection 64 | */ 65 | public function testSaveGeneratedPdfFile() 66 | { 67 | /* 68 | * Direct HTML in header does not work, and with file I get 69 | * 70 | * > Error: Failed loading page file:///tmp/php_tmpfile_wjJkuX 71 | * 72 | * Restrict test to text headers 73 | */ 74 | //$headerHtmlFile = new File('I am a HTML header'); 75 | $pdf = $this->pdfFactory->create(); 76 | $pdf->addOptions(new PdfOptions( 77 | [ 78 | PdfOptions::KEY_GLOBAL_TITLE => 'TEST PDF', 79 | PdfOptions::KEY_PAGE_HEADER_SPACING => '10', 80 | PdfOptions::FLAG_PAGE_HEADER_LINE, 81 | PdfOptions::KEY_PAGE_HEADER_TEXT_LEFT => 'HEADER', 82 | PdfOptions::KEY_PAGE_HEADER_TEXT_RIGHT => 'Page [page]', 83 | PdfOptions::KEY_GLOBAL_IGNORE_WARNINGS => true, 84 | PdfOptions::KEY_PAGE_FOOTER_TEXT_LEFT => 'Hello, I am a footer.', 85 | PdfOptions::KEY_PAGE_FOOTER_TEXT_RIGHT => 'I am text based.' 86 | ] 87 | )); 88 | $pdf->appendCover(new FakeSourceDocument('BIG COVER', new PdfOptions([]))); 89 | $pdf->appendTableOfContents(new PdfOptions([ 90 | ])); 91 | $contentHtml = <<<'HTML' 92 | 93 | 94 | 95 | 96 | 97 |

Überschrift

eins

Überschrift

zwei

Unterüberschrift

drei 98 | HTML; 99 | $pdf->appendContent(new FakeSourceDocument($contentHtml, new PdfOptions([]))); 100 | $pdf->file()->saveAs('tmp/staempflitest.pdf'); 101 | $this->assertNotEmpty(\realpath('tmp/staempflitest.pdf'), 'File tmp/staempflitest.pdf should be saved'); 102 | $this->assertGreaterThan(10000, \filesize(\realpath('tmp/staempflitest.pdf')), 'File tmp/staempflitest.pdf should be > 10 KB'); 103 | } 104 | 105 | /** 106 | * @param $command 107 | * @return bool 108 | * @SuppressWarnings(PHPMD.UnusedLocalVariable) // https://phpmd.org/rules/index.html 109 | */ 110 | private function isExecutable($command) 111 | { 112 | exec($command . ' 1> /dev/null 2> /dev/null', $output, $return); 113 | return $return <= 1; 114 | } 115 | 116 | /** 117 | * @return mixed|string 118 | */ 119 | private function getWkhtmltopdfBinary() 120 | { 121 | /** @var ScopeConfigInterface $config */ 122 | $config = $this->objectManager->get(ScopeConfigInterface::class); 123 | $wkhtmltopdfBinary = $config->getValue(Config::XML_PATH_BINARY); 124 | if (empty($wkhtmltopdfBinary)) { 125 | return 'wkhtmltopdf'; 126 | } 127 | return $wkhtmltopdfBinary; 128 | } 129 | 130 | /** 131 | * @return \Staempfli\Pdf\Service\Pdf 132 | */ 133 | private function createInvalidPdf() 134 | { 135 | $pdf = $this->pdfFactory->create(); 136 | $pdf->setOptions( 137 | new PdfOptions( 138 | [ 139 | 'gibberish' => 'pffflllltt', 140 | ] 141 | ) 142 | ); 143 | return $pdf; 144 | } 145 | } -------------------------------------------------------------------------------- /Test/Unit/Model/PdfFactoryTest.php: -------------------------------------------------------------------------------- 1 | fakePdfEngine = new FakePdfEngine(); 24 | $this->stubConfig = $this->getMockBuilder(ScopeConfigInterface::class)->getMockForAbstractClass(); 25 | } 26 | 27 | /** 28 | * @dataProvider dataScopeConfig 29 | * @param array $scopeConfigValueMap 30 | * @param array $expectedOptions 31 | */ 32 | public function testOptionsFromConfig(array $scopeConfigValueMap, array $expectedOptions) 33 | { 34 | $this->stubConfig->method('getValue')->willReturnMap($scopeConfigValueMap); 35 | $pdfFactory = new PdfFactory($this->fakePdfEngine, $this->stubConfig); 36 | 37 | $pdfFactory->create()->file(); 38 | 39 | $this->assertEquals( 40 | $expectedOptions, 41 | (array) $this->fakePdfEngine->globalOptions 42 | ); 43 | } 44 | public static function dataScopeConfig() 45 | { 46 | $default = ScopeConfigInterface::SCOPE_TYPE_DEFAULT; 47 | return [ 48 | 'default' => [ 49 | 'scope_config_value_map' => [ 50 | [Config::XML_PATH_BINARY, $default, null, null], 51 | [Config::XML_PATH_VERSION9, $default, null, null], 52 | [Config::XML_PATH_TMP_DIR, $default, null, null], 53 | [Config::XML_PATH_ESCAPE_ARGS, $default, null, null], 54 | [Config::XML_PATH_USE_EXEC, $default, null, null], 55 | [Config::XML_PATH_USE_XVFB_RUN, $default, null, null], 56 | [Config::XML_PATH_XVFB_RUN_BINARY, $default, null, null], 57 | [Config::XML_PATH_XVFB_RUN_OPTIONS, $default, null, null], 58 | ], 59 | 'expected_options' => [ 60 | PdfOptions::KEY_GLOBAL_CLI_OPTIONS => [ 61 | ] 62 | ] 63 | ], 64 | 'all' => [ 65 | 'scope_config_value_map' => [ 66 | [Config::XML_PATH_BINARY, $default, null, '/usr/bin/wkpdf2html'], 67 | [Config::XML_PATH_VERSION9, $default, null, true], 68 | [Config::XML_PATH_TMP_DIR, $default, null, '/tmp/wkpdf'], 69 | [Config::XML_PATH_ESCAPE_ARGS, $default, null, true], 70 | [Config::XML_PATH_USE_EXEC, $default, null, true], 71 | [Config::XML_PATH_USE_XVFB_RUN, $default, null, true], 72 | [Config::XML_PATH_XVFB_RUN_BINARY, $default, null, 'xvfb-run'], 73 | [Config::XML_PATH_XVFB_RUN_OPTIONS, $default, null, '--server-args="-screen 0, 1024x768x24"'], 74 | ], 75 | 'expected_options' => [ 76 | PdfOptions::KEY_GLOBAL_BINARY => '/usr/bin/wkpdf2html', 77 | PdfOptions::KEY_GLOBAL_VERSION9 => true, 78 | PdfOptions::KEY_GLOBAL_TMP_DIR => '/tmp/wkpdf', 79 | PdfOptions::KEY_GLOBAL_CLI_OPTIONS => [ 80 | PdfOptions::KEY_CLI_OPTIONS_ESCAPE_ARGS => true, 81 | PdfOptions::KEY_CLI_OPTIONS_USE_EXEC => true, 82 | PdfOptions::KEY_CLI_OPTIONS_USE_XVFB_RUN => true, 83 | PdfOptions::KEY_CLI_OPTIONS_XVFB_RUN_BINARY => 'xvfb-run', 84 | PdfOptions::KEY_CLI_OPTIONS_XVFB_RUN_OPTIONS => '--server-args="-screen 0, 1024x768x24"', 85 | ] 86 | ] 87 | ] 88 | ]; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Test/Unit/Model/PdfResultTest.php: -------------------------------------------------------------------------------- 1 | fakePdfEngine = new FakePdfEngine(); 28 | $this->stubConfig = $this->getMockBuilder(ScopeConfigInterface::class)->getMockForAbstractClass(); 29 | } 30 | 31 | public function testRenderWithOptions() 32 | { 33 | $pageResultMock = $this->getMockBuilder(PageResultWithoutHttp::class)->disableOriginalConstructor()->getMock(); 34 | $pageResultMock->expects($this->once()) 35 | ->method('renderNonHttpResult') 36 | ->with($this->isInstanceOf(PdfResponse::class)) 37 | ->willReturnSelf(); 38 | $optionsFactoryMock = $this->getMockBuilder(OptionsFactory::class)->disableOriginalConstructor()->setMethods(['create'])->getMock(); 39 | $optionsFactoryMock->method('create')->willReturnCallback(function() { return new PdfOptions(); }); 40 | $pdfResult = new PdfResult( 41 | $this->mockPdfResponseFactory(), 42 | new PdfFactory($this->fakePdfEngine, $this->stubConfig), 43 | $optionsFactoryMock, 44 | $pageResultMock 45 | ); 46 | 47 | $pdfResult->addGlobalOptions(new PdfOptions( 48 | [ 49 | PdfOptions::KEY_GLOBAL_ORIENTATION => PdfOptions::ORIENTATION_LANDSCAPE, 50 | ] 51 | )); 52 | $pdfResult->addPageOptions(new PdfOptions( 53 | [ 54 | PdfOptions::KEY_PAGE_COOKIES => ['frontend' => '0123456789abcdef0123456789abcdef'], 55 | ] 56 | )); 57 | $pdfResult->renderResult($this->getMockBuilder(Http::class)->disableOriginalConstructor()->getMock()); 58 | 59 | $this->assertEquals(new PdfOptions( 60 | [ 61 | PdfOptions::KEY_GLOBAL_CLI_OPTIONS => [], 62 | PdfOptions::KEY_GLOBAL_ORIENTATION => PdfOptions::ORIENTATION_LANDSCAPE, 63 | ] 64 | ), $this->fakePdfEngine->globalOptions); 65 | $this->assertEquals(new PdfOptions( 66 | [ 67 | PdfOptions::KEY_PAGE_COOKIES => ['frontend' => '0123456789abcdef0123456789abcdef'], 68 | ] 69 | ), $this->fakePdfEngine->pages[0][1]); 70 | } 71 | 72 | /** 73 | * @return PdfResponseFactory|\PHPUnit_Framework_MockObject_MockObject 74 | */ 75 | private function mockPdfResponseFactory() 76 | { 77 | $pdfResponseFactory = $this->getMockBuilder(PdfResponseFactory::class)->disableOriginalConstructor()->setMethods(['create'])->getMock(); 78 | $pdfResponseFactory->method('create')->willReturnCallback( 79 | function($data) { 80 | return new PdfResponse($data[PdfResponse::PARAM_OPTIONS]); 81 | } 82 | ); 83 | return $pdfResponseFactory; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Test/Unit/Service/FakePdfFileTest.php: -------------------------------------------------------------------------------- 1 | fakePdfFile = new FakePdfFile(); 16 | $this->assertFalse($this->fakePdfFile->isSent()); 17 | } 18 | 19 | public function testSend() 20 | { 21 | $this->fakePdfFile->send(); 22 | $this->assertTrue($this->fakePdfFile->isSent(), 'isSend flag should be set'); 23 | } 24 | 25 | public function testToString() 26 | { 27 | $this->fakePdfFile->contents = 'this is a PDF'; 28 | $this->assertEquals('this is a PDF', $this->fakePdfFile->toString()); 29 | } 30 | 31 | public function testSaveAs() 32 | { 33 | $this->fakePdfFile->contents = 'save this totally real PDF'; 34 | $vfsRoot = vfsStream::setUp('root'); 35 | $this->fakePdfFile->saveAs(vfsStream::url('root/out.pdf')); 36 | $this->assertTrue($vfsRoot->hasChild('out.pdf'), 'file should be created'); 37 | $this->assertInstanceOf(vfsStreamFile::class, $vfsRoot->getChild('out.pdf')); 38 | $this->assertEquals('save this totally real PDF', $vfsRoot->getChild('out.pdf')->getContent()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Test/Unit/Service/FakeSourceDocumentTest.php: -------------------------------------------------------------------------------- 1 | Hello World'; 16 | $options = new PdfOptions(['html7' => true]); 17 | $doc = new FakeSourceDocument($html, $options); 18 | $doc->printTo(new PdfAppendContent($pdfEngine)); 19 | $this->assertEquals([$html, $options], $pdfEngine->pages[0]); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Test/Unit/Service/PdfAppendContentTest.php: -------------------------------------------------------------------------------- 1 | pdfEngine = new FakePdfEngine(); 18 | $this->medium = new PdfAppendContent($this->pdfEngine); 19 | } 20 | public function testPrintHtml() 21 | { 22 | $html = '

html

'; 23 | $options = new PdfOptions(['version' => 'html10']); 24 | $this->medium->printHtml($html, $options); 25 | $this->assertEquals( 26 | [[$html, $options]], $this->pdfEngine->pages); 27 | 28 | $additionalHtml = '

more html

'; 29 | $additionalOptions = new PdfOptions(['version' => 'html11']); 30 | $this->medium->printHtml($additionalHtml, $additionalOptions); 31 | $this->assertEquals( 32 | [[$html, $options], [$additionalHtml, $additionalOptions]], $this->pdfEngine->pages); 33 | } 34 | } -------------------------------------------------------------------------------- /Test/Unit/Service/PdfCoverTest.php: -------------------------------------------------------------------------------- 1 | pdfEngine = new FakePdfEngine(); 18 | $this->medium = new PdfCover($this->pdfEngine); 19 | } 20 | public function testPrintHtml() 21 | { 22 | $html = 'COVER'; 23 | $options = new PdfOptions(['size' => 'huge']); 24 | $this->medium->printHtml($html, $options); 25 | $this->assertEquals([$html, $options], $this->pdfEngine->cover); 26 | 27 | $html = 'NEW COVER'; 28 | $options = new PdfOptions(['new' => 'true']); 29 | $this->medium->printHtml($html, $options); 30 | $this->assertEquals([$html, $options], $this->pdfEngine->cover); 31 | } 32 | } -------------------------------------------------------------------------------- /Test/Unit/Service/PdfOptionsTest.php: -------------------------------------------------------------------------------- 1 | 'some-value', 12 | 'other-key' => 'other-value', 13 | ]; 14 | $options = new PdfOptions($array); 15 | $this->assertEquals($array, $options->asArray()); 16 | } 17 | /** 18 | * @dataProvider dataMerge 19 | * @param $originalValues 20 | * @param $valuesToMerge 21 | * @param $expectedMergedValues 22 | */ 23 | public function testMerge($originalValues, $valuesToMerge, $expectedMergedValues) 24 | { 25 | $options = new PdfOptions($originalValues); 26 | $mergedOptions = $options->merge(new PdfOptions($valuesToMerge)); 27 | $this->assertEquals( 28 | $expectedMergedValues, 29 | \iterator_to_array($mergedOptions) 30 | ); 31 | $this->assertEquals( 32 | $originalValues, 33 | \iterator_to_array($options), 34 | 'original options should be unchanged' 35 | ); 36 | } 37 | public static function dataMerge() 38 | { 39 | return [ 40 | [ 41 | 'original' => ['some-key' => 'some-default-value', 'other-key' => 'other-default-value'], 42 | 'to_merge' => ['some-key' => 'overridden-value', 'new-key' => 'new-value'], 43 | 'expected' => ['some-key' => 'overridden-value', 'other-key' => 'other-default-value', 'new-key' => 'new-value'], 44 | ], 45 | ]; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Test/Unit/Service/PdfTableOfContentsTest.php: -------------------------------------------------------------------------------- 1 | pdfEngine = new \Staempfli\Pdf\Service\FakePdfEngine(); 18 | $this->tableOfContents = new PdfTableOfContents($this->pdfEngine); 19 | } 20 | public function testPrintToc() 21 | { 22 | $this->tableOfContents->printToc(new PdfOptions(['print-option' => 'something'])); 23 | $this->assertEquals(new PdfOptions(['print-option' => 'something']), $this->pdfEngine->tableOfContents); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Test/Unit/Service/PdfTest.php: -------------------------------------------------------------------------------- 1 | fakePdfEngine = new FakePdfEngine(); 23 | $this->pdf = new Pdf($this->fakePdfEngine); 24 | } 25 | 26 | public function testBuildEmpty() 27 | { 28 | $this->assertFalse($this->fakePdfEngine->fakePdfFile->isGenerated); 29 | $this->assertInstanceOf(PdfFile::class, $this->pdf->file()); 30 | $this->assertTrue($this->fakePdfEngine->fakePdfFile->isGenerated); 31 | $this->assertNull($this->fakePdfEngine->tableOfContents); 32 | $this->assertNull($this->fakePdfEngine->cover); 33 | $this->assertCount(0, $this->fakePdfEngine->pages); 34 | } 35 | 36 | public function testBuildWithGlobalOptions() 37 | { 38 | $this->assertFalse($this->fakePdfEngine->fakePdfFile->isGenerated); 39 | $options = new PdfOptions(['something' => 'something']); 40 | $this->pdf->setOptions($options); 41 | 42 | $this->assertInstanceOf(PdfFile::class, $this->pdf->file()); 43 | $this->assertTrue($this->fakePdfEngine->fakePdfFile->isGenerated); 44 | $this->assertSame($options, $this->fakePdfEngine->globalOptions); 45 | } 46 | 47 | public function testBuildWithTableOfContents() 48 | { 49 | $this->assertFalse($this->fakePdfEngine->fakePdfFile->isGenerated); 50 | $this->pdf->appendTableOfContents(new PdfOptions(['toc' => 'tic'])); 51 | 52 | $this->assertInstanceOf(PdfFile::class, $this->pdf->file()); 53 | $this->assertTrue($this->fakePdfEngine->fakePdfFile->isGenerated); 54 | $this->assertEquals(new PdfOptions(['toc' => 'tic']), $this->fakePdfEngine->tableOfContents); 55 | } 56 | 57 | public function testAddOptions() 58 | { 59 | $this->pdf->setOptions(new PdfOptions(['first-option' => 'foo', 'second-option' => 'bar'])); 60 | $this->pdf->addOptions(new PdfOptions(['third-option' => 'baz', 'first-option' => 'foo²'])); 61 | $this->pdf->file(); 62 | $this->assertEquals(new PdfOptions(['first-option' => 'foo²', 'second-option' => 'bar', 'third-option' => 'baz']), $this->fakePdfEngine->globalOptions); 63 | } 64 | public function testAppendContent() 65 | { 66 | $this->pdf->appendContent(new FakeSourceDocument('HTML source', new PdfOptions(['looks' => 'nice']))); 67 | $this->pdf->file(); 68 | $this->assertEquals(['HTML source', new PdfOptions(['looks' => 'nice'])], $this->fakePdfEngine->pages[0]); 69 | } 70 | public function testSetCover() 71 | { 72 | $this->pdf->appendCover(new FakeSourceDocument('Cover HTML source', new PdfOptions(['looks' => 'great']))); 73 | $this->pdf->file(); 74 | $this->assertEquals(['Cover HTML source', new PdfOptions(['looks' => 'great'])], $this->fakePdfEngine->cover); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "staempfli/magento2-module-pdf", 3 | "description": "Staempfli PDF generator.", 4 | "require": { 5 | "php": "~5.6.0|~7.0.0|~7.1.0", 6 | "mikehaertl/phpwkhtmltopdf": "2.1.0", 7 | "magento/framework": "~100.1.2|~101.0.0", 8 | "magento/module-backend": "^100.1.1" 9 | }, 10 | "require-dev": { 11 | "mikey179/vfsStream": "^1.6" 12 | }, 13 | "type": "magento2-module", 14 | "license": [ 15 | "OSL-3.0" 16 | ], 17 | "autoload": { 18 | "files": [ 19 | "registration.php" 20 | ], 21 | "psr-4": { 22 | "Staempfli\\Pdf\\": "" 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /docs/img/config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/staempfli/magento2-module-pdf/b7f31abd409f158250f04b33e70a22503ceb3ad8/docs/img/config.png -------------------------------------------------------------------------------- /etc/acl.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /etc/adminhtml/system.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | separator-top 6 | 7 | advanced 8 | Staempfli_Pdf::config_staempfli_pdf 9 | 10 | 11 | 12 | 13 | Full path to wkhtmltopdf binary. If empty, "wkhtmltopdf" should be available in system path 14 | 15 | 16 | 17 | Output directory for wkhtmltopdf. Leave empty to use system tmp dir 18 | 19 | 20 | 21 | Magento\Config\Model\Config\Source\Yesno 22 | Whether to use command line syntax for older wkhtmltopdf versions 23 | 24 | 25 | 26 | Magento\Config\Model\Config\Source\Yesno 27 | Ignore warnings of wkhtmltopdf 28 | 29 | 30 | 31 | Magento\Config\Model\Config\Source\Yesno 32 | Whether to escape wkhtmltopdf shell arguments (Default: yes) 33 | 34 | 35 | 36 | Magento\Config\Model\Config\Source\Yesno 37 | Only set to "yes", if "proc_open" does not work on your system (e.g. on Windows) 38 | 39 | 40 | 41 | Magento\Config\Model\Config\Source\Yesno 42 | Use xvfb-run to wrap each call in a new Xvfb session, so you don't need a running Xvfb process (only recommended for low frequency sites) 43 | 44 | 45 | 46 | Full path to the xvfb-run binary. If empty, "xvfb-run" should be available in system path 47 | 48 | 1 49 | 50 | 51 | 52 | 53 | Default: -a --server-args="-screen 0, 1024x768x24" 54 | 55 | 1 56 | 57 | 58 | 59 |
60 |
61 |
62 | -------------------------------------------------------------------------------- /etc/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 1 7 | 1 8 | -a --server-args="-screen 0, 1024x768x24" 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /etc/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | \Staempfli\Pdf\Model\View\PdfResult::TYPE 10 | \Staempfli\Pdf\Model\View\PdfResult 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /grumphp.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: ../../staempfli/magento2-conventions-checker/magento2-module-grumphp.yml } -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | Test/Unit 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | * 17 | 18 | Test 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 |