PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 4.0.9
Visualizer – Tables & Charts Manager with Built-in AI Generator v4.0.9
4.0.9 4.0.8 4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.10.1 3.10.10 3.10.11 3.10.12 3.10.13 3.10.14 3.10.15 3.10.2 All 150 releases
← All changes | classes/Visualizer/Module/Setup.php +447 -45 3.1.0 → 4.0.9 View file →
@@ -31,8 +31,28 @@
31 31
32 32 const NAME = __CLASS__;
33 33
34 34 /**
35 + * Hook that refreshes database charts.
36 + */
37 + const REFRESH_DB_HOOK = 'visualizer_schedule_refresh_db';
38 +
39 + /**
40 + * Action Scheduler group that owns the refresh.
41 + */
42 + const REFRESH_DB_GROUP = 'visualizer';
43 +
44 + /**
45 + * Marks the refresh trigger as checked recently.
46 + */
47 + const REFRESH_DB_CHECK_TRANSIENT = 'visualizer-refresh-db-checked';
48 +
49 + /**
50 + * Seconds a check stays valid; matches Action Scheduler's timeout for a killed run.
51 + */
52 + const REFRESH_DB_CHECK_WINDOW = 300;
53 +
54 + /**
35 55 * Constructor.
36 56 *
37 57 * @since 1.0.0
38 58 *
@@ -43,16 +63,24 @@
43 63 parent::__construct( $plugin );
44 64
45 65 register_activation_hook( VISUALIZER_BASEFILE, array( $this, 'activate' ) );
46 66 register_deactivation_hook( VISUALIZER_BASEFILE, array( $this, 'deactivate' ) );
47 - $this->_addAction( 'visualizer_schedule_refresh_db', 'refreshDbChart' );
48 - $this->_addFilter( 'visualizer_schedule_refresh_chart', 'refresh_db_for_chart', null, 10, 3 );
67 + $this->_addAction( self::REFRESH_DB_HOOK, 'refreshDbChart' );
68 + $this->_addAction( 'init', 'maybe_reschedule_refresh_db' );
69 + $this->_addAction( 'action_scheduler_ensure_recurring_actions', 'ensure_refresh_db_action' );
70 + $this->_addFilter( 'visualizer_schedule_refresh_chart', 'refresh_db_for_chart', 10, 3 );
49 71
72 + $this->_addAction( 'admin_init', 'adminInit' );
50 73 $this->_addAction( 'init', 'setupCustomPostTypes' );
74 + $this->_addFilter( 'cron_schedules', 'custom_cron_schedules' );
51 75 $this->_addAction( 'plugins_loaded', 'loadTextDomain' );
52 76 $this->_addFilter( 'visualizer_logger_data', 'getLoggerData' );
53 - $this->_addFilter( 'visualizer_get_chart_counts', 'getChartCountsByTypeAndMeta' );
77 + $this->_addFilter( 'visualizer_get_chart_counts', 'getUsage', 10, 2 );
54 78
79 + $this->_addAction( 'init', 'checkIsExistingUser' );
80 +
81 + // only for testing
82 + // $this->_addAction( 'admin_init', 'getUsage' );
55 83 }
56 84 /**
57 85 * Fetches the SDK logger data.
58 86 *
@@ -60,36 +88,82 @@
60 88 *
61 89 * @access public
62 90 */
63 91 public function getLoggerData( $data ) {
64 - return $this->getChartCountsByTypeAndMeta();
92 + return $this->getUsage( $data );
65 93 }
66 94
67 95 /**
68 - * Fetches the types of charts created and their counts.
96 + * Fetches the usage of charts.
69 97 *
98 + * @param array $data The default data that needs to be sent.
70 99 * @param array $meta_keys An array of name vs. meta keys - to return how many charts have these keys.
71 - * @access private
100 + *
101 + * @access public
72 102 */
73 - public function getChartCountsByTypeAndMeta( $meta_keys = array() ) {
74 - $charts = array();
75 - $charts['chart_types'] = array();
76 - // the initial query arguments to fetch charts
103 + public function getUsage( $data, $meta_keys = array() ) {
104 + $charts = array( 'types' => array(), 'sources' => array(), 'library' => array(), 'permissions' => 0, 'manual_config' => 0, 'scheduled' => 0 );
77 105 $query_args = array(
78 106 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
79 107 'posts_per_page' => 300,
108 + 'post_status' => 'publish',
80 109 'fields' => 'ids',
81 110 'no_rows_found' => false,
82 111 'update_post_meta_cache' => false,
83 112 'update_post_term_cache' => false,
113 + );
84 114
115 + // default permissions.
116 + $default_perms = array(
117 + 'read' => 'all',
118 + 'read-specific' => null,
119 + 'edit' => 'roles',
120 + 'edit-specific' => array( 'administrator' ),
85 121 );
86 122
123 + // collect all schedules chart ids.
124 + $scheduled = array_merge(
125 + array_keys( get_option( Visualizer_Plugin::CF_CHART_SCHEDULE, array() ) ),
126 + array_keys( get_option( Visualizer_Plugin::CF_DB_SCHEDULE, array() ) )
127 + );
87 128 $query = new WP_Query( $query_args );
88 129 while ( $query->have_posts() ) {
89 130 $chart_id = $query->next_post();
90 131 $type = get_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_TYPE, true );
91 - $charts['chart_types'][ $type ] = isset( $charts['chart_types'][ $type ] ) ? $charts['chart_types'][ $type ] + 1 : 1;
132 + $charts['types'][ $type ] = isset( $charts['types'][ $type ] ) ? $charts['types'][ $type ] + 1 : 1;
133 + $source = get_post_meta( $chart_id, Visualizer_Plugin::CF_SOURCE, true );
134 + $charts['sources'][ $source ] = isset( $charts['sources'][ $source ] ) ? $charts['sources'][ $source ] + 1 : 1;
135 + $lib = get_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_LIBRARY, true );
136 + $charts['library'][ $lib ] = isset( $charts['library'][ $lib ] ) ? $charts['library'][ $lib ] + 1 : 1;
137 + $settings = get_post_meta( $chart_id, Visualizer_Plugin::CF_SETTINGS, true );
138 + if ( is_array( $settings ) && ! empty( $settings['manual'] ) ) {
139 + $charts['manual_config'] = $charts['manual_config'] + 1;
140 + }
141 +
142 + // phpcs:ignore WordPress.PHP.StrictInArray.FoundNonStrictFalse
143 + if ( in_array( $chart_id, $scheduled, false ) ) {
144 + $charts['scheduled'] = $charts['scheduled'] + 1;
145 + }
146 +
147 + if ( Visualizer_Module::is_pro() ) {
148 + $permissions = get_post_meta( $chart_id, Visualizer_Pro::CF_PERMISSIONS, true );
149 + if ( ! is_array( $permissions ) || empty( $permissions['permissions'] ) || ! is_array( $permissions['permissions'] ) ) {
150 + continue;
151 + }
152 + $permissions = $permissions['permissions'];
153 + $customized = false;
154 + foreach ( $default_perms as $key => $val ) {
155 + if ( ! is_array( $val ) && ! is_null( $val ) && isset( $permissions[ $key ] ) && $permissions[ $key ] !== $val ) {
156 + $customized = true;
157 + } elseif ( is_array( $val ) && ! is_null( $val ) && isset( $permissions[ $key ] ) && is_array( $permissions[ $key ] ) && count( $permissions[ $key ] ) !== count( $val ) ) {
158 + $customized = true;
159 + }
160 + }
161 + if ( $customized ) {
162 + $charts['permissions'] = $charts['permissions'] + 1;
163 + }
164 + }
165 +
92 166 if ( ! empty( $meta_keys ) ) {
93 167 foreach ( $meta_keys as $name => $key ) {
94 168 $data = get_post_meta( $chart_id, $key, true );
95 169 if ( ! empty( $data ) ) {
@@ -99,8 +173,9 @@
99 173 }
100 174 }
101 175 }
102 176 }
177 +
103 178 return $charts;
104 179 }
105 180
106 181 /**
@@ -139,27 +214,110 @@
139 214
140 215 /**
141 216 * Activate the plugin
142 217 */
143 - public function activate() {
144 - wp_clear_scheduled_hook( 'visualizer_schedule_refresh_db' );
145 - wp_schedule_event( strtotime( 'midnight' ) - get_option( 'gmt_offset' ) * HOUR_IN_SECONDS, 'hourly', 'visualizer_schedule_refresh_db' );
218 + public function activate( $network_wide ) {
219 + if ( is_multisite() && $network_wide ) {
220 + foreach ( get_sites( array( 'fields' => 'ids' ) ) as $blog_id ) {
221 + switch_to_blog( $blog_id );
222 + $this->activate_on_site();
223 + restore_current_blog();
224 + }
225 + } else {
226 + $this->activate_on_site();
227 + }
146 228 }
147 229
148 230 /**
231 + * Activates the plugin on a particular blog instance (supports multisite and single site).
232 + */
233 + private function activate_on_site() {
234 + $this->schedule_refresh_db_action();
235 + add_option( 'visualizer-activated', true );
236 + $is_fresh_install = get_option( 'visualizer_fresh_install', false );
237 + if ( ! defined( 'TI_E2E_TESTING' ) && false === $is_fresh_install ) {
238 + update_option( 'visualizer_fresh_install', '1' );
239 + }
240 + }
241 +
242 + /**
149 243 * Deactivate the plugin
150 244 */
151 - public function deactivate() {
152 - wp_clear_scheduled_hook( 'visualizer_schedule_refresh_db' );
245 + public function deactivate( $network_wide ) {
246 + if ( is_multisite() && $network_wide ) {
247 + foreach ( get_sites( array( 'fields' => 'ids' ) ) as $blog_id ) {
248 + switch_to_blog( $blog_id );
249 + $this->deactivate_on_site();
250 + restore_current_blog();
251 + }
252 + } else {
253 + $this->deactivate_on_site();
254 + }
153 255 }
154 256
155 257 /**
156 - * Refresh the specific chart from the db. This is mostly for charts that have 0 refresh time i.e. live data.
258 + * Deactivates the plugin on a particular blog instance (supports multisite and single site).
259 + */
260 + private function deactivate_on_site() {
261 + $this->unschedule_refresh_db_action();
262 + delete_option( 'visualizer-activated', true );
263 + }
264 +
265 + /**
266 + * Check if plugin has been activated and then redirect to the correct page.
267 + */
268 + public function adminInit() {
269 + if ( defined( 'TI_UNIT_TESTING' ) ) {
270 + return;
271 + }
272 +
273 + define( 'VISUALIZER_SURVEY', Visualizer_Module::is_pro() ? 'https://forms.gle/7Zo7FuZbvQ8DTvRi6' : 'https://forms.gle/muMtbcyvHn1aTvmJ7' );
274 + // fire any upgrades necessary.
275 + Visualizer_Module_Upgrade::upgrade();
276 +
277 + $activated_flag = get_option( 'visualizer-activated' );
278 + $fresh_install = get_option( 'visualizer_fresh_install', false );
279 + $is_pro = Visualizer_Module::is_pro();
280 + if ( $activated_flag ) {
281 + if ( function_exists( 'wp_doing_ajax' ) && wp_doing_ajax() ) {
282 + // Defer redirect until a normal admin request.
283 + return;
284 + }
285 + if ( wp_doing_cron() ) {
286 + // Defer redirect during cron requests.
287 + return;
288 + }
289 + delete_option( 'visualizer-activated' );
290 + if ( ! headers_sent() ) {
291 + if ( ! $is_pro && ! empty( $fresh_install ) ) {
292 + $redirect_url = array(
293 + 'page' => 'visualizer-setup-wizard',
294 + 'tab' => '#step-1',
295 + );
296 + } else {
297 + $page_name = Visualizer_Module::numberOfCharts() > 0 ? Visualizer_Plugin::NAME : 'viz-support';
298 + $redirect_url = array(
299 + 'page' => $page_name,
300 + );
301 + }
302 + wp_safe_redirect(
303 + add_query_arg(
304 + $redirect_url,
305 + admin_url( 'admin.php' )
306 + )
307 + );
308 + exit();
309 + }
310 + }
311 + }
312 +
313 +
314 + /**
315 + * Refresh the specific chart from the db.
157 316 *
158 - * @param WP_Post $chart The chart object.
159 - * @param int $chart_id The chart id.
160 - * @param bool $force If this is true, then the chart data will be refreshed no matter if the chart requests live data or cached data.
161 - * If false, data will be refreshed only if the chart requests live data.
317 + * @param WP_Post|null $chart The chart object.
318 + * @param int $chart_id The chart id.
319 + * @param bool $force If this is true, then the chart data will be force refreshed. If false, data will be refreshed only if the chart requests live data.
162 320 *
163 321 * @access public
164 322 */
165 323 public function refresh_db_for_chart( $chart, $chart_id, $force = false ) {
@@ -170,38 +328,97 @@
170 328 if ( ! $chart ) {
171 329 $chart = get_post( $chart_id );
172 330 }
173 331
174 - // check if its a live-data chart or a cached-data chart.
175 - if ( ! $force ) {
176 - $hours = get_post_meta( $chart_id, Visualizer_Plugin::CF_DB_SCHEDULE, true );
177 - if ( ! empty( $hours ) ) {
178 - // cached, bail!
179 - return $chart;
180 - }
332 + if ( ! $chart ) {
333 + return $chart;
181 334 }
182 335
183 336 // check if the source is correct.
184 337 $source = get_post_meta( $chart_id, Visualizer_Plugin::CF_SOURCE, true );
185 - if ( $source !== 'Visualizer_Source_Query_Params' ) {
186 - return $chart;
338 + $load_series = false;
339 + switch ( $source ) {
340 + case 'Visualizer_Source_Query':
341 + // check if its a live-data chart or a cached-data chart.
342 + if ( ! $force ) {
343 + $hours = get_post_meta( $chart_id, Visualizer_Plugin::CF_DB_SCHEDULE, true );
344 + if ( ! empty( $hours ) ) {
345 + // cached, bail!
346 + return $chart;
347 + }
348 + }
349 +
350 + $params = get_post_meta( $chart_id, Visualizer_Plugin::CF_DB_QUERY, true );
351 + $source = new Visualizer_Source_Query( $params, $chart_id );
352 + $source->fetch( false );
353 + $load_series = true;
354 + break;
355 + case 'Visualizer_Source_Json':
356 + // check if its a live-data chart or a cached-data chart.
357 + if ( ! $force ) {
358 + $hours = get_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_SCHEDULE, true );
359 + if ( ! empty( $hours ) ) {
360 + // cached, bail!
361 + return $chart;
362 + }
363 + }
364 +
365 + $url = get_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_URL, true );
366 + $root = get_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_ROOT, true );
367 + $paging = get_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_PAGING, true );
368 + $series = get_post_meta( $chart_id, Visualizer_Plugin::CF_SERIES, true );
369 + $source = new Visualizer_Source_Json( array( 'url' => $url, 'root' => $root, 'paging' => $paging ) );
370 + $source->refresh( $series );
371 + break;
372 + case 'Visualizer_Source_Csv_Remote':
373 + // check if its a live-data chart or a cached-data chart.
374 + if ( ! $force ) {
375 + $hours = get_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_SCHEDULE, true );
376 + if ( ! empty( $hours ) ) {
377 + // cached, bail!
378 + return $chart;
379 + }
380 + }
381 + do_action( 'visualizer_schedule_import' );
382 + return get_post( $chart_id );
383 + default:
384 + return $chart;
187 385 }
188 386
189 - $params = get_post_meta( $chart_id, Visualizer_Plugin::CF_DB_QUERY, true );
190 - $source = new Visualizer_Source_Query( $params );
191 - $source->fetch( false );
192 387 $error = $source->get_error();
193 388 if ( empty( $error ) ) {
194 - update_post_meta( $chart_id, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
389 + $this->disableRevisionsTemporarily();
390 + if ( $load_series ) {
391 + update_post_meta( $chart_id, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
392 + }
195 393
394 + $allow_html = false;
395 + $settings = get_post_meta( $chart_id, Visualizer_Plugin::CF_SETTINGS, true );
396 + if ( isset( $settings['allowHtml'] ) && intval( $settings['allowHtml'] ) === 1 ) {
397 + $allow_html = true;
398 + }
399 +
400 + $allow_html = apply_filters( 'visualizer_allow_html_content', $allow_html, $chart_id, $chart );
401 +
402 + if ( $allow_html ) {
403 + kses_remove_filters();
404 + }
405 +
196 406 wp_update_post(
197 407 array(
198 408 'ID' => $chart_id,
199 - 'post_content' => $source->getData(),
409 + 'post_content' => $source->getData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) ),
200 410 )
201 411 );
202 412
413 + if ( $allow_html ) {
414 + kses_init_filters();
415 + }
416 +
203 417 $chart = get_post( $chart_id );
418 + delete_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR );
419 + } else {
420 + update_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR, sprintf( 'Error while updating chart: %s', $error ) );
204 421 }
205 422
206 423 return $chart;
207 424 }
@@ -206,17 +423,19 @@
206 423 return $chart;
207 424 }
208 425
209 426 /**
210 - * Refresh the db chart.
427 + * Refresh the Database chart type.
211 428 *
212 429 * @access public
213 430 */
214 431 public function refreshDbChart() {
215 - $schedules = get_option( Visualizer_Plugin::CF_DB_SCHEDULE, array() );
216 - if ( ! $schedules ) {
432 + // NOTE: This use a different key from normal schedule. Updated only by Database chart. Check `visualizer_schedule_import` action.
433 + $chart_schedules = get_option( Visualizer_Plugin::CF_DB_SCHEDULE, array() );
434 + if ( ! $chart_schedules ) {
217 435 return;
218 436 }
437 +
219 438 if ( ! defined( 'VISUALIZER_DO_NOT_DIE' ) ) {
220 439 // define this so that the ajax call does not die
221 440 // this means that if the new version of pro and the old version of free are installed, only the first chart will be updated
222 441 define( 'VISUALIZER_DO_NOT_DIE', true );
@@ -222,19 +441,202 @@
222 441 define( 'VISUALIZER_DO_NOT_DIE', true );
223 442 }
224 443
225 444 $new_schedules = array();
226 - $now = time();
227 - foreach ( $schedules as $chart_id => $time ) {
228 - $new_schedules[ $chart_id ] = $time;
229 - if ( $time > $now ) {
445 + $current_time = time();
446 + foreach ( $chart_schedules as $chart_id => $scheduled_time ) {
447 +
448 + // Skip deleted charts.
449 + if ( false === get_post_status( $chart_id ) ) {
230 450 continue;
231 451 }
232 452
233 - $this->refresh_db_for_chart( null, $chart_id, false );
234 - $hours = get_post_meta( $chart_id, Visualizer_Plugin::CF_DB_SCHEDULE, true );
235 - $new_schedules[ $chart_id ] = time() + $hours * HOUR_IN_SECONDS;
453 + $new_schedules[ $chart_id ] = $scheduled_time;
454 +
455 + // Should we do an update?
456 + if ( $scheduled_time > $current_time ) {
457 + continue;
458 + }
459 +
460 + $this->refresh_db_for_chart( null, $chart_id, true );
461 +
462 + // Clear existing chart cache.
463 + $cache_key = Visualizer_Plugin::CF_CHART_CACHE . '_' . $chart_id;
464 + if ( get_transient( $cache_key ) ) {
465 + delete_transient( $cache_key );
466 + }
467 +
468 + $scheduled_hours = get_post_meta( $chart_id, Visualizer_Plugin::CF_DB_SCHEDULE, true );
469 + $new_schedules[ $chart_id ] = $current_time + $scheduled_hours * HOUR_IN_SECONDS;
236 470 }
237 471 update_option( Visualizer_Plugin::CF_DB_SCHEDULE, $new_schedules );
238 472 }
239 473
474 + /**
475 + * Save flag for existing users.
476 + */
477 + public function checkIsExistingUser() {
478 + $chart_exists = get_option( 'visualizer-new-user', '' );
479 + if ( '' === $chart_exists ) {
480 + $charts = get_posts(
481 + array(
482 + 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
483 + 'fields' => 'ids',
484 + )
485 + );
486 + update_option( 'visualizer-new-user', ! empty( $charts ) ? 'no' : 'yes' );
487 + }
488 + }
489 +
490 + /**
491 + * Add custom cron schedules.
492 + *
493 + * @param array $schedules The current schedules options.
494 + * @return array The modified schedules options.
495 + */
496 + public function custom_cron_schedules( $schedules ) {
497 + $schedules['visualizer_ten_minutes'] = array(
498 + 'interval' => 600,
499 + 'display' => __( 'Every 10 minutes', 'visualizer' ),
500 + );
501 +
502 + return $schedules;
503 + }
504 +
505 + /**
506 + * Schedule the recurring DB refresh action.
507 + */
508 + private function schedule_refresh_db_action(): void {
509 + $hook = self::REFRESH_DB_HOOK;
510 + $group = self::REFRESH_DB_GROUP;
511 + $schedules = wp_get_schedules();
512 + $interval_key = apply_filters( 'visualizer_chart_schedule_interval', 'visualizer_ten_minutes' );
513 +
514 + // wp_schedule_event() refuses an unregistered schedule.
515 + if ( ! isset( $schedules[ $interval_key ]['interval'] ) ) {
516 + $interval_key = 'visualizer_ten_minutes';
517 + }
518 +
519 + $interval = isset( $schedules[ $interval_key ]['interval'] ) ? (int) $schedules[ $interval_key ]['interval'] : 600;
520 + // gmt_offset can be fractional, and WP-Cron keys its array by this value.
521 + $timestamp = (int) ( strtotime( 'midnight' ) - get_option( 'gmt_offset' ) * HOUR_IN_SECONDS );
522 +
523 + // West of UTC that midnight is still ahead; start from the previous one.
524 + if ( $timestamp > time() ) {
525 + $timestamp -= DAY_IN_SECONDS;
526 + }
527 +
528 + if (
529 + visualizer_can_use_action_scheduler()
530 + && function_exists( 'as_next_scheduled_action' )
531 + && function_exists( 'as_schedule_recurring_action' )
532 + ) {
533 + $next = as_next_scheduled_action( $hook, array(), $group );
534 + if ( false === $next ) {
535 + // Unique: a concurrent request can arrive while nothing is pending.
536 + as_schedule_recurring_action( $timestamp, $interval, $hook, array(), $group, true );
537 +
538 + // Returns 0 on failure, so ask the store.
539 + $next = as_next_scheduled_action( $hook, array(), $group );
540 + }
541 +
542 + // Drop the WP-Cron fallback only once the action exists.
543 + if ( false !== $next ) {
544 + wp_clear_scheduled_hook( $hook );
545 + return;
546 + }
547 + }
548 +
549 + // Re-arming a live event would pin it to a past timestamp and keep it due.
550 + $event = wp_get_scheduled_event( $hook );
551 + if ( $event && $event->schedule === $interval_key ) {
552 + return;
553 + }
554 +
555 + // Schedule first so a refused replacement keeps the old event, then remove the old one
556 + // by its timestamp: wp_clear_scheduled_hook() would take the new one too.
557 + if ( false === wp_schedule_event( $timestamp, $interval_key, $hook ) ) {
558 + return;
559 + }
560 +
561 + // A matching timestamp was already overwritten in place.
562 + if ( $event && $event->timestamp !== $timestamp ) {
563 + wp_unschedule_event( $event->timestamp, $hook );
564 + }
565 + }
566 +
567 + /**
568 + * Check once per window, on init, that something still fires the refresh.
569 + */
570 + public function maybe_reschedule_refresh_db(): void {
571 + if ( get_transient( self::REFRESH_DB_CHECK_TRANSIENT ) ) {
572 + return;
573 + }
574 +
575 + $this->ensure_refresh_db_action();
576 +
577 + // Cache only a check that left a trigger; a failed one retries next request.
578 + if ( $this->has_refresh_db_trigger() ) {
579 + set_transient( self::REFRESH_DB_CHECK_TRANSIENT, 1, self::REFRESH_DB_CHECK_WINDOW );
580 + }
581 + }
582 +
583 + /**
584 + * Keep the DB refresh scheduled.
585 + *
586 + * A killed run never reaches schedule_next_instance(), so Action Scheduler's chain ends there.
587 + */
588 + public function ensure_refresh_db_action(): void {
589 + if ( ! $this->refresh_db_is_settled() ) {
590 + $this->schedule_refresh_db_action();
591 + }
592 + }
593 +
594 + /**
595 + * Whether the refresh is on Action Scheduler with no WP-Cron event beside it.
596 + *
597 + * @return bool
598 + */
599 + private function refresh_db_is_settled(): bool {
600 + $hook = self::REFRESH_DB_HOOK;
601 +
602 + if (
603 + visualizer_can_use_action_scheduler()
604 + && function_exists( 'as_next_scheduled_action' )
605 + && function_exists( 'as_schedule_recurring_action' )
606 + ) {
607 + return false !== as_next_scheduled_action( $hook, array(), self::REFRESH_DB_GROUP )
608 + && ! wp_next_scheduled( $hook );
609 + }
610 +
611 + return (bool) wp_next_scheduled( $hook );
612 + }
613 +
614 + /**
615 + * Whether anything will fire the refresh hook again.
616 + *
617 + * @return bool
618 + */
619 + private function has_refresh_db_trigger(): bool {
620 + $hook = self::REFRESH_DB_HOOK;
621 +
622 + if ( visualizer_can_use_action_scheduler() && function_exists( 'as_next_scheduled_action' ) ) {
623 + if ( false !== as_next_scheduled_action( $hook, array(), self::REFRESH_DB_GROUP ) ) {
624 + return true;
625 + }
626 + }
627 +
628 + return (bool) wp_next_scheduled( $hook );
629 + }
630 +
631 + /**
632 + * Unschedule the recurring DB refresh action.
633 + */
634 + private function unschedule_refresh_db_action(): void {
635 + $hook = self::REFRESH_DB_HOOK;
636 + $group = self::REFRESH_DB_GROUP;
637 + if ( function_exists( 'as_unschedule_all_actions' ) ) {
638 + as_unschedule_all_actions( $hook, array(), $group );
639 + }
640 + wp_clear_scheduled_hook( $hook );
641 + }
240 642 }