File: /var/www/html/ielts-store/wp-content/plugins/automatewoo/includes/Cron.php
<?php
// phpcs:ignoreFile
namespace AutomateWoo;
/**
 * Cron manager
 * @class Cron
 */
class Cron {
	const TWO_MINUTE_WORKER = 'automatewoo_two_minute_worker';
	const FIVE_MINUTE_WORKER = 'automatewoo_five_minute_worker';
	const FIFTEEN_MINUTE_WORKER = 'automatewoo_fifteen_minute_worker';
	const THIRTY_MINUTE_WORKER = 'automatewoo_thirty_minute_worker';
	const HOURLY_WORKER = 'automatewoo_hourly_worker';
	const FOUR_HOUR_WORKER = 'automatewoo_four_hourly_worker';
	const DAILY_WORKER = 'automatewoo_daily_worker';
	const TWO_DAY_WORKER = 'automatewoo_two_days_worker';
	const WEEKLY_WORKER = 'automatewoo_weekly_worker';
	/** @var array : worker => schedule */
	static $workers = [
		'events' => 'automatewoo_one_minute',
		'two_minute' => 'automatewoo_two_minutes',
		'five_minute' => 'automatewoo_five_minutes',
		'fifteen_minute' => 'automatewoo_fifteen_minutes',
		'thirty_minute' => 'automatewoo_thirty_minutes',
		'hourly' => 'hourly',
		'four_hourly' => 'automatewoo_four_hours',
		'daily' => 'daily',
		'two_days' => 'automatewoo_two_days',
		'weekly' => 'automatewoo_weekly'
	];
	/**
	 * Init cron
	 */
	static function init() {
		add_filter( 'cron_schedules', [ __CLASS__, 'add_schedules' ], 100 );
		foreach ( self::$workers as $worker => $schedule ) {
			add_action( 'automatewoo_' . $worker . '_worker', [ __CLASS__, 'before_worker' ], 1 );
		}
		add_action( 'admin_init', [ __CLASS__, 'add_events' ] );
		add_action( self::FIVE_MINUTE_WORKER, [ __CLASS__, 'check_for_gmt_offset_change' ] );
		add_action( self::THIRTY_MINUTE_WORKER, [ __CLASS__, 'check_midnight_cron' ], 1 );
		add_action( 'automatewoo_midnight', [ __CLASS__, 'update_midnight_cron_last_run' ], 1 );
		// set up midnight cron job, but doesn't repair it (which is important)
		add_action( 'admin_init', [ __CLASS__, 'setup_midnight_cron' ] );
	}
	/**
	 * Prevents workers from working if they have done so in the past 30 seconds
	 */
	static function before_worker() {
		$action = current_action();
		if ( self::is_worker_locked( $action ) ) {
			remove_all_actions( $action ); // prevent actions from running
			return;
		}
		@set_time_limit(300);
		self::update_last_run( $action );
	}
	/**
	 * @param $action
	 * @return \DateTime|bool
	 */
	static function get_last_run( $action ) {
		$last_runs = get_option('aw_workers_last_run');
		if ( is_array( $last_runs ) && isset( $last_runs[$action] ) ) {
			$date = new DateTime();
			$date->setTimestamp( $last_runs[$action] );
			return $date;
		}
		else {
			return false;
		}
	}
	/**
	 * @param $action
	 */
	static function update_last_run( $action ) {
		$last_runs = get_option('aw_workers_last_run');
		if ( ! $last_runs ) $last_runs = [];
		$last_runs[$action] = time();
		update_option( 'aw_workers_last_run', $last_runs, false );
	}
	/**
	 * @param $action
	 * @return int|false
	 */
	static function get_worker_interval( $action ) {
		$schedules = wp_get_schedules();
		$schedule = wp_get_schedule( $action );
		if ( isset( $schedules[$schedule] ) ) {
			return $schedules[$schedule]['interval'];
		}
		return false;
	}
	/**
	 * Checks if worker started running less than 30 seconds
	 *
	 * @param $action
	 * @return bool
	 */
	static function is_worker_locked( $action ) {
		if ( ! $time_last_run = self::get_last_run( $action ) ) {
			return false;
		}
		$time_unlocked = clone $time_last_run;
		$time_unlocked->modify( '+30 seconds' );
		if ( $time_unlocked->getTimestamp() > time() ) {
			return true;
		}
		return false;
	}
	/**
	 * Add cron workers
	 */
	static function add_events() {
		foreach ( self::$workers as $worker => $schedule ) {
			$hook = 'automatewoo_' . $worker . '_worker';
			if ( ! wp_next_scheduled( $hook ) ) {
				wp_schedule_event( time(), $schedule, $hook );
			}
		}
	}
	/**
	 * @param $schedules
	 * @return mixed
	 */
	static function add_schedules( $schedules ) {
		$schedules['automatewoo_one_minute'] = [
			'interval' => 60,
			'display' => __( 'One minute', 'automatewoo' )
		];
		$schedules['automatewoo_two_minutes'] = [
			'interval' => 120,
			'display' => __( 'Two minutes', 'automatewoo' )
		];
		$schedules['automatewoo_five_minutes'] = [
			'interval' => 300,
			'display' => __( 'Five minutes', 'automatewoo' )
		];
		$schedules['automatewoo_fifteen_minutes'] = [
			'interval' => 900,
			'display' => __( 'Fifteen minutes', 'automatewoo' )
		];
		$schedules['automatewoo_thirty_minutes'] = [
			'interval' => 1800,
			'display' => __( 'Thirty minutes', 'automatewoo' )
		];
		$schedules['automatewoo_two_days'] = [
			'interval' => 172800,
			'display' => __( 'Two days', 'automatewoo' )
		];
		$schedules['automatewoo_four_hours'] = [
			'interval' => 14400,
			'display' => __( 'Four hours', 'automatewoo' )
		];
		$schedules['automatewoo_weekly'] = [
			'interval' => 604800,
			'display' => __('Once weekly', 'automatewoo' )
		];
		return $schedules;
	}
	/**
	 * Track changes in the GMT offset such as DST
	 *
	 * @since 3.8
	 */
	static function check_for_gmt_offset_change() {
		$new_offset = Time_Helper::get_timezone_offset();
		$existing_offset = get_option( 'automatewoo_gmt_offset' );
		if ( $existing_offset === false ) {
			update_option( 'automatewoo_gmt_offset', $new_offset, false );
			return;
		}
		if ( $existing_offset != $new_offset ) {
			do_action( 'automatewoo/gmt_offset_changed', $new_offset, $existing_offset );
			update_option( 'automatewoo_gmt_offset', $new_offset, false );
		}
	}
	/**
	 * Set midnight cron, if not already set
	 * @since 3.8
	 */
	static function setup_midnight_cron() {
		if ( $next = wp_next_scheduled( 'automatewoo_midnight' ) ) {
			return false; // already setup
		}
		// calculate next midnight in the site's timezone
		$date = new DateTime( 'now' );
		$date->convert_to_site_time();
		$date->set_time_to_day_start();
		// actually trigger now instead of tomorrow, to avoid issues with custom time of day triggers
		// these triggers could skip 1 day if we don't run the midnight cron immediately when adding
		// TODO remove in the future
		//$date->modify('+1 day');
		$date->convert_to_utc_time(); // convert back to UTC
		self::update_midnight_cron( $date );
	}
	/**
	 * @return bool
	 */
	static function is_midnight_cron_correct() {
		if ( ! $next = wp_next_scheduled( 'automatewoo_midnight' ) ) {
			return false;
		}
		$date = new DateTime();
		$date->setTimestamp( $next );
		$date->convert_to_site_time();
		return $date->format('Hi') == '0000';
	}
	/**
	 * Sets a new time for the midnight cron.
	 *
	 * @param DateTime $date GMT
	 */
	static function update_midnight_cron( $date ) {
		wp_clear_scheduled_hook( 'automatewoo_midnight' );
		wp_schedule_event( $date->getTimestamp(), 'daily', 'automatewoo_midnight' );
	}
	/**
	 * Check the midnight cron job is correctly scheduled.
	 *
	 * If schedule is not correct this method fixes the schedule.
	 *
	 * @since 4.6.0
	 */
	public static function check_midnight_cron() {
		if ( self::is_midnight_cron_correct() ) {
			return;
		}
		// Repair the cron job schedule
		$date = new DateTime();
		$date->convert_to_site_time();
		$date->set_time_to_day_start();
		// If midnight cron should not run for today, schedule it for tomorrow
		// Otherwise, run it now because it's better to run at a slightly wrong time rather than not run at all.
		if ( ! self::should_midnight_cron_run_today() ) {
			// Replace date with last run +1 day, i.e. the day after last run
			$date = self::get_midnight_cron_last_run();
			$date->modify('+1 day');
		}
		$date->convert_to_utc_time();
		self::update_midnight_cron( $date );
	}
	/**
	 * Update the last run date of the midnight cron to now.
	 *
	 * Store last run in site time as Y-m-d.
	 *
	 * This is stored as site time because the goal of the midnight cron event is to run once per day
	 * in the site's timezone. Storing in site time means we can handle DST timezone changes better.
	 *
	 * @since 4.6.0
	 */
	public static function update_midnight_cron_last_run() {
		$now = new DateTime();
		$now->convert_to_site_time();
		update_option( 'automatewoo_midnight_cron_last_run', $now->format( 'Y-m-d' ), false );
	}
	/**
	 * Get the last run date of the midnight cron in site time.
	 *
	 * @since 4.6.0
	 *
	 * @return DateTime|false
	 */
	public static function get_midnight_cron_last_run() {
		$last_run = get_option( 'automatewoo_midnight_cron_last_run' );
		return $last_run ? aw_normalize_date( $last_run ) : false;
	}
	/**
	 * Did the midnight cron task run today (in local time)?
	 *
	 * Also returns true if midnight cron has run for tomorrow. E.g. in the case of DST changes.
	 *
	 * @since 4.6.0
	 *
	 * @return bool
	 */
	public static function should_midnight_cron_run_today() {
		$last_run = self::get_midnight_cron_last_run();
		if ( ! $last_run ) {
			return true;
		}
		$last_run->set_time_to_day_end();
		$now = new DateTime();
		$now->convert_to_site_time();
		// Return false if cron has run today or even for tomorrow
		return $now > $last_run;
	}
}