t_ update the last fired time. * * @param string $id * * @return void */ abstract public function run_recurring_event( $id ); /** * Run a single event, even if it is not time to. * * This will clear the event from the schedule. * * @param string $id * @param array $data * * @return void */ abstract public function run_single_event( $id, $data = array() ); /** * Run a single event by it's hash. * * @param string $id * @param string $hash * * @return void */ abstract public function run_single_event_by_hash( $id, $hash ); /** * Run any events that are due now. * * @param int $now * * @return void */ abstract public function run_due_now( $now = 0 ); /** * Code executed on every page load to setup the scheduler. * * @return void */ abstract public function run(); /** * Check whether the scheduler is currently executing an event. * * @return bool */ final public function is_running() { return $this->is_running; } /** * Manually trigger modules to register their scheduled events. * * @return void */ public function register_events() { /** * Register scheduled events. * * Events should be registered in response to a user action, for example activating a module or changing a setting. * Occasionally, Solid Security will manually ask for all events to be scheduled. * * @param ITSEC_Scheduler $this */ do_action( 'itsec_scheduler_register_events', $this ); $this->register_events_for_module( ':active' ); } /** * Registers events for a given module using the "scheduling.php" module file. * * @param string $module The module specifier. For example ':active' or 'global'. */ public function register_events_for_module( $module ) { ITSEC_Modules::load_module_file( 'scheduling.php', $module, function ( $fn ) { if ( ! is_callable( $fn ) ) { _doing_it_wrong( 'scheduling.php', __( 'A Solid Security module\'s scheduling.php file must return a callable.', 'better-wp-security' ), '5.8.0' ); return; } $fn( $this ); } ); foreach ( ITSEC_Modules::get_config_list( $module ) as $config ) { $this->register_events_for_config( $config ); } } /** * Registers events based on a module configuration. * * @param Module_Config $config The config to use. */ public function register_events_for_config( Module_Config $config ) { foreach ( $config->get_scheduling() as $id => $definition ) { if ( $conditional_schema = $definition['conditional'] ?? [] ) { $settings = ITSEC_Modules::get_settings( $config->get_id() ); $validated = rest_validate_value_from_schema( $settings, $conditional_schema ); $sanitized = is_wp_error( $validated ) ? $validated : rest_sanitize_value_from_schema( $settings, $conditional_schema ); $is_active = ! is_wp_error( $validated ) && ! is_wp_error( $sanitized ); if ( ! $is_active ) { $this->unschedule( $id ); continue; } } $schedule = $definition['schedule']; $data = $definition['data'] ?? []; $opts = $definition['opts'] ?? []; if ( isset( $opts['fire_at'] ) ) { $opts['fire_at'] = ITSEC_Core::get_current_time_gmt() + $opts['fire_at']; } $this->schedule( $schedule, $id, $data, $opts ); } } /** * Unregisters events for a given module. * * @param Module_Config $config The config to use. */ public function unregister_events_for_config( Module_Config $config ) { foreach ( $config->get_scheduling() as $id => $definition ) { $this->unschedule( $id ); } } /** * Register a custom schedule. * * @param string $slug * @param int $interval */ public function register_custom_schedule( $slug, $interval ) { $this->custom_schedules[ $slug ] = $interval; } /** * Get a registered custom schedules. * * @return array */ public function get_custom_schedules() { return $this->custom_schedules; } /** * Register an event loop. * * This allows for splitting up a long running process across multiple page loads. * * @param string $id The event ID. * @param string $schedule The schedule between loop starts. This is the maximum amount of time to wait. * @param int $wait Time to wait in seconds between loop parts. */ public function register_loop( $id, $schedule, $wait ) { $this->loops[ $id ] = array( 'schedule' => $schedule, 'wait' => $wait, ); } /** * Get the loop configuration. * * @param string $id * * @return array */ public function get_loop( $id ) { return isset( $this->loops[ $id ] ) ? $this->loops[ $id ] : array(); } /** * Get a lock to be used for scheduling events. * * @return bool */ protected function scheduling_lock() { return ITSEC_Lib::get_lock( self::LOCK_SCHEDULING, 5 ); } /** * Release the lock used for scheduling events. */ protected function scheduling_unlock() { ITSEC_Lib::release_lock( self::LOCK_SCHEDULING ); } /** * Make a job object. * * @param string $id * @param array $data * @param array $opts * * @return ITSEC_Job */ protected function make_job( $id, $data, $opts = array() ) { return new ITSEC_Job( $this, $id, $data, $opts ); } /** * Dispatch the action to execute the scheduled job. * * @param ITSEC_Job $job */ protected final function call_action( ITSEC_Job $job ) { $interactive = ITSEC_Core::is_interactive(); ITSEC_Core::set_interactive( false ); $this->is_running = true; try { /** * Fires when a scheduled job should be executed. * * @param ITSEC_Job $job */ do_action( "itsec_scheduled_{$job->get_id()}", $job ); } catch ( Exception $e ) { ITSEC_Log::add_fatal_error( 'scheduler', 'unhandled-exception', array( 'exception' => (string) $e, 'job' => $job->get_id(), 'data' => $job->get_data(), ) ); $job->reschedule_in( 500 ); } $this->is_running = false; ITSEC_Core::set_interactive( $interactive ); } /** * Generate a unique hash of the data. * * @param array $data * * @return string */ protected function hash_data( $data ) { return md5( serialize( $data ) ); } /** * Get the interval for the schedule. * * @param string $schedule * * @return int */ final public function get_schedule_interval( $schedule ) { switch ( $schedule ) { case self::S_TWICE_HOURLY: return HOUR_IN_SECONDS / 2; case self::S_HOURLY: return HOUR_IN_SECONDS; case self::S_FOUR_DAILY: return DAY_IN_SECONDS / 4; case self::S_TWICE_DAILY: return DAY_IN_SECONDS / 2; case self::S_DAILY: return DAY_IN_SECONDS; case self::S_WEEKLY: return WEEK_IN_SECONDS; case self::S_MONTHLY: return MONTH_IN_SECONDS; default: return isset( $this->custom_schedules[ $schedule ] ) ? $this->custom_schedules[ $schedule ] : false; } } /** * Run code when the plugin is uninstalled. */ public function uninstall() { } /** * Reset the scheduler. * * This unregisters all events, and re-registers them. */ public function reset() { $this->uninstall(); $this->run(); $this->register_events(); } } t_ update the last fired time. * * @param string $id * * @return void */ abstract public function run_recurring_event( $id ); /** * Run a single event, even if it is not time to. * * This will clear the event from the schedule. * * @param string $id * @param array $data * * @return void */ abstract public function run_single_event( $id, $data = array() ); /** * Run a single event by it's hash. * * @param string $id * @param string $hash * * @return void */ abstract public function run_single_event_by_hash( $id, $hash ); /** * Run any events that are due now. * * @param int $now * * @return void */ abstract public function run_due_now( $now = 0 ); /** * Code executed on every page load to setup the scheduler. * * @return void */ abstract public function run(); /** * Check whether the scheduler is currently executing an event. * * @return bool */ final public function is_running() { return $this->is_running; } /** * Manually trigger modules to register their scheduled events. * * @return void */ public function register_events() { /** * Register scheduled events. * * Events should be registered in response to a user action, for example activating a module or changing a setting. * Occasionally, Solid Security will manually ask for all events to be scheduled. * * @param ITSEC_Scheduler $this */ do_action( 'itsec_scheduler_register_events', $this ); $this->register_events_for_module( ':active' ); } /** * Registers events for a given module using the "scheduling.php" module file. * * @param string $module The module specifier. For example ':active' or 'global'. */ public function register_events_for_module( $module ) { ITSEC_Modules::load_module_file( 'scheduling.php', $module, function ( $fn ) { if ( ! is_callable( $fn ) ) { _doing_it_wrong( 'scheduling.php', __( 'A Solid Security module\'s scheduling.php file must return a callable.', 'better-wp-security' ), '5.8.0' ); return; } $fn( $this ); } ); foreach ( ITSEC_Modules::get_config_list( $module ) as $config ) { $this->register_events_for_config( $config ); } } /** * Registers events based on a module configuration. * * @param Module_Config $config The config to use. */ public function register_events_for_config( Module_Config $config ) { foreach ( $config->get_scheduling() as $id => $definition ) { if ( $conditional_schema = $definition['conditional'] ?? [] ) { $settings = ITSEC_Modules::get_settings( $config->get_id() ); $validated = rest_validate_value_from_schema( $settings, $conditional_schema ); $sanitized = is_wp_error( $validated ) ? $validated : rest_sanitize_value_from_schema( $settings, $conditional_schema ); $is_active = ! is_wp_error( $validated ) && ! is_wp_error( $sanitized ); if ( ! $is_active ) { $this->unschedule( $id ); continue; } } $schedule = $definition['schedule']; $data = $definition['data'] ?? []; $opts = $definition['opts'] ?? []; if ( isset( $opts['fire_at'] ) ) { $opts['fire_at'] = ITSEC_Core::get_current_time_gmt() + $opts['fire_at']; } $this->schedule( $schedule, $id, $data, $opts ); } } /** * Unregisters events for a given module. * * @param Module_Config $config The config to use. */ public function unregister_events_for_config( Module_Config $config ) { foreach ( $config->get_scheduling() as $id => $definition ) { $this->unschedule( $id ); } } /** * Register a custom schedule. * * @param string $slug * @param int $interval */ public function register_custom_schedule( $slug, $interval ) { $this->custom_schedules[ $slug ] = $interval; } /** * Get a registered custom schedules. * * @return array */ public function get_custom_schedules() { return $this->custom_schedules; } /** * Register an event loop. * * This allows for splitting up a long running process across multiple page loads. * * @param string $id The event ID. * @param string $schedule The schedule between loop starts. This is the maximum amount of time to wait. * @param int $wait Time to wait in seconds between loop parts. */ public function register_loop( $id, $schedule, $wait ) { $this->loops[ $id ] = array( 'schedule' => $schedule, 'wait' => $wait, ); } /** * Get the loop configuration. * * @param string $id * * @return array */ public function get_loop( $id ) { return isset( $this->loops[ $id ] ) ? $this->loops[ $id ] : array(); } /** * Get a lock to be used for scheduling events. * * @return bool */ protected function scheduling_lock() { return ITSEC_Lib::get_lock( self::LOCK_SCHEDULING, 5 ); } /** * Release the lock used for scheduling events. */ protected function scheduling_unlock() { ITSEC_Lib::release_lock( self::LOCK_SCHEDULING ); } /** * Make a job object. * * @param string $id * @param array $data * @param array $opts * * @return ITSEC_Job */ protected function make_job( $id, $data, $opts = array() ) { return new ITSEC_Job( $this, $id, $data, $opts ); } /** * Dispatch the action to execute the scheduled job. * * @param ITSEC_Job $job */ protected final function call_action( ITSEC_Job $job ) { $interactive = ITSEC_Core::is_interactive(); ITSEC_Core::set_interactive( false ); $this->is_running = true; try { /** * Fires when a scheduled job should be executed. * * @param ITSEC_Job $job */ do_action( "itsec_scheduled_{$job->get_id()}", $job ); } catch ( Exception $e ) { ITSEC_Log::add_fatal_error( 'scheduler', 'unhandled-exception', array( 'exception' => (string) $e, 'job' => $job->get_id(), 'data' => $job->get_data(), ) ); $job->reschedule_in( 500 ); } $this->is_running = false; ITSEC_Core::set_interactive( $interactive ); } /** * Generate a unique hash of the data. * * @param array $data * * @return string */ protected function hash_data( $data ) { return md5( serialize( $data ) ); } /** * Get the interval for the schedule. * * @param string $schedule * * @return int */ final public function get_schedule_interval( $schedule ) { switch ( $schedule ) { case self::S_TWICE_HOURLY: return HOUR_IN_SECONDS / 2; case self::S_HOURLY: return HOUR_IN_SECONDS; case self::S_FOUR_DAILY: return DAY_IN_SECONDS / 4; case self::S_TWICE_DAILY: return DAY_IN_SECONDS / 2; case self::S_DAILY: return DAY_IN_SECONDS; case self::S_WEEKLY: return WEEK_IN_SECONDS; case self::S_MONTHLY: return MONTH_IN_SECONDS; default: return isset( $this->custom_schedules[ $schedule ] ) ? $this->custom_schedules[ $schedule ] : false; } } /** * Run code when the plugin is uninstalled. */ public function uninstall() { } /** * Reset the scheduler. * * This unregisters all events, and re-registers them. */ public function reset() { $this->uninstall(); $this->run(); $this->register_events(); } }