Source: includes/enrolment/class-sensei-enrolment-learner-calculation-job.php

<?php
/**
 * File containing the class Sensei_Enrolment_Learner_Calculation_Job.
 *
 * @package sensei
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * The Sensei_Enrolment_Learner_Calculation_Job is responsible for running jobs of user enrolment calculations. It is set
 * up to run once after a Sensei version upgrade or when Sensei_Course_Enrolment_Manager::get_site_salt() is updated.
 */
class Sensei_Enrolment_Learner_Calculation_Job implements Sensei_Background_Job_Interface {
	const NAME                      = 'sensei_calculate_learner_enrolments';
	const DEFAULT_BATCH_SIZE        = 20;
	const OPTION_TRACK_LAST_USER_ID = 'sensei_calculate_learner_enrolments_job_last_user_id';
	const OPTION_TRACK_VERSION_CALC = 'sensei_calculate_learner_enrolments_job_calculating_version';

	/**
	 * Number of users for each job run.
	 *
	 * @var integer
	 */
	private $batch_size;

	/**
	 * Whether the job is complete.
	 *
	 * @var bool
	 */
	private $is_complete = false;

	/**
	 * Sensei_Enrolment_Learner_Calculation_Job constructor.
	 */
	public function __construct() {
		/**
		 * Filter the batch size for the number of users to query per run in the learner calculation job.
		 *
		 * @since 3.0.0
		 *
		 * @hook sensei_enrolment_learner_calculation_job_batch_size
		 *
		 * @param {int} $batch_size Batch size to filter.
		 * @return {int} Filtered batch size.
		 */
		$this->batch_size = apply_filters( 'sensei_enrolment_learner_calculation_job_batch_size', self::DEFAULT_BATCH_SIZE );
	}

	/**
	 * Get the action name for the scheduled job.
	 *
	 * @return string
	 */
	public function get_name() {
		return self::NAME;
	}

	/**
	 * Get the arguments to run with the job.
	 *
	 * @return array
	 */
	public function get_args() {
		return [];
	}

	/**
	 * Run the job.
	 */
	public function run() {
		$user_args = [
			'fields'  => 'ID',
			'number'  => $this->batch_size,
			'order'   => 'ASC',
			'orderby' => 'ID',
		];

		add_action( 'pre_user_query', [ $this, 'modify_user_query_add_user_id' ] );
		$user_query = new WP_User_Query( $user_args );
		remove_action( 'pre_user_query', [ $this, 'modify_user_query_add_user_id' ] );

		$user_ids = $user_query->get_results();

		add_filter( 'sensei_course_enrolment_store_results', [ Sensei_Course_Enrolment::class, 'do_not_store_negative_enrolment_results' ], 10, 5 );
		foreach ( $user_ids as $user_id ) {
			Sensei_Course_Enrolment_Manager::instance()->recalculate_enrolments( $user_id );
			$this->set_last_user_id( $user_id );
		}
		remove_filter( 'sensei_course_enrolment_store_results', [ Sensei_Course_Enrolment::class, 'do_not_store_negative_enrolment_results' ], 10 );

		if (
			empty( $user_ids )
			|| (int) $user_query->get_total() <= (int) $this->batch_size
		) {
			$this->end();
		}
	}

	/**
	 * Set up job before it is scheduled for the first time.
	 *
	 * @param string $current_version Setting up current version.
	 */
	public function setup( $current_version ) {
		update_option( self::OPTION_TRACK_VERSION_CALC, $current_version, false );

		$this->set_last_user_id( 0 );
	}

	/**
	 * Check if version that is running is for the current version.
	 *
	 * @param string $version_check Version to check.
	 *
	 * @return bool
	 */
	public function is_calculating_version( $version_check ) {
		$version = get_option( self::OPTION_TRACK_VERSION_CALC, false );

		return $version === $version_check;
	}

	/**
	 * After the job runs, check to see if it needs to be re-queued for the next batch.
	 *
	 * @return bool
	 */
	public function is_complete() {
		return $this->is_complete;
	}

	/**
	 * Modify user query to add the user ID check.
	 *
	 * @access private
	 *
	 * @param WP_User_Query $user_query User query to modify.
	 */
	public function modify_user_query_add_user_id( WP_User_Query $user_query ) {
		global $wpdb;

		$user_query->query_where .= $wpdb->prepare( ' AND ID>%d', $this->get_last_user_id() );
	}

	/**
	 * Set the last calculated user ID.
	 *
	 * @param int $user_id User ID.
	 */
	private function set_last_user_id( $user_id ) {
		update_option( self::OPTION_TRACK_LAST_USER_ID, (int) $user_id, false );
	}

	/**
	 * Get the last user ID that was calculated.
	 *
	 * @return int
	 */
	public function get_last_user_id() {
		return (int) get_option( self::OPTION_TRACK_LAST_USER_ID, 0 );
	}

	/**
	 * Clean up after the job ends.
	 */
	public function end() {
		$this->is_complete = true;

		delete_option( self::OPTION_TRACK_LAST_USER_ID );
		delete_option( self::OPTION_TRACK_VERSION_CALC );
	}
}