PluginProbe
Automatic YouTube Gallery – Embed Auto-Updating YouTube Video Galleries, Feeds, Playlists & Channels / 2.6.3
Automatic YouTube Gallery – Embed Auto-Updating YouTube Video Galleries, Feeds, Playlists & Channels v2.6.3
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 | admin/admin.php +64 -440 trunk2.6.3 View file →
@@ -21,17 +21,8 @@
21 21 */
22 22 class AYG_Admin {
23 23
24 24 /**
25 - * Gallery list table instance, shared between bulk-action processing
26 - * and rendering, to avoid instantiating it twice per request.
27 - *
28 - * @since 2.8.0
29 - * @var AYG_Admin_Gallery_List_Table
30 - */
31 - private $gallery_list_table;
32 -
33 - /**
34 25 * Insert missing plugin options.
35 26 *
36 27 * @since 1.6.4
37 28 */
@@ -38,18 +29,12 @@
38 29 public function insert_missing_options() {
39 30 if ( AYG_VERSION !== get_option( 'ayg_version' ) ) {
40 31 $defaults = ayg_get_default_settings();
41 32
42 - // Insert the gallery settings
43 - $gallery_settings = get_option( 'ayg_gallery_settings' );
44 -
45 - if ( ! is_array( $gallery_settings ) || empty( $gallery_settings ) ) {
46 - $gallery_settings = $defaults['ayg_gallery_settings'];
47 - update_option( 'ayg_gallery_settings', $gallery_settings );
48 - }
49 -
50 33 // Insert the strings settings
51 34 if ( false == get_option( 'ayg_strings_settings' ) ) {
35 + $gallery_settings = get_option( 'ayg_gallery_settings' );
36 +
52 37 $strings_settings = array(
53 38 'more_button_label' => ! empty( $gallery_settings['more_button_label'] ) ? $gallery_settings['more_button_label'] : $defaults['ayg_strings_settings']['more_button_label'],
54 39 'previous_button_label' => ! empty( $gallery_settings['previous_button_label'] ) ? $gallery_settings['previous_button_label'] : $defaults['ayg_strings_settings']['previous_button_label'],
55 40 'next_button_label' => ! empty( $gallery_settings['next_button_label'] ) ? $gallery_settings['next_button_label'] : $defaults['ayg_strings_settings']['next_button_label'],
@@ -62,13 +47,8 @@
62 47
63 48 // Update the player settings
64 49 $player_settings = get_option( 'ayg_player_settings' );
65 50
66 - if ( ! is_array( $player_settings ) || empty( $player_settings ) ) {
67 - $player_settings = $defaults['ayg_player_settings'];
68 - update_option( 'ayg_player_settings', $player_settings );
69 - }
70 -
71 51 if ( ! array_key_exists( 'player_type', $player_settings ) ) {
72 52 $player_settings['player_type'] = $defaults['ayg_player_settings']['player_type'];
73 53 $player_settings['player_color'] = $defaults['ayg_player_settings']['player_color'];
74 54
@@ -103,15 +83,23 @@
103 83 */
104 84 public function enqueue_styles() {
105 85 wp_enqueue_style( 'wp-color-picker' );
106 86
107 - wp_enqueue_style(
108 - AYG_SLUG . '-admin',
109 - AYG_URL . 'admin/assets/css/admin.min.css',
110 - array(),
111 - AYG_VERSION,
112 - 'all'
87 + wp_enqueue_style(
88 + AYG_SLUG . '-magnific-popup',
89 + AYG_URL . 'vendor/magnific-popup/magnific-popup.css',
90 + array(),
91 + '1.2.0',
92 + 'all'
113 93 );
94 +
95 + wp_enqueue_style(
96 + AYG_SLUG . '-admin',
97 + AYG_URL . 'admin/assets/css/admin.min.css',
98 + array(),
99 + AYG_VERSION,
100 + 'all'
101 + );
114 102 }
115 103
116 104 /**
117 105 * Enqueue scripts for the admin area.
@@ -121,71 +109,35 @@
121 109 public function enqueue_scripts() {
122 110 wp_enqueue_media();
123 111 wp_enqueue_script( 'wp-color-picker' );
124 112
125 - wp_enqueue_script(
126 - AYG_SLUG . '-admin',
127 - AYG_URL . 'admin/assets/js/admin.min.js',
128 - array( 'jquery' ),
129 - AYG_VERSION,
130 - false
113 + wp_enqueue_script(
114 + AYG_SLUG . '-magnific-popup',
115 + AYG_URL . 'vendor/magnific-popup/magnific-popup.min.js',
116 + array( 'jquery' ),
117 + '1.2.0',
118 + array( 'strategy' => 'defer' )
131 119 );
132 120
133 - wp_localize_script(
134 - AYG_SLUG . '-admin',
135 - 'ayg_admin',
121 + wp_enqueue_script(
122 + AYG_SLUG . '-admin',
123 + AYG_URL . 'admin/assets/js/admin.min.js',
124 + array( 'jquery' ),
125 + AYG_VERSION,
126 + false
127 + );
128 +
129 + wp_localize_script(
130 + AYG_SLUG . '-admin',
131 + 'ayg_admin',
136 132 array(
137 133 'ajax_nonce' => wp_create_nonce( 'ayg_ajax_nonce' ),
138 - 'admin_url' => admin_url( 'admin.php' ),
139 - 'live_types' => array( 'search', 'livestream', 'video' ), // Rendered live — source stays editable (mirrors gallery-form.php).
140 - 'i18n' => array(
141 - // API key
142 - 'invalid_api_key' => __( 'Please enter your YouTube API key.', 'automatic-youtube-gallery' ),
143 -
144 - // Settings
145 - 'cache_cleared' => __( 'Cleared', 'automatic-youtube-gallery' ),
146 -
147 - // Common
148 - 'save_failed' => __( 'Save failed. Please try again.', 'automatic-youtube-gallery' ),
149 - 'delete_failed' => __( 'Delete failed. Please try again.', 'automatic-youtube-gallery' ),
150 -
151 - // Gallery: Shortcode
152 - 'shortcode_copied' => __( 'Shortcode copied! Paste it into any post, page, or widget to display your gallery.', 'automatic-youtube-gallery' ),
153 -
154 - // Gallery: Delete confirmations
155 - 'confirm_delete' => __( 'Are you sure you want to delete this gallery? This action cannot be undone.', 'automatic-youtube-gallery' ),
156 - 'confirm_bulk_delete' => __( 'Are you sure you want to delete the selected galleries? This action cannot be undone.', 'automatic-youtube-gallery' ),
157 -
158 - // Gallery Form
159 - 'unsaved_changes' => __( 'You have unsaved changes. Are you sure you want to leave?', 'automatic-youtube-gallery' ),
160 - 'update_gallery' => __( 'Update Gallery', 'automatic-youtube-gallery' ),
161 -
162 - // Gallery Form: Source validation
163 - 'source_required' => array(
164 - 'channel' => __( 'A YouTube channel ID (or) a video URL from the channel is required.', 'automatic-youtube-gallery' ),
165 - 'playlist' => __( 'A YouTube playlist ID (or) URL is required.', 'automatic-youtube-gallery' ),
166 - 'username' => __( 'A YouTube account username is required.', 'automatic-youtube-gallery' ),
167 - 'search' => __( 'A search keyword is required.', 'automatic-youtube-gallery' ),
168 - 'livestream' => __( 'A YouTube channel ID (or) a video URL from the channel is required.', 'automatic-youtube-gallery' ),
169 - 'video' => __( 'A YouTube video ID (or) URL is required.', 'automatic-youtube-gallery' ),
170 - 'videos' => __( 'At least one YouTube video ID (or) URL is required.', 'automatic-youtube-gallery' )
171 - ),
172 - 'channel_handle' => __( 'YouTube @handle URLs aren’t supported here. Please enter a channel ID, a /channel/ URL, or a video URL from the channel.', 'automatic-youtube-gallery' ),
173 -
174 - // Source hint text, swapped live as the source-type dropdown changes. Must match
175 - // the strings rendered in gallery-form.php's .ayg-source-hint (live / importable).
176 - 'source_live' => __( 'This is a live source — you can change it anytime. Updates take effect immediately the next time the gallery is viewed.', 'automatic-youtube-gallery' ),
177 - 'source_importable' => __( 'The video source can’t be changed once videos are imported. So please make sure you select the right source before saving.', 'automatic-youtube-gallery' ),
178 -
179 - // Gallery Form: Import progress
180 - 'processing' => __( 'Processing', 'automatic-youtube-gallery' ),
181 - 'import_progress' => __( 'Imported: %imported%, Updated: %updated%. Please do not close this window until you see a success or error message', 'automatic-youtube-gallery' ),
182 - 'import_complete' => __( 'Done! Imported: %imported%, Updated: %updated%, Deleted: %deleted%. Refreshing the page', 'automatic-youtube-gallery' ),
183 - 'import_failed' => __( 'Import failed. Please try again.', 'automatic-youtube-gallery' ),
184 - 'quota_exceeded' => __( 'YouTube API quota exceeded. The import has been paused — click "Update Gallery" to resume after the quota resets.', 'automatic-youtube-gallery' )
185 - )
134 + 'i18n' => array(
135 + 'invalid_api_key' => __( 'Invalid API Key', 'automatic-youtube-gallery' ),
136 + 'cleared' => __( 'Cleared', 'automatic-youtube-gallery' )
137 + )
186 138 )
187 - );
139 + );
188 140 }
189 141
190 142 /**
191 143 * Add dashboard page link on the plugins menu.
@@ -210,22 +162,19 @@
210 162 * Add "Dashboard" menu.
211 163 *
212 164 * @since 1.3.0
213 165 */
214 - public function admin_menu() {
215 - $hook = add_menu_page(
216 - __( 'Automatic YouTube Gallery', 'automatic-youtube-gallery' ),
166 + public function admin_menu() {
167 + add_menu_page(
168 + __( 'Automatic YouTube Gallery', 'automatic-youtube-gallery' ),
217 169 __( 'YouTube Gallery', 'automatic-youtube-gallery' ),
218 - 'manage_options',
219 - 'automatic-youtube-gallery',
170 + 'manage_options',
171 + 'automatic-youtube-gallery',
220 172 array( $this, 'display_dashboard_content' ),
221 - 'dashicons-format-video',
222 - 10
173 + 'dashicons-video-alt3',
174 + 10
223 175 );
224 176
225 - // Process list table bulk actions before any HTML is output to allow wp_redirect().
226 - add_action( 'load-' . $hook, array( $this, 'process_list_table_bulk_actions' ) );
227 -
228 177 add_submenu_page(
229 178 'automatic-youtube-gallery',
230 179 __( 'Dashboard', 'automatic-youtube-gallery' ),
231 180 __( 'Dashboard', 'automatic-youtube-gallery' ),
@@ -235,56 +184,22 @@
235 184 );
236 185 }
237 186
238 187 /**
239 - * Runs before the dashboard page HTML is output: hides the Screen Options tab
240 - * and processes list-table bulk actions on the list view.
241 - *
242 - * @since 2.8.0
243 - */
244 - public function process_list_table_bulk_actions() {
245 - $general_settings = ayg_get_option( 'ayg_general_settings' );
246 -
247 - // Hide the Screen Options tab entirely. The list table would otherwise
248 - // auto-populate a "Columns" section there, which we don't want on our
249 - // custom dashboard header.
250 - add_filter( 'screen_options_show_screen', '__return_false' );
251 -
252 - // No list table is rendered on the setup screen or the gallery form view.
253 - $action = isset( $_GET['action'] ) ? sanitize_text_field( $_GET['action'] ) : '';
254 - if ( empty( $general_settings['api_key'] ) || 'new' === $action || 'edit' === $action ) {
255 - return;
256 - }
257 -
258 - // Strip the GET search form's leftover bulk-action fields from the URL.
259 - add_filter( 'removable_query_args', array( $this, 'add_removable_query_args' ) );
260 -
261 - // Process bulk actions
262 - require_once AYG_DIR . 'admin/galleries.php';
263 -
264 - $this->gallery_list_table = new AYG_Admin_Gallery_List_Table();
265 - $this->gallery_list_table->process_bulk_action();
266 - }
267 -
268 - /**
269 - * Add the gallery list table's bulk-action fields to the list of query args
270 - * WordPress strips from the URL via wp_admin_canonical_url().
271 - *
272 - * @since 2.8.0
273 - * @param array $args Removable query argument names.
274 - * @return array
275 - */
276 - public function add_removable_query_args( $args ) {
277 - return array_merge( $args, array( 'action', 'action2', 'bulk_action', '_wpnonce', '_wp_http_referer' ) );
278 - }
279 -
280 - /**
281 188 * Display dashboard content.
282 189 *
283 190 * @since 1.3.0
284 191 */
285 192 public function display_dashboard_content() {
286 - require_once AYG_DIR . 'admin/templates/dashboard.php';
193 + $general_settings = get_option( 'ayg_general_settings' );
194 +
195 + $tabs = array(
196 + 'dashboard' => __( 'Build Gallery', 'automatic-youtube-gallery' )
197 + );
198 +
199 + $active_tab = isset( $_GET['tab'] ) ? sanitize_text_field( $_GET['tab'] ) : 'dashboard';
200 +
201 + require_once AYG_DIR . 'admin/templates/dashboard.php';
287 202 }
288 203
289 204 /**
290 205 * Prints admin screen notices.
@@ -291,27 +206,13 @@
291 206 *
292 207 * @since 2.0.0
293 208 */
294 209 public function admin_notices() {
295 - $screen = get_current_screen();
296 - $on_page = $screen && 'toplevel_page_automatic-youtube-gallery' === $screen->id;
210 + $general_settings = get_option( 'ayg_general_settings' );
297 211
298 - // Show on all admin pages except our own gallery form (action=new|edit).
299 - if ( $on_page ) {
300 - $action = isset( $_GET['action'] ) ? sanitize_text_field( $_GET['action'] ) : '';
301 - if ( 'new' === $action || 'edit' === $action ) {
302 - return;
303 - }
304 - }
305 -
306 - $general_settings = ayg_get_option( 'ayg_general_settings' );
307 -
308 - if ( ! empty( $general_settings['development_mode'] ) ) {
309 - // WordPress relocates non-inline notices on our custom dashboard header, hiding them.
310 - // Add the "inline" class only there so the notice stays put; leave it off elsewhere.
311 - $classes = 'notice notice-info' . ( $on_page ? ' inline' : '' );
212 + if ( isset( $general_settings['development_mode'] ) && ! empty( $general_settings['development_mode'] ) ) {
312 213 ?>
313 - <div class="<?php echo esc_attr( $classes ); ?>">
214 + <div class="notice notice-info">
314 215 <p>
315 216 <?php
316 217 printf(
317 218 __( '<strong>Automatic YouTube Gallery:</strong> You have <a href="%s">development mode</a> enabled. We do not cache API results in this mode. While this is ok when you are testing the plugin, we strongly recommend disabling this option when your site goes live.', 'automatic-youtube-gallery' ),
@@ -328,295 +229,18 @@
328 229 * Save API Key.
329 230 *
330 231 * @since 1.3.0
331 232 */
332 - public function ajax_callback_save_api_key() {
233 + public function ajax_callback_save_api_key() {
333 234 check_ajax_referer( 'ayg_ajax_nonce', 'security' );
235 +
236 + if ( current_user_can( 'manage_options' ) ) {
237 + $general_settings = get_option( 'ayg_general_settings' );
238 + $general_settings['api_key'] = sanitize_text_field( $_POST['api_key'] );
334 239
335 - if ( ! current_user_can( 'manage_options' ) ) {
336 - wp_send_json_error( array( 'message' => __( 'Permission denied.', 'automatic-youtube-gallery' ) ) );
240 + update_option( 'ayg_general_settings', $general_settings );
337 241 }
338 242
339 - $general_settings = ayg_get_option( 'ayg_general_settings' );
340 -
341 - $general_settings['api_key'] = sanitize_text_field( $_POST['api_key'] );
342 - update_option( 'ayg_general_settings', $general_settings );
343 -
344 - wp_send_json_success();
345 - }
346 -
347 - /**
348 - * Save a gallery (insert or update).
349 - *
350 - * @since 2.8.0
351 - */
352 - public function ajax_callback_save_gallery() {
353 - check_ajax_referer( 'ayg_ajax_nonce', 'security' );
354 -
355 - if ( ! current_user_can( 'manage_options' ) ) {
356 - wp_send_json_error( array( 'message' => __( 'Permission denied.', 'automatic-youtube-gallery' ) ) );
357 - }
358 -
359 - $gallery_id = absint( isset( $_POST['gallery_id'] ) ? $_POST['gallery_id'] : 0 );
360 - $gallery_title = sanitize_text_field( isset( $_POST['title'] ) ? $_POST['title'] : '' );
361 -
362 - $params = array();
363 - $source_locked = false;
364 - $source_type = '';
365 - $source_value = '';
366 -
367 - if ( $gallery_id > 0 ) {
368 - $existing = ayg_get_gallery( $gallery_id );
369 -
370 - if ( ! $existing ) {
371 - wp_send_json_error( array( 'message' => __( 'Gallery not found.', 'automatic-youtube-gallery' ) ) );
372 - }
373 -
374 - // Seed params from the saved row so values from disabled inputs and runtime
375 - // keys (page_token, last_video_published_at) survive the update.
376 - $saved_params = json_decode( (string) $existing->params, true );
377 - if ( is_array( $saved_params ) ) {
378 - $params = $saved_params;
379 - }
380 -
381 - // The source locks only once videos have been imported. While the gallery
382 - // still has no videos (e.g. the first import failed on a bad source), the
383 - // source stays editable so it can be corrected in place. Live sources
384 - // (search / livestream / single video) never import, so they never lock.
385 - $source_locked = ( (int) $existing->video_count > 0 && ! in_array( $existing->source_type, array( 'search', 'livestream', 'video' ), true ) );
386 -
387 - if ( $source_locked ) {
388 - $source_type = $existing->source_type;
389 - $source_value = $existing->source_value;
390 - }
391 - }
392 -
393 - // Read the source from POST for a new gallery, or for an update before the first
394 - // import. A re-edited source starts a clean import, so drop the runtime keys.
395 - if ( ! $source_locked ) {
396 - unset( $params['page_token'], $params['last_video_published_at'] );
397 -
398 - $allowed_source_types = ayg_get_source_types();
399 - $source_type = sanitize_key( isset( $_POST['type'] ) ? $_POST['type'] : 'playlist' );
400 - if ( ! array_key_exists( $source_type, $allowed_source_types ) ) {
401 - $source_type = 'playlist';
402 - }
403 -
404 - switch ( $source_type ) {
405 - case 'channel':
406 - case 'livestream':
407 - $source_value = sanitize_text_field( isset( $_POST['channel'] ) ? $_POST['channel'] : '' );
408 - break;
409 -
410 - case 'playlist':
411 - $source_value = sanitize_text_field( isset( $_POST['playlist'] ) ? $_POST['playlist'] : '' );
412 - break;
413 -
414 - case 'username':
415 - $source_value = sanitize_text_field( isset( $_POST['username'] ) ? $_POST['username'] : '' );
416 - break;
417 -
418 - case 'search':
419 - $source_value = sanitize_text_field( isset( $_POST['search'] ) ? $_POST['search'] : '' );
420 - break;
421 -
422 - case 'video':
423 - $source_value = sanitize_text_field( isset( $_POST['video'] ) ? $_POST['video'] : '' );
424 - break;
425 -
426 - case 'videos':
427 - $source_value = sanitize_textarea_field( isset( $_POST['videos'] ) ? $_POST['videos'] : '' );
428 - break;
429 -
430 - default:
431 - $source_value = '';
432 - }
433 - }
434 -
435 - $exclude = sanitize_textarea_field( isset( $_POST['exclude'] ) ? $_POST['exclude'] : '' );
436 - if ( ! empty( $exclude ) ) $exclude = array_values( array_filter( array_map( 'trim', explode( "\n", $exclude ) ) ) );
437 - $params['exclude'] = $exclude;
438 -
439 - // 'paused' is a non-numeric sentinel (stop automatic imports); every other value is seconds.
440 - $schedule = sanitize_text_field( isset( $_POST['schedule'] ) ? $_POST['schedule'] : 86400 );
441 - $params['schedule'] = ( 'paused' === $schedule ) ? 'paused' : absint( $schedule );
442 -
443 - // Display-time sort (gallery-form-only fields). Validated against whitelists; get_videos_from_db()
444 - // maps them into a safe ORDER BY. Defaults reproduce the prior hardcoded "newest first".
445 - $sort_by = sanitize_key( isset( $_POST['sort_by'] ) ? $_POST['sort_by'] : '' );
446 - $params['sort_by'] = in_array( $sort_by, array( 'date', 'title', 'duration', 'random' ), true ) ? $sort_by : 'date';
447 -
448 - $sort_order = sanitize_key( isset( $_POST['sort_order'] ) ? $_POST['sort_order'] : '' );
449 - $params['sort_order'] = ( 'asc' === $sort_order ) ? 'asc' : 'desc';
450 -
451 - // Display-time duration filter. get_videos_from_db() applies it only when a direction is set.
452 - $duration_filter = sanitize_key( isset( $_POST['duration_filter'] ) ? $_POST['duration_filter'] : '' );
453 - $params['duration_filter'] = in_array( $duration_filter, array( 'long', 'short' ), true ) ? $duration_filter : '';
454 - $params['duration'] = absint( isset( $_POST['duration'] ) ? $_POST['duration'] : 0 );
455 -
456 - // Fields stored in dedicated DB columns, not in params. 'cache' is kept (saved to params) so
457 - // the live 'search' source type can control its API cache duration.
458 - $fields = ayg_get_editor_fields();
459 - $excluded = array( 'type', 'channel', 'playlist', 'username', 'search', 'video', 'videos' );
460 -
461 - foreach ( $fields as $section ) {
462 - foreach ( $section['fields'] as $field ) {
463 - $name = $field['name'];
464 -
465 - if ( in_array( $name, $excluded, true ) ) {
466 - continue;
467 - }
468 -
469 - if ( 'checkbox' === $field['type'] ) {
470 - $params[ $name ] = isset( $_POST[ $name ] ) ? 1 : 0;
471 - } elseif ( ! isset( $_POST[ $name ] ) ) {
472 - continue;
473 - } elseif ( 'select' === $field['type'] && isset( $field['options'] ) ) {
474 - $raw = sanitize_text_field( $_POST[ $name ] );
475 - $params[ $name ] = array_key_exists( $raw, $field['options'] ) ? $raw : sanitize_text_field( $field['value'] );
476 - } else {
477 - $sanitize_callback = ! empty( $field['sanitize_callback'] ) ? $field['sanitize_callback'] : 'sanitize_text_field';
478 - $params[ $name ] = call_user_func( $sanitize_callback, $_POST[ $name ] );
479 - }
480 - }
481 - }
482 -
483 - global $wpdb;
484 - $table = $wpdb->prefix . 'ayg_galleries';
485 -
486 - $now = current_time( 'mysql' );
487 -
488 - $data = array(
489 - 'title' => $gallery_title,
490 - 'params' => wp_json_encode( $params ),
491 - 'updated_at' => $now
492 - );
493 -
494 - // Persist the source columns whenever they aren't locked (new gallery, or an
495 - // update before the first import). All columns here are strings, so the format
496 - // is left to default ('%s') instead of tracking a positional list.
497 - if ( ! $source_locked ) {
498 - $data['source_type'] = $source_type;
499 - $data['source_value'] = $source_value;
500 - }
501 -
502 - // A "paused" schedule stops automatic imports: flag the gallery paused, clear any prior
503 - // import error (so a user pause is distinguishable from a quota pause, which keeps its
504 - // message), and drop it from the cron queue. The JS save flow skips the import for this case.
505 - if ( 'paused' === $params['schedule'] ) {
506 - $data['import_status'] = 'paused';
507 - $data['import_error'] = '';
508 - $data['next_import_at'] = null;
509 - } elseif ( 0 === $params['schedule'] ) {
510 - // "Only Once" is non-recurring: ensure no future cron run stays queued. Switching here from a
511 - // recurring schedule may have left a stale next_import_at behind; clear it so a display-only
512 - // save (import skipped) can't trigger one more unexpected background import. A save that does
513 - // import will have import_batch() finalize the status / next run on completion anyway.
514 - $data['next_import_at'] = null;
515 - }
516 -
517 - if ( $gallery_id > 0 ) {
518 - if ( empty( $gallery_title ) ) {
519 - $gallery_title = sprintf( __( 'Gallery %d', 'automatic-youtube-gallery' ), $gallery_id );
520 - $data['title'] = $gallery_title;
521 - }
522 -
523 - // Drop any newly-excluded videos from this gallery's links (the videos table is left intact —
524 - // rows may be shared by other galleries) and fold the refreshed count into this same update.
525 - if ( ayg_delete_excluded_relationships( $gallery_id, $params['exclude'] ) > 0 ) {
526 - $data['video_count'] = (int) $wpdb->get_var(
527 - $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}ayg_gallery_relationships WHERE gallery_id = %s", strval( $gallery_id ) )
528 - );
529 - }
530 -
531 - $wpdb->update(
532 - $table,
533 - $data,
534 - array( 'id' => $gallery_id ),
535 - null,
536 - array( '%d' )
537 - );
538 - } else {
539 - $data['created_at'] = $now;
540 -
541 - $wpdb->insert( $table, $data );
542 -
543 - $gallery_id = (int) $wpdb->insert_id;
544 -
545 - if ( empty( $gallery_title ) ) {
546 - $gallery_title = sprintf( __( 'Gallery %d', 'automatic-youtube-gallery' ), $gallery_id );
547 -
548 - $wpdb->update(
549 - $table,
550 - array( 'title' => $gallery_title ),
551 - array( 'id' => $gallery_id ),
552 - array( '%s' ),
553 - array( '%d' )
554 - );
555 - }
556 - }
557 -
558 - // Live galleries (search / livestream / single video) render from the API and cache responses. Clear
559 - // this gallery's cache on save so the front-end reflects the latest results right after "Update Gallery".
560 - if ( in_array( $source_type, array( 'search', 'livestream', 'video' ), true ) ) {
561 - ayg_delete_cache( strval( $gallery_id ) );
562 - }
563 -
564 - wp_send_json_success( array(
565 - 'gallery_id' => $gallery_id,
566 - 'shortcode' => sprintf( '[automatic_youtube_gallery id="%d"]', $gallery_id ),
567 - ) );
568 - }
569 -
570 - /**
571 - * Import one batch of videos into a gallery via AJAX.
572 - *
573 - * The client calls this in a loop, passing back the page_token returned by
574 - * the previous call, until done = true.
575 - *
576 - * @since 2.8.0
577 - */
578 - public function ajax_callback_import_gallery() {
579 - check_ajax_referer( 'ayg_ajax_nonce', 'security' );
580 -
581 - if ( ! current_user_can( 'manage_options' ) ) {
582 - wp_send_json_error( array( 'message' => __( 'Permission denied.', 'automatic-youtube-gallery' ) ) );
583 - }
584 -
585 - $gallery_id = absint( isset( $_POST['id'] ) ? $_POST['id'] : 0 );
586 - $page_token = sanitize_text_field( isset( $_POST['page_token'] ) ? $_POST['page_token'] : '' );
587 -
588 - // The browser-driven import is always a manual run: full re-scan, uncapped, prunes deletions.
589 - $importer = new AYG_Import();
590 - $result = $importer->import_batch( $gallery_id, $page_token, true );
591 -
592 - if ( isset( $result['error'] ) ) {
593 - wp_send_json_error( array(
594 - 'message' => $result['error'],
595 - 'quota_exceeded' => ! empty( $result['quota_exceeded'] )
596 - ) );
597 - }
598 -
599 - wp_send_json_success( $result );
600 - }
601 -
602 - /**
603 - * Delete a gallery via AJAX.
604 - *
605 - * @since 2.8.0
606 - */
607 - public function ajax_callback_delete_gallery() {
608 - check_ajax_referer( 'ayg_ajax_nonce', 'security' );
609 -
610 - if ( ! current_user_can( 'manage_options' ) ) {
611 - wp_send_json_error( array( 'message' => __( 'Permission denied.', 'automatic-youtube-gallery' ) ) );
612 - }
613 -
614 - require_once AYG_DIR . 'admin/galleries.php';
615 -
616 - $id = absint( isset( $_POST['id'] ) ? $_POST['id'] : 0 );
617 - AYG_Admin_Gallery_List_Table::delete_gallery( $id );
618 -
619 - wp_send_json_success();
243 + wp_die();
620 244 }
621 245
622 246 }