PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.3.0
Defender Security – Malware Scanner, Login Security & Firewall v6.3.0
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
← All changes | src/component/class-network-cron-manager.php +93 -25 5.6.0 → 6.3.0 View file →
@@ -25,9 +25,9 @@
25 25 */
26 26 public const LOG_FILE_NAME = 'network-cron-manager';
27 27
28 28 /**
29 - * Array of registered callbacks.
29 + * Array of registered hook configurations.
30 30 *
31 31 * @var array
32 32 */
33 33 private $callbacks = array();
@@ -64,10 +64,12 @@
64 64
65 65 /**
66 66 * Load callbacks from network options.
67 67 */
68 - private function load_callbacks() {
69 - $this->callbacks = get_network_option( get_main_network_id(), $this->callbacks_option, array() );
68 + public function load_callbacks() {
69 + if ( array() === $this->callbacks ) {
70 + $this->callbacks = get_network_option( get_main_network_id(), $this->callbacks_option, array() );
71 + }
70 72 }
71 73
72 74 /**
73 75 * Save callbacks to network options.
@@ -92,9 +94,9 @@
92 94 * @return bool|void False on validation failure, void on success.
93 95 */
94 96 public function register_callback( string $hook_name, callable $callback, int $interval_seconds, $start_time = null, array $args = array() ) {
95 97 $hook_name = sanitize_key( $hook_name );
96 - if ( empty( $hook_name ) || ! is_string( $hook_name ) ) {
98 + if ( '' === $hook_name ) {
97 99 $this->log( 'Task registration failed: Invalid task name provided', self::LOG_FILE_NAME );
98 100 return false;
99 101 }
100 102 if ( ! is_callable( $callback ) ) {
@@ -111,23 +113,25 @@
111 113 wp_clear_scheduled_hook( $hook_name );
112 114 $this->log( "Cleared existing WordPress cron event for '{$hook_name}' to prevent conflicts", self::LOG_FILE_NAME );
113 115 }
114 116
115 - $this->callbacks[ $hook_name ] = array(
116 - 'callback' => $callback,
117 - 'interval' => $interval_seconds,
118 - 'start_time' => $start_time,
119 - 'args' => $args,
120 - );
121 - $this->save_callbacks();
122 - } else {
123 - if ( ! wp_next_scheduled( $hook_name ) ) {
117 + // Only save if this is a new callback or something changed.
118 + if ( ! isset( $this->callbacks[ $hook_name ] ) || $this->callbacks[ $hook_name ]['interval'] !== $interval_seconds || $this->callbacks[ $hook_name ]['start_time'] !== $start_time ) {
119 + $this->callbacks[ $hook_name ] = array(
120 + 'interval' => $interval_seconds,
121 + 'start_time' => $start_time,
122 + 'args' => $args,
123 + );
124 + $this->save_callbacks();
125 + }
126 + } elseif ( ! wp_next_scheduled( $hook_name ) ) {
124 127 $start_timestamp = $this->calculate_start_time( $start_time );
125 128 $schedule = $this->get_schedule_name( $interval_seconds );
126 129 wp_schedule_event( $start_timestamp, $schedule, $hook_name );
127 - }
128 - add_action( $hook_name, $callback );
129 130 }
131 +
132 + // Add the action to the hook (applies to both multisite and single-site).
133 + add_action( $hook_name, $callback );
130 134 }
131 135
132 136 /**
133 137 * Calculate the start time for a cron event.
@@ -186,12 +190,30 @@
186 190 /**
187 191 * Check and execute registered callbacks.
188 192 */
189 193 public function check_and_execute_callbacks() {
190 - $this->load_callbacks();
191 - if ( empty( $this->callbacks ) ) {
194 + // Early exit during plugin deletion/uninstall.
195 + if ( defined( 'WP_UNINSTALL_PLUGIN' ) ) {
192 196 return;
193 197 }
198 +
199 + // Skip if page post ID exists, or during AJAX/cron execution to prevent concurrent runs.
200 + $post_data = defender_get_data_from_request( null, 'p' );
201 + if (
202 + ( is_array( $post_data ) && array() !== $post_data )
203 + || defined( 'DOING_AJAX' )
204 + || defined( 'DOING_CRON' )
205 + ) {
206 + return;
207 + }
208 +
209 + if ( did_action( 'after_setup_theme' ) > 0 ) {
210 + $this->load_callbacks();
211 + }
212 +
213 + if ( array() === $this->callbacks ) {
214 + return;
215 + }
194 216 foreach ( $this->callbacks as $hook_name => $config ) {
195 217 $this->execute_callback( $hook_name, $config );
196 218 }
197 219 }
@@ -208,14 +230,13 @@
208 230 }
209 231 if ( ! $this->acquire_lock( $hook_name ) ) {
210 232 return;
211 233 }
212 - $start_time = defender_get_current_time();
234 +
213 235 try {
214 - if ( is_callable( $config['callback'] ) ) {
215 - call_user_func_array( $config['callback'], $config['args'] );
216 - $this->update_last_run( $hook_name );
217 - }
236 + // Execute the hook with arguments.
237 + do_action( $hook_name, ...$config['args'] );
238 + $this->update_last_run( $hook_name );
218 239 } catch ( \Exception $exception ) {
219 240 $this->log( "Task '{$hook_name}' failed to run: " . $exception->getMessage(), self::LOG_FILE_NAME );
220 241 } finally {
221 242 $this->release_lock( $hook_name );
@@ -238,9 +259,10 @@
238 259 *
239 260 * @param int $interval The interval in seconds.
240 261 * @param string $hook_name The hook name being executed.
241 262 */
242 - $interval = (int) apply_filters( 'wpdef_network_cron_interval', $interval, $hook_name );
263 + $interval = apply_filters( 'wpdef_network_cron_interval', $interval, $hook_name );
264 + $interval = ! is_int( $interval ) ? (int) $interval : $interval;
243 265 $last_run = $this->get_last_run( $hook_name );
244 266 $current_time = defender_get_current_time();
245 267
246 268 if ( ! $last_run && isset( $config['start_time'] ) && null !== $config['start_time'] ) {
@@ -272,12 +294,13 @@
272 294 *
273 295 * @param int $timeout The timeout in seconds.
274 296 * @param string $hook_name The hook name being locked.
275 297 */
276 - $lock_timeout = (int) apply_filters( 'wpdef_network_cron_lock_timeout', 300, $hook_name );
298 + $lock_timeout = apply_filters( 'wpdef_network_cron_lock_timeout', 300, $hook_name );
299 + $lock_timeout = ! is_int( $lock_timeout ) ? (int) $lock_timeout : $lock_timeout;
277 300 $lock_key = $this->lock_prefix . $hook_name;
278 301 $existing_lock = get_network_option( get_main_network_id(), $lock_key );
279 - if ( $existing_lock && ( defender_get_current_time() - $existing_lock ) < $lock_timeout ) {
302 + if ( 0 < $existing_lock && ( defender_get_current_time() - $existing_lock ) < $lock_timeout ) {
280 303 return false;
281 304 }
282 305 $lock_value = defender_get_current_time();
283 306 return update_network_option( get_main_network_id(), $lock_key, $lock_value );
@@ -343,6 +366,51 @@
343 366 $this->lock_prefix . '%',
344 367 $this->lastrun_prefix . '%'
345 368 )
346 369 );
370 + }
371 +
372 + /**
373 + * Remove a specific callback.
374 + *
375 + * - For multisite: Remove the callback from the network option and release the lock.
376 + * - For single-site: Unschedule the task.
377 + *
378 + * @param string $hook_name The hook name.
379 + * @return bool Whether the callback was removed.
380 + */
381 + public function remove_callback( string $hook_name ) {
382 + $hook_name = sanitize_key( $hook_name );
383 +
384 + if ( '' === $hook_name ) {
385 + return false;
386 + }
387 +
388 + if ( is_multisite() ) {
389 + // Ensure callbacks are loaded.
390 + $this->load_callbacks();
391 +
392 + if ( isset( $this->callbacks[ $hook_name ] ) ) {
393 + unset( $this->callbacks[ $hook_name ] );
394 + $this->save_callbacks();
395 + $this->log( "Task '{$hook_name}' removed.", self::LOG_FILE_NAME );
396 + }
397 +
398 + // Cleanup hook history.
399 + $last_run_key = $this->lastrun_prefix . $hook_name;
400 + delete_network_option( get_main_network_id(), $last_run_key );
401 +
402 + // Release the lock.
403 + $this->release_lock( $hook_name );
404 + } else {
405 + // Unschedule the task.
406 + $timestamp = wp_next_scheduled( $hook_name );
407 +
408 + if ( $timestamp ) {
409 + wp_unschedule_event( $timestamp, $hook_name );
410 + $this->log( "Task '{$hook_name}' unscheduled.", self::LOG_FILE_NAME );
411 + }
412 + }
413 +
414 + return true;
347 415 }
348 416 }