AYG_URL, 'plugin_version' => AYG_VERSION, 'ajax_url' => admin_url( 'admin-ajax.php' ), 'ajax_nonce' => wp_create_nonce( 'ayg_ajax_nonce' ), 'current_page_url' => get_permalink(), 'current_gallery_id' => get_query_var( 'ayg_gallery_id' ), 'player_type' => $player_type, 'player_color' => isset( $player_settings['player_color'] ) ? sanitize_text_field( $player_settings['player_color'] ) : '#00b3ff', 'privacy_enhanced_mode' => isset( $player_settings['privacy_enhanced_mode'] ) ? (int) $player_settings['privacy_enhanced_mode'] : 0, 'origin' => '', 'cookieconsent' => 0, 'top_offset' => $scroll_top_offset, 'i18n' => array( 'show_more' => $show_more_label, 'show_less' => $show_less_label ) ); if ( isset( $player_settings['origin'] ) && ! empty( $player_settings['origin'] ) ) { $url_parts = parse_url( site_url() ); $script_args['origin'] = $url_parts['scheme'] . '://' . $url_parts['host']; } if ( ! isset( $_COOKIE['ayg_gdpr_consent'] ) ) { if ( ! empty( $privacy_settings['cookie_consent'] ) && ! empty( $privacy_settings['consent_message'] ) && ! empty( $privacy_settings['button_label'] ) ) { $script_args['cookieconsent'] = 1; $script_args['cookieconsent_message'] = wp_kses_post( trim( $privacy_settings['consent_message'] ) ); $script_args['cookieconsent_button_label'] = esc_html( $privacy_settings['button_label'] ); } } // Register Scripts $deps = array( 'jquery' ); wp_register_script( AYG_SLUG . '-plyr', AYG_URL . 'vendor/plyr/plyr.polyfilled.js', array(), '3.7.8', array( 'strategy' => 'defer' ) ); if ( empty( $general_settings['force_load_assets']['js'] ) ) { if ( isset( $player_settings['player_type'] ) && 'custom' == $player_settings['player_type'] ) { $deps[] = AYG_SLUG . '-plyr'; } } wp_register_script( AYG_SLUG . '-public', AYG_URL . 'public/assets/js/public.min.js', $deps, AYG_VERSION, array( 'strategy' => 'defer' ) ); wp_localize_script( AYG_SLUG . '-public', 'ayg_config', $script_args ); wp_register_script( AYG_SLUG . '-theme-classic', AYG_URL . 'public/assets/js/theme-classic.min.js', array( 'jquery' ), AYG_VERSION, array( 'strategy' => 'defer' ) ); // Enqueue Scripts if ( ! empty( $general_settings['force_load_assets']['js'] ) ) { wp_enqueue_script( AYG_SLUG . '-public' ); } } /** * Enqueue block assets inside the block editor (iframe). * * Hooked to enqueue_block_assets with an is_admin() guard so styles and scripts * are injected inside the iframed block editor (WP 6.3+ / WP 7.0 always) only, * and not duplicated on the front end where wp_enqueue_scripts already handles them. * * @since 2.7.2 */ public function enqueue_block_assets() { if ( ! is_admin() ) { return; } $this->enqueue_editor_assets(); } /** * Enqueue the plugin's public styles and scripts in any editor context. * * Called by enqueue_block_assets() (WordPress block editor, guarded by is_admin()) * and hooked directly to Elementor actions so assets are also available in the * Elementor editor panel and its frontend live-preview iframe: * - elementor/editor/after_enqueue_scripts (admin context) * - elementor/preview/enqueue_scripts (frontend context, is_admin() = false) * * @since 1.6.1 */ public function enqueue_editor_assets() { // Styles $this->register_styles(); wp_enqueue_style( AYG_SLUG . '-public' ); // Scripts $this->register_scripts(); wp_enqueue_script( AYG_SLUG . '-public' ); wp_enqueue_script( AYG_SLUG . '-theme-classic' ); } /** * Process the shortcode [automatic_youtube_gallery]. * * @since 1.0.0 * @param array $attributes An associative array of attributes. * @param string $content Enclosing content. * @return string Shortcode HTML output. */ public function shortcode_automatic_youtube_gallery( $attributes, $content = null ) { if ( ! empty( $content ) ) { $attributes['content'] = $content; } return ayg_build_gallery( $attributes ); } /** * Load more videos. * * Registered for both wp_ajax_ and wp_ajax_nopriv_, so every value in $_POST arrives from an * unauthenticated visitor. The gallery being paginated is therefore resolved server side * rather than taken from the request — see resolve_gallery_request(). * * @since 1.0.0 */ public function ajax_callback_load_videos() { // Security check check_ajax_referer( 'ayg_ajax_nonce', 'security' ); // Proceed safe $json = array(); $attributes = array_map( 'sanitize_text_field', $_POST ); // Work out which gallery this request belongs to, from the site's own data. $request = $this->resolve_gallery_request( $attributes ); // Only galleries this site actually displays may reach the YouTube API from here. A saved // Gallery Builder record qualifies, and so does a legacy shortcode gallery that has been // rendered at least once — its videos are already in the relationship table. Anything else // describes a gallery that does not exist here, or a UID that could not be tied to the // source sent with it, so it is refused before a single unit of API quota is spent on it. if ( ! $request['gallery'] && ! ayg_db_gallery_has_videos( $request['uid'] ) ) { wp_send_json_error( array( 'message' => __( 'No videos found matching your query.', 'automatic-youtube-gallery' ) ) ); } $source_type = $request['type']; // The resolved values replace whatever was posted, so the thumbnails rendered below — and // the deeplink URLs built from them — belong to the gallery we resolved. $attributes['uid'] = $request['uid']; $attributes['type'] = $source_type; // Videos per page and search limit are normalised exactly as ayg_build_gallery() does, so a // paginated request can't ask for a page size the initial render would never produce. $per_page = isset( $attributes['per_page'] ) ? (int) $attributes['per_page'] : 0; if ( 'db' === $source_type ) { $per_page = max( 0, $per_page ); // 0 = every video on a single page (DB served galleries only). } else { $per_page = min( 50, $per_page ); // YouTube returns at most 50 results per request. if ( $per_page < 1 ) { $per_page = 50; } } $limit = isset( $attributes['limit'] ) ? min( 500, (int) $attributes['limit'] ) : 500; if ( $limit < 1 ) { $limit = 500; } // Page token. For the sources that page through the live API, every distinct token costs // another API call, so only tokens this site actually issued are accepted — see // ayg_sign_page_token(). A search request is answered from our own tables instead, so its // page number is just a number and needs no signature. $page_token = isset( $attributes['pageToken'] ) ? $attributes['pageToken'] : ''; if ( empty( $attributes['searchTerm'] ) && ayg_page_token_is_signed( $source_type ) ) { $page_token = ayg_verify_page_token( $page_token, $request['uid'] ); if ( false === $page_token ) { wp_send_json_error( array( 'message' => __( 'No videos found matching your query.', 'automatic-youtube-gallery' ) ) ); } } $api_params = array( 'uid' => $request['uid'], 'type' => $source_type, 'src' => $request['src'], 'store' => true, // Safe: the uid above is resolved server side, never posted. 'featured_video_id' => isset( $attributes['featured_video_id'] ) ? $attributes['featured_video_id'] : '', // Works only when type=db (deeplinked video pinned first) 'order' => isset( $attributes['order'] ) ? $attributes['order'] : 'date', // Works only when type=search 'sort_by' => isset( $attributes['sort_by'] ) ? $attributes['sort_by'] : 'date', // Works only when type=db 'sort_order' => isset( $attributes['sort_order'] ) ? $attributes['sort_order'] : 'desc', // Works only when type=db 'sort_seed' => isset( $attributes['sort_seed'] ) ? (int) $attributes['sort_seed'] : 0, // Works only when type=db + sort_by=random 'duration_filter' => isset( $attributes['duration_filter'] ) ? $attributes['duration_filter'] : '', // Works only when type=db 'duration' => isset( $attributes['duration'] ) ? (int) $attributes['duration'] : 0, // Works only when type=db 'limit' => $limit, 'maxResults' => $per_page, 'cache' => (int) apply_filters( 'ayg_ajax_cache_duration', $request['cache'], $attributes ), 'pageToken' => $page_token ); if ( ! empty( $attributes['searchTerm'] ) ) { $api_params['searchTerm'] = $attributes['searchTerm']; } $youtube_api = new AYG_YouTube_API(); $response = $youtube_api->query( $api_params ); if ( ! isset( $response->error ) ) { if ( isset( $response->page_info ) ) { $json = $response->page_info; // Sign the tokens handed back to the browser, exactly as the initial render does, // so the next page request can be verified the same way. if ( ayg_page_token_is_signed( $source_type ) ) { $json = ayg_sign_page_tokens( $json, $request['uid'] ); } $json['message'] = sprintf( _n( '%s video found matching your query.', '%s videos found matching your query.', $json['videos_found'], 'automatic-youtube-gallery' ), number_format_i18n( $json['videos_found'] ) ); } if ( isset( $response->videos ) ) { $videos = $response->videos; $columns = isset( $attributes['columns'] ) ? min( 12, max( 1, (int) $attributes['columns'] ) ) : 3; ob_start(); foreach ( $videos as $index => $video ) { $classes = array(); $classes[] = 'ayg-video'; $classes[] = 'ayg-video-' . $video->id; $classes[] = 'ayg-col'; $classes[] = 'ayg-col-' . $columns; if ( $columns > 3 ) $classes[] = 'ayg-col-sm-3'; if ( $columns > 2 ) $classes[] = 'ayg-col-xs-2'; echo'