PluginProbe
Automatic YouTube Gallery – Embed Auto-Updating YouTube Video Galleries, Feeds, Playlists & Channels / 2.3.2
Automatic YouTube Gallery – Embed Auto-Updating YouTube Video Galleries, Feeds, Playlists & Channels v2.3.2
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 | public/public.php +37 -338 trunk2.3.2 View file →
@@ -35,11 +35,8 @@
35 35 *
36 36 * @since 1.0.0
37 37 */
38 38 public function register_styles() {
39 - $general_settings = ayg_get_option( 'ayg_general_settings' );
40 -
41 - // Register Styles
42 39 wp_register_style(
43 40 AYG_SLUG . '-public',
44 41 AYG_URL . 'public/assets/css/public.min.css',
45 42 array(),
@@ -45,13 +42,8 @@
45 42 array(),
46 43 AYG_VERSION,
47 44 'all'
48 45 );
49 -
50 - // Enqueue Styles
51 - if ( ! empty( $general_settings['force_load_assets']['css'] ) ) {
52 - wp_enqueue_style( AYG_SLUG . '-public' );
53 - }
54 46 }
55 47
56 48 /**
57 49 * Enqueue scripts for the public-facing side of the site.
@@ -58,46 +50,34 @@
58 50 *
59 51 * @since 1.0.0
60 52 */
61 53 public function register_scripts() {
62 - $general_settings = ayg_get_option( 'ayg_general_settings' );
63 - $gallery_settings = ayg_get_option( 'ayg_gallery_settings' );
64 - $player_settings = ayg_get_option( 'ayg_player_settings' );
65 - $privacy_settings = ayg_get_option( 'ayg_privacy_settings' );
66 - $strings_settings = ayg_get_option( 'ayg_strings_settings' );
54 + $player_settings = get_option( 'ayg_player_settings' );
55 + $privacy_settings = get_option( 'ayg_privacy_settings' );
67 56
68 - $player_type = isset( $player_settings['player_type'] ) ? sanitize_text_field( $player_settings['player_type'] ) : 'youtube';
57 + wp_register_script(
58 + AYG_SLUG . '-public',
59 + AYG_URL . 'public/assets/js/public.min.js',
60 + array( 'jquery' ),
61 + AYG_VERSION,
62 + false
63 + );
69 64
70 - // YouTube rejects embeds rendered inside wp-admin with "Error 153" (missing/unacceptable
71 - // referrer). Force the Plyr.js player in editor previews so the poster image is shown
72 - // instead of the failed native embed. Front-end output is unaffected.
73 - if ( is_admin() ) {
74 - $player_type = 'custom';
75 - }
76 -
77 - $scroll_top_offset = ( isset( $gallery_settings['scroll_top_offset'] ) && ! empty( $gallery_settings['scroll_top_offset'] ) ) ? (int) $gallery_settings['scroll_top_offset'] : 10;
78 - $scroll_top_offset = apply_filters( 'ayg_gallery_scrolltop_offset', $scroll_top_offset ); // Backward compatibility to 2.4.3
79 - $scroll_top_offset = apply_filters( 'ayg_gallery_scroll_top_offset', $scroll_top_offset );
80 -
81 - $show_more_label = ! empty( $strings_settings['show_more_label'] ) ? sanitize_text_field( $strings_settings['show_more_label'] ) : __( 'Show More', 'automatic-youtube-gallery' );
82 - $show_less_label = ! empty( $strings_settings['show_less_label'] ) ? sanitize_text_field( $strings_settings['show_less_label'] ) : __( 'Show Less', 'automatic-youtube-gallery' );
83 -
84 65 $script_args = array(
85 - 'plugin_url' => AYG_URL,
86 - 'plugin_version' => AYG_VERSION,
66 + 'current_url' => get_permalink(),
87 67 'ajax_url' => admin_url( 'admin-ajax.php' ),
88 68 'ajax_nonce' => wp_create_nonce( 'ayg_ajax_nonce' ),
89 - 'current_page_url' => get_permalink(),
90 - 'current_gallery_id' => get_query_var( 'ayg_gallery_id' ),
91 - 'player_type' => $player_type,
92 - 'player_color' => isset( $player_settings['player_color'] ) ? sanitize_text_field( $player_settings['player_color'] ) : '#00b3ff',
69 + 'gallery_index' => 0,
70 + 'gallery_id' => get_query_var( 'ayg_gallery_id' ),
71 + 'video_id' => get_query_var( 'ayg_video_id' ),
72 + 'active_player_id' => '',
93 73 'privacy_enhanced_mode' => isset( $player_settings['privacy_enhanced_mode'] ) ? (int) $player_settings['privacy_enhanced_mode'] : 0,
94 74 'origin' => '',
95 - 'cookieconsent' => 0,
96 - 'top_offset' => $scroll_top_offset,
75 + 'cookie_consent' => 0,
76 + 'top_offset' => apply_filters( 'ayg_gallery_scrolltop_offset', 10 ),
97 77 'i18n' => array(
98 - 'show_more' => $show_more_label,
99 - 'show_less' => $show_less_label
78 + 'show_more' => '[+] ' . __( 'Show More', 'automatic-youtube-gallery' ),
79 + 'show_less' => '[-] ' . __( 'Show Less', 'automatic-youtube-gallery' )
100 80 )
101 81 );
102 82
103 83 if ( isset( $player_settings['origin'] ) && ! empty( $player_settings['origin'] ) ) {
@@ -106,88 +86,27 @@
106 86 }
107 87
108 88 if ( ! isset( $_COOKIE['ayg_gdpr_consent'] ) ) {
109 89 if ( ! empty( $privacy_settings['cookie_consent'] ) && ! empty( $privacy_settings['consent_message'] ) && ! empty( $privacy_settings['button_label'] ) ) {
110 - $script_args['cookieconsent'] = 1;
111 - $script_args['cookieconsent_message'] = wp_kses_post( trim( $privacy_settings['consent_message'] ) );
112 - $script_args['cookieconsent_button_label'] = esc_html( $privacy_settings['button_label'] );
90 + $script_args['cookie_consent'] = 1;
91 + $script_args['consent_message'] = wp_kses_post( trim( $privacy_settings['consent_message'] ) );
92 + $script_args['button_label'] = esc_html( $privacy_settings['button_label'] );
113 93 }
114 94 }
115 95
116 - // Register Scripts
117 - $deps = array( 'jquery' );
118 -
119 - wp_register_script(
120 - AYG_SLUG . '-plyr',
121 - AYG_URL . 'vendor/plyr/plyr.polyfilled.js',
122 - array(),
123 - '3.7.8',
124 - array( 'strategy' => 'defer' )
125 - );
126 -
127 - if ( empty( $general_settings['force_load_assets']['js'] ) ) {
128 - if ( isset( $player_settings['player_type'] ) && 'custom' == $player_settings['player_type'] ) {
129 - $deps[] = AYG_SLUG . '-plyr';
130 - }
131 - }
132 -
133 - wp_register_script(
134 - AYG_SLUG . '-public',
135 - AYG_URL . 'public/assets/js/public.min.js',
136 - $deps,
137 - AYG_VERSION,
138 - array( 'strategy' => 'defer' )
139 - );
140 -
141 96 wp_localize_script(
142 97 AYG_SLUG . '-public',
143 - 'ayg_config',
98 + 'ayg_public',
144 99 $script_args
145 100 );
146 -
147 - wp_register_script(
148 - AYG_SLUG . '-theme-classic',
149 - AYG_URL . 'public/assets/js/theme-classic.min.js',
150 - array( 'jquery' ),
151 - AYG_VERSION,
152 - array( 'strategy' => 'defer' )
153 - );
154 -
155 - // Enqueue Scripts
156 - if ( ! empty( $general_settings['force_load_assets']['js'] ) ) {
157 - wp_enqueue_script( AYG_SLUG . '-public' );
158 - }
159 101 }
160 102
161 103 /**
162 - * Enqueue block assets inside the block editor (iframe).
104 + * Enqueue Gutenberg block assets for backend editor.
163 105 *
164 - * Hooked to enqueue_block_assets with an is_admin() guard so styles and scripts
165 - * are injected inside the iframed block editor (WP 6.3+ / WP 7.0 always) only,
166 - * and not duplicated on the front end where wp_enqueue_scripts already handles them.
167 - *
168 - * @since 2.7.2
169 - */
170 - public function enqueue_block_assets() {
171 - if ( ! is_admin() ) {
172 - return;
173 - }
174 -
175 - $this->enqueue_editor_assets();
176 - }
177 -
178 - /**
179 - * Enqueue the plugin's public styles and scripts in any editor context.
180 - *
181 - * Called by enqueue_block_assets() (WordPress block editor, guarded by is_admin())
182 - * and hooked directly to Elementor actions so assets are also available in the
183 - * Elementor editor panel and its frontend live-preview iframe:
184 - * - elementor/editor/after_enqueue_scripts (admin context)
185 - * - elementor/preview/enqueue_scripts (frontend context, is_admin() = false)
186 - *
187 106 * @since 1.6.1
188 107 */
189 - public function enqueue_editor_assets() {
108 + public function enqueue_block_editor_assets() {
190 109 // Styles
191 110 $this->register_styles();
192 111 wp_enqueue_style( AYG_SLUG . '-public' );
193 112
@@ -192,11 +111,9 @@
192 111 wp_enqueue_style( AYG_SLUG . '-public' );
193 112
194 113 // Scripts
195 114 $this->register_scripts();
196 -
197 115 wp_enqueue_script( AYG_SLUG . '-public' );
198 - wp_enqueue_script( AYG_SLUG . '-theme-classic' );
199 116 }
200 117
201 118 /**
202 119 * Process the shortcode [automatic_youtube_gallery].
@@ -202,16 +119,11 @@
202 119 * Process the shortcode [automatic_youtube_gallery].
203 120 *
204 121 * @since 1.0.0
205 122 * @param array $attributes An associative array of attributes.
206 - * @param string $content Enclosing content.
207 123 * @return string Shortcode HTML output.
208 124 */
209 - public function shortcode_automatic_youtube_gallery( $attributes, $content = null ) {
210 - if ( ! empty( $content ) ) {
211 - $attributes['content'] = $content;
212 - }
213 -
125 + public function shortcode_automatic_youtube_gallery( $attributes ) {
214 126 return ayg_build_gallery( $attributes );
215 127 }
216 128
217 129 /**
@@ -216,101 +128,28 @@
216 128
217 129 /**
218 130 * Load more videos.
219 131 *
220 - * Registered for both wp_ajax_ and wp_ajax_nopriv_, so every value in $_POST arrives from an
221 - * unauthenticated visitor. The gallery being paginated is therefore resolved server side
222 - * rather than taken from the request — see resolve_gallery_request().
223 - *
224 132 * @since 1.0.0
225 133 */
226 - public function ajax_callback_load_videos() {
134 + public function ajax_callback_load_more_videos() {
227 135 // Security check
228 - check_ajax_referer( 'ayg_ajax_nonce', 'security' );
136 + check_ajax_referer( 'ayg_ajax_nonce', 'security' );
229 137
230 138 // Proceed safe
231 - $json = array();
232 - $attributes = array_map( 'sanitize_text_field', $_POST );
139 + $json = array();
140 + $attributes = array_map( 'sanitize_text_field', $_POST );
141 + $source_type = $attributes['type'];
233 142
234 - // Work out which gallery this request belongs to, from the site's own data.
235 - $request = $this->resolve_gallery_request( $attributes );
236 -
237 - // Only galleries this site actually displays may reach the YouTube API from here. A saved
238 - // Gallery Builder record qualifies, and so does a legacy shortcode gallery that has been
239 - // rendered at least once — its videos are already in the relationship table. Anything else
240 - // describes a gallery that does not exist here, or a UID that could not be tied to the
241 - // source sent with it, so it is refused before a single unit of API quota is spent on it.
242 - if ( ! $request['gallery'] && ! ayg_db_gallery_has_videos( $request['uid'] ) ) {
243 - wp_send_json_error( array(
244 - 'message' => __( 'No videos found matching your query.', 'automatic-youtube-gallery' )
245 - ) );
246 - }
247 -
248 - $source_type = $request['type'];
249 -
250 - // The resolved values replace whatever was posted, so the thumbnails rendered below — and
251 - // the deeplink URLs built from them — belong to the gallery we resolved.
252 - $attributes['uid'] = $request['uid'];
253 - $attributes['type'] = $source_type;
254 -
255 - // Videos per page and search limit are normalised exactly as ayg_build_gallery() does, so a
256 - // paginated request can't ask for a page size the initial render would never produce.
257 - $per_page = isset( $attributes['per_page'] ) ? (int) $attributes['per_page'] : 0;
258 -
259 - if ( 'db' === $source_type ) {
260 - $per_page = max( 0, $per_page ); // 0 = every video on a single page (DB served galleries only).
261 - } else {
262 - $per_page = min( 50, $per_page ); // YouTube returns at most 50 results per request.
263 -
264 - if ( $per_page < 1 ) {
265 - $per_page = 50;
266 - }
267 - }
268 -
269 - $limit = isset( $attributes['limit'] ) ? min( 500, (int) $attributes['limit'] ) : 500;
270 -
271 - if ( $limit < 1 ) {
272 - $limit = 500;
273 - }
274 -
275 - // Page token. For the sources that page through the live API, every distinct token costs
276 - // another API call, so only tokens this site actually issued are accepted — see
277 - // ayg_sign_page_token(). A search request is answered from our own tables instead, so its
278 - // page number is just a number and needs no signature.
279 - $page_token = isset( $attributes['pageToken'] ) ? $attributes['pageToken'] : '';
280 -
281 - if ( empty( $attributes['searchTerm'] ) && ayg_page_token_is_signed( $source_type ) ) {
282 - $page_token = ayg_verify_page_token( $page_token, $request['uid'] );
283 -
284 - if ( false === $page_token ) {
285 - wp_send_json_error( array(
286 - 'message' => __( 'No videos found matching your query.', 'automatic-youtube-gallery' )
287 - ) );
288 - }
289 - }
290 -
291 143 $api_params = array(
292 - 'uid' => $request['uid'],
293 - 'type' => $source_type,
294 - 'src' => $request['src'],
295 - 'store' => true, // Safe: the uid above is resolved server side, never posted.
296 - 'featured_video_id' => isset( $attributes['featured_video_id'] ) ? $attributes['featured_video_id'] : '', // Works only when type=db (deeplinked video pinned first)
297 - 'order' => isset( $attributes['order'] ) ? $attributes['order'] : 'date', // Works only when type=search
298 - 'sort_by' => isset( $attributes['sort_by'] ) ? $attributes['sort_by'] : 'date', // Works only when type=db
299 - 'sort_order' => isset( $attributes['sort_order'] ) ? $attributes['sort_order'] : 'desc', // Works only when type=db
300 - 'sort_seed' => isset( $attributes['sort_seed'] ) ? (int) $attributes['sort_seed'] : 0, // Works only when type=db + sort_by=random
301 - 'duration_filter' => isset( $attributes['duration_filter'] ) ? $attributes['duration_filter'] : '', // Works only when type=db
302 - 'duration' => isset( $attributes['duration'] ) ? (int) $attributes['duration'] : 0, // Works only when type=db
303 - 'limit' => $limit,
304 - 'maxResults' => $per_page,
305 - 'cache' => (int) apply_filters( 'ayg_ajax_cache_duration', $request['cache'], $attributes ),
306 - 'pageToken' => $page_token
144 + 'type' => $source_type,
145 + 'src' => $attributes['src'],
146 + 'order' => $attributes['order'], // works only when type=search
147 + 'maxResults' => (int) $attributes['per_page'],
148 + 'cache' => (int) $attributes['cache'],
149 + 'pageToken' => $attributes['pageToken']
307 150 );
308 151
309 - if ( ! empty( $attributes['searchTerm'] ) ) {
310 - $api_params['searchTerm'] = $attributes['searchTerm'];
311 - }
312 -
313 152 $youtube_api = new AYG_YouTube_API();
314 153 $response = $youtube_api->query( $api_params );
315 154
316 155 if ( ! isset( $response->error ) ) {
@@ -315,36 +154,16 @@
315 154
316 155 if ( ! isset( $response->error ) ) {
317 156 if ( isset( $response->page_info ) ) {
318 157 $json = $response->page_info;
319 -
320 - // Sign the tokens handed back to the browser, exactly as the initial render does,
321 - // so the next page request can be verified the same way.
322 - if ( ayg_page_token_is_signed( $source_type ) ) {
323 - $json = ayg_sign_page_tokens( $json, $request['uid'] );
324 - }
325 -
326 - $json['message'] = sprintf(
327 - _n( '%s video found matching your query.', '%s videos found matching your query.', $json['videos_found'], 'automatic-youtube-gallery' ),
328 - number_format_i18n( $json['videos_found'] )
329 - );
330 158 }
331 159
332 160 if ( isset( $response->videos ) ) {
333 161 $videos = $response->videos;
334 - $columns = isset( $attributes['columns'] ) ? min( 12, max( 1, (int) $attributes['columns'] ) ) : 3;
335 162
336 163 ob_start();
337 164 foreach ( $videos as $index => $video ) {
338 - $classes = array();
339 - $classes[] = 'ayg-video';
340 - $classes[] = 'ayg-video-' . $video->id;
341 - $classes[] = 'ayg-col';
342 - $classes[] = 'ayg-col-' . $columns;
343 - if ( $columns > 3 ) $classes[] = 'ayg-col-sm-3';
344 - if ( $columns > 2 ) $classes[] = 'ayg-col-xs-2';
345 -
346 - echo'<div class="' . implode( ' ', $classes ) . '">';
165 + echo'<div class="ayg-item ayg-item-' . $video->id . ' ayg-col ayg-col-' . (int) $attributes['columns'] . '">';
347 166 the_ayg_gallery_thumbnail( $video, $attributes );
348 167 echo '</div>';
349 168 }
350 169 $json['html'] = ob_get_clean();
@@ -354,128 +173,8 @@
354 173 } else {
355 174 $json['message'] = $response->error_message;
356 175 wp_send_json_error( $json );
357 176 }
358 - }
359 -
360 - /**
361 - * Work out which gallery an AJAX request belongs to, without trusting the request.
362 - *
363 - * The gallery UID decides which gallery any videos fetched by the request are linked to, and a
364 - * UID on its own proves nothing — it is printed in the page for anyone to read. So it is only
365 - * accepted when it is demonstrably tied to the source posted with it.
366 - *
367 - * A numeric UID may be a Gallery Builder gallery ID — but it may equally be a legacy shortcode
368 - * gallery that was given a numeric "uid" attribute ( e.g. [automatic_youtube_gallery
369 - * type="channel" channel="UC..." uid="5"] ). So a numeric UID is only treated as a Builder
370 - * gallery when the request is genuinely tied to that gallery's own saved source — signed for
371 - * it, or, for pages cached before this version, posting that exact source. Otherwise it is
372 - * resolved as the legacy gallery it is, so a legacy "uid" that happens to match a Builder ID is
373 - * never misrouted to the wrong gallery.
374 - *
375 - * For a Builder gallery the source type and value come from the saved row, the one thing a
376 - * shortcode cannot override. Its other settings, cache duration included, can be overridden per
377 - * shortcode, so the posted cache is honoured when the signature proves it.
378 - *
379 - * A legacy shortcode gallery has no saved row, so its UID must prove itself in one of two ways:
380 - *
381 - * 1. It is the source's own fingerprint. ayg_build_gallery() derives the UID as
382 - * md5( source type + source ), so for the great majority of galleries the posted UID
383 - * already proves which source it belongs to and needs nothing else.
384 - * 2. It carries a valid signature. A gallery can set its own UID through the "uid" shortcode
385 - * attribute or the ayg_gallery_id filter, and neither can be recomputed here. For those the
386 - * render signs the UID together with its source, and that pairing is verified here.
387 - *
388 - * Either way the caller cannot combine one gallery's UID with another source, which is what
389 - * the whole fix rests on.
390 - *
391 - * @since 2.9.0
392 - * @access private
393 - * @param array $attributes Sanitized $_POST values.
394 - * @return array Resolved "gallery" row (null for legacy galleries), "uid",
395 - * "type" and "src". The UID is an empty string when the request
396 - * could not be tied to a gallery.
397 - */
398 - private function resolve_gallery_request( $attributes ) {
399 - $requested_uid = isset( $attributes['uid'] ) ? (string) $attributes['uid'] : '';
400 - $source_type = isset( $attributes['type'] ) ? $attributes['type'] : '';
401 - $source_url = isset( $attributes['src'] ) ? $attributes['src'] : '';
402 - $signature = isset( $attributes['signature'] ) ? $attributes['signature'] : '';
403 - $cache = isset( $attributes['cache'] ) ? (int) $attributes['cache'] : 0;
404 -
405 - // A numeric UID might be a Gallery Builder gallery — look it up, but only commit to that
406 - // reading when the request is actually tied to the gallery's own source ( see the method
407 - // note ). This is what stops a legacy gallery using a numeric "uid" attribute from being
408 - // misrouted to a Builder gallery that happens to share the number.
409 - $gallery = ( '' !== $requested_uid && ctype_digit( $requested_uid ) ) ? ayg_get_gallery( (int) $requested_uid ) : null;
410 -
411 - if ( $gallery ) {
412 - $source = ayg_get_gallery_source( $gallery );
413 -
414 - // Signed for this gallery's own source ( normal, post-2.9.0 render ), or — for pages
415 - // cached before this version, which carry no signature — posting that exact source.
416 - $is_signed = ( '' !== $signature && hash_equals( ayg_get_gallery_signature( $requested_uid, $source['type'], $source['src'], $cache ), $signature ) );
417 - $matches_source = ( $source['type'] === $source_type && $source['src'] === $source_url );
418 -
419 - if ( $is_signed || $matches_source ) {
420 - return array(
421 - 'gallery' => $gallery,
422 - 'uid' => strval( $gallery->id ),
423 - 'type' => $source['type'],
424 - 'src' => $source['src'],
425 - 'cache' => $is_signed ? max( 0, $cache ) : $this->get_gallery_cache_duration( $gallery )
426 - );
427 - }
428 -
429 - // Not this Builder gallery after all — fall through and resolve as a legacy gallery.
430 - }
431 -
432 - // Legacy shortcode gallery.
433 - //
434 - // Route 1 — the UID is the fingerprint of the source posted with it. Accepting this
435 - // without a signature also means pages rendered before this version, including ones held
436 - // in a full page cache, keep paginating normally.
437 - $is_derived = ( '' !== $requested_uid && hash_equals( md5( $source_type . $source_url ), $requested_uid ) );
438 -
439 - // Route 2 — the UID, source and cache duration were all signed together when the gallery
440 - // was rendered, so the whole set is known to be one this site issued. This is the route a
441 - // legacy gallery with a custom numeric "uid" takes.
442 - $is_signed = ( '' !== $signature && hash_equals( ayg_get_gallery_signature( $requested_uid, $source_type, $source_url, $cache ), $signature ) );
443 -
444 - return array(
445 - 'gallery' => null,
446 - 'uid' => ( $is_derived || $is_signed ) ? $requested_uid : '',
447 - 'type' => $source_type,
448 - 'src' => $source_url,
449 - // The gallery's own "Cache Duration" setting is honoured in full — including "No
450 - // Caching" — because a signed request proves the value is the one the shortcode was
451 - // rendered with rather than one the caller picked. Only an unsigned request (a page
452 - // rendered before this version) falls back to the default.
453 - 'cache' => $is_signed ? max( 0, $cache ) : DAY_IN_SECONDS
454 - );
455 - }
456 -
457 - /**
458 - * Read a Gallery Builder gallery's saved cache duration.
459 - *
460 - * Taken from the gallery's own row rather than the request, so there is nothing for a caller
461 - * to influence.
462 - *
463 - * @since 2.9.0
464 - * @access private
465 - * @param object $gallery Gallery row.
466 - * @return int Cache duration in seconds.
467 - */
468 - private function get_gallery_cache_duration( $gallery ) {
469 - $cache_duration = DAY_IN_SECONDS; // Matches the "Cache Duration" field default.
470 -
471 - $params = json_decode( (string) $gallery->params, true );
472 -
473 - if ( is_array( $params ) && isset( $params['cache'] ) ) {
474 - $cache_duration = (int) $params['cache'];
475 - }
476 -
477 - return max( 0, $cache_duration );
478 177 }
479 178
480 179 /**
481 180 * Set cookie for accepting the privacy consent.