PluginProbe
Automatic YouTube Gallery – Embed Auto-Updating YouTube Video Galleries, Feeds, Playlists & Channels / 2.6.3
Automatic YouTube Gallery – Embed Auto-Updating YouTube Video Galleries, Feeds, Playlists & Channels v2.6.3
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 +93 -297 trunk2.6.3 View file →
@@ -39,21 +39,8 @@
39 39 */
40 40 protected $params = array();
41 41
42 42 /**
43 - * May this query write gallery membership rows?
44 - *
45 - * Opt-in, set from the "store" query param. Only trusted server side callers set it — the
46 - * page renderer (ayg_build_gallery), the Gallery Builder importer, and the public AJAX
47 - * endpoint once it has resolved the gallery itself. See request_api().
48 - *
49 - * @since 2.9.0
50 - * @access protected
51 - * @var bool
52 - */
53 - protected $can_store = false;
54 -
55 - /**
56 43 * Is development mode enabled?
57 44 *
58 45 * @since 2.3.0
59 46 * @access protected
@@ -76,18 +63,9 @@
76 63 );
77 64
78 65 /**
79 66 * Get videos.
80 - *
81 - * Side effect: successful responses are persisted to the custom tables via
82 - * ayg_db_store_videos() (called inside request_api()) — video rows always, and
83 - * gallery relationships only when the caller passes both a "uid" and "store" => true.
84 - * This is how both legacy galleries and the Gallery Builder importer store their videos.
85 - *
86 - * "store" must never be set from user input: it decides which gallery the fetched videos
87 - * are shown in. Callers reachable by unauthenticated visitors have to resolve the gallery
88 - * server side first — see AYG_Public::ajax_callback_load_videos().
89 - *
67 + *
90 68 * @since 1.0.0
91 69 * @param array $params Array of query params.
92 70 * @return mixed
93 71 */
@@ -92,23 +70,16 @@
92 70 * @return mixed
93 71 */
94 72 public function query( $params = array() ) {
95 73 // Get YouTube API Key
96 - $general_settings = ayg_get_option( 'ayg_general_settings' );
74 + $general_settings = get_option( 'ayg_general_settings' );
97 75
98 - // DB-served responses (gallery search + the internal "db" source type) read from the
99 - // custom tables, so they're handled before the API-key guard — no key required.
100 - if ( ! empty( $params['searchTerm'] ) || ( isset( $params['type'] ) && 'db' === $params['type'] ) ) {
101 - return $this->get_videos_from_db( $params );
102 - }
103 -
104 76 if ( empty( $general_settings['api_key'] ) ) {
105 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/' ) );
106 78 }
107 79
108 - $this->api_key = $general_settings['api_key'];
109 - $this->params = $params;
110 - $this->can_store = ! empty( $params['store'] );
80 + $this->api_key = $general_settings['api_key'];
81 + $this->params = $params;
111 82
112 83 // Is development mode enabled?
113 84 if ( isset( $general_settings['development_mode'] ) && ! empty( $general_settings['development_mode'] ) ) {
114 85 $this->is_development_mode = true;
@@ -113,20 +84,20 @@
113 84 if ( isset( $general_settings['development_mode'] ) && ! empty( $general_settings['development_mode'] ) ) {
114 85 $this->is_development_mode = true;
115 86 }
116 87
117 - // Advanced mode fetches duration + live broadcast details via a supplementary
118 - // videos.list call (used by the Gallery Builder importer). Defaults to off so
119 - // legacy galleries make no extra API request.
120 - $mode = isset( $params['mode'] ) ? $params['mode'] : 'basic';
121 -
122 88 // Process output
123 89 $response = array();
124 90
91 + if ( ! empty( $params['searchTerm'] ) ) {
92 + $response = $this->get_videos_from_db( $params );
93 + return $response;
94 + }
95 +
125 96 switch ( $params['type'] ) {
126 97 case 'playlist':
127 98 if ( empty( $params['src'] ) ) {
128 - return $this->get_error( __( 'A YouTube playlist ID (or) URL is required.', 'automatic-youtube-gallery' ) );
99 + return $this->get_error( __( 'YouTube Playlist ID (or) URL is required.', 'automatic-youtube-gallery' ) );
129 100 }
130 101
131 102 $response = $this->request_api_playlist_items( $params );
132 103 break;
@@ -132,21 +103,15 @@
132 103 break;
133 104
134 105 case 'channel':
135 106 if ( empty( $params['src'] ) ) {
136 - return $this->get_error( __( 'A YouTube channel ID (or) a video URL from the channel is required.', 'automatic-youtube-gallery' ) );
107 + return $this->get_error( __( 'YouTube Channel ID (or) or a YouTube Video URL from the Channel is required.', 'automatic-youtube-gallery' ) );
137 108 }
138 109
139 - // @handle URLs can't be resolved to a channel ID here (mirrors the client-side
140 - // check in admin.js / gallery-form.php).
141 - if ( false !== strpos( $params['src'], '@' ) ) {
142 - return $this->get_error( __( 'YouTube @handle URLs aren’t supported here. Please enter a channel ID, a /channel/ URL, or a video URL from the channel.', 'automatic-youtube-gallery' ) );
143 - }
144 -
145 110 $params['id'] = $this->get_channel_id( $params );
146 111
147 112 if ( empty( $params['id'] ) ) {
148 - return $this->get_error( __( 'Invalid YouTube channel ID.', 'automatic-youtube-gallery' ) );
113 + return $this->get_error( __( 'Invalid YouTube Channel ID.', 'automatic-youtube-gallery' ) );
149 114 }
150 115
151 116 // Get playlist id from the channel
152 117 $playlist_id = $this->get_playlist_id( $params );
@@ -165,9 +130,9 @@
165 130 break;
166 131
167 132 case 'username':
168 133 if ( empty( $params['src'] ) ) {
169 - return $this->get_error( __( 'A YouTube account username is required.', 'automatic-youtube-gallery' ) );
134 + return $this->get_error( __( 'YouTube Account Username is required.', 'automatic-youtube-gallery' ) );
170 135 }
171 136
172 137 // Get playlist id from the channel
173 138 $params['forUsername'] = $this->parse_youtube_id_from_url( $params['src'], 'username' );
@@ -187,9 +152,9 @@
187 152 break;
188 153
189 154 case 'search':
190 155 if ( empty( $params['src'] ) ) {
191 - return $this->get_error( __( 'A search keyword is required.', 'automatic-youtube-gallery' ) );
156 + return $this->get_error( __( 'Cannot search an empty string. A search keyword is required.', 'automatic-youtube-gallery' ) );
192 157 }
193 158
194 159 $response = $this->request_api_search( $params );
195 160 break;
@@ -195,9 +160,9 @@
195 160 break;
196 161
197 162 case 'videos':
198 163 if ( empty( $params['src'] ) ) {
199 - return $this->get_error( __( 'At least one YouTube video ID (or) URL is required.', 'automatic-youtube-gallery' ) );
164 + return $this->get_error( __( 'Atleast one YouTube Video ID (or) URL is required.', 'automatic-youtube-gallery' ) );
200 165 }
201 166
202 167 $response = $this->request_api_videos( $params );
203 168 break;
@@ -203,21 +168,15 @@
203 168 break;
204 169
205 170 case 'livestream':
206 171 if ( empty( $params['src'] ) ) {
207 - return $this->get_error( __( 'A YouTube channel ID (or) a video URL from the channel is required.', 'automatic-youtube-gallery' ) );
172 + return $this->get_error( __( 'YouTube Channel ID (or) or a YouTube Video URL from the Channel is required.', 'automatic-youtube-gallery' ) );
208 173 }
209 174
210 - // @handle URLs can't be resolved to a channel ID here (mirrors the client-side
211 - // check in admin.js / gallery-form.php).
212 - if ( false !== strpos( $params['src'], '@' ) ) {
213 - return $this->get_error( __( 'YouTube @handle URLs aren’t supported here. Please enter a channel ID, a /channel/ URL, or a video URL from the channel.', 'automatic-youtube-gallery' ) );
214 - }
215 -
216 175 $params['channelId'] = $this->get_channel_id( $params );
217 176
218 177 if ( empty( $params['channelId'] ) ) {
219 - return $this->get_error( __( 'Invalid YouTube channel ID.', 'automatic-youtube-gallery' ) );
178 + return $this->get_error( __( 'Invalid YouTube Channel ID.', 'automatic-youtube-gallery' ) );
220 179 }
221 180
222 181 // Get live video using the channel id
223 182 $response = $this->request_api_live_video( $params );
@@ -224,9 +183,9 @@
224 183 break;
225 184
226 185 default: // video
227 186 if ( empty( $params['src'] ) ) {
228 - return $this->get_error( __( 'A YouTube video ID (or) URL is required.', 'automatic-youtube-gallery' ) );
187 + return $this->get_error( __( 'YouTube Video ID (or) URL is required.', 'automatic-youtube-gallery' ) );
229 188 }
230 189
231 190 $response = $this->request_api_video( $params );
232 191 break;
@@ -231,14 +190,8 @@
231 190 $response = $this->request_api_video( $params );
232 191 break;
233 192 }
234 193
235 - // Advanced mode: enrich channel/playlist/username/search videos (which come from
236 - // playlistItems.list / search.list and lack duration + live broadcast details).
237 - if ( 'advanced' === $mode && ! isset( $response->error ) && ! empty( $response->videos ) && in_array( $params['type'], array( 'playlist', 'channel', 'username', 'search' ), true ) ) {
238 - $response->videos = $this->enrich_video_details( $response->videos );
239 - }
240 -
241 194 return $response;
242 195 }
243 196
244 197 /**
@@ -329,9 +282,12 @@
329 282 // Get channel ID from a Video URL: https://www.youtube.com/watch?v=XXXXXXXXXX
330 283 $video_id = $this->parse_youtube_id_from_url( $params['src'], 'video' );
331 284
332 285 // Request from cache
333 - $channel_ids = ayg_get_option( 'ayg_channel_ids' );
286 + $channel_ids = get_option( 'ayg_channel_ids', array() );
287 + if ( ! is_array( $channel_ids ) ) {
288 + $channel_ids = (array) $channel_ids;
289 + }
334 290
335 291 if ( isset( $channel_ids[ $video_id ] ) && ! empty( $channel_ids[ $video_id ] ) ) {
336 292 return $channel_ids[ $video_id ];
337 293 }
@@ -363,9 +319,9 @@
363 319 // Process output
364 320 if ( $id = $videos[0]->channel_id ) {
365 321 // Store in cache
366 322 $channel_ids[ $video_id ] = $id;
367 - update_option( 'ayg_channel_ids', $channel_ids, false );
323 + update_option( 'ayg_channel_ids', $channel_ids );
368 324 }
369 325 }
370 326
371 327 return $id;
@@ -380,9 +336,12 @@
380 336 * @return mixed
381 337 */
382 338 private function get_playlist_id( $params = array() ) {
383 339 // Request from cache
384 - $playlist_ids = ayg_get_option( 'ayg_playlist_ids' );
340 + $playlist_ids = get_option( 'ayg_playlist_ids', array() );
341 + if ( ! is_array( $playlist_ids ) ) {
342 + $playlist_ids = (array) $playlist_ids;
343 + }
385 344
386 345 $key = '';
387 346
388 347 if ( isset( $params['forUsername'] ) && ! empty( $params['forUsername'] ) ) {
@@ -428,9 +387,9 @@
428 387 // Process output
429 388 if ( $id = $items[0]->contentDetails->relatedPlaylists->uploads ) {
430 389 // Store in cache
431 390 $playlist_ids[ $key ] = $id;
432 - update_option( 'ayg_playlist_ids', $playlist_ids, false );
391 + update_option( 'ayg_playlist_ids', $playlist_ids );
433 392
434 393 // Return
435 394 return $id;
436 395 }
@@ -490,13 +449,14 @@
490 449 */
491 450 private function request_api_search( $params = array() ) {
492 451 $api_url = $this->get_api_url( 'search.list' );
493 452
494 - // Passed through unmodified: request_api() now runs every parameter through
495 - // http_build_query(), which encodes the OR operator "|" to %7C on its own. Pre-encoding it
496 - // here would be double encoded into %257C and break the search.
497 - $params['q'] = $params['src'];
453 + $params['q'] = $params['src'];
498 454
455 + if ( ! empty( $params['q'] ) ) {
456 + $params['q'] = str_replace( '|', '%7C', $params['q'] );
457 + }
458 +
499 459 $params['type'] = 'video'; // Overrides user defined type value 'search'
500 460
501 461 $api_params = $this->safe_merge_params(
502 462 array(
@@ -505,15 +465,14 @@
505 465 'type' => 'video',
506 466 'videoEmbeddable' => true,
507 467 'part' => 'id,snippet',
508 468 'order' => 'date',
509 - 'publishedAfter' => '', // Set by incremental sync to fetch only newly published videos
510 469 'maxResults' => 50,
511 470 'pageToken' => '',
512 471 'cache' => 0
513 472 ),
514 473 $params
515 - );
474 + );
516 475
517 476 $api_response = $this->request_api( $api_url, $api_params );
518 477 if ( isset( $api_response->error ) ) {
519 478 return $api_response;
@@ -562,9 +521,9 @@
562 521 }
563 522
564 523 $videos = $this->parse_videos( $api_response );
565 524 if ( isset( $videos->error ) ) {
566 - $livestream_settings = ayg_get_option( 'ayg_livestream_settings' );
525 + $livestream_settings = get_option( 'ayg_livestream_settings' );
567 526 return $this->get_error( '<div class="ayg-livestream-fallback-message">' . $livestream_settings['fallback_message'] . '</div>' );
568 527 }
569 528
570 529 // Process output
@@ -623,14 +582,12 @@
623 582 */
624 583 private function request_api_videos( $params = array() ) {
625 584 $api_url = $this->get_api_url( 'videos.list' );
626 585
627 - // Accept the video list separated by commas, spaces, or newlines (one per line) in any
628 - // combination and line-ending style. The old "\n\r" replace looked for LF+CR (reversed),
629 - // so a one-per-line list never split — it collapsed into a single invalid ID and returned
630 - // "No videos found". Split on any run of whitespace or commas and drop empties instead.
631 - $urls = preg_split( '/[\s,]+/', trim( (string) $params['src'] ), -1, PREG_SPLIT_NO_EMPTY );
632 - $urls = is_array( $urls ) ? $urls : array();
586 + $urls = str_replace( "\n\r", ',', $params['src'] );
587 + $urls = str_replace( ' ', ',', $urls );
588 + $urls = explode( ',', $urls );
589 + $urls = array_filter( $urls );
633 590
634 591 $all_ids = array();
635 592 foreach ( $urls as $url ) {
636 593 $all_ids[] = $this->parse_youtube_id_from_url( $url, 'video' );
@@ -697,45 +654,25 @@
697 654 */
698 655 private function get_videos_from_db( $params = array() ) {
699 656 global $wpdb;
700 657
701 - $videos_table = $wpdb->prefix . 'ayg_videos';
702 - $rel_table = $wpdb->prefix . 'ayg_gallery_relationships';
658 + $videos_table = $wpdb->prefix . 'ayg_videos';
659 + $galleries_table = $wpdb->prefix . 'ayg_galleries';
703 660
704 - $gallery_id = $params['uid'];
661 + $search_term = '%' . $wpdb->esc_like( $params['searchTerm'] ) . '%';
662 + $gallery_id = $params['uid'];
705 663
706 - // Base query: every video linked to this gallery. An optional search term (the search
707 - // form) narrows it by title/description; the "db" source type passes none and gets all.
708 - $where = 'r.gallery_id = %s';
709 - $values = array( $gallery_id );
710 -
711 - if ( ! empty( $params['searchTerm'] ) ) {
712 - $search_term = '%' . $wpdb->esc_like( $params['searchTerm'] ) . '%';
713 -
714 - $where .= ' AND (v.title LIKE %s OR v.description LIKE %s)';
715 - $values[] = $search_term;
716 - $values[] = $search_term;
717 - }
718 -
719 - // Optional duration filter. Whitelisted operator; value parameterized. Affects count + select.
720 - $duration_filter = isset( $params['duration_filter'] ) ? $params['duration_filter'] : '';
721 - $duration = isset( $params['duration'] ) ? (int) $params['duration'] : 0;
722 -
723 - if ( $duration > 0 && in_array( $duration_filter, array( 'long', 'short' ), true ) ) {
724 - $where .= ( 'long' === $duration_filter ) ? ' AND v.duration_seconds > %d' : ' AND v.duration_seconds < %d';
725 - $values[] = $duration;
726 - }
727 -
728 664 // Get Total Videos Count
729 - $total_videos = $wpdb->get_var(
730 - $wpdb->prepare(
731 - "SELECT COUNT(*)
732 - FROM $videos_table AS v
733 - INNER JOIN $rel_table AS r ON v.video_id = r.video_id
734 - WHERE $where",
735 - $values
736 - )
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
737 672 );
673 +
674 + $total_videos = $wpdb->get_var( $total_query );
738 675
739 676 if ( empty( $total_videos ) ) {
740 677 return $this->get_error( __( 'No videos found matching your query.', 'automatic-youtube-gallery' ) );
741 678 }
@@ -740,84 +677,29 @@
740 677 return $this->get_error( __( 'No videos found matching your query.', 'automatic-youtube-gallery' ) );
741 678 }
742 679
743 680 // Fetch Paginated Videos
744 - $limit = (int) $params['maxResults'];
681 + $limit = $params['maxResults'];
682 +
683 + $total_pages = ceil( $total_videos / $limit );
745 684
746 - if ( $limit <= 0 ) {
747 - // 0 = show all videos on a single page (Gallery Builder "unlimited" per page).
748 - // A LIMIT of the total count avoids the empty result set that LIMIT 0 would return.
749 - $limit = (int) $total_videos;
750 - $total_pages = 1;
751 - $current_page = 1;
752 - $offset = 0;
753 - } else {
754 - $total_pages = ceil( $total_videos / $limit );
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 );
755 688
756 - $current_page = isset( $params['pageToken'] ) ? (int) $params['pageToken'] : 1;
757 - $current_page = max( $current_page, 1 );
758 - $current_page = min( $current_page, $total_pages );
689 + $offset = max( 0, ( $current_page - 1 ) * $limit );
759 690
760 - $offset = max( 0, ( $current_page - 1 ) * $limit );
761 - }
762 -
763 - // Display-time ordering. Whitelisted (ORDER BY can't be parameterized); v.id breaks ties.
764 - $orderby_map = array(
765 - 'date' => 'v.published_at_datetime',
766 - 'title' => 'v.title',
767 - 'duration' => 'v.duration_seconds'
768 - );
769 -
770 - $sort_by_raw = isset( $params['sort_by'] ) ? $params['sort_by'] : 'date';
771 - $sort_order = ( isset( $params['sort_order'] ) && 'asc' === strtolower( $params['sort_order'] ) ) ? 'ASC' : 'DESC';
772 -
773 - // Random: a seed (set per render in ayg_build_gallery) makes RAND(seed) stable across this
774 - // gallery's own pagination/search, so pages don't repeat or skip videos.
775 - $random_seed = 0;
776 -
777 - if ( 'random' === $sort_by_raw ) {
778 - $random_seed = isset( $params['sort_seed'] ) ? (int) $params['sort_seed'] : 0;
779 - $order_by = ( $random_seed > 0 ) ? 'RAND(%d)' : 'RAND()';
780 - } else {
781 - $sort_by = isset( $orderby_map[ $sort_by_raw ] ) ? $orderby_map[ $sort_by_raw ] : 'v.published_at_datetime';
782 - $order_by = "$sort_by $sort_order, v.id $sort_order";
783 - }
784 -
785 - // Deeplinked video: pin it to the top of the list so page 1 starts with the shared video
786 - // while the per-page count stays exact. The pin reorders the whole list (not just page 1),
787 - // so paginated AJAX requests passing the same id never repeat or skip videos. Ignored
788 - // while searching — search results are a fresh listing of their own.
789 - $featured_video_id = '';
790 -
791 - if ( empty( $params['searchTerm'] ) && ! empty( $params['featured_video_id'] ) ) {
792 - $featured_video_id = (string) $params['featured_video_id'];
793 - $order_by = '(v.video_id = %s) DESC, ' . $order_by;
794 - }
795 -
796 - // Assemble placeholder values in SQL order: WHERE ..., [featured video], [seed], LIMIT, OFFSET.
797 - $query_values = $values;
798 -
799 - if ( '' !== $featured_video_id ) {
800 - $query_values[] = $featured_video_id;
801 - }
802 -
803 - if ( $random_seed > 0 ) {
804 - $query_values[] = $random_seed;
805 - }
806 -
807 - $query_values[] = $limit;
808 - $query_values[] = $offset;
809 -
810 691 $query = $wpdb->prepare(
811 - "SELECT v.*
692 + "SELECT v.*
812 693 FROM $videos_table AS v
813 - INNER JOIN $rel_table AS r ON v.video_id = r.video_id
814 - WHERE $where
815 - ORDER BY $order_by
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
816 698 LIMIT %d OFFSET %d",
817 - $query_values
699 + $gallery_id, $search_term, $search_term, $limit, $offset
818 700 );
819 -
701 +
820 702 $videos = $wpdb->get_results( $query );
821 703
822 704 if ( empty( $videos ) ) {
823 705 return $this->get_error( __( 'No videos found matching your query.', 'automatic-youtube-gallery' ) );
@@ -824,13 +706,10 @@
824 706 }
825 707
826 708 foreach ( $videos as $index => $video ) {
827 709 if ( ! empty( $video->thumbnails ) ) {
828 - $videos[ $index ]->thumbnails = ayg_maybe_unserialize( $video->thumbnails );
710 + $videos[ $index ]->thumbnails = maybe_unserialize( $video->thumbnails );
829 711 }
830 -
831 - // Backward compat: templates reference $video->id as the YouTube video ID.
832 - $videos[ $index ]->id = $video->video_id;
833 712 }
834 713
835 714 // Process output
836 715 $response = new stdClass();
@@ -885,20 +764,22 @@
885 764 unset( $params['cache'] );
886 765 }
887 766 $cache_duration = min( $cache_duration, 2419200 ); // Max cache duration: 1 Month
888 767
889 - // Every parameter — the search term "q" included — goes through http_build_query() so it is
890 - // URL encoded. "q" used to be appended to the URL raw, which meant a caller could smuggle
891 - // extra parameters into the outbound request by putting "&" in the search keywords.
768 + $q = '';
769 + if ( isset( $params['q'] ) ) {
770 + $q = $params['q'];
771 + unset( $params['q'] );
772 + }
773 +
892 774 $api_url = $url . ( strpos( $url, '?' ) === false ? '?' : '&' ) . http_build_query( $params );
775 + if ( ! empty( $q ) ) {
776 + $api_url .= '&q=' . $q;
777 + }
893 778
894 - // Prefix the cache key with the gallery uid so a single gallery's transients can be cleared
895 - // on demand (e.g. saving a live search/livestream gallery) via ayg_delete_cache( $uid ).
896 - $cache_uid = isset( $this->params['uid'] ) ? (string) $this->params['uid'] : '';
897 - $cache_key = 'ayg_' . ( '' !== $cache_uid ? $cache_uid . '_' : '' ) . md5( $api_url );
898 -
899 779 // Request from cache
900 780 if ( ! $this->is_development_mode && $cache_duration > 0 ) {
781 + $cache_key = 'ayg_' . md5( $api_url );
901 782 $cache_data = get_transient( $cache_key );
902 783
903 784 if ( ! empty( $cache_data ) ) {
904 785 return $cache_data;
@@ -952,9 +833,12 @@
952 833 if ( $cache_enabled ) {
953 834 set_transient( $cache_key, $data, $cache_duration );
954 835
955 836 // Get the current list of transients
956 - $cache_keys = ayg_get_option( 'ayg_transient_keys' );
837 + $cache_keys = get_option( 'ayg_transient_keys', array() );
838 + if ( ! is_array( $cache_keys ) ) {
839 + $cache_keys = (array) $cache_keys;
840 + }
957 841
958 842 // Append our new one
959 843 if ( ! in_array( $cache_key, $cache_keys ) ) {
960 844 $cache_keys[] = $cache_key;
@@ -959,30 +843,15 @@
959 843 if ( ! in_array( $cache_key, $cache_keys ) ) {
960 844 $cache_keys[] = $cache_key;
961 845 }
962 846
963 - // Save it to the DB (autoload=no: this list can grow large and is not needed on every page load)
964 - update_option( 'ayg_transient_keys', $cache_keys, false );
847 + // Save it to the DB
848 + update_option( 'ayg_transient_keys', $cache_keys );
965 849 }
966 850
967 - // Store videos in our custom database table "{$wpdb->prefix}ayg_videos"
968 - //
969 - // What reaches the storage layer is rebuilt from an explicit allowlist instead of being
970 - // handed the raw request params, so the allowlist that governs the outbound request now
971 - // governs the database write made from the same function. Gallery membership ("uid") is
972 - // the value that decides what a gallery displays, so it is only passed along when the
973 - // caller opted in through "store" — never on the strength of a "uid" alone.
974 - $store_attributes = array(
975 - 'type' => isset( $this->params['type'] ) ? $this->params['type'] : '',
976 - 'exclude' => ( isset( $this->params['exclude'] ) && is_array( $this->params['exclude'] ) ) ? $this->params['exclude'] : array()
977 - );
978 -
979 - if ( $this->can_store && ! empty( $this->params['uid'] ) ) {
980 - $store_attributes['uid'] = (string) $this->params['uid'];
981 - }
982 -
983 - ayg_db_store_videos( $data, $store_attributes );
984 -
851 + // Store videos in our custom database table "{$wpdb->prefix}ayg_videos"
852 + ayg_db_store_videos( $data, $this->params );
853 +
985 854 // Finally return the data
986 855 return $data;
987 856 }
988 857
@@ -995,26 +864,19 @@
995 864 * @return mixed
996 865 */
997 866 private function parse_videos( $data ) {
998 867 if ( empty( $data->items ) || ! is_array( $data->items ) ) {
999 - $error = $this->get_error( __( 'No videos found matching your query.', 'automatic-youtube-gallery' ) );
1000 -
1001 - // Flag an empty result set so an incremental sync can tell "nothing new" apart
1002 - // from a genuine API failure (quota, bad source) and complete cleanly.
1003 - $error->no_results = true;
1004 -
1005 - return $error;
868 + return $this->get_error( __( 'No videos found matching your query.', 'automatic-youtube-gallery' ) );
1006 869 }
1007 870
1008 - $items = $data->items;
1009 - $videos = array();
1010 - $exclude = ( isset( $this->params['exclude'] ) && is_array( $this->params['exclude'] ) ) ? $this->params['exclude'] : array();
871 + $items = $data->items;
872 + $videos = array();
1011 873
1012 874 foreach ( $items as $item ) {
1013 875 $video = new stdClass();
1014 876
1015 877 // Video ID
1016 - $video->id = '';
878 + $video->id = '';
1017 879
1018 880 if ( isset( $item->snippet->resourceId ) && isset( $item->snippet->resourceId->videoId ) ) {
1019 881 $video->id = $item->snippet->resourceId->videoId;
1020 882 } elseif ( isset( $item->contentDetails ) && isset( $item->contentDetails->videoId ) ) {
@@ -1022,16 +884,11 @@
1022 884 } elseif ( isset( $item->id ) && isset( $item->id->videoId ) ) {
1023 885 $video->id = $item->id->videoId;
1024 886 } elseif ( isset( $item->id ) ) {
1025 887 $video->id = $item->id;
1026 - }
888 + }
1027 889
1028 - // Skip videos on the gallery's exclude list
1029 - if ( ayg_is_video_excluded( $video->id, $exclude ) ) {
1030 - continue;
1031 - }
1032 -
1033 - // Video channel ID
890 + // Video channel ID
1034 891 $video->channel_id = '';
1035 892
1036 893 if ( isset( $item->snippet->channelId ) ) {
1037 894 $video->channel_id = $item->snippet->channelId;
@@ -1078,68 +935,8 @@
1078 935 return $videos;
1079 936 }
1080 937
1081 938 /**
1082 - * Enrich parsed videos with duration and live broadcast details.
1083 - *
1084 - * playlistItems.list / search.list responses lack contentDetails.duration and
1085 - * snippet.liveBroadcastContent, so this makes a supplementary videos.list request (1 quota
1086 - * unit per 50 IDs) — updating the stored rows and merging duration + video_type into $videos.
1087 - *
1088 - * @since 2.8.0
1089 - * @access private
1090 - * @param array $videos Parsed video objects from parse_videos().
1091 - * @return array The same videos, with duration / duration_seconds / video_type set.
1092 - */
1093 - private function enrich_video_details( $videos ) {
1094 - // Index by video ID for the merge. PHP holds objects by handle, so mutating a $map
1095 - // entry below also updates the same instance in $videos (what we return).
1096 - $map = array();
1097 -
1098 - foreach ( $videos as $video ) {
1099 - if ( ! empty( $video->id ) ) {
1100 - $map[ $video->id ] = $video;
1101 - }
1102 - }
1103 -
1104 - if ( empty( $map ) ) {
1105 - return $videos;
1106 - }
1107 -
1108 - $api_url = $this->get_api_url( 'videos.list' );
1109 -
1110 - // videos.list accepts up to 50 IDs per request.
1111 - foreach ( array_chunk( array_keys( $map ), 50 ) as $chunk ) {
1112 - $api_params = array(
1113 - 'id' => implode( ',', $chunk ),
1114 - 'part' => 'id,snippet,contentDetails,status'
1115 - );
1116 -
1117 - // request_api() also stores the enriched rows (videos.list response →
1118 - // ayg_db_store_videos() writes duration_seconds + video_type).
1119 - $api_response = $this->request_api( $api_url, $api_params );
1120 -
1121 - if ( isset( $api_response->error ) || empty( $api_response->items ) || ! is_array( $api_response->items ) ) {
1122 - continue;
1123 - }
1124 -
1125 - foreach ( $api_response->items as $item ) {
1126 - if ( empty( $item->id ) || ! isset( $map[ $item->id ] ) ) {
1127 - continue;
1128 - }
1129 -
1130 - $duration = isset( $item->contentDetails->duration ) ? $item->contentDetails->duration : '';
1131 -
1132 - $map[ $item->id ]->duration = $duration;
1133 - $map[ $item->id ]->duration_seconds = ayg_parse_duration_seconds( $duration );
1134 - $map[ $item->id ]->video_type = ! empty( $item->snippet->liveBroadcastContent ) ? $item->snippet->liveBroadcastContent : 'none';
1135 - }
1136 - }
1137 -
1138 - return $videos;
1139 - }
1140 -
1141 - /**
1142 939 * Parse page info from the YouTube API response object.
1143 940 *
1144 941 * @since 1.0.0
1145 942 * @access private
@@ -1161,10 +958,9 @@
1161 958 if ( 'search' == $this->params['type'] ) {
1162 959 $limit = min( (int) $this->params['limit'], $page_info['videos_found'] );
1163 960 $page_info['total_pages'] = ceil( $limit / (int) $this->params['maxResults'] );
1164 961 } else {
1165 - $max_results = (int) $this->params['maxResults'];
1166 - $page_info['total_pages'] = ( $max_results <= 0 ) ? 1 : ceil( $page_info['videos_found'] / $max_results );
962 + $page_info['total_pages'] = ceil( $page_info['videos_found'] / (int) $this->params['maxResults'] );
1167 963 }
1168 964 }
1169 965
1170 966 // Token for the previous page