PluginProbe
Automatic YouTube Gallery – Embed Auto-Updating YouTube Video Galleries, Feeds, Playlists & Channels / 2.6.4
Automatic YouTube Gallery – Embed Auto-Updating YouTube Video Galleries, Feeds, Playlists & Channels v2.6.4
2.9.1 2.9.0 trunk 1.0.0 1.1.0 1.2.0 1.3.0 1.4.0 1.5.0 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 2.0.0 2.1.0 2.2.0 2.3.2 2.3.3 2.3.5 2.3.6 2.3.8 2.3.9 2.4.3 All 37 releases
← All changes | public/public.php +56 -287 trunk2.6.4 View file →
@@ -35,23 +35,30 @@
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
39 + $player_settings = get_option( 'ayg_player_settings' );
40 +
41 + $deps = array();
42 + if ( isset( $player_settings['player_type'] ) && 'custom' == $player_settings['player_type'] ) {
43 + $deps[] = AYG_SLUG . '-plyr';
44 + }
45 +
42 46 wp_register_style(
47 + AYG_SLUG . '-plyr',
48 + AYG_URL . 'vendor/plyr/plyr.css',
49 + array(),
50 + '3.7.8',
51 + 'all'
52 + );
53 +
54 + wp_register_style(
43 55 AYG_SLUG . '-public',
44 56 AYG_URL . 'public/assets/css/public.min.css',
45 - array(),
57 + $deps,
46 58 AYG_VERSION,
47 59 'all'
48 60 );
49 -
50 - // Enqueue Styles
51 - if ( ! empty( $general_settings['force_load_assets']['css'] ) ) {
52 - wp_enqueue_style( AYG_SLUG . '-public' );
53 - }
54 61 }
55 62
56 63 /**
57 64 * Enqueue scripts for the public-facing side of the site.
@@ -58,23 +65,34 @@
58 65 *
59 66 * @since 1.0.0
60 67 */
61 68 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' );
69 + $strings_settings = get_option( 'ayg_strings_settings' );
70 + $gallery_settings = get_option( 'ayg_gallery_settings' );
71 + $player_settings = get_option( 'ayg_player_settings' );
72 + $privacy_settings = get_option( 'ayg_privacy_settings' );
67 73
68 - $player_type = isset( $player_settings['player_type'] ) ? sanitize_text_field( $player_settings['player_type'] ) : 'youtube';
74 + $deps = array( 'jquery' );
75 + if ( isset( $player_settings['player_type'] ) && 'custom' == $player_settings['player_type'] ) {
76 + $deps[] = AYG_SLUG . '-plyr';
77 + }
69 78
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 -
79 + wp_register_script(
80 + AYG_SLUG . '-plyr',
81 + AYG_URL . 'vendor/plyr/plyr.polyfilled.js',
82 + array(),
83 + '3.7.8',
84 + array( 'strategy' => 'defer' )
85 + );
86 +
87 + wp_register_script(
88 + AYG_SLUG . '-public',
89 + AYG_URL . 'public/assets/js/public.min.js',
90 + $deps,
91 + AYG_VERSION,
92 + array( 'strategy' => 'defer' )
93 + );
94 +
77 95 $scroll_top_offset = ( isset( $gallery_settings['scroll_top_offset'] ) && ! empty( $gallery_settings['scroll_top_offset'] ) ) ? (int) $gallery_settings['scroll_top_offset'] : 10;
78 96 $scroll_top_offset = apply_filters( 'ayg_gallery_scrolltop_offset', $scroll_top_offset ); // Backward compatibility to 2.4.3
79 97 $scroll_top_offset = apply_filters( 'ayg_gallery_scroll_top_offset', $scroll_top_offset );
80 98
@@ -81,15 +99,13 @@
81 99 $show_more_label = ! empty( $strings_settings['show_more_label'] ) ? sanitize_text_field( $strings_settings['show_more_label'] ) : __( 'Show More', 'automatic-youtube-gallery' );
82 100 $show_less_label = ! empty( $strings_settings['show_less_label'] ) ? sanitize_text_field( $strings_settings['show_less_label'] ) : __( 'Show Less', 'automatic-youtube-gallery' );
83 101
84 102 $script_args = array(
85 - 'plugin_url' => AYG_URL,
86 - 'plugin_version' => AYG_VERSION,
87 103 'ajax_url' => admin_url( 'admin-ajax.php' ),
88 104 'ajax_nonce' => wp_create_nonce( 'ayg_ajax_nonce' ),
89 105 'current_page_url' => get_permalink(),
90 106 'current_gallery_id' => get_query_var( 'ayg_gallery_id' ),
91 - 'player_type' => $player_type,
107 + 'player_type' => isset( $player_settings['player_type'] ) ? sanitize_text_field( $player_settings['player_type'] ) : 'youtube',
92 108 'player_color' => isset( $player_settings['player_color'] ) ? sanitize_text_field( $player_settings['player_color'] ) : '#00b3ff',
93 109 'privacy_enhanced_mode' => isset( $player_settings['privacy_enhanced_mode'] ) ? (int) $player_settings['privacy_enhanced_mode'] : 0,
94 110 'origin' => '',
95 111 'cookieconsent' => 0,
@@ -112,33 +128,8 @@
112 128 $script_args['cookieconsent_button_label'] = esc_html( $privacy_settings['button_label'] );
113 129 }
114 130 }
115 131
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 132 wp_localize_script(
142 133 AYG_SLUG . '-public',
143 134 'ayg_config',
144 135 $script_args
@@ -150,44 +141,16 @@
150 141 array( 'jquery' ),
151 142 AYG_VERSION,
152 143 array( 'strategy' => 'defer' )
153 144 );
154 -
155 - // Enqueue Scripts
156 - if ( ! empty( $general_settings['force_load_assets']['js'] ) ) {
157 - wp_enqueue_script( AYG_SLUG . '-public' );
158 - }
159 145 }
160 146
161 147 /**
162 - * Enqueue block assets inside the block editor (iframe).
148 + * Enqueue Gutenberg block assets for backend editor.
163 149 *
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 150 * @since 1.6.1
188 151 */
189 - public function enqueue_editor_assets() {
152 + public function enqueue_block_editor_assets() {
190 153 // Styles
191 154 $this->register_styles();
192 155 wp_enqueue_style( AYG_SLUG . '-public' );
193 156
@@ -216,95 +179,28 @@
216 179
217 180 /**
218 181 * Load more videos.
219 182 *
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 183 * @since 1.0.0
225 184 */
226 185 public function ajax_callback_load_videos() {
227 186 // Security check
228 - check_ajax_referer( 'ayg_ajax_nonce', 'security' );
187 + check_ajax_referer( 'ayg_ajax_nonce', 'security' );
229 188
230 189 // Proceed safe
231 - $json = array();
232 - $attributes = array_map( 'sanitize_text_field', $_POST );
190 + $json = array();
191 + $attributes = array_map( 'sanitize_text_field', $_POST );
192 + $source_type = $attributes['type'];
233 193
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 194 $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
195 + 'uid' => $attributes['uid'],
196 + 'type' => $source_type,
197 + 'src' => $attributes['src'],
198 + 'order' => $attributes['order'], // works only when type=search
199 + 'limit' => (int) $attributes['limit'],
200 + 'maxResults' => (int) $attributes['per_page'],
201 + 'cache' => (int) $attributes['cache'],
202 + 'pageToken' => $attributes['pageToken']
307 203 );
308 204
309 205 if ( ! empty( $attributes['searchTerm'] ) ) {
310 206 $api_params['searchTerm'] = $attributes['searchTerm'];
@@ -315,15 +211,8 @@
315 211
316 212 if ( ! isset( $response->error ) ) {
317 213 if ( isset( $response->page_info ) ) {
318 214 $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 215 $json['message'] = sprintf(
327 216 _n( '%s video found matching your query.', '%s videos found matching your query.', $json['videos_found'], 'automatic-youtube-gallery' ),
328 217 number_format_i18n( $json['videos_found'] )
329 218 );
@@ -330,9 +219,9 @@
330 219 }
331 220
332 221 if ( isset( $response->videos ) ) {
333 222 $videos = $response->videos;
334 - $columns = isset( $attributes['columns'] ) ? min( 12, max( 1, (int) $attributes['columns'] ) ) : 3;
223 + $columns = (int) $attributes['columns'];
335 224
336 225 ob_start();
337 226 foreach ( $videos as $index => $video ) {
338 227 $classes = array();
@@ -354,128 +243,8 @@
354 243 } else {
355 244 $json['message'] = $response->error_message;
356 245 wp_send_json_error( $json );
357 246 }
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 247 }
479 248
480 249 /**
481 250 * Set cookie for accepting the privacy consent.