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 | includes/functions.php +210 -1225 trunk2.3.2 View file →
@@ -21,78 +21,22 @@
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' );
27 -
28 25 global $post;
29 26
30 27 // Vars
31 28 $fields = ayg_get_editor_fields();
32 - $excluded = array( 'popup' ); // Fields not part of attributes
29 + $defaults = array();
33 30
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 31 foreach ( $fields as $key => $value ) {
45 32 foreach ( $value['fields'] as $field ) {
46 - if ( in_array( $field['name'], $excluded ) ) {
47 - continue;
48 - }
49 -
50 33 $defaults[ $field['name'] ] = $field['value'];
51 34 }
52 35 }
53 36
54 - $defaults = array_merge( $defaults, (array) $strings_settings );
37 + $attributes = shortcode_atts( $defaults, $args );
55 38
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 - $attributes = shortcode_atts( $defaults, $args, 'automatic_youtube_gallery' );
94 -
95 39 $attributes['post_id'] = 0;
96 40 if ( isset( $post->ID ) ) {
97 41 $attributes['post_id'] = (int) $post->ID;
98 42 }
@@ -106,90 +50,31 @@
106 50 if ( empty( $attributes['limit'] ) ) {
107 51 $attributes['limit'] = 500;
108 52 }
109 53
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 - }
54 + $attributes['per_page'] = min( 50, (int) $attributes['per_page'] );
55 + if ( empty( $attributes['per_page'] ) ) {
56 + $attributes['per_page'] = 50;
122 57 }
123 58
59 + $source_type = sanitize_text_field( $attributes['type'] );
124 60 if ( 'livestream' == $source_type ) {
125 61 $attributes['livestream'] = $attributes['channel'];
126 62 }
127 63
128 - $attributes['lazyload'] = 0;
129 - if ( ! empty( $general_settings['lazyload'] ) ) {
130 - $attributes['lazyload'] = 1;
131 - }
132 -
133 - $source_url = sanitize_text_field( $attributes[ $source_type ] );
134 -
135 - if ( isset( $args['id'] ) && ! empty( $args['id'] ) ) {
136 - $attributes['id'] = absint( $args['id'] );
137 - }
138 -
139 64 if ( isset( $args['uid'] ) && ! empty( $args['uid'] ) ) {
140 - $attributes['uid'] = sanitize_text_field( $args['uid'] );
65 + $attributes['uid'] = $args['uid'];
141 66 } else {
142 - $attributes['uid'] = md5( $source_type . $source_url );
67 + $attributes['uid'] = md5( $source_type . sanitize_text_field( $attributes[ $source_type ] ) . sanitize_text_field( $attributes['theme'] ) );
143 68 }
144 69
145 - $attributes['uid'] = apply_filters( 'ayg_gallery_id', $attributes['uid'], $args );
146 -
147 - // Deprecated since v2.5.8. Retained for backward compatibility.
148 - $deprecated_uid = md5( $source_type . $source_url . sanitize_text_field( $attributes['theme'] ) ); // Deprecated
149 - if ( isset( $args['deprecated_uid'] ) && ! empty( $args['deprecated_uid'] ) ) {
150 - $deprecated_uid = sanitize_text_field( $args['deprecated_uid'] );
151 - }
152 -
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 70 // Get Videos
177 71 $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']
72 + 'type' => $source_type,
73 + 'src' => sanitize_text_field( $attributes[ $source_type ] ),
74 + 'order' => sanitize_text_field( $attributes['order'] ), // applicable only when type=search
75 + 'maxResults' => $attributes['per_page'],
76 + 'cache' => (int) $attributes['cache']
192 77 );
193 78
194 79 $api_params = apply_filters( 'ayg_youtube_api_request_params', $api_params, $args );
195 80
@@ -199,9 +84,9 @@
199 84 // Process output
200 85 if ( ! isset( $response->error ) ) {
201 86 // Store Gallery ID
202 87 if ( $attributes['post_id'] > 0 && isset( $attributes['deeplinking'] ) && 1 == $attributes['deeplinking'] ) {
203 - $pages = ayg_get_option( 'ayg_gallery_page_ids' );
88 + $pages = get_option( 'ayg_gallery_page_ids', array() );
204 89 $page_id = $attributes['post_id'];
205 90
206 91 if ( ! in_array( $page_id, $pages ) ) {
207 92 $pages[] = $page_id;
@@ -207,26 +92,31 @@
207 92 $pages[] = $page_id;
208 93 update_option( 'ayg_gallery_page_ids', $pages );
209 94 }
210 95 }
96 +
97 + // Enqueue dependencies
98 + wp_enqueue_style( AYG_SLUG . '-public' );
99 + wp_enqueue_script( AYG_SLUG . '-public' );
211 100
212 101 // Gallery
213 - $videos = array();
102 + $videos = array();
214 103
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;
104 + $gallery_id_from_url = get_query_var( 'ayg_gallery_id' );
105 + $video_id_from_url = get_query_var( 'ayg_video_id' );
221 106
222 - if ( ! $is_pinned_by_sql ) {
223 - $videos[] = $deeplinked_video;
107 + if ( $attributes['uid'] == $gallery_id_from_url ) {
108 + $video = ayg_db_get_video( $video_id_from_url );
109 +
110 + if ( $video ) {
111 + $videos[] = $video;
112 + } else {
113 + $video_id_from_url = '';
224 114 }
225 115 }
226 -
116 +
227 117 if ( isset( $response->videos ) ) {
228 - if ( ! empty( $videos ) ) {
118 + if ( ! empty( $video_id_from_url ) ) {
229 119 foreach ( $response->videos as $video ) {
230 120 if ( $video->id != $video_id_from_url ) {
231 121 $videos[] = $video;
232 122 }
@@ -237,17 +127,9 @@
237 127 }
238 128
239 129 // Pagination
240 130 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 );
131 + $attributes = array_merge( $attributes, $api_params, $response->page_info );
250 132 }
251 133
252 134 // Theme
253 135 $theme = 'classic';
@@ -261,328 +143,92 @@
261 143 $theme = 'single';
262 144 }
263 145 }
264 146
265 - // Enqueue dependencies
266 - wp_enqueue_style( AYG_SLUG . '-public' );
267 -
268 - wp_enqueue_script( AYG_SLUG . '-public' );
269 - if ( $attributes['theme'] == $theme && 'classic' == $attributes['theme'] ) {
270 - wp_enqueue_script( AYG_SLUG . '-theme-classic' );
271 - }
272 -
273 147 // Output
274 148 ob_start();
275 149 include ayg_get_template( AYG_DIR . "public/templates/theme-{$theme}.php", $attributes['theme'] );
276 150 return ob_get_clean();
277 151 } else {
278 - return sprintf( '<div class="ayg ayg-error">%s</div>', wp_kses_post( $response->error_message ) );
152 + return '<p class="ayg-error">' . $response->error_message . '</p>';
279 153 }
280 154 }
281 155
282 156 /**
283 - * Combine video attributes as a string.
284 - *
285 - * @since 2.5.0
286 - * @param array $atts Array of video attributes.
287 - * @param string Combined attributes string.
288 - */
289 -function ayg_combine_video_attributes( $atts ) {
290 - $attributes = array();
291 -
292 - foreach ( $atts as $key => $value ) {
293 - if ( '' === $value ) {
294 - $attributes[] = $key;
295 - } else {
296 - $attributes[] = sprintf( '%s="%s"', $key, $value );
297 - }
298 - }
299 -
300 - return implode( ' ', $attributes );
301 -}
302 -
303 -/**
304 - * Create custom database tables
157 + * Create a custom database table "{$wpdb->prefix}ayg_videos"
305 158 *
306 159 * @since 2.1.0
307 160 */
308 -function ayg_db_create_custom_tables() {
161 +function ayg_db_create_videos_table() {
309 162 global $wpdb;
310 163
311 - require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
312 - $charset_collate = $wpdb->get_charset_collate();
164 + $charset_collate = $wpdb->get_charset_collate();
313 165
314 - $videos_table = $wpdb->prefix . 'ayg_videos';
315 - $rel_table = $wpdb->prefix . 'ayg_gallery_relationships';
316 - $old_table = $wpdb->prefix . 'ayg_galleries';
317 -
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;
323 -
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).
360 - $wpdb->query(
361 - "DELETE v1 FROM $videos_table v1
362 - INNER JOIN $videos_table v2
363 - ON v1.video_id = v2.video_id AND v1.id > v2.id"
364 - );
365 - }
366 -
367 - // Create/update wp_ayg_videos — dbDelta adds the 2 new columns on existing installs.
368 - $sql = "CREATE TABLE $videos_table (
369 - id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
370 - video_id varchar(100) NOT NULL,
166 + $sql = "CREATE TABLE `{$wpdb->prefix}ayg_videos` (
167 + NUM bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
168 + id varchar(100) NOT NULL,
371 169 title text NOT NULL,
372 170 description text NOT NULL,
373 - thumbnails text NOT NULL,
171 + thumbnails text NOT NULL,
374 172 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 173 status varchar(100) NOT NULL,
378 174 published_at varchar(100) NOT NULL,
379 - 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)
175 + PRIMARY KEY (NUM)
383 176 ) $charset_collate;";
384 -
385 - dbDelta( $sql );
386 -
387 - // Fallback: add UNIQUE KEY if dbDelta did not create it (edge case on existing installs).
388 - $existing_keys = $wpdb->get_results( "SHOW KEYS FROM $videos_table WHERE Key_name = 'ayg_unique_video_id'" );
389 177
390 - if ( empty( $existing_keys ) ) {
391 - $wpdb->query( "ALTER TABLE $videos_table ADD UNIQUE KEY ayg_unique_video_id (video_id)" );
392 - }
393 -
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 - }
400 -
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,
416 - video_id varchar(100) NOT NULL,
417 - gallery_id varchar(100) NOT NULL,
418 - synced_at datetime NULL,
419 - PRIMARY KEY (id),
420 - UNIQUE KEY ayg_unique_video_gallery (video_id, gallery_id),
421 - INDEX ayg_idx_gallery_id (gallery_id)
422 - ) $charset_collate;";
423 -
178 + require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
424 179 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 180 }
456 181
457 182 /**
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 183 * Store videos in our custom database table "{$wpdb->prefix}ayg_videos"
521 184 *
522 185 * @since 2.1.0
523 - * @param object $data YouTube API response object.
524 - * @param array $attributes Array of user attributes.
186 + * @param object $data YouTube API response object.
525 187 */
526 -function ayg_db_store_videos( $data, $attributes = array() ) {
188 +function ayg_db_store_videos( $data ) {
527 189 if ( AYG_VERSION !== get_option( 'ayg_version' ) ) {
528 190 return false;
529 191 }
530 192
193 + global $wpdb, $post;
194 +
195 + $table_name = $wpdb->prefix . 'ayg_videos';
196 +
531 197 if ( isset( $data->kind ) && 'youtube#channelListResponse' == $data->kind ) {
532 198 return false;
533 199 }
534 200
535 - if ( empty( $data->items ) || ! is_array( $data->items ) ) {
201 + if ( ! isset( $data->items ) ) {
536 202 return false;
537 203 }
538 204
539 - global $wpdb;
205 + $items = $data->items;
206 + if ( ! is_array( $items ) || 0 == count( $items ) ) {
207 + return false;
208 + }
540 209
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();
546 -
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 ) {
210 + // Store Videos
211 + foreach ( $items as $item ) {
561 212 $row = array();
562 213
563 214 // Video ID
564 - $row['video_id'] = '';
215 + $row['id'] = '';
565 216
566 217 if ( isset( $item->snippet->resourceId ) && isset( $item->snippet->resourceId->videoId ) ) {
567 - $row['video_id'] = $item->snippet->resourceId->videoId;
218 + $row['id'] = $item->snippet->resourceId->videoId;
568 219 } elseif ( isset( $item->contentDetails ) && isset( $item->contentDetails->videoId ) ) {
569 - $row['video_id'] = $item->contentDetails->videoId;
220 + $row['id'] = $item->contentDetails->videoId;
570 221 } elseif ( isset( $item->id ) && isset( $item->id->videoId ) ) {
571 - $row['video_id'] = $item->id->videoId;
222 + $row['id'] = $item->id->videoId;
572 223 } elseif ( isset( $item->id ) ) {
573 - $row['video_id'] = $item->id;
574 - }
224 + $row['id'] = $item->id;
225 + }
575 226
576 - if ( empty( $row['video_id'] ) ) {
227 + if ( empty( $row['id'] ) ) {
577 228 continue;
578 229 }
579 230
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 231 // Video title
586 232 $row['title'] = $item->snippet->title;
587 233
588 234 // Video description
@@ -591,206 +237,103 @@
591 237 // Video thumbnails
592 238 $row['thumbnails'] = '';
593 239 if ( isset( $item->snippet->thumbnails ) ) {
594 240 $row['thumbnails'] = serialize( $item->snippet->thumbnails );
595 - }
241 + }
596 242
597 243 // Video duration
598 244 $row['duration'] = '';
599 245 if ( isset( $item->contentDetails ) && isset( $item->contentDetails->duration ) ) {
600 246 $row['duration'] = $item->contentDetails->duration;
601 - }
247 + }
602 248
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 249 // Video status
615 250 $row['status'] = 'private';
616 -
251 +
617 252 if ( isset( $item->status ) && ( 'public' == $item->status->privacyStatus || 'unlisted' == $item->status->privacyStatus ) ) {
618 - $row['status'] = 'public';
253 + $row['status'] = 'public';
619 254 }
620 255
621 256 if ( isset( $item->snippet->status ) && ( 'public' == $item->snippet->status->privacyStatus || 'unlisted' == $item->snippet->status->privacyStatus ) ) {
622 - $row['status'] = 'public';
257 + $row['status'] = 'public';
623 258 }
624 259
625 260 if ( 'youtube#searchResult' == $item->kind ) {
626 - $row['status'] = 'public';
261 + $row['status'] = 'public';
627 262 }
628 263
629 264 // Video publish date
630 265 $row['published_at'] = $item->snippet->publishedAt;
631 266
632 - $datetime = new DateTime( $item->snippet->publishedAt );
633 - $row['published_at_datetime'] = date_format( $datetime, 'Y-m-d H:i:s' );
267 + // Store
268 + $query = $wpdb->prepare( "SELECT NUM FROM $table_name WHERE id = %s", $row['id'] );
269 + $insert_id = $wpdb->get_var( $query );
634 270
635 - // Collect for bulk insert
636 - if ( $has_video_details ) {
637 - $video_placeholders[] = '(%s, %s, %s, %s, %s, %d, %s, %s, %s, %s)';
638 -
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']
271 + if ( empty( $insert_id ) ) {
272 + $wpdb->insert(
273 + $table_name,
274 + $row,
275 + array( '%s', '%s', '%s', '%s', '%s', '%s', '%s' )
651 276 );
652 277 } else {
653 - $video_placeholders[] = '(%s, %s, %s, %s, %s, %s, %s, %s)';
654 -
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']
278 + $wpdb->update(
279 + $table_name,
280 + $row,
281 + array( 'NUM' => $insert_id ),
282 + array( '%s', '%s', '%s', '%s', '%s', '%s', '%s' ),
283 + array( '%d' )
665 284 );
666 285 }
667 -
668 - if ( $store_gallery ) {
669 - $gallery_placeholders[] = '(%s, %s)';
670 - array_push( $gallery_values, $row['video_id'], $gallery_id );
671 - }
672 286 }
673 -
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 - }
701 -
702 - $wpdb->query( $wpdb->prepare( $sql, $video_values ) );
703 - }
704 -
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 ) );
709 - }
710 287 }
711 288
712 289 /**
713 - * Dump our plugin transients.
290 + * Get a single video record from our custom database table "{$wpdb->prefix}ayg_videos"
714 291 *
715 - * @since 2.1.0
716 - * @param string $uid Gallery uid to target. Empty string flushes all plugin transients.
292 + * @since 2.1.0
293 + * @param string $video_id YouTube Video ID.
294 + * @return mixed
717 295 */
718 -function ayg_delete_cache( $uid = '' ) {
719 - $uid = (string) $uid;
720 - $keys = (array) ayg_get_option( 'ayg_transient_keys' );
296 +function ayg_db_get_video( $video_id ) {
297 + global $wpdb;
721 298
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();
299 + $cache_key = 'ayg_'. $video_id;
300 + $row = wp_cache_get( $cache_key );
726 301
727 - foreach ( $keys as $key ) {
728 - if ( 0 === strpos( $key, $prefix ) ) {
729 - delete_transient( $key );
730 - } else {
731 - $remaining[] = $key;
302 + if ( false === $row ) {
303 + $query = $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}ayg_videos WHERE id = %s", $video_id );
304 + $row = $wpdb->get_row( $query );
305 +
306 + if ( $row ) {
307 + if ( ! empty( $row->thumbnails ) ) {
308 + $row->thumbnails = unserialize( $row->thumbnails );
732 309 }
733 - }
734 310
735 - update_option( 'ayg_transient_keys', $remaining, false );
736 - return;
311 + wp_cache_set( $cache_key, $row );
312 + }
737 313 }
738 314
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 );
315 + return $row;
748 316 }
749 317
750 318 /**
751 - * Remove a gallery's excluded videos from the relationship table.
319 + * Dump our plugin transients.
752 320 *
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.
321 + * @since 2.1.0
757 322 */
758 -function ayg_delete_excluded_relationships( $gallery_id, $exclude ) {
759 - global $wpdb;
323 +function ayg_delete_cache() {
324 + delete_option( 'ayg_gallery_page_ids' );
325 +
326 + // Get the current list of transients
327 + $transient_keys = get_option( 'ayg_transient_keys', array() );
760 328
761 - $gallery_id = absint( $gallery_id );
762 -
763 - if ( $gallery_id <= 0 || empty( $exclude ) || ! is_array( $exclude ) ) {
764 - return 0;
329 + // For each key, delete that transient
330 + foreach ( $transient_keys as $key ) {
331 + delete_transient( $key );
765 332 }
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 - );
333 +
334 + // Reset our DB value
335 + update_option( 'ayg_transient_keys', array() );
793 336 }
794 337
795 338 /**
796 339 * Get current address bar URL.
@@ -813,22 +356,11 @@
813 356 */
814 357 function ayg_get_default_settings() {
815 358 $defaults = array(
816 359 'ayg_general_settings' => array(
817 - 'force_load_assets' => array(
818 - 'css' => 'css'
819 - ),
820 - 'api_key' => '',
821 - 'lazyload' => 0,
822 - 'development_mode' => 0
360 + 'api_key' => '',
361 + 'development_mode' => 1
823 362 ),
824 - 'ayg_strings_settings' => array(
825 - 'more_button_label' => __( 'Load More', 'automatic-youtube-gallery' ),
826 - 'previous_button_label' => __( 'Previous', 'automatic-youtube-gallery' ),
827 - 'next_button_label' => __( 'Next', 'automatic-youtube-gallery' ),
828 - 'show_more_label' => __( 'Show More', 'automatic-youtube-gallery' ),
829 - 'show_less_label' => __( 'Show Less', 'automatic-youtube-gallery' )
830 - ),
831 363 'ayg_gallery_settings' => array(
832 364 'theme' => 'classic',
833 365 'columns' => 3,
834 366 'per_page' => 12,
@@ -838,14 +370,13 @@
838 370 'thumb_excerpt' => 1,
839 371 'thumb_excerpt_length' => 75,
840 372 'pagination' => 1,
841 373 'pagination_type' => 'more',
842 - 'scroll_top_offset' => 10
374 + 'more_button_label' => __( 'Load More', 'automatic-youtube-gallery' ),
375 + 'previous_button_label' => __( 'Previous', 'automatic-youtube-gallery' ),
376 + 'next_button_label' => __( 'Next', 'automatic-youtube-gallery' )
843 377 ),
844 378 'ayg_player_settings' => array(
845 - 'player_type' => 'youtube',
846 - 'player_color' => '#00b3ff',
847 - 'player_width' => '',
848 379 'player_ratio' => 56.25,
849 380 'player_title' => 1,
850 381 'player_description' => 1,
851 382 'autoplay' => 0,
@@ -854,8 +385,9 @@
854 385 'muted' => 0,
855 386 'controls' => 1,
856 387 'modestbranding' => 1,
857 388 'cc_load_policy' => 0,
389 + 'iv_load_policy' => 0,
858 390 'hl' => '',
859 391 'cc_lang_pref' => '',
860 392 'privacy_enhanced_mode' => 0,
861 393 'origin' => 0
@@ -878,12 +410,12 @@
878 410 *
879 411 * @since 1.0.0
880 412 * @return array Array of fields.
881 413 */
882 -function ayg_get_editor_fields() {
414 +function ayg_get_editor_fields() {
883 415 $fields = array(
884 416 'source' => array(
885 - 'label' => __( 'Source Configuration', 'automatic-youtube-gallery' ),
417 + 'label' => __( 'General', 'automatic-youtube-gallery' ),
886 418 'fields' => array(
887 419 array(
888 420 'name' => 'type',
889 421 'label' => __( 'Source Type', 'automatic-youtube-gallery' ),
@@ -888,9 +420,17 @@
888 420 'name' => 'type',
889 421 'label' => __( 'Source Type', 'automatic-youtube-gallery' ),
890 422 'description' => '',
891 423 'type' => 'select',
892 - 'options' => ayg_get_source_types(),
424 + 'options' => array(
425 + 'playlist' => __( 'Playlist', 'automatic-youtube-gallery' ),
426 + 'channel' => __( 'Channel', 'automatic-youtube-gallery' ),
427 + 'username' => __( 'Username', 'automatic-youtube-gallery' ),
428 + 'search' => __( 'Search Keywords', 'automatic-youtube-gallery' ),
429 + 'video' => __( 'Single Video', 'automatic-youtube-gallery' ),
430 + 'livestream' => __( 'Livestream', 'automatic-youtube-gallery' ),
431 + 'videos' => __( 'Custom Videos List', 'automatic-youtube-gallery' )
432 + ),
893 433 'value' => 'playlist',
894 434 'sanitize_callback' => 'sanitize_key'
895 435 ),
896 436 array(
@@ -897,18 +437,16 @@
897 437 'name' => 'playlist',
898 438 'label' => __( 'YouTube Playlist ID (or) URL', 'automatic-youtube-gallery' ),
899 439 'description' => sprintf( '%s: https://www.youtube.com/playlist?list=XXXXXXXXXX', __( 'Example', 'automatic-youtube-gallery' ) ),
900 440 'type' => 'url',
901 - 'required' => 1,
902 441 'value' => '',
903 442 'sanitize_callback' => 'sanitize_text_field'
904 443 ),
905 444 array(
906 445 'name' => 'channel',
907 - 'label' => __( 'YouTube Channel ID (or) a Video URL from the Channel', 'automatic-youtube-gallery' ),
446 + 'label' => __( 'YouTube Channel ID (or) a YouTube Video URL from the Channel', 'automatic-youtube-gallery' ),
908 447 'description' => sprintf( '%s: https://www.youtube.com/channel/XXXXXXXXXX', __( 'Example', 'automatic-youtube-gallery' ) ),
909 448 'type' => 'url',
910 - 'required' => 1,
911 449 'value' => '',
912 450 'sanitize_callback' => 'sanitize_text_field'
913 451 ),
914 452 array(
@@ -915,9 +453,8 @@
915 453 'name' => 'username',
916 454 'label' => __( 'YouTube Account Username', 'automatic-youtube-gallery' ),
917 455 'description' => sprintf( '%s: SanRosh', __( 'Example', 'automatic-youtube-gallery' ) ),
918 456 'type' => 'text',
919 - 'required' => 1,
920 457 'value' => '',
921 458 'sanitize_callback' => 'sanitize_text_field'
922 459 ),
923 460 array(
@@ -924,9 +461,8 @@
924 461 'name' => 'search',
925 462 'label' => __( 'Search Keywords', 'automatic-youtube-gallery' ),
926 463 'description' => sprintf( '%s: Cartoon (space:AND , -:NOT , |:OR)', __( 'Example', 'automatic-youtube-gallery' ) ),
927 464 'type' => 'text',
928 - 'required' => 1,
929 465 'value' => '',
930 466 'sanitize_callback' => 'sanitize_text_field'
931 467 ),
932 468 array(
@@ -933,9 +469,8 @@
933 469 'name' => 'video',
934 470 'label' => __( 'YouTube Video ID (or) URL', 'automatic-youtube-gallery' ),
935 471 'description' => sprintf( '%s: https://www.youtube.com/watch?v=XXXXXXXXXX', __( 'Example', 'automatic-youtube-gallery' ) ),
936 472 'type' => 'url',
937 - 'required' => 1,
938 473 'value' => '',
939 474 'sanitize_callback' => 'sanitize_text_field'
940 475 ),
941 476 array(
@@ -942,16 +477,15 @@
942 477 'name' => 'videos',
943 478 'label' => __( 'YouTube Video IDs (or) URLs', 'automatic-youtube-gallery' ),
944 479 'description' => sprintf( '%s: https://www.youtube.com/watch?v=XXXXXXXXXX', __( 'Example', 'automatic-youtube-gallery' ) ),
945 480 'type' => 'textarea',
946 - 'required' => 1,
947 481 'placeholder' => __( 'Enter one video per line', 'automatic-youtube-gallery' ),
948 482 'value' => '',
949 - 'sanitize_callback' => 'sanitize_textarea_field'
483 + 'sanitize_callback' => 'sanitize_text_field'
950 484 ),
951 485 array(
952 486 'name' => 'order',
953 - 'label' => __( 'Search Order', 'automatic-youtube-gallery' ),
487 + 'label' => __( 'Order Videos by', 'automatic-youtube-gallery' ),
954 488 'description' => '',
955 489 'type' => 'select',
956 490 'options' => array(
957 491 'date' => __( 'Date', 'automatic-youtube-gallery' ),
@@ -960,14 +494,14 @@
960 494 'title' => __( 'Title', 'automatic-youtube-gallery' ),
961 495 'viewCount' => __( 'View Count', 'automatic-youtube-gallery' )
962 496 ),
963 497 'value' => 'relevance',
964 - 'sanitize_callback' => 'sanitize_text_field'
498 + 'sanitize_callback' => 'sanitize_key'
965 499 ),
966 500 array(
967 501 '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' ),
502 + 'label' => __( 'Number of Videos', 'automatic-youtube-gallery' ),
503 + '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 504 'type' => 'number',
971 505 'min' => 0,
972 506 'max' => 500,
973 507 'value' => 0,
@@ -978,9 +512,8 @@
978 512 'label' => __( 'Cache Duration', 'automatic-youtube-gallery' ),
979 513 'description' => __( 'Specifies how frequently we should check your YouTube source for new videos/updates.', 'automatic-youtube-gallery' ),
980 514 'type' => 'select',
981 515 'options' => array(
982 - '0' => '— '. __( 'No Caching', 'automatic-youtube-gallery' ) . ' —',
983 516 '900' => __( '15 Minutes', 'automatic-youtube-gallery' ),
984 517 '1800' => __( '30 Minutes', 'automatic-youtube-gallery' ),
985 518 '3600' => __( '1 Hour', 'automatic-youtube-gallery' ),
986 519 '86400' => __( '1 Day', 'automatic-youtube-gallery' ),
@@ -992,30 +525,14 @@
992 525 )
993 526 )
994 527 ),
995 528 'gallery' => array(
996 - 'label' => __( 'Gallery Options', 'automatic-youtube-gallery' ),
529 + 'label' => __( 'Gallery (optional)', 'automatic-youtube-gallery' ),
997 530 'fields' => ayg_get_gallery_settings_fields()
998 531 ),
999 532 'player' => array(
1000 - 'label' => __( 'Player Options', 'automatic-youtube-gallery' ),
533 + 'label' => __( 'Player (optional)', 'automatic-youtube-gallery' ),
1001 534 'fields' => ayg_get_player_settings_fields()
1002 - ),
1003 - 'search' => array(
1004 - 'label' => __( 'Search Form', 'automatic-youtube-gallery' ),
1005 - 'fields' => array(
1006 - array(
1007 - 'name' => 'search_form',
1008 - 'label' => __( 'Search Form', 'automatic-youtube-gallery' ),
1009 - 'description' => sprintf(
1010 - __( 'Check this option to enable the search form. Having issues? <a href="%s" target="_blank" rel="noopener noreferrer">Check here</a>.', 'automatic-youtube-gallery' ),
1011 - 'https://plugins360.com/automatic-youtube-gallery/searchform/'
1012 - ),
1013 - 'type' => 'checkbox',
1014 - 'value' => 0,
1015 - 'sanitize_callback' => 'intval'
1016 - )
1017 - )
1018 535 )
1019 536 );
1020 537
1021 538 return apply_filters( 'ayg_editor_fields', $fields );
@@ -1021,23 +538,8 @@
1021 538 return apply_filters( 'ayg_editor_fields', $fields );
1022 539 }
1023 540
1024 541 /**
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 542 * Get gallery settings fields.
1041 543 *
1042 544 * @since 1.0.0
1043 545 * @return array $fields Array of fields.
@@ -1042,9 +544,9 @@
1042 544 * @since 1.0.0
1043 545 * @return array $fields Array of fields.
1044 546 */
1045 547 function ayg_get_gallery_settings_fields() {
1046 - $gallery_settings = ayg_get_option( 'ayg_gallery_settings' );
548 + $gallery_settings = get_option( 'ayg_gallery_settings' );
1047 549
1048 550 $fields = array(
1049 551 array(
1050 552 'name' => 'theme',
@@ -1069,9 +571,9 @@
1069 571 ),
1070 572 array(
1071 573 'name' => 'per_page',
1072 574 'label' => __( 'Videos per Page', 'automatic-youtube-gallery' ),
1073 - 'description' => __( 'Enter the number of videos to show per page. Maximum of 50.', 'automatic-youtube-gallery' ),
575 + 'description' => __( 'Enter the number of videos to show per page. Maximum of 50.', 'automatic-youtube-gallery' ),
1074 576 'type' => 'number',
1075 577 'min' => 0,
1076 578 'max' => 50,
1077 579 'value' => $gallery_settings['per_page'],
@@ -1080,13 +582,12 @@
1080 582 array(
1081 583 'name' => 'thumb_ratio',
1082 584 'label' => __( 'Image Height (Ratio)', 'automatic-youtube-gallery' ),
1083 585 'description' => __( 'Select the ratio value used to calculate the image height in the gallery thumbnails.', 'automatic-youtube-gallery' ),
1084 - 'type' => 'select',
586 + 'type' => 'radio',
1085 587 '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' )
588 + '56.25' => '16:9',
589 + '75' => '4:3'
1089 590 ),
1090 591 'value' => $gallery_settings['thumb_ratio'],
1091 592 'sanitize_callback' => 'floatval'
1092 593 ),
@@ -1144,8 +645,32 @@
1144 645
1145 646 ),
1146 647 'value' => $gallery_settings['pagination_type'],
1147 648 'sanitize_callback' => 'sanitize_key'
649 + ),
650 + array(
651 + 'name' => 'more_button_label',
652 + 'label' => __( 'More Button Label', 'automatic-youtube-gallery' ),
653 + 'description' => __( 'Enter the more button label text.', 'automatic-youtube-gallery' ),
654 + 'type' => 'text',
655 + 'value' => isset( $gallery_settings['more_button_label'] ) ? $gallery_settings['more_button_label'] : __( 'Load More', 'automatic-youtube-gallery' ),
656 + 'sanitize_callback' => 'sanitize_text_field'
657 + ),
658 + array(
659 + 'name' => 'previous_button_label',
660 + 'label' => __( 'Previous Button Label', 'automatic-youtube-gallery' ),
661 + 'description' => __( 'Enter the previous button label text.', 'automatic-youtube-gallery' ),
662 + 'type' => 'text',
663 + 'value' => isset( $gallery_settings['previous_button_label'] ) ? $gallery_settings['previous_button_label'] : __( 'Previous', 'automatic-youtube-gallery' ),
664 + 'sanitize_callback' => 'sanitize_text_field'
665 + ),
666 + array(
667 + 'name' => 'next_button_label',
668 + 'label' => __( 'Next Button Label', 'automatic-youtube-gallery' ),
669 + 'description' => __( 'Enter the next button label text.', 'automatic-youtube-gallery' ),
670 + 'type' => 'text',
671 + 'value' => isset( $gallery_settings['next_button_label'] ) ? $gallery_settings['next_button_label'] : __( 'Next', 'automatic-youtube-gallery' ),
672 + 'sanitize_callback' => 'sanitize_text_field'
1148 673 )
1149 674 );
1150 675
1151 676 return apply_filters( 'ayg_gallery_settings_fields', $fields );
@@ -1151,222 +676,30 @@
1151 676 return apply_filters( 'ayg_gallery_settings_fields', $fields );
1152 677 }
1153 678
1154 679 /**
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 680 * Get video description to show on top of the player.
1346 681 *
1347 682 * @since 1.0.0
1348 683 * @param stdClass $video YouTube video object.
1349 - * @param array $attributes Array of user attributes.
1350 684 * @param int $words_count Number of words to show by default.
1351 685 * @return string Video description.
1352 686 */
1353 -function ayg_get_player_description( $video, $attributes = array(), $words_count = 30 ) {
687 +function ayg_get_player_description( $video, $words_count = 30 ) {
1354 688 $description = $video->description;
1355 689
1356 690 $words_array = explode( ' ', strip_tags( $description ) );
1357 691 if ( count( $words_array ) > $words_count ) {
1358 - $show_more_label = ! empty( $attributes['show_more_label'] ) ? $attributes['show_more_label'] : __( 'Show More', 'automatic-youtube-gallery' );
1359 692 $words_array[ $words_count ] = '<span class="ayg-player-description-dots">...</span></span><span class="ayg-player-description-more">' . $words_array[ $words_count ];
1360 693
1361 694 $description = '<span class="ayg-player-description-less">' . implode( ' ', $words_array ) . '</span>';
1362 - $description .= '<a href="#" class="ayg-player-description-toggle-btn">' . esc_html( $show_more_label ) . '</a>';
695 + $description .= '<a href="#" class="ayg-player-description-toggle-btn">[+] ' . __( 'Show More', 'automatic-youtube-gallery' ) . '</a>';
1363 696 }
1364 697
1365 698 $description = nl2br( $description );
1366 699 $description = make_clickable( $description );
1367 700
1368 - return apply_filters( 'ayg_player_description', $description, $video, $attributes, $words_count );
701 + return apply_filters( 'ayg_player_description', $description, $video, $words_count );
1369 702 }
1370 703
1371 704 /**
1372 705 * Get player settings fields.
@@ -1374,28 +707,19 @@
1374 707 * @since 1.0.0
1375 708 * @return array $fields Array of fields.
1376 709 */
1377 710 function ayg_get_player_settings_fields() {
1378 - $player_settings = ayg_get_option( 'ayg_player_settings' );
711 + $player_settings = get_option( 'ayg_player_settings' );
1379 712
1380 713 $fields = array(
1381 714 array(
1382 - 'name' => 'player_width',
1383 - 'label' => __( 'Player Width', 'automatic-youtube-gallery' ),
1384 - 'description' => __( 'In pixels. Maximum width of the player. Leave this field empty to scale 100% of its enclosing container/html element.', 'automatic-youtube-gallery' ),
1385 - 'type' => 'text',
1386 - 'value' => isset( $player_settings['player_width'] ) ? $player_settings['player_width'] : '',
1387 - 'sanitize_callback' => 'ayg_sanitize_int'
1388 - ),
1389 - array(
1390 715 'name' => 'player_ratio',
1391 716 'label' => __( 'Player Height (Ratio)', 'automatic-youtube-gallery' ),
1392 717 'description' => __( 'Select the ratio value used to calculate the player height.', 'automatic-youtube-gallery' ),
1393 - 'type' => 'select',
718 + 'type' => 'radio',
1394 719 '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' )
720 + '56.25' => '16:9',
721 + '75' => '4:3'
1398 722 ),
1399 723 'value' => $player_settings['player_ratio'],
1400 724 'sanitize_callback' => 'floatval'
1401 725 ),
@@ -1417,9 +741,9 @@
1417 741 ),
1418 742 array(
1419 743 'name' => 'autoplay',
1420 744 'label' => __( 'Autoplay', 'automatic-youtube-gallery' ),
1421 - 'description' => __( 'Check this option to automatically start playing the initial video when the player loads.', 'automatic-youtube-gallery' ),
745 + 'description' => __( 'Specifies whether the initial video will automatically start to play when the player loads.', 'automatic-youtube-gallery' ),
1422 746 'type' => 'checkbox',
1423 747 'value' => $player_settings['autoplay'],
1424 748 'sanitize_callback' => 'intval'
1425 749 ),
@@ -1425,9 +749,9 @@
1425 749 ),
1426 750 array(
1427 751 'name' => 'autoadvance',
1428 752 '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' ),
753 + 'description' => __( 'Specifies whether to play the next video in the list automatically after previous one end.', 'automatic-youtube-gallery' ),
1430 754 'type' => 'checkbox',
1431 755 'value' => $player_settings['autoadvance'],
1432 756 'sanitize_callback' => 'intval'
1433 757 ),
@@ -1433,9 +757,9 @@
1433 757 ),
1434 758 array(
1435 759 'name' => 'loop',
1436 760 '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' ),
761 + '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 762 'type' => 'checkbox',
1439 763 'value' => $player_settings['loop'],
1440 764 'sanitize_callback' => 'intval'
1441 765 ),
@@ -1457,9 +781,9 @@
1457 781 ),
1458 782 array(
1459 783 'name' => 'modestbranding',
1460 784 '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' ),
785 + '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 786 'type' => 'checkbox',
1463 787 'value' => $player_settings['modestbranding'],
1464 788 'sanitize_callback' => 'intval'
1465 789 ),
@@ -1465,12 +789,20 @@
1465 789 ),
1466 790 array(
1467 791 'name' => 'cc_load_policy',
1468 792 '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' ),
793 + '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 794 'type' => 'checkbox',
1471 795 'value' => $player_settings['cc_load_policy'],
1472 796 'sanitize_callback' => 'intval'
797 + ),
798 + array(
799 + 'name' => 'iv_load_policy',
800 + 'label' => __( 'Show Annotations', 'automatic-youtube-gallery' ),
801 + 'description' => __( 'Choose whether to show annotations or not.', 'automatic-youtube-gallery' ),
802 + 'type' => 'checkbox',
803 + 'value' => $player_settings['iv_load_policy'],
804 + 'sanitize_callback' => 'intval'
1473 805 ),
1474 806 array(
1475 807 'name' => 'hl',
1476 808 'label' => __( 'Player Language', 'automatic-youtube-gallery' ),
@@ -1491,9 +823,25 @@
1491 823 ),
1492 824 'type' => 'text',
1493 825 'value' => $player_settings['cc_lang_pref'],
1494 826 'sanitize_callback' => 'sanitize_text_field'
1495 - )
827 + ),
828 + array(
829 + 'name' => 'privacy_enhanced_mode',
830 + 'label' => __( 'Privacy Enhanced Mode', 'automatic-youtube-gallery' ),
831 + 'description' => __( "Prevent YouTube from leaving tracking cookies on your visitor's browsers unless they actually play the videos. Please uncheck this option if you see errors while testing your playlist embeds or watching your videos on mobile.", 'automatic-youtube-gallery' ),
832 + 'type' => 'checkbox',
833 + 'value' => isset( $player_settings['privacy_enhanced_mode'] ) ? $player_settings['privacy_enhanced_mode'] : 0,
834 + 'sanitize_callback' => 'intval'
835 + ),
836 + array(
837 + 'name' => 'origin',
838 + 'label' => __( 'Extra Player Security', 'automatic-youtube-gallery' ),
839 + 'description' => __( 'Add site origin information with each embed code as an extra security measure. In YouTube\'s own words, checking this option "protects against malicious third-party JavaScript being injected into your page and hijacking control of your YouTube player."', 'automatic-youtube-gallery' ),
840 + 'type' => 'checkbox',
841 + 'value' => isset( $player_settings['origin'] ) ? $player_settings['origin'] : 0,
842 + 'sanitize_callback' => 'intval'
843 + ),
1496 844 );
1497 845
1498 846 return $fields;
1499 847 }
@@ -1511,38 +859,8 @@
1511 859 return apply_filters( 'ayg_single_video_url', '', $video, $attributes );
1512 860 }
1513 861
1514 862 /**
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 863 * Get filtered php template file path.
1546 864 *
1547 865 * @since 1.0.0
1548 866 * @param array $template PHP file path.
@@ -1575,9 +893,9 @@
1575 893 * @since 2.3.0
1576 894 * @return string YouTube embed domain.
1577 895 */
1578 896 function ayg_get_youtube_domain() {
1579 - $player_settings = ayg_get_option( 'ayg_player_settings' );
897 + $player_settings = get_option( 'ayg_player_settings' );
1580 898
1581 899 $domain = 'https://www.youtube.com';
1582 900 if ( isset( $player_settings['privacy_enhanced_mode'] ) && ! empty( $player_settings['privacy_enhanced_mode'] ) ) {
1583 901 $domain = 'https://www.youtube-nocookie.com';
@@ -1586,110 +904,8 @@
1586 904 return $domain;
1587 905 }
1588 906
1589 907 /**
1590 - * Get the YouTube embed URL.
1591 - *
1592 - * @since 2.5.0
1593 - * @param string $video_id YouTube video ID.
1594 - * @param array $attributes Array of user attributes.
1595 - * @return string Player embed URL.
1596 - */
1597 -function ayg_get_youtube_embed_url( $video_id, $attributes = array() ) {
1598 - $player_settings = ayg_get_option( 'ayg_player_settings' );
1599 -
1600 - $player_website = 'https://www.youtube.com';
1601 - if ( isset( $player_settings['privacy_enhanced_mode'] ) && ! empty( $player_settings['privacy_enhanced_mode'] ) ) {
1602 - $player_website = 'https://www.youtube-nocookie.com';
1603 - }
1604 -
1605 - if ( empty( $video_id ) ) {
1606 - return '';
1607 - }
1608 -
1609 - $url = $player_website . '/embed/' . $video_id . '?enablejsapi=1&playsinline=1&rel=0';
1610 -
1611 - if ( isset( $player_settings['origin'] ) && ! empty( $player_settings['origin'] ) ) {
1612 - $site_url_parts = parse_url( site_url() );
1613 - $origin = $site_url_parts['scheme'] . '://' . $site_url_parts['host'];
1614 -
1615 - $url = add_query_arg( 'origin', $origin, $url );
1616 - }
1617 -
1618 - if ( ! is_array( $attributes ) ) {
1619 - $attributes = (array) $attributes;
1620 - }
1621 -
1622 - $autoplay = isset( $attributes['autoplay'] ) ? (int) $attributes['autoplay'] : 0;
1623 - if ( 1 == $autoplay ) {
1624 - $url = add_query_arg( 'autoplay', 1, $url );
1625 - }
1626 -
1627 - $loop = isset( $attributes['loop'] ) ? (int) $attributes['loop'] : 0;
1628 - if ( 1 == $loop ) {
1629 - $url = add_query_arg( 'playlist', $video_id, $url );
1630 - $url = add_query_arg( 'loop', 1, $url );
1631 - }
1632 -
1633 - $muted = isset( $attributes['muted'] ) ? (int) $attributes['muted'] : 0;
1634 - if ( 1 == $muted ) {
1635 - $url = add_query_arg( 'mute', 1, $url );
1636 - }
1637 -
1638 - $controls = isset( $attributes['controls'] ) ? (int) $attributes['controls'] : 1;
1639 - if ( 0 == $controls ) {
1640 - $url = add_query_arg( 'controls', 0, $url );
1641 - }
1642 -
1643 - $modestbranding = isset( $attributes['modestbranding'] ) ? (int) $attributes['modestbranding'] : 0;
1644 - if ( 1 == $modestbranding ) {
1645 - $url = add_query_arg( 'modestbranding', 1, $url );
1646 - }
1647 -
1648 - $cc_load_policy = isset( $attributes['cc_load_policy'] ) ? (int) $attributes['cc_load_policy'] : 0;
1649 - if ( 1 == $cc_load_policy ) {
1650 - $url = add_query_arg( 'cc_load_policy', 1, $url );
1651 - }
1652 -
1653 - if ( isset( $attributes['hl'] ) && ! empty( $attributes['hl'] ) ) {
1654 - $url = add_query_arg( 'hl', sanitize_text_field( $attributes['hl'] ), $url );
1655 - }
1656 -
1657 - if ( isset( $attributes['cc_lang_pref'] ) && ! empty( $attributes['cc_lang_pref'] ) ) {
1658 - $url = add_query_arg( 'cc_lang_pref', sanitize_text_field( $attributes['cc_lang_pref'] ), $url );
1659 - }
1660 -
1661 - return apply_filters( 'ayg_youtube_embed_url', $url, $video_id, $attributes );
1662 -}
1663 -
1664 -/**
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 908 * Inserts a new associative array after another associative array key.
1693 909 *
1694 910 * @since 2.1.0
1695 911 * @param string $key The associative array key to insert after.
@@ -1717,141 +933,8 @@
1717 933 return $array;
1718 934 }
1719 935
1720 936 /**
1721 - * Detect if the client is using an iOS device (iPhone, iPad, or iPod).
1722 - *
1723 - * @return bool True if the user agent string suggests an iOS device, false otherwise.
1724 - */
1725 -function ayg_is_ios() {
1726 - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
1727 - return false;
1728 - }
1729 -
1730 - $ua = strtolower( $_SERVER['HTTP_USER_AGENT'] );
1731 -
1732 - if (
1733 - strpos( $ua, 'iphone' ) !== false ||
1734 - strpos( $ua, 'ipad' ) !== false ||
1735 - strpos( $ua, 'ipod' ) !== false
1736 - ) {
1737 - return true;
1738 - }
1739 -
1740 - return false;
1741 -}
1742 -
1743 -/**
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 937 * Sanitize the integer inputs, accepts empty values.
1855 938 *
1856 939 * @since 1.0.0
1857 940 * @param string|int $value Input value.
@@ -1862,49 +945,8 @@
1862 945 return ( 0 == $value ) ? '' : $value;
1863 946 }
1864 947
1865 948 /**
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 949 * Trims text to a certain number of characters.
1908 950 *
1909 951 * @since 2.0.0
1910 952 * @param string $text Text to trim.
@@ -1931,51 +973,17 @@
1931 973
1932 974 $text .= $append;
1933 975 }
1934 976
1935 - return apply_filters( 'ayg_trim_words', $text, $original_text, $num_characters, $append );
977 + return apply_filters( 'ayg_trim_words', $text, $original_text, $num_characters, $append );
1936 978 }
1937 979
1938 980 /**
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;
1970 -}
1971 -
1972 -/**
1973 981 * Gallery HTML output.
1974 982 *
1975 - * @since 1.0.0
1976 - * @param array $video YouTube video object.
1977 - * @param array $attributes Array of user attributes.
983 + * @since 1.0.0
984 + * @param array $video YouTube video object.
985 + * @param array $attributes Array of user attributes.
1978 986 */
1979 987 function the_ayg_gallery_thumbnail( $video, $attributes ) {
1980 988 include ayg_get_template( AYG_DIR . 'public/templates/thumbnail.php' );
1981 989 }
@@ -1982,35 +990,12 @@
1982 990
1983 991 /**
1984 992 * Pagination HTML output.
1985 993 *
1986 - * @since 1.0.0
1987 - * @param array $attributes Array of user attributes.
994 + * @since 1.0.0
995 + * @param array $attributes Array of user attributes.
1988 996 */
1989 997 function the_ayg_pagination( $attributes ) {
1990 998 if ( ! empty( $attributes['pagination'] ) ) {
1991 999 include ayg_get_template( AYG_DIR . 'public/templates/pagination.php' );
1992 1000 }
1993 -}
1994 -
1995 -/**
1996 - * Search Form HTML output.
1997 - *
1998 - * @since 2.5.7
1999 - * @param array $attributes Array of user attributes.
2000 - */
2001 -function the_ayg_search_form( $attributes ) {
2002 - if ( ! empty( $attributes['search_form'] ) ) {
2003 - include ayg_get_template( AYG_DIR . 'public/templates/search-form.php' );
2004 - }
2005 -}
2006 -
2007 -/**
2008 - * Gallery HTML output.
2009 - *
2010 - * @since 2.5.0
2011 - * @param array $video YouTube video object.
2012 - * @param array $attributes Array of user attributes.
2013 - */
2014 -function the_ayg_player( $video, $attributes ) {
2015 - include ayg_get_template( AYG_DIR . 'public/templates/player.php' );
2016 1001 }