PluginProbe
WebberZone Top 10 — Popular Posts / 4.5.1
WebberZone Top 10 — Popular Posts v4.5.1
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 1.6.1 All 117 releases
← All changes | includes/admin/class-cron.php +158 -9 4.3.14.5.1 View file →
@@ -32,15 +32,34 @@
32 32 */
33 33 private bool $aggregation_cron_interval_changed = false;
34 34
35 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 + /**
36 50 * Initialize the class.
37 51 */
38 52 public function __construct() {
39 53 Hook_Registry::add_action( 'tptn_cron_hook', array( $this, 'run_cron' ) );
40 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' ) );
41 58 Hook_Registry::add_action( 'admin_init', array( $this, 'check_aggregation_cron' ) );
42 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 );
43 62 }
44 63
45 64 /**
46 65 * Function to truncate daily run.
@@ -49,8 +68,13 @@
49 68 */
50 69 public function run_cron() {
51 70 global $wpdb;
52 71
72 + $table_statuses = Database::get_table_installation_status( true );
73 + if ( in_array( false, $table_statuses, true ) ) {
74 + return;
75 + }
76 +
53 77 $delete_from = TOP_TEN_STORE_DATA;
54 78
55 79 /**
56 80 * Override maintenance day range.
@@ -96,8 +120,10 @@
96 120 do {
97 121 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
98 122 $deleted_log = $wpdb->query( $wpdb->prepare( "DELETE FROM {$log_table} WHERE visited_at < %s LIMIT 1000", $from_date_log ) );
99 123 } while ( $deleted_log > 0 && microtime( true ) < $deadline_log );
124 +
125 + Dashboard_Widgets::clear_network_dashboard_cache();
100 126 }
101 127
102 128 /**
103 129 * Function to enable run or actions.
@@ -175,23 +201,31 @@
175 201 public function check_aggregation_cron() {
176 202 /** This filter is documented in includes/admin/class-cron.php */
177 203 $interval = (string) apply_filters( 'tptn_aggregation_cron_interval', 'two_minutes' );
178 204
179 - $timestamp = wp_next_scheduled( 'tptn_aggregation_cron_hook' );
180 -
181 - if ( ! $timestamp ) {
205 + if ( ! wp_next_scheduled( 'tptn_aggregation_cron_hook' ) ) {
182 206 self::enable_aggregation_run();
183 207 $this->aggregation_cron_was_missing = true;
184 208 return;
185 209 }
186 210
187 - $crons = _get_cron_array();
188 - $args_key = md5( serialize( array() ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
189 - $current = isset( $crons[ $timestamp ]['tptn_aggregation_cron_hook'][ $args_key ]['schedule'] )
190 - ? $crons[ $timestamp ]['tptn_aggregation_cron_hook'][ $args_key ]['schedule']
191 - : '';
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;
192 217
193 - 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 ) {
194 228 wp_clear_scheduled_hook( 'tptn_aggregation_cron_hook' );
195 229 self::enable_aggregation_run();
196 230 $this->aggregation_cron_interval_changed = true;
197 231 }
@@ -218,7 +252,122 @@
218 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' ); ?>
219 253 </p>
220 254 </div>
221 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 ) ) {
294 + return;
295 + }
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 );
222 371 }
223 372 }
224 373 }