1<?php
2// Icinga PDF Export | (c) 2018 Icinga GmbH | GPLv2
3
4namespace Icinga\Module\Pdfexport;
5
6use Icinga\File\Storage\TemporaryLocalFileStorage;
7
8class HeadlessChrome
9{
10    /** @var string Path to the Chrome binary */
11    protected $binary;
12
13    /** @var string Target Url */
14    protected $url;
15
16    /** @var TemporaryLocalFileStorage */
17    protected $fileStorage;
18
19    public function __construct()
20    {
21        $this->fileStorage = new TemporaryLocalFileStorage();
22    }
23
24    /**
25     * Get the path to the Chrome binary
26     *
27     * @return  string
28     */
29    public function getBinary()
30    {
31        return $this->binary;
32    }
33
34    /**
35     * Set the path to the Chrome binary
36     *
37     * @param   string  $binary
38     *
39     * @return  $this
40     */
41    public function setBinary($binary)
42    {
43        $this->binary = $binary;
44
45        return $this;
46    }
47
48    /**
49     * Render the given argument name-value pairs as shell-escaped string
50     *
51     * @param   array   $arguments
52     *
53     * @return  string
54     */
55    public static function renderArgumentList(array $arguments)
56    {
57        $list = [];
58
59        foreach ($arguments as $name => $value) {
60            if ($value !== null) {
61                $value = escapeshellarg($value);
62
63                if (! is_int($name)) {
64                    if (substr($name, -1) === '=') {
65                        $glue = '';
66                    } else {
67                        $glue = ' ';
68                    }
69
70                    $list[] = escapeshellarg($name) . $glue . $value;
71                } else {
72                    $list[] = $value;
73                }
74            } else {
75                $list[] = escapeshellarg($name);
76            }
77        }
78
79        return implode(' ', $list);
80    }
81
82    /**
83     * Get the target Url
84     *
85     * @return  string
86     */
87    public function getUrl()
88    {
89        return $this->url;
90    }
91
92    /**
93     * Set the target Url
94     *
95     * @param   string  $url
96     *
97     * @return  $this
98     */
99    public function setUrl($url)
100    {
101        $this->url = $url;
102
103        return $this;
104    }
105
106    /**
107     * Use the given HTML string as input
108     *
109     * @param   string  $html
110     * @param   bool    $asFile
111     *
112     * @return  $this
113     */
114    public function fromHtml($html, $asFile = true)
115    {
116        if ($asFile) {
117            $path = uniqid('icingaweb2-pdfexport-') . '.html';
118
119            $this->fileStorage->create($path, $html);
120
121            $path = $this->fileStorage->resolvePath($path, true);
122
123            $this->setUrl("file://$path");
124        } else {
125            $this->setUrl('data:text/html,' . rawurlencode($html));
126        }
127
128        return $this;
129    }
130
131    /**
132     * Export to PDF
133     *
134     * @param   $filename
135     *
136     * @return  string
137     */
138    public function toPdf($filename)
139    {
140        $path = uniqid('icingaweb2-pdfexport-') . $filename;
141
142        $this->fileStorage->create($path, '');
143
144        $path = $this->fileStorage->resolvePath($path, true);
145
146        $arguments = [
147            '--headless',
148            '--disable-gpu',
149            '--no-sandbox',
150            '--print-to-pdf=' => $path,
151            $this->getUrl()
152        ];
153
154        $command = new ShellCommand(
155            escapeshellarg($this->getBinary()) . ' ' . static::renderArgumentList($arguments),
156            false
157        );
158
159        $command->execute();
160
161        return $path;
162    }
163
164    /**
165     * Get the major version number of Chrome or false on failure
166     *
167     * @return  bool|int
168     */
169    public function getVersion()
170    {
171        $command = new ShellCommand(
172            escapeshellarg($this->getBinary()) . ' ' . static::renderArgumentList(['--version']),
173            false
174        );
175
176        $output = $command->execute();
177
178        if ($command->getExitCode() !== 0) {
179            return false;
180        }
181
182        $parts = explode(' ', trim($output->stdout));
183
184        $version = (int) array_pop($parts);
185
186        return $version;
187    }
188}
189