361 lines
11 KiB
PHP
361 lines
11 KiB
PHP
<?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: Whether arguments to wkhtmltopdf should be escaped. Default is true.
|
|
* * version9: Whether to use command line syntax for wkhtmltopdf < 0.10
|
|
*
|
|
*
|
|
* 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 <haertl.mike@gmail.com> (sponsored by PeoplePerHour.com)
|
|
* @version 1.1.6
|
|
* @license http://www.opensource.org/licenses/MIT
|
|
*/
|
|
class WkHtmlToPdf
|
|
{
|
|
// protected $bin = '/usr/bin/wkhtmltopdf'; // MC (10.08.2021) :: Activated after installation of package "wkhtmltopdf"
|
|
// protected $bin = '../include/wkhtmltopdf'; // MC (earlier) :: Binary in votian include path directly, this solutions works
|
|
protected $bin = 'wkhtmltopdf'; // MC (26.09.2025) :: Binary system call
|
|
|
|
protected $enableEscaping = true;
|
|
protected $version9 = false;
|
|
|
|
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'] = ($this->version9 ? '--' : '')."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'] = ($this->version9 ? '--' : '')."toc";
|
|
$this->objects[] = $options;
|
|
}
|
|
|
|
/**
|
|
* Save the PDF to given filename (triggers PDF creation)
|
|
*
|
|
* @param string $filename to save PDF as
|
|
* @return bool whether 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 whether PDF was created successfully
|
|
*/
|
|
public function send($filename=null)
|
|
{
|
|
if(($pdfFile = $this->getPdfFilename())===false)
|
|
return false;
|
|
if (ob_get_level()) {
|
|
ob_end_clean();
|
|
}
|
|
header('Content-Type: application/pdf');
|
|
header('Content-Disposition: inline; filename="' . basename($filename) . '"');
|
|
header('Content-Length: ' . filesize($pdfFile));
|
|
header('Cache-Control: private, max-age=0, must-revalidate');
|
|
header('Pragma: public');
|
|
if ($filename !== null)
|
|
header("Content-Disposition: attachment; filename=\"$filename\"");
|
|
readfile($filename);
|
|
exit;
|
|
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($key==='version9')
|
|
$this->version9 = (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;
|
|
}
|
|
}
|