Skip to content
51 changes: 49 additions & 2 deletions partials/preload.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div class="wpsc-settings-inner">
<?php
global $wp_cache_preload_posts;
global $wp_cache_preload_interval, $wp_cache_preload_posts, $preload_schedule_type, $preload_scheduled_time, $preload_schedule_interval;

echo '<a name="preload"></a>';
if ( ! $cache_enabled || ! $super_cache_enabled || true === defined( 'DISABLESUPERCACHEPRELOADING' ) ) {
Expand All @@ -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 '<div class="wpsc-card">';
echo '<p>' . __( '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' ) . '</p>';
echo '<p>' . __( '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' ) . '</p>';
Expand All @@ -26,7 +37,6 @@
echo '<input type="hidden" name="page" value="wpsupercache" />';
echo '</div>';
echo '<div class="wpsc-card">';
echo '<p>' . sprintf( __( 'Refresh preloaded cache files every %s minutes. (0 to disable, minimum %d minutes.)', 'wp-super-cache' ), "<input type='text' size=4 name='wp_cache_preload_interval' value='" . (int) $wp_cache_preload_interval . "' />", $min_refresh_interval ) . '</p>';
if ( $count > 100 ) {
$step = (int)( $count / 10 );

Expand Down Expand Up @@ -66,6 +76,43 @@
echo '<input type="hidden" name="wp_cache_preload_posts" value="' . $count . '" />';
}

// Preload Scheduler UI
echo "<script type='text/javascript'>";
echo "jQuery(function () {
jQuery('#preload_interval_time').on('click',function () {
jQuery('#preload_schedule_interval_radio').attr('checked', true);
});
jQuery('#preload_scheduled_time').on('click',function () {
jQuery('#preload_schedule_time_radio').attr('checked', true);
});
jQuery('#preload_scheduled_select').on('click',function () {
jQuery('#preload_schedule_time_radio').attr('checked', true);
});
});";
echo '</script>';

echo '<table class="form-table">';
echo '<tr><td valign="top"><strong>' . esc_html__( 'Scheduler', 'wp-super-cache' ) . '</strong></td><td><table cellpadding=0 cellspacing=0><tr><td valign="top"><input type="radio" id="preload_schedule_interval_radio" name="preload_schedule_type" value="interval" ' . checked( 'interval', $preload_schedule_type, false ) . ' /></td><td valign="top"><label for="preload_schedule_interval_radio">' . esc_html__( 'Timer:', 'wp-super-cache' ) . '</label></td>';
// translators: %d is the minimum refresh interval in minutes.
echo '<td><input type=\'text\' id=\'preload_interval_time\' size=6 name=\'wp_cache_preload_interval\' value=\'' . esc_attr( $wp_cache_preload_interval ) . '\' /> ' . esc_html__( 'minutes', 'wp-super-cache' ) . '<br />' . sprintf( esc_html__( 'Refresh preloaded cache files at this interval. (0 to disable, minimum %d minutes)', 'wp-super-cache' ), (int) $min_refresh_interval ) . '</td></tr>';
echo '<tr><td valign="top"><input type="radio" id="preload_schedule_time_radio" name="preload_schedule_type" value="time" ' . checked( 'time', $preload_schedule_type, false ) . ' /></td><td valign="top"><label for="preload_schedule_time_radio">' . esc_html__( 'Clock:', 'wp-super-cache' ) . '</label></td>';
$wpsc_tz_label = function_exists( 'wp_timezone_string' ) ? wp_timezone_string() : 'UTC';
echo '<td><input type=\'text\' size=5 id=\'preload_scheduled_time\' name=\'preload_scheduled_time\' value=\'' . esc_attr( $preload_scheduled_time ) . '\' /> ' . esc_html__( 'HH:MM', 'wp-super-cache' ) . '<br />'
. 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' ),
'<code>' . esc_html( $wpsc_tz_label ) . '</code>'
)
. '</td></tr>';
$schedules = wp_get_schedules();
echo '<tr><td><br /></td><td><label for=\'preload_scheduled_select\'>' . esc_html__( 'Interval:', 'wp-super-cache' ) . '</label></td><td><select id=\'preload_scheduled_select\' name=\'preload_schedule_interval\' size=1>';
foreach ( $schedules as $desc => $details ) {
echo '<option value=\'' . esc_attr( $desc ) . '\' ' . selected( $desc, $preload_schedule_interval, false ) . '>' . esc_html( $details['display'] ) . '</option>';
}
echo '</select></td></tr>';
echo '</table></td></tr>';
echo '</table>';

echo '<input type="checkbox" name="wp_cache_preload_on" value="1" ';
echo $wp_cache_preload_on == 1 ? 'checked=1' : '';
echo ' /> ' . __( 'Preload mode (garbage collection disabled. Recommended.)', 'wp-super-cache' ) . '<br />';
Expand Down
3 changes: 3 additions & 0 deletions wp-cache-config-sample.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
139 changes: 131 additions & 8 deletions wp-cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 ) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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' ] ) {
Expand Down Expand Up @@ -4095,24 +4192,50 @@ 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 ) ) {
wp_cache_debug( 'Clearing old preload event' );
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;
}

// 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';
}
Comment on lines +4226 to +4233
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Time-based scheduling uses wp_schedule_event( strtotime( $preload_scheduled_time ), ...), but strtotime('HH:MM') is interpreted in the server/PHP timezone and can resolve to a timestamp in the past (causing the event to run immediately rather than at the next intended time). Since the UI says “UTC”, it would be safer to compute the next future UTC timestamp explicitly (e.g., based on current_time()/wp_timezone() and advancing to tomorrow when needed).

Suggested change
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';
}
// Time-based: schedule recurring event at a specific UTC time.
if ( ! isset( $preload_scheduled_time ) ) {
$preload_scheduled_time = '00:00';
}
if ( ! isset( $preload_schedule_interval ) ) {
$preload_schedule_interval = 'daily';
}
$preload_time_parts = explode( ':', $preload_scheduled_time, 2 );
$preload_hour = isset( $preload_time_parts[0] ) ? (int) $preload_time_parts[0] : 0;
$preload_minute = isset( $preload_time_parts[1] ) ? (int) $preload_time_parts[1] : 0;
if ( $preload_hour < 0 || $preload_hour > 23 ) {
$preload_hour = 0;
}
if ( $preload_minute < 0 || $preload_minute > 59 ) {
$preload_minute = 0;
}
$preload_utc_now = new DateTime( 'now', new DateTimeZone( 'UTC' ) );
$preload_utc_next = new DateTime( 'now', new DateTimeZone( 'UTC' ) );
$preload_utc_next->setTime( $preload_hour, $preload_minute, 0 );
if ( $preload_utc_next->getTimestamp() <= $preload_utc_now->getTimestamp() ) {
$preload_utc_next->modify( '+1 day' );
}
wp_schedule_event( $preload_utc_next->getTimestamp(), $preload_schedule_interval, 'wp_cache_full_preload_hook' );

Copilot uses AI. Check for mistakes.
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' );
}
}
}

Expand Down
Loading