Passed
Branch develop (bc8038)
by Alexey
02:01
created

GoogleImage::getUrl()   C

Complexity

Conditions 12
Paths 270

Size

Total Lines 43
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 25
dl 0
loc 43
rs 5.2583
c 0
b 0
f 0
cc 12
nc 270
nop 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
declare(strict_types=1);
3
4
namespace Nelexa\GPlay\Model;
5
6
use GuzzleHttp\Exception\GuzzleException;
7
use GuzzleHttp\RequestOptions;
8
use Nelexa\GPlay\Exception\GooglePlayException;
9
use Nelexa\GPlay\Http\HttpClient;
10
11
/**
12
 * Google User Content URLs.
13
 *
14
 * @see https://developers.google.com/people/image-sizing
15
 * @see https://github.com/null-dev/libGoogleUserContent
16
 * @see https://sites.google.com/site/picasaresources/Home/Picasa-FAQ/google-photos-1/how-to/how-to-get-a-direct-link-to-an-image
17
 *
18
 * sN   size             Sets the longer of height or width to N pixels
19
 * wN   width            Sets width of image to N pixels
20
 * hN   height           Sets height of image to N pixels
21
 * c    square crop      Sets height and width of crop to the same value
22
 * n    ? crop           Required sets width and height
23
 * p    smart crop       Sets smart crop
24
 * bN   border           Sets border of image to N pixels
25
 * fv   vertical flip    Vertically flips the image
26
 * fh   horizontal flip  Horizontally flips the image
27
 * d    download         Attachment header
28
 *
29
 * Animation image:
30
 * aN   frame number     Sets frame number of animation image
31
 * k    stop animation   Without GIF animation (1 frame)
32
 * no   hidden play      Hidden play button if video thumbnail or animation
33
 *
34
 * Video contents:
35
 * mN   media format
36
 * m18  mp4              video, 640x360, medium
37
 * m22  mp4              video, 1280x720, hd720
38
 * m36  3gp              video, 320x180
39
 * m37  mp4              video, 1920x1080, full-hd
40
 * m140 m4a              audio, 133k
41
 *
42
 * Without parameters, s512 is used.
43
 *
44
 * Example URLs:
45
 *
46
 * https://lh3.googleusercontent.com/GbJNOZ-E87H68Tq6Q_G4uqABQRKnA1zJqU1C5LTP8hUhCKq3BomtfntBnIJF2YhRrQ
47
 * https://lh3.googleusercontent.com/GbJNOZ-E87H68Tq6Q_G4uqABQRKnA1zJqU1C5LTP8hUhCKq3BomtfntBnIJF2YhRrQ=s0-k-no
48
 *
49
 * https://lh3.googleusercontent.com/HANcKpgwKaXt380ZJKK8_YpZlGn0NcjY5os1GOJmRHQjn9x9iCz9C-_lZRUkgTHYOChGMcMuuw=w200-h300
50
 *
51
 * https://lh3.googleusercontent.com/-LB59qNIqtS4/AAAAAAAAAAI/AAAAAAAAAAA/ACHi3rf3YR_W16kFTuh5tCgHpZ02_ndQOg/
52
 * https://lh3.googleusercontent.com/-LB59qNIqtS4/AAAAAAAAAAI/AAAAAAAAAAA/ACHi3rf3YR_W16kFTuh5tCgHpZ02_ndQOg/w40/
53
 * https://lh3.googleusercontent.com/-LB59qNIqtS4/AAAAAAAAAAI/AAAAAAAAAAA/ACHi3rf3YR_W16kFTuh5tCgHpZ02_ndQOg/photo.jpg
54
 * https://lh3.googleusercontent.com/-LB59qNIqtS4/AAAAAAAAAAI/AAAAAAAAAAA/ACHi3rf3YR_W16kFTuh5tCgHpZ02_ndQOg/s100-no/photo.jpg
55
 *
56
 * https://lh3.googleusercontent.com/-khz-7NpZXic/AAAAAAAAAAI/AAAAAAAAAAA/ACHi3reKXLHM7Pk6A7iXGRNBP8HxB0Xs1Q/w48-h48-n-mo/photo.jpg
57
 *
58
 * https://1.bp.blogspot.com/-gZoPZt6mOLQ/XMa2QFgXs6I/AAAAAAAACGs/wqldyhxSPX4PcttYLT1SB32O8-mbe5q7QCEwYBhgL/s0/top%2B40%2Bbest%2Btravel%2Bquotes.png
59
 *
60
 * https://lh4.googleusercontent.com/-0A4JtBQDKrs/VVJPnSLrOXI/AAAAAAABXR8/VFxcZF53zqk/w1134-h850-no/20141002_080237_HDR%257E2.jpg
61
 *
62
 * https://lh3.googleusercontent.com/FStqaBaXK7pteZ4jX5poKc0c-Ed2tqKcv2NyTAP7MwuH
63
 * https://lh3.googleusercontent.com/FStqaBaXK7pteZ4jX5poKc0c-Ed2tqKcv2NyTAP7MwuH=s0-b30-fv
64
 * https://lh3.googleusercontent.com/FStqaBaXK7pteZ4jX5poKc0c-Ed2tqKcv2NyTAP7MwuH=s100-k-no
65
 * https://lh3.googleusercontent.com/FStqaBaXK7pteZ4jX5poKc0c-Ed2tqKcv2NyTAP7MwuH=s0
66
 *
67
 * https://lh3.googleusercontent.com/proxy/3cI6bAx1WWTsIL5iPDRiPPknXImSb8xJtNuEUKGgXg8hWaGTY48kqGOdpOkLQJG1BGj3N6Y1Dc-6qvdfHoIdtk2PcwByKzpu3PkrsFIOXe-ePM9r9jPRL1lg9A=w720-h405
68
 *
69
 * https://lh3.ggpht.com/EGemoI2NTXmTsBVtJqk8jxF9rh8ApRWfsIMQSt2uE4OcpQqbFu7f7NbTK05lx80nuSijCz7sc3a277R67g
70
 *
71
 * https://lh3.googleusercontent.com/a-/AAuE7mAndrvGgUUJNSkl3mPSa-y-XcUJch1aKZDzCD2S
72
 *
73
 * https://lh3.googleusercontent.com/BeUSIDayFKt8LBwm-xbw4gpktnthRNW7aeYo-2oqG0pIscoyablMSJpxiTfkSIkNjuiDsQ=w640-h360-k
74
 * https://lh3.googleusercontent.com/BeUSIDayFKt8LBwm-xbw4gpktnthRNW7aeYo-2oqG0pIscoyablMSJpxiTfkSIkNjuiDsQ=w640-h360-k-n
75
 *
76
 * https://lh4.googleusercontent.com/-2XOcvsAH-kc/VHvmCm1aOoI/AAAAAAABtzg/SDdN1Vg5FFs/s300/
77
 */
78
class GoogleImage
79
{
80
    private const PARAM_SIZE = 's';
81
    private const PARAM_WIDTH = 'w';
82
    private const PARAM_HEIGHT = 'h';
83
    private const PARAM_BORDER = 'b';
84
    private const PARAM_SQUARE_CROP = 'c';
85
    private const PARAM_SMART_CROP = 'p';
86
    private const PARAM_FLIP_VERTICAL = 'fv';
87
    private const PARAM_FLIP_HORIZONTAL = 'fh';
88
89
    /**
90
     * @var string
91
     */
92
    private $url;
93
    /**
94
     * Size longer of height or width to N pixels.
95
     * If set, then GoogleImage::width and GoogleImage::height are set to null.
96
     *
97
     * @var int|null
98
     */
99
    private $size;
100
    /**
101
     * Image width size up to N pixels.
102
     * If set, then GoogleImage::size is null.
103
     *
104
     * @var int|null
105
     */
106
    private $width;
107
    /**
108
     * Image height size up to N pixels.
109
     * If set, then GoogleImage::size is null.
110
     *
111
     * @var int|null
112
     */
113
    private $height;
114
    /**
115
     * @var int|null
116
     */
117
    private $border;
118
    /**
119
     * @var bool
120
     */
121
    private $squareCrop = false;
122
    /**
123
     * @var bool
124
     */
125
    private $smartCrop = false;
126
    /**
127
     * @var bool
128
     */
129
    private $verticalFlip = false;
130
    /**
131
     * @var bool
132
     */
133
    private $horizontalFlip = false;
134
    /**
135
     * Variant URL with file name at the end.
136
     * A special URL structure is used.
137
     * URL starts with /-
138
     *
139
     * Example URL:
140
     * https://lh3.googleusercontent.com/-LB59qNIqtS4/AAAAAAAAAAI/AAAAAAAAAAA/ACHi3rf3YR_W16kFTuh5tCgHpZ02_ndQOg/s100-no/photo.jpg
141
     *
142
     * @var bool
143
     * @internal
144
     */
145
    private $variantOfUrlWithFileName = false;
146
147
    /**
148
     * @param string $url google image url
149
     * @param bool $keepParams keep url params
150
     */
151
    public function __construct(string $url, bool $keepParams = true)
152
    {
153
        $httpComponents = parse_url($url);
154
        $path = ltrim($httpComponents['path'], '/');
155
        $parts = explode('/', $path);
156
        $paramString = null;
157
        if (count($parts) > 4 && strpos($parts[0], '-') === 0) {
158
            if (isset($parts[5]) || (isset($parts[4]) && strrpos($url, '/') === strlen($url) - 1)) {
159
                $paramString = $parts[4];
160
            }
161
            $parts = array_slice($parts, 0, 4);
162
            $path = implode('/', $parts);
163
            $url = $httpComponents['scheme'] . '://' . $httpComponents['host'] . '/' . $path . '/';
164
            $this->variantOfUrlWithFileName = true;
165
        } elseif (($pos = strpos($url, '=')) !== false) {
166
            $paramString = substr($url, $pos + 1);
167
            $url = substr($url, 0, $pos);
168
        }
169
170
        $this->url = $url;
171
        if ($keepParams && $paramString !== null) {
172
            $this->parseParams($paramString);
173
        }
174
    }
175
176
    /**
177
     * @param string $paramString
178
     * @internal
179
     */
180
    private function parseParams(string $paramString): void
181
    {
182
        $params = explode('-', $paramString);
183
        foreach ($params as $param) {
184
            if (empty($param)) {
185
                continue;
186
            }
187
188
            $command = $param[0]; // 1 char
189
            switch ($command) {
190
                case self::PARAM_SIZE:
191
                    $arg = (int)substr($param, 1);
192
                    $this->setSize($arg);
193
                    break;
194
                case self::PARAM_WIDTH:
195
                    $arg = (int)substr($param, 1);
196
                    $this->setWidth($arg);
197
                    break;
198
                case self::PARAM_HEIGHT:
199
                    $arg = (int)substr($param, 1);
200
                    $this->setHeight($arg);
201
                    break;
202
                case self::PARAM_BORDER:
203
                    $arg = (int)substr($param, 1);
204
                    $this->setBorder($arg);
205
                    break;
206
                case self::PARAM_SQUARE_CROP:
207
                    $this->setSquareCrop(true);
208
                    break;
209
                case self::PARAM_SMART_CROP:
210
                    $this->setSmartCrop(true);
211
                    break;
212
                default:
213
                    switch ($param) {
214
                        case self::PARAM_FLIP_VERTICAL:
215
                            $this->setVerticalFlip(true);
216
                            break;
217
                        case self::PARAM_FLIP_HORIZONTAL:
218
                            $this->setHorizontalFlip(true);
219
                            break;
220
                        // ignore other parameters
221
                    }
222
            }
223
        }
224
    }
225
226
    /**
227
     * @return string
228
     */
229
    public function getUrl(): string
230
    {
231
        $params = [];
232
        if ($this->size !== null) {
233
            $params[] = self::PARAM_SIZE . $this->size;
234
        } else {
235
            if ($this->width !== null) {
236
                $params[] = self::PARAM_WIDTH . $this->width;
237
            }
238
            if ($this->height !== null) {
239
                $params[] = self::PARAM_HEIGHT . $this->height;
240
            }
241
        }
242
243
        if ($this->isValidSmartCrop()) {
244
            $params[] = self::PARAM_SMART_CROP;
245
        } elseif ($this->squareCrop) {
246
            $params[] = self::PARAM_SQUARE_CROP;
247
        }
248
249
        if ($this->variantOfUrlWithFileName) {
250
            if (empty($params)) {
251
                return $this->url;
252
            }
253
            return $this->url . implode('-', $params) . '/';
254
        }
255
256
        if ($this->border !== null) {
257
            $params[] = self::PARAM_BORDER . $this->border;
258
        }
259
260
        if ($this->verticalFlip) {
261
            $params[] = self::PARAM_FLIP_VERTICAL;
262
        }
263
264
        if ($this->horizontalFlip) {
265
            $params[] = self::PARAM_FLIP_HORIZONTAL;
266
        }
267
268
        if (empty($params)) {
269
            return $this->url;
270
        }
271
        return $this->url . '=' . implode('-', $params);
272
    }
273
274
    /**
275
     * @return bool
276
     */
277
    private function isValidSmartCrop(): bool
278
    {
279
        return $this->smartCrop && (
280
            $this->size !== null ||
281
            ($this->width !== null && $this->height !== null) ||
282
            ($this->size === null && $this->width === null && $this->height === null)
283
        );
284
    }
285
286
    /**
287
     * Returns the URL with the best image size.
288
     *
289
     * @return string url
290
     */
291
    public function getBestSizeUrl(): string
292
    {
293
        $params = [self::PARAM_SIZE . '0'];
294
        if ($this->variantOfUrlWithFileName) {
295
            return $this->url . implode('-', $params) . '/';
296
        }
297
        return $this->url . '=' . implode('-', $params);
298
    }
299
300
    /**
301
     * Returns a hash value for this object.
302
     * Can be used to generate a file save path.
303
     *
304
     * @param string $hashAlgorithm Hash algorithm. Default is md5.
305
     * @param int $parts Nesting path. Maximum 6.
306
     * @param int $partLength The length of the nested path.
307
     * @return string unique hash value of this object
308
     */
309
    public function getHashUrl(string $hashAlgorithm = 'md5', int $parts = 1, int $partLength = 2): string
310
    {
311
        $hash = hash($hashAlgorithm, $this->getUrl());
312
        $hashLength = strlen($hash);
313
        $parts = max(1, min(6, $parts));
314
        if ($parts > 1) {
315
            $partLength = max(1, min($partLength, (int)($hashLength / $parts)));
316
            $partsBuild = [];
317
            for ($i = 0; $i < $parts; $i++) {
318
                $partsBuild[] = substr($hash, $i * $partLength, $partLength);
319
            }
320
            $hash = implode('/', $partsBuild) . '/' . $hash;
321
        }
322
        return $hash;
323
    }
324
325
    /**
326
     * Sets the original image size.
327
     *
328
     * @return GoogleImage
329
     */
330
    public function useOriginalSize(): GoogleImage
331
    {
332
        $this->setSize(0);
333
        return $this;
334
    }
335
336
    /**
337
     * @param int|null $size
338
     * @return GoogleImage
339
     */
340
    public function setSize(?int $size): GoogleImage
341
    {
342
        $this->size = $size;
343
        $this->width = null;
344
        $this->height = null;
345
        return $this;
346
    }
347
348
    /**
349
     * @param int|null $width
350
     * @return GoogleImage
351
     */
352
    public function setWidth(?int $width): GoogleImage
353
    {
354
        $this->width = $width;
355
        $this->size = null;
356
        return $this;
357
    }
358
359
    /**
360
     * @param int|null $height
361
     * @return GoogleImage
362
     */
363
    public function setHeight(?int $height): GoogleImage
364
    {
365
        $this->height = $height;
366
        $this->size = null;
367
        return $this;
368
    }
369
370
    /**
371
     * @param int $width
372
     * @param int $height
373
     * @return GoogleImage
374
     */
375
    public function setWidthAndHeight(int $width, int $height): GoogleImage
376
    {
377
        $this->width = $width;
378
        $this->height = $height;
379
        $this->size = null;
380
        return $this;
381
    }
382
383
    /**
384
     * @param int|null $border
385
     * @return GoogleImage
386
     */
387
    public function setBorder(?int $border): GoogleImage
388
    {
389
        $this->border = $border;
390
        return $this;
391
    }
392
393
    /**
394
     * @param bool $squareCrop
395
     * @return GoogleImage
396
     */
397
    public function setSquareCrop(bool $squareCrop): GoogleImage
398
    {
399
        $this->squareCrop = $squareCrop;
400
        $this->smartCrop = false;
401
        return $this;
402
    }
403
404
    /**
405
     * @param bool $smartCrop
406
     * @return GoogleImage
407
     */
408
    public function setSmartCrop(bool $smartCrop): GoogleImage
409
    {
410
        $this->smartCrop = $smartCrop;
411
        $this->squareCrop = false;
412
        return $this;
413
    }
414
415
    /**
416
     * @param bool $verticalFlip
417
     * @return GoogleImage
418
     */
419
    public function setVerticalFlip(bool $verticalFlip): GoogleImage
420
    {
421
        $this->verticalFlip = $verticalFlip;
422
        return $this;
423
    }
424
425
    /**
426
     * @param bool $horizontalFlip
427
     * @return GoogleImage
428
     */
429
    public function setHorizontalFlip(bool $horizontalFlip): GoogleImage
430
    {
431
        $this->horizontalFlip = $horizontalFlip;
432
        return $this;
433
    }
434
435
    /**
436
     * @return int|null
437
     */
438
    public function getSize(): ?int
439
    {
440
        return $this->size;
441
    }
442
443
    /**
444
     * @return int|null
445
     */
446
    public function getWidth(): ?int
447
    {
448
        return $this->width;
449
    }
450
451
    /**
452
     * @return int|null
453
     */
454
    public function getHeight(): ?int
455
    {
456
        return $this->height;
457
    }
458
459
    /**
460
     * @return int|null
461
     */
462
    public function getBorder(): ?int
463
    {
464
        return $this->border;
465
    }
466
467
    /**
468
     * @return bool
469
     */
470
    public function isSquareCrop(): bool
471
    {
472
        return $this->squareCrop;
473
    }
474
475
    /**
476
     * @return bool
477
     */
478
    public function isSmartCrop(): bool
479
    {
480
        return $this->smartCrop;
481
    }
482
483
    /**
484
     * @return bool
485
     */
486
    public function isVerticalFlip(): bool
487
    {
488
        return $this->verticalFlip;
489
    }
490
491
    /**
492
     * @return bool
493
     */
494
    public function isHorizontalFlip(): bool
495
    {
496
        return $this->horizontalFlip;
497
    }
498
499
    public function reset(): void
500
    {
501
        $this->size = null;
502
        $this->width = null;
503
        $this->height = null;
504
        $this->border = null;
505
        $this->squareCrop = false;
506
        $this->smartCrop = false;
507
        $this->verticalFlip = false;
508
        $this->horizontalFlip = false;
509
    }
510
511
    /**
512
     * @param string $destPath
513
     * @return ImageInfo
514
     * @throws GooglePlayException
515
     */
516
    public function saveAs(string $destPath): ImageInfo
517
    {
518
        static $httpClient;
519
520
        if ($httpClient === null) {
521
            $httpClient = new HttpClient();
522
        }
523
524
        $url = $this->getUrl();
525
526
        $dirName = dirname($destPath);
527
        if (!is_dir($dirName) && !mkdir($dirName, 0755, true) && !is_dir($dirName)) {
528
            throw new \RuntimeException(sprintf('Failed to create "%s"', $dirName));
529
        }
530
531
        try {
532
            $httpClient->request('GET', $url, [
533
                RequestOptions::SINK => $destPath,
534
                RequestOptions::HTTP_ERRORS => true,
535
            ]);
536
537
            return new ImageInfo($url, $destPath);
538
        } catch (\Throwable|GuzzleException $e) {
539
            if (is_file($destPath)) {
540
                unlink($destPath);
541
            }
542
            $ge = new GooglePlayException($e->getMessage(), $e->getCode(), $e);
543
            $ge->setUrl($url);
544
            throw $ge;
545
        }
546
    }
547
548
    /**
549
     * @return string
550
     */
551
    public function __toString()
552
    {
553
        return $this->getUrl();
554
    }
555
}
556