PluginProbe
WebberZone Top 10 — Popular Posts / 4.5.2
WebberZone Top 10 — Popular Posts v4.5.2
4.5.2 4.5.1 4.5.0 4.4.3 4.4.2 4.4.1 4.4.0 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 trunk 1.0 1.0.1 1.1 1.2 1.3 1.4 1.4.1 1.5 1.5.1 1.5.2 1.5.3 1.6 All 118 releases
← All changes | includes/admin/class-cron.php +185 -22 4.3.0 → 4.5.2 View file →
@@ -25,15 +25,41 @@
25 25 */
26 26 private bool $aggregation_cron_was_missing = false;
27 27
28 28 /**
29 + * Whether the aggregation cron interval changed and had to be rescheduled.
30 + *
31 + * @var bool
32 + */
33 + private bool $aggregation_cron_interval_changed = false;
34 +
35 + /**
36 + * Transient key prefix used to record core cron reschedule failures for our hooks.
37 + *
38 + * @since 4.4.0
39 + */
40 + private const RESCHEDULE_ERROR_TRANSIENT_PREFIX = 'tptn_cron_reschedule_error_';
41 +
42 + /**
43 + * Hooks that this class schedules and should track reschedule errors for.
44 + *
45 + * @since 4.4.0
46 + */
47 + private const TRACKED_HOOKS = array( 'tptn_cron_hook', 'tptn_aggregation_cron_hook' );
48 +
49 + /**
29 50 * Initialize the class.
30 51 */
31 52 public function __construct() {
32 53 Hook_Registry::add_action( 'tptn_cron_hook', array( $this, 'run_cron' ) );
33 54 Hook_Registry::add_action( 'tptn_aggregation_cron_hook', array( $this, 'run_aggregation' ) );
55 + Hook_Registry::add_action( 'tptn_count_updated', array( Dashboard_Widgets::class, 'clear_network_dashboard_cache' ) );
56 + Hook_Registry::add_action( 'tptn_delete_counts', array( Dashboard_Widgets::class, 'clear_network_dashboard_cache' ) );
57 + Hook_Registry::add_action( 'tptn_set_count', array( Dashboard_Widgets::class, 'clear_network_dashboard_cache' ) );
34 58 Hook_Registry::add_action( 'admin_init', array( $this, 'check_aggregation_cron' ) );
35 59 Hook_Registry::add_action( 'admin_notices', array( $this, 'aggregation_cron_missing_notice' ) );
60 + Hook_Registry::add_action( 'cron_reschedule_event_error', array( $this, 'log_reschedule_error' ), 10, 2 );
61 + Hook_Registry::add_action( 'cron_unschedule_event_error', array( $this, 'log_unschedule_error' ), 10, 2 );
36 62 }
37 63
38 64 /**
39 65 * Function to truncate daily run.
@@ -42,8 +68,13 @@
42 68 */
43 69 public function run_cron() {
44 70 global $wpdb;
45 71
72 + $table_statuses = Database::get_table_installation_status( true );
73 + if ( in_array( false, $table_statuses, true ) ) {
74 + return;
75 + }
76 +
46 77 $delete_from = TOP_TEN_STORE_DATA;
47 78
48 79 /**
49 80 * Override maintenance day range.
@@ -89,8 +120,10 @@
89 120 do {
90 121 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
91 122 $deleted_log = $wpdb->query( $wpdb->prepare( "DELETE FROM {$log_table} WHERE visited_at < %s LIMIT 1000", $from_date_log ) );
92 123 } while ( $deleted_log > 0 && microtime( true ) < $deadline_log );
124 +
125 + Dashboard_Widgets::clear_network_dashboard_cache();
93 126 }
94 127
95 128 /**
96 129 * Function to enable run or actions.
@@ -141,11 +174,11 @@
141 174 * (e.g. 'one_minute', 'two_minutes', 'three_minutes', 'five_minutes').
142 175 *
143 176 * @since 4.3.0
144 177 *
145 - * @param string $interval Schedule name. Default 'five_minutes'.
178 + * @param string $interval Schedule name. Default 'two_minutes'.
146 179 */
147 - $interval = (string) apply_filters( 'tptn_aggregation_cron_interval', 'five_minutes' );
180 + $interval = (string) apply_filters( 'tptn_aggregation_cron_interval', 'two_minutes' );
148 181
149 182 if ( ! wp_next_scheduled( 'tptn_aggregation_cron_hook' ) ) {
150 183 wp_schedule_event( time(), $interval, 'tptn_aggregation_cron_hook' );
151 184 }
@@ -166,45 +199,175 @@
166 199 * @since 4.3.0
167 200 */
168 201 public function check_aggregation_cron() {
169 202 /** This filter is documented in includes/admin/class-cron.php */
170 - $interval = (string) apply_filters( 'tptn_aggregation_cron_interval', 'five_minutes' );
203 + $interval = (string) apply_filters( 'tptn_aggregation_cron_interval', 'two_minutes' );
171 204
172 - $timestamp = wp_next_scheduled( 'tptn_aggregation_cron_hook' );
173 -
174 - if ( ! $timestamp ) {
205 + if ( ! wp_next_scheduled( 'tptn_aggregation_cron_hook' ) ) {
175 206 self::enable_aggregation_run();
176 207 $this->aggregation_cron_was_missing = true;
177 208 return;
178 209 }
179 210
180 - $crons = _get_cron_array();
181 - $args_key = md5( serialize( array() ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
182 - $current = isset( $crons[ $timestamp ]['tptn_aggregation_cron_hook'][ $args_key ]['schedule'] )
183 - ? $crons[ $timestamp ]['tptn_aggregation_cron_hook'][ $args_key ]['schedule']
184 - : '';
211 + // Look across all scheduled occurrences of the hook, not just the earliest one:
212 + // a legitimate one-off catch-up event (schedule = false) can be scheduled alongside
213 + // the recurring event and must not be mistaken for a wrong/missing recurring schedule.
214 + $args_key = md5( serialize( array() ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
215 + $crons = _get_cron_array();
216 + $has_correct_recurrence = false;
185 217
186 - if ( $current !== $interval ) {
218 + foreach ( $crons as $events ) {
219 + if ( isset( $events['tptn_aggregation_cron_hook'][ $args_key ]['schedule'] )
220 + && $interval === $events['tptn_aggregation_cron_hook'][ $args_key ]['schedule']
221 + ) {
222 + $has_correct_recurrence = true;
223 + break;
224 + }
225 + }
226 +
227 + if ( ! $has_correct_recurrence ) {
187 228 wp_clear_scheduled_hook( 'tptn_aggregation_cron_hook' );
188 229 self::enable_aggregation_run();
189 - $this->aggregation_cron_was_missing = true;
230 + $this->aggregation_cron_interval_changed = true;
190 231 }
191 232 }
192 233
193 234 /**
194 - * Show an admin notice when the aggregation cron was found missing and rescheduled.
235 + * Show an admin notice when the aggregation cron was missing or rescheduled due to an interval change.
195 236 *
196 237 * @since 4.3.0
197 238 */
198 239 public function aggregation_cron_missing_notice() {
199 - if ( ! $this->aggregation_cron_was_missing ) {
240 + if ( $this->aggregation_cron_interval_changed ) {
241 + ?>
242 + <div class="notice notice-info is-dismissible">
243 + <p>
244 + <?php esc_html_e( 'Top 10: The visit aggregation cron job (tptn_aggregation_cron_hook) has been rescheduled to match the updated interval.', 'top-10' ); ?>
245 + </p>
246 + </div>
247 + <?php
248 + } elseif ( $this->aggregation_cron_was_missing ) {
249 + ?>
250 + <div class="notice notice-warning is-dismissible">
251 + <p>
252 + <?php esc_html_e( 'Top 10: The visit aggregation cron job (tptn_aggregation_cron_hook) was missing and has been rescheduled automatically.', 'top-10' ); ?>
253 + </p>
254 + </div>
255 + <?php
256 + }
257 +
258 + foreach ( self::TRACKED_HOOKS as $hook ) {
259 + $error = self::get_reschedule_error( $hook );
260 + if ( ! $error ) {
261 + continue;
262 + }
263 + ?>
264 + <div class="notice notice-warning is-dismissible">
265 + <p>
266 + <?php
267 + printf(
268 + /* translators: 1: Hook name, 2: Error message, 3: Human-readable time difference. */
269 + esc_html__( 'Top 10: WP-Cron reported an error rescheduling %1$s: %2$s (%3$s ago). Use the "Fix Cron Schedules" tool on the Top 10 Tools page if the job stops running.', 'top-10' ),
270 + '<code>' . esc_html( $hook ) . '</code>',
271 + esc_html( $error['message'] ),
272 + esc_html( human_time_diff( $error['time'] ) )
273 + );
274 + ?>
275 + </p>
276 + </div>
277 + <?php
278 + }
279 + }
280 +
281 + /**
282 + * Record a core WP-Cron reschedule failure for one of our hooks.
283 + *
284 + * Fired from wp-cron.php when the real cron runner fails to persist the
285 + * next occurrence of a recurring event (`_set_cron_array()` failure).
286 + *
287 + * @since 4.4.0
288 + *
289 + * @param mixed $result Expected to be a WP_Error when core reports a failure.
290 + * @param string $hook Hook name the error occurred for.
291 + */
292 + public function log_reschedule_error( $result, $hook ) {
293 + if ( ! in_array( $hook, self::TRACKED_HOOKS, true ) || ! is_wp_error( $result ) ) {
200 294 return;
201 295 }
202 - ?>
203 - <div class="notice notice-warning is-dismissible">
204 - <p>
205 - <?php esc_html_e( 'Top 10: The visit aggregation cron job (tptn_aggregation_cron_hook) was missing and has been rescheduled automatically.', 'top-10' ); ?>
206 - </p>
207 - </div>
208 - <?php
296 +
297 + self::set_reschedule_error( $hook, $result );
298 + }
299 +
300 + /**
301 + * Record a core WP-Cron unschedule failure for one of our hooks.
302 + *
303 + * @since 4.4.0
304 + *
305 + * @param mixed $result Expected to be a WP_Error when core reports a failure.
306 + * @param string $hook Hook name the error occurred for.
307 + */
308 + public function log_unschedule_error( $result, $hook ) {
309 + if ( ! in_array( $hook, self::TRACKED_HOOKS, true ) || ! is_wp_error( $result ) ) {
310 + return;
311 + }
312 +
313 + self::set_reschedule_error( $hook, $result );
314 + }
315 +
316 + /**
317 + * Store the last cron scheduling error for a hook.
318 + *
319 + * @since 4.4.0
320 + *
321 + * @param string $hook Hook name.
322 + * @param \WP_Error $result Error returned by WordPress core.
323 + */
324 + private static function set_reschedule_error( $hook, $result ) {
325 + set_transient(
326 + self::RESCHEDULE_ERROR_TRANSIENT_PREFIX . $hook,
327 + array(
328 + 'code' => $result->get_error_code(),
329 + 'message' => $result->get_error_message(),
330 + 'time' => time(),
331 + ),
332 + WEEK_IN_SECONDS
333 + );
334 + }
335 +
336 + /**
337 + * Retrieve the last recorded cron scheduling error for a hook, if any.
338 + *
339 + * Core reports a reschedule failure when update_option( 'cron' ) is a no-op —
340 + * e.g. a concurrent cron run already saved the identical schedule. If the hook
341 + * has a valid next occurrence the recorded error is stale: clear and ignore it.
342 + *
343 + * @since 4.4.0
344 + *
345 + * @param string $hook Hook name.
346 + * @return array{code: string, message: string, time: int}|false Error data, or false if none recorded.
347 + */
348 + public static function get_reschedule_error( $hook ) {
349 + $error = get_transient( self::RESCHEDULE_ERROR_TRANSIENT_PREFIX . $hook );
350 +
351 + if ( ! is_array( $error ) ) {
352 + return false;
353 + }
354 +
355 + if ( wp_next_scheduled( $hook ) ) {
356 + delete_transient( self::RESCHEDULE_ERROR_TRANSIENT_PREFIX . $hook );
357 + return false;
358 + }
359 +
360 + return $error;
361 + }
362 +
363 + /**
364 + * Clear the recorded cron scheduling errors for all tracked hooks.
365 + *
366 + * @since 4.4.0
367 + */
368 + public static function clear_reschedule_errors() {
369 + foreach ( self::TRACKED_HOOKS as $hook ) {
370 + delete_transient( self::RESCHEDULE_ERROR_TRANSIENT_PREFIX . $hook );
371 + }
209 372 }
210 373 }