Passed
Branch feature/refactoring (13cbf0)
by Alexey
03:46
created

GoogleImage::getImageExtension()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 8.125

Importance

Changes 0
Metric Value
eloc 11
c 0
b 0
f 0
dl 0
loc 17
ccs 5
cts 10
cp 0.5
rs 9.6111
cc 5
nc 5
nop 1
crap 8.125
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @author   Ne-Lexa
7
 * @license  MIT
8
 *
9
 * @see      https://github.com/Ne-Lexa/google-play-scraper
10
 */
11
12
namespace Nelexa\GPlay\Model;
13
14
use GuzzleHttp\RequestOptions;
15
use Nelexa\GPlay\Exception\GooglePlayException;
16
use Nelexa\GPlay\Util\LazyStream;
17
use Nelexa\HttpClient\HttpClient;
18
use Psr\Http\Message\ResponseInterface;
19
20
/**
21
 * Contains a link to the image, allows you to customize its size and download it.
22
 *
23
 * This class only works with images stored on googleusercontent.com.
24
 * To modify the image, special parameters are added to the URL, using a hyphen.
25
 *
26
 * **Supported parameters:**
27
 *
28
 * | Param | Name         | Description                                     | Example                       |
29
 * | :---: |:------------ | :---------------------------------------------- | ----------------------------: |
30
 * | sN | size            | Sets the longer of height or width to N pixels  | s70 ![][_s] ![][_s2] ![][_s3] |
31
 * | wN | width           | Sets width of image to N pixels                 | w70 ![][_w] ![][_w2] ![][_w3] |
32
 * | hN | height          | Sets height of image to N pixels                | h70 ![][_h] ![][_h2] ![][_h3] |
33
 * | c  | square crop     | Sets square crop                   | w40-h70-c ![][_c1.1] ![][_c1.2] ![][_c1.3] |
34
 * |    |                 |                                    | w70-h40-c ![][_c2.1] ![][_c2.2] ![][_c2.3] |
35
 * |    |                 |                                    | w70-h70-c ![][_c3.1] ![][_c3.2] ![][_c3.3] |
36
 * | p  | smart crop      | Sets smart crop                    | w40-h70-p ![][_p1.1] ![][_p1.2] ![][_p1.3] |
37
 * |    |                 |                                    | w70-h40-p ![][_p2.1] ![][_p2.2] ![][_p2.3] |
38
 * |    |                 |                                    | w70-h70-p ![][_p3.1] ![][_p3.2] ![][_p3.3] |
39
 * | bN | border          | Sets border of image to N pixels            | s70-b10 ![][_b] ![][_b2] ![][_b3] |
40
 * | fv | vertical flip   | Vertically flips the image                | s70-fv ![][_fv] ![][_fv2] ![][_fv3] |
41
 * | fh | horizontal flip | Horizontally flips the image              | s70-fh ![][_fh] ![][_fh2] ![][_fh3] |
42
 *
43
 * [_s]:https://lh3.googleusercontent.com/6EtT4dght1QF9-XYvSiwx2uqkBiOnrwq-N-dPZLUw4x61Bh2Bp_w6BZ_d0dZPoTBVqM=s70
44
 * [_w]:https://lh3.googleusercontent.com/6EtT4dght1QF9-XYvSiwx2uqkBiOnrwq-N-dPZLUw4x61Bh2Bp_w6BZ_d0dZPoTBVqM=w70
45
 * [_h]:https://lh3.googleusercontent.com/6EtT4dght1QF9-XYvSiwx2uqkBiOnrwq-N-dPZLUw4x61Bh2Bp_w6BZ_d0dZPoTBVqM=h70
46
 * [_c1.1]:https://lh3.googleusercontent.com/6EtT4dght1QF9-XYvSiwx2uqkBiOnrwq-N-dPZLUw4x61Bh2Bp_w6BZ_d0dZPoTBVqM=w40-h70-c
47
 * [_c2.1]:https://lh3.googleusercontent.com/6EtT4dght1QF9-XYvSiwx2uqkBiOnrwq-N-dPZLUw4x61Bh2Bp_w6BZ_d0dZPoTBVqM=w70-h40-c
48
 * [_c3.1]:https://lh3.googleusercontent.com/6EtT4dght1QF9-XYvSiwx2uqkBiOnrwq-N-dPZLUw4x61Bh2Bp_w6BZ_d0dZPoTBVqM=w70-h70-c
49
 * [_p1.1]:https://lh3.googleusercontent.com/6EtT4dght1QF9-XYvSiwx2uqkBiOnrwq-N-dPZLUw4x61Bh2Bp_w6BZ_d0dZPoTBVqM=w40-h70-p
50
 * [_p2.1]:https://lh3.googleusercontent.com/6EtT4dght1QF9-XYvSiwx2uqkBiOnrwq-N-dPZLUw4x61Bh2Bp_w6BZ_d0dZPoTBVqM=w70-h40-p
51
 * [_p3.1]:https://lh3.googleusercontent.com/6EtT4dght1QF9-XYvSiwx2uqkBiOnrwq-N-dPZLUw4x61Bh2Bp_w6BZ_d0dZPoTBVqM=w70-h70-p
52
 * [_b]:https://lh3.googleusercontent.com/6EtT4dght1QF9-XYvSiwx2uqkBiOnrwq-N-dPZLUw4x61Bh2Bp_w6BZ_d0dZPoTBVqM=s70-b10
53
 * [_fv]:https://lh3.googleusercontent.com/6EtT4dght1QF9-XYvSiwx2uqkBiOnrwq-N-dPZLUw4x61Bh2Bp_w6BZ_d0dZPoTBVqM=s70-fv
54
 * [_fh]:https://lh3.googleusercontent.com/6EtT4dght1QF9-XYvSiwx2uqkBiOnrwq-N-dPZLUw4x61Bh2Bp_w6BZ_d0dZPoTBVqM=s70-fh
55
 *
56
 * [_s2]:https://lh3.googleusercontent.com/7tB9mdZ61rXn1uhgPVeGDV39FMtce_bDxyFcRMKlbZy_AbGP6rHn8BknJI4n-U4hki8p=s70
57
 * [_w2]:https://lh3.googleusercontent.com/7tB9mdZ61rXn1uhgPVeGDV39FMtce_bDxyFcRMKlbZy_AbGP6rHn8BknJI4n-U4hki8p=w70
58
 * [_h2]:https://lh3.googleusercontent.com/7tB9mdZ61rXn1uhgPVeGDV39FMtce_bDxyFcRMKlbZy_AbGP6rHn8BknJI4n-U4hki8p=h70
59
 * [_c1.2]:https://lh3.googleusercontent.com/7tB9mdZ61rXn1uhgPVeGDV39FMtce_bDxyFcRMKlbZy_AbGP6rHn8BknJI4n-U4hki8p=w40-h70-c
60
 * [_c2.2]:https://lh3.googleusercontent.com/7tB9mdZ61rXn1uhgPVeGDV39FMtce_bDxyFcRMKlbZy_AbGP6rHn8BknJI4n-U4hki8p=w70-h40-c
61
 * [_c3.2]:https://lh3.googleusercontent.com/7tB9mdZ61rXn1uhgPVeGDV39FMtce_bDxyFcRMKlbZy_AbGP6rHn8BknJI4n-U4hki8p=w70-h70-c
62
 * [_p1.2]:https://lh3.googleusercontent.com/7tB9mdZ61rXn1uhgPVeGDV39FMtce_bDxyFcRMKlbZy_AbGP6rHn8BknJI4n-U4hki8p=w40-h70-p
63
 * [_p2.2]:https://lh3.googleusercontent.com/7tB9mdZ61rXn1uhgPVeGDV39FMtce_bDxyFcRMKlbZy_AbGP6rHn8BknJI4n-U4hki8p=w70-h40-p
64
 * [_p3.2]:https://lh3.googleusercontent.com/7tB9mdZ61rXn1uhgPVeGDV39FMtce_bDxyFcRMKlbZy_AbGP6rHn8BknJI4n-U4hki8p=w70-h70-p
65
 * [_b2]:https://lh3.googleusercontent.com/7tB9mdZ61rXn1uhgPVeGDV39FMtce_bDxyFcRMKlbZy_AbGP6rHn8BknJI4n-U4hki8p=s70-b10
66
 * [_fv2]:https://lh3.googleusercontent.com/7tB9mdZ61rXn1uhgPVeGDV39FMtce_bDxyFcRMKlbZy_AbGP6rHn8BknJI4n-U4hki8p=s70-fv
67
 * [_fh2]:https://lh3.googleusercontent.com/7tB9mdZ61rXn1uhgPVeGDV39FMtce_bDxyFcRMKlbZy_AbGP6rHn8BknJI4n-U4hki8p=s70-fh
68
 *
69
 * [_s3]:https://lh3.googleusercontent.com/tCijG_gfFddONMX6aDD8RjnohoVy0TNbx5wc_Jn9ERSBBXIVtMqO_vs1h-v_FPFrzA0=s70
70
 * [_w3]:https://lh3.googleusercontent.com/tCijG_gfFddONMX6aDD8RjnohoVy0TNbx5wc_Jn9ERSBBXIVtMqO_vs1h-v_FPFrzA0=w70
71
 * [_h3]:https://lh3.googleusercontent.com/tCijG_gfFddONMX6aDD8RjnohoVy0TNbx5wc_Jn9ERSBBXIVtMqO_vs1h-v_FPFrzA0=h70
72
 * [_c1.3]:https://lh3.googleusercontent.com/tCijG_gfFddONMX6aDD8RjnohoVy0TNbx5wc_Jn9ERSBBXIVtMqO_vs1h-v_FPFrzA0=w40-h70-c
73
 * [_c2.3]:https://lh3.googleusercontent.com/tCijG_gfFddONMX6aDD8RjnohoVy0TNbx5wc_Jn9ERSBBXIVtMqO_vs1h-v_FPFrzA0=w70-h40-c
74
 * [_c3.3]:https://lh3.googleusercontent.com/tCijG_gfFddONMX6aDD8RjnohoVy0TNbx5wc_Jn9ERSBBXIVtMqO_vs1h-v_FPFrzA0=w70-h70-c
75
 * [_p1.3]:https://lh3.googleusercontent.com/tCijG_gfFddONMX6aDD8RjnohoVy0TNbx5wc_Jn9ERSBBXIVtMqO_vs1h-v_FPFrzA0=w40-h70-p
76
 * [_p2.3]:https://lh3.googleusercontent.com/tCijG_gfFddONMX6aDD8RjnohoVy0TNbx5wc_Jn9ERSBBXIVtMqO_vs1h-v_FPFrzA0=w70-h40-p
77
 * [_p3.3]:https://lh3.googleusercontent.com/tCijG_gfFddONMX6aDD8RjnohoVy0TNbx5wc_Jn9ERSBBXIVtMqO_vs1h-v_FPFrzA0=w70-h70-p
78
 * [_b3]:https://lh3.googleusercontent.com/tCijG_gfFddONMX6aDD8RjnohoVy0TNbx5wc_Jn9ERSBBXIVtMqO_vs1h-v_FPFrzA0=s70-b10
79
 * [_fv3]:https://lh3.googleusercontent.com/tCijG_gfFddONMX6aDD8RjnohoVy0TNbx5wc_Jn9ERSBBXIVtMqO_vs1h-v_FPFrzA0=s70-fv
80
 * [_fh3]:https://lh3.googleusercontent.com/tCijG_gfFddONMX6aDD8RjnohoVy0TNbx5wc_Jn9ERSBBXIVtMqO_vs1h-v_FPFrzA0=s70-fh
81
 *
82
 * If the URL has no parameters, by default GoogleUserContents uses the parameter **s512**.
83
 * This means that the width or height will not exceed 512px.
84
 *
85
 * @see https://developers.google.com/people/image-sizing Goolge People API - Image Sizing.
86
 * @see https://github.com/null-dev/libGoogleUserContent Java library to create googleusercontent.com URLs.
87
 * @see https://sites.google.com/site/picasaresources/Home/Picasa-FAQ/google-photos-1/how-to/how-to-get-a-direct-link-to-an-image
88
 *      Google Photos and Picasa: How to get a direct link to an image (of a specific size)
89
 */
90
class GoogleImage
91
{
92
    private const
93
        PARAM_SIZE = 's';
94
95
    private const
96
        PARAM_WIDTH = 'w';
97
98
    private const
99
        PARAM_HEIGHT = 'h';
100
101
    private const
102
        PARAM_BORDER = 'b';
103
104
    private const
105
        PARAM_SQUARE_CROP = 'c';
106
107
    private const
108
        PARAM_SMART_CROP = 'p';
109
110
    private const
111
        PARAM_FLIP_VERTICAL = 'fv';
112
113
    private const
114
        PARAM_FLIP_HORIZONTAL = 'fh';
115
116
    /** @var string Base URL without parameters and file name. */
117
    private $baseUrl;
118
119
    /** @var int|null Size longer of height or width to N pixels or `null`. */
120
    private $size;
121
122
    /** @var int|null Image width size up to N pixels. */
123
    private $width;
124
125
    /** @var int|null Image height size up to N pixels. */
126
    private $height;
127
128
    /** @var int|null Image border size or `null`. */
129
    private $border;
130
131
    /**
132
     * Using square crop.
133
     *
134
     * When cropping, only the center of the image is saved.
135
     *
136
     * @var bool using square crop
137
     */
138
    private $squareCrop = false;
139
140
    /**
141
     * Using smart crop.
142
     *
143
     * When cropping, some algorithm searches for the most interesting part of the image,
144
     * so the result of cropping does not always preserve the center of the image.
145
     *
146
     * @var bool using smart crop
147
     */
148
    private $smartCrop = false;
149
150
    /** @var bool Vertical flip effect */
151
    private $verticalFlip = false;
152
153
    /** @var bool Horizontal flip effect */
154
    private $horizontalFlip = false;
155
156
    /**
157
     * Variant URL with file name at the end.
158
     *
159
     * A special URL structure is used. URL starts with /-.
160
     *
161
     * Example URL:
162
     * https://lh3.googleusercontent.com/-LB59qNIqtS4/AAAAAAAAAAI/AAAAAAAAAAA/ACHi3rf3YR_W16kFTuh5tCgHpZ02_ndQOg/s100-no/photo.jpg
163
     *
164
     * @var bool special URL structure is used
165
     */
166
    private $variantOfUrlWithFileName = false;
167
168
    /**
169
     * Creates a GoogleImage object from the URL of the googleusercontent.com.
170
     *
171
     * @param string $url        URL image of googleusercontent.com server
172
     * @param bool   $keepParams keep parameters from URL
173
     */
174 95
    public function __construct(string $url, bool $keepParams = true)
175
    {
176 95
        $httpComponents = parse_url($url);
177
178
        if (
179 95
            !isset($httpComponents['host']) ||
180 95
            !preg_match('~\.(googleusercontent\.com|ggpht\.com|bp\.blogspot\.com)$~i', $httpComponents['host'])
181
        ) {
182 5
            throw new \InvalidArgumentException(sprintf('Unsupported URL: %s', $url));
183
        }
184 90
        $path = ltrim($httpComponents['path'], '/');
185 90
        $parts = explode('/', $path);
186 90
        $paramString = null;
187
188 90
        if (\count($parts) > 4 && strpos($parts[0], '-') === 0) {
189 26
            if (isset($parts[5]) || (isset($parts[4]) && strrpos($url, '/') === \strlen($url) - 1)) {
190 6
                $paramString = $parts[4];
191
            }
192 26
            $parts = \array_slice($parts, 0, 4);
193 26
            $path = implode('/', $parts);
194 26
            $url = $httpComponents['scheme'] . '://' . $httpComponents['host'] . '/' . $path . '/';
195 26
            $this->variantOfUrlWithFileName = true;
196 83
        } elseif (($pos = strpos($url, '=')) !== false) {
197 22
            $paramString = substr($url, $pos + 1);
198 22
            $url = substr($url, 0, $pos);
199
        }
200
201 90
        $this->baseUrl = $url;
202
203 90
        if ($keepParams && $paramString !== null) {
204 18
            $this->parseParams($paramString);
205
        }
206 90
    }
207
208
    /**
209
     * @param string $paramString Image parameters
210
     */
211 18
    private function parseParams(string $paramString): void
212
    {
213 18
        $params = explode('-', $paramString);
214
215 18
        foreach ($params as $param) {
216 18
            if (empty($param)) {
217 1
                continue;
218
            }
219
220 17
            $command = $param[0]; // 1 char
221
            switch ($command) {
222 17
                case self::PARAM_SIZE:
223 7
                    $arg = (int) substr($param, 1);
224 7
                    $this->setSize($arg);
225 7
                    break;
226
227 16
                case self::PARAM_WIDTH:
228 10
                    $arg = (int) substr($param, 1);
229 10
                    $this->setWidth($arg);
230 10
                    break;
231
232 13
                case self::PARAM_HEIGHT:
233 6
                    $arg = (int) substr($param, 1);
234 6
                    $this->setHeight($arg);
235 6
                    break;
236
237 9
                case self::PARAM_BORDER:
238 2
                    $arg = (int) substr($param, 1);
239 2
                    $this->setBorder($arg);
240 2
                    break;
241
242 9
                case self::PARAM_SQUARE_CROP:
243 1
                    $this->setSquareCrop(true);
244 1
                    break;
245
246 8
                case self::PARAM_SMART_CROP:
247
                    $this->setSmartCrop(true);
248
                    break;
249
250
                default:
251
                    switch ($param) {
252 8
                        case self::PARAM_FLIP_VERTICAL:
253 1
                            $this->setVerticalFlip(true);
254 1
                            break;
255
256 7
                        case self::PARAM_FLIP_HORIZONTAL:
257 1
                            $this->setHorizontalFlip(true);
258 17
                            break;
259
                        // ignore other parameters
260
                    }
261
            }
262
        }
263 18
    }
264
265
    /**
266
     * Returns the URL of the image with all the parameters set.
267
     *
268
     * @return string image URL
269
     */
270 35
    public function getUrl(): string
271
    {
272 35
        $params = [];
273
274 35
        if ($this->size !== null) {
275 14
            $params[] = self::PARAM_SIZE . $this->size;
276
        } else {
277 23
            if ($this->width !== null) {
278 14
                $params[] = self::PARAM_WIDTH . $this->width;
279
            }
280
281 23
            if ($this->height !== null) {
282 10
                $params[] = self::PARAM_HEIGHT . $this->height;
283
            }
284
        }
285
286 35
        if ($this->isValidSmartCrop()) {
287 4
            $params[] = self::PARAM_SMART_CROP;
288 33
        } elseif ($this->squareCrop) {
289 7
            $params[] = self::PARAM_SQUARE_CROP;
290
        }
291
292 35
        if ($this->variantOfUrlWithFileName) {
293 7
            if (empty($params)) {
294 3
                return $this->baseUrl;
295
            }
296
297 5
            return $this->baseUrl . implode('-', $params) . '/';
298
        }
299
300 28
        if ($this->border !== null) {
301 3
            $params[] = self::PARAM_BORDER . $this->border;
302
        }
303
304 28
        if ($this->verticalFlip) {
305 2
            $params[] = self::PARAM_FLIP_VERTICAL;
306
        }
307
308 28
        if ($this->horizontalFlip) {
309 2
            $params[] = self::PARAM_FLIP_HORIZONTAL;
310
        }
311
312 28
        if (empty($params)) {
313 6
            return $this->baseUrl;
314
        }
315
316 23
        return $this->baseUrl . '=' . implode('-', $params);
317
    }
318
319
    /**
320
     * @return bool
321
     */
322 35
    private function isValidSmartCrop(): bool
323
    {
324 35
        return $this->smartCrop && (
325 4
            $this->size !== null ||
326 3
                ($this->width !== null && $this->height !== null) ||
327 35
                ($this->size === null && $this->width === null && $this->height === null)
328
        );
329
    }
330
331
    /**
332
     * Returns a URL with the original image size.
333
     *
334
     * @return string URL of the original image size
335
     */
336 28
    public function getOriginalSizeUrl(): string
337
    {
338 28
        $params = [self::PARAM_SIZE . '0'];
339
340 28
        if ($this->variantOfUrlWithFileName) {
341 6
            return $this->baseUrl . implode('-', $params) . '/';
342
        }
343
344 22
        return $this->baseUrl . '=' . implode('-', $params);
345
    }
346
347
    /**
348
     * Returns a hash value for this object.
349
     * Can be used to generate a file save path.
350
     *
351
     * @param string $hashAlgorithm Hash algorithm. Default is md5.
352
     * @param int    $parts         Nesting path. Maximum 6.
353
     * @param int    $partLength    the length of the nested path
354
     *
355
     * @return string unique hash value of this object
356
     */
357 3
    public function getHashUrl(string $hashAlgorithm = 'md5', int $parts = 0, int $partLength = 2): string
358
    {
359 3
        $hash = hash($hashAlgorithm, $this->getUrl());
360 3
        $hashLength = \strlen($hash);
361 3
        $parts = max(0, min(6, $parts));
362
363 3
        if ($parts > 0) {
364 2
            $partLength = max(1, min($partLength, (int) ($hashLength / $parts)));
365 2
            $partsBuild = [];
366 2
            for ($i = 0; $i < $parts; $i++) {
367 2
                $partsBuild[] = substr($hash, $i * $partLength, $partLength);
368
            }
369 2
            $hash = implode('/', $partsBuild) . '/' . $hash;
370
        }
371
372 3
        return $hash;
373
    }
374
375
    /**
376
     * Sets the original image size.
377
     *
378
     * @return GoogleImage returns the same object \Nelexa\GPlay\Model\GoogleImage to support the call chain
379
     */
380 1
    public function useOriginalSize(): self
381
    {
382 1
        $this->setSize(0);
383
384 1
        return $this;
385
    }
386
387
    /**
388
     * Sets the image size greater than height or width up to N pixels.
389
     *
390
     * @param int|null $size width or height of the image in pixels
391
     *
392
     * @return GoogleImage returns the same object \Nelexa\GPlay\Model\GoogleImage to support the call chain
393
     */
394 34
    public function setSize(?int $size): self
395
    {
396 34
        $this->size = $size;
397 34
        $this->width = null;
398 34
        $this->height = null;
399
400 34
        return $this;
401
    }
402
403
    /**
404
     * Sets the width of the image.
405
     *
406
     * @param int|null $width image width
407
     *
408
     * @return GoogleImage returns the same object \Nelexa\GPlay\Model\GoogleImage to support the call chain
409
     */
410 15
    public function setWidth(?int $width): self
411
    {
412 15
        $this->width = $width;
413 15
        $this->size = null;
414
415 15
        return $this;
416
    }
417
418
    /**
419
     * Sets the height of the image.
420
     *
421
     * @param int|null $height image height
422
     *
423
     * @return GoogleImage returns the same object \Nelexa\GPlay\Model\GoogleImage to support the call chain
424
     */
425 11
    public function setHeight(?int $height): self
426
    {
427 11
        $this->height = $height;
428 11
        $this->size = null;
429
430 11
        return $this;
431
    }
432
433
    /**
434
     * Sets the width and height of the image.
435
     *
436
     * @param int $width  image width
437
     * @param int $height image height
438
     *
439
     * @return GoogleImage returns the same object \Nelexa\GPlay\Model\GoogleImage to support the call chain
440
     */
441
    public function setWidthAndHeight(int $width, int $height): self
442
    {
443
        $this->width = $width;
444
        $this->height = $height;
445
        $this->size = null;
446
447
        return $this;
448
    }
449
450
    /**
451
     * Sets the border around the image.
452
     *
453
     * @param int|null $border the number of pixels of the border
454
     *
455
     * @return GoogleImage returns the same object \Nelexa\GPlay\Model\GoogleImage to support the call chain
456
     */
457 4
    public function setBorder(?int $border): self
458
    {
459 4
        $this->border = $border;
460
461 4
        return $this;
462
    }
463
464
    /**
465
     * Sets the use of square crop.
466
     *
467
     * @param bool $squareCrop square crop
468
     *
469
     * @return GoogleImage returns the same object \Nelexa\GPlay\Model\GoogleImage to support the call chain
470
     */
471 7
    public function setSquareCrop(bool $squareCrop): self
472
    {
473 7
        $this->squareCrop = $squareCrop;
474 7
        $this->smartCrop = false;
475
476 7
        return $this;
477
    }
478
479
    /**
480
     * Sets the use of smart crop.
481
     *
482
     * @param bool $smartCrop smart crop
483
     *
484
     * @return GoogleImage returns the same object \Nelexa\GPlay\Model\GoogleImage to support the call chain
485
     */
486 4
    public function setSmartCrop(bool $smartCrop): self
487
    {
488 4
        $this->smartCrop = $smartCrop;
489 4
        $this->squareCrop = false;
490
491 4
        return $this;
492
    }
493
494
    /**
495
     * Sets the use of vertical flip.
496
     *
497
     * @param bool $verticalFlip vertical flip
498
     *
499
     * @return GoogleImage returns the same object \Nelexa\GPlay\Model\GoogleImage to support the call chain
500
     */
501 3
    public function setVerticalFlip(bool $verticalFlip): self
502
    {
503 3
        $this->verticalFlip = $verticalFlip;
504
505 3
        return $this;
506
    }
507
508
    /**
509
     * Sets the use of horizontal flip.
510
     *
511
     * @param bool $horizontalFlip horizontal flip
512
     *
513
     * @return GoogleImage returns the same object \Nelexa\GPlay\Model\GoogleImage to support the call chain
514
     */
515 3
    public function setHorizontalFlip(bool $horizontalFlip): self
516
    {
517 3
        $this->horizontalFlip = $horizontalFlip;
518
519 3
        return $this;
520
    }
521
522
    /**
523
     * @return int|null
524
     * @ignore
525
     */
526 1
    public function getSize(): ?int
527
    {
528 1
        return $this->size;
529
    }
530
531
    /**
532
     * @return int|null
533
     * @ignore
534
     */
535 1
    public function getWidth(): ?int
536
    {
537 1
        return $this->width;
538
    }
539
540
    /**
541
     * @return int|null
542
     * @ignore
543
     */
544 1
    public function getHeight(): ?int
545
    {
546 1
        return $this->height;
547
    }
548
549
    /**
550
     * @return int|null
551
     * @ignore
552
     */
553 1
    public function getBorder(): ?int
554
    {
555 1
        return $this->border;
556
    }
557
558
    /**
559
     * @return bool
560
     * @ignore
561
     */
562 1
    public function isSquareCrop(): bool
563
    {
564 1
        return $this->squareCrop;
565
    }
566
567
    /**
568
     * @return bool
569
     * @ignore
570
     */
571 1
    public function isSmartCrop(): bool
572
    {
573 1
        return $this->smartCrop;
574
    }
575
576
    /**
577
     * @return bool
578
     * @ignore
579
     */
580 1
    public function isVerticalFlip(): bool
581
    {
582 1
        return $this->verticalFlip;
583
    }
584
585
    /**
586
     * @return bool
587
     * @ignore
588
     */
589 1
    public function isHorizontalFlip(): bool
590
    {
591 1
        return $this->horizontalFlip;
592
    }
593
594
    /**
595
     * Reset all parameters.
596
     */
597 2
    public function reset(): void
598
    {
599 2
        $this->size = null;
600 2
        $this->width = null;
601 2
        $this->height = null;
602 2
        $this->border = null;
603 2
        $this->squareCrop = false;
604 2
        $this->smartCrop = false;
605 2
        $this->verticalFlip = false;
606 2
        $this->horizontalFlip = false;
607 2
    }
608
609
    /**
610
     * Save image to disk.
611
     *
612
     * @param string $destPath output filename
613
     *
614
     * @throws GooglePlayException if the error is HTTP or save to disk
615
     *
616
     * @return ImageInfo returns the object {@see ImageInfo}
617
     *
618
     * @see ImageInfo Contains information about the image.
619
     */
620 2
    public function saveAs(string $destPath): ImageInfo
621
    {
622 2
        $url = $this->getUrl();
623
624 2
        $stream = new LazyStream($destPath, 'w+b');
625
626
        try {
627 2
            $this->getHttpClient()->request(
628 2
                'GET',
629 2
                $url,
630
                [
631 2
                    RequestOptions::COOKIES => null,
632 2
                    RequestOptions::HTTP_ERRORS => true,
633 2
                    RequestOptions::SINK => $stream,
634
                    RequestOptions::ON_HEADERS => static function (ResponseInterface $response) use (
635 2
                        $url,
636 2
                        $stream
637
                    ): void {
638 2
                        self::onHeaders($response, $url, $stream);
639 2
                    },
640
                ]
641
            );
642
643 1
            return new ImageInfo($url, $stream->getFilename());
644 1
        } catch (\Throwable $e) {
645 1
            if (is_file($destPath)) {
646 1
                unlink($destPath);
647
            }
648 1
            $ge = new GooglePlayException($e->getMessage(), 1, $e);
649 1
            $ge->setUrl($url);
650
651 1
            throw $ge;
652
        }
653
    }
654
655
    /**
656
     * @param ResponseInterface $response
657
     * @param string            $url
658
     * @param LazyStream        $stream
659
     *
660
     * @throws GooglePlayException
661
     */
662 4
    public static function onHeaders(ResponseInterface $response, string $url, LazyStream $stream): void
663
    {
664 4
        if ($response->getStatusCode() >= 400) {
665 2
            return;
666
        }
667
668 2
        $contentType = $response->getHeaderLine('Content-Type');
669
670 2
        if (!preg_match('~\bimage/.*\b~i', $contentType, $match)) {
671
            throw new GooglePlayException('Url ' . $url . ' is not image');
672
        }
673 2
        $contentType = $match[0];
674 2
        $imageType = self::getImageExtension($contentType);
675 2
        $stream->replaceFilename('{ext}', $imageType);
0 ignored issues
show
Bug introduced by
It seems like $imageType can also be of type null; however, parameter $to of Nelexa\GPlay\Util\LazyStream::replaceFilename() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

675
        $stream->replaceFilename('{ext}', /** @scrutinizer ignore-type */ $imageType);
Loading history...
676 2
    }
677
678
    /**
679
     * @param string $mimeType
680
     *
681
     * @return string|null
682
     */
683 2
    public static function getImageExtension(string $mimeType): ?string
684
    {
685
        switch ($mimeType) {
686 2
            case 'image/gif':
687
                return 'gif';
688
689 2
            case 'image/jpeg':
690
                return 'jpg';
691
692 2
            case 'image/png':
693 2
                return 'png';
694
695
            case 'image/webp':
696
                return 'webp';
697
698
            default:
699
                return null;
700
        }
701
    }
702
703
    /**
704
     * @return HttpClient
705
     */
706 2
    private function getHttpClient(): HttpClient
707
    {
708 2
        static $httpClient;
709
710 2
        if ($httpClient === null) {
711 1
            $httpClient = new HttpClient();
712
        }
713
714 2
        return $httpClient;
715
    }
716
717
    /**
718
     * Returns binary image contents.
719
     *
720
     * @throws GooglePlayException if an HTTP error occurred
721
     *
722
     * @return string binary image content
723
     */
724
    public function getBinaryImageContent(): string
725
    {
726
        $url = $this->getUrl();
727
728
        try {
729
            $response = $this->getHttpClient()->request(
730
                'GET',
731
                $url,
732
                [
733
                    RequestOptions::HTTP_ERRORS => true,
734
                ]
735
            );
736
737
            return $response->getBody()->getContents();
738
        } catch (\Throwable $e) {
739
            $ge = new GooglePlayException($e->getMessage(), 1, $e);
740
            $ge->setUrl($url);
741
742
            throw $ge;
743
        }
744
    }
745
746
    /**
747
     * @return array
748
     * @ignore
749
     */
750
    public function __debugInfo()
751
    {
752
        return [
753
            'url' => $this->getUrl(),
754
        ];
755
    }
756
757
    /**
758
     * Returns the URL of the image.
759
     *
760
     * This method is equivalent to {@see GoogleImage::getUrl()}.
761
     *
762
     * @return string image URL
763
     */
764 1
    public function __toString()
765
    {
766 1
        return $this->getUrl();
767
    }
768
}
769