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 +138 -28 4.0.3 → 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,9 +63,11 @@
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' );
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' );
48 70 $this->_addFilter( 'visualizer_schedule_refresh_chart', 'refresh_db_for_chart', 10, 3 );
49 71
50 72 $this->_addAction( 'admin_init', 'adminInit' );
51 73 $this->_addAction( 'init', 'setupCustomPostTypes' );
@@ -112,9 +134,9 @@
112 134 $charts['sources'][ $source ] = isset( $charts['sources'][ $source ] ) ? $charts['sources'][ $source ] + 1 : 1;
113 135 $lib = get_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_LIBRARY, true );
114 136 $charts['library'][ $lib ] = isset( $charts['library'][ $lib ] ) ? $charts['library'][ $lib ] + 1 : 1;
115 137 $settings = get_post_meta( $chart_id, Visualizer_Plugin::CF_SETTINGS, true );
116 - if ( array_key_exists( 'manual', $settings ) && ! empty( $settings['manual'] ) ) {
138 + if ( is_array( $settings ) && ! empty( $settings['manual'] ) ) {
117 139 $charts['manual_config'] = $charts['manual_config'] + 1;
118 140 }
119 141
120 142 // phpcs:ignore WordPress.PHP.StrictInArray.FoundNonStrictFalse
@@ -123,9 +145,9 @@
123 145 }
124 146
125 147 if ( Visualizer_Module::is_pro() ) {
126 148 $permissions = get_post_meta( $chart_id, Visualizer_Pro::CF_PERMISSIONS, true );
127 - if ( empty( $permissions ) ) {
149 + if ( ! is_array( $permissions ) || empty( $permissions['permissions'] ) || ! is_array( $permissions['permissions'] ) ) {
128 150 continue;
129 151 }
130 152 $permissions = $permissions['permissions'];
131 153 $customized = false;
@@ -131,9 +153,9 @@
131 153 $customized = false;
132 154 foreach ( $default_perms as $key => $val ) {
133 155 if ( ! is_array( $val ) && ! is_null( $val ) && isset( $permissions[ $key ] ) && $permissions[ $key ] !== $val ) {
134 156 $customized = true;
135 - } elseif ( is_array( $val ) && ! is_null( $val ) && isset( $permissions[ $key ] ) && count( $permissions[ $key ] ) !== count( $val ) ) {
157 + } elseif ( is_array( $val ) && ! is_null( $val ) && isset( $permissions[ $key ] ) && is_array( $permissions[ $key ] ) && count( $permissions[ $key ] ) !== count( $val ) ) {
136 158 $customized = true;
137 159 }
138 160 }
139 161 if ( $customized ) {
@@ -483,50 +505,138 @@
483 505 /**
484 506 * Schedule the recurring DB refresh action.
485 507 */
486 508 private function schedule_refresh_db_action(): void {
487 - $hook = 'visualizer_schedule_refresh_db';
488 - $group = 'visualizer';
509 + $hook = self::REFRESH_DB_HOOK;
510 + $group = self::REFRESH_DB_GROUP;
511 + $schedules = wp_get_schedules();
489 512 $interval_key = apply_filters( 'visualizer_chart_schedule_interval', 'visualizer_ten_minutes' );
490 - $interval = $this->get_schedule_interval_seconds( $interval_key );
491 - $timestamp = strtotime( 'midnight' ) - get_option( 'gmt_offset' ) * HOUR_IN_SECONDS;
492 513
493 - if ( function_exists( 'as_next_scheduled_action' ) && function_exists( 'as_schedule_recurring_action' ) ) {
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 + ) {
494 533 $next = as_next_scheduled_action( $hook, array(), $group );
495 534 if ( false === $next ) {
496 - as_schedule_recurring_action( $timestamp, $interval, $hook, array(), $group );
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 );
497 540 }
498 - wp_clear_scheduled_hook( $hook );
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 ) {
499 552 return;
500 553 }
501 554
502 - wp_clear_scheduled_hook( $hook );
503 - wp_schedule_event( $timestamp, $interval_key, $hook );
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 + }
504 565 }
505 566
506 567 /**
507 - * Unschedule the recurring DB refresh action.
568 + * Check once per window, on init, that something still fires the refresh.
508 569 */
509 - private function unschedule_refresh_db_action(): void {
510 - $hook = 'visualizer_schedule_refresh_db';
511 - $group = 'visualizer';
512 - if ( function_exists( 'as_unschedule_all_actions' ) ) {
513 - as_unschedule_all_actions( $hook, array(), $group );
570 + public function maybe_reschedule_refresh_db(): void {
571 + if ( get_transient( self::REFRESH_DB_CHECK_TRANSIENT ) ) {
572 + return;
514 573 }
515 - wp_clear_scheduled_hook( $hook );
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 + }
516 581 }
517 582
518 583 /**
519 - * Resolve a cron schedule key to seconds.
584 + * Keep the DB refresh scheduled.
520 585 *
521 - * @param string $interval_key Cron schedule key.
522 - * @return int Interval in seconds.
586 + * A killed run never reaches schedule_next_instance(), so Action Scheduler's chain ends there.
523 587 */
524 - private function get_schedule_interval_seconds( $interval_key ) {
525 - $schedules = wp_get_schedules();
526 - if ( isset( $schedules[ $interval_key ]['interval'] ) ) {
527 - return (int) $schedules[ $interval_key ]['interval'];
588 + public function ensure_refresh_db_action(): void {
589 + if ( ! $this->refresh_db_is_settled() ) {
590 + $this->schedule_refresh_db_action();
528 591 }
592 + }
529 593
530 - return 600;
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 );
531 641 }
532 642 }