recurrence_fast = $this->get_recurrence_time(); // initialize from configuration. $this->recurrence_slow = 2 * $this->recurrence_fast; if ( $add_handlers ) { add_filter( 'cron_schedules', [ $this, 'cron_schedules_add_interval' ] ); // phpcs:ignore WordPress.VIP.CronInterval.ChangeDetected,WordPress.WP.CronInterval.ChangeDetected add_action( $this->event_name, [ $this, 'run_task' ] ); } } /** * Add custom schedule interval for Internal links updates. * * @param array $schedules Existing schedules. * @return array Schedules with our intervals. */ public function cron_schedules_add_interval( $schedules ) { // Note: callback, do not use parameter types. if ( ! is_array( $schedules ) ) { $schedules = []; } if ( ! isset( $schedules['ahrefs_fast'] ) ) { $schedules['ahrefs_fast'] = [ 'interval' => $this->recurrence_fast, 'display' => __( 'Ahrefs SEO', 'ahrefs-seo' ) . ': ' . __( 'fast update', 'ahrefs-seo' ), ]; } if ( ! isset( $schedules['ahrefs_slow'] ) ) { $schedules['ahrefs_slow'] = [ 'interval' => $this->recurrence_slow, 'display' => __( 'Ahrefs SEO', 'ahrefs-seo' ) . ': ' . __( 'slow update', 'ahrefs-seo' ), ]; } return $schedules; } /** * Start links fast or slow updates or change scheduled recurrence/next time. * * @param bool $fast_updates True: run fast updates, False: run slow updates. * @param bool $is_recurrence_updated Is new recurrence value different from previous? Need to recreate existing task. No need to start new task. * @return void */ public function start_tasks( $fast_updates = true, $is_recurrence_updated = false ) { Ahrefs_Seo::breadcrumbs( get_called_class() . '::' . __FUNCTION__ . (string) wp_json_encode( func_get_args() ) ); $recurrence = $fast_updates ? 'ahrefs_fast' : 'ahrefs_slow'; $next_time = wp_next_scheduled( $this->event_name ); if ( ! $next_time ) { if ( ! $is_recurrence_updated ) { // start next task, nearest call in 15 seconds. $desired_time = time() + 15; if ( false === wp_schedule_event( $desired_time, $recurrence, $this->event_name ) ) { Ahrefs_Seo::notify( new Ahrefs_Seo_Exception( 'Cron schedule failed ' . (string) wp_json_encode( [ $desired_time, $recurrence, $this->event_name, time() ] ) ) ); } } } else { $existing = wp_get_schedule( $this->event_name ); // update event if recurrence is different from existing or scheduled call is after longest wait time for fast update. $desired_time = time() + $this->recurrence_fast; if ( $is_recurrence_updated || $existing !== $recurrence || $next_time > $desired_time ) { wp_clear_scheduled_hook( $this->event_name ); if ( false === wp_schedule_event( $desired_time, $recurrence, $this->event_name ) ) { Ahrefs_Seo::notify( new Ahrefs_Seo_Exception( 'Cron schedule failed ' . (string) wp_json_encode( [ $desired_time, $recurrence, $this->event_name, time() ] ) ) ); } } } } /** * Stop tasks, remove scheduled event * * @return void */ public function stop_tasks() { Ahrefs_Seo::breadcrumbs( get_called_class() . '::' . __FUNCTION__ ); if ( wp_next_scheduled( $this->event_name ) ) { wp_clear_scheduled_hook( $this->event_name ); } } /** * Apply time limits * * @since 0.7.3 * * @return void */ protected function apply_time_limits() { Ahrefs_Seo::set_time_limit( 300 ); // call it before set transient, because it can update transient time. } /** * Run the task * * @return void */ public function run_task() { try { $this->apply_time_limits(); Ahrefs_Seo::breadcrumbs( get_called_class() . '::' . __FUNCTION__ . sprintf( ' Transient time: %d', Ahrefs_Seo::transient_time() ) ); if ( ! $this->is_busy() ) { set_transient( $this->transient_name, true, Ahrefs_Seo::transient_time() ); Ahrefs_Seo::ignore_user_abort( true ); // run until finished or time limit reached. while ( $executed = $this->execute() ) { // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition if ( Ahrefs_Seo::should_finish( null, 33 ) ) { // allow 2/3 of all time to update internal links. Ahrefs_Seo::breadcrumbs( get_called_class() . '::' . __FUNCTION__ . ' exit earlier.' ); break; } } if ( ! $executed ) { // Nothing to update now, but there may be tasks, blocked by something. if ( $this->has_slow_tasks() ) { $this->start_tasks( false ); // switch to slow update mode. } else { $this->stop_tasks(); // all finished: stop cron task. } } delete_transient( $this->transient_name ); } Ahrefs_Seo::breadcrumbs( get_called_class() . '::' . __FUNCTION__ . ' exit.' ); } catch ( Error $e ) { Ahrefs_Seo_Compatibility::on_type_error( $e, __METHOD__, __FILE__, __( 'Run task failed', 'ahrefs-seo' ) ); } catch ( Exception $e ) { Ahrefs_Seo::notify( $e, 'Run task failed' ); } } /** * Is task already running? * * @since 0.9.2 * * @return bool */ protected function is_busy() { return ! empty( get_transient( $this->transient_name ) ); } /** * Set recurrence time * * @since 0.9.2 * * @param int $seconds Time between subsequent requests, in seconds. * @return void */ public function set_recurrence_time( $seconds ) { if ( $seconds < 30 ) { $seconds = self::DEFAULT_RECURRENCE_SECONDS; } update_option( self::OPTION_RECURRENCE_SECONDS, $seconds ); $this->recurrence_fast = $seconds; $this->recurrence_slow = 2 * $this->recurrence_fast; $old_value = $this->get_recurrence_time(); // does the new value different from existing? if ( $old_value !== $seconds ) { $this->start_tasks( true, true ); // update existing tasks. } } /** * Get recurrence time * * @since 0.9.2 * * @return int Time between subsequent requests, in seconds. */ public function get_recurrence_time() { $result = (int) get_option( self::OPTION_RECURRENCE_SECONDS, self::DEFAULT_RECURRENCE_SECONDS ); return $result >= 30 ? $result : self::DEFAULT_RECURRENCE_SECONDS; } /** * Execute an update. * * @return bool True if task finished, false if nothing to run. */ public abstract function execute(); /** * Has more tasks, but need to switch to slow mode. * * @return bool True if we have pending tasks. */ public abstract function has_slow_tasks(); }