PluginProbe
Automatic YouTube Gallery – Embed Auto-Updating YouTube Video Galleries, Feeds, Playlists & Channels / 2.6.4
Automatic YouTube Gallery – Embed Auto-Updating YouTube Video Galleries, Feeds, Playlists & Channels v2.6.4
2.9.1 2.9.0 trunk 1.0.0 1.1.0 1.2.0 1.3.0 1.4.0 1.5.0 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 2.0.0 2.1.0 2.2.0 2.3.2 2.3.3 2.3.5 2.3.6 2.3.8 2.3.9 2.4.3 All 37 releases
automatic-youtube-gallery / includes / youtube-api.php

youtube-api.php in Automatic YouTube Gallery – Embed Auto-Updating YouTube Video Galleries, Feeds, Playlists & Channels 2.6.4, at includes/youtube-api.php

1,024 lines 26.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * A wrapper class for the Youtube Data API v3.
5 *
6 * @link https://plugins360.com
7 * @since 1.0.0
8 *
9 * @package Automatic_YouTube_Gallery
10 */
11
12 // Exit if accessed directly
13 if ( ! defined( 'WPINC' ) ) {
14 die;
15 }
16
17 /**
18 * AYG_YouTube_API class.
19 *
20 * @since 1.0.0
21 */
22 class AYG_YouTube_API {
23
24 /**
25 * The YouTube API Key.
26 *
27 * @since 1.0.0
28 * @access protected
29 * @var string
30 */
31 protected $api_key;
32
33 /**
34 * Array of query params.
35 *
36 * @since 2.5.7
37 * @access protected
38 * @var array
39 */
40 protected $params = array();
41
42 /**
43 * Is development mode enabled?
44 *
45 * @since 2.3.0
46 * @access protected
47 * @var bool
48 */
49 protected $is_development_mode = false;
50
51 /**
52 * The YouTube API URLs.
53 *
54 * @since 1.0.0
55 * @access protected
56 * @var array
57 */
58 public $api_urls = array(
59 'playlistItems.list' => 'https://www.googleapis.com/youtube/v3/playlistItems',
60 'channels.list' => 'https://www.googleapis.com/youtube/v3/channels',
61 'search.list' => 'https://www.googleapis.com/youtube/v3/search',
62 'videos.list' => 'https://www.googleapis.com/youtube/v3/videos'
63 );
64
65 /**
66 * Get videos.
67 *
68 * @since 1.0.0
69 * @param array $params Array of query params.
70 * @return mixed
71 */
72 public function query( $params = array() ) {
73 // Get YouTube API Key
74 $general_settings = get_option( 'ayg_general_settings' );
75
76 if ( empty( $general_settings['api_key'] ) ) {
77 return $this->get_error( __( 'YouTube API key not found.', 'automatic-youtube-gallery' ) . ' ' . sprintf( __( 'Kindly follow this URL <a href="%s" target="_blank" rel="noopener noreferrer">this guide</a> to get your own API key.', 'automatic-youtube-gallery' ), 'https://plugins360.com/automatic-youtube-gallery/how-to-get-youtube-api-key/' ) );
78 }
79
80 $this->api_key = $general_settings['api_key'];
81 $this->params = $params;
82
83 // Is development mode enabled?
84 if ( isset( $general_settings['development_mode'] ) && ! empty( $general_settings['development_mode'] ) ) {
85 $this->is_development_mode = true;
86 }
87
88 // Process output
89 $response = array();
90
91 if ( ! empty( $params['searchTerm'] ) ) {
92 $response = $this->get_videos_from_db( $params );
93 return $response;
94 }
95
96 switch ( $params['type'] ) {
97 case 'playlist':
98 if ( empty( $params['src'] ) ) {
99 return $this->get_error( __( 'YouTube Playlist ID (or) URL is required.', 'automatic-youtube-gallery' ) );
100 }
101
102 $response = $this->request_api_playlist_items( $params );
103 break;
104
105 case 'channel':
106 if ( empty( $params['src'] ) ) {
107 return $this->get_error( __( 'YouTube Channel ID (or) or a YouTube Video URL from the Channel is required.', 'automatic-youtube-gallery' ) );
108 }
109
110 $params['id'] = $this->get_channel_id( $params );
111
112 if ( empty( $params['id'] ) ) {
113 return $this->get_error( __( 'Invalid YouTube Channel ID.', 'automatic-youtube-gallery' ) );
114 }
115
116 // Get playlist id from the channel
117 $playlist_id = $this->get_playlist_id( $params );
118
119 if ( is_object( $playlist_id ) && isset( $playlist_id->error ) ) {
120 return $playlist_id;
121 }
122
123 if ( empty( $playlist_id ) ) {
124 return $this->get_error( __( 'No videos found matching your query.', 'automatic-youtube-gallery' ) );
125 }
126
127 // Get videos using the playlist id
128 $params['src'] = $playlist_id;
129 $response = $this->request_api_playlist_items( $params );
130 break;
131
132 case 'username':
133 if ( empty( $params['src'] ) ) {
134 return $this->get_error( __( 'YouTube Account Username is required.', 'automatic-youtube-gallery' ) );
135 }
136
137 // Get playlist id from the channel
138 $params['forUsername'] = $this->parse_youtube_id_from_url( $params['src'], 'username' );
139 $playlist_id = $this->get_playlist_id( $params );
140
141 if ( is_object( $playlist_id ) && isset( $playlist_id->error ) ) {
142 return $playlist_id;
143 }
144
145 if ( empty( $playlist_id ) ) {
146 return $this->get_error( __( 'No videos found matching your query.', 'automatic-youtube-gallery' ) );
147 }
148
149 // Get videos using the playlist id
150 $params['src'] = $playlist_id;
151 $response = $this->request_api_playlist_items( $params );
152 break;
153
154 case 'search':
155 if ( empty( $params['src'] ) ) {
156 return $this->get_error( __( 'Cannot search an empty string. A search keyword is required.', 'automatic-youtube-gallery' ) );
157 }
158
159 $response = $this->request_api_search( $params );
160 break;
161
162 case 'videos':
163 if ( empty( $params['src'] ) ) {
164 return $this->get_error( __( 'Atleast one YouTube Video ID (or) URL is required.', 'automatic-youtube-gallery' ) );
165 }
166
167 $response = $this->request_api_videos( $params );
168 break;
169
170 case 'livestream':
171 if ( empty( $params['src'] ) ) {
172 return $this->get_error( __( 'YouTube Channel ID (or) or a YouTube Video URL from the Channel is required.', 'automatic-youtube-gallery' ) );
173 }
174
175 $params['channelId'] = $this->get_channel_id( $params );
176
177 if ( empty( $params['channelId'] ) ) {
178 return $this->get_error( __( 'Invalid YouTube Channel ID.', 'automatic-youtube-gallery' ) );
179 }
180
181 // Get live video using the channel id
182 $response = $this->request_api_live_video( $params );
183 break;
184
185 default: // video
186 if ( empty( $params['src'] ) ) {
187 return $this->get_error( __( 'YouTube Video ID (or) URL is required.', 'automatic-youtube-gallery' ) );
188 }
189
190 $response = $this->request_api_video( $params );
191 break;
192 }
193
194 return $response;
195 }
196
197 /**
198 * Grab the playlist, channel or video ID using the YouTube URL given.
199 *
200 * @since 1.0.0
201 * @access private
202 * @param string $url YouTube URL.
203 * @param string $type Type of the URL (playlist|channel|video).
204 * @return mixed
205 */
206 private function parse_youtube_id_from_url( $url, $type = 'video' ) {
207 $url = trim( $url );
208 $id = $url;
209
210 switch ( $type ) {
211 case 'playlist':
212 if ( preg_match( '/[?&]list=([^&]+)/', $url, $matches ) ) {
213 $id = $matches[1];
214 }
215 break;
216
217 case 'channel':
218 if ( wp_http_validate_url( $id ) ) {
219 $id = '';
220 }
221
222 $url = parse_url( rtrim( $url, '/' ) );
223
224 if ( isset( $url['path'] ) && preg_match( '/^\/channel\/(([^\/])+?)$/', $url['path'], $matches ) ) {
225 $id = $matches[1];
226 }
227 break;
228
229 case 'username':
230 $url = parse_url( rtrim( $url, '/' ) );
231
232 if ( isset( $url['path'] ) && preg_match( '/^\/user\/(([^\/])+?)$/', $url['path'], $matches ) ) {
233 $id = $matches[1];
234 }
235 break;
236
237 default: // video
238 if ( wp_http_validate_url( $id ) ) {
239 $id = '';
240 }
241
242 $url = parse_url( $url );
243
244 if ( array_key_exists( 'host', $url ) ) {
245 if ( 0 === strcasecmp( $url['host'], 'youtu.be' ) ) {
246 $id = substr( $url['path'], 1 );
247 } elseif ( 0 === strcasecmp( $url['host'], 'www.youtube.com' ) || 0 === strcasecmp( $url['host'], 'youtube.com' ) ) {
248 if ( isset( $url['query'] ) ) {
249 parse_str( $url['query'], $url['query'] );
250
251 if ( isset( $url['query']['v'] ) ) {
252 $id = $url['query']['v'];
253 }
254 }
255
256 if ( empty( $id ) ) {
257 $url['path'] = explode( '/', substr( $url['path'], 1 ) );
258 if ( in_array( $url['path'][0], array( 'e', 'embed', 'v', 'shorts', 'live' ) ) ) {
259 $id = $url['path'][1];
260 }
261 }
262 }
263 }
264 }
265
266 return $id;
267 }
268
269 /**
270 * Get the channel ID.
271 *
272 * @since 2.0.0
273 * @access private
274 * @param array $params Array of query params.
275 * @return string
276 */
277 private function get_channel_id( $params = array() ) {
278 // Parse channel ID from URL: https://www.youtube.com/channel/XXXXXXXXXX
279 $id = $this->parse_youtube_id_from_url( $params['src'], 'channel' );
280
281 if ( empty( $id ) ) {
282 // Get channel ID from a Video URL: https://www.youtube.com/watch?v=XXXXXXXXXX
283 $video_id = $this->parse_youtube_id_from_url( $params['src'], 'video' );
284
285 // Request from cache
286 $channel_ids = get_option( 'ayg_channel_ids', array() );
287 if ( ! is_array( $channel_ids ) ) {
288 $channel_ids = (array) $channel_ids;
289 }
290
291 if ( isset( $channel_ids[ $video_id ] ) && ! empty( $channel_ids[ $video_id ] ) ) {
292 return $channel_ids[ $video_id ];
293 }
294
295 // Request from API
296 $api_url = $this->get_api_url( 'videos.list' );
297
298 $params['id'] = $video_id;
299
300 $api_params = $this->safe_merge_params(
301 array(
302 'id' => '',
303 'part' => 'id,snippet,contentDetails,status',
304 'cache' => 0
305 ),
306 $params
307 );
308
309 $api_response = $this->request_api( $api_url, $api_params, 'channel_id' );
310 if ( isset( $api_response->error ) ) {
311 return $id;
312 }
313
314 $videos = $this->parse_videos( $api_response );
315 if ( isset( $videos->error ) ) {
316 return $id;
317 }
318
319 // Process output
320 if ( $id = $videos[0]->channel_id ) {
321 // Store in cache
322 $channel_ids[ $video_id ] = $id;
323 update_option( 'ayg_channel_ids', $channel_ids );
324 }
325 }
326
327 return $id;
328 }
329
330 /**
331 * Get playlist id using channels API.
332 *
333 * @since 1.0.0
334 * @access private
335 * @param array $params Array of query params.
336 * @return mixed
337 */
338 private function get_playlist_id( $params = array() ) {
339 // Request from cache
340 $playlist_ids = get_option( 'ayg_playlist_ids', array() );
341 if ( ! is_array( $playlist_ids ) ) {
342 $playlist_ids = (array) $playlist_ids;
343 }
344
345 $key = '';
346
347 if ( isset( $params['forUsername'] ) && ! empty( $params['forUsername'] ) ) {
348 $key = $params['forUsername'];
349 }
350
351 if ( isset( $params['id'] ) && ! empty( $params['id'] ) ) {
352 unset( $params['forUsername'] );
353 $key = $params['id'];
354 }
355
356 if ( isset( $playlist_ids[ $key ] ) && ! empty( $playlist_ids[ $key ] ) ) {
357 return $playlist_ids[ $key ];
358 }
359
360 // Request from API
361 $api_url = $this->get_api_url( 'channels.list' );
362
363 $api_params = $this->safe_merge_params(
364 array(
365 'id' => '',
366 'forUsername' => '',
367 'part' => 'contentDetails',
368 'cache' => 0
369 ),
370 $params
371 );
372
373 $api_response = $this->request_api( $api_url, $api_params, 'playlist_id' );
374 if ( isset( $api_response->error ) ) {
375 return $api_response;
376 }
377
378 if ( ! isset( $api_response->items ) ) {
379 return false;
380 }
381
382 $items = $api_response->items;
383 if ( ! is_array( $items ) || count( $items ) == 0 ) {
384 return false;
385 }
386
387 // Process output
388 if ( $id = $items[0]->contentDetails->relatedPlaylists->uploads ) {
389 // Store in cache
390 $playlist_ids[ $key ] = $id;
391 update_option( 'ayg_playlist_ids', $playlist_ids );
392
393 // Return
394 return $id;
395 }
396
397 return false;
398 }
399
400 /**
401 * Get videos using playlistItems API.
402 *
403 * @since 1.0.0
404 * @access private
405 * @param array $params Array of query params.
406 * @return stdClass
407 */
408 private function request_api_playlist_items( $params = array() ) {
409 $api_url = $this->get_api_url( 'playlistItems.list' );
410
411 $params['playlistId'] = $this->parse_youtube_id_from_url( $params['src'], 'playlist' );
412
413 $api_params = $this->safe_merge_params(
414 array(
415 'playlistId' => '',
416 'part' => 'id,snippet,contentDetails,status',
417 'maxResults' => 50,
418 'pageToken' => '',
419 'cache' => 0
420 ),
421 $params
422 );
423
424 $api_response = $this->request_api( $api_url, $api_params );
425 if ( isset( $api_response->error ) ) {
426 return $api_response;
427 }
428
429 $videos = $this->parse_videos( $api_response );
430 if ( isset( $videos->error ) ) {
431 return $videos;
432 }
433
434 // Process output
435 $response = new stdClass();
436 $response->page_info = $this->parse_page_info( $api_response );
437 $response->videos = $videos;
438
439 return $response;
440 }
441
442 /**
443 * Get videos using search API.
444 *
445 * @since 1.0.0
446 * @access private
447 * @param array $params Array of query params.
448 * @return stdClass
449 */
450 private function request_api_search( $params = array() ) {
451 $api_url = $this->get_api_url( 'search.list' );
452
453 $params['q'] = $params['src'];
454
455 if ( ! empty( $params['q'] ) ) {
456 $params['q'] = str_replace( '|', '%7C', $params['q'] );
457 }
458
459 $params['type'] = 'video'; // Overrides user defined type value 'search'
460
461 $api_params = $this->safe_merge_params(
462 array(
463 'q' => '',
464 'channelId' => '',
465 'type' => 'video',
466 'videoEmbeddable' => true,
467 'part' => 'id,snippet',
468 'order' => 'date',
469 'maxResults' => 50,
470 'pageToken' => '',
471 'cache' => 0
472 ),
473 $params
474 );
475
476 $api_response = $this->request_api( $api_url, $api_params );
477 if ( isset( $api_response->error ) ) {
478 return $api_response;
479 }
480
481 $videos = $this->parse_videos( $api_response );
482 if ( isset( $videos->error ) ) {
483 return $videos;
484 }
485
486 // Process output
487 $response = new stdClass();
488 $response->page_info = $this->parse_page_info( $api_response );
489 $response->videos = $videos;
490
491 return $response;
492 }
493
494 /**
495 * Get live video using search API.
496 *
497 * @since 2.3.7
498 * @access private
499 * @param array $params Array of query params.
500 * @return mixed
501 */
502 private function request_api_live_video( $params = array() ) {
503 $api_url = $this->get_api_url( 'search.list' );
504
505 $params['type'] = 'video'; // Overrides user defined type value 'livestream'
506
507 $api_params = $this->safe_merge_params(
508 array(
509 'type' => 'video',
510 'eventType' => 'live',
511 'part' => 'snippet',
512 'channelId' => '',
513 'cache' => 0
514 ),
515 $params
516 );
517
518 $api_response = $this->request_api( $api_url, $api_params, 'live' );
519 if ( isset( $api_response->error ) ) {
520 return $api_response;
521 }
522
523 $videos = $this->parse_videos( $api_response );
524 if ( isset( $videos->error ) ) {
525 $livestream_settings = get_option( 'ayg_livestream_settings' );
526 return $this->get_error( '<div class="ayg-livestream-fallback-message">' . $livestream_settings['fallback_message'] . '</div>' );
527 }
528
529 // Process output
530 $response = new stdClass();
531 $response->videos = $videos;
532
533 return $response;
534 }
535
536 /**
537 * Get details of the given video ID.
538 *
539 * @since 1.0.0
540 * @access private
541 * @param array $params Array of query params.
542 * @return stdClass
543 */
544 private function request_api_video( $params = array() ) {
545 $api_url = $this->get_api_url( 'videos.list' );
546
547 $params['id'] = $this->parse_youtube_id_from_url( $params['src'], 'video' );
548
549 $api_params = $this->safe_merge_params(
550 array(
551 'id' => '',
552 'part' => 'id,snippet,contentDetails,status',
553 'cache' => 0
554 ),
555 $params
556 );
557
558 $api_response = $this->request_api( $api_url, $api_params );
559 if ( isset( $api_response->error ) ) {
560 return $api_response;
561 }
562
563 $videos = $this->parse_videos( $api_response );
564 if ( isset( $videos->error ) ) {
565 return $videos;
566 }
567
568 // Process output
569 $response = new stdClass();
570 $response->videos = $videos;
571
572 return $response;
573 }
574
575 /**
576 * Get details of the given video IDs.
577 *
578 * @since 1.0.0
579 * @access private
580 * @param array $params Array of query params.
581 * @return stdClass
582 */
583 private function request_api_videos( $params = array() ) {
584 $api_url = $this->get_api_url( 'videos.list' );
585
586 $urls = str_replace( "\n\r", ',', $params['src'] );
587 $urls = str_replace( ' ', ',', $urls );
588 $urls = explode( ',', $urls );
589 $urls = array_filter( $urls );
590
591 $all_ids = array();
592 foreach ( $urls as $url ) {
593 $all_ids[] = $this->parse_youtube_id_from_url( $url, 'video' );
594 }
595 $total_videos = count( $all_ids );
596 $total_pages = ceil( $total_videos / $params['maxResults'] );
597
598 $current_page = isset( $params['pageToken'] ) ? (int) $params['pageToken'] : 1;
599 $current_page = max( $current_page, 1 );
600 $current_page = min( $current_page, $total_pages );
601
602 $offset = max( 0, ( $current_page - 1 ) * $params['maxResults'] );
603
604 $current_ids = array_slice( $all_ids, $offset, $params['maxResults'] );
605 $params['id'] = implode( ',', $current_ids );
606
607 $api_params = $this->safe_merge_params(
608 array(
609 'id' => '',
610 'part' => 'id,snippet,contentDetails,status',
611 'cache' => 0
612 ),
613 $params
614 );
615
616 $api_response = $this->request_api( $api_url, $api_params );
617 if ( isset( $api_response->error ) ) {
618 return $api_response;
619 }
620
621 $videos = $this->parse_videos( $api_response );
622 if ( isset( $videos->error ) ) {
623 return $videos;
624 }
625
626 // Process output
627 $response = new stdClass();
628 $response->videos = $videos;
629
630 $response->page_info = array(
631 'videos_found' => $total_videos,
632 'total_pages' => $total_pages,
633 'paged' => $current_page
634 );
635
636 if ( $current_page > 1 ) {
637 $response->page_info['prev_page_token'] = $current_page - 1;
638 }
639
640 if ( $current_page < $total_pages ) {
641 $response->page_info['next_page_token'] = $current_page + 1;
642 }
643
644 return $response;
645 }
646
647 /**
648 * Get videos from our custom database table "{$wpdb->prefix}ayg_videos".
649 *
650 * @since 2.5.7
651 * @access private
652 * @param array $params Array of query params.
653 * @return stdClass
654 */
655 private function get_videos_from_db( $params = array() ) {
656 global $wpdb;
657
658 $videos_table = $wpdb->prefix . 'ayg_videos';
659 $galleries_table = $wpdb->prefix . 'ayg_galleries';
660
661 $search_term = '%' . $wpdb->esc_like( $params['searchTerm'] ) . '%';
662 $gallery_id = $params['uid'];
663
664 // Get Total Videos Count
665 $total_query = $wpdb->prepare(
666 "SELECT COUNT(*)
667 FROM $videos_table AS v
668 INNER JOIN $galleries_table AS g ON v.id = g.video_id
669 WHERE g.gallery_id = %s
670 AND (v.title LIKE %s OR v.description LIKE %s)",
671 $gallery_id, $search_term, $search_term
672 );
673
674 $total_videos = $wpdb->get_var( $total_query );
675
676 if ( empty( $total_videos ) ) {
677 return $this->get_error( __( 'No videos found matching your query.', 'automatic-youtube-gallery' ) );
678 }
679
680 // Fetch Paginated Videos
681 $limit = $params['maxResults'];
682
683 $total_pages = ceil( $total_videos / $limit );
684
685 $current_page = isset( $params['pageToken'] ) ? (int) $params['pageToken'] : 1;
686 $current_page = max( $current_page, 1 );
687 $current_page = min( $current_page, $total_pages );
688
689 $offset = max( 0, ( $current_page - 1 ) * $limit );
690
691 $query = $wpdb->prepare(
692 "SELECT v.*
693 FROM $videos_table AS v
694 INNER JOIN $galleries_table AS g ON v.id = g.video_id
695 WHERE g.gallery_id = %s
696 AND (v.title LIKE %s OR v.description LIKE %s)
697 ORDER BY v.published_at_datetime DESC
698 LIMIT %d OFFSET %d",
699 $gallery_id, $search_term, $search_term, $limit, $offset
700 );
701
702 $videos = $wpdb->get_results( $query );
703
704 if ( empty( $videos ) ) {
705 return $this->get_error( __( 'No videos found matching your query.', 'automatic-youtube-gallery' ) );
706 }
707
708 foreach ( $videos as $index => $video ) {
709 if ( ! empty( $video->thumbnails ) ) {
710 $videos[ $index ]->thumbnails = maybe_unserialize( $video->thumbnails );
711 }
712 }
713
714 // Process output
715 $response = new stdClass();
716 $response->videos = $videos;
717
718 $response->page_info = array(
719 'videos_found' => $total_videos,
720 'total_pages' => $total_pages,
721 'paged' => $current_page
722 );
723
724 if ( $current_page > 1 ) {
725 $response->page_info['prev_page_token'] = $current_page - 1;
726 }
727
728 if ( $current_page < $total_pages ) {
729 $response->page_info['next_page_token'] = $current_page + 1;
730 }
731
732 return $response;
733 }
734
735 /**
736 * Get API URL by request.
737 *
738 * @since 1.0.0
739 * @access private
740 * @param array $name
741 * @return string
742 */
743 private function get_api_url( $name ) {
744 return $this->api_urls[ $name ];
745 }
746
747 /**
748 * Request data from the API server.
749 *
750 * @since 1.0.0
751 * @access private
752 * @param string $url YouTube API URL.
753 * @param array $params Array of query params.
754 * @param string $context "channel_id", "playlist_id", "videos", or "live"
755 * @return mixed
756 */
757 private function request_api( $url, $params, $context = 'videos' ) {
758 $params['key'] = $this->api_key;
759
760 // Build API URL
761 $cache_duration = 0;
762 if ( isset( $params['cache'] ) ) {
763 $cache_duration = (int) $params['cache'];
764 unset( $params['cache'] );
765 }
766 $cache_duration = min( $cache_duration, 2419200 ); // Max cache duration: 1 Month
767
768 $q = '';
769 if ( isset( $params['q'] ) ) {
770 $q = $params['q'];
771 unset( $params['q'] );
772 }
773
774 $api_url = $url . ( strpos( $url, '?' ) === false ? '?' : '&' ) . http_build_query( $params );
775 if ( ! empty( $q ) ) {
776 $api_url .= '&q=' . $q;
777 }
778
779 // Request from cache
780 if ( ! $this->is_development_mode && $cache_duration > 0 ) {
781 $cache_key = 'ayg_' . md5( $api_url );
782 $cache_data = get_transient( $cache_key );
783
784 if ( ! empty( $cache_data ) ) {
785 return $cache_data;
786 }
787 }
788
789 // Request from API
790 $timeout = apply_filters( 'ayg_api_request_timeout', 15 );
791
792 $request = wp_remote_get( $api_url, array(
793 'headers' => [ 'referer' => home_url() ],
794 'timeout' => $timeout, // Increase timeout if needed
795 ) );
796
797 if ( is_wp_error( $request ) ) {
798 return $this->get_error( $request->get_error_message() );
799 }
800
801 $body = wp_remote_retrieve_body( $request );
802 $data = json_decode( $body );
803
804 if ( empty( $data ) ) {
805 return $this->get_error( __( 'Empty or invalid API response', 'automatic-youtube-gallery' ) );
806 }
807
808 if ( isset( $data->error ) ) {
809 $message = "Error " . $data->error->code . " " . $data->error->message;
810
811 if ( isset( $data->error->errors[0] ) ) {
812 $message .= " : " . $data->error->errors[0]->reason;
813 }
814
815 return $this->get_error( $message );
816 }
817
818 // Store in cache (transients)
819 $cache_enabled = false;
820
821 if ( ! $this->is_development_mode && $cache_duration > 0 ) {
822 if ( 'videos' === $context ) {
823 if ( ! empty( $data->items ) && is_array( $data->items ) ) {
824 $cache_enabled = true;
825 }
826 }
827
828 if ( 'live' === $context ) {
829 $cache_enabled = true;
830 }
831 }
832
833 if ( $cache_enabled ) {
834 set_transient( $cache_key, $data, $cache_duration );
835
836 // Get the current list of transients
837 $cache_keys = get_option( 'ayg_transient_keys', array() );
838 if ( ! is_array( $cache_keys ) ) {
839 $cache_keys = (array) $cache_keys;
840 }
841
842 // Append our new one
843 if ( ! in_array( $cache_key, $cache_keys ) ) {
844 $cache_keys[] = $cache_key;
845 }
846
847 // Save it to the DB
848 update_option( 'ayg_transient_keys', $cache_keys );
849 }
850
851 // Store videos in our custom database table "{$wpdb->prefix}ayg_videos"
852 ayg_db_store_videos( $data, $this->params );
853
854 // Finally return the data
855 return $data;
856 }
857
858 /**
859 * Parse videos from the YouTube API response object.
860 *
861 * @since 1.0.0
862 * @access private
863 * @param object $data YouTube API response object.
864 * @return mixed
865 */
866 private function parse_videos( $data ) {
867 if ( empty( $data->items ) || ! is_array( $data->items ) ) {
868 return $this->get_error( __( 'No videos found matching your query.', 'automatic-youtube-gallery' ) );
869 }
870
871 $items = $data->items;
872 $videos = array();
873
874 foreach ( $items as $item ) {
875 $video = new stdClass();
876
877 // Video ID
878 $video->id = '';
879
880 if ( isset( $item->snippet->resourceId ) && isset( $item->snippet->resourceId->videoId ) ) {
881 $video->id = $item->snippet->resourceId->videoId;
882 } elseif ( isset( $item->contentDetails ) && isset( $item->contentDetails->videoId ) ) {
883 $video->id = $item->contentDetails->videoId;
884 } elseif ( isset( $item->id ) && isset( $item->id->videoId ) ) {
885 $video->id = $item->id->videoId;
886 } elseif ( isset( $item->id ) ) {
887 $video->id = $item->id;
888 }
889
890 // Video channel ID
891 $video->channel_id = '';
892
893 if ( isset( $item->snippet->channelId ) ) {
894 $video->channel_id = $item->snippet->channelId;
895 }
896
897 // Video title
898 $video->title = $item->snippet->title;
899
900 // Video description
901 $video->description = $item->snippet->description;
902
903 // Video thumbnails
904 if ( isset( $item->snippet->thumbnails ) ) {
905 $video->thumbnails = $item->snippet->thumbnails;
906 }
907
908 // Video publish date
909 $video->published_at = $item->snippet->publishedAt;
910
911 // Push resulting object to the main array
912 $status = 'private';
913
914 if ( isset( $item->status ) && ( 'public' == $item->status->privacyStatus || 'unlisted' == $item->status->privacyStatus ) ) {
915 $status = 'public';
916 }
917
918 if ( isset( $item->snippet->status ) && ( 'public' == $item->snippet->status->privacyStatus || 'unlisted' == $item->snippet->status->privacyStatus ) ) {
919 $status = 'public';
920 }
921
922 if ( 'youtube#searchResult' == $item->kind ) {
923 $status = 'public';
924 }
925
926 if ( 'public' == $status ) {
927 $videos[] = $video;
928 }
929 }
930
931 if ( 0 == count( $videos ) ) {
932 return $this->get_error( __( 'No videos found matching your query.', 'automatic-youtube-gallery' ) );
933 }
934
935 return $videos;
936 }
937
938 /**
939 * Parse page info from the YouTube API response object.
940 *
941 * @since 1.0.0
942 * @access private
943 * @param object $data YouTube API response object.
944 * @return array
945 */
946 private function parse_page_info( $data ) {
947 $page_info = array(
948 'videos_found' => 0
949 );
950
951 // Total number of videos found
952 if ( isset( $data->pageInfo ) && isset( $data->pageInfo->totalResults ) ) {
953 $page_info['videos_found'] = (int) $data->pageInfo->totalResults;
954 }
955
956 // Calculate total number of pages
957 if ( $page_info['videos_found'] > 0 ) {
958 if ( 'search' == $this->params['type'] ) {
959 $limit = min( (int) $this->params['limit'], $page_info['videos_found'] );
960 $page_info['total_pages'] = ceil( $limit / (int) $this->params['maxResults'] );
961 } else {
962 $page_info['total_pages'] = ceil( $page_info['videos_found'] / (int) $this->params['maxResults'] );
963 }
964 }
965
966 // Token for the previous page
967 if ( isset( $data->prevPageToken ) ) {
968 $page_info['prev_page_token'] = $data->prevPageToken;
969 }
970
971 // Token for the next page
972 if ( isset( $data->nextPageToken ) ) {
973 $page_info['next_page_token'] = $data->nextPageToken;
974 }
975
976 return $page_info;
977 }
978
979 /**
980 * Combine user params with known params and fill in defaults when needed.
981 *
982 * @since 1.0.0
983 * @access private
984 * @param array $pairs Entire list of supported params and their defaults.
985 * @param array $params User defined params.
986 * @return array $out Combined and filtered params array.
987 */
988 private function safe_merge_params( $pairs, $params ) {
989 $params = (array) $params;
990 $out = array();
991
992 foreach ( $pairs as $name => $default ) {
993 if ( array_key_exists( $name, $params ) ) {
994 $out[ $name ] = $params[ $name ];
995 } else {
996 $out[ $name ] = $default;
997 }
998
999 if ( empty( $out[ $name ] ) ) {
1000 unset( $out[ $name ] );
1001 }
1002 }
1003
1004 return $out;
1005 }
1006
1007 /**
1008 * Build error object.
1009 *
1010 * @since 1.0.0
1011 * @access private
1012 * @param string $message Error message.
1013 * @return object Error object.
1014 */
1015 private function get_error( $message ) {
1016 $obj = new stdClass();
1017 $obj->error = 1;
1018 $obj->error_message = $message;
1019
1020 return $obj;
1021 }
1022
1023 }
1024