forked from mikehaertl/phpwkhtmltopdf
-
Notifications
You must be signed in to change notification settings - Fork 1
/
WkHtmlToPdf.php
354 lines (313 loc) · 10.3 KB
/
WkHtmlToPdf.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
<?php
/**
* WkHtmlToPdf
*
* This class is a slim wrapper around wkhtmltopdf.
*
* It provides a simple and clean interface to ease PDF creation with wkhtmltopdf.
* The wkhtmltopdf binary must be installed and working on your system. The static
* binary is preferred but this class should also work with the non static version,
* even though a lot of features will be missing.
*
* Basic use
* ---------
*
* $pdf = new WkHtmlToPdf;
*
* // Add a HTML file, a HTML string or a page from URL
* $pdf->addPage('/home/joe/page.html');
* $pdf->addPage('<html>....</html>');
* $pdf->addPage('http://google.com');
*
* // Add a cover (same sources as above are possible)
* $pdf->addCover('mycover.html');
*
* // Add a Table of contents
* $pdf->addToc();
*
* // Save the PDF
* $pdf->saveAs('/tmp/new.pdf');
*
* // ... or send to client for inline display
* $pdf->send();
*
* // ... or send to client as file download
* $pdf->send('test.pdf');
*
*
* Setting options
* ---------------
*
* The wkhtmltopdf binary knows different types of options (please see wkhtmltopdf -H):
*
* * global options (e.g. to set the document's DPI)
* * page options (e.g. to supply a custom CSS file for a page)
* * toc options (e.g. to set a TOC header)
*
* In addition this class also supports global page options: You can set default page options
* that will be applied to every page you add. You can also override these defaults per page:
*
* $pdf = new WkHtmlToPdf($options); // Set global PDF options
* $pdf->setOptions($options); // Set global PDF options (alternative)
* $pdf->setPageOptions($options); // Set default page options
* $pdf->addPage($page, $options); // Add page with options (overrides default page options)
* $pdf->addCover($page, $options); // Add cover with options (overrides default page options)
* $pdf->addToc($options); // Add TOC with options
*
*
* Special global options
* ----------------------
*
* * bin: Path to the wkhtmltopdf binary. Defaults to /usr/bin/wkhtmltopdf.
* * tmp: Path to tmp directory. Defaults to PHP temp dir.
* * enableEscaping: Wether arguments to wkhtmltopdf should be escaped. Default is true.
*
*
* Error handling
* --------------
*
* saveAs() and send() will return false on error. In this case the detailed error message
* from wkhtmltopdf can be obtained through getError():
*
* if(!$pdf->send())
* throw new Exception('Could not create PDF: '.$pdf->getError());
*
*
* Note for Windows users
* ----------------------
*
* If you use double quotes (") or percent signs (%) as option values, they may get
* converted to spaces. You can set `enableEscaping` to `false` in this case. But
* then you have to take care of proper escaping yourself. In some cases it may be
* neccessary to surround your argument values with extra double quotes.
*
*
* @author Michael Härtl <[email protected]> (sponsored by PeoplePerHour.com)
* @version 1.1.5-dev
* @license http://www.opensource.org/licenses/MIT
*/
class WkHtmlToPdf
{
protected $bin = '/usr/bin/wkhtmltopdf';
protected $enableEscaping = true;
protected $options = array();
protected $pageOptions = array();
protected $objects = array();
protected $tmp;
protected $tmpFile;
protected $tmpFiles = array();
protected $error;
// Regular expression to detect HTML strings
const REGEX_HTML = '/<html/i';
/**
* @param array $options global options for wkhtmltopdf (optional)
*/
public function __construct($options=array())
{
if($options!==array())
$this->setOptions($options);
}
/**
* Remove temporary PDF file and pages when script completes
*/
public function __destruct()
{
if($this->tmpFile!==null)
unlink($this->tmpFile);
foreach($this->tmpFiles as $tmp)
unlink($tmp);
}
/**
* Add a page object to the output
*
* @param string $input either a URL, a HTML string or a PDF/HTML filename
* @param array $options optional options for this page
*/
public function addPage($input,$options=array())
{
$options['input'] = preg_match(self::REGEX_HTML, $input) ? $this->createTmpFile($input) : $input;
$this->objects[] = array_merge($this->pageOptions,$options);
}
/**
* Add a cover page object to the output
*
* @param string $input either a URL or a PDF filename
* @param array $options optional options for this page
*/
public function addCover($input,$options=array())
{
$options['input'] = "cover $input";
$this->objects[] = array_merge($this->pageOptions,$options);
}
/**
* Add a TOC object to the output
*
* @param array $options optional options for the table of contents
*/
public function addToc($options=array())
{
$options['input'] = "toc";
$this->objects[] = $options;
}
/**
* Save the PDF to given filename (triggers PDF creation)
*
* @param string $filename to save PDF as
* @return bool wether PDF was created successfully
*/
public function saveAs($filename)
{
if(($pdfFile = $this->getPdfFilename())===false)
return false;
copy($pdfFile,$filename);
return true;
}
/**
* Send PDF to client, either inline or as download (triggers PDF creation)
*
* @param mixed $filename the filename to send. If empty, the PDF is streamed.
* @return bool wether PDF was created successfully
*/
public function send($filename=null)
{
if(($pdfFile = $this->getPdfFilename())===false)
return false;
header('Pragma: public');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Content-Type: application/pdf');
header('Content-Transfer-Encoding: binary');
header('Content-Length: '.filesize($pdfFile));
if($filename!==null)
header("Content-Disposition: attachment; filename=\"$filename\"");
readfile($pdfFile);
return true;
}
/**
* Set global option(s)
*
* @param array $options list of global options to set as name/value pairs
*/
public function setOptions($options)
{
foreach($options as $key=>$val)
if($key==='bin')
$this->bin = $val;
elseif($key==='tmp')
$this->tmp = $val;
elseif($key==='enableEscaping')
$this->enableEscaping = (bool)$val;
elseif(is_int($key))
$this->options[] = $val;
else
$this->options[$key] = $val;
}
/**
* @param array $options that should be applied to all pages as name/value pairs
*/
public function setPageOptions($options=array())
{
$this->pageOptions = $options;
}
/**
* @return mixed the detailled error message including the wkhtmltopdf command or null if none
*/
public function getError()
{
return $this->error;
}
/**
* @return string path to temp directory
*/
public function getTmpDir()
{
if($this->tmp===null)
$this->tmp = sys_get_temp_dir();
return $this->tmp;
}
/**
* @param string $filename the filename of the output file
* @return string the wkhtmltopdf command string
*/
public function getCommand($filename)
{
$command = $this->enableEscaping ? escapeshellarg($this->bin) : $this->bin;
$command .= $this->renderOptions($this->options);
foreach($this->objects as $object)
{
$command .= ' '.$object['input'];
unset($object['input']);
$command .= $this->renderOptions($object);
}
return $command.' '.$filename;
}
/**
* @return mixed the temporary PDF filename or false on error (triggers PDf creation)
*/
protected function getPdfFilename()
{
if($this->tmpFile===null)
{
$tmpFile = tempnam($this->getTmpDir(),'tmp_WkHtmlToPdf_');
if($this->createPdf($tmpFile)===true)
$this->tmpFile = $tmpFile;
else
return false;
}
return $this->tmpFile;
}
/**
* Create the temporary PDF file
*/
protected function createPdf($fileName)
{
$command = $this->getCommand($fileName);
// we use proc_open with pipes to fetch error output
$descriptors = array(
2 => array('pipe','w'),
);
$process = proc_open($command, $descriptors, $pipes, null, null, array('bypass_shell'=>true));
if(is_resource($process)) {
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[2]);
$result = proc_close($process);
if($result!==0)
{
if (!file_exists($fileName) || filesize($fileName)===0)
$this->error = "Could not run command $command:\n$stderr";
else
$this->error = "Warning: an error occured while creating the PDF.\n$stderr";
}
} else
$this->error = "Could not run command $command";
return $this->error===null;
}
/**
* Create a tmp file with given content
*
* @param string $content the file content
* @return string the path to the created file
*/
protected function createTmpFile($content)
{
$tmpFile = tempnam($this->getTmpDir(),'tmp_WkHtmlToPdf_');
rename($tmpFile, ($tmpFile.='.html'));
file_put_contents($tmpFile, $content);
$this->tmpFiles[] = $tmpFile;
return $tmpFile;
}
/**
* @param array $options for a wkhtml, either global or for an object
* @return string the string with options
*/
protected function renderOptions($options)
{
$out = '';
foreach($options as $key=>$val)
if(is_numeric($key))
$out .= " --$val";
else
$out .= " --$key ".($this->enableEscaping ? escapeshellarg($val) : $val);
return $out;
}
}