GoogleImage::saveAs()   A
last analyzed

Complexity

Conditions 3
Paths 5

Size

Total Lines 32
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 3

Importance

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

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