Issues (47)

src/gateways/Vimeo.php (2 issues)

1
<?php
2
/**
3
 * @link      https://dukt.net/videos/
4
 * @copyright Copyright (c) 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\helpers\VideosHelper;
14
use dukt\videos\models\Collection;
15
use dukt\videos\models\Section;
16
use dukt\videos\models\Video;
17
use GuzzleHttp\Client;
18
use DateTime;
19
20
/**
21
 * Vimeo represents the Vimeo gateway
22
 *
23
 * @author    Dukt <[email protected]>
24
 * @since     1.0
25
 */
26
class Vimeo extends Gateway
27
{
28
    // Public Methods
29
    // =========================================================================
30
31
    /**
32
     * @inheritDoc
33
     *
34
     * @return string
35
     */
36
    public function getIconAlias(): string
37
    {
38
        return '@dukt/videos/icons/vimeo.svg';
39
    }
40
41
    /**
42
     * @inheritDoc
43
     *
44
     * @return string
45
     */
46
    public function getName(): string
47
    {
48
        return 'Vimeo';
49
    }
50
51
    /**
52
     * Returns the OAuth provider’s API console URL.
53
     *
54
     * @return string
55
     */
56
    public function getOauthProviderApiConsoleUrl(): string
57
    {
58
        return 'https://developer.vimeo.com/apps';
59
    }
60
61
    /**
62
     * @inheritDoc
63
     *
64
     * @return array
65
     */
66
    public function getOauthScope(): array
67
    {
68
        return [
69
            'public',
70
            'private',
71
        ];
72
    }
73
74
    /**
75
     * Creates the OAuth provider.
76
     *
77
     * @param array $options
78
     *
79
     * @return \Dukt\OAuth2\Client\Provider\Vimeo
80
     */
81
    public function createOauthProvider(array $options): \Dukt\OAuth2\Client\Provider\Vimeo
82
    {
83
        return new \Dukt\OAuth2\Client\Provider\Vimeo($options);
84
    }
85
86
    /**
87
     * @inheritDoc
88
     *
89
     * @return array
90
     * @throws CollectionParsingException
91
     * @throws \dukt\videos\errors\ApiResponseException
92
     */
93
    public function getExplorerSections(): array
94
    {
95
        $sections = [];
96
97
98
        // Library
99
100
        $sections[] = new Section([
101
            'name' => 'Library',
102
            'collections' => [
103
                new Collection([
104
                    'name' => 'Uploads',
105
                    'method' => 'uploads',
106
                    'icon' => 'video-camera',
107
                ]),
108
                new Collection([
109
                    'name' => 'Likes',
110
                    'method' => 'likes',
111
                    'icon' => 'thumb-up'
112
                ]),
113
            ]
114
        ]);
115
116
117
        // Folders
118
119
        $folders = $this->getCollectionsFolders();
120
121
        $collections = [];
122
123
        foreach ($folders as $folder) {
124
            $collections[] = new Collection([
125
                'name' => $folder['title'],
126
                'method' => 'folder',
127
                'options' => ['id' => $folder['id']],
128
                'icon' => 'folder',
129
            ]);
130
        }
131
132
        if ($collections !== []) {
133
            $sections[] = new Section([
134
                'name' => 'Folders',
135
                'collections' => $collections,
136
            ]);
137
        }
138
139
        // Albums
140
141
        $albums = $this->getCollectionsAlbums();
142
143
        $collections = [];
144
145
        foreach ($albums as $album) {
146
            $collections[] = new Collection([
147
                'name' => $album['title'],
148
                'method' => 'album',
149
                'options' => ['id' => $album['id']],
150
                'icon' => 'layout'
151
            ]);
152
        }
153
154
        if ($collections !== []) {
155
            $sections[] = new Section([
156
                'name' => 'Showcases',
157
                'collections' => $collections,
158
            ]);
159
        }
160
161
162
        // channels
163
164
        $channels = $this->getCollectionsChannels();
165
166
        $collections = [];
167
168
        foreach ($channels as $channel) {
169
            $collections[] = new Collection([
170
                'name' => $channel['title'],
171
                'method' => 'channel',
172
                'options' => ['id' => $channel['id']],
173
            ]);
174
        }
175
176
        if ($collections !== []) {
177
            $sections[] = new Section([
178
                'name' => 'Channels',
179
                'collections' => $collections,
180
            ]);
181
        }
182
183
        return $sections;
184
    }
185
186
    /**
187
     * @inheritDoc
188
     *
189
     * @param string $id
190
     *
191
     * @return Video
192
     * @throws VideoNotFoundException
193
     * @throws \dukt\videos\errors\ApiResponseException
194
     */
195
    public function getVideoById(string $id): Video
196
    {
197
        $data = $this->get('videos/' . $id, [
198
            'query' => [
199
                'fields' => 'created_time,description,duration,height,link,name,pictures,pictures,privacy,stats,uri,user,width,download,review_link,files'
200
            ],
201
        ]);
202
203
        if ($data !== []) {
204
            return $this->parseVideo($data);
205
        }
206
207
        throw new VideoNotFoundException('Video not found.');
208
    }
209
210
    /**
211
     * @inheritDoc
212
     *
213
     * @return string
214
     */
215
    public function getEmbedFormat(): string
216
    {
217
        return 'https://player.vimeo.com/video/%s';
218
    }
219
220
    /**
221
     * @param string $url
222
     *
223
     * @return bool|string
224
     */
225
    public function extractVideoIdFromUrl(string $url)
226
    {
227
        // check if url works with this service and extract video_id
228
229
        $videoId = false;
230
231
        $regexp = ['/^https?:\/\/(www\.)?vimeo\.com\/([0-9]*)/', 2];
232
233
        if (preg_match($regexp[0], $url, $matches, PREG_OFFSET_CAPTURE) > 0) {
234
235
            // regexp match key
236
            $match_key = $regexp[1];
237
238
239
            // define video id
240
            $videoId = $matches[$match_key][0];
241
242
243
            // Fixes the youtube &feature_gdata bug
244
            if (strpos($videoId, '&')) {
245
                $videoId = substr($videoId, 0, strpos($videoId, '&'));
246
            }
247
        }
248
249
        // here we should have a valid video_id or false if service not matching
250
        return $videoId;
251
    }
252
253
    /**
254
     * @inheritDoc
255
     *
256
     * @return bool
257
     */
258
    public function supportsSearch(): bool
259
    {
260
        return true;
261
    }
262
263
    // Protected
264
    // =========================================================================
265
266
    /**
267
     * Returns an authenticated Guzzle client
268
     *
269
     * @return Client
270
     * @throws \yii\base\InvalidConfigException
271
     */
272
    protected function createClient(): Client
273
    {
274
        $options = [
275
            'base_uri' => $this->getApiUrl(),
276
            'headers' => [
277
                'Accept' => 'application/vnd.vimeo.*+json;version=' . $this->getApiVersion(),
278
                'Authorization' => 'Bearer ' . $this->getOauthToken()->getToken()
279
            ],
280
        ];
281
282
        return new Client($options);
283
    }
284
285
    /**
286
     * Returns a list of videos in an album
287
     *
288
     * @param array $params
289
     *
290
     * @return array
291
     * @throws \dukt\videos\errors\ApiResponseException
292
     */
293
    protected function getVideosAlbum(array $params = []): array
294
    {
295
        $albumId = $params['id'];
296
        unset($params['id']);
297
298
        // albums/#album_id
299
        return $this->performVideosRequest('me/albums/' . $albumId . '/videos', $params);
300
    }
301
302
    /**
303
     * Returns a list of videos in an folder
304
     *
305
     * @param array $params
306
     *
307
     * @return array
308
     * @throws \dukt\videos\errors\ApiResponseException
309
     */
310
    protected function getVideosFolder(array $params = []): array
311
    {
312
        $folderId = $params['id'];
313
        unset($params['id']);
314
315
        // folders/#folder_id
316
        return $this->performVideosRequest('me/folders/' . $folderId . '/videos', $params);
317
    }
318
319
    /**
320
     * Returns a list of videos in a channel
321
     *
322
     * @param array $params
323
     *
324
     * @return array
325
     * @throws \dukt\videos\errors\ApiResponseException
326
     */
327
    protected function getVideosChannel(array $params = []): array
328
    {
329
        $params['channel_id'] = $params['id'];
330
        unset($params['id']);
331
332
        return $this->performVideosRequest('channels/' . $params['channel_id'] . '/videos', $params);
333
    }
334
335
    /**
336
     * Returns a list of favorite videos
337
     *
338
     * @param array $params
339
     *
340
     * @return array
341
     * @throws \dukt\videos\errors\ApiResponseException
342
     */
343
    protected function getVideosLikes(array $params = []): array
344
    {
345
        return $this->performVideosRequest('me/likes', $params);
346
    }
347
348
    /**
349
     * Returns a list of videos from a search request
350
     *
351
     * @param array $params
352
     *
353
     * @return array
354
     * @throws \dukt\videos\errors\ApiResponseException
355
     */
356
    protected function getVideosSearch(array $params = []): array
357
    {
358
        return $this->performVideosRequest('videos', $params);
359
    }
360
361
    /**
362
     * Returns a list of uploaded videos
363
     *
364
     * @param array $params
365
     *
366
     * @return array
367
     * @throws \dukt\videos\errors\ApiResponseException
368
     */
369
    protected function getVideosUploads(array $params = []): array
370
    {
371
        return $this->performVideosRequest('me/videos', $params);
372
    }
373
374
    // Private Methods
375
    // =========================================================================
376
377
    /**
378
     * @return string
379
     */
380
    private function getApiUrl(): string
381
    {
382
        return 'https://api.vimeo.com/';
383
    }
384
385
    /**
386
     * @return string
387
     */
388
    private function getApiVersion(): string
389
    {
390
        return '3.0';
391
    }
392
393
    /**
394
     * @param array $params
395
     *
396
     * @return array
397
     * @throws CollectionParsingException
398
     * @throws \dukt\videos\errors\ApiResponseException
399
     */
400
    private function getCollectionsAlbums(array $params = []): array
401
    {
402
        $query = $this->queryFromParams($params);
403
        $query['fields'] = 'name,uri,stats';
404
405
        $data = $this->get('me/albums', [
406
            'query' => $query,
407
        ]);
408
409
        return $this->parseCollections('album', $data['data']);
410
    }
411
412
    /**
413
     * @param array $params
414
     *
415
     * @return array
416
     * @throws CollectionParsingException
417
     * @throws \dukt\videos\errors\ApiResponseException
418
     */
419
    private function getCollectionsFolders(array $params = []): array
420
    {
421
        $query = $this->queryFromParams($params);
422
        $query['fields'] = 'name,uri';
423
424
        $data = $this->get('me/folders', [
425
            'query' => $query
426
        ]);
427
428
        return $this->parseCollections('folder', $data['data']);
429
    }
430
431
    /**
432
     * @param array $params
433
     *
434
     * @return array
435
     * @throws CollectionParsingException
436
     * @throws \dukt\videos\errors\ApiResponseException
437
     */
438
    private function getCollectionsChannels(array $params = []): array
439
    {
440
        $query = $this->queryFromParams($params);
441
        $query['fields'] = 'name,uri';
442
443
        $data = $this->get('me/channels', [
444
            'query' => $query
445
        ]);
446
447
        return $this->parseCollections('channel', $data['data']);
448
    }
449
450
    /**
451
     * @param $type
452
     * @param $collections
453
     *
454
     * @return array
455
     * @throws CollectionParsingException
456
     */
457
    private function parseCollections($type, array $collections): array
458
    {
459
        $parseCollections = [];
460
461
        foreach ($collections as $collection) {
462
463
            switch ($type) {
464
                case 'folder':
465
                    $parsedCollection = $this->parseCollectionFolder($collection);
466
                    break;
467
                case 'album':
468
                    $parsedCollection = $this->parseCollectionAlbum($collection);
469
                    break;
470
                case 'channel':
471
                    $parsedCollection = $this->parseCollectionChannel($collection);
472
                    break;
473
474
                default:
475
                    throw new CollectionParsingException('Couldn’t parse collection of type ”' . $type . '“.');
476
            }
477
478
            $parseCollections[] = $parsedCollection;
479
        }
480
481
        return $parseCollections;
482
    }
483
484
    /**
485
     * @param $data
486
     *
487
     * @return array
488
     */
489
    private function parseCollectionAlbum($data): array
490
    {
491
        $collection = [];
492
        $collection['id'] = substr($data['uri'], strpos($data['uri'], '/albums/') + \strlen('/albums/'));
493
        $collection['url'] = $data['uri'];
494
        $collection['title'] = $data['name'];
495
        $collection['totalVideos'] = $data['stats']['videos'] ?? 0;
496
497
        return $collection;
498
    }
499
500
    /**
501
     * @param $data
502
     *
503
     * @return array
504
     */
505
    private function parseCollectionFolder($data): array
506
    {
507
        $collection = [];
508
        $collection['id'] = substr($data['uri'], strpos($data['uri'], '/projects/') + \strlen('/projects/'));
509
        $collection['url'] = $data['uri'];
510
        $collection['title'] = $data['name'];
511
        $collection['totalVideos'] = $data['metadata']['connections']['videos']['total'] ?? 0;
512
513
        return $collection;
514
    }
515
516
    /**
517
     * @param $data
518
     *
519
     * @return array
520
     */
521
    private function parseCollectionChannel($data): array
522
    {
523
        $collection = [];
524
        $collection['id'] = substr($data['uri'], strpos($data['uri'], '/channels/') + \strlen('/channels/'));
525
        $collection['url'] = $data['uri'];
526
        $collection['title'] = $data['name'];
527
        $collection['totalVideos'] = $data['stats']['videos'] ?? 0;
528
529
        return $collection;
530
    }
531
532
    /**
533
     * @param $data
534
     *
535
     * @return array
536
     */
537
    private function parseVideos(array $data): array
538
    {
539
        $videos = [];
540
541
        if (!empty($data)) {
542
            foreach ($data as $videoData) {
543
                $video = $this->parseVideo($videoData);
544
545
                $videos[] = $video;
546
            }
547
        }
548
549
        return $videos;
550
    }
551
552
    /**
553
     * Parse video.
554
     *
555
     * @param array $data
556
     *
557
     * @return Video
558
     */
559
    private function parseVideo(array $data): Video
560
    {
561
        $video = new Video;
562
        $video->raw = $data;
563
        $video->authorName = $data['user']['name'];
564
        $video->authorUrl = $data['user']['link'];
565
        $video->date = new DateTime($data['created_time']);
566
        $video->description = $data['description'];
567
        $video->gatewayHandle = 'vimeo';
568
        $video->gatewayName = 'Vimeo';
569
        $video->id = (int)substr($data['uri'], \strlen('/videos/'));
570
        $video->plays = $data['stats']['plays'] ?? 0;
571
        $video->title = $data['name'];
572
        $video->url = 'https://vimeo.com/' . substr($data['uri'], 8);
573
        $video->width = $data['width'];
574
        $video->height = $data['height'];
575
576
        // Video duration
577
        $video->durationSeconds = $data['duration'];
578
        $video->duration8601 = VideosHelper::getDuration8601($data['duration']);
0 ignored issues
show
Documentation Bug introduced by
It seems like dukt\videos\helpers\Vide...8601($data['duration']) of type string is incompatible with the declared type integer|null of property $duration8601.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
579
580
        $this->parsePrivacy($video, $data);
581
        $this->parseThumbnails($video, $data);
582
583
        return $video;
584
    }
585
586
    /**
587
     * Parse video’s privacy data.
588
     *
589
     * @param Video $video
590
     * @param array $data
591
     * @return null
592
     */
593
    private function parsePrivacy(Video $video, array $data)
594
    {
595
        $privacyOptions = ['nobody', 'contacts', 'password', 'users', 'disable'];
596
597
        if (in_array($data['privacy']['view'], $privacyOptions, true)) {
598
            $video->private = true;
599
        }
600
601
        return null;
602
    }
603
604
605
    /**
606
     * Parse thumbnails.
607
     *
608
     * @param Video $video
609
     * @param array $data
610
     *
611
     * @return null
612
     */
613
    private function parseThumbnails(Video $video, array $data)
614
    {
615
        if (!\is_array($data['pictures'])) {
616
            return null;
617
        }
618
619
        $largestSize = 0;
620
621
        foreach ($this->getVideoDataPictures($data, 'thumbnail') as $picture) {
622
            // Retrieve highest quality thumbnail
623
            if ($picture['width'] > $largestSize) {
624
                $video->thumbnailSource = $picture['link'];
625
                $largestSize = $picture['width'];
626
            }
627
        }
628
629
630
        $video->thumbnailLargeSource = $video->thumbnailSource;
0 ignored issues
show
Deprecated Code introduced by
The property dukt\videos\models\Video::$thumbnailLargeSource has been deprecated: in 2.1. Use [[\dukt\videos\models\Video::$thumbnailSource]] instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

630
        /** @scrutinizer ignore-deprecated */ $video->thumbnailLargeSource = $video->thumbnailSource;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
631
632
        return null;
633
    }
634
635
    /**
636
     * Get video data pictures.
637
     *
638
     * @param array $data
639
     * @param string $type
640
     * @return array
641
     */
642
    private function getVideoDataPictures(array $data, string $type = 'thumbnail'): array
643
    {
644
        $pictures = [];
645
646
        foreach ($data['pictures'] as $picture) {
647
            if ($picture['type'] === $type) {
648
                $pictures[] = $picture;
649
            }
650
        }
651
652
        return $pictures;
653
    }
654
655
    /**
656
     * @param $uri
657
     * @param $params
658
     *
659
     * @return array
660
     * @throws \dukt\videos\errors\ApiResponseException
661
     */
662
    private function performVideosRequest($uri, array $params): array
663
    {
664
        $query = $this->queryFromParams($params);
665
        $query['fields'] = 'created_time,description,duration,height,link,name,pictures,pictures,privacy,stats,uri,user,width,download,review_link,files';
666
667
        $data = $this->get($uri, [
668
            'query' => $query
669
        ]);
670
671
        $videos = $this->parseVideos($data['data']);
672
673
        $more = false;
674
        $moreToken = null;
675
676
        if ($data['paging']['next']) {
677
            $more = true;
678
            $moreToken = $query['page'] + 1;
679
        }
680
681
        return [
682
            'videos' => $videos,
683
            'moreToken' => $moreToken,
684
            'more' => $more
685
        ];
686
    }
687
688
    /**
689
     * @param array $params
690
     *
691
     * @return array
692
     */
693
    private function queryFromParams(array $params = []): array
694
    {
695
        $query = [
696
            'full_response' => 1,
697
            'page' => 1,
698
            'per_page' => $this->getVideosPerPage(),
699
        ];
700
701
        if (!empty($params['moreToken'])) {
702
            $query['page'] = $params['moreToken'];
703
            unset($params['moreToken']);
704
        }
705
706
        if (!empty($params['q'])) {
707
            $query['query'] = $params['q'];
708
            unset($params['q']);
709
        }
710
711
        return array_merge($query, $params);
712
    }
713
}
714