Passed
Push — v2 ( cb23fe...963ffc )
by Benjamin
03:29
created

Vimeo::getAlbumsSection()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 12
nc 4
nop 0
dl 0
loc 21
rs 9.3142
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[] = $this->getLibrarySection();
100
101
102
        // Albums
103
104
        $albumsSection = $this->getAlbumsSection();
105
106
        if($albumsSection) {
107
            $sections[] = $albumsSection;
108
        }
109
110
111
        // channels
112
113
        $channelsSection = $this->getChannelsSection();
114
115
        if($channelsSection) {
116
            $sections[] = $channelsSection;
117
        }
118
119
        return $sections;
120
    }
121
122
    /**
123
     * @inheritDoc
124
     *
125
     * @param string $id
126
     *
127
     * @return Video
128
     * @throws VideoNotFoundException
129
     * @throws \dukt\videos\errors\ApiResponseException
130
     */
131
    public function getVideoById(string $id): Video
132
    {
133
        $data = $this->get('videos/'.$id, [
134
            'query' => [
135
                'fields' => 'created_time,description,duration,height,link,name,pictures,pictures,privacy,stats,uri,user,width,download,review_link,files'
136
            ],
137
        ]);
138
139
        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...
140
            return $this->parseVideo($data);
141
        }
142
143
        throw new VideoNotFoundException('Video not found.');
144
    }
145
146
    /**
147
     * @inheritDoc
148
     *
149
     * @return string
150
     */
151
    public function getEmbedFormat(): string
152
    {
153
        return 'https://player.vimeo.com/video/%s';
154
    }
155
156
    /**
157
     * @param string $url
158
     *
159
     * @return bool|string
160
     */
161
    public function extractVideoIdFromUrl(string $url)
162
    {
163
        // check if url works with this service and extract video_id
164
165
        $videoId = false;
166
167
        $regexp = ['/^https?:\/\/(www\.)?vimeo\.com\/([0-9]*)/', 2];
168
169
        if (preg_match($regexp[0], $url, $matches, PREG_OFFSET_CAPTURE) > 0) {
170
171
            // regexp match key
172
            $match_key = $regexp[1];
173
174
175
            // define video id
176
            $videoId = $matches[$match_key][0];
177
178
179
            // Fixes the youtube &feature_gdata bug
180
            if (strpos($videoId, '&')) {
181
                $videoId = substr($videoId, 0, strpos($videoId, '&'));
182
            }
183
        }
184
185
        // here we should have a valid video_id or false if service not matching
186
        return $videoId;
187
    }
188
189
    /**
190
     * @inheritDoc
191
     *
192
     * @return bool
193
     */
194
    public function supportsSearch(): bool
195
    {
196
        return true;
197
    }
198
199
    // Protected
200
    // =========================================================================
201
202
    /**
203
     * Returns an authenticated Guzzle client
204
     *
205
     * @return Client
206
     * @throws \yii\base\InvalidConfigException
207
     */
208
    protected function createClient(): Client
209
    {
210
        $options = [
211
            'base_uri' => $this->getApiUrl(),
212
            'headers' => [
213
                'Accept' => 'application/vnd.vimeo.*+json;version='.$this->getApiVersion(),
214
                'Authorization' => 'Bearer '.$this->getOauthToken()->getToken()
215
            ],
216
        ];
217
218
        return new Client($options);
219
    }
220
221
    /**
222
     * Returns a list of videos in an album
223
     *
224
     * @param array $params
225
     *
226
     * @return array
227
     * @throws \dukt\videos\errors\ApiResponseException
228
     */
229
    protected function getVideosAlbum(array $params = []): array
230
    {
231
        $albumId = $params['id'];
232
        unset($params['id']);
233
234
        // albums/#album_id
235
        return $this->performVideosRequest('me/albums/'.$albumId.'/videos', $params);
236
    }
237
238
    /**
239
     * Returns a list of videos in a channel
240
     *
241
     * @param array $params
242
     *
243
     * @return array
244
     * @throws \dukt\videos\errors\ApiResponseException
245
     */
246
    protected function getVideosChannel(array $params = []): array
247
    {
248
        $params['channel_id'] = $params['id'];
249
        unset($params['id']);
250
251
        return $this->performVideosRequest('channels/'.$params['channel_id'].'/videos', $params);
252
    }
253
254
    /**
255
     * Returns a list of favorite videos
256
     *
257
     * @param array $params
258
     *
259
     * @return array
260
     * @throws \dukt\videos\errors\ApiResponseException
261
     */
262
    protected function getVideosFavorites(array $params = []): array
263
    {
264
        return $this->performVideosRequest('me/likes', $params);
265
    }
266
267
    /**
268
     * Returns a list of videos from a search request
269
     *
270
     * @param array $params
271
     *
272
     * @return array
273
     * @throws \dukt\videos\errors\ApiResponseException
274
     */
275
    protected function getVideosSearch(array $params = []): array
276
    {
277
        return $this->performVideosRequest('videos', $params);
278
    }
279
280
    /**
281
     * Returns a list of uploaded videos
282
     *
283
     * @param array $params
284
     *
285
     * @return array
286
     * @throws \dukt\videos\errors\ApiResponseException
287
     */
288
    protected function getVideosUploads(array $params = []): array
289
    {
290
        return $this->performVideosRequest('me/videos', $params);
291
    }
292
293
    // Private Methods
294
    // =========================================================================
295
296
    /**
297
     * @return string
298
     */
299
    private function getApiUrl(): string
300
    {
301
        return 'https://api.vimeo.com/';
302
    }
303
304
    /**
305
     * @return string
306
     */
307
    private function getApiVersion(): string
308
    {
309
        return '3.0';
310
    }
311
312
    /**
313
     * @param array $params
314
     *
315
     * @return array
316
     * @throws CollectionParsingException
317
     * @throws \dukt\videos\errors\ApiResponseException
318
     */
319
    private function getCollectionsAlbums(array $params = []): array
320
    {
321
        $data = $this->get('me/albums', [
322
            'query' => $this->queryFromParams($params)
323
        ]);
324
325
        return $this->parseCollections('album', $data['data']);
326
    }
327
328
    /**
329
     * @param array $params
330
     *
331
     * @return array
332
     * @throws CollectionParsingException
333
     * @throws \dukt\videos\errors\ApiResponseException
334
     */
335
    private function getCollectionsChannels(array $params = []): array
336
    {
337
        $data = $this->get('me/channels', [
338
            'query' => $this->queryFromParams($params)
339
        ]);
340
341
        return $this->parseCollections('channel', $data['data']);
342
    }
343
344
    /**
345
     * @param $type
346
     * @param $collections
347
     *
348
     * @return array
349
     * @throws CollectionParsingException
350
     */
351
    private function parseCollections($type, array $collections): array
352
    {
353
        $parseCollections = [];
354
355
        foreach ($collections as $collection) {
356
357
            switch ($type) {
358
                case 'album':
359
                    $parsedCollection = $this->parseCollectionAlbum($collection);
360
                    break;
361
                case 'channel':
362
                    $parsedCollection = $this->parseCollectionChannel($collection);
363
                    break;
364
365
                default:
366
                    throw new CollectionParsingException('Couldn’t parse collection of type ”'.$type.'“.');
367
            }
368
369
            $parseCollections[] = $parsedCollection;
370
        }
371
372
        return $parseCollections;
373
    }
374
375
    /**
376
     * @param $data
377
     *
378
     * @return array
379
     */
380
    private function parseCollectionAlbum($data): array
381
    {
382
        $collection = [];
383
        $collection['id'] = substr($data['uri'], strpos($data['uri'], '/albums/') + \strlen('/albums/'));
384
        $collection['url'] = $data['uri'];
385
        $collection['title'] = $data['name'];
386
        $collection['totalVideos'] = $data['stats']['videos'];
387
388
        return $collection;
389
    }
390
391
    /**
392
     * @param $data
393
     *
394
     * @return array
395
     */
396
    private function parseCollectionChannel($data): array
397
    {
398
        $collection = [];
399
        $collection['id'] = substr($data['uri'], strpos($data['uri'], '/channels/') + \strlen('/channels/'));
400
        $collection['url'] = $data['uri'];
401
        $collection['title'] = $data['name'];
402
        $collection['totalVideos'] = $data['stats']['videos'];
403
404
        return $collection;
405
    }
406
407
    /**
408
     * @param $data
409
     *
410
     * @return array
411
     */
412
    private function parseVideos(array $data): array
413
    {
414
        $videos = [];
415
416
        if (!empty($data)) {
417
            foreach ($data as $videoData) {
418
                $video = $this->parseVideo($videoData);
419
420
                $videos[] = $video;
421
            }
422
        }
423
424
        return $videos;
425
    }
426
427
    /**
428
     * Parse video.
429
     *
430
     * @param array $data
431
     *
432
     * @return Video
433
     */
434
    private function parseVideo(array $data): Video
435
    {
436
        $video = new Video;
437
        $video->raw = $data;
438
        $video->authorName = $data['user']['name'];
439
        $video->authorUrl = $data['user']['link'];
440
        $video->date = new DateTime($data['created_time']);
441
        $video->durationSeconds = $data['duration'];
442
        $video->description = $data['description'];
443
        $video->gatewayHandle = 'vimeo';
444
        $video->gatewayName = 'Vimeo';
445
        $video->id = (int) substr($data['uri'], \strlen('/videos/'));
446
        $video->plays = $data['stats']['plays'] ?? 0;
447
        $video->title = $data['name'];
448
        $video->url = $data['link'];
449
        $video->width = $data['width'];
450
        $video->height = $data['height'];
451
452
453
        // privacy
454
455
        if ($data['privacy']['view'] === 'nobody'
456
            || $data['privacy']['view'] === 'contacts'
457
            || $data['privacy']['view'] === 'password'
458
            || $data['privacy']['view'] === 'users'
459
            || $data['privacy']['view'] === 'disable') {
460
            $video->private = true;
461
        }
462
463
        $this->parseThumbnails($video, $data);
464
465
        return $video;
466
    }
467
468
    /**
469
     * Parse thumbnails.
470
     *
471
     * @param Video $video
472
     * @param array $data
473
     *
474
     * @return null
475
     */
476
    private function parseThumbnails(Video &$video, array $data)
477
    {
478
        // Retrieve largest thumbnail
479
480
        $largestSize = 0;
481
        $thumbSize = 0;
482
483
        if (!\is_array($data['pictures'])) {
484
            return null;
485
        }
486
487
        foreach ($data['pictures'] as $picture) {
488
            if ($picture['type'] === 'thumbnail') {
489
                if ($picture['width'] > $largestSize) {
490
                    $video->thumbnailLargeSource = $picture['link'];
491
492
                    $largestSize = $picture['width'];
493
                }
494
495
                if ($picture['width'] > $thumbSize && $thumbSize < 400) {
496
                    $video->thumbnailSource = $picture['link'];
497
498
                    $thumbSize = $picture['width'];
499
                }
500
            }
501
        }
502
503
        if (empty($video->thumbnailSource) && !empty($video->thumbnailLargeSource)) {
504
            $video->thumbnailSource = $video->thumbnailLargeSource;
505
        }
506
507
        return null;
508
    }
509
510
    /**
511
     * @param $uri
512
     * @param $params
513
     *
514
     * @return array
515
     * @throws \dukt\videos\errors\ApiResponseException
516
     */
517
    private function performVideosRequest($uri, $params): array
518
    {
519
        $query = $this->queryFromParams($params);
520
521
        $data = $this->get($uri, [
522
            'query' => $query
523
        ]);
524
525
        $videos = $this->parseVideos($data['data']);
526
527
        $more = false;
528
        $moreToken = null;
529
530
        if ($data['paging']['next']) {
531
            $more = true;
532
            $moreToken = $query['page'] + 1;
533
        }
534
535
        return [
536
            'videos' => $videos,
537
            'moreToken' => $moreToken,
538
            'more' => $more
539
        ];
540
    }
541
542
    /**
543
     * @param array $params
544
     *
545
     * @return array
546
     */
547
    private function queryFromParams(array $params = []): array
548
    {
549
        $query = [];
550
551
        $query['full_response'] = 1;
552
553
        if (!empty($params['moreToken'])) {
554
            $query['page'] = $params['moreToken'];
555
            unset($params['moreToken']);
556
        } else {
557
            $query['page'] = 1;
558
        }
559
560
        // $params['moreToken'] = $query['page'] + 1;
561
562
        if (!empty($params['q'])) {
563
            $query['query'] = $params['q'];
564
            unset($params['q']);
565
        }
566
567
        $query['per_page'] = $this->getVideosPerPage();
568
        $query = array_merge($query, $params);
569
570
        return $query;
571
    }
572
    
573
    /**
574
     * @return Section
575
     */
576
    private function getLibrarySection(): Section
577
    {
578
        return new Section([
579
            'name' => 'Library',
580
            'collections' => [
581
                new Collection([
582
                    'name' => 'Uploads',
583
                    'method' => 'uploads',
584
                ]),
585
                new Collection([
586
                    'name' => 'Favorites',
587
                    'method' => 'favorites',
588
                ]),
589
            ]
590
        ]);
591
    }
592
593
    /**
594
     * @return Section|null
595
     * @throws CollectionParsingException
596
     * @throws \dukt\videos\errors\ApiResponseException
597
     */
598
    private function getAlbumsSection()
599
    {
600
        $albums = $this->getCollectionsAlbums();
601
602
        $collections = [];
603
604
        foreach ($albums as $album) {
605
            $collections[] = new Collection([
606
                'name' => $album['title'],
607
                'method' => 'album',
608
                'options' => ['id' => $album['id']]
609
            ]);
610
        }
611
612
        if (\count($collections) === 0) {
613
            return null;
614
        }
615
616
        return new Section([
617
            'name' => 'Playlists',
618
            'collections' => $collections,
619
        ]);
620
    }
621
622
    /**
623
     * @return Section|null
624
     * @throws CollectionParsingException
625
     * @throws \dukt\videos\errors\ApiResponseException
626
     */
627
    private function getChannelsSection()
628
    {
629
        $channels = $this->getCollectionsChannels();
630
631
        $collections = [];
632
633
        foreach ($channels as $channel) {
634
            $collections[] = new Collection([
635
                'name' => $channel['title'],
636
                'method' => 'channel',
637
                'options' => ['id' => $channel['id']],
638
            ]);
639
        }
640
641
        if (\count($collections) === 0) {
642
            return null;
643
        }
644
645
        return new Section([
646
            'name' => 'Channels',
647
            'collections' => $collections,
648
        ]);
649
    }
650
}
651