Wkhtml   A
last analyzed

Complexity

Total Complexity 31

Size/Duplication

Total Lines 317
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 3

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 31
lcom 3
cbo 3
dl 0
loc 317
ccs 0
cts 164
cp 0
rs 9.8
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 17 3
B findWkhtml() 0 16 5
B findXvfb() 0 16 5
A wkhtmlBinary() 0 7 1
A wkhtmlArgs() 0 6 1
A xvfbBinary() 0 7 1
A xvfbArgs() 0 6 1
A fromUri() 0 9 1
A fromFile() 0 9 1
A fromString() 0 9 1
A orientation() 0 9 1
B generate() 0 38 4
A toBrowser() 0 9 1
A toFile() 0 11 2
A toString() 0 7 1
A error() 0 6 2
1
<?php declare(strict_types=1);
2
3
namespace Terah\Wkhtml;
4
5
use function Terah\Assert\Assert;
6
7
use Psr\Log\LoggerAwareTrait;
8
9
class Wkhtml
10
{
11
    use LoggerAwareTrait;
12
13
    /** @var string */
14
    protected $tempPath     = '';
15
16
    /** @var string */
17
    protected $tempFile     = '';
18
19
    /** @var bool */
20
    protected $doCacheFile  = true;
21
22
    /** @var string */
23
    protected $wkhtmlBinary = '';
24
25
    /** @var string  */
26
    protected $wkhtmlArgs   = '';
27
28
    /** @var string */
29
    protected $orientation  = 'portrait';
30
31
    /** @var string */
32
    protected $uriSource    = '';
33
34
    /** @var string */
35
    protected $fileSource   = '';
36
37
    /** @var string */
38
    protected $stringSource = '';
39
40
    /** @var string */
41
    protected $content      = '';
42
43
    /** @var bool */
44
    protected $generated    = false;
45
46
    /** @var array */
47
    protected $xvfbLocations = [
48
        '/usr/bin/xvfb-run', // Ubuntu
49
    ];
50
51
    /** @var null|string */
52
    protected $xvfbBinary   = '/usr/bin/xvfb-run';
53
54
    /** @var null|string */
55
    protected $xvfbArgs     = '--server-args="-screen 0, 1024x768x24"';
56
    protected $xvfbLog      = null;
57
58
    /** @var array */
59
    protected $wkhtmlLocations = [
60
        '/usr/bin/wkhtmltopdf', // Ubuntu
61
        '/usr/local/bin/wkhtmltopdf', // Mac
62
    ];
63
64
    /**
65
     * @param array $settings
66
     */
67
    public function __construct(array $settings)
68
    {
69
        $wkhtmltopdf            = $xvfb = $xvfb_args = '';
70
        extract($settings);
71
        $wkhtmltopdf            = $this->findWkhtml($wkhtmltopdf);
72
        Assert($wkhtmltopdf)->notEmpty('Could not find the wkhtmltopdf binary installed on your system.')->file('Could not find the wkhtmltopdf binary installed on your system.');
73
        $this->wkhtmlBinary     = $wkhtmltopdf;
74
        if ( $xvfb === false )
75
        {
76
            $this->xvfbBinary       = $this->xvfbArgs = '';
77
        }
78
        if ( $xvfb )
79
        {
80
            $this->xvfbBinary($this->findXvfb($xvfb));
81
            $this->xvfbArgs         = $xvfb_args;
82
        }
83
    }
84
85
    /**
86
     * @param string $wkhtmlBinary
87
     * @return string
88
     */
89
    protected function findWkhtml(string $wkhtmlBinary) : string
90
    {
91
        if ( $wkhtmlBinary && file_exists($wkhtmlBinary) )
92
        {
93
            return $wkhtmlBinary;
94
        }
95
        foreach ( $this->wkhtmlLocations as $path )
96
        {
97
            if ( file_exists($path) )
98
            {
99
                return $path;
100
            }
101
        }
102
103
        return '';
104
    }
105
106
    /**
107
     * @param string $xvfbBinary
108
     * @return string
109
     */
110
    protected function findXvfb(string $xvfbBinary='') : string
111
    {
112
        if ( $xvfbBinary && file_exists($xvfbBinary) )
113
        {
114
            return $xvfbBinary;
115
        }
116
        foreach ( $this->xvfbLocations as $path )
117
        {
118
            if ( file_exists($path) )
119
            {
120
                return $path;
121
            }
122
        }
123
124
        return '';
125
    }
126
127
    /**
128
     * @param string $wkhtmlBinary
129
     * @return Wkhtml
130
     */
131
    public function wkhtmlBinary(string $wkhtmlBinary) : Wkhtml
132
    {
133
        Assert($wkhtmlBinary)->file("The wkhtml binary does not exist.");
134
        $this->wkhtmlBinary = $wkhtmlBinary;
135
136
        return $this;
137
    }
138
139
    /**
140
     * @param string $wkhtmlArgs
141
     * @return Wkhtml
142
     */
143
    public function wkhtmlArgs(string $wkhtmlArgs) : Wkhtml
144
    {
145
        $this->wkhtmlArgs = $wkhtmlArgs;
146
147
        return $this;
148
    }
149
150
    /**
151
     * @param string $xvfbBinary
152
     * @return Wkhtml
153
     */
154
    public function xvfbBinary(string $xvfbBinary) : Wkhtml
155
    {
156
        Assert($xvfbBinary)->file("The xvfb binary does not exist.");
157
        $this->xvfbBinary = $xvfbBinary;
158
159
        return $this;
160
    }
161
162
    /**
163
     * @param string $xvfbArgs
164
     * @return Wkhtml
165
     */
166
    public function xvfbArgs(string $xvfbArgs) : Wkhtml
167
    {
168
        $this->xvfbArgs = $xvfbArgs;
169
170
        return $this;
171
    }
172
173
    /**
174
     * @param string $source
175
     * @return Wkhtml
176
     */
177
    public function fromUri(string $source) : Wkhtml
178
    {
179
        $this->uriSource    = $source;
180
        $this->fileSource   = '';
181
        $this->stringSource = '';
182
        $this->generated    = false;
183
184
        return $this;
185
    }
186
187
    /**
188
     * @param string $source
189
     * @return Wkhtml
190
     */
191
    public function fromFile(string $source) : Wkhtml
192
    {
193
        $this->uriSource    = '';
194
        $this->fileSource   = $source;
195
        $this->stringSource = '';
196
        $this->generated    = false;
197
198
        return $this;
199
    }
200
201
    /**
202
     * @param string $source
203
     * @return Wkhtml
204
     */
205
    public function fromString(string $source) : Wkhtml
206
    {
207
        $this->uriSource    = '';
208
        $this->fileSource   = '';
209
        $this->stringSource = $source;
210
        $this->generated    = false;
211
212
        return $this;
213
    }
214
215
    /**
216
     * @param string $orientation
217
     * @return Wkhtml
218
     */
219
    public function orientation(string $orientation) : Wkhtml
220
    {
221
        $orientation        = ucfirst(strtolower($orientation));
222
        Assert($orientation)->inArray(['Landscape', 'Portrait'], "The orientation must be either landscape or portrait.");
223
        $this->orientation  = $orientation;
224
        $this->generated    = false;
225
226
        return $this;
227
    }
228
229
    /**
230
     * @return Wkhtml
231
     * @throws \Exception
232
     */
233
    public function generate()
234
    {
235
        if ( $this->generated )
236
        {
237
            return $this;
238
        }
239
        $descriptorSpec     = [
240
            0                   => ['pipe', 'r'], // stdin
241
            1                   => ['pipe', 'w'], // stdout
242
            2                   => ['pipe', 'w'], // stderr
243
        ];
244
245
        $orientation    = '--orientation ' . escapeshellarg($this->orientation);
246
        $wkhtmlBinary   = escapeshellarg($this->wkhtmlBinary);
247
        $command        = "{$wkhtmlBinary} -q {$orientation} {$this->wkhtmlArgs} - -";
248
        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...
249
        {
250
            $command        = str_replace('wkhtmltopdf', 'wkhtmltopdf --use-xserver --page-size A4', $command);
251
            $command        = "{$this->xvfbBinary} {$this->xvfbArgs} {$command}";
252
        }
253
        $process        = proc_open($command, $descriptorSpec, $pipes);
254
        fwrite($pipes[0], $this->stringSource);
255
        fclose($pipes[0]);
256
        // Read the outputs
257
        $this->content  = stream_get_contents($pipes[1]);
258
        $errors         = stream_get_contents($pipes[2]);
259
        // Close the process
260
        fclose($pipes[1]);
261
        proc_close($process);
262
263
        if ( $errors )
264
        {
265
            $this->error('Failed to generate PDF. Command: ' . $command, [$errors]);
266
        }
267
        $this->generated = true;
268
269
        return $this;
270
    }
271
272
    /**
273
     * @param string|null $fileName
274
     */
275
    public function toBrowser(string $fileName='pdf_file')
276
    {
277
        $this->generate();
278
        header("Content-Disposition:attachment;filename='{$fileName}'");
279
        header('Content-type: application/pdf');
280
        echo $this->content;
281
282
        exit;
283
    }
284
285
    /**
286
     * @param string $fullPath
287
     * @return bool
288
     * @throws \Exception
289
     */
290
    public function toFile(string $fullPath) : bool
291
    {
292
        $this->generate();
293
        Assert($this->content)->notEmpty("The generated pdf file is empty");
294
        if ( ! file_put_contents($fullPath, $this->content) )
295
        {
296
            $this->error("Failed to copy PDF to {$fullPath}");
297
        }
298
299
        return true;
300
    }
301
302
    /**
303
     * @return string
304
     * @throws \Exception
305
     */
306
    public function toString() : string
307
    {
308
        $this->generate();
309
        Assert($this->content)->notEmpty("The generated pdf file is empty");
310
311
        return $this->content;
312
    }
313
314
    /**
315
     * @param string $message
316
     * @param array $context
317
     * @throws \Exception
318
     */
319
    protected function error(string $message, array $context=[])
320
    {
321
        $this->logger->error($message, $context);
322
        $context = $context ? ": " . print_r($context, true) : '';
323
        throw new \Exception($message . $context);
324
    }
325
}
326