Completed
Pull Request — master (#200)
by Raffael
16:31
created

Burl::setOptions()   B

Complexity

Conditions 8
Paths 8

Size

Total Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
dl 0
loc 35
ccs 0
cts 20
cp 0
rs 8.1155
c 0
b 0
f 0
cc 8
nc 8
nop 1
crap 72
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);
0 ignored issues
show
Compatibility introduced by
$response of type object<Psr\Http\Message\ResponseInterface> is not a sub-type of object<GuzzleHttp\Psr7\Response>. It seems like you assume a concrete implementation of the interface Psr\Http\Message\ResponseInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
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');
0 ignored issues
show
Compatibility introduced by
$response of type object<Psr\Http\Message\ResponseInterface> is not a sub-type of object<GuzzleHttp\Psr7\Response>. It seems like you assume a concrete implementation of the interface Psr\Http\Message\ResponseInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
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