Skip to content

Commit dcd1236

Browse files
authored
Merge pull request #308 from mikehaertl/307-is-file-check
Issue #307 Refactor check for temp file creation
2 parents 35cb070 + 96c78ad commit dcd1236

File tree

3 files changed

+108
-28
lines changed

3 files changed

+108
-28
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ composer require mikehaertl/phpwkhtmltopdf
2222
Make sure, that you include the composer [autoloader](https://getcomposer.org/doc/01-basic-usage.md#autoloading)
2323
somewhere in your codebase.
2424

25+
## Examples
26+
2527
### Single page PDF
2628

2729
```php
@@ -186,6 +188,23 @@ $pdf = new Pdf(array(
186188
));
187189
```
188190

191+
### Passing strings
192+
193+
Some options like `header-html` usually expect a URL or a filename. With our
194+
library you can also pass a string. The class will try to detect if the
195+
argument is a URL, a filename or some HTML or XML content. To make detection
196+
easier you can surround your content in `<html>` tag.
197+
198+
If this doesn't work correctly you can also pass an instance of our `File`
199+
helper as a last resort:
200+
201+
```php
202+
use mikehaertl\tmp\File;
203+
$options = [
204+
'header-html' => new File('Complex content', '.html'),
205+
];
206+
```
207+
189208
## Error handling
190209

191210
`send()`, `saveAs()` and `toString()` will return `false` on error. In this case the detailed error message is

src/Pdf.php

Lines changed: 57 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,19 @@ class Pdf
2323
// Regular expression to detect XML strings
2424
const REGEX_XML = '/<\??xml/i';
2525

26+
// Regular expression to detect URL strings
27+
const REGEX_URL = '/^(https?:)?\/\//i';
28+
2629
// Regular expression to detect options that expect an URL or a file name,
2730
// so we need to create a tmp file for the content.
2831
const REGEX_OPTS_TMPFILE = '/^((header|footer)-html|(xsl|user)-style-sheet)$/i';
2932

30-
// prefix for tmp files
33+
// Prefix for tmp files
3134
const TMP_PREFIX = 'tmp_wkhtmlto_pdf_';
3235

36+
// Maximum length of a file path if PHP_MAXPATHLEN is not defined
37+
const MAX_PATHLEN = 255;
38+
3339
/**
3440
* @var string the name of the `wkhtmltopdf` binary. Default is
3541
* `wkhtmltopdf`. You can also configure a full path here.
@@ -122,8 +128,8 @@ public function __construct($options = null)
122128
*/
123129
public function addPage($input, $options = array(), $type = null)
124130
{
125-
$options['inputArg'] = $this->processInput($input, $type);
126-
$this->_objects[] = $this->processOptions($options);
131+
$options['inputArg'] = $this->ensureUrlOrFile($input, $type);
132+
$this->_objects[] = $this->ensureUrlOrFileOptions($options);
127133
return $this;
128134
}
129135

@@ -139,9 +145,9 @@ public function addPage($input, $options = array(), $type = null)
139145
*/
140146
public function addCover($input, $options = array(), $type = null)
141147
{
142-
$options['input'] = ($this->version9 ? '--' : '').'cover';
143-
$options['inputArg'] = $this->processInput($input, $type);
144-
$this->_objects[] = $this->processOptions($options);
148+
$options['input'] = ($this->version9 ? '--' : '') . 'cover';
149+
$options['inputArg'] = $this->ensureUrlOrFile($input, $type);
150+
$this->_objects[] = $this->ensureUrlOrFileOptions($options);
145151
return $this;
146152
}
147153

@@ -153,8 +159,8 @@ public function addCover($input, $options = array(), $type = null)
153159
*/
154160
public function addToc($options = array())
155161
{
156-
$options['input'] = ($this->version9 ? '--' : '')."toc";
157-
$this->_objects[] = $this->processOptions($options);
162+
$options['input'] = ($this->version9 ? '--' : '') . 'toc';
163+
$this->_objects[] = $this->ensureUrlOrFileOptions($options);
158164
return $this;
159165
}
160166

@@ -215,16 +221,16 @@ public function toString()
215221
*/
216222
public function setOptions($options = array())
217223
{
218-
// #264 tmpDir must be set before calling processOptions
224+
// #264 tmpDir must be set before calling ensureUrlOrFileOptions
219225
if (isset($options['tmpDir'])) {
220226
$this->tmpDir = $options['tmpDir'];
221227
unset($options['tmpDir']);
222228
}
223-
$options = $this->processOptions($options);
229+
$options = $this->ensureUrlOrFileOptions($options);
224230
foreach ($options as $key => $val) {
225231
if (is_int($key)) {
226232
$this->_options[] = $val;
227-
} elseif ($key[0]!=='_' && property_exists($this, $key)) {
233+
} elseif ($key[0] !== '_' && property_exists($this, $key)) {
228234
$this->$key = $val;
229235
} else {
230236
$this->_options[$key] = $val;
@@ -287,7 +293,7 @@ protected function createPdf()
287293
$command->addArg($fileName, null, true); // Always escape filename
288294
if (!$command->execute()) {
289295
$this->_error = $command->getError();
290-
if (!(file_exists($fileName) && filesize($fileName)!==0 && $this->ignoreWarnings)) {
296+
if (!(file_exists($fileName) && filesize($fileName) !== 0 && $this->ignoreWarnings)) {
291297
return false;
292298
}
293299
}
@@ -296,35 +302,58 @@ protected function createPdf()
296302
}
297303

298304
/**
299-
* @param string $input
300-
* @param string|null $type a type hint if the input is a string of known type. This can either be
301-
* `TYPE_HTML` or `TYPE_XML`. If `null` (default) the type is auto detected from the string content.
302-
* @return \mikehaertl\tmp\File|string a File object if the input is a HTML or XML string. The unchanged input otherwhise.
305+
* This method creates a temporary file if the passed argument is neither a
306+
* File instance or URL nor contains XML or HTML and is also not a valid
307+
* file name.
308+
*
309+
* @param string|File $input the input argument File to check
310+
* @param string|null $type a type hint if the input is a string of known
311+
* type. This can either be `TYPE_HTML` or `TYPE_XML`. If `null` (default)
312+
* the type is auto detected from the string content.
313+
* @return \mikehaertl\tmp\File|string a File object if the input is a HTML
314+
* or XML string. The unchanged input otherwhise.
303315
*/
304-
protected function processInput($input, $type = null)
316+
protected function ensureUrlOrFile($input, $type = null)
305317
{
306-
if ($type === self::TYPE_HTML || $type === null && preg_match(self::REGEX_HTML, $input)) {
307-
return $this->_tmpFiles[] = new File($input, '.html', self::TMP_PREFIX, $this->tmpDir);
308-
} elseif ($type === self::TYPE_XML || preg_match(self::REGEX_XML, $input)) {
309-
return $this->_tmpFiles[] = new File($input, '.xml', self::TMP_PREFIX, $this->tmpDir);
310-
} else {
318+
if ($input instanceof File) {
319+
$this->_tmpFiles[] = $input;
320+
return $input;
321+
} elseif (preg_match(self::REGEX_URL, $input)) {
311322
return $input;
323+
} elseif ($type === self::TYPE_XML || $type === null && preg_match(self::REGEX_XML, $input)) {
324+
$ext = '.xml';
325+
} else {
326+
// First check for obvious HTML content to avoid is_file() as much
327+
// as possible as it can trigger open_basedir restriction warnings
328+
// with long strings.
329+
$isHtml = $type === self::TYPE_HTML || preg_match(self::REGEX_HTML, $input);
330+
if (!$isHtml) {
331+
$maxPathLen = defined('PHP_MAXPATHLEN') ?
332+
constant('PHP_MAXPATHLEN') : self::MAX_PATHLEN;
333+
if (strlen($input) <= $maxPathLen && is_file($input)) {
334+
return $input;
335+
}
336+
}
337+
$ext = '.html';
312338
}
339+
$file = new File($input, $ext, self::TMP_PREFIX, $this->tmpDir);
340+
$this->_tmpFiles[] = $file;
341+
return $file;
313342
}
314343

315344
/**
316345
* @param array $options list of options as name/value pairs
317-
* @return array options with raw content converted to tmp files where neccessary
346+
* @return array options with raw HTML/XML/String content converted to tmp
347+
* files where neccessary
318348
*/
319-
protected function processOptions($options = array())
349+
protected function ensureUrlOrFileOptions($options = array())
320350
{
321351
foreach ($options as $key => $val) {
322352
// Some options expect a URL or a file name, so check if we need a temp file
323353
if (is_string($val) && preg_match(self::REGEX_OPTS_TMPFILE, $key) ) {
324-
defined('PHP_MAXPATHLEN') || define('PHP_MAXPATHLEN', 255);
325-
$isFile = (strlen($val) <= PHP_MAXPATHLEN) ? is_file($val) : false;
326-
if (!($isFile || preg_match('/^(https?:)?\/\//i',$val) || $val === strip_tags($val))) {
327-
$options[$key] = new File($val, '.html', self::TMP_PREFIX, $this->tmpDir);
354+
$file = $this->ensureUrlOrFile($val);
355+
if ($file instanceof File) {
356+
$options[$key] = $file;
328357
}
329358
}
330359
}

tests/PdfTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?php
22
use mikehaertl\wkhtmlto\Pdf;
3+
use mikehaertl\tmp\File;
34

45
class PdfTest extends \PHPUnit\Framework\TestCase
56
{
@@ -129,6 +130,18 @@ public function testCanAddPageFromHtmlString()
129130
$this->assertRegexp('/tmp_wkhtmlto_pdf_.*?\.html/', $pdf->getCommand()->getExecCommand());
130131
unlink($outFile);
131132
}
133+
public function testCanAddPageFromFileInstance()
134+
{
135+
$outFile = $this->getOutFile();
136+
$binary = $this->getBinary();
137+
138+
$pdf = new Pdf;
139+
$pdf->binary = $binary;
140+
$pdf->addPage(new File('Some content', '.html'));
141+
$pdf->saveAs($outFile);
142+
$this->assertRegexp('/php_tmpfile_.*?\.html/', $pdf->getCommand()->getExecCommand());
143+
unlink($outFile);
144+
}
132145
public function testCanAddPageFromXmlString()
133146
{
134147
$outFile = $this->getOutFile();
@@ -353,6 +366,25 @@ public function testCanAddHeaderAndFooterAsHtml()
353366
$this->assertRegExp("#$binary --header-html '/tmp/[^ ]+' --footer-html '/tmp/[^ ]+' '$inFile' '$tmpFile'#", (string) $pdf->getCommand());
354367
unlink($outFile);
355368
}
369+
public function testCanAddHeaderAndFooterAsFile()
370+
{
371+
$inFile = $this->getHtmlAsset();
372+
$outFile = $this->getOutFile();
373+
$binary = $this->getBinary();
374+
375+
$pdf = new Pdf(array(
376+
'binary' => $binary,
377+
'header-html' => new File('Some header content', '.html'),
378+
'footer-html' => new File('Some footer content', '.html'),
379+
));
380+
$this->assertInstanceOf('mikehaertl\wkhtmlto\Pdf', $pdf->addPage($inFile));
381+
$this->assertTrue($pdf->saveAs($outFile));
382+
$this->assertFileExists($outFile);
383+
384+
$tmpFile = $pdf->getPdfFilename();
385+
$this->assertRegExp("#$binary --header-html '/tmp/[^ ]+' --footer-html '/tmp/[^ ]+' '$inFile' '$tmpFile'#", (string) $pdf->getCommand());
386+
unlink($outFile);
387+
}
356388
public function testCanAddHeaderAndFooterAsHtmlToPagesAndCoverAndToc()
357389
{
358390
$inFile = $this->getHtmlAsset();

0 commit comments

Comments
 (0)