diff --git a/partials/preload.php b/partials/preload.php index 34b8cbb6..5d6f36ad 100644 --- a/partials/preload.php +++ b/partials/preload.php @@ -1,6 +1,6 @@
'; if ( ! $cache_enabled || ! $super_cache_enabled || true === defined( 'DISABLESUPERCACHEPRELOADING' ) ) { @@ -17,6 +17,17 @@ $min_refresh_interval = wpsc_get_minimum_preload_interval(); +// Set defaults for preload scheduler variables if not set. +if ( ! isset( $preload_schedule_type ) ) { + $preload_schedule_type = 'interval'; +} +if ( ! isset( $preload_scheduled_time ) ) { + $preload_scheduled_time = '00:00'; +} +if ( ! isset( $preload_schedule_interval ) ) { + $preload_schedule_interval = 'daily'; +} + echo '
'; echo '

' . __( 'This will cache every published post and page on your site. It will create supercache static files so unknown visitors (including bots) will hit a cached page. This will probably help your Google ranking as they are using speed as a metric when judging websites now.', 'wp-super-cache' ) . '

'; echo '

' . __( 'Preloading creates lots of files however. Caching is done from the newest post to the oldest so please consider only caching the newest if you have lots (10,000+) of posts. This is especially important on shared hosting.', 'wp-super-cache' ) . '

'; @@ -26,7 +37,6 @@ echo ''; echo '
'; echo '
'; -echo '

' . sprintf( __( 'Refresh preloaded cache files every %s minutes. (0 to disable, minimum %d minutes.)', 'wp-super-cache' ), "", $min_refresh_interval ) . '

'; if ( $count > 100 ) { $step = (int)( $count / 10 ); @@ -66,6 +76,43 @@ echo ''; } +// Preload Scheduler UI +echo "'; + +echo ''; +echo ''; +echo '
' . esc_html__( 'Scheduler', 'wp-super-cache' ) . ''; +// translators: %d is the minimum refresh interval in minutes. +echo ''; +echo ''; +$wpsc_tz_label = function_exists( 'wp_timezone_string' ) ? wp_timezone_string() : 'UTC'; +echo ''; +$schedules = wp_get_schedules(); +echo ''; +echo '
' . esc_html__( 'minutes', 'wp-super-cache' ) . '
' . sprintf( esc_html__( 'Refresh preloaded cache files at this interval. (0 to disable, minimum %d minutes)', 'wp-super-cache' ), (int) $min_refresh_interval ) . '
' . esc_html__( 'HH:MM', 'wp-super-cache' ) . '
' + . sprintf( + /* translators: %s: site timezone string, e.g. "Europe/Dublin" or "+00:00". */ + esc_html__( 'Start preloading at this time (site timezone: %s) or starting at this time every interval below.', 'wp-super-cache' ), + '' . esc_html( $wpsc_tz_label ) . '' + ) + . '

'; + echo ' ' . __( 'Preload mode (garbage collection disabled. Recommended.)', 'wp-super-cache' ) . '
'; diff --git a/wp-cache-config-sample.php b/wp-cache-config-sample.php index 67b77b2d..34f6a08a 100644 --- a/wp-cache-config-sample.php +++ b/wp-cache-config-sample.php @@ -94,6 +94,9 @@ $wp_cache_preload_taxonomies = 0; $wp_cache_preload_email_me = 0; $wp_cache_preload_email_volume = 'none'; +$preload_schedule_type = 'interval'; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable +$preload_scheduled_time = '00:00'; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable +$preload_schedule_interval = 'daily'; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable $wp_cache_mobile_prefixes = ''; $cached_direct_pages = array(); $wpsc_served_header = false; diff --git a/wp-cache.php b/wp-cache.php index 88c15585..c2723b73 100644 --- a/wp-cache.php +++ b/wp-cache.php @@ -1083,6 +1083,7 @@ function toggleLayer( whichLayer ) { if ( 'preload' === $curr_tab ) { if ( true == $super_cache_enabled && ! defined( 'DISABLESUPERCACHEPRELOADING' ) ) { global $wp_cache_preload_interval, $wp_cache_preload_on, $wp_cache_preload_taxonomies, $wp_cache_preload_email_me, $wp_cache_preload_email_volume, $wp_cache_preload_posts, $wpdb; + global $preload_schedule_type, $preload_scheduled_time, $preload_schedule_interval; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Used in partials/preload.php. wpsc_preload_settings(); $currently_preloading = false; @@ -3689,11 +3690,42 @@ function wp_cron_preload_cache() { } else { $msg = ''; wpsc_reset_preload_counter(); - if ( (int)$wp_cache_preload_interval && defined( 'DOING_CRON' ) ) { - if ( $wp_cache_preload_email_me ) - $msg = sprintf( __( 'Scheduling next preload refresh in %d minutes.', 'wp-super-cache' ), (int)$wp_cache_preload_interval ); - wp_cache_debug( "wp_cron_preload_cache: no more posts. scheduling next preload in $wp_cache_preload_interval minutes.", 5 ); - wp_schedule_single_event( time() + ( (int)$wp_cache_preload_interval * 60 ), 'wp_cache_full_preload_hook' ); + global $preload_schedule_type, $preload_scheduled_time, $preload_schedule_interval; + + // Set defaults if not set + if ( ! isset( $preload_schedule_type ) ) { + $preload_schedule_type = 'interval'; + } + + if ( defined( 'DOING_CRON' ) ) { + if ( $preload_schedule_type === 'interval' && (int) $wp_cache_preload_interval ) { + // Interval-based scheduling + if ( $wp_cache_preload_email_me ) { + // translators: %d is the number of minutes until the next preload refresh. + $msg = sprintf( __( 'Scheduling next preload refresh in %d minutes.', 'wp-super-cache' ), (int) $wp_cache_preload_interval ); + } + wp_cache_debug( "wp_cron_preload_cache: no more posts. scheduling next preload in $wp_cache_preload_interval minutes.", 5 ); + wp_schedule_single_event( time() + ( (int) $wp_cache_preload_interval * 60 ), 'wp_cache_full_preload_hook' ); + } elseif ( $preload_schedule_type === 'time' ) { + // Time-based scheduling - the event will already be scheduled as recurring + // Check if already scheduled, if not schedule it + if ( ! wp_next_scheduled( 'wp_cache_full_preload_hook' ) ) { + if ( ! isset( $preload_scheduled_time ) ) { + $preload_scheduled_time = '00:00'; + } + if ( ! isset( $preload_schedule_interval ) ) { + $preload_schedule_interval = 'daily'; + } + $schedules = wp_get_schedules(); + $interval_display = isset( $schedules[ $preload_schedule_interval ]['display'] ) ? $schedules[ $preload_schedule_interval ]['display'] : $preload_schedule_interval; + if ( $wp_cache_preload_email_me ) { + /* translators: 1: scheduled time, 2: schedule interval display name */ + $msg = sprintf( __( 'Scheduling next preload at %1$s (%2$s).', 'wp-super-cache' ), $preload_scheduled_time, $interval_display ); + } + wp_cache_debug( "wp_cron_preload_cache: no more posts. scheduling next preload at $preload_scheduled_time ($preload_schedule_interval).", 5 ); + wp_schedule_event( wpsc_next_scheduled_preload_timestamp( $preload_scheduled_time ), $preload_schedule_interval, 'wp_cache_full_preload_hook' ); + } + } } global $file_prefix, $cache_max_time; if ( $wp_cache_preload_interval > 0 ) { @@ -4022,8 +4054,37 @@ function wpsc_get_minimum_preload_interval() { return apply_filters( 'wpsc_minimum_preload_interval', 10 ); } +/** + * Convert an "HH:MM" clock time (interpreted in the site's timezone) into the + * next UTC timestamp at or after "now". Falls back to "00:00" for malformed + * input. + * + * @param string $hhmm Wall-clock time in the site's timezone, e.g. "03:00". + * @return int UTC timestamp suitable for wp_schedule_event(). + */ +function wpsc_next_scheduled_preload_timestamp( $hhmm ) { + if ( ! preg_match( '/^(?:[01][0-9]|2[0-3]):[0-5][0-9]$/', (string) $hhmm ) ) { + $hhmm = '00:00'; + } + + $site_tz = function_exists( 'wp_timezone' ) ? wp_timezone() : new DateTimeZone( 'UTC' ); + $dt = DateTime::createFromFormat( 'H:i', $hhmm, $site_tz ); + + if ( ! $dt ) { + $dt = new DateTime( 'now', $site_tz ); + $dt->setTime( (int) substr( $hhmm, 0, 2 ), (int) substr( $hhmm, 3, 2 ), 0 ); + } + + if ( $dt->getTimestamp() <= time() ) { + $dt->modify( '+1 day' ); + } + + return $dt->getTimestamp(); +} + function wpsc_preload_settings() { global $wp_cache_preload_interval, $wp_cache_preload_on, $wp_cache_preload_taxonomies, $wp_cache_preload_email_volume, $wp_cache_preload_posts, $valid_nonce; + global $preload_schedule_type, $preload_scheduled_time, $preload_schedule_interval; if ( isset( $_POST[ 'action' ] ) == false || $_POST[ 'action' ] != 'preload' ) return; @@ -4051,6 +4112,42 @@ function wpsc_preload_settings() { // Set to true if the preload interval is changed, and a reschedule is required. $force_preload_reschedule = false; + // Handle preload schedule type (interval vs time) + // Nonce is verified in wp_cache_manager() before this function is called. + if ( isset( $_POST['preload_schedule_type'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $new_schedule_type = sanitize_text_field( wp_unslash( $_POST['preload_schedule_type'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing + $new_schedule_type = in_array( $new_schedule_type, array( 'interval', 'time' ), true ) ? $new_schedule_type : 'interval'; + if ( ! isset( $preload_schedule_type ) || $preload_schedule_type !== $new_schedule_type ) { + $force_preload_reschedule = true; + } + $preload_schedule_type = $new_schedule_type; + wp_cache_setting( 'preload_schedule_type', $preload_schedule_type ); + } + + if ( isset( $_POST['preload_scheduled_time'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $new_scheduled_time = sanitize_text_field( wp_unslash( $_POST['preload_scheduled_time'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing + if ( ! preg_match( '/^(?:[01][0-9]|2[0-3]):[0-5][0-9]$/', $new_scheduled_time ) ) { + $new_scheduled_time = '00:00'; + } + if ( ! isset( $preload_scheduled_time ) || $preload_scheduled_time !== $new_scheduled_time ) { + $force_preload_reschedule = true; + } + $preload_scheduled_time = $new_scheduled_time; + wp_cache_setting( 'preload_scheduled_time', $preload_scheduled_time ); + } + + // Handle preload schedule interval (hourly, twicedaily, daily, etc.) + if ( isset( $_POST['preload_schedule_interval'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $schedules = wp_get_schedules(); + $new_schedule_interval = sanitize_text_field( wp_unslash( $_POST['preload_schedule_interval'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing + $new_schedule_interval = isset( $schedules[ $new_schedule_interval ] ) ? $new_schedule_interval : 'daily'; + if ( ! isset( $preload_schedule_interval ) || $preload_schedule_interval !== $new_schedule_interval ) { + $force_preload_reschedule = true; + } + $preload_schedule_interval = $new_schedule_interval; + wp_cache_setting( 'preload_schedule_interval', $preload_schedule_interval ); + } + if ( isset( $_POST[ 'wp_cache_preload_interval' ] ) && ( $_POST[ 'wp_cache_preload_interval' ] == 0 || $_POST[ 'wp_cache_preload_interval' ] >= $min_refresh_interval ) ) { $_POST[ 'wp_cache_preload_interval' ] = (int)$_POST[ 'wp_cache_preload_interval' ]; if ( $wp_cache_preload_interval != $_POST[ 'wp_cache_preload_interval' ] ) { @@ -4095,9 +4192,21 @@ function wpsc_preload_settings() { } wp_cache_setting( 'wp_cache_preload_on', $wp_cache_preload_on ); + // Set defaults if not set + if ( ! isset( $preload_schedule_type ) ) { + $preload_schedule_type = 'interval'; + } + // Ensure that preload settings are applied to scheduled cron. - $next_preload = wp_next_scheduled( 'wp_cache_full_preload_hook' ); - $should_schedule = ( $wp_cache_preload_on === 1 && $wp_cache_preload_interval > 0 ); + $next_preload = wp_next_scheduled( 'wp_cache_full_preload_hook' ); + + // Determine if we should schedule based on schedule type + if ( $preload_schedule_type === 'interval' ) { + $should_schedule = ( $wp_cache_preload_on === 1 && $wp_cache_preload_interval > 0 ); + } else { + // For time-based scheduling, we schedule if preload mode is on + $should_schedule = ( $wp_cache_preload_on === 1 ); + } // If forcing a reschedule, or preload is disabled, clear the next scheduled event. if ( $next_preload && ( ! $should_schedule || $force_preload_reschedule ) ) { @@ -4105,6 +4214,7 @@ function wpsc_preload_settings() { wpsc_reset_preload_counter(); wpsc_create_stop_preload_flag(); wp_unschedule_event( $next_preload, 'wp_cache_full_preload_hook' ); + wp_clear_scheduled_hook( 'wp_cache_full_preload_hook' ); $next_preload = 0; } @@ -4112,7 +4222,20 @@ function wpsc_preload_settings() { // Ensure a preload is scheduled if it should be. if ( ! $next_preload && $should_schedule ) { wp_cache_debug( 'Scheduling new preload event' ); - wp_schedule_single_event( time() + ( $wp_cache_preload_interval * 60 ), 'wp_cache_full_preload_hook' ); + + if ( $preload_schedule_type === 'interval' ) { + // Interval-based: schedule single event X minutes from now + wp_schedule_single_event( time() + ( $wp_cache_preload_interval * 60 ), 'wp_cache_full_preload_hook' ); + } else { + // Time-based: schedule recurring event at specific time + if ( ! isset( $preload_scheduled_time ) ) { + $preload_scheduled_time = '00:00'; + } + if ( ! isset( $preload_schedule_interval ) ) { + $preload_schedule_interval = 'daily'; + } + wp_schedule_event( wpsc_next_scheduled_preload_timestamp( $preload_scheduled_time ), $preload_schedule_interval, 'wp_cache_full_preload_hook' ); + } } }