├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── LICENSE.md ├── README.md ├── composer.json ├── phpunit.xml.dist └── src └── PDFMerger ├── Facades └── PDFMergerFacade.php ├── PDFMerger.php ├── Providers └── PDFMergerServiceProvider.php └── examples ├── pdf_one.pdf └── pdf_two.pdf /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | .idea -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - hhvm 5 | - 7.0 6 | - 5.6 7 | - 5.5 8 | 9 | matrix: 10 | fast_finish: true 11 | allow_failures: 12 | - php: hhvm 13 | 14 | sudo: false 15 | 16 | install: composer install --no-interaction 17 | 18 | script: 19 | - mkdir -p build/logs 20 | - vendor/bin/phpunit --coverage-clover build/logs/clover.xml 21 | 22 | after_success: 23 | - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then php vendor/bin/coveralls -v; fi;' 24 | 25 | notifications: 26 | email: 27 | on_success: always 28 | on_failure: always -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `webklex/laravel-pdfmerger` will be documented in this file. 4 | 5 | Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) principles. 6 | 7 | ## [Unreleased] 8 | 9 | ### Added 10 | - NaN 11 | 12 | ### Fixed 13 | - NaN 14 | 15 | ## [1.3.2] (2024-07-02) 16 | 17 | ### Added 18 | - Support Auto-Discovery #47 (thanks @hasanwijaya ) 19 | 20 | ### Fixed 21 | - No response being send on download #31 #32 (thanks @KreutzerCode) 22 | 23 | ## [1.3.1] (2022-09-14) 24 | 25 | ### Fixed 26 | - Don't force portrait orientation during merge if not specified (thanks @Jason-Toh) 27 | 28 | ## [1.3.0] (2021-10-25) 29 | 30 | ### Fixed 31 | - String (and Array) Helper functions are now removed in Laravel #17 (thanks @warksit) 32 | 33 | ## [1.2.0] (2021-10-21) 34 | 35 | ### Fixed 36 | - Replace itbz (deprecated) with setasign #14 #15 (thanks @laraben) 37 | 38 | ### Added 39 | - auto orientation #11 (thanks @laraben) 40 | 41 | ## [1.1.0] (2018-05-29) 42 | 43 | ### Added 44 | - Added `duplexMerge` to support duplex-safe merging 45 | 46 | ### Fixed 47 | - Basic PDF library changed 48 | 49 | ## [1.0.0] (2017-02-17) 50 | 51 | ### Added 52 | - new laravel-pdfmerger package 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Webklex 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Malte Goldenbaum 4 | 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in 13 | > all copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | > THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel PDFMerger 2 | 3 | [![Latest Version on Packagist][ico-version]][link-packagist] 4 | [![Software License][ico-license]](LICENSE.md) 5 | [![Build Status][ico-travis]][link-travis] 6 | [![Total Downloads][ico-downloads]][link-downloads] 7 | 8 | 9 | ## Install 10 | 11 | Via Composer 12 | 13 | ``` bash 14 | $ composer require webklex/laravel-pdfmerger 15 | ``` 16 | 17 | ## Setup 18 | 19 | Add the service provider to the providers array in `config/app.php`. 20 | 21 | ``` php 22 | 'providers' => [ 23 | ... 24 | Webklex\PDFMerger\Providers\PDFMergerServiceProvider::class 25 | ], 26 | 27 | 'aliases' => [ 28 | ... 29 | 'PDFMerger' => Webklex\PDFMerger\Facades\PDFMergerFacade::class 30 | ] 31 | ``` 32 | 33 | ## Usage 34 | A basic usage example: 35 | 36 | ``` php 37 | use Webklex\PDFMerger\Facades\PDFMergerFacade as PDFMerger; 38 | 39 | $oMerger = PDFMerger::init(); 40 | 41 | $oMerger->addPDF('/path/to/project/vendors/webklex/laravel-pdfmerger/src/PDFMerger/examples/pdf_one.pdf', [2]); 42 | $oMerger->addPDF('/path/to/project/vendors/webklex/laravel-pdfmerger/src/PDFMerger/examples/pdf_two.pdf', 'all'); 43 | 44 | $oMerger->merge(); 45 | $oMerger->save('merged_result.pdf'); 46 | 47 | ``` 48 | 49 | ...add raw content data: 50 | 51 | ``` php 52 | $oMerger->addString(file_get_contents('/path/to/project/vendors/webklex/laravel-pdfmerger/src/PDFMerger/examples/pdf_two.pdf'), [1]); 53 | 54 | ``` 55 | 56 | ...select the pages you want to merge: 57 | 58 | ``` php 59 | $oMerger->addPDF($file, 'all'); //Add all pages 60 | $oMerger->addPDF($file, [1]); //Add page one only 61 | $oMerger->addPDF($file, [2]); //Add page two only 62 | $oMerger->addPDF($file, [1, 3]); //Add page one and three only 63 | 64 | ``` 65 | 66 | ...merge files together but add blank pages to support duplex printing: 67 | ```php 68 | $oMerger->duplexMerge(); 69 | ``` 70 | 71 | ...stream the merged content: 72 | 73 | ``` php 74 | $oMerger->stream(); 75 | 76 | ``` 77 | ...download the merged content: 78 | 79 | ``` php 80 | $oMerger->download(); 81 | 82 | ``` 83 | ..get the raw content data: 84 | ``` php 85 | echo $oMerger->output(); 86 | 87 | ``` 88 | ...set the filename if you don't want to do it later: 89 | 90 | ``` php 91 | $oMerger->setFileName('example.pdf'); 92 | 93 | ``` 94 | 95 | ## Change log 96 | 97 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 98 | 99 | ## Testing 100 | 101 | ``` bash 102 | $ composer test 103 | ``` 104 | 105 | ## Security 106 | 107 | If you discover any security related issues, please email github@webklex.com instead of using the issue tracker. 108 | 109 | ## Credits 110 | 111 | - [Webklex][link-author] 112 | - All Contributors 113 | 114 | ## License 115 | 116 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 117 | 118 | [ico-version]: https://img.shields.io/packagist/v/Webklex/laravel-pdfmerger.svg?style=flat-square 119 | [ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square 120 | [ico-travis]: https://img.shields.io/travis/Webklex/translator/master.svg?style=flat-square 121 | [ico-scrutinizer]: https://img.shields.io/scrutinizer/coverage/g/Webklex/laravel-pdfmerger.svg?style=flat-square 122 | [ico-code-quality]: https://img.shields.io/scrutinizer/g/Webklex/laravel-pdfmerger.svg?style=flat-square 123 | [ico-downloads]: https://img.shields.io/packagist/dt/Webklex/laravel-pdfmerger.svg?style=flat-square 124 | 125 | [link-packagist]: https://packagist.org/packages/Webklex/laravel-pdfmerger 126 | [link-travis]: https://travis-ci.org/Webklex/laravel-pdfmerger 127 | [link-scrutinizer]: https://scrutinizer-ci.com/g/Webklex/laravel-pdfmerger/code-structure 128 | [link-code-quality]: https://scrutinizer-ci.com/g/Webklex/laravel-pdfmerger 129 | [link-downloads]: https://packagist.org/packages/Webklex/laravel-pdfmerger 130 | [link-author]: https://github.com/webklex 131 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webklex/laravel-pdfmerger", 3 | "type": "library", 4 | "description": "Generic PDF merger for Laravel", 5 | "keywords": [ 6 | "webklex", 7 | "laravel", 8 | "pdf", 9 | "merger", 10 | "pdfmerger" 11 | ], 12 | "homepage": "https://github.com/webklex/pdfmerger", 13 | "license": "MIT", 14 | "authors": [ 15 | { 16 | "name": "Malte Goldenbaum", 17 | "email": "github@webklex.com", 18 | "role": "Developer" 19 | } 20 | ], 21 | "require": { 22 | "php": ">=5.5.9", 23 | "illuminate/support": ">=5.0", 24 | "setasign/fpdf": "1.8.*", 25 | "setasign/fpdi": "^2.0" 26 | }, 27 | "require-dev": { 28 | "phpunit/phpunit": "4.*", 29 | "scrutinizer/ocular": "~1.1", 30 | "squizlabs/php_codesniffer": "~2.3" 31 | }, 32 | "autoload": { 33 | "psr-4": { 34 | "Webklex\\PDFMerger\\": "src/PDFMerger" 35 | } 36 | }, 37 | "autoload-dev": { 38 | "psr-4": { 39 | "Webklex\\PDFMerger\\Test\\": "tests" 40 | } 41 | }, 42 | "scripts": { 43 | "test": "phpunit" 44 | }, 45 | "extra": { 46 | "branch-alias": { 47 | "dev-master": "1.0-dev" 48 | }, 49 | "laravel": { 50 | "providers": [ 51 | "Webklex\\PDFMerger\\Providers\\PDFMergerServiceProvider" 52 | ], 53 | "aliases": { 54 | "PDFMerger": "Webklex\\PDFMerger\\Facades\\PDFMergerFacade" 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | tests 15 | 16 | 17 | 18 | 19 | src/ 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/PDFMerger/Facades/PDFMergerFacade.php: -------------------------------------------------------------------------------- 1 | oFilesystem = $oFilesystem; 65 | $this->oFPDI = new FPDI(); 66 | $this->tmpFiles = collect([]); 67 | 68 | $this->init(); 69 | } 70 | 71 | /** 72 | * The class deconstructor method 73 | */ 74 | public function __destruct() { 75 | $oFilesystem = $this->oFilesystem; 76 | $this->tmpFiles->each(function($filePath) use($oFilesystem){ 77 | $oFilesystem->delete($filePath); 78 | }); 79 | } 80 | 81 | /** 82 | * Initialize a new internal instance of FPDI in order to prevent any problems with shared resources 83 | * Please visit https://www.setasign.com/products/fpdi/manual/#p-159 for more information on this issue 84 | * 85 | * @return self 86 | */ 87 | public function init(){ 88 | $this->oFPDI = new FPDI(); 89 | $this->aFiles = collect([]); 90 | return $this; 91 | } 92 | 93 | /** 94 | * Stream the merged PDF content 95 | * 96 | * @return string 97 | */ 98 | public function stream(){ 99 | return $this->oFPDI->Output($this->fileName, 'I'); 100 | } 101 | 102 | /** 103 | * Download the merged PDF content 104 | * 105 | * @return string 106 | */ 107 | public function download(){ 108 | $output = $this->output(); 109 | $response = new Response($output, 200, [ 110 | 'Content-Type' => 'application/pdf', 111 | 'Content-Disposition' => 'attachment; filename="' . $this->fileName . '"', 112 | 'Content-Length' => strlen($output), 113 | ]); 114 | return $response->send(); 115 | } 116 | 117 | /** 118 | * Save the merged PDF content to the filesystem 119 | * 120 | * @return string 121 | */ 122 | public function save($filePath = null){ 123 | return $this->oFilesystem->put($filePath?$filePath:$this->fileName, $this->output()); 124 | } 125 | 126 | /** 127 | * Get the merged PDF content 128 | * 129 | * @return string 130 | */ 131 | public function output(){ 132 | return $this->oFPDI->Output($this->fileName, 'S'); 133 | } 134 | 135 | /** 136 | * Set the final filename 137 | * @param string $fileName 138 | * 139 | * @return string 140 | */ 141 | public function setFileName($fileName){ 142 | $this->fileName = $fileName; 143 | return $this; 144 | } 145 | 146 | /** 147 | * Set the final filename 148 | * @param string $string 149 | * @param mixed $pages 150 | * @param mixed $orientation 151 | * 152 | * @return string 153 | */ 154 | public function addString($string, $pages = 'all', $orientation = null){ 155 | 156 | $filePath = storage_path('tmp/'.Str::random(16).'.pdf'); 157 | $this->oFilesystem->put($filePath, $string); 158 | $this->tmpFiles->push($filePath); 159 | 160 | return $this->addPDF($filePath, $pages, $orientation); 161 | } 162 | 163 | /** 164 | * Add a PDF for inclusion in the merge with a valid file path. Pages should be formatted: 1,3,6, 12-16. 165 | * @param string $filePath 166 | * @param string $pages 167 | * @param string $orientation 168 | * 169 | * @return self 170 | * 171 | * @throws \Exception if the given pages aren't correct 172 | */ 173 | public function addPDF($filePath, $pages = 'all', $orientation = null) { 174 | if (file_exists($filePath)) { 175 | if (!is_array($pages) && strtolower($pages) != 'all') { 176 | throw new \Exception($filePath."'s pages could not be validated"); 177 | } 178 | 179 | $this->aFiles->push([ 180 | 'name' => $filePath, 181 | 'pages' => $pages, 182 | 'orientation' => $orientation 183 | ]); 184 | } else { 185 | throw new \Exception("Could not locate PDF on '$filePath'"); 186 | } 187 | 188 | return $this; 189 | } 190 | 191 | /** 192 | * Merges your provided PDFs and outputs to specified location. 193 | * @param string $orientation 194 | * 195 | * @return void 196 | * 197 | * @throws \Exception if there are now PDFs to merge 198 | */ 199 | public function merge($orientation = null) { 200 | $this->doMerge($orientation, false); 201 | } 202 | 203 | /** 204 | * Merges your provided PDFs and adds blank pages between documents as needed to allow duplex printing 205 | * @param string $orientation 206 | * 207 | * @return void 208 | * 209 | * @throws \Exception if there are now PDFs to merge 210 | */ 211 | public function duplexMerge($orientation = 'P') { 212 | $this->doMerge($orientation, true); 213 | } 214 | 215 | protected function doMerge($orientation, $duplexSafe) { 216 | 217 | if ($this->aFiles->count() == 0) { 218 | throw new \Exception("No PDFs to merge."); 219 | } 220 | 221 | $oFPDI = $this->oFPDI; 222 | 223 | $this->aFiles->each(function($file) use($oFPDI, $orientation, $duplexSafe){ 224 | $file['orientation'] = is_null($file['orientation'])?$orientation:$file['orientation']; 225 | $count = $oFPDI->setSourceFile(StreamReader::createByString(file_get_contents($file['name']))); 226 | 227 | if ($file['pages'] == 'all') { 228 | 229 | for ($i = 1; $i <= $count; $i++) { 230 | $template = $oFPDI->importPage($i); 231 | $size = $oFPDI->getTemplateSize($template); 232 | $autoOrientation = isset($file['orientation']) ? $file['orientation'] : $size['orientation']; 233 | 234 | $oFPDI->AddPage($autoOrientation, [$size['width'], $size['height']]); 235 | $oFPDI->useTemplate($template); 236 | } 237 | } else { 238 | foreach ($file['pages'] as $page) { 239 | if (!$template = $oFPDI->importPage($page)) { 240 | throw new \Exception("Could not load page '$page' in PDF '" . $file['name'] . "'. Check that the page exists."); 241 | } 242 | $size = $oFPDI->getTemplateSize($template); 243 | $autoOrientation = isset($file['orientation']) ? $file['orientation'] : $size['orientation']; 244 | 245 | $oFPDI->AddPage($autoOrientation, [$size['width'], $size['height']]); 246 | $oFPDI->useTemplate($template); 247 | } 248 | } 249 | 250 | if ($duplexSafe && $oFPDI->page % 2) { 251 | $oFPDI->AddPage($file['orientation'], [$size['width'], $size['height']]); 252 | } 253 | }); 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /src/PDFMerger/Providers/PDFMergerServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton('PDFMerger', function ($app) { 39 | $oPDFMerger = new PDFMerger($app['files']); 40 | return $oPDFMerger; 41 | }); 42 | } 43 | } -------------------------------------------------------------------------------- /src/PDFMerger/examples/pdf_one.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Webklex/laravel-pdfmerger/de88152c793fb190631a050688478f4a18bbac22/src/PDFMerger/examples/pdf_one.pdf -------------------------------------------------------------------------------- /src/PDFMerger/examples/pdf_two.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Webklex/laravel-pdfmerger/de88152c793fb190631a050688478f4a18bbac22/src/PDFMerger/examples/pdf_two.pdf --------------------------------------------------------------------------------