Completed
Push — master ( 45165d...5e44c4 )
by Alexey
04:08 queued 11s
created

GoogleImage::isValidSmartCrop()   B

Complexity

Conditions 7
Paths 11

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 7

Importance

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

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