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\base; |
||||||
9 | |||||||
10 | use Craft; |
||||||
11 | use craft\helpers\Json; |
||||||
12 | use craft\helpers\UrlHelper; |
||||||
13 | use dukt\videos\errors\ApiResponseException; |
||||||
14 | use dukt\videos\errors\GatewayMethodNotFoundException; |
||||||
15 | use dukt\videos\errors\JsonParsingException; |
||||||
16 | use dukt\videos\errors\VideoNotFoundException; |
||||||
17 | use dukt\videos\Plugin as Videos; |
||||||
18 | use dukt\videos\Plugin; |
||||||
19 | use dukt\videos\records\Token as TokenRecord; |
||||||
20 | use GuzzleHttp\Exception\BadResponseException; |
||||||
21 | use Exception; |
||||||
22 | use Psr\Http\Message\ResponseInterface; |
||||||
23 | use yii\web\Response; |
||||||
24 | |||||||
25 | |||||||
26 | /** |
||||||
27 | * Gateway is the base class for classes representing video gateways. |
||||||
28 | * |
||||||
29 | * @author Dukt <[email protected]> |
||||||
30 | * @since 1.0 |
||||||
31 | */ |
||||||
32 | abstract class Gateway implements GatewayInterface |
||||||
33 | { |
||||||
34 | // Public Methods |
||||||
35 | // ========================================================================= |
||||||
36 | |||||||
37 | /** |
||||||
38 | * Return the handle of the gateway based on its class name |
||||||
39 | * |
||||||
40 | * @return string |
||||||
41 | */ |
||||||
42 | public function getHandle(): string |
||||||
43 | { |
||||||
44 | $handle = \get_class($this); |
||||||
45 | |||||||
46 | return strtolower(substr($handle, strrpos($handle, "\\") + 1)); |
||||||
47 | } |
||||||
48 | |||||||
49 | /** |
||||||
50 | * Returns the icon URL. |
||||||
51 | * |
||||||
52 | * @return string|false|null |
||||||
53 | */ |
||||||
54 | public function getIconUrl() |
||||||
55 | { |
||||||
56 | $iconAlias = $this->getIconAlias(); |
||||||
57 | |||||||
58 | if (file_exists(Craft::getAlias($iconAlias))) { |
||||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||||
59 | return Craft::$app->assetManager->getPublishedUrl($iconAlias, true); |
||||||
0 ignored issues
–
show
The call to
yii\web\AssetManager::getPublishedUrl() has too many arguments starting with true .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above. ![]() |
|||||||
60 | } |
||||||
61 | |||||||
62 | return null; |
||||||
63 | } |
||||||
64 | |||||||
65 | /** |
||||||
66 | * OAuth Connect. |
||||||
67 | * |
||||||
68 | * @return Response |
||||||
69 | * @throws \craft\errors\MissingComponentException |
||||||
70 | * @throws \yii\base\InvalidConfigException |
||||||
71 | */ |
||||||
72 | public function oauthConnect(): Response |
||||||
73 | { |
||||||
74 | $provider = $this->getOauthProvider(); |
||||||
75 | |||||||
76 | Craft::$app->getSession()->set('videos.oauthState', $provider->getState()); |
||||||
77 | |||||||
78 | $scope = $this->getOauthScope(); |
||||||
79 | $options = $this->getOauthAuthorizationOptions(); |
||||||
80 | |||||||
81 | if (!\is_array($options)) { |
||||||
0 ignored issues
–
show
|
|||||||
82 | $options = []; |
||||||
83 | } |
||||||
84 | |||||||
85 | $options['scope'] = $scope; |
||||||
86 | |||||||
87 | $authorizationUrl = $provider->getAuthorizationUrl($options); |
||||||
88 | |||||||
89 | return Craft::$app->getResponse()->redirect($authorizationUrl); |
||||||
0 ignored issues
–
show
|
|||||||
90 | } |
||||||
91 | |||||||
92 | /** |
||||||
93 | * Returns the gateway's OAuth provider. |
||||||
94 | * |
||||||
95 | * @return mixed |
||||||
96 | * @throws \yii\base\InvalidConfigException |
||||||
97 | */ |
||||||
98 | public function getOauthProvider() |
||||||
99 | { |
||||||
100 | $options = $this->getOauthProviderOptions(); |
||||||
101 | |||||||
102 | return $this->createOauthProvider($options); |
||||||
103 | } |
||||||
104 | |||||||
105 | /** |
||||||
106 | * Returns the OAuth provider’s name. |
||||||
107 | * |
||||||
108 | * @return string |
||||||
109 | */ |
||||||
110 | public function getOauthProviderName(): string |
||||||
111 | { |
||||||
112 | return $this->getName(); |
||||||
113 | } |
||||||
114 | |||||||
115 | /** |
||||||
116 | * Returns the redirect URI. |
||||||
117 | * |
||||||
118 | * @return string |
||||||
119 | */ |
||||||
120 | public function getRedirectUri(): string |
||||||
121 | { |
||||||
122 | return UrlHelper::actionUrl('videos/oauth/callback'); |
||||||
123 | } |
||||||
124 | |||||||
125 | /** |
||||||
126 | * OAuth Scope |
||||||
127 | * |
||||||
128 | * @return array|null |
||||||
129 | */ |
||||||
130 | public function getOauthScope() |
||||||
131 | { |
||||||
132 | return null; |
||||||
133 | } |
||||||
134 | |||||||
135 | /** |
||||||
136 | * OAuth Authorization Options |
||||||
137 | * |
||||||
138 | * @return array|null |
||||||
139 | */ |
||||||
140 | public function getOauthAuthorizationOptions() |
||||||
141 | { |
||||||
142 | return null; |
||||||
143 | } |
||||||
144 | |||||||
145 | /** |
||||||
146 | * OAuth Callback |
||||||
147 | * |
||||||
148 | * @return Response |
||||||
149 | * @throws \craft\errors\MissingComponentException |
||||||
150 | * @throws \yii\base\InvalidConfigException |
||||||
151 | */ |
||||||
152 | public function oauthCallback(): Response |
||||||
153 | { |
||||||
154 | $provider = $this->getOauthProvider(); |
||||||
155 | |||||||
156 | $code = Craft::$app->getRequest()->getParam('code'); |
||||||
157 | |||||||
158 | try { |
||||||
159 | // Try to get an access token (using the authorization code grant) |
||||||
160 | $token = $provider->getAccessToken('authorization_code', [ |
||||||
161 | 'code' => $code |
||||||
162 | ]); |
||||||
163 | |||||||
164 | // Save token |
||||||
165 | Videos::$plugin->getOauth()->saveToken($this->getHandle(), $token); |
||||||
166 | |||||||
167 | // Reset session variables |
||||||
168 | |||||||
169 | // Redirect |
||||||
170 | Craft::$app->getSession()->setNotice(Craft::t('videos', 'Connected to {gateway}.', ['gateway' => $this->getName()])); |
||||||
171 | } catch (Exception $exception) { |
||||||
172 | Craft::error('Couldn’t connect to video gateway:' . "\r\n" |
||||||
173 | . 'Message: ' . "\r\n" . $exception->getMessage() . "\r\n" |
||||||
174 | . 'Trace: ' . "\r\n" . $exception->getTraceAsString(), __METHOD__); |
||||||
175 | |||||||
176 | // Failed to get the token credentials or user details. |
||||||
177 | Craft::$app->getSession()->setError($exception->getMessage()); |
||||||
178 | } |
||||||
179 | |||||||
180 | $redirectUrl = UrlHelper::cpUrl('videos/settings'); |
||||||
181 | |||||||
182 | return Craft::$app->getResponse()->redirect($redirectUrl); |
||||||
0 ignored issues
–
show
|
|||||||
183 | } |
||||||
184 | |||||||
185 | /** |
||||||
186 | * Has Token |
||||||
187 | * |
||||||
188 | * @return bool |
||||||
189 | * @throws \yii\base\InvalidConfigException |
||||||
190 | */ |
||||||
191 | public function hasToken(): bool |
||||||
192 | { |
||||||
193 | $token = Videos::$plugin->getOauth()->getToken($this->getHandle(), false); |
||||||
194 | return (bool) $token; |
||||||
195 | } |
||||||
196 | |||||||
197 | /** |
||||||
198 | * Returns the gateway's OAuth token. |
||||||
199 | * |
||||||
200 | * @return mixed |
||||||
201 | * @throws \yii\base\InvalidConfigException |
||||||
202 | */ |
||||||
203 | public function getOauthToken(): ?\League\OAuth2\Client\Token\AccessToken |
||||||
204 | { |
||||||
205 | return Videos::$plugin->getOauth()->getToken($this->getHandle()); |
||||||
206 | } |
||||||
207 | |||||||
208 | /** |
||||||
209 | * Whether the OAuth flow should be enable or not for this gateway. |
||||||
210 | * |
||||||
211 | * @return bool |
||||||
212 | */ |
||||||
213 | public function enableOauthFlow(): bool |
||||||
214 | { |
||||||
215 | return true; |
||||||
216 | } |
||||||
217 | |||||||
218 | /** |
||||||
219 | * Returns the HTML of the embed from a video ID. |
||||||
220 | * |
||||||
221 | * @param $videoId |
||||||
222 | * @param array $options |
||||||
223 | * |
||||||
224 | * @return string |
||||||
225 | */ |
||||||
226 | public function getEmbedHtml($videoId, array $options = []): string |
||||||
227 | { |
||||||
228 | $embedAttributes = [ |
||||||
229 | 'title' => 'External video from ' . $this->getHandle(), |
||||||
230 | 'frameborder' => '0', |
||||||
231 | 'allowfullscreen' => 'true', |
||||||
232 | 'allowscriptaccess' => 'true', |
||||||
233 | 'allow' => 'autoplay; encrypted-media', |
||||||
234 | ]; |
||||||
235 | |||||||
236 | $disableSize = $options['disable_size'] ?? false; |
||||||
237 | |||||||
238 | if (!$disableSize) { |
||||||
239 | $this->parseEmbedAttribute($embedAttributes, $options, 'width', 'width'); |
||||||
240 | $this->parseEmbedAttribute($embedAttributes, $options, 'height', 'height'); |
||||||
241 | } |
||||||
242 | |||||||
243 | $title = $options['title'] ?? false; |
||||||
244 | |||||||
245 | if ($title) { |
||||||
246 | $this->parseEmbedAttribute($embedAttributes, $options, 'title', 'title'); |
||||||
247 | } |
||||||
248 | |||||||
249 | $this->parseEmbedAttribute($embedAttributes, $options, 'iframeClass', 'class'); |
||||||
250 | |||||||
251 | $embedUrl = $this->getEmbedUrl($videoId, $options); |
||||||
252 | |||||||
253 | $embedAttributesString = ''; |
||||||
254 | |||||||
255 | foreach ($embedAttributes as $key => $value) { |
||||||
256 | $embedAttributesString .= ' ' . $key . '="' . $value . '"'; |
||||||
257 | } |
||||||
258 | |||||||
259 | return '<iframe src="' . $embedUrl . '"' . $embedAttributesString . '></iframe>'; |
||||||
260 | } |
||||||
261 | |||||||
262 | /** |
||||||
263 | * Returns the URL of the embed from a video ID. |
||||||
264 | * |
||||||
265 | * @param $videoId |
||||||
266 | * @param array $options |
||||||
267 | * |
||||||
268 | * @return string |
||||||
269 | */ |
||||||
270 | public function getEmbedUrl($videoId, array $options = []): string |
||||||
271 | { |
||||||
272 | $format = $this->getEmbedFormat(); |
||||||
273 | |||||||
274 | if ($options !== []) { |
||||||
275 | $queryMark = '?'; |
||||||
276 | |||||||
277 | if (strpos($this->getEmbedFormat(), '?') !== false) { |
||||||
278 | $queryMark = '&'; |
||||||
279 | } |
||||||
280 | |||||||
281 | $options = http_build_query($options); |
||||||
282 | |||||||
283 | $format .= $queryMark . $options; |
||||||
284 | } |
||||||
285 | |||||||
286 | return sprintf($format, $videoId); |
||||||
287 | } |
||||||
288 | |||||||
289 | /** |
||||||
290 | * Returns the javascript origin URL. |
||||||
291 | * |
||||||
292 | * @return string |
||||||
293 | * @throws \craft\errors\SiteNotFoundException |
||||||
294 | */ |
||||||
295 | public function getJavascriptOrigin(): string |
||||||
296 | { |
||||||
297 | return UrlHelper::baseUrl(); |
||||||
298 | } |
||||||
299 | |||||||
300 | /** |
||||||
301 | * Returns the account. |
||||||
302 | * |
||||||
303 | * @return mixed |
||||||
304 | * @throws Exception |
||||||
305 | */ |
||||||
306 | public function getAccount() |
||||||
307 | { |
||||||
308 | $token = $this->getOauthToken(); |
||||||
309 | |||||||
310 | if ($token !== null) { |
||||||
311 | $account = Videos::$plugin->getCache()->get(['getAccount', $token]); |
||||||
312 | |||||||
313 | if (!$account) { |
||||||
314 | $oauthProvider = $this->getOauthProvider(); |
||||||
315 | $account = $oauthProvider->getResourceOwner($token); |
||||||
316 | |||||||
317 | Videos::$plugin->getCache()->set(['getAccount', $token], $account); |
||||||
318 | } |
||||||
319 | |||||||
320 | if ($account) { |
||||||
321 | return $account; |
||||||
322 | } |
||||||
323 | } |
||||||
324 | |||||||
325 | return null; |
||||||
326 | } |
||||||
327 | |||||||
328 | /** |
||||||
329 | * Returns a video from its public URL. |
||||||
330 | * |
||||||
331 | * @param $url |
||||||
332 | * |
||||||
333 | * @return mixed |
||||||
334 | * @throws VideoNotFoundException |
||||||
335 | */ |
||||||
336 | public function getVideoByUrl($url): \dukt\videos\models\Video |
||||||
337 | { |
||||||
338 | $url = $url['url']; |
||||||
339 | |||||||
340 | $videoId = $this->extractVideoIdFromUrl($url); |
||||||
341 | |||||||
342 | if (!$videoId) { |
||||||
343 | throw new VideoNotFoundException('Video not found with url given.'); |
||||||
344 | } |
||||||
345 | |||||||
346 | return $this->getVideoById($videoId); |
||||||
0 ignored issues
–
show
It seems like
$videoId can also be of type true ; however, parameter $id of dukt\videos\base\GatewayInterface::getVideoById() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
347 | } |
||||||
348 | |||||||
349 | /** |
||||||
350 | * Returns a list of videos. |
||||||
351 | * |
||||||
352 | * @param $method |
||||||
353 | * @param $options |
||||||
354 | * |
||||||
355 | * @return mixed |
||||||
356 | * @throws GatewayMethodNotFoundException |
||||||
357 | */ |
||||||
358 | public function getVideos($method, $options) |
||||||
359 | { |
||||||
360 | $realMethod = 'getVideos' . ucwords($method); |
||||||
361 | |||||||
362 | if (method_exists($this, $realMethod)) { |
||||||
363 | return $this->{$realMethod}($options); |
||||||
364 | } |
||||||
365 | |||||||
366 | throw new GatewayMethodNotFoundException('Gateway method “' . $realMethod . '” not found.'); |
||||||
367 | } |
||||||
368 | |||||||
369 | /** |
||||||
370 | * Number of videos per page. |
||||||
371 | * |
||||||
372 | * @return mixed |
||||||
373 | */ |
||||||
374 | public function getVideosPerPage() |
||||||
375 | { |
||||||
376 | return Videos::$plugin->getSettings()->videosPerPage; |
||||||
377 | } |
||||||
378 | |||||||
379 | /** |
||||||
380 | * Returns the OAuth provider options. |
||||||
381 | * |
||||||
382 | * @param bool $parse |
||||||
383 | * @throws \yii\base\InvalidConfigException |
||||||
384 | * @return mixed[]|null |
||||||
385 | */ |
||||||
386 | public function getOauthProviderOptions(bool $parse = true): array |
||||||
387 | { |
||||||
388 | return Plugin::getInstance()->getOauthProviderOptions($this->getHandle(), $parse); |
||||||
0 ignored issues
–
show
|
|||||||
389 | } |
||||||
390 | |||||||
391 | /** |
||||||
392 | * Whether the gateway supports search or not. |
||||||
393 | * |
||||||
394 | * @return bool |
||||||
395 | */ |
||||||
396 | public function supportsSearch(): bool |
||||||
397 | { |
||||||
398 | return false; |
||||||
399 | } |
||||||
400 | |||||||
401 | // Protected Methods |
||||||
402 | // ========================================================================= |
||||||
403 | |||||||
404 | /** |
||||||
405 | * Performs a GET request on the API. |
||||||
406 | * |
||||||
407 | * @param $uri |
||||||
408 | * @param array $options |
||||||
409 | * |
||||||
410 | * @return array |
||||||
411 | * @throws ApiResponseException |
||||||
412 | */ |
||||||
413 | protected function get($uri, array $options = []): array |
||||||
414 | { |
||||||
415 | $client = $this->createClient(); |
||||||
0 ignored issues
–
show
The method
createClient() does not exist on dukt\videos\base\Gateway . Since it exists in all sub-types, consider adding an abstract or default implementation to dukt\videos\base\Gateway .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
416 | |||||||
417 | try { |
||||||
418 | $response = $client->request('GET', $uri, $options); |
||||||
419 | |||||||
420 | if (Videos::$plugin->getVideos()->pluginDevMode && $this->getHandle() === 'vimeo') { |
||||||
421 | Craft::info('URI: '.Json::encode($uri), __METHOD__); |
||||||
422 | Craft::info('Options: '.Json::encode($options), __METHOD__); |
||||||
423 | Craft::info('Vimeo X-RateLimit-Limit: '.Json::encode($response->getHeader('X-RateLimit-Limit')), __METHOD__); |
||||||
424 | Craft::info('Vimeo X-RateLimit-Remaining: '.Json::encode($response->getHeader('X-RateLimit-Remaining')), __METHOD__); |
||||||
425 | } |
||||||
426 | |||||||
427 | $body = (string)$response->getBody(); |
||||||
428 | $data = Json::decode($body); |
||||||
429 | } catch (BadResponseException $badResponseException) { |
||||||
430 | $response = $badResponseException->getResponse(); |
||||||
431 | $body = (string)$response->getBody(); |
||||||
432 | |||||||
433 | try { |
||||||
434 | $data = Json::decode($body); |
||||||
435 | } catch (JsonParsingException $jsonParsingException) { |
||||||
436 | throw $badResponseException; |
||||||
437 | } |
||||||
438 | } |
||||||
439 | |||||||
440 | $this->checkResponse($response, $data); |
||||||
441 | |||||||
442 | return $data; |
||||||
0 ignored issues
–
show
|
|||||||
443 | } |
||||||
444 | |||||||
445 | /** |
||||||
446 | * Checks a provider response for errors. |
||||||
447 | * |
||||||
448 | * @param ResponseInterface $response |
||||||
449 | * @param $data |
||||||
450 | * |
||||||
451 | * @throws ApiResponseException |
||||||
452 | */ |
||||||
453 | protected function checkResponse(ResponseInterface $response, $data) |
||||||
454 | { |
||||||
455 | if (!empty($data['error'])) { |
||||||
456 | $code = 0; |
||||||
457 | $error = $data['error']; |
||||||
458 | |||||||
459 | if (\is_array($error)) { |
||||||
460 | $code = $error['code']; |
||||||
461 | $error = $error['message']; |
||||||
462 | } |
||||||
463 | |||||||
464 | throw new ApiResponseException($error, $code); |
||||||
465 | } |
||||||
466 | } |
||||||
467 | |||||||
468 | // Private Methods |
||||||
469 | // ========================================================================= |
||||||
470 | |||||||
471 | /** |
||||||
472 | * Parse embed attribute. |
||||||
473 | * |
||||||
474 | * @param $embedAttributes |
||||||
475 | * @param $options |
||||||
476 | * @param $option |
||||||
477 | * @param $attribute |
||||||
478 | * |
||||||
479 | * @return null |
||||||
480 | */ |
||||||
481 | private function parseEmbedAttribute(&$embedAttributes, &$options, $option, $attribute) |
||||||
482 | { |
||||||
483 | if (isset($options[$option])) { |
||||||
484 | $embedAttributes[$attribute] = $options[$option]; |
||||||
485 | unset($options[$option]); |
||||||
486 | } |
||||||
487 | |||||||
488 | return null; |
||||||
489 | } |
||||||
490 | } |
||||||
491 |