Passed
Push — v2 ( 5f6f0e...8b6728 )
by Benjamin
03:36
created

Vimeo::parseThumbnails()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 29
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 14
nc 7
nop 2
dl 0
loc 29
rs 6.7272
c 0
b 0
f 0
1
<?php
2
/**
3
 * @link      https://dukt.net/videos/
4
 * @copyright Copyright (c) 2018, Dukt
5
 * @license   https://github.com/dukt/videos/blob/v2/LICENSE.md
6
 */
7
8
namespace dukt\videos\gateways;
9
10
use dukt\videos\base\Gateway;
11
use dukt\videos\errors\CollectionParsingException;
12
use dukt\videos\errors\VideoNotFoundException;
13
use dukt\videos\models\Collection;
14
use dukt\videos\models\Section;
15
use dukt\videos\models\Video;
16
use GuzzleHttp\Client;
17
use DateTime;
18
19
/**
20
 * Vimeo represents the Vimeo gateway
21
 *
22
 * @author    Dukt <[email protected]>
23
 * @since     1.0
24
 */
25
class Vimeo extends Gateway
26
{
27
    // Public Methods
28
    // =========================================================================
29
30
    /**
31
     * @inheritDoc
32
     *
33
     * @return string
34
     */
35
    public function getIconAlias(): string
36
    {
37
        return '@dukt/videos/icons/vimeo.svg';
38
    }
39
40
    /**
41
     * @inheritDoc
42
     *
43
     * @return string
44
     */
45
    public function getName(): string
46
    {
47
        return 'Vimeo';
48
    }
49
50
    /**
51
     * Returns the OAuth provider’s API console URL.
52
     *
53
     * @return string
54
     */
55
    public function getOauthProviderApiConsoleUrl(): string
56
    {
57
        return 'https://developer.vimeo.com/apps';
58
    }
59
60
    /**
61
     * @inheritDoc
62
     *
63
     * @return array
64
     */
65
    public function getOauthScope(): array
66
    {
67
        return [
68
            'public',
69
            'private',
70
        ];
71
    }
72
73
    /**
74
     * Creates the OAuth provider.
75
     *
76
     * @param array $options
77
     *
78
     * @return \Dukt\OAuth2\Client\Provider\Vimeo
79
     */
80
    public function createOauthProvider(array $options): \Dukt\OAuth2\Client\Provider\Vimeo
81
    {
82
        return new \Dukt\OAuth2\Client\Provider\Vimeo($options);
83
    }
84
85
    /**
86
     * @inheritDoc
87
     *
88
     * @return array
89
     * @throws CollectionParsingException
90
     * @throws \dukt\videos\errors\ApiResponseException
91
     */
92
    public function getExplorerSections(): array
93
    {
94
        $sections = [];
95
96
97
        // Library
98
99
        $sections[] = new Section([
100
            'name' => 'Library',
101
            'collections' => [
102
                new Collection([
103
                    'name' => 'Uploads',
104
                    'method' => 'uploads',
105
                ]),
106
                new Collection([
107
                    'name' => 'Favorites',
108
                    'method' => 'favorites',
109
                ]),
110
            ]
111
        ]);
112
113
114
        // Albums
115
116
        $albums = $this->getCollectionsAlbums();
117
118
        $collections = [];
119
120
        foreach ($albums as $album) {
121
            $collections[] = new Collection([
122
                'name' => $album['title'],
123
                'method' => 'album',
124
                'options' => ['id' => $album['id']]
125
            ]);
126
        }
127
128
        if (\count($collections) > 0) {
129
            $sections[] = new Section([
130
                'name' => 'Playlists',
131
                'collections' => $collections,
132
            ]);
133
        }
134
135
136
        // channels
137
138
        $channels = $this->getCollectionsChannels();
139
140
        $collections = [];
141
142
        foreach ($channels as $channel) {
143
            $collections[] = new Collection([
144
                'name' => $channel['title'],
145
                'method' => 'channel',
146
                'options' => ['id' => $channel['id']],
147
            ]);
148
        }
149
150
        if (\count($collections) > 0) {
151
            $sections[] = new Section([
152
                'name' => 'Channels',
153
                'collections' => $collections,
154
            ]);
155
        }
156
157
        return $sections;
158
    }
159
160
    /**
161
     * @inheritDoc
162
     *
163
     * @param string $id
164
     *
165
     * @return Video
166
     * @throws VideoNotFoundException
167
     * @throws \dukt\videos\errors\ApiResponseException
168
     */
169
    public function getVideoById(string $id): Video
170
    {
171
        $data = $this->get('videos/'.$id, [
172
            'query' => [
173
                'fields' => 'created_time,description,duration,height,link,name,pictures,pictures,privacy,stats,uri,user,width,download,review_link,files'
174
            ],
175
        ]);
176
177
        if ($data) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $data of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
178
            return $this->parseVideo($data);
179
        }
180
181
        throw new VideoNotFoundException('Video not found.');
182
    }
183
184
    /**
185
     * @inheritDoc
186
     *
187
     * @return string
188
     */
189
    public function getEmbedFormat(): string
190
    {
191
        return 'https://player.vimeo.com/video/%s';
192
    }
193
194
    /**
195
     * @param string $url
196
     *
197
     * @return bool|string
198
     */
199
    public function extractVideoIdFromUrl(string $url)
200
    {
201
        // check if url works with this service and extract video_id
202
203
        $videoId = false;
204
205
        $regexp = ['/^https?:\/\/(www\.)?vimeo\.com\/([0-9]*)/', 2];
206
207
        if (preg_match($regexp[0], $url, $matches, PREG_OFFSET_CAPTURE) > 0) {
208
209
            // regexp match key
210
            $match_key = $regexp[1];
211
212
213
            // define video id
214
            $videoId = $matches[$match_key][0];
215
216
217
            // Fixes the youtube &feature_gdata bug
218
            if (strpos($videoId, '&')) {
219
                $videoId = substr($videoId, 0, strpos($videoId, '&'));
220
            }
221
        }
222
223
        // here we should have a valid video_id or false if service not matching
224
        return $videoId;
225
    }
226
227
    /**
228
     * @inheritDoc
229
     *
230
     * @return bool
231
     */
232
    public function supportsSearch(): bool
233
    {
234
        return true;
235
    }
236
237
    // Protected
238
    // =========================================================================
239
240
    /**
241
     * Returns an authenticated Guzzle client
242
     *
243
     * @return Client
244
     * @throws \yii\base\InvalidConfigException
245
     */
246
    protected function createClient(): Client
247
    {
248
        $options = [
249
            'base_uri' => $this->getApiUrl(),
250
            'headers' => [
251
                'Accept' => 'application/vnd.vimeo.*+json;version='.$this->getApiVersion(),
252
                'Authorization' => 'Bearer '.$this->getOauthToken()->getToken()
253
            ],
254
        ];
255
256
        return new Client($options);
257
    }
258
259
    /**
260
     * Returns a list of videos in an album
261
     *
262
     * @param array $params
263
     *
264
     * @return array
265
     * @throws \dukt\videos\errors\ApiResponseException
266
     */
267
    protected function getVideosAlbum(array $params = []): array
268
    {
269
        $albumId = $params['id'];
270
        unset($params['id']);
271
272
        // albums/#album_id
273
        return $this->performVideosRequest('me/albums/'.$albumId.'/videos', $params);
274
    }
275
276
    /**
277
     * Returns a list of videos in a channel
278
     *
279
     * @param array $params
280
     *
281
     * @return array
282
     * @throws \dukt\videos\errors\ApiResponseException
283
     */
284
    protected function getVideosChannel(array $params = []): array
285
    {
286
        $params['channel_id'] = $params['id'];
287
        unset($params['id']);
288
289
        return $this->performVideosRequest('channels/'.$params['channel_id'].'/videos', $params);
290
    }
291
292
    /**
293
     * Returns a list of favorite videos
294
     *
295
     * @param array $params
296
     *
297
     * @return array
298
     * @throws \dukt\videos\errors\ApiResponseException
299
     */
300
    protected function getVideosFavorites(array $params = []): array
301
    {
302
        return $this->performVideosRequest('me/likes', $params);
303
    }
304
305
    /**
306
     * Returns a list of videos from a search request
307
     *
308
     * @param array $params
309
     *
310
     * @return array
311
     * @throws \dukt\videos\errors\ApiResponseException
312
     */
313
    protected function getVideosSearch(array $params = []): array
314
    {
315
        return $this->performVideosRequest('videos', $params);
316
    }
317
318
    /**
319
     * Returns a list of uploaded videos
320
     *
321
     * @param array $params
322
     *
323
     * @return array
324
     * @throws \dukt\videos\errors\ApiResponseException
325
     */
326
    protected function getVideosUploads(array $params = []): array
327
    {
328
        return $this->performVideosRequest('me/videos', $params);
329
    }
330
331
    // Private Methods
332
    // =========================================================================
333
334
    /**
335
     * @return string
336
     */
337
    private function getApiUrl(): string
338
    {
339
        return 'https://api.vimeo.com/';
340
    }
341
342
    /**
343
     * @return string
344
     */
345
    private function getApiVersion(): string
346
    {
347
        return '3.0';
348
    }
349
350
    /**
351
     * @param array $params
352
     *
353
     * @return array
354
     * @throws CollectionParsingException
355
     * @throws \dukt\videos\errors\ApiResponseException
356
     */
357
    private function getCollectionsAlbums(array $params = []): array
358
    {
359
        $data = $this->get('me/albums', [
360
            'query' => $this->queryFromParams($params)
361
        ]);
362
363
        return $this->parseCollections('album', $data['data']);
364
    }
365
366
    /**
367
     * @param array $params
368
     *
369
     * @return array
370
     * @throws CollectionParsingException
371
     * @throws \dukt\videos\errors\ApiResponseException
372
     */
373
    private function getCollectionsChannels(array $params = []): array
374
    {
375
        $data = $this->get('me/channels', [
376
            'query' => $this->queryFromParams($params)
377
        ]);
378
379
        return $this->parseCollections('channel', $data['data']);
380
    }
381
382
    /**
383
     * @param $type
384
     * @param $collections
385
     *
386
     * @return array
387
     * @throws CollectionParsingException
388
     */
389
    private function parseCollections($type, array $collections): array
390
    {
391
        $parseCollections = [];
392
393
        foreach ($collections as $collection) {
394
395
            switch ($type) {
396
                case 'album':
397
                    $parsedCollection = $this->parseCollectionAlbum($collection);
398
                    break;
399
                case 'channel':
400
                    $parsedCollection = $this->parseCollectionChannel($collection);
401
                    break;
402
403
                default:
404
                    throw new CollectionParsingException('Couldn’t parse collection of type ”'.$type.'“.');
405
            }
406
407
            $parseCollections[] = $parsedCollection;
408
        }
409
410
        return $parseCollections;
411
    }
412
413
    /**
414
     * @param $data
415
     *
416
     * @return array
417
     */
418
    private function parseCollectionAlbum($data): array
419
    {
420
        $collection = [];
421
        $collection['id'] = substr($data['uri'], strpos($data['uri'], '/albums/') + \strlen('/albums/'));
422
        $collection['url'] = $data['uri'];
423
        $collection['title'] = $data['name'];
424
        $collection['totalVideos'] = $data['stats']['videos'];
425
426
        return $collection;
427
    }
428
429
    /**
430
     * @param $data
431
     *
432
     * @return array
433
     */
434
    private function parseCollectionChannel($data): array
435
    {
436
        $collection = [];
437
        $collection['id'] = substr($data['uri'], strpos($data['uri'], '/channels/') + \strlen('/channels/'));
438
        $collection['url'] = $data['uri'];
439
        $collection['title'] = $data['name'];
440
        $collection['totalVideos'] = $data['stats']['videos'];
441
442
        return $collection;
443
    }
444
445
    /**
446
     * @param $data
447
     *
448
     * @return array
449
     */
450
    private function parseVideos(array $data): array
451
    {
452
        $videos = [];
453
454
        if (!empty($data)) {
455
            foreach ($data as $videoData) {
456
                $video = $this->parseVideo($videoData);
457
458
                $videos[] = $video;
459
            }
460
        }
461
462
        return $videos;
463
    }
464
465
    /**
466
     * Parse video.
467
     *
468
     * @param array $data
469
     *
470
     * @return Video
471
     */
472
    private function parseVideo(array $data): Video
473
    {
474
        $video = new Video;
475
        $video->raw = $data;
476
        $video->authorName = $data['user']['name'];
477
        $video->authorUrl = $data['user']['link'];
478
        $video->date = new DateTime($data['created_time']);
479
        $video->durationSeconds = $data['duration'];
480
        $video->description = $data['description'];
481
        $video->gatewayHandle = 'vimeo';
482
        $video->gatewayName = 'Vimeo';
483
        $video->id = (int) substr($data['uri'], \strlen('/videos/'));
484
        $video->plays = $data['stats']['plays'] ?? 0;
485
        $video->title = $data['name'];
486
        $video->url = $data['link'];
487
        $video->width = $data['width'];
488
        $video->height = $data['height'];
489
490
491
        // privacy
492
493
        if ($data['privacy']['view'] === 'nobody'
494
            || $data['privacy']['view'] === 'contacts'
495
            || $data['privacy']['view'] === 'password'
496
            || $data['privacy']['view'] === 'users'
497
            || $data['privacy']['view'] === 'disable') {
498
            $video->private = true;
499
        }
500
501
        $this->parseThumbnails($video, $data);
502
503
        return $video;
504
    }
505
506
    /**
507
     * Parse thumbnails.
508
     *
509
     * @param Video $video
510
     * @param array $data
511
     *
512
     * @return null
513
     */
514
    private function parseThumbnails(Video &$video, array $data)
515
    {
516
        if (!\is_array($data['pictures'])) {
517
            return null;
518
        }
519
520
        $largestSize = 0;
521
        $thumbSize = 0;
522
523
        foreach ($data['pictures'] as $picture) {
524
            if ($picture['type'] === 'thumbnail') {
525
                
526
                // Retrieve highest quality thumbnail
527
                if ($picture['width'] > $largestSize) {
528
                    $video->thumbnailLargeSource = $picture['link'];
529
                    $largestSize = $picture['width'];
530
                }
531
532
                // Retrieve highest quality thumbnail with width < 400
533
                if ($picture['width'] > $thumbSize && $thumbSize < 400) {
534
                    $video->thumbnailSource = $picture['link'];
535
                    $thumbSize = $picture['width'];
536
                }
537
            }
538
        }
539
540
        $video->thumbnailSource = $video->thumbnailSource ?? $video->thumbnailLargeSource;
541
542
        return null;
543
    }
544
545
    /**
546
     * @param $uri
547
     * @param $params
548
     *
549
     * @return array
550
     * @throws \dukt\videos\errors\ApiResponseException
551
     */
552
    private function performVideosRequest($uri, $params): array
553
    {
554
        $query = $this->queryFromParams($params);
555
556
        $data = $this->get($uri, [
557
            'query' => $query
558
        ]);
559
560
        $videos = $this->parseVideos($data['data']);
561
562
        $more = false;
563
        $moreToken = null;
564
565
        if ($data['paging']['next']) {
566
            $more = true;
567
            $moreToken = $query['page'] + 1;
568
        }
569
570
        return [
571
            'videos' => $videos,
572
            'moreToken' => $moreToken,
573
            'more' => $more
574
        ];
575
    }
576
577
    /**
578
     * @param array $params
579
     *
580
     * @return array
581
     */
582
    private function queryFromParams(array $params = []): array
583
    {
584
        $query = [];
585
586
        $query['full_response'] = 1;
587
588
        if (!empty($params['moreToken'])) {
589
            $query['page'] = $params['moreToken'];
590
            unset($params['moreToken']);
591
        } else {
592
            $query['page'] = 1;
593
        }
594
595
        // $params['moreToken'] = $query['page'] + 1;
596
597
        if (!empty($params['q'])) {
598
            $query['query'] = $params['q'];
599
            unset($params['q']);
600
        }
601
602
        $query['per_page'] = $this->getVideosPerPage();
603
        $query = array_merge($query, $params);
604
605
        return $query;
606
    }
607
}
608