| @@ -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 |