Completed
Push — master ( 138ae0...2b8ade )
by Terry
03:59
created

Wkhtml::wkhtmlArgs()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 5
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
3
namespace Terah\Wkhtml;
4
5
use function Terah\Assert\Assert;
6
use function Terah\Assert\Validate;
7
8
use Terah\ColourLog\LoggerTrait;
9
10
class Wkhtml
11
{
12
    use LoggerTrait;
13
14
    /** @var string */
15
    protected $tempPath     = '';
16
17
    /** @var string */
18
    protected $tempFile     = '';
19
20
    /** @var bool */
21
    protected $doCacheFile  = true;
22
23
    /** @var string */
24
    protected $wkhtmlBinary = '';
25
26
    /** @var string  */
27
    protected $wkhtmlArgs   = '';
28
29
    /** @var string */
30
    protected $orientation  = 'portrait';
31
32
    /** @var string */
33
    protected $uriSource    = '';
34
35
    /** @var string */
36
    protected $fileSource   = '';
37
38
    /** @var string */
39
    protected $stringSource = '';
40
41
    /** @var string */
42
    protected $content      = '';
43
44
    /** @var bool */
45
    protected $generated    = false;
46
47
    /** @var array */
48
    protected $xvfbLocations = [
49
        '/usr/bin/xvfb-run', // Ubuntu
50
    ];
51
52
    /** @var null|string */
53
    protected $xvfbBinary   = '/usr/bin/xvfb-run';
54
55
    /** @var null|string */
56
    protected $xvfbArgs     = '--server-args="-screen 0, 1024x768x24"';
57
    protected $xvfbLog      = null;
58
59
    /** @var array */
60
    protected $wkhtmlLocations = [
61
        '/usr/bin/wkhtmltopdf', // Ubuntu
62
        '/usr/local/bin/wkhtmltopdf', // Mac
63
    ];
64
65
    /**
66
     * @param array $settings
67
     */
68
    public function __construct(array $settings)
69
    {
70
        $wkhtmltopdf = $xvfb = $xvfb_args = null;
71
        extract($settings);
72
        $wkhtmltopdf = $this->findWkhtml($wkhtmltopdf);
73
        Assert($wkhtmltopdf)->notEmpty()->file();
74
        $this->wkhtmlBinary = $wkhtmltopdf;
75
        if ( $xvfb === false )
76
        {
77
            $this->xvfbBinary = $this->xvfbArgs = '';
78
        }
79
        if ( $xvfb )
80
        {
81
            $this->xvfbBinary($this->findXvfb($xvfb));
82
            $this->xvfbArgs     = $xvfb_args;
83
        }
84
        $this->tempPath(sys_get_temp_dir());
85
        $this->tempFile(str_replace($this->tempPath, '', tempnam($this->tempPath, 'pdf')));
86
    }
87
88
    /**
89
     * @param string $wkhtmlBinary
90
     * @return string|null
91
     */
92
    protected function findWkhtml($wkhtmlBinary)
93
    {
94
        if ( ! is_null($wkhtmlBinary) && file_exists($wkhtmlBinary) )
95
        {
96
            return $wkhtmlBinary;
97
        }
98
        foreach ( $this->wkhtmlLocations as $path )
99
        {
100
            if ( file_exists($path) )
101
            {
102
                return $path;
103
            }
104
        }
105
        return null;
106
    }
107
108
    /**
109
     * @param string $xvfbBinary
110
     * @return string|null
111
     */
112
    protected function findXvfb($xvfbBinary=null)
113
    {
114
        if ( ! is_null($xvfbBinary) && file_exists($xvfbBinary) )
115
        {
116
            return $xvfbBinary;
117
        }
118
        foreach ( $this->xvfbLocations as $path )
119
        {
120
            if ( file_exists($path) )
121
            {
122
                return $path;
123
            }
124
        }
125
        return null;
126
    }
127
128
    /**
129
     * @param string $wkhtmlBinary
130
     * @return $this
131
     */
132
    public function wkhtmlBinary($wkhtmlBinary)
133
    {
134
        Assert($wkhtmlBinary)->file("The wkhtml binary does not exist.");
135
        $this->wkhtmlBinary = $wkhtmlBinary;
136
        return $this;
137
    }
138
139
    /**
140
     * @param string $wkhtmlArgs
141
     * @return $this
142
     */
143
    public function wkhtmlArgs($wkhtmlArgs)
144
    {
145
        $this->wkhtmlArgs = $wkhtmlArgs;
146
        return $this;
147
    }
148
149
    /**
150
     * @param string $xvfbBinary
151
     * @return $this
152
     */
153
    public function xvfbBinary($xvfbBinary)
154
    {
155
        Assert($xvfbBinary)->file("The xvfb binary does not exist.");
156
        $this->xvfbBinary = $xvfbBinary;
157
        return $this;
158
    }
159
160
    /**
161
     * @param string $xvfbArgs
162
     * @return $this
163
     */
164
    public function xvfbArgs($xvfbArgs)
165
    {
166
        $this->xvfbArgs = $xvfbArgs;
167
        return $this;
168
    }
169
170
    /**
171
     * @param string $tempPath
172
     * @return $this
173
     */
174
    public function tempPath($tempPath)
175
    {
176
        Assert($tempPath)
177
            ->notEmpty('tmp_path cannot be empty')
178
            ->directory('tmp_path must be a directory')
179
            ->writeable('tmp_path must be writable.');
180
        $sep                = DIRECTORY_SEPARATOR;
181
        $this->tempPath     = str_replace($sep . $sep, $sep, $tempPath . $sep);
182
        $this->generated    = false;
183
        return $this;
184
    }
185
186
    /**
187
     * @param string $tempFile
188
     * @param bool $doCacheFile
189
     * @return $this
190
     */
191
    public function tempFile($tempFile, $doCacheFile=true)
192
    {
193
        $tempFile = str_replace(DIRECTORY_SEPARATOR, '', $tempFile);
194
        Assert($tempFile)->notEmpty('tmp_path cannot be empty');
195
        Assert($doCacheFile)->boolean();
196
        $this->tempFile     = $tempFile;
197
        $this->doCacheFile  = false;
198
        $this->generated    = false;
199
        return $this;
200
    }
201
202
    /**
203
     * @param string $source
204
     * @return $this
205
     */
206
    public function fromUri($source)
207
    {
208
        $this->uriSource    = $source;
209
        $this->fileSource   = '';
210
        $this->stringSource = '';
211
        $this->generated    = false;
212
        return $this;
213
    }
214
215
    /**
216
     * @param string $source
217
     * @return $this
218
     */
219
    public function fromFile($source)
220
    {
221
        $this->uriSource    = '';
222
        $this->fileSource   = $source;
223
        $this->stringSource = '';
224
        $this->generated    = false;
225
        return $this;
226
    }
227
228
    /**
229
     * @param string $source
230
     * @return $this
231
     */
232
    public function fromString($source)
233
    {
234
        $this->uriSource    = '';
235
        $this->fileSource   = '';
236
        $this->stringSource = $source;
237
        $this->generated    = false;
238
        return $this;
239
    }
240
241
    /**
242
     * @param string $orientation
243
     * @return $this
244
     */
245
    public function orientation($orientation)
246
    {
247
        $orientation        = ucfirst(strtolower($orientation));
248
        Assert($orientation)->inArray(['Landscape', 'Portrait'], "The orientation must be either landscape or portrait.");
249
        $this->orientation  = $orientation;
250
        $this->generated    = false;
251
        return $this;
252
    }
253
254
    /**
255
     * @return $this|bool
256
     * @throws \Exception
257
     */
258
    public function generate()
259
    {
260
        if ( $this->generated )
261
        {
262
            return true;
263
        }
264
        $fullTempPath   = $this->getTempFullPath();
265
        Assert($fullTempPath)->notEmpty();
266
        $this->saveStringSourceToFile($fullTempPath);
267
        $source         = $this->uriSource;
268
        if ( $this->fileSource )
269
        {
270
            $source         = $this->fileSource;
271
            clearstatcache();
272
            Assert($source)->file('A PDF source file does not exist.');
273
        }
274
        $orientation    = '--orientation ' . escapeshellarg($this->orientation);
275
        $source         = escapeshellarg($source);
276
        $fullTempPath   = escapeshellarg($fullTempPath);
277
        $command        = "{$this->wkhtmlBinary} -q {$orientation} {$this->wkhtmlArgs} {$source} {$fullTempPath}";
278
        if ( $this->xvfbBinary )
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->xvfbBinary of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
279
        {
280
            $command    = str_replace('wkhtmltopdf', 'wkhtmltopdf --use-xserver --page-size A4', $command);
281
            $command    = "{$this->xvfbBinary} {$this->xvfbArgs} {$command}";
282
        }
283
        exec($command, $output, $result);
284
        if ( $result !== 0 )
285
        {
286
            $this->error('Failed to generate PDF. Command: ' . $command, $output);
287
        }
288
        $this->generated = true;
289
        return $this;
290
    }
291
292
    /**
293
     * @param string $fullTempPath
294
     * @return bool
295
     * @throws \Exception
296
     */
297
    protected function saveStringSourceToFile($fullTempPath)
298
    {
299
        if ( ! empty($this->stringSource) )
300
        {
301
            $this->fileSource = $fullTempPath . '.html';
302
            if ( ! file_put_contents($this->fileSource, $this->stringSource) )
303
            {
304
                $this->error("Failed to save tmp file ({$this->fileSource}).");
305
            }
306
        }
307
        return true;
308
    }
309
310
    /**
311
     * @param string|null $fileName
312
     */
313
    public function toBrowser($fileName=null)
314
    {
315
        $this->generate();
316
        $fileName = $fileName ? $fileName : $this->tempFile;
317
        header("Content-Disposition:attachment;filename='{$fileName}'");
318
        header('Content-type: application/pdf');
319
        readfile($this->getTempFullPath());
320
        exit;
321
    }
322
323
    /**
324
     * @param string $fullPath
325
     * @return bool
326
     * @throws \Exception
327
     */
328
    public function toFile($fullPath)
329
    {
330
        $this->generate();
331
        $tempFile = $this->getTempFullPath();
332
        if ( ! copy($tempFile, $fullPath) )
333
        {
334
            $this->error("Failed to copy PDF from {$tempFile} to {$fullPath}");
335
        }
336
        return true;
337
    }
338
339
    /**
340
     * @return string
341
     * @throws \Exception
342
     */
343
    public function toString()
344
    {
345
        $this->generate();
346
        $tempFile = $this->getTempFullPath();
347
        $fileData =  file_get_contents($tempFile);
348
        Assert($fileData)->notEmpty("The generated pdf file is empty");
349
        return $fileData;
350
    }
351
352
    /**
353
     * @return string
354
     */
355
    protected function getTempFullPath()
356
    {
357
        return $this->tempPath . $this->tempFile;
358
    }
359
360
    /**
361
     * @param string $message
362
     * @param array $context
363
     * @throws \Exception
364
     */
365
    protected function error($message, array $context=[])
366
    {
367
        $this->logger->error($message, $context);
368
        $context = $context ? ": " . print_r($context, true) : '';
369
        throw new \Exception($message . $context);
370
    }
371
}
372