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 | includes/functions.php +187 -970 trunk2.6.4 View file →
@@ -21,30 +21,21 @@
21 21 * @param array $args An array of gallery options.
22 22 * @return mixed
23 23 */
24 24 function ayg_build_gallery( $args ) {
25 - $general_settings = ayg_get_option( 'ayg_general_settings' );
26 - $strings_settings = ayg_get_option( 'ayg_strings_settings' );
25 + $general_settings = get_option( 'ayg_general_settings' );
26 + $strings_settings = get_option( 'ayg_strings_settings' );
27 27
28 28 global $post;
29 29
30 30 // Vars
31 - $fields = ayg_get_editor_fields();
32 - $excluded = array( 'popup' ); // Fields not part of attributes
31 + $fields = ayg_get_editor_fields();
32 + $excluded_fields = array( 'popup' ); // Fields not part of attributes
33 + $defaults = array();
33 34
34 - // Defaults not backed by an editor field (kept through shortcode_atts): internal "db" source + sort.
35 - $defaults = array(
36 - 'db' => '',
37 - 'sort_by' => 'date',
38 - 'sort_order' => 'desc',
39 - 'sort_seed' => '',
40 - 'duration_filter' => '',
41 - 'duration' => 0
42 - );
43 -
44 35 foreach ( $fields as $key => $value ) {
45 36 foreach ( $value['fields'] as $field ) {
46 - if ( in_array( $field['name'], $excluded ) ) {
37 + if ( in_array( $field['name'], $excluded_fields ) ) {
47 38 continue;
48 39 }
49 40
50 41 $defaults[ $field['name'] ] = $field['value'];
@@ -51,46 +42,8 @@
51 42 }
52 43 }
53 44
54 45 $defaults = array_merge( $defaults, (array) $strings_settings );
55 -
56 - // Gallery Builder mode: a numeric "id" loads the saved gallery config, layers its saved
57 - // settings under $args (shortcode attributes still win, except the source), and switches
58 - // the source to the internal "db" type so the existing build pipeline serves it from the DB.
59 - if ( isset( $args['id'] ) && is_numeric( $args['id'] ) && (int) $args['id'] > 0 ) {
60 - $config = ayg_get_gallery( (int) $args['id'] );
61 -
62 - if ( ! $config ) {
63 - return sprintf( '<div class="ayg ayg-error">%s</div>', esc_html__( 'Gallery not found.', 'automatic-youtube-gallery' ) );
64 - }
65 -
66 - $config_params = json_decode( (string) $config->params, true );
67 - if ( ! is_array( $config_params ) ) {
68 - $config_params = array();
69 - }
70 -
71 - // Saved settings act as defaults; explicit shortcode attributes override them.
72 - $args = array_merge( $config_params, $args );
73 -
74 - // The source always comes from the config and can't be overridden by the shortcode.
75 - // Livestream, search and single video query the live API at display time (nothing is
76 - // imported/stored); everything else renders from the DB. ayg_get_gallery_source() owns that
77 - // mapping so the public AJAX endpoint resolves the same source from the same saved row.
78 - $source = ayg_get_gallery_source( $config );
79 -
80 - $args['type'] = $source['type'];
81 - $args[ $source['field'] ] = $source['src'];
82 -
83 - // Single video + popup mode → force the popup theme, mirroring the block and widget (which
84 - // apply this before calling ayg_build_gallery()).
85 - if ( 'video' === $config->source_type && ! empty( $args['popup'] ) ) {
86 - $args['theme'] = 'popup';
87 - }
88 -
89 - // Gallery Builder relationships are keyed by the config ID as a string.
90 - $args['uid'] = strval( $config->id );
91 - }
92 -
93 46 $attributes = shortcode_atts( $defaults, $args, 'automatic_youtube_gallery' );
94 47
95 48 $attributes['post_id'] = 0;
96 49 if ( isset( $post->ID ) ) {
@@ -106,22 +59,14 @@
106 59 if ( empty( $attributes['limit'] ) ) {
107 60 $attributes['limit'] = 500;
108 61 }
109 62
110 - $source_type = sanitize_text_field( $attributes['type'] );
111 -
112 - // Videos per page. Database (Gallery Builder) galleries read from our own table, so 0 means
113 - // "show all videos on a single page" (no pagination). The live-API sources (search, livestream)
114 - // stay capped at 50 because that is YouTube's maximum number of results per request.
115 - if ( 'db' === $source_type ) {
116 - $attributes['per_page'] = max( 0, (int) $attributes['per_page'] );
117 - } else {
118 - $attributes['per_page'] = min( 50, (int) $attributes['per_page'] );
119 - if ( empty( $attributes['per_page'] ) ) {
120 - $attributes['per_page'] = 50;
121 - }
63 + $attributes['per_page'] = min( 50, (int) $attributes['per_page'] );
64 + if ( empty( $attributes['per_page'] ) ) {
65 + $attributes['per_page'] = 50;
122 66 }
123 67
68 + $source_type = sanitize_text_field( $attributes['type'] );
124 69 if ( 'livestream' == $source_type ) {
125 70 $attributes['livestream'] = $attributes['channel'];
126 71 }
127 72
@@ -131,12 +76,8 @@
131 76 }
132 77
133 78 $source_url = sanitize_text_field( $attributes[ $source_type ] );
134 79
135 - if ( isset( $args['id'] ) && ! empty( $args['id'] ) ) {
136 - $attributes['id'] = absint( $args['id'] );
137 - }
138 -
139 80 if ( isset( $args['uid'] ) && ! empty( $args['uid'] ) ) {
140 81 $attributes['uid'] = sanitize_text_field( $args['uid'] );
141 82 } else {
142 83 $attributes['uid'] = md5( $source_type . $source_url );
@@ -149,47 +90,17 @@
149 90 if ( isset( $args['deprecated_uid'] ) && ! empty( $args['deprecated_uid'] ) ) {
150 91 $deprecated_uid = sanitize_text_field( $args['deprecated_uid'] );
151 92 }
152 93
153 - // Deeplinked video (premium): resolve before the query so DB-served galleries can pin it as
154 - // the first item at the SQL level, keeping the per-page count exact. API-served (legacy)
155 - // galleries keep the old prepend behavior below.
156 - $gallery_id_from_url = get_query_var( 'ayg_gallery_id' );
157 - $video_id_from_url = get_query_var( 'ayg_video_id' );
158 - $deeplinked_video = false;
159 -
160 - if ( ! empty( $video_id_from_url ) && ( $attributes['uid'] == $gallery_id_from_url || $deprecated_uid == $gallery_id_from_url ) ) {
161 - $deeplinked_video = ayg_db_get_video( $video_id_from_url );
162 - }
163 -
164 - if ( ! $deeplinked_video ) {
165 - $video_id_from_url = '';
166 - }
167 -
168 - $featured_video_id = ( 'db' === $source_type && ! empty( $video_id_from_url ) ) ? sanitize_text_field( $video_id_from_url ) : '';
169 -
170 - // Random ordering: generate a per-render seed (once) so RAND(seed) stays stable across this
171 - // gallery's own pagination/search AJAX calls. A seed hardcoded in the shortcode is respected.
172 - if ( 'random' === $attributes['sort_by'] && empty( $attributes['sort_seed'] ) ) {
173 - $attributes['sort_seed'] = wp_rand( 1, 2147483647 );
174 - }
175 -
176 94 // Get Videos
177 95 $api_params = array(
178 - 'uid' => $attributes['uid'],
179 - 'type' => $source_type,
180 - 'src' => $source_url,
181 - 'store' => true, // Trusted context: the source comes from the rendered shortcode, not from a visitor's request.
182 - 'featured_video_id' => $featured_video_id, // Works only when type = "db".
183 - 'order' => sanitize_text_field( $attributes['order'] ), // Works only when type = "search".
184 - 'sort_by' => sanitize_key( $attributes['sort_by'] ), // Works only when type = "db".
185 - 'sort_order' => sanitize_key( $attributes['sort_order'] ), // Works only when type = "db".
186 - 'sort_seed' => absint( $attributes['sort_seed'] ), // Works only when type = "db" + sort_by = "random".
187 - 'duration_filter' => sanitize_key( $attributes['duration_filter'] ), // Works only when type = "db".
188 - 'duration' => absint( $attributes['duration'] ), // Works only when type = "db".
189 - 'limit' => $attributes['limit'], // Works only when type = "search".
190 - 'maxResults' => $attributes['per_page'],
191 - 'cache' => (int) $attributes['cache']
96 + 'uid' => $attributes['uid'],
97 + 'type' => $source_type,
98 + 'src' => $source_url,
99 + 'order' => sanitize_text_field( $attributes['order'] ), // Works only when type = "search".
100 + 'limit' => $attributes['limit'], // Works only when type = "search".
101 + 'maxResults' => $attributes['per_page'],
102 + 'cache' => (int) $attributes['cache']
192 103 );
193 104
194 105 $api_params = apply_filters( 'ayg_youtube_api_request_params', $api_params, $args );
195 106
@@ -199,9 +110,9 @@
199 110 // Process output
200 111 if ( ! isset( $response->error ) ) {
201 112 // Store Gallery ID
202 113 if ( $attributes['post_id'] > 0 && isset( $attributes['deeplinking'] ) && 1 == $attributes['deeplinking'] ) {
203 - $pages = ayg_get_option( 'ayg_gallery_page_ids' );
114 + $pages = get_option( 'ayg_gallery_page_ids', array() );
204 115 $page_id = $attributes['post_id'];
205 116
206 117 if ( ! in_array( $page_id, $pages ) ) {
207 118 $pages[] = $page_id;
@@ -209,24 +120,25 @@
209 120 }
210 121 }
211 122
212 123 // Gallery
213 - $videos = array();
124 + $videos = array();
214 125
215 - if ( ! empty( $video_id_from_url ) ) {
216 - // DB-served galleries pin the deeplinked video via the SQL query itself, so the result
217 - // set already starts with it. Fall back to prepending it only when the pin couldn't
218 - // apply: API-served (legacy) sources, or the video is no longer part of this gallery's
219 - // result set (removed from the gallery, or excluded by a duration filter).
220 - $is_pinned_by_sql = ! empty( $api_params['featured_video_id'] ) && ! empty( $response->videos ) && $response->videos[0]->id == $video_id_from_url;
126 + $gallery_id_from_url = get_query_var( 'ayg_gallery_id' );
127 + $video_id_from_url = get_query_var( 'ayg_video_id' );
221 128
222 - if ( ! $is_pinned_by_sql ) {
223 - $videos[] = $deeplinked_video;
129 + if ( $attributes['uid'] == $gallery_id_from_url || $deprecated_uid == $gallery_id_from_url ) {
130 + $video = ayg_db_get_video( $video_id_from_url );
131 +
132 + if ( $video ) {
133 + $videos[] = $video;
134 + } else {
135 + $video_id_from_url = '';
224 136 }
225 137 }
226 -
138 +
227 139 if ( isset( $response->videos ) ) {
228 - if ( ! empty( $videos ) ) {
140 + if ( ! empty( $video_id_from_url ) ) {
229 141 foreach ( $response->videos as $video ) {
230 142 if ( $video->id != $video_id_from_url ) {
231 143 $videos[] = $video;
232 144 }
@@ -237,17 +149,9 @@
237 149 }
238 150
239 151 // Pagination
240 152 if ( isset( $response->page_info ) ) {
241 - $page_info = $response->page_info;
242 -
243 - // Sign the page tokens on their way out to the browser so the public AJAX endpoint can
244 - // tell the tokens this site issued from ones a visitor made up.
245 - if ( ayg_page_token_is_signed( $source_type ) ) {
246 - $page_info = ayg_sign_page_tokens( $page_info, $attributes['uid'] );
247 - }
248 -
249 - $attributes = array_merge( $attributes, $api_params, $page_info );
153 + $attributes = array_merge( $attributes, $api_params, $response->page_info );
250 154 }
251 155
252 156 // Theme
253 157 $theme = 'classic';
@@ -309,215 +213,63 @@
309 213 global $wpdb;
310 214
311 215 require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
312 216 $charset_collate = $wpdb->get_charset_collate();
313 -
217 +
314 218 $videos_table = $wpdb->prefix . 'ayg_videos';
315 - $rel_table = $wpdb->prefix . 'ayg_gallery_relationships';
316 - $old_table = $wpdb->prefix . 'ayg_galleries';
317 219
318 - // Rename wp_ayg_galleries (old pivot) → wp_ayg_gallery_relationships.
319 - // Guard: only when the old table exists AND the new name does not yet exist AND
320 - // the old table is the pivot (identified by having a video_id column).
321 - $old_exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $old_table ) ) ) === $old_table;
322 - $new_not_exist = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $rel_table ) ) ) !== $rel_table;
220 + // Check if the videos table exists
221 + $query = $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $videos_table ) );
323 222
324 - if ( $old_exists && $new_not_exist ) {
325 - $is_old_pivot = ! empty( $wpdb->get_results( "SHOW COLUMNS FROM `$old_table` LIKE 'video_id'" ) );
326 -
327 - if ( $is_old_pivot ) {
328 - $renamed = $wpdb->query( "RENAME TABLE `$old_table` TO `$rel_table`" );
329 -
330 - if ( false === $renamed ) {
331 - // Rename failed (insufficient DB privilege on some hosts).
332 - // This table holds auto-generated cache data, not user content — safe to drop.
333 - $wpdb->query( "DROP TABLE IF EXISTS `$old_table`" );
334 - }
335 - }
336 - }
337 -
338 - // Rename columns in wp_ayg_videos (NUM → id, id → video_id).
339 - // Each ALTER is guarded so the block is idempotent on re-runs.
340 - $videos_table_exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $videos_table ) ) ) === $videos_table;
341 -
342 - if ( $videos_table_exists ) {
343 - // Rename 'id' → 'video_id' first. Must happen before NUM → id to avoid a duplicate
344 - // column name error: if NUM were renamed to 'id' while the varchar 'id' still exists,
345 - // MySQL would reject it with "Duplicate column name 'id'".
346 - $old_id_col = $wpdb->get_row( "SHOW COLUMNS FROM `$videos_table` LIKE 'id'" );
347 -
348 - if ( $old_id_col && false !== strpos( strtolower( $old_id_col->Type ), 'varchar' ) ) {
349 - $wpdb->query( "ALTER TABLE `$videos_table` CHANGE `id` `video_id` varchar(100) NOT NULL" );
350 - }
351 -
352 - // Now safe to rename NUM → id (no column named 'id' exists at this point).
353 - $has_num = ! empty( $wpdb->get_results( "SHOW COLUMNS FROM `$videos_table` LIKE 'NUM'" ) );
354 -
355 - if ( $has_num ) {
356 - $wpdb->query( "ALTER TABLE `$videos_table` CHANGE `NUM` `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT" );
357 - }
358 -
359 - // Remove duplicate rows using the new column names (idempotent).
223 + if ( $wpdb->get_var( $query ) === $videos_table ) {
224 + // Find and remove duplicate video IDs
360 225 $wpdb->query(
361 226 "DELETE v1 FROM $videos_table v1
362 227 INNER JOIN $videos_table v2
363 - ON v1.video_id = v2.video_id AND v1.id > v2.id"
228 + ON v1.id = v2.id AND v1.NUM > v2.NUM"
364 229 );
365 230 }
366 231
367 - // Create/update wp_ayg_videos — dbDelta adds the 2 new columns on existing installs.
232 + // Create the videos table
368 233 $sql = "CREATE TABLE $videos_table (
369 - id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
370 - video_id varchar(100) NOT NULL,
234 + NUM bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
235 + id varchar(100) NOT NULL,
371 236 title text NOT NULL,
372 237 description text NOT NULL,
373 238 thumbnails text NOT NULL,
374 239 duration varchar(25) NOT NULL,
375 - duration_seconds int(11) UNSIGNED NOT NULL DEFAULT 0,
376 - video_type varchar(20) NOT NULL DEFAULT 'none',
377 240 status varchar(100) NOT NULL,
378 241 published_at varchar(100) NOT NULL,
379 242 published_at_datetime datetime NULL,
380 - PRIMARY KEY (id),
381 - UNIQUE KEY ayg_unique_video_id (video_id),
382 - INDEX ayg_idx_published_at_datetime (published_at_datetime)
243 + PRIMARY KEY (NUM),
244 + UNIQUE KEY ayg_unique_video_id (id)
383 245 ) $charset_collate;";
384 246
385 247 dbDelta( $sql );
386 248
387 - // Fallback: add UNIQUE KEY if dbDelta did not create it (edge case on existing installs).
249 + // Add UNIQUE KEY if it is not added
388 250 $existing_keys = $wpdb->get_results( "SHOW KEYS FROM $videos_table WHERE Key_name = 'ayg_unique_video_id'" );
389 -
251 +
390 252 if ( empty( $existing_keys ) ) {
391 - $wpdb->query( "ALTER TABLE $videos_table ADD UNIQUE KEY ayg_unique_video_id (video_id)" );
253 + $wpdb->query( "ALTER TABLE $videos_table ADD UNIQUE KEY ayg_unique_video_id (id)" );
392 254 }
393 255
394 - // Fallback: add INDEX on published_at_datetime if missing (existing installs).
395 - $existing_dt_index = $wpdb->get_results( "SHOW KEYS FROM $videos_table WHERE Key_name = 'ayg_idx_published_at_datetime'" );
396 -
397 - if ( empty( $existing_dt_index ) ) {
398 - $wpdb->query( "ALTER TABLE $videos_table ADD INDEX ayg_idx_published_at_datetime (published_at_datetime)" );
399 - }
256 + // Create the galleries table
257 + $galleries_table = $wpdb->prefix . 'ayg_galleries';
400 258
401 - // Rename NUM → id in wp_ayg_gallery_relationships (only if rename succeeded above).
402 - // If the table was recreated fresh by dbDelta below, it already has 'id' — no ALTER needed.
403 - $rel_table_exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $rel_table ) ) ) === $rel_table;
404 -
405 - if ( $rel_table_exists ) {
406 - $has_num_in_rel = ! empty( $wpdb->get_results( "SHOW COLUMNS FROM `$rel_table` LIKE 'NUM'" ) );
407 -
408 - if ( $has_num_in_rel ) {
409 - $wpdb->query( "ALTER TABLE `$rel_table` CHANGE `NUM` `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT" );
410 - }
411 - }
412 -
413 - // Create/update wp_ayg_gallery_relationships (fresh creation if the rename fallback dropped the old table).
414 - $sql = "CREATE TABLE $rel_table (
415 - id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
259 + $sql = "CREATE TABLE $galleries_table (
260 + NUM bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
416 261 video_id varchar(100) NOT NULL,
417 262 gallery_id varchar(100) NOT NULL,
418 - synced_at datetime NULL,
419 - PRIMARY KEY (id),
263 + PRIMARY KEY (NUM),
420 264 UNIQUE KEY ayg_unique_video_gallery (video_id, gallery_id),
421 265 INDEX ayg_idx_gallery_id (gallery_id)
422 266 ) $charset_collate;";
423 267
424 268 dbDelta( $sql );
425 -
426 - // Fallback: add the synced_at column if dbDelta did not (existing installs).
427 - $has_synced_at = ! empty( $wpdb->get_results( "SHOW COLUMNS FROM `$rel_table` LIKE 'synced_at'" ) );
428 -
429 - if ( ! $has_synced_at ) {
430 - $wpdb->query( "ALTER TABLE `$rel_table` ADD `synced_at` datetime NULL" );
431 - }
432 -
433 - // Create new wp_ayg_galleries entity table (one row per saved gallery config).
434 - $galleries_table = $wpdb->prefix . 'ayg_galleries';
435 -
436 - $sql = "CREATE TABLE $galleries_table (
437 - id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
438 - title text NOT NULL,
439 - source_type varchar(50) NOT NULL DEFAULT 'channel',
440 - source_value text NOT NULL,
441 - params longtext NOT NULL,
442 - import_status varchar(20) NOT NULL DEFAULT 'idle',
443 - import_error text NULL,
444 - import_log longtext NULL,
445 - last_imported_at datetime NULL,
446 - next_import_at datetime NULL,
447 - video_count int(11) UNSIGNED NOT NULL DEFAULT 0,
448 - created_at datetime NOT NULL,
449 - updated_at datetime NOT NULL,
450 - PRIMARY KEY (id),
451 - INDEX idx_import (next_import_at, import_status)
452 - ) $charset_collate;";
453 -
454 - dbDelta( $sql );
455 269 }
456 270
457 271 /**
458 - * Check whether any videos are linked to the given gallery UID.
459 - *
460 - * Lets the public AJAX endpoint tell a gallery this site actually displays from a UID a visitor
461 - * simply made up: a gallery that has been rendered even once always has its videos in the
462 - * relationship table, an invented UID never does.
463 - *
464 - * @since 2.9.0
465 - * @param string $gallery_id Gallery UID.
466 - * @return bool True when at least one video is linked to the UID.
467 - */
468 -function ayg_db_gallery_has_videos( $gallery_id ) {
469 - global $wpdb;
470 -
471 - $gallery_id = (string) $gallery_id;
472 -
473 - if ( '' === $gallery_id ) {
474 - return false;
475 - }
476 -
477 - $found = $wpdb->get_var(
478 - $wpdb->prepare(
479 - "SELECT 1 FROM {$wpdb->prefix}ayg_gallery_relationships WHERE gallery_id = %s LIMIT 1",
480 - $gallery_id
481 - )
482 - );
483 -
484 - return ! empty( $found );
485 -}
486 -
487 -/**
488 - * Get a single video record from our custom database table "{$wpdb->prefix}ayg_videos"
489 - *
490 - * @since 2.1.0
491 - * @param string $video_id YouTube Video ID.
492 - * @return mixed
493 - */
494 -function ayg_db_get_video( $video_id ) {
495 - global $wpdb;
496 -
497 - $cache_key = 'ayg_'. $video_id;
498 - $row = wp_cache_get( $cache_key );
499 -
500 - if ( false === $row ) {
501 - $query = $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}ayg_videos WHERE video_id = %s", $video_id );
502 - $row = $wpdb->get_row( $query );
503 -
504 - if ( $row ) {
505 - if ( ! empty( $row->thumbnails ) ) {
506 - $row->thumbnails = ayg_maybe_unserialize( $row->thumbnails );
507 - }
508 -
509 - // Backward compat: templates reference $video->id as the YouTube video ID.
510 - $row->id = $row->video_id;
511 -
512 - wp_cache_set( $cache_key, $row );
513 - }
514 - }
515 -
516 - return $row;
517 -}
518 -
519 -/**
520 272 * Store videos in our custom database table "{$wpdb->prefix}ayg_videos"
521 273 *
522 274 * @since 2.1.0
523 275 * @param object $data YouTube API response object.
@@ -537,52 +289,31 @@
537 289 }
538 290
539 291 global $wpdb;
540 292
541 - $items = $data->items;
542 - $gallery_id = isset( $attributes['uid'] ) ? $attributes['uid'] : '';
543 - $source_type = isset( $attributes['type'] ) ? $attributes['type'] : 'videos';
544 - $store_gallery = ! empty( $gallery_id ) && 'livestream' !== $source_type;
545 - $exclude = ( isset( $attributes['exclude'] ) && is_array( $attributes['exclude'] ) ) ? $attributes['exclude'] : array();
293 + $items = $data->items;
546 294
547 - // Only videos.list responses carry contentDetails.duration and snippet.liveBroadcastContent.
548 - // For other responses (playlistItems.list, search.list) the duration_seconds / video_type
549 - // columns are left out entirely so existing values are never clobbered with defaults.
550 - $has_video_details = isset( $data->kind ) && 'youtube#videoListResponse' === $data->kind;
551 -
552 - $videos_table = $wpdb->prefix . 'ayg_videos';
553 - $galleries_table = $wpdb->prefix . 'ayg_gallery_relationships';
554 -
555 - $video_placeholders = array();
556 - $video_values = array();
557 - $gallery_placeholders = array();
558 - $gallery_values = array();
559 -
560 - foreach ( $items as $item ) {
295 + // Store Videos
296 + foreach ( $items as $item ) {
561 297 $row = array();
562 298
563 299 // Video ID
564 - $row['video_id'] = '';
300 + $row['id'] = '';
565 301
566 302 if ( isset( $item->snippet->resourceId ) && isset( $item->snippet->resourceId->videoId ) ) {
567 - $row['video_id'] = $item->snippet->resourceId->videoId;
303 + $row['id'] = $item->snippet->resourceId->videoId;
568 304 } elseif ( isset( $item->contentDetails ) && isset( $item->contentDetails->videoId ) ) {
569 - $row['video_id'] = $item->contentDetails->videoId;
305 + $row['id'] = $item->contentDetails->videoId;
570 306 } elseif ( isset( $item->id ) && isset( $item->id->videoId ) ) {
571 - $row['video_id'] = $item->id->videoId;
307 + $row['id'] = $item->id->videoId;
572 308 } elseif ( isset( $item->id ) ) {
573 - $row['video_id'] = $item->id;
574 - }
309 + $row['id'] = $item->id;
310 + }
575 311
576 - if ( empty( $row['video_id'] ) ) {
312 + if ( empty( $row['id'] ) ) {
577 313 continue;
578 314 }
579 315
580 - // Skip videos on the gallery's exclude list (kept out of both tables)
581 - if ( ayg_is_video_excluded( $row['video_id'], $exclude ) ) {
582 - continue;
583 - }
584 -
585 316 // Video title
586 317 $row['title'] = $item->snippet->title;
587 318
588 319 // Video description
@@ -591,40 +322,29 @@
591 322 // Video thumbnails
592 323 $row['thumbnails'] = '';
593 324 if ( isset( $item->snippet->thumbnails ) ) {
594 325 $row['thumbnails'] = serialize( $item->snippet->thumbnails );
595 - }
326 + }
596 327
597 328 // Video duration
598 329 $row['duration'] = '';
599 330 if ( isset( $item->contentDetails ) && isset( $item->contentDetails->duration ) ) {
600 331 $row['duration'] = $item->contentDetails->duration;
601 - }
332 + }
602 333
603 - if ( $has_video_details ) {
604 - $row['duration_seconds'] = ayg_parse_duration_seconds( $row['duration'] );
605 -
606 - // Raw liveBroadcastContent value ('live', 'upcoming', 'none'); shorts are not
607 - // classified at import time — filter by duration_seconds <= 60 at query time.
608 - $row['video_type'] = 'none';
609 - if ( ! empty( $item->snippet->liveBroadcastContent ) ) {
610 - $row['video_type'] = $item->snippet->liveBroadcastContent;
611 - }
612 - }
613 -
614 334 // Video status
615 335 $row['status'] = 'private';
616 -
336 +
617 337 if ( isset( $item->status ) && ( 'public' == $item->status->privacyStatus || 'unlisted' == $item->status->privacyStatus ) ) {
618 - $row['status'] = 'public';
338 + $row['status'] = 'public';
619 339 }
620 340
621 341 if ( isset( $item->snippet->status ) && ( 'public' == $item->snippet->status->privacyStatus || 'unlisted' == $item->snippet->status->privacyStatus ) ) {
622 - $row['status'] = 'public';
342 + $row['status'] = 'public';
623 343 }
624 344
625 345 if ( 'youtube#searchResult' == $item->kind ) {
626 - $row['status'] = 'public';
346 + $row['status'] = 'public';
627 347 }
628 348
629 349 // Video publish date
630 350 $row['published_at'] = $item->snippet->publishedAt;
@@ -629,168 +349,101 @@
629 349 // Video publish date
630 350 $row['published_at'] = $item->snippet->publishedAt;
631 351
632 352 $datetime = new DateTime( $item->snippet->publishedAt );
633 - $row['published_at_datetime'] = date_format( $datetime, 'Y-m-d H:i:s' );
353 + $row['published_at_datetime'] = date_format( $datetime, 'Y-m-d H:i:s' );
634 354
635 - // Collect for bulk insert
636 - if ( $has_video_details ) {
637 - $video_placeholders[] = '(%s, %s, %s, %s, %s, %d, %s, %s, %s, %s)';
355 + // Store the video
356 + $videos_table = $wpdb->prefix . 'ayg_videos';
638 357
639 - array_push(
640 - $video_values,
641 - $row['video_id'],
642 - $row['title'],
643 - $row['description'],
644 - $row['thumbnails'],
645 - $row['duration'],
646 - $row['duration_seconds'],
647 - $row['video_type'],
648 - $row['status'],
649 - $row['published_at'],
650 - $row['published_at_datetime']
651 - );
652 - } else {
653 - $video_placeholders[] = '(%s, %s, %s, %s, %s, %s, %s, %s)';
358 + $query = $wpdb->prepare(
359 + "INSERT INTO $videos_table (id, title, description, thumbnails, duration, status, published_at, published_at_datetime)
360 + VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
361 + ON DUPLICATE KEY UPDATE
362 + title = VALUES(title),
363 + description = VALUES(description),
364 + thumbnails = VALUES(thumbnails),
365 + duration = VALUES(duration),
366 + status = VALUES(status),
367 + published_at = VALUES(published_at),
368 + published_at_datetime = VALUES(published_at_datetime)",
369 + $row['id'],
370 + $row['title'],
371 + $row['description'],
372 + $row['thumbnails'],
373 + $row['duration'],
374 + $row['status'],
375 + $row['published_at'],
376 + $row['published_at_datetime']
377 + );
654 378
655 - array_push(
656 - $video_values,
657 - $row['video_id'],
658 - $row['title'],
659 - $row['description'],
660 - $row['thumbnails'],
661 - $row['duration'],
662 - $row['status'],
663 - $row['published_at'],
664 - $row['published_at_datetime']
665 - );
666 - }
379 + $wpdb->query( $query );
667 380
668 - if ( $store_gallery ) {
669 - $gallery_placeholders[] = '(%s, %s)';
670 - array_push( $gallery_values, $row['video_id'], $gallery_id );
671 - }
672 - }
381 + // Store the gallery association
382 + $gallery_id = isset( $attributes['uid'] ) ? $attributes['uid'] : '';
383 + $source_type = isset( $attributes['type'] ) ? $attributes['type'] : 'videos';
673 384
674 - // Bulk insert videos (2 queries total instead of N×2)
675 - if ( ! empty( $video_placeholders ) ) {
676 - if ( $has_video_details ) {
677 - $sql = "INSERT INTO $videos_table (video_id, title, description, thumbnails, duration, duration_seconds, video_type, status, published_at, published_at_datetime)
678 - VALUES " . implode( ', ', $video_placeholders ) . "
679 - ON DUPLICATE KEY UPDATE
680 - title = VALUES(title),
681 - description = VALUES(description),
682 - thumbnails = VALUES(thumbnails),
683 - duration = VALUES(duration),
684 - duration_seconds = VALUES(duration_seconds),
685 - video_type = VALUES(video_type),
686 - status = VALUES(status),
687 - published_at = VALUES(published_at),
688 - published_at_datetime = VALUES(published_at_datetime)";
689 - } else {
690 - $sql = "INSERT INTO $videos_table (video_id, title, description, thumbnails, duration, status, published_at, published_at_datetime)
691 - VALUES " . implode( ', ', $video_placeholders ) . "
692 - ON DUPLICATE KEY UPDATE
693 - title = VALUES(title),
694 - description = VALUES(description),
695 - thumbnails = VALUES(thumbnails),
696 - duration = VALUES(duration),
697 - status = VALUES(status),
698 - published_at = VALUES(published_at),
699 - published_at_datetime = VALUES(published_at_datetime)";
700 - }
385 + if ( ! empty( $gallery_id ) && ! in_array( $source_type, array( 'video', 'livestream' ) ) ) {
386 + $galleries_table = $wpdb->prefix . 'ayg_galleries';
701 387
702 - $wpdb->query( $wpdb->prepare( $sql, $video_values ) );
703 - }
388 + $query = $wpdb->prepare(
389 + "INSERT IGNORE INTO $galleries_table (video_id, gallery_id) VALUES (%s, %s)",
390 + $row['id'],
391 + $gallery_id
392 + );
704 393
705 - // Bulk insert gallery relationships (2 queries total instead of N×2)
706 - if ( ! empty( $gallery_placeholders ) ) {
707 - $sql = "INSERT IGNORE INTO $galleries_table (video_id, gallery_id) VALUES " . implode( ', ', $gallery_placeholders );
708 - $wpdb->query( $wpdb->prepare( $sql, $gallery_values ) );
394 + $wpdb->query( $query );
395 + }
709 396 }
710 397 }
711 398
712 399 /**
713 - * Dump our plugin transients.
400 + * Get a single video record from our custom database table "{$wpdb->prefix}ayg_videos"
714 401 *
715 - * @since 2.1.0
716 - * @param string $uid Gallery uid to target. Empty string flushes all plugin transients.
402 + * @since 2.1.0
403 + * @param string $video_id YouTube Video ID.
404 + * @return mixed
717 405 */
718 -function ayg_delete_cache( $uid = '' ) {
719 - $uid = (string) $uid;
720 - $keys = (array) ayg_get_option( 'ayg_transient_keys' );
406 +function ayg_db_get_video( $video_id ) {
407 + global $wpdb;
721 408
722 - // Targeted: delete only the keys prefixed with this gallery's uid, keep the rest in the registry.
723 - if ( '' !== $uid ) {
724 - $prefix = 'ayg_' . $uid . '_';
725 - $remaining = array();
409 + $cache_key = 'ayg_'. $video_id;
410 + $row = wp_cache_get( $cache_key );
726 411
727 - foreach ( $keys as $key ) {
728 - if ( 0 === strpos( $key, $prefix ) ) {
729 - delete_transient( $key );
730 - } else {
731 - $remaining[] = $key;
412 + if ( false === $row ) {
413 + $query = $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}ayg_videos WHERE id = %s", $video_id );
414 + $row = $wpdb->get_row( $query );
415 +
416 + if ( $row ) {
417 + if ( ! empty( $row->thumbnails ) ) {
418 + $row->thumbnails = unserialize( $row->thumbnails );
732 419 }
733 - }
734 420
735 - update_option( 'ayg_transient_keys', $remaining, false );
736 - return;
421 + wp_cache_set( $cache_key, $row );
422 + }
737 423 }
738 424
739 - // Full flush: every transient, plus the cached gallery page-id lookup.
740 - delete_option( 'ayg_gallery_page_ids' );
741 -
742 - foreach ( $keys as $key ) {
743 - delete_transient( $key );
744 - }
745 -
746 - // Reset our DB value (autoload=no: not needed on every page load)
747 - update_option( 'ayg_transient_keys', array(), false );
425 + return $row;
748 426 }
749 427
750 428 /**
751 - * Remove a gallery's excluded videos from the relationship table.
429 + * Dump our plugin transients.
752 430 *
753 - * @since 2.8.0
754 - * @param int $gallery_id Gallery ID.
755 - * @param array $exclude Exclude entries (video IDs or URLs).
756 - * @return int Number of relationships removed.
431 + * @since 2.1.0
757 432 */
758 -function ayg_delete_excluded_relationships( $gallery_id, $exclude ) {
759 - global $wpdb;
433 +function ayg_delete_cache() {
434 + delete_option( 'ayg_gallery_page_ids' );
435 +
436 + // Get the current list of transients
437 + $transient_keys = get_option( 'ayg_transient_keys', array() );
760 438
761 - $gallery_id = absint( $gallery_id );
762 -
763 - if ( $gallery_id <= 0 || empty( $exclude ) || ! is_array( $exclude ) ) {
764 - return 0;
439 + // For each key, delete that transient
440 + foreach ( $transient_keys as $key ) {
441 + delete_transient( $key );
765 442 }
766 -
767 - // Resolve the exclude entries (IDs or URLs) to video IDs.
768 - $video_ids = array();
769 -
770 - foreach ( $exclude as $entry ) {
771 - $video_id = ayg_get_youtube_video_id( $entry );
772 -
773 - if ( '' !== $video_id ) {
774 - $video_ids[] = $video_id;
775 - }
776 - }
777 -
778 - $video_ids = array_values( array_unique( $video_ids ) );
779 -
780 - if ( empty( $video_ids ) ) {
781 - return 0;
782 - }
783 -
784 - $rel_table = $wpdb->prefix . 'ayg_gallery_relationships';
785 - $placeholders = implode( ',', array_fill( 0, count( $video_ids ), '%s' ) );
786 -
787 - return (int) $wpdb->query(
788 - $wpdb->prepare(
789 - "DELETE FROM $rel_table WHERE gallery_id = %s AND video_id IN ( $placeholders )",
790 - array_merge( array( strval( $gallery_id ) ), $video_ids )
791 - )
792 - );
443 +
444 + // Reset our DB value
445 + update_option( 'ayg_transient_keys', array() );
793 446 }
794 447
795 448 /**
796 449 * Get current address bar URL.
@@ -813,14 +466,11 @@
813 466 */
814 467 function ayg_get_default_settings() {
815 468 $defaults = array(
816 469 'ayg_general_settings' => array(
817 - 'force_load_assets' => array(
818 - 'css' => 'css'
819 - ),
820 - 'api_key' => '',
821 - 'lazyload' => 0,
822 - 'development_mode' => 0
470 + 'api_key' => '',
471 + 'lazyload' => 0,
472 + 'development_mode' => 0
823 473 ),
824 474 'ayg_strings_settings' => array(
825 475 'more_button_label' => __( 'Load More', 'automatic-youtube-gallery' ),
826 476 'previous_button_label' => __( 'Previous', 'automatic-youtube-gallery' ),
@@ -854,8 +504,9 @@
854 504 'muted' => 0,
855 505 'controls' => 1,
856 506 'modestbranding' => 1,
857 507 'cc_load_policy' => 0,
508 + 'iv_load_policy' => 0,
858 509 'hl' => '',
859 510 'cc_lang_pref' => '',
860 511 'privacy_enhanced_mode' => 0,
861 512 'origin' => 0
@@ -878,12 +529,12 @@
878 529 *
879 530 * @since 1.0.0
880 531 * @return array Array of fields.
881 532 */
882 -function ayg_get_editor_fields() {
533 +function ayg_get_editor_fields() {
883 534 $fields = array(
884 535 'source' => array(
885 - 'label' => __( 'Source Configuration', 'automatic-youtube-gallery' ),
536 + 'label' => __( 'General', 'automatic-youtube-gallery' ),
886 537 'fields' => array(
887 538 array(
888 539 'name' => 'type',
889 540 'label' => __( 'Source Type', 'automatic-youtube-gallery' ),
@@ -888,9 +539,17 @@
888 539 'name' => 'type',
889 540 'label' => __( 'Source Type', 'automatic-youtube-gallery' ),
890 541 'description' => '',
891 542 'type' => 'select',
892 - 'options' => ayg_get_source_types(),
543 + 'options' => array(
544 + 'playlist' => __( 'Playlist', 'automatic-youtube-gallery' ),
545 + 'channel' => __( 'Channel', 'automatic-youtube-gallery' ),
546 + 'username' => __( 'Username', 'automatic-youtube-gallery' ),
547 + 'search' => __( 'Search Keywords', 'automatic-youtube-gallery' ),
548 + 'video' => __( 'Single Video', 'automatic-youtube-gallery' ),
549 + 'livestream' => __( 'Livestream', 'automatic-youtube-gallery' ),
550 + 'videos' => __( 'Custom Videos List', 'automatic-youtube-gallery' )
551 + ),
893 552 'value' => 'playlist',
894 553 'sanitize_callback' => 'sanitize_key'
895 554 ),
896 555 array(
@@ -897,18 +556,16 @@
897 556 'name' => 'playlist',
898 557 'label' => __( 'YouTube Playlist ID (or) URL', 'automatic-youtube-gallery' ),
899 558 'description' => sprintf( '%s: https://www.youtube.com/playlist?list=XXXXXXXXXX', __( 'Example', 'automatic-youtube-gallery' ) ),
900 559 'type' => 'url',
901 - 'required' => 1,
902 560 'value' => '',
903 561 'sanitize_callback' => 'sanitize_text_field'
904 562 ),
905 563 array(
906 564 'name' => 'channel',
907 - 'label' => __( 'YouTube Channel ID (or) a Video URL from the Channel', 'automatic-youtube-gallery' ),
565 + 'label' => __( 'YouTube Channel ID (or) a YouTube Video URL from the Channel', 'automatic-youtube-gallery' ),
908 566 'description' => sprintf( '%s: https://www.youtube.com/channel/XXXXXXXXXX', __( 'Example', 'automatic-youtube-gallery' ) ),
909 567 'type' => 'url',
910 - 'required' => 1,
911 568 'value' => '',
912 569 'sanitize_callback' => 'sanitize_text_field'
913 570 ),
914 571 array(
@@ -915,9 +572,8 @@
915 572 'name' => 'username',
916 573 'label' => __( 'YouTube Account Username', 'automatic-youtube-gallery' ),
917 574 'description' => sprintf( '%s: SanRosh', __( 'Example', 'automatic-youtube-gallery' ) ),
918 575 'type' => 'text',
919 - 'required' => 1,
920 576 'value' => '',
921 577 'sanitize_callback' => 'sanitize_text_field'
922 578 ),
923 579 array(
@@ -924,9 +580,8 @@
924 580 'name' => 'search',
925 581 'label' => __( 'Search Keywords', 'automatic-youtube-gallery' ),
926 582 'description' => sprintf( '%s: Cartoon (space:AND , -:NOT , |:OR)', __( 'Example', 'automatic-youtube-gallery' ) ),
927 583 'type' => 'text',
928 - 'required' => 1,
929 584 'value' => '',
930 585 'sanitize_callback' => 'sanitize_text_field'
931 586 ),
932 587 array(
@@ -933,9 +588,8 @@
933 588 'name' => 'video',
934 589 'label' => __( 'YouTube Video ID (or) URL', 'automatic-youtube-gallery' ),
935 590 'description' => sprintf( '%s: https://www.youtube.com/watch?v=XXXXXXXXXX', __( 'Example', 'automatic-youtube-gallery' ) ),
936 591 'type' => 'url',
937 - 'required' => 1,
938 592 'value' => '',
939 593 'sanitize_callback' => 'sanitize_text_field'
940 594 ),
941 595 array(
@@ -942,16 +596,15 @@
942 596 'name' => 'videos',
943 597 'label' => __( 'YouTube Video IDs (or) URLs', 'automatic-youtube-gallery' ),
944 598 'description' => sprintf( '%s: https://www.youtube.com/watch?v=XXXXXXXXXX', __( 'Example', 'automatic-youtube-gallery' ) ),
945 599 'type' => 'textarea',
946 - 'required' => 1,
947 600 'placeholder' => __( 'Enter one video per line', 'automatic-youtube-gallery' ),
948 601 'value' => '',
949 - 'sanitize_callback' => 'sanitize_textarea_field'
602 + 'sanitize_callback' => 'sanitize_text_field'
950 603 ),
951 604 array(
952 605 'name' => 'order',
953 - 'label' => __( 'Search Order', 'automatic-youtube-gallery' ),
606 + 'label' => __( 'Order Videos by', 'automatic-youtube-gallery' ),
954 607 'description' => '',
955 608 'type' => 'select',
956 609 'options' => array(
957 610 'date' => __( 'Date', 'automatic-youtube-gallery' ),
@@ -960,14 +613,14 @@
960 613 'title' => __( 'Title', 'automatic-youtube-gallery' ),
961 614 'viewCount' => __( 'View Count', 'automatic-youtube-gallery' )
962 615 ),
963 616 'value' => 'relevance',
964 - 'sanitize_callback' => 'sanitize_text_field'
617 + 'sanitize_callback' => 'sanitize_key'
965 618 ),
966 619 array(
967 620 'name' => 'limit',
968 - 'label' => __( 'Search Limit', 'automatic-youtube-gallery' ),
969 - 'description' => __( 'Enter the number of videos to display in this gallery. Set to 0 for the maximum amount (500).', 'automatic-youtube-gallery' ),
621 + 'label' => __( 'Number of Videos', 'automatic-youtube-gallery' ),
622 + 'description' => __( 'Specifies the maximum number of videos that will appear in this gallery. Set to 0 for the maximum amount (500).', 'automatic-youtube-gallery' ),
970 623 'type' => 'number',
971 624 'min' => 0,
972 625 'max' => 500,
973 626 'value' => 0,
@@ -992,17 +645,17 @@
992 645 )
993 646 )
994 647 ),
995 648 'gallery' => array(
996 - 'label' => __( 'Gallery Options', 'automatic-youtube-gallery' ),
649 + 'label' => __( 'Gallery (optional)', 'automatic-youtube-gallery' ),
997 650 'fields' => ayg_get_gallery_settings_fields()
998 651 ),
999 652 'player' => array(
1000 - 'label' => __( 'Player Options', 'automatic-youtube-gallery' ),
653 + 'label' => __( 'Player (optional)', 'automatic-youtube-gallery' ),
1001 654 'fields' => ayg_get_player_settings_fields()
1002 655 ),
1003 656 'search' => array(
1004 - 'label' => __( 'Search Form', 'automatic-youtube-gallery' ),
657 + 'label' => __( 'Search Form (optional)', 'automatic-youtube-gallery' ),
1005 658 'fields' => array(
1006 659 array(
1007 660 'name' => 'search_form',
1008 661 'label' => __( 'Search Form', 'automatic-youtube-gallery' ),
@@ -1021,23 +674,8 @@
1021 674 return apply_filters( 'ayg_editor_fields', $fields );
1022 675 }
1023 676
1024 677 /**
1025 - * Fetch a single gallery row by ID.
1026 - *
1027 - * @since 2.8.0
1028 - * @param int $gallery_id Gallery ID.
1029 - * @return object|null
1030 - */
1031 -function ayg_get_gallery( $gallery_id ) {
1032 - global $wpdb;
1033 -
1034 - return $wpdb->get_row(
1035 - $wpdb->prepare( "SELECT * FROM `{$wpdb->prefix}ayg_galleries` WHERE id = %d", absint( $gallery_id ) )
1036 - );
1037 -}
1038 -
1039 -/**
1040 678 * Get gallery settings fields.
1041 679 *
1042 680 * @since 1.0.0
1043 681 * @return array $fields Array of fields.
@@ -1042,9 +680,9 @@
1042 680 * @since 1.0.0
1043 681 * @return array $fields Array of fields.
1044 682 */
1045 683 function ayg_get_gallery_settings_fields() {
1046 - $gallery_settings = ayg_get_option( 'ayg_gallery_settings' );
684 + $gallery_settings = get_option( 'ayg_gallery_settings' );
1047 685
1048 686 $fields = array(
1049 687 array(
1050 688 'name' => 'theme',
@@ -1069,9 +707,9 @@
1069 707 ),
1070 708 array(
1071 709 'name' => 'per_page',
1072 710 'label' => __( 'Videos per Page', 'automatic-youtube-gallery' ),
1073 - 'description' => __( 'Enter the number of videos to show per page. Maximum of 50.', 'automatic-youtube-gallery' ),
711 + 'description' => __( 'Enter the number of videos to show per page. Maximum of 50.', 'automatic-youtube-gallery' ),
1074 712 'type' => 'number',
1075 713 'min' => 0,
1076 714 'max' => 50,
1077 715 'value' => $gallery_settings['per_page'],
@@ -1080,13 +718,12 @@
1080 718 array(
1081 719 'name' => 'thumb_ratio',
1082 720 'label' => __( 'Image Height (Ratio)', 'automatic-youtube-gallery' ),
1083 721 'description' => __( 'Select the ratio value used to calculate the image height in the gallery thumbnails.', 'automatic-youtube-gallery' ),
1084 - 'type' => 'select',
722 + 'type' => 'radio',
1085 723 'options' => array(
1086 - '56.25' => __( 'Standard (16:9) — Default', 'automatic-youtube-gallery' ),
1087 - '177.78' => __( 'Shorts / Vertical (9:16)', 'automatic-youtube-gallery' ),
1088 - '75' => __( 'Classic (4:3)', 'automatic-youtube-gallery' )
724 + '56.25' => '16:9',
725 + '75' => '4:3'
1089 726 ),
1090 727 'value' => $gallery_settings['thumb_ratio'],
1091 728 'sanitize_callback' => 'floatval'
1092 729 ),
@@ -1151,198 +788,8 @@
1151 788 return apply_filters( 'ayg_gallery_settings_fields', $fields );
1152 789 }
1153 790
1154 791 /**
1155 - * Build the signature that ties a gallery UID to the source and cache duration it was rendered
1156 - * with.
1157 - *
1158 - * A gallery UID is printed into the page, so it is not a secret and cannot be trusted on its own.
1159 - * What this protects is the *pairing*: a request may only ask for the source that was rendered
1160 - * alongside that UID, at the cache duration that gallery was configured with.
1161 - *
1162 - * It exists because a UID cannot always be recomputed. ayg_build_gallery() derives it as
1163 - * md5( type + source ), but a gallery can override that through the "uid" shortcode attribute or
1164 - * the ayg_gallery_id filter, and neither is knowable from the request alone. Signing the whole set
1165 - * at render time lets the public AJAX endpoint accept such galleries without also having to accept
1166 - * whatever source — or cache duration — the caller claims goes with them.
1167 - *
1168 - * Including the cache duration is what lets the "Cache Duration" gallery option keep working
1169 - * exactly as the site owner set it, right down to "No Caching", while still making it impossible
1170 - * for a visitor to bypass the cache and force live API requests.
1171 - *
1172 - * Each part is length prefixed so that no two different sets of values can produce the same
1173 - * string to sign.
1174 - *
1175 - * @since 2.9.0
1176 - * @param string $uid Gallery UID.
1177 - * @param string $type Gallery source type.
1178 - * @param string $src Gallery source value.
1179 - * @param string|int $cache Gallery cache duration in seconds.
1180 - * @return string Signature.
1181 - */
1182 -function ayg_get_gallery_signature( $uid, $type, $src, $cache ) {
1183 - $parts = array( (string) $uid, (string) $type, (string) $src, (string) (int) $cache );
1184 - $data = 'ayg_gallery';
1185 -
1186 - foreach ( $parts as $part ) {
1187 - $data .= '|' . strlen( $part ) . ':' . $part;
1188 - }
1189 -
1190 - return substr( wp_hash( $data ), 0, 16 );
1191 -}
1192 -
1193 -/**
1194 - * Resolve a saved gallery's source type and value.
1195 - *
1196 - * Livestream, search and single video galleries query the YouTube API at display time — nothing
1197 - * is imported for them — so they keep their own source type. Everything else is served from the
1198 - * custom tables through the internal "db" source type, keyed by the gallery ID.
1199 - *
1200 - * Both the renderer (ayg_build_gallery) and the public AJAX endpoint resolve a saved gallery's
1201 - * source through this function, so an untrusted request can never point a gallery at a source
1202 - * other than the one stored in its own row.
1203 - *
1204 - * @since 2.9.0
1205 - * @param object $gallery Gallery row from "{$wpdb->prefix}ayg_galleries".
1206 - * @return array Source "type", the matching attribute name in "field", and the
1207 - * source value in "src".
1208 - */
1209 -function ayg_get_gallery_source( $gallery ) {
1210 - $source_type = isset( $gallery->source_type ) ? $gallery->source_type : '';
1211 - $source_value = isset( $gallery->source_value ) ? $gallery->source_value : '';
1212 -
1213 - if ( 'livestream' === $source_type ) {
1214 - return array(
1215 - 'type' => 'livestream',
1216 - 'field' => 'channel',
1217 - 'src' => $source_value
1218 - );
1219 - }
1220 -
1221 - if ( 'search' === $source_type ) {
1222 - return array(
1223 - 'type' => 'search',
1224 - 'field' => 'search',
1225 - 'src' => $source_value
1226 - );
1227 - }
1228 -
1229 - if ( 'video' === $source_type ) {
1230 - return array(
1231 - 'type' => 'video',
1232 - 'field' => 'video',
1233 - 'src' => $source_value
1234 - );
1235 - }
1236 -
1237 - return array(
1238 - 'type' => 'db',
1239 - 'field' => 'db',
1240 - 'src' => strval( $gallery->id )
1241 - );
1242 -}
1243 -
1244 -/**
1245 - * Return the schedule interval options for import scheduling.
1246 - *
1247 - * @since 2.8.0
1248 - * @return array Value (seconds as string) => label pairs.
1249 - */
1250 -function ayg_get_import_schedule_options() {
1251 - return array(
1252 - 'paused' => __( '— Pause Automatic Imports —', 'automatic-youtube-gallery' ),
1253 - '0' => __( 'Only Once', 'automatic-youtube-gallery' ),
1254 - '3600' => __( 'Every Hour', 'automatic-youtube-gallery' ),
1255 - '86400' => __( 'Every Day', 'automatic-youtube-gallery' ),
1256 - '604800' => __( 'Every Week', 'automatic-youtube-gallery' ),
1257 - '2592000' => __( 'Every Month', 'automatic-youtube-gallery' )
1258 - );
1259 -}
1260 -
1261 -/**
1262 - * Return the human-readable label for an import status key.
1263 - *
1264 - * @since 2.8.0
1265 - * @param string $status Import status key (idle, running, paused, error, completed).
1266 - * @param int|null $video_count Optional. Imported video count, used to resolve the
1267 - * ambiguous "idle" status into "Pending Import" (nothing
1268 - * imported yet) or "Up to Date" (waiting for the next sync).
1269 - * @param bool $resuming Optional. True when an import was capped mid-run and parked
1270 - * as 'idle' for cron to resume — shown as "Importing", not idle.
1271 - * @param string $context Optional. 'log' for a per-run history entry, where a successful
1272 - * run (stored as 'idle' for recurring galleries) reads "Completed"
1273 - * like non-recurring runs, instead of the live-state "Idle".
1274 - * @return string
1275 - */
1276 -function ayg_get_import_status_label( $status, $video_count = null, $resuming = false, $context = '' ) {
1277 - if ( 'idle' === $status ) {
1278 - // History rows record a finished run: a recurring gallery's success is stored as 'idle',
1279 - // so surface it as "Completed" to match non-recurring runs rather than the live "Idle".
1280 - if ( 'log' === $context ) {
1281 - return __( 'Completed', 'automatic-youtube-gallery' );
1282 - }
1283 -
1284 - // If the import was capped mid-run and parked as 'idle' for cron to resume, show "Importing".
1285 - if ( $resuming ) {
1286 - return __( 'Importing', 'automatic-youtube-gallery' );
1287 - }
1288 -
1289 - // If the import has never run, show "Pending Import" instead of "Idle".
1290 - if ( null !== $video_count ) {
1291 - return ( (int) $video_count > 0 ) ? __( 'Up to Date', 'automatic-youtube-gallery' ) : __( 'Pending Import', 'automatic-youtube-gallery' );
1292 - }
1293 - }
1294 -
1295 - $statuses = array(
1296 - 'idle' => __( 'Idle', 'automatic-youtube-gallery' ),
1297 - 'running' => __( 'Importing', 'automatic-youtube-gallery' ),
1298 - 'paused' => __( 'Paused', 'automatic-youtube-gallery' ),
1299 - 'error' => __( 'Error', 'automatic-youtube-gallery' ),
1300 - 'completed' => __( 'Completed', 'automatic-youtube-gallery' )
1301 - );
1302 -
1303 - return isset( $statuses[ $status ] ) ? $statuses[ $status ] : ucfirst( $status );
1304 -}
1305 -
1306 -/**
1307 - * Retrieve a plugin option with fallback to default settings.
1308 - *
1309 - * @since 2.7.0
1310 - * @param string $option The option name to retrieve.
1311 - * @return mixed
1312 - */
1313 -function ayg_get_option( $option ) {
1314 - $defaults = ayg_get_default_settings();
1315 - $default = isset( $defaults[ $option ] ) ? $defaults[ $option ] : array();
1316 -
1317 - $saved = get_option( $option, null );
1318 -
1319 - // Option does not exist OR corrupted
1320 - if ( null === $saved || ! is_array( $saved ) ) {
1321 - return $default;
1322 - }
1323 -
1324 - // Merge saved values with defaults
1325 - return wp_parse_args( $saved, $default );
1326 -}
1327 -
1328 -/**
1329 - * Build the signature for a pagination token.
1330 - *
1331 - * Keyed on the site's own salts through wp_hash(), and bound to the gallery so a token issued for
1332 - * one gallery cannot be replayed against another. Unlike a nonce this never expires, so tokens
1333 - * printed into a page that is held by a full page cache keep working.
1334 - *
1335 - * @since 2.9.0
1336 - * @param string $token Raw page token.
1337 - * @param string $uid Gallery UID the token belongs to.
1338 - * @return string Signature.
1339 - */
1340 -function ayg_get_page_token_signature( $token, $uid ) {
1341 - return substr( wp_hash( 'ayg_page_token|' . (string) $uid . '|' . (string) $token ), 0, 16 );
1342 -}
1343 -
1344 -/**
1345 792 * Get video description to show on top of the player.
1346 793 *
1347 794 * @since 1.0.0
1348 795 * @param stdClass $video YouTube video object.
@@ -1374,9 +821,9 @@
1374 821 * @since 1.0.0
1375 822 * @return array $fields Array of fields.
1376 823 */
1377 824 function ayg_get_player_settings_fields() {
1378 - $player_settings = ayg_get_option( 'ayg_player_settings' );
825 + $player_settings = get_option( 'ayg_player_settings' );
1379 826
1380 827 $fields = array(
1381 828 array(
1382 829 'name' => 'player_width',
@@ -1389,13 +836,12 @@
1389 836 array(
1390 837 'name' => 'player_ratio',
1391 838 'label' => __( 'Player Height (Ratio)', 'automatic-youtube-gallery' ),
1392 839 'description' => __( 'Select the ratio value used to calculate the player height.', 'automatic-youtube-gallery' ),
1393 - 'type' => 'select',
840 + 'type' => 'radio',
1394 841 'options' => array(
1395 - '56.25' => __( 'Standard (16:9) — Default', 'automatic-youtube-gallery' ),
1396 - '177.78' => __( 'Shorts / Vertical (9:16)', 'automatic-youtube-gallery' ),
1397 - '75' => __( 'Classic (4:3)', 'automatic-youtube-gallery' )
842 + '56.25' => '16:9',
843 + '75' => '4:3'
1398 844 ),
1399 845 'value' => $player_settings['player_ratio'],
1400 846 'sanitize_callback' => 'floatval'
1401 847 ),
@@ -1417,9 +863,9 @@
1417 863 ),
1418 864 array(
1419 865 'name' => 'autoplay',
1420 866 'label' => __( 'Autoplay', 'automatic-youtube-gallery' ),
1421 - 'description' => __( 'Check this option to automatically start playing the initial video when the player loads.', 'automatic-youtube-gallery' ),
867 + 'description' => __( 'Specifies whether the initial video will automatically start to play when the player loads.', 'automatic-youtube-gallery' ),
1422 868 'type' => 'checkbox',
1423 869 'value' => $player_settings['autoplay'],
1424 870 'sanitize_callback' => 'intval'
1425 871 ),
@@ -1425,9 +871,9 @@
1425 871 ),
1426 872 array(
1427 873 'name' => 'autoadvance',
1428 874 'label' => __( 'Autoplay Next Video', 'automatic-youtube-gallery' ),
1429 - 'description' => __( 'Check this option to automatically play the next video in the list after the previous one ends.', 'automatic-youtube-gallery' ),
875 + 'description' => __( 'Specifies whether to play the next video in the list automatically after previous one end.', 'automatic-youtube-gallery' ),
1430 876 'type' => 'checkbox',
1431 877 'value' => $player_settings['autoadvance'],
1432 878 'sanitize_callback' => 'intval'
1433 879 ),
@@ -1433,9 +879,9 @@
1433 879 ),
1434 880 array(
1435 881 'name' => 'loop',
1436 882 'label' => __( 'Loop', 'automatic-youtube-gallery' ),
1437 - 'description' => __( 'Check this option to loop playback. In the case of a single video player, plays the initial video again and again. In the case of a gallery, plays the entire list and then starts again at the first video.', 'automatic-youtube-gallery' ),
883 + 'description' => __( 'In the case of a single video player, plays the initial video again and again. In the case of a gallery, plays the entire list in the gallery and then starts again at the first video.', 'automatic-youtube-gallery' ),
1438 884 'type' => 'checkbox',
1439 885 'value' => $player_settings['loop'],
1440 886 'sanitize_callback' => 'intval'
1441 887 ),
@@ -1457,9 +903,9 @@
1457 903 ),
1458 904 array(
1459 905 'name' => 'modestbranding',
1460 906 'label' => __( 'Hide YouTube Logo', 'automatic-youtube-gallery' ),
1461 - 'description' => __( "Check this option to prevent the YouTube logo from displaying in the control bar. Note that a small YouTube text label will still display in the upper-right corner of a paused video when the user's mouse pointer hovers over the player.", 'automatic-youtube-gallery' ),
907 + 'description' => __( "Lets you prevent the YouTube logo from displaying in the control bar. Note that a small YouTube text label will still display in the upper-right corner of a paused video when the user's mouse pointer hovers over the player.", 'automatic-youtube-gallery' ),
1462 908 'type' => 'checkbox',
1463 909 'value' => $player_settings['modestbranding'],
1464 910 'sanitize_callback' => 'intval'
1465 911 ),
@@ -1465,12 +911,20 @@
1465 911 ),
1466 912 array(
1467 913 'name' => 'cc_load_policy',
1468 914 'label' => __( 'Force Closed Captions', 'automatic-youtube-gallery' ),
1469 - 'description' => __( 'Check this option to show captions by default, even if the user has turned captions off. The default behavior is based on user preference.', 'automatic-youtube-gallery' ),
915 + 'description' => __( 'Show captions by default, even if the user has turned captions off. The default behavior is based on user preference.', 'automatic-youtube-gallery' ),
1470 916 'type' => 'checkbox',
1471 917 'value' => $player_settings['cc_load_policy'],
1472 918 'sanitize_callback' => 'intval'
919 + ),
920 + array(
921 + 'name' => 'iv_load_policy',
922 + 'label' => __( 'Show Annotations', 'automatic-youtube-gallery' ),
923 + 'description' => __( 'Choose whether to show annotations or not.', 'automatic-youtube-gallery' ),
924 + 'type' => 'checkbox',
925 + 'value' => $player_settings['iv_load_policy'],
926 + 'sanitize_callback' => 'intval'
1473 927 ),
1474 928 array(
1475 929 'name' => 'hl',
1476 930 'label' => __( 'Player Language', 'automatic-youtube-gallery' ),
@@ -1511,38 +965,8 @@
1511 965 return apply_filters( 'ayg_single_video_url', '', $video, $attributes );
1512 966 }
1513 967
1514 968 /**
1515 - * Return the human-readable label for a source type key.
1516 - *
1517 - * @since 2.8.0
1518 - * @param string $type Source type key (channel, playlist, username, search, livestream, video, videos).
1519 - * @return string
1520 - */
1521 -function ayg_get_source_type_label( $type ) {
1522 - $types = ayg_get_source_types();
1523 - return isset( $types[ $type ] ) ? $types[ $type ] : ucfirst( $type );
1524 -}
1525 -
1526 -/**
1527 - * Get source types.
1528 - *
1529 - * @since 2.8.0
1530 - * @return array
1531 - */
1532 -function ayg_get_source_types() {
1533 - return array(
1534 - 'channel' => __( 'Channel', 'automatic-youtube-gallery' ),
1535 - 'playlist' => __( 'Playlist', 'automatic-youtube-gallery' ),
1536 - 'username' => __( 'Username', 'automatic-youtube-gallery' ),
1537 - 'search' => __( 'Search Keywords', 'automatic-youtube-gallery' ),
1538 - 'livestream' => __( 'Livestream', 'automatic-youtube-gallery' ),
1539 - 'video' => __( 'Single Video', 'automatic-youtube-gallery' ),
1540 - 'videos' => __( 'Custom Videos List', 'automatic-youtube-gallery' )
1541 - );
1542 -}
1543 -
1544 -/**
1545 969 * Get filtered php template file path.
1546 970 *
1547 971 * @since 1.0.0
1548 972 * @param array $template PHP file path.
@@ -1575,9 +999,9 @@
1575 999 * @since 2.3.0
1576 1000 * @return string YouTube embed domain.
1577 1001 */
1578 1002 function ayg_get_youtube_domain() {
1579 - $player_settings = ayg_get_option( 'ayg_player_settings' );
1003 + $player_settings = get_option( 'ayg_player_settings' );
1580 1004
1581 1005 $domain = 'https://www.youtube.com';
1582 1006 if ( isset( $player_settings['privacy_enhanced_mode'] ) && ! empty( $player_settings['privacy_enhanced_mode'] ) ) {
1583 1007 $domain = 'https://www.youtube-nocookie.com';
@@ -1594,9 +1018,9 @@
1594 1018 * @param array $attributes Array of user attributes.
1595 1019 * @return string Player embed URL.
1596 1020 */
1597 1021 function ayg_get_youtube_embed_url( $video_id, $attributes = array() ) {
1598 - $player_settings = ayg_get_option( 'ayg_player_settings' );
1022 + $player_settings = get_option( 'ayg_player_settings' );
1599 1023
1600 1024 $player_website = 'https://www.youtube.com';
1601 1025 if ( isset( $player_settings['privacy_enhanced_mode'] ) && ! empty( $player_settings['privacy_enhanced_mode'] ) ) {
1602 1026 $player_website = 'https://www.youtube-nocookie.com';
@@ -1649,8 +1073,13 @@
1649 1073 if ( 1 == $cc_load_policy ) {
1650 1074 $url = add_query_arg( 'cc_load_policy', 1, $url );
1651 1075 }
1652 1076
1077 + $iv_load_policy = isset( $attributes['iv_load_policy'] ) ? (int) $attributes['iv_load_policy'] : 0;
1078 + if ( 0 == $iv_load_policy ) {
1079 + $url = add_query_arg( 'iv_load_policy', 3, $url );
1080 + }
1081 +
1653 1082 if ( isset( $attributes['hl'] ) && ! empty( $attributes['hl'] ) ) {
1654 1083 $url = add_query_arg( 'hl', sanitize_text_field( $attributes['hl'] ), $url );
1655 1084 }
1656 1085
@@ -1661,35 +1090,8 @@
1661 1090 return apply_filters( 'ayg_youtube_embed_url', $url, $video_id, $attributes );
1662 1091 }
1663 1092
1664 1093 /**
1665 - * Resolve a YouTube video ID from a bare ID or a YouTube URL.
1666 - *
1667 - * @since 2.8.0
1668 - * @param string $string A video ID or YouTube URL.
1669 - * @return string The 11-char video ID, or '' if none found.
1670 - */
1671 -function ayg_get_youtube_video_id( $string ) {
1672 - $string = trim( (string) $string );
1673 -
1674 - if ( '' === $string ) {
1675 - return '';
1676 - }
1677 -
1678 - // Already a bare video ID.
1679 - if ( preg_match( '~^[A-Za-z0-9_-]{11}$~', $string ) ) {
1680 - return $string;
1681 - }
1682 -
1683 - // Common YouTube URL forms.
1684 - if ( preg_match( '~(?:v=|/(?:embed|v|shorts|live)/|youtu\.be/)([A-Za-z0-9_-]{11})~', $string, $matches ) ) {
1685 - return $matches[1];
1686 - }
1687 -
1688 - return '';
1689 -}
1690 -
1691 -/**
1692 1094 * Inserts a new associative array after another associative array key.
1693 1095 *
1694 1096 * @since 2.1.0
1695 1097 * @param string $key The associative array key to insert after.
@@ -1740,118 +1142,8 @@
1740 1142 return false;
1741 1143 }
1742 1144
1743 1145 /**
1744 - * Check whether a video ID matches a gallery's exclude list.
1745 - *
1746 - * @since 2.8.0
1747 - * @param string $video_id YouTube video ID.
1748 - * @param array $exclude Exclude entries (video IDs or URLs).
1749 - * @return bool True if the video is excluded.
1750 - */
1751 -function ayg_is_video_excluded( $video_id, $exclude ) {
1752 - if ( empty( $video_id ) || empty( $exclude ) || ! is_array( $exclude ) ) {
1753 - return false;
1754 - }
1755 -
1756 - foreach ( $exclude as $entry ) {
1757 - if ( false !== strpos( $entry, $video_id ) ) {
1758 - return true;
1759 - }
1760 - }
1761 -
1762 - return false;
1763 -}
1764 -
1765 -/**
1766 - * Unserialize a stored value without letting it instantiate arbitrary classes.
1767 - *
1768 - * Our serialized columns only ever hold the thumbnail set from an API response — plain data plus
1769 - * stdClass objects — so nothing else is allowed through. This keeps a row that was tampered with
1770 - * or carried over from another install from building an object of some other class.
1771 - *
1772 - * @since 2.9.0
1773 - * @param string $value Serialized value.
1774 - * @return mixed Unserialized value, or the value unchanged when it isn't serialized.
1775 - */
1776 -function ayg_maybe_unserialize( $value ) {
1777 - if ( ! is_serialized( $value ) ) {
1778 - return $value;
1779 - }
1780 -
1781 - // is_serialized() trims the value before testing it, so trim here as well — otherwise a padded
1782 - // value passes the test and then fails to unserialize. Mirrors core's maybe_unserialize().
1783 - $value = trim( $value );
1784 -
1785 - // PHP 7.0+ can restrict this natively.
1786 - if ( version_compare( PHP_VERSION, '7.0', '>=' ) ) {
1787 - return @unserialize( $value, array( 'allowed_classes' => array( 'stdClass' ) ) );
1788 - }
1789 -
1790 - // PHP 5.6 has no "allowed_classes" option, so screen the payload instead: "O:" introduces a
1791 - // plain object and "C:" a Serializable one, so refuse the value outright if it names any class
1792 - // other than stdClass. Same protection as the call above, without raising the minimum PHP
1793 - // version and cutting those sites off from plugin updates.
1794 - if ( preg_match_all( '/(?:^|[;{])([OC]):\d+:"([^"]*)"/', $value, $matches, PREG_SET_ORDER ) ) {
1795 - foreach ( $matches as $match ) {
1796 - if ( 'O' !== $match[1] || 'stdClass' !== $match[2] ) {
1797 - return false;
1798 - }
1799 - }
1800 - }
1801 -
1802 - return @unserialize( $value );
1803 -}
1804 -
1805 -/**
1806 - * Does this source type hand out signed pagination tokens?
1807 - *
1808 - * Only the sources that page through the live YouTube API do, because for those every distinct
1809 - * page token means another API call. The rest either never reach the API at all (the Gallery
1810 - * Builder "db" source) or page through a list already known to the site ("videos"), where the
1811 - * page number is simply clamped to the real range and so cannot force extra calls.
1812 - *
1813 - * @since 2.9.0
1814 - * @param string $source_type Gallery source type.
1815 - * @return bool True when tokens of this source type are signed.
1816 - */
1817 -function ayg_page_token_is_signed( $source_type ) {
1818 - return in_array( $source_type, array( 'playlist', 'channel', 'username', 'search' ), true );
1819 -}
1820 -
1821 -/**
1822 - * Parse an ISO 8601 duration (e.g. PT4M13S) into total seconds.
1823 - *
1824 - * @since 2.8.0
1825 - * @param string $iso8601 ISO 8601 duration string from the YouTube API.
1826 - * @return int Duration in seconds.
1827 - */
1828 -function ayg_parse_duration_seconds( $iso8601 ) {
1829 - if ( empty( $iso8601 ) ) {
1830 - return 0;
1831 - }
1832 -
1833 - try {
1834 - $interval = new DateInterval( $iso8601 );
1835 - } catch ( Exception $e ) {
1836 - return 0;
1837 - }
1838 -
1839 - return ( $interval->d * DAY_IN_SECONDS ) + ( $interval->h * HOUR_IN_SECONDS ) + ( $interval->i * MINUTE_IN_SECONDS ) + $interval->s;
1840 -}
1841 -
1842 -/**
1843 - * Sanitize the array inputs.
1844 - *
1845 - * @since 2.7.0
1846 - * @param array $value Input array.
1847 - * @return array Sanitized array.
1848 - */
1849 -function ayg_sanitize_array( $value ) {
1850 - return ! empty( $value ) ? array_map( 'sanitize_text_field', $value ) : array();
1851 -}
1852 -
1853 -/**
1854 1146 * Sanitize the integer inputs, accepts empty values.
1855 1147 *
1856 1148 * @since 1.0.0
1857 1149 * @param string|int $value Input value.
@@ -1862,49 +1154,8 @@
1862 1154 return ( 0 == $value ) ? '' : $value;
1863 1155 }
1864 1156
1865 1157 /**
1866 - * Sign a pagination token before it is handed to the browser.
1867 - *
1868 - * Page tokens travel out to the visitor and are posted back to the public AJAX endpoint, where
1869 - * each distinct token means another live call to the YouTube Data API. Signing them means only
1870 - * the tokens this site actually issued are ever acted on, so a visitor cannot invent an endless
1871 - * stream of new ones and burn through the site's daily API quota.
1872 - *
1873 - * @since 2.9.0
1874 - * @param string $token Raw page token from the API response.
1875 - * @param string $uid Gallery UID the token belongs to.
1876 - * @return string Signed token, or an empty string when there is no token.
1877 - */
1878 -function ayg_sign_page_token( $token, $uid ) {
1879 - $token = (string) $token;
1880 -
1881 - if ( '' === $token ) {
1882 - return '';
1883 - }
1884 -
1885 - return $token . '.' . ayg_get_page_token_signature( $token, $uid );
1886 -}
1887 -
1888 -/**
1889 - * Sign the pagination tokens in a page info array.
1890 - *
1891 - * @since 2.9.0
1892 - * @param array $page_info Page info array from the API response.
1893 - * @param string $uid Gallery UID the tokens belong to.
1894 - * @return array The same array with its tokens signed.
1895 - */
1896 -function ayg_sign_page_tokens( $page_info, $uid ) {
1897 - foreach ( array( 'next_page_token', 'prev_page_token' ) as $key ) {
1898 - if ( ! empty( $page_info[ $key ] ) ) {
1899 - $page_info[ $key ] = ayg_sign_page_token( $page_info[ $key ], $uid );
1900 - }
1901 - }
1902 -
1903 - return $page_info;
1904 -}
1905 -
1906 -/**
1907 1158 * Trims text to a certain number of characters.
1908 1159 *
1909 1160 * @since 2.0.0
1910 1161 * @param string $text Text to trim.
@@ -1931,43 +1182,9 @@
1931 1182
1932 1183 $text .= $append;
1933 1184 }
1934 1185
1935 - return apply_filters( 'ayg_trim_words', $text, $original_text, $num_characters, $append );
1936 -}
1937 -
1938 -/**
1939 - * Verify a pagination token that came back from the browser.
1940 - *
1941 - * @since 2.9.0
1942 - * @param string $token Signed token as posted by the browser.
1943 - * @param string $uid Gallery UID the token is claimed to belong to.
1944 - * @return string|false The raw token when the signature matches, false when it does not. An
1945 - * empty token is the first page and needs no signature.
1946 - */
1947 -function ayg_verify_page_token( $token, $uid ) {
1948 - $token = (string) $token;
1949 -
1950 - if ( '' === $token ) {
1951 - return '';
1952 - }
1953 -
1954 - // The signature is appended last, and a YouTube page token can itself contain almost anything,
1955 - // so split on the final separator rather than the first.
1956 - $position = strrpos( $token, '.' );
1957 -
1958 - if ( false === $position ) {
1959 - return false;
1960 - }
1961 -
1962 - $raw = substr( $token, 0, $position );
1963 - $signature = substr( $token, $position + 1 );
1964 -
1965 - if ( '' === $raw || ! hash_equals( ayg_get_page_token_signature( $raw, $uid ), $signature ) ) {
1966 - return false;
1967 - }
1968 -
1969 - return $raw;
1186 + return apply_filters( 'ayg_trim_words', $text, $original_text, $num_characters, $append );
1970 1187 }
1971 1188
1972 1189 /**
1973 1190 * Gallery HTML output.