Completed
Push — master ( afaf42...8ac772 )
by Raffael
20:10 queued 16:21
created

Burl::getResponseIntoResult()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 0
cts 7
cp 0
rs 9.8333
c 0
b 0
f 0
cc 3
nc 2
nop 2
crap 12
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * balloon
7
 *
8
 * @copyright   Copryright (c) 2012-2018 gyselroth GmbH (https://gyselroth.com)
9
 * @license     GPL-3.0 https://opensource.org/licenses/GPL-3.0
10
 */
11
12
namespace Balloon\App\Burl\Converter\Adapter;
13
14
use Balloon\Converter\Adapter\AdapterInterface;
15
use Balloon\Converter\Exception;
16
use Balloon\Converter\Result;
17
use Balloon\Filesystem\Node\File;
18
use GuzzleHttp\ClientInterface as GuzzleHttpClientInterface;
19
use GuzzleHttp\Psr7\Response;
20
use GuzzleHttp\Psr7\StreamWrapper;
21
use Imagick;
22
use Psr\Log\LoggerInterface;
23
24
class Burl implements AdapterInterface
25
{
26
    /**
27
     * Preview format.
28
     */
29
    const PREVIEW_FORMAT = 'png';
30
31
    /**
32
     * preview max size.
33
     *
34
     * @var int
35
     */
36
    protected $preview_max_size = 500;
37
38
    /**
39
     * GuzzleHttpClientInterface.
40
     *
41
     * @var GuzzleHttpClientInterface
42
     */
43
    protected $client;
44
45
    /**
46
     * LoggerInterface.
47
     *
48
     * @var LoggerInterface
49
     */
50
    protected $logger;
51
52
    /**
53
     * Browserlerss microservice url.
54
     *
55
     * @var string
56
     */
57
    protected $browserlessUrl = 'https://chrome.browserless.io';
58
59
    /**
60
     * Timeout.
61
     *
62
     * @var string
63
     */
64
    protected $timeout = '10';
65
66
    /**
67
     * Formats.
68
     *
69
     * @var array
70
     */
71
    protected $formats = [
72
        'burl' => 'application/vnd.balloon.burl',
73
    ];
74
75
    /**
76
     * One way formats.
77
     *
78
     * @param array
79
     */
80
    protected $target_formats = [
81
        'pdf' => 'application/pdf',
82
        'jpg' => 'image/jpeg',
83
        'jpeg' => 'image/jpeg',
84
        'png' => 'image/png',
85
    ];
86
87
    /**
88
     * Initialize.
89
     *
90
     * @param iterable $config
91
     */
92
    public function __construct(GuzzleHttpClientInterface $client, LoggerInterface $logger, ?Iterable $config = null)
93
    {
94
        $this->client = $client;
95
        $this->logger = $logger;
96
        $this->setOptions($config);
97
    }
98
99
    /**
100
     * Set options.
101
     *
102
     * @param iterable $config
103
     */
104
    public function setOptions(Iterable $config = null): AdapterInterface
105
    {
106
        if (null === $config) {
107
            return $this;
108
        }
109
110
        foreach ($config as $option => $value) {
111
            switch ($option) {
112
                case 'browserlessUrl':
113
                    if (!filter_var($value, FILTER_VALIDATE_URL)) {
114
                        throw new Exception('browserlessUrl option must be a valid url to a browserless instance');
115
                    }
116
117
                    $this->browserlessUrl = (string) $value;
118
119
                    break;
120
                case 'timeout':
121
                    if (!is_numeric($value)) {
122
                        throw new Exception('timeout option must be a number');
123
                    }
124
125
                    $this->timeout = (string) $value;
126
127
                    break;
128
                case 'preview_max_size':
129
                    $this->preview_max_size = (int) $value;
130
131
                    break;
132
                default:
133
                    throw new Exception('invalid option '.$option.' given');
134
            }
135
        }
136
137
        return $this;
138
    }
139
140
    /**
141
     * {@inheritdoc}
142
     */
143
    public function match(File $file): bool
144
    {
145
        foreach ($this->formats as $format => $mimetype) {
146
            if ($file->getContentType() === $mimetype) {
147
                return true;
148
            }
149
        }
150
151
        return false;
152
    }
153
154
    /**
155
     * {@inheritdoc}
156
     */
157
    public function matchPreview(File $file): bool
158
    {
159
        return $this->match($file);
160
    }
161
162
    /**
163
     * {@inheritdoc}
164
     */
165
    public function getSupportedFormats(File $file): array
166
    {
167
        return array_keys($this->target_formats);
168
    }
169
170
    /**
171
     * {@inheritdoc}
172
     */
173
    public function createPreview(File $file): Result
174
    {
175
        try {
176
            $imageFile = $this->getImage(\stream_get_contents($file->get()), self::PREVIEW_FORMAT);
177
        } catch (Exception $e) {
178
            throw new Exception('failed create preview');
179
        }
180
        $image = new Imagick($imageFile->getPath());
181
182
        $width = $image->getImageWidth();
183
        $height = $image->getImageHeight();
184
185
        if ($height <= $width && $width > $this->preview_max_size) {
186
            $image->scaleImage($this->preview_max_size, 0);
187
        } elseif ($height > $this->preview_max_size) {
188
            $image->scaleImage(0, $this->preview_max_size);
189
        }
190
191
        $image->writeImage($imageFile->getPath());
192
193
        return $imageFile;
194
    }
195
196
    /**
197
     * {@inheritdoc}
198
     */
199
    public function convert(File $file, string $format): Result
200
    {
201
        switch ($format) {
202
            case 'pdf':
203
                return $this->getPdf(\stream_get_contents($file->get()));
204
            case 'png':
205
                return $this->getImage(\stream_get_contents($file->get()), 'png');
206
            case 'jpg':
207
            case 'jpeg':
208
                return $this->getImage(\stream_get_contents($file->get()), 'jpeg');
209
            default:
210
                throw new Exception('target format ['.$format.'] not supported');
211
        }
212
    }
213
214
    /**
215
     * Get screenshot of url.
216
     */
217
    protected function getImage(string $url, string $format): Result
218
    {
219
        $options = [
220
            'fullPage' => false,
221
            'type' => $format,
222
        ];
223
        if ('jpeg' === $format) {
224
            $options['quality'] = 75;
225
        }
226
227
        $response = $this->client->request(
228
            'POST',
229
            $this->browserlessUrl.'/screenshot',
230
            [
231
                'connect_timeout' => $this->timeout,
232
                'timeout' => $this->timeout,
233
                'json' => [
234
                    'url' => $url,
235
                    'options' => $options,
236
                ],
237
            ]
238
        );
239
240
        return $this->getResponseIntoResult($response, $format);
241
    }
242
243
    /**
244
     * Get pdf of url contents.
245
     */
246
    protected function getPdf(string $url): Result
247
    {
248
        $response = $this->client->request(
249
            'POST',
250
            $this->browserlessUrl.'/pdf',
251
            [
252
                'json' => [
253
                    'url' => $url,
254
                    'options' => [
255
                        'printBackground' => false,
256
                        'format' => 'A4',
257
                    ],
258
                ],
259
            ]
260
        );
261
262
        return $this->getResponseIntoResult($response, 'pdf');
263
    }
264
265
    /**
266
     * Turn PSR7-Response into a Result.
267
     */
268
    protected function getResponseIntoResult(Response $response, string $format): Result
269
    {
270
        $desth = tmpfile();
271
        $dest = stream_get_meta_data($desth)['uri'];
272
273
        stream_copy_to_stream(StreamWrapper::getResource($response->getBody()), $desth);
274
275
        if (!file_exists($dest) || filesize($dest) <= 0) {
276
            throw new Exception('failed get '.$format);
277
        }
278
279
        return new Result($dest, $desth);
280
    }
281
}
282