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 +458 -142 1.6.52.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 );
@@ -127,17 +167,24 @@
127 167 $response = $this->request_api_videos( $params );
128 168 break;
129 169
130 170 case 'livestream':
131 - if ( empty( $params['id'] ) ) {
132 - return $this->get_error( __( 'YouTube Channel ID (or) URL is required.', 'automatic-youtube-gallery' ) );
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' ) );
133 173 }
134 174
135 - $response = $this->request_api_livestream( $params );
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 );
136 183 break;
137 184
138 185 default: // video
139 - if ( empty( $params['id'] ) ) {
186 + if ( empty( $params['src'] ) ) {
140 187 return $this->get_error( __( 'YouTube Video ID (or) URL is required.', 'automatic-youtube-gallery' ) );
141 188 }
142 189
143 190 $response = $this->request_api_video( $params );
@@ -156,18 +203,23 @@
156 203 * @param string $type Type of the URL (playlist|channel|video).
157 204 * @return mixed
158 205 */
159 206 private function parse_youtube_id_from_url( $url, $type = 'video' ) {
160 - $id = $url;
207 + $url = trim( $url );
208 + $id = $url;
161 209
162 210 switch ( $type ) {
163 211 case 'playlist':
164 - if ( preg_match( '/list=(.*)&?\/?/', $url, $matches ) ) {
212 + if ( preg_match( '/[?&]list=([^&]+)/', $url, $matches ) ) {
165 213 $id = $matches[1];
166 214 }
167 215 break;
168 216
169 217 case 'channel':
218 + if ( wp_http_validate_url( $id ) ) {
219 + $id = '';
220 + }
221 +
170 222 $url = parse_url( rtrim( $url, '/' ) );
171 223
172 224 if ( isset( $url['path'] ) && preg_match( '/^\/channel\/(([^\/])+?)$/', $url['path'], $matches ) ) {
173 225 $id = $matches[1];
@@ -182,14 +234,18 @@
182 234 }
183 235 break;
184 236
185 237 default: // video
238 + if ( wp_http_validate_url( $id ) ) {
239 + $id = '';
240 + }
241 +
186 242 $url = parse_url( $url );
187 243
188 244 if ( array_key_exists( 'host', $url ) ) {
189 245 if ( 0 === strcasecmp( $url['host'], 'youtu.be' ) ) {
190 246 $id = substr( $url['path'], 1 );
191 - } elseif ( 0 === strcasecmp( $url['host'], 'www.youtube.com' ) ) {
247 + } elseif ( 0 === strcasecmp( $url['host'], 'www.youtube.com' ) || 0 === strcasecmp( $url['host'], 'youtube.com' ) ) {
192 248 if ( isset( $url['query'] ) ) {
193 249 parse_str( $url['query'], $url['query'] );
194 250
195 251 if ( isset( $url['query']['v'] ) ) {
@@ -198,10 +254,9 @@
198 254 }
199 255
200 256 if ( empty( $id ) ) {
201 257 $url['path'] = explode( '/', substr( $url['path'], 1 ) );
202 -
203 - if ( in_array( $url['path'][0], array( 'e', 'embed', 'v' ) ) ) {
258 + if ( in_array( $url['path'][0], array( 'e', 'embed', 'v', 'shorts', 'live' ) ) ) {
204 259 $id = $url['path'][1];
205 260 }
206 261 }
207 262 }
@@ -211,51 +266,70 @@
211 266 return $id;
212 267 }
213 268
214 269 /**
215 - * Get videos using playlistItems API.
270 + * Get the channel ID.
216 271 *
217 - * @since 1.0.0
272 + * @since 2.0.0
218 273 * @access private
219 - * @param array $params Array of query params.
220 - * @return stdClass
274 + * @param array $params Array of query params.
275 + * @return string
221 276 */
222 - private function request_api_playlist_items( $params = array() ) {
223 - $api_url = $this->get_api_url( 'playlistItems.list' );
224 -
225 - $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' );
226 280
227 - $api_params = $this->safe_merge_params(
228 - array(
229 - 'playlistId' => '',
230 - 'part' => 'id,snippet,contentDetails,status',
231 - 'maxResults' => 50,
232 - 'pageToken' => '',
233 - 'cache' => 0
234 - ),
235 - $params
236 - );
237 -
238 - $api_response = $this->request_api( $api_url, $api_params );
239 - if ( isset( $api_response->error ) ) {
240 - return $api_response;
241 - }
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' );
242 284
243 - $videos = $this->parse_videos( $api_response );
244 - if ( isset( $videos->error ) ) {
245 - 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 + }
246 325 }
247 326
248 - // Process result
249 - $response = new stdClass();
250 - $response->page_info = $this->parse_page_info( $api_response );
251 - $response->videos = $videos;
252 -
253 - return $response;
327 + return $id;
254 328 }
255 329
256 330 /**
257 - * Find playlistId using channels API.
331 + * Get playlist id using channels API.
258 332 *
259 333 * @since 1.0.0
260 334 * @access private
261 335 * @param array $params Array of query params.
@@ -260,9 +334,31 @@
260 334 * @access private
261 335 * @param array $params Array of query params.
262 336 * @return mixed
263 337 */
264 - 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
265 361 $api_url = $this->get_api_url( 'channels.list' );
266 362
267 363 $api_params = $this->safe_merge_params(
268 364 array(
@@ -273,24 +369,76 @@
273 369 ),
274 370 $params
275 371 );
276 372
277 - $api_response = $this->request_api( $api_url, $api_params );
373 + $api_response = $this->request_api( $api_url, $api_params, 'playlist_id' );
278 374 if ( isset( $api_response->error ) ) {
279 375 return $api_response;
280 376 }
281 377
378 + if ( ! isset( $api_response->items ) ) {
379 + return false;
380 + }
381 +
282 382 $items = $api_response->items;
283 383 if ( ! is_array( $items ) || count( $items ) == 0 ) {
284 - return $this->get_error( __( 'No videos found matching your query.', 'automatic-youtube-gallery' ) );
384 + return false;
285 385 }
286 386
287 - // 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
288 435 $response = new stdClass();
289 - $response->playlistId = $items[0]->contentDetails->relatedPlaylists->uploads;
436 + $response->page_info = $this->parse_page_info( $api_response );
437 + $response->videos = $videos;
290 438
291 - return $response;
292 - }
439 + return $response;
440 + }
293 441
294 442 /**
295 443 * Get videos using search API.
296 444 *
@@ -301,9 +449,9 @@
301 449 */
302 450 private function request_api_search( $params = array() ) {
303 451 $api_url = $this->get_api_url( 'search.list' );
304 452
305 - $params['q'] = $params['id'];
453 + $params['q'] = $params['src'];
306 454
307 455 if ( ! empty( $params['q'] ) ) {
308 456 $params['q'] = str_replace( '|', '%7C', $params['q'] );
309 457 }
@@ -334,9 +482,9 @@
334 482 if ( isset( $videos->error ) ) {
335 483 return $videos;
336 484 }
337 485
338 - // Process result
486 + // Process output
339 487 $response = new stdClass();
340 488 $response->page_info = $this->parse_page_info( $api_response );
341 489 $response->videos = $videos;
342 490
@@ -343,8 +491,50 @@
343 491 return $response;
344 492 }
345 493
346 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 + /**
347 537 * Get details of the given video ID.
348 538 *
349 539 * @since 1.0.0
350 540 * @access private
@@ -353,9 +543,9 @@
353 543 */
354 544 private function request_api_video( $params = array() ) {
355 545 $api_url = $this->get_api_url( 'videos.list' );
356 546
357 - $params['id'] = $this->parse_youtube_id_from_url( $params['id'], 'video' );
547 + $params['id'] = $this->parse_youtube_id_from_url( $params['src'], 'video' );
358 548
359 549 $api_params = $this->safe_merge_params(
360 550 array(
361 551 'id' => '',
@@ -370,32 +560,20 @@
370 560 return $api_response;
371 561 }
372 562
373 563 $videos = $this->parse_videos( $api_response );
564 + if ( isset( $videos->error ) ) {
565 + return $videos;
566 + }
374 567
375 - // Process result
568 + // Process output
376 569 $response = new stdClass();
377 570 $response->videos = $videos;
378 571
379 572 return $response;
380 - }
573 + }
381 574
382 575 /**
383 - * Get the livestream data.
384 - *
385 - * @since 1.6.4
386 - * @access private
387 - * @param array $params Array of query params.
388 - * @return stdClass
389 - */
390 - private function request_api_livestream( $params = array() ) {
391 - $response = new stdClass();
392 - $response->id = $this->parse_youtube_id_from_url( $params['id'], 'channel' );
393 -
394 - return $response;
395 - }
396 -
397 - /**
398 576 * Get details of the given video IDs.
399 577 *
400 578 * @since 1.0.0
401 579 * @access private
@@ -404,10 +582,10 @@
404 582 */
405 583 private function request_api_videos( $params = array() ) {
406 584 $api_url = $this->get_api_url( 'videos.list' );
407 585
408 - $urls = str_replace( "\n\r", ',', $params['id'] );
409 - $urls = str_replace( ' ', ',', $params['id'] );
586 + $urls = str_replace( "\n\r", ',', $params['src'] );
587 + $urls = str_replace( ' ', ',', $urls );
410 588 $urls = explode( ',', $urls );
411 589 $urls = array_filter( $urls );
412 590
413 591 $all_ids = array();
@@ -420,12 +598,9 @@
420 598 $current_page = isset( $params['pageToken'] ) ? (int) $params['pageToken'] : 1;
421 599 $current_page = max( $current_page, 1 );
422 600 $current_page = min( $current_page, $total_pages );
423 601
424 - $offset = ( $current_page - 1 ) * $params['maxResults'];
425 - if ( $offset < 0 ) {
426 - $offset = 0;
427 - }
602 + $offset = max( 0, ( $current_page - 1 ) * $params['maxResults'] );
428 603
429 604 $current_ids = array_slice( $all_ids, $offset, $params['maxResults'] );
430 605 $params['id'] = implode( ',', $current_ids );
431 606
@@ -443,15 +618,20 @@
443 618 return $api_response;
444 619 }
445 620
446 621 $videos = $this->parse_videos( $api_response );
622 + if ( isset( $videos->error ) ) {
623 + return $videos;
624 + }
447 625
448 - // Process result
626 + // Process output
449 627 $response = new stdClass();
450 628 $response->videos = $videos;
451 629
452 630 $response->page_info = array(
453 - 'videos_found' => $total_videos
631 + 'videos_found' => $total_videos,
632 + 'total_pages' => $total_pages,
633 + 'paged' => $current_page
454 634 );
455 635
456 636 if ( $current_page > 1 ) {
457 637 $response->page_info['prev_page_token'] = $current_page - 1;
@@ -462,8 +642,96 @@
462 642 }
463 643
464 644 return $response;
465 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 + }
466 734
467 735 /**
468 736 * Get API URL by request.
469 737 *
@@ -480,32 +748,24 @@
480 748 * Request data from the API server.
481 749 *
482 750 * @since 1.0.0
483 751 * @access private
484 - * @param string $url YouTube API URL.
485 - * @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"
486 755 * @return mixed
487 756 */
488 - private function request_api( $url, $params ) {
757 + private function request_api( $url, $params, $context = 'videos' ) {
489 758 $params['key'] = $this->api_key;
490 -
491 - // Request data from transients
492 - $transient_url = $url . ( strpos( $url, '?' ) === false ? '?' : '' ) . http_build_query( $params );
493 - $transient_key = 'ayg_' . md5( $transient_url );
494 - $transient_data = get_transient( $transient_key );
495 -
496 - if ( ! empty( $transient_data ) ) {
497 - return $transient_data;
498 - }
499 -
500 - $transient_expiration = 0;
501 759
760 + // Build API URL
761 + $cache_duration = 0;
502 762 if ( isset( $params['cache'] ) ) {
503 - $transient_expiration = (int) $params['cache'];
763 + $cache_duration = (int) $params['cache'];
504 764 unset( $params['cache'] );
505 765 }
766 + $cache_duration = min( $cache_duration, 2419200 ); // Max cache duration: 1 Month
506 767
507 - // Request data from API server
508 768 $q = '';
509 769 if ( isset( $params['q'] ) ) {
510 770 $q = $params['q'];
511 771 unset( $params['q'] );
@@ -510,15 +770,29 @@
510 770 $q = $params['q'];
511 771 unset( $params['q'] );
512 772 }
513 773
514 - $url = $url . ( strpos( $url, '?' ) === false ? '?' : '' ) . http_build_query( $params );
774 + $api_url = $url . ( strpos( $url, '?' ) === false ? '?' : '&' ) . http_build_query( $params );
515 775 if ( ! empty( $q ) ) {
516 - $url .= '&q=' . $q;
776 + $api_url .= '&q=' . $q;
517 777 }
518 778
519 - $request = wp_remote_get( $url, array(
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(
520 793 'headers' => [ 'referer' => home_url() ],
794 + 'timeout' => $timeout, // Increase timeout if needed
521 795 ) );
522 796
523 797 if ( is_wp_error( $request ) ) {
524 798 return $this->get_error( $request->get_error_message() );
@@ -524,10 +798,13 @@
524 798 return $this->get_error( $request->get_error_message() );
525 799 }
526 800
527 801 $body = wp_remote_retrieve_body( $request );
802 + $data = json_decode( $body );
528 803
529 - $data = json_decode( $body );
804 + if ( empty( $data ) ) {
805 + return $this->get_error( __( 'Empty or invalid API response', 'automatic-youtube-gallery' ) );
806 + }
530 807
531 808 if ( isset( $data->error ) ) {
532 809 $message = "Error " . $data->error->code . " " . $data->error->message;
533 810
@@ -537,27 +814,44 @@
537 814
538 815 return $this->get_error( $message );
539 816 }
540 817
541 - // Store data in transients
542 - if ( $transient_expiration > 0 ) {
543 - set_transient( $transient_key, $data, $transient_expiration );
818 + // Store in cache (transients)
819 + $cache_enabled = false;
544 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 +
545 836 // Get the current list of transients
546 - $transient_keys = get_option( 'ayg_transient_keys', array() );
547 - if ( ! is_array( $transient_keys ) ) {
548 - $transient_keys = (array) $transient_keys;
837 + $cache_keys = get_option( 'ayg_transient_keys', array() );
838 + if ( ! is_array( $cache_keys ) ) {
839 + $cache_keys = (array) $cache_keys;
549 840 }
550 841
551 842 // Append our new one
552 - if ( ! in_array( $transient_key, $transient_keys ) ) {
553 - $transient_keys[] = $transient_key;
843 + if ( ! in_array( $cache_key, $cache_keys ) ) {
844 + $cache_keys[] = $cache_key;
554 845 }
555 846
556 847 // Save it to the DB
557 - update_option( 'ayg_transient_keys', $transient_keys );
848 + update_option( 'ayg_transient_keys', $cache_keys );
558 849 }
559 850
851 + // Store videos in our custom database table "{$wpdb->prefix}ayg_videos"
852 + ayg_db_store_videos( $data, $this->params );
853 +
560 854 // Finally return the data
561 855 return $data;
562 856 }
563 857
@@ -569,14 +863,13 @@
569 863 * @param object $data YouTube API response object.
570 864 * @return mixed
571 865 */
572 866 private function parse_videos( $data ) {
573 - $items = $data->items;
574 -
575 - if ( ! is_array( $items ) || 0 == count( $items ) ) {
867 + if ( empty( $data->items ) || ! is_array( $data->items ) ) {
576 868 return $this->get_error( __( 'No videos found matching your query.', 'automatic-youtube-gallery' ) );
577 869 }
578 870
871 + $items = $data->items;
579 872 $videos = array();
580 873
581 874 foreach ( $items as $item ) {
582 875 $video = new stdClass();
@@ -591,10 +884,17 @@
591 884 } elseif ( isset( $item->id ) && isset( $item->id->videoId ) ) {
592 885 $video->id = $item->id->videoId;
593 886 } elseif ( isset( $item->id ) ) {
594 887 $video->id = $item->id;
595 - }
888 + }
596 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 +
597 897 // Video title
598 898 $video->title = $item->snippet->title;
599 899
600 900 // Video description
@@ -627,8 +927,12 @@
627 927 $videos[] = $video;
628 928 }
629 929 }
630 930
931 + if ( 0 == count( $videos ) ) {
932 + return $this->get_error( __( 'No videos found matching your query.', 'automatic-youtube-gallery' ) );
933 + }
934 +
631 935 return $videos;
632 936 }
633 937
634 938 /**
@@ -639,14 +943,26 @@
639 943 * @param object $data YouTube API response object.
640 944 * @return array
641 945 */
642 946 private function parse_page_info( $data ) {
643 - $page_info = array();
947 + $page_info = array(
948 + 'videos_found' => 0
949 + );
644 950
645 - // Total count of videos found
951 + // Total number of videos found
646 952 if ( isset( $data->pageInfo ) && isset( $data->pageInfo->totalResults ) ) {
647 - $page_info['videos_found'] = $data->pageInfo->totalResults;
648 - }
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 + }
649 965
650 966 // Token for the previous page
651 967 if ( isset( $data->prevPageToken ) ) {
652 968 $page_info['prev_page_token'] = $data->prevPageToken;