Completed
Branch develop (45165d)
by Alexey
04:09
created

GoogleImage::getUrl()   F

Complexity

Conditions 18
Paths 270

Size

Total Lines 48
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 28
dl 0
loc 48
rs 3.1583
c 0
b 0
f 0
cc 18
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->smartCrop && (
244
            $this->size !== null ||
245
                ($this->width !== null && $this->height !== null) ||
246
                ($this->size === null && $this->width === null && $this->height === null)
247
            )
248
        ) {
249
            $params[] = self::PARAM_SMART_CROP;
250
        } elseif ($this->squareCrop) {
251
            $params[] = self::PARAM_SQUARE_CROP;
252
        }
253
254
        if ($this->variantOfUrlWithFileName) {
255
            if (empty($params)) {
256
                return $this->url;
257
            }
258
            return $this->url . implode('-', $params) . '/';
259
        }
260
261
        if ($this->border !== null) {
262
            $params[] = self::PARAM_BORDER . $this->border;
263
        }
264
265
        if ($this->verticalFlip) {
266
            $params[] = self::PARAM_FLIP_VERTICAL;
267
        }
268
269
        if ($this->horizontalFlip) {
270
            $params[] = self::PARAM_FLIP_HORIZONTAL;
271
        }
272
273
        if (empty($params)) {
274
            return $this->url;
275
        }
276
        return $this->url . '=' . implode('-', $params);
277
    }
278
279
    /**
280
     * Returns the URL with the best image size.
281
     *
282
     * @return string url
283
     */
284
    public function getBestSizeUrl(): string
285
    {
286
        $params = [self::PARAM_SIZE . '0'];
287
        if ($this->variantOfUrlWithFileName) {
288
            return $this->url . implode('-', $params) . '/';
289
        }
290
        return $this->url . '=' . implode('-', $params);
291
    }
292
293
    /**
294
     * Returns a hash value for this object.
295
     * Can be used to generate a file save path.
296
     *
297
     * @param string $hashAlgorithm Hash algorithm. Default is md5.
298
     * @param int $parts Nesting path. Maximum 6.
299
     * @param int $partLength The length of the nested path.
300
     * @return string unique hash value of this object
301
     */
302
    public function getHashUrl(string $hashAlgorithm = 'md5', int $parts = 1, int $partLength = 2): string
303
    {
304
        $hash = hash($hashAlgorithm, $this->getUrl());
305
        $hashLength = strlen($hash);
306
        $parts = max(1, min(6, $parts));
307
        if ($parts > 1) {
308
            $partLength = max(1, min($partLength, (int)($hashLength / $parts)));
309
            $partsBuild = [];
310
            for ($i = 0; $i < $parts; $i++) {
311
                $partsBuild[] = substr($hash, $i * $partLength, $partLength);
312
            }
313
            $hash = implode('/', $partsBuild) . '/' . $hash;
314
        }
315
        return $hash;
316
    }
317
318
    /**
319
     * Sets the original image size.
320
     *
321
     * @return GoogleImage
322
     */
323
    public function useOriginalSize(): GoogleImage
324
    {
325
        $this->setSize(0);
326
        return $this;
327
    }
328
329
    /**
330
     * @param int|null $size
331
     * @return GoogleImage
332
     */
333
    public function setSize(?int $size): GoogleImage
334
    {
335
        $this->size = $size;
336
        $this->width = null;
337
        $this->height = null;
338
        return $this;
339
    }
340
341
    /**
342
     * @param int|null $width
343
     * @return GoogleImage
344
     */
345
    public function setWidth(?int $width): GoogleImage
346
    {
347
        $this->width = $width;
348
        $this->size = null;
349
        return $this;
350
    }
351
352
    /**
353
     * @param int|null $height
354
     * @return GoogleImage
355
     */
356
    public function setHeight(?int $height): GoogleImage
357
    {
358
        $this->height = $height;
359
        $this->size = null;
360
        return $this;
361
    }
362
363
    /**
364
     * @param int $width
365
     * @param int $height
366
     * @return GoogleImage
367
     */
368
    public function setWidthAndHeight(int $width, int $height): GoogleImage
369
    {
370
        $this->width = $width;
371
        $this->height = $height;
372
        $this->size = null;
373
        return $this;
374
    }
375
376
    /**
377
     * @param int|null $border
378
     * @return GoogleImage
379
     */
380
    public function setBorder(?int $border): GoogleImage
381
    {
382
        $this->border = $border;
383
        return $this;
384
    }
385
386
    /**
387
     * @param bool $squareCrop
388
     * @return GoogleImage
389
     */
390
    public function setSquareCrop(bool $squareCrop): GoogleImage
391
    {
392
        $this->squareCrop = $squareCrop;
393
        $this->smartCrop = false;
394
        return $this;
395
    }
396
397
    /**
398
     * @param bool $smartCrop
399
     * @return GoogleImage
400
     */
401
    public function setSmartCrop(bool $smartCrop): GoogleImage
402
    {
403
        $this->smartCrop = $smartCrop;
404
        $this->squareCrop = false;
405
        return $this;
406
    }
407
408
    /**
409
     * @param bool $verticalFlip
410
     * @return GoogleImage
411
     */
412
    public function setVerticalFlip(bool $verticalFlip): GoogleImage
413
    {
414
        $this->verticalFlip = $verticalFlip;
415
        return $this;
416
    }
417
418
    /**
419
     * @param bool $horizontalFlip
420
     * @return GoogleImage
421
     */
422
    public function setHorizontalFlip(bool $horizontalFlip): GoogleImage
423
    {
424
        $this->horizontalFlip = $horizontalFlip;
425
        return $this;
426
    }
427
428
    /**
429
     * @return int|null
430
     */
431
    public function getSize(): ?int
432
    {
433
        return $this->size;
434
    }
435
436
    /**
437
     * @return int|null
438
     */
439
    public function getWidth(): ?int
440
    {
441
        return $this->width;
442
    }
443
444
    /**
445
     * @return int|null
446
     */
447
    public function getHeight(): ?int
448
    {
449
        return $this->height;
450
    }
451
452
    /**
453
     * @return int|null
454
     */
455
    public function getBorder(): ?int
456
    {
457
        return $this->border;
458
    }
459
460
    /**
461
     * @return bool
462
     */
463
    public function isSquareCrop(): bool
464
    {
465
        return $this->squareCrop;
466
    }
467
468
    /**
469
     * @return bool
470
     */
471
    public function isSmartCrop(): bool
472
    {
473
        return $this->smartCrop;
474
    }
475
476
    /**
477
     * @return bool
478
     */
479
    public function isVerticalFlip(): bool
480
    {
481
        return $this->verticalFlip;
482
    }
483
484
    /**
485
     * @return bool
486
     */
487
    public function isHorizontalFlip(): bool
488
    {
489
        return $this->horizontalFlip;
490
    }
491
492
    public function reset(): void
493
    {
494
        $this->size = null;
495
        $this->width = null;
496
        $this->height = null;
497
        $this->border = null;
498
        $this->squareCrop = false;
499
        $this->smartCrop = false;
500
        $this->verticalFlip = false;
501
        $this->horizontalFlip = false;
502
    }
503
504
    /**
505
     * @param string $destPath
506
     * @return ImageInfo
507
     * @throws GooglePlayException
508
     */
509
    public function saveAs(string $destPath): ImageInfo
510
    {
511
        static $httpClient;
512
513
        if ($httpClient === null) {
514
            $httpClient = new HttpClient();
515
        }
516
517
        $url = $this->getUrl();
518
519
        $dirName = dirname($destPath);
520
        if (!is_dir($dirName) && !mkdir($dirName, 0755, true) && !is_dir($dirName)) {
521
            throw new \RuntimeException(sprintf('Failed to create "%s"', $dirName));
522
        }
523
524
        try {
525
            $httpClient->request('GET', $url, [
526
                RequestOptions::SINK => $destPath,
527
                RequestOptions::HTTP_ERRORS => true,
528
            ]);
529
530
            return new ImageInfo($url, $destPath);
531
        } catch (\Throwable|GuzzleException $e) {
532
            if (is_file($destPath)) {
533
                unlink($destPath);
534
            }
535
            $ge = new GooglePlayException($e->getMessage(), $e->getCode(), $e);
536
            $ge->setUrl($url);
537
            throw $ge;
538
        }
539
    }
540
541
    /**
542
     * @return string
543
     */
544
    public function __toString()
545
    {
546
        return $this->getUrl();
547
    }
548
}
549