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
← All changes | includes/youtube-api.php +502 -137 1.6.22.6.4 View file →
@@ -30,8 +30,26 @@
30 30 */
31 31 protected $api_key;
32 32
33 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 + /**
34 52 * The YouTube API URLs.
35 53 *
36 54 * @since 1.0.0
37 55 * @access protected
@@ -47,27 +65,38 @@
47 65 /**
48 66 * Get videos.
49 67 *
50 68 * @since 1.0.0
51 - * @param array $params Array of query params.
69 + * @param array $params Array of query params.
52 70 * @return mixed
53 71 */
54 - public function get_videos( $params = array() ) {
72 + public function query( $params = array() ) {
55 73 // Get YouTube API Key
56 74 $general_settings = get_option( 'ayg_general_settings' );
57 75
58 76 if ( empty( $general_settings['api_key'] ) ) {
59 - return $this->get_error( __( 'YouTube API Key Missing.', 'automatic-youtube-gallery' ) . ' ' . sprintf( __( 'Follow <a href="%s" target="_blank">this guide</a> to get your own API key.', 'automatic-youtube-gallery' ), 'https://plugins360.com/automatic-youtube-gallery/how-to-get-youtube-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/' ) );
60 78 }
61 79
62 80 $this->api_key = $general_settings['api_key'];
81 + $this->params = $params;
63 82
64 - // Process response
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
65 89 $response = array();
66 90
91 + if ( ! empty( $params['searchTerm'] ) ) {
92 + $response = $this->get_videos_from_db( $params );
93 + return $response;
94 + }
95 +
67 96 switch ( $params['type'] ) {
68 97 case 'playlist':
69 - if ( empty( $params['id'] ) ) {
98 + if ( empty( $params['src'] ) ) {
70 99 return $this->get_error( __( 'YouTube Playlist ID (or) URL is required.', 'automatic-youtube-gallery' ) );
71 100 }
72 101
73 102 $response = $this->request_api_playlist_items( $params );
@@ -73,48 +102,59 @@
73 102 $response = $this->request_api_playlist_items( $params );
74 103 break;
75 104
76 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 +
77 112 if ( empty( $params['id'] ) ) {
78 - return $this->get_error( __( 'YouTube Channel ID (or) URL is required.', 'automatic-youtube-gallery' ) );
113 + return $this->get_error( __( 'Invalid YouTube Channel ID.', 'automatic-youtube-gallery' ) );
79 114 }
80 115
81 - // Find playlistId from the channel
82 - $params['id'] = $this->parse_youtube_id_from_url( $params['id'], 'channel' );
83 - $response = $this->request_api_channels( $params );
116 + // Get playlist id from the channel
117 + $playlist_id = $this->get_playlist_id( $params );
84 118
85 - // Get videos using the playlistId
86 - if ( ! isset( $response->error ) ) {
119 + if ( is_object( $playlist_id ) && isset( $playlist_id->error ) ) {
120 + return $playlist_id;
121 + }
87 122
88 - $params['id'] = $response->playlistId;
89 - $response = $this->request_api_playlist_items( $params );
123 + if ( empty( $playlist_id ) ) {
124 + return $this->get_error( __( 'No videos found matching your query.', 'automatic-youtube-gallery' ) );
125 + }
90 126
91 - }
127 + // Get videos using the playlist id
128 + $params['src'] = $playlist_id;
129 + $response = $this->request_api_playlist_items( $params );
92 130 break;
93 131
94 132 case 'username':
95 - if ( empty( $params['id'] ) ) {
133 + if ( empty( $params['src'] ) ) {
96 134 return $this->get_error( __( 'YouTube Account Username is required.', 'automatic-youtube-gallery' ) );
97 135 }
98 136
99 - // Find playlistId from the channel
100 - $params['forUsername'] = $this->parse_youtube_id_from_url( $params['id'], 'username' );
101 - unset( $params['id'] );
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 );
102 140
103 - $response = $this->request_api_channels( $params );
141 + if ( is_object( $playlist_id ) && isset( $playlist_id->error ) ) {
142 + return $playlist_id;
143 + }
104 144
105 - // Get videos using the playlistId
106 - if ( ! isset( $response->error ) ) {
145 + if ( empty( $playlist_id ) ) {
146 + return $this->get_error( __( 'No videos found matching your query.', 'automatic-youtube-gallery' ) );
147 + }
107 148
108 - $params['id'] = $response->playlistId;
109 - $response = $this->request_api_playlist_items( $params );
110 -
111 - }
149 + // Get videos using the playlist id
150 + $params['src'] = $playlist_id;
151 + $response = $this->request_api_playlist_items( $params );
112 152 break;
113 153
114 154 case 'search':
115 - if ( empty( $params['id'] ) ) {
116 - return $this->get_error( __( 'Cannot search an empty string. A search term is required.', 'automatic-youtube-gallery' ) );
155 + if ( empty( $params['src'] ) ) {
156 + return $this->get_error( __( 'Cannot search an empty string. A search keyword is required.', 'automatic-youtube-gallery' ) );
117 157 }
118 158
119 159 $response = $this->request_api_search( $params );
120 160 break;
@@ -119,9 +159,9 @@
119 159 $response = $this->request_api_search( $params );
120 160 break;
121 161
122 162 case 'videos':
123 - if ( empty( $params['id'] ) ) {
163 + if ( empty( $params['src'] ) ) {
124 164 return $this->get_error( __( 'Atleast one YouTube Video ID (or) URL is required.', 'automatic-youtube-gallery' ) );
125 165 }
126 166
127 167 $response = $this->request_api_videos( $params );
@@ -126,10 +166,25 @@
126 166
127 167 $response = $this->request_api_videos( $params );
128 168 break;
129 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 +
130 185 default: // video
131 - if ( empty( $params['id'] ) ) {
186 + if ( empty( $params['src'] ) ) {
132 187 return $this->get_error( __( 'YouTube Video ID (or) URL is required.', 'automatic-youtube-gallery' ) );
133 188 }
134 189
135 190 $response = $this->request_api_video( $params );
@@ -148,18 +203,23 @@
148 203 * @param string $type Type of the URL (playlist|channel|video).
149 204 * @return mixed
150 205 */
151 206 private function parse_youtube_id_from_url( $url, $type = 'video' ) {
152 - $id = $url;
207 + $url = trim( $url );
208 + $id = $url;
153 209
154 210 switch ( $type ) {
155 211 case 'playlist':
156 - if ( preg_match( '/list=(.*)&?\/?/', $url, $matches ) ) {
212 + if ( preg_match( '/[?&]list=([^&]+)/', $url, $matches ) ) {
157 213 $id = $matches[1];
158 214 }
159 215 break;
160 216
161 217 case 'channel':
218 + if ( wp_http_validate_url( $id ) ) {
219 + $id = '';
220 + }
221 +
162 222 $url = parse_url( rtrim( $url, '/' ) );
163 223
164 224 if ( isset( $url['path'] ) && preg_match( '/^\/channel\/(([^\/])+?)$/', $url['path'], $matches ) ) {
165 225 $id = $matches[1];
@@ -174,14 +234,18 @@
174 234 }
175 235 break;
176 236
177 237 default: // video
238 + if ( wp_http_validate_url( $id ) ) {
239 + $id = '';
240 + }
241 +
178 242 $url = parse_url( $url );
179 243
180 244 if ( array_key_exists( 'host', $url ) ) {
181 245 if ( 0 === strcasecmp( $url['host'], 'youtu.be' ) ) {
182 246 $id = substr( $url['path'], 1 );
183 - } elseif ( 0 === strcasecmp( $url['host'], 'www.youtube.com' ) ) {
247 + } elseif ( 0 === strcasecmp( $url['host'], 'www.youtube.com' ) || 0 === strcasecmp( $url['host'], 'youtube.com' ) ) {
184 248 if ( isset( $url['query'] ) ) {
185 249 parse_str( $url['query'], $url['query'] );
186 250
187 251 if ( isset( $url['query']['v'] ) ) {
@@ -190,10 +254,9 @@
190 254 }
191 255
192 256 if ( empty( $id ) ) {
193 257 $url['path'] = explode( '/', substr( $url['path'], 1 ) );
194 -
195 - if ( in_array( $url['path'][0], array( 'e', 'embed', 'v' ) ) ) {
258 + if ( in_array( $url['path'][0], array( 'e', 'embed', 'v', 'shorts', 'live' ) ) ) {
196 259 $id = $url['path'][1];
197 260 }
198 261 }
199 262 }
@@ -203,51 +266,70 @@
203 266 return $id;
204 267 }
205 268
206 269 /**
207 - * Get videos using playlistItems API.
270 + * Get the channel ID.
208 271 *
209 - * @since 1.0.0
272 + * @since 2.0.0
210 273 * @access private
211 - * @param array $params Array of query params.
212 - * @return stdClass
274 + * @param array $params Array of query params.
275 + * @return string
213 276 */
214 - private function request_api_playlist_items( $params = array() ) {
215 - $api_url = $this->get_api_url( 'playlistItems.list' );
216 -
217 - $params['playlistId'] = $this->parse_youtube_id_from_url( $params['id'], 'playlist' );
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' );
218 280
219 - $api_params = $this->safe_merge_params(
220 - array(
221 - 'playlistId' => '',
222 - 'part' => 'id,snippet,contentDetails,status',
223 - 'maxResults' => 50,
224 - 'pageToken' => '',
225 - 'cache' => 0
226 - ),
227 - $params
228 - );
229 -
230 - $api_response = $this->request_api( $api_url, $api_params );
231 - if ( isset( $api_response->error ) ) {
232 - return $api_response;
233 - }
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' );
234 284
235 - $videos = $this->parse_videos( $api_response );
236 - if ( isset( $videos->error ) ) {
237 - return $videos;
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 + }
238 325 }
239 326
240 - // Process result
241 - $response = new stdClass();
242 - $response->page_info = $this->parse_page_info( $api_response );
243 - $response->videos = $videos;
244 -
245 - return $response;
327 + return $id;
246 328 }
247 329
248 330 /**
249 - * Find playlistId using channels API.
331 + * Get playlist id using channels API.
250 332 *
251 333 * @since 1.0.0
252 334 * @access private
253 335 * @param array $params Array of query params.
@@ -252,9 +334,31 @@
252 334 * @access private
253 335 * @param array $params Array of query params.
254 336 * @return mixed
255 337 */
256 - private function request_api_channels( $params = array() ) {
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
257 361 $api_url = $this->get_api_url( 'channels.list' );
258 362
259 363 $api_params = $this->safe_merge_params(
260 364 array(
@@ -265,24 +369,76 @@
265 369 ),
266 370 $params
267 371 );
268 372
269 - $api_response = $this->request_api( $api_url, $api_params );
373 + $api_response = $this->request_api( $api_url, $api_params, 'playlist_id' );
270 374 if ( isset( $api_response->error ) ) {
271 375 return $api_response;
272 376 }
273 377
378 + if ( ! isset( $api_response->items ) ) {
379 + return false;
380 + }
381 +
274 382 $items = $api_response->items;
275 383 if ( ! is_array( $items ) || count( $items ) == 0 ) {
276 - return $this->get_error( __( 'No videos found matching your query.', 'automatic-youtube-gallery' ) );
384 + return false;
277 385 }
278 386
279 - // Process result
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
280 435 $response = new stdClass();
281 - $response->playlistId = $items[0]->contentDetails->relatedPlaylists->uploads;
436 + $response->page_info = $this->parse_page_info( $api_response );
437 + $response->videos = $videos;
282 438
283 - return $response;
284 - }
439 + return $response;
440 + }
285 441
286 442 /**
287 443 * Get videos using search API.
288 444 *
@@ -293,9 +449,9 @@
293 449 */
294 450 private function request_api_search( $params = array() ) {
295 451 $api_url = $this->get_api_url( 'search.list' );
296 452
297 - $params['q'] = $params['id'];
453 + $params['q'] = $params['src'];
298 454
299 455 if ( ! empty( $params['q'] ) ) {
300 456 $params['q'] = str_replace( '|', '%7C', $params['q'] );
301 457 }
@@ -326,17 +482,98 @@
326 482 if ( isset( $videos->error ) ) {
327 483 return $videos;
328 484 }
329 485
330 - // Process result
486 + // Process output
331 487 $response = new stdClass();
332 488 $response->page_info = $this->parse_page_info( $api_response );
333 489 $response->videos = $videos;
334 490
335 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;
336 534 }
337 535
338 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 + /**
339 576 * Get details of the given video IDs.
340 577 *
341 578 * @since 1.0.0
342 579 * @access private
@@ -345,10 +582,10 @@
345 582 */
346 583 private function request_api_videos( $params = array() ) {
347 584 $api_url = $this->get_api_url( 'videos.list' );
348 585
349 - $urls = str_replace( "\n\r", ',', $params['id'] );
350 - $urls = str_replace( ' ', ',', $params['id'] );
586 + $urls = str_replace( "\n\r", ',', $params['src'] );
587 + $urls = str_replace( ' ', ',', $urls );
351 588 $urls = explode( ',', $urls );
352 589 $urls = array_filter( $urls );
353 590
354 591 $all_ids = array();
@@ -361,12 +598,9 @@
361 598 $current_page = isset( $params['pageToken'] ) ? (int) $params['pageToken'] : 1;
362 599 $current_page = max( $current_page, 1 );
363 600 $current_page = min( $current_page, $total_pages );
364 601
365 - $offset = ( $current_page - 1 ) * $params['maxResults'];
366 - if ( $offset < 0 ) {
367 - $offset = 0;
368 - }
602 + $offset = max( 0, ( $current_page - 1 ) * $params['maxResults'] );
369 603
370 604 $current_ids = array_slice( $all_ids, $offset, $params['maxResults'] );
371 605 $params['id'] = implode( ',', $current_ids );
372 606
@@ -372,9 +606,9 @@
372 606
373 607 $api_params = $this->safe_merge_params(
374 608 array(
375 609 'id' => '',
376 - 'part' => 'id,snippet,contentDetails,statistics,status',
610 + 'part' => 'id,snippet,contentDetails,status',
377 611 'cache' => 0
378 612 ),
379 613 $params
380 614 );
@@ -384,15 +618,20 @@
384 618 return $api_response;
385 619 }
386 620
387 621 $videos = $this->parse_videos( $api_response );
622 + if ( isset( $videos->error ) ) {
623 + return $videos;
624 + }
388 625
389 - // Process result
626 + // Process output
390 627 $response = new stdClass();
391 628 $response->videos = $videos;
392 629
393 630 $response->page_info = array(
394 - 'videos_found' => $total_videos
631 + 'videos_found' => $total_videos,
632 + 'total_pages' => $total_pages,
633 + 'paged' => $current_page
395 634 );
396 635
397 636 if ( $current_page > 1 ) {
398 637 $response->page_info['prev_page_token'] = $current_page - 1;
@@ -405,40 +644,92 @@
405 644 return $response;
406 645 }
407 646
408 647 /**
409 - * Get details of the given video ID.
648 + * Get videos from our custom database table "{$wpdb->prefix}ayg_videos".
410 649 *
411 - * @since 1.0.0
650 + * @since 2.5.7
412 651 * @access private
413 652 * @param array $params Array of query params.
414 653 * @return stdClass
415 654 */
416 - private function request_api_video( $params = array() ) {
417 - $api_url = $this->get_api_url( 'videos.list' );
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 + );
418 673
419 - $params['id'] = $this->parse_youtube_id_from_url( $params['id'], 'video' );
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'];
420 682
421 - $api_params = $this->safe_merge_params(
422 - array(
423 - 'id' => '',
424 - 'part' => 'id,snippet,contentDetails,statistics,status',
425 - 'cache' => 0
426 - ),
427 - $params
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
428 700 );
701 +
702 + $videos = $wpdb->get_results( $query );
429 703
430 - $api_response = $this->request_api( $api_url, $api_params );
431 - if ( isset( $api_response->error ) ) {
432 - return $api_response;
704 + if ( empty( $videos ) ) {
705 + return $this->get_error( __( 'No videos found matching your query.', 'automatic-youtube-gallery' ) );
433 706 }
434 707
435 - $videos = $this->parse_videos( $api_response );
708 + foreach ( $videos as $index => $video ) {
709 + if ( ! empty( $video->thumbnails ) ) {
710 + $videos[ $index ]->thumbnails = maybe_unserialize( $video->thumbnails );
711 + }
712 + }
436 713
437 - // Process result
714 + // Process output
438 715 $response = new stdClass();
439 716 $response->videos = $videos;
440 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 +
441 732 return $response;
442 733 }
443 734
444 735 /**
@@ -457,36 +748,51 @@
457 748 * Request data from the API server.
458 749 *
459 750 * @since 1.0.0
460 751 * @access private
461 - * @param string $url YouTube API URL.
462 - * @param array $params Array of query params.
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"
463 755 * @return mixed
464 756 */
465 - private function request_api( $url, $params ) {
757 + private function request_api( $url, $params, $context = 'videos' ) {
466 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
467 767
468 - // Request data from transients
469 - $transient_url = $url . ( strpos( $url, '?' ) === false ? '?' : '' ) . http_build_query( $params );
470 - $transient_key = 'ayg_' . md5( $transient_url );
471 - $transient_data = get_transient( $transient_key );
768 + $q = '';
769 + if ( isset( $params['q'] ) ) {
770 + $q = $params['q'];
771 + unset( $params['q'] );
772 + }
472 773
473 - if ( ! empty( $transient_data ) ) {
474 - return $transient_data;
774 + $api_url = $url . ( strpos( $url, '?' ) === false ? '?' : '&' ) . http_build_query( $params );
775 + if ( ! empty( $q ) ) {
776 + $api_url .= '&q=' . $q;
475 777 }
476 778
477 - $transient_expiration = 0;
478 -
479 - if ( isset( $params['cache'] ) ) {
480 - $transient_expiration = (int) $params['cache'];
481 - unset( $params['cache'] );
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 + }
482 787 }
483 788
484 - // Request data from API server
485 - $url = $url . ( strpos( $url, '?' ) === false ? '?' : '' ) . http_build_query( $params );
486 -
487 - $request = wp_remote_get( $url, array(
789 + // Request from API
790 + $timeout = apply_filters( 'ayg_api_request_timeout', 15 );
791 +
792 + $request = wp_remote_get( $api_url, array(
488 793 'headers' => [ 'referer' => home_url() ],
794 + 'timeout' => $timeout, // Increase timeout if needed
489 795 ) );
490 796
491 797 if ( is_wp_error( $request ) ) {
492 798 return $this->get_error( $request->get_error_message() );
@@ -492,10 +798,13 @@
492 798 return $this->get_error( $request->get_error_message() );
493 799 }
494 800
495 801 $body = wp_remote_retrieve_body( $request );
802 + $data = json_decode( $body );
496 803
497 - $data = json_decode( $body );
804 + if ( empty( $data ) ) {
805 + return $this->get_error( __( 'Empty or invalid API response', 'automatic-youtube-gallery' ) );
806 + }
498 807
499 808 if ( isset( $data->error ) ) {
500 809 $message = "Error " . $data->error->code . " " . $data->error->message;
501 810
@@ -505,24 +814,44 @@
505 814
506 815 return $this->get_error( $message );
507 816 }
508 817
509 - // Store data in transients
510 - if ( $transient_expiration > 0 ) {
511 - set_transient( $transient_key, $data, $transient_expiration );
818 + // Store in cache (transients)
819 + $cache_enabled = false;
512 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 +
513 836 // Get the current list of transients
514 - $transient_keys = get_option( 'ayg_transient_keys', array() );
837 + $cache_keys = get_option( 'ayg_transient_keys', array() );
838 + if ( ! is_array( $cache_keys ) ) {
839 + $cache_keys = (array) $cache_keys;
840 + }
515 841
516 842 // Append our new one
517 - if ( ! in_array( $transient_key, $transient_keys ) ) {
518 - $transient_keys[] = $transient_key;
843 + if ( ! in_array( $cache_key, $cache_keys ) ) {
844 + $cache_keys[] = $cache_key;
519 845 }
520 846
521 847 // Save it to the DB
522 - update_option( 'ayg_transient_keys', $transient_keys );
848 + update_option( 'ayg_transient_keys', $cache_keys );
523 849 }
524 850
851 + // Store videos in our custom database table "{$wpdb->prefix}ayg_videos"
852 + ayg_db_store_videos( $data, $this->params );
853 +
525 854 // Finally return the data
526 855 return $data;
527 856 }
528 857
@@ -534,14 +863,13 @@
534 863 * @param object $data YouTube API response object.
535 864 * @return mixed
536 865 */
537 866 private function parse_videos( $data ) {
538 - $items = $data->items;
539 -
540 - if ( ! is_array( $items ) || 0 == count( $items ) ) {
867 + if ( empty( $data->items ) || ! is_array( $data->items ) ) {
541 868 return $this->get_error( __( 'No videos found matching your query.', 'automatic-youtube-gallery' ) );
542 869 }
543 870
871 + $items = $data->items;
544 872 $videos = array();
545 873
546 874 foreach ( $items as $item ) {
547 875 $video = new stdClass();
@@ -556,10 +884,17 @@
556 884 } elseif ( isset( $item->id ) && isset( $item->id->videoId ) ) {
557 885 $video->id = $item->id->videoId;
558 886 } elseif ( isset( $item->id ) ) {
559 887 $video->id = $item->id;
560 - }
888 + }
561 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 +
562 897 // Video title
563 898 $video->title = $item->snippet->title;
564 899
565 900 // Video description
@@ -573,13 +908,31 @@
573 908 // Video publish date
574 909 $video->published_at = $item->snippet->publishedAt;
575 910
576 911 // Push resulting object to the main array
577 - if ( ( isset( $item->status ) && 'public' == $item->status->privacyStatus ) || ( isset( $item->snippet->status ) && 'public' == $item->snippet->status->privacyStatus ) || 'youtube#searchResult' == $item->kind ) {
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 ) {
578 927 $videos[] = $video;
579 928 }
580 929 }
581 930
931 + if ( 0 == count( $videos ) ) {
932 + return $this->get_error( __( 'No videos found matching your query.', 'automatic-youtube-gallery' ) );
933 + }
934 +
582 935 return $videos;
583 936 }
584 937
585 938 /**
@@ -590,14 +943,26 @@
590 943 * @param object $data YouTube API response object.
591 944 * @return array
592 945 */
593 946 private function parse_page_info( $data ) {
594 - $page_info = array();
947 + $page_info = array(
948 + 'videos_found' => 0
949 + );
595 950
596 - // Total count of videos found
951 + // Total number of videos found
597 952 if ( isset( $data->pageInfo ) && isset( $data->pageInfo->totalResults ) ) {
598 - $page_info['videos_found'] = $data->pageInfo->totalResults;
599 - }
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 + }
600 965
601 966 // Token for the previous page
602 967 if ( isset( $data->prevPageToken ) ) {
603 968 $page_info['prev_page_token'] = $data->prevPageToken;