class.llms.controller.lesson.progression.php 7.9 KB
Newer Older
cyrille's avatar
cyrille committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
<?php
/**
 * Lesson Progression Actions
 *
 * @package LifterLMS/Controllers/Classes
 *
 * @since 3.17.1
 * @version 5.9.0
 */

defined( 'ABSPATH' ) || exit;

/**
 * LLMS_Controller_Lesson_Progression class
 *
 * @since 3.17.1
 */
class LLMS_Controller_Lesson_Progression {

	/**
	 * Constructor
	 *
	 * @since 3.17.1
	 * @since 3.29.0 Unknown
	 *
	 * @return void
	 */
	public function __construct() {

		add_action( 'admin_init', array( $this, 'handle_admin_managment_forms' ) );

		add_action( 'init', array( $this, 'handle_complete_form' ) );
		add_action( 'init', array( $this, 'handle_incomplete_form' ) );

		add_action( 'lifterlms_quiz_completed', array( $this, 'quiz_complete' ), 10, 3 );
		add_filter( 'llms_allow_lesson_completion', array( $this, 'quiz_maybe_prevent_lesson_completion' ), 10, 5 );

		add_action( 'llms_trigger_lesson_completion', array( $this, 'mark_complete' ), 10, 4 );

	}

	/**
	 * Retrieve a lesson ID from form data for the mark complete / incomplete forms
	 *
	 * @since 3.29.0
	 *
	 * @param tring $action Form action, either "complete" or "incomplete".
	 * @return int|null Returns `null` when either required post fields are missing or if the lesson_id is non-numeric, int (lesson id) on success.
	 */
	private function get_lesson_id_from_form_data( $action ) {

		if ( ! llms_verify_nonce( '_wpnonce', 'mark_' . $action, 'POST' ) ) {
			return null;
		}

		$submitted = llms_filter_input( INPUT_POST, 'mark_' . $action );
		$lesson_id = llms_filter_input( INPUT_POST, 'mark-' . $action );

		// Required fields.
		if ( is_null( $submitted ) || is_null( $lesson_id ) ) {
			return null;
		}

		$lesson_id = absint( $lesson_id );

		// Invalid lesson ID.
		if ( ! $lesson_id || ! is_numeric( $lesson_id ) ) {

			llms_add_notice( __( 'An error occurred, please try again.', 'lifterlms' ), 'error' );
			return null;

		}

		return $lesson_id;

	}

	/**
	 * Handle form submission from the Student -> Courses -> Course table where admins can toggle completion of lessons for a student.
	 *
	 * @since 3.29.0
	 * @since 5.9.0 Stop using deprecated `FILTER_SANITIZE_STRING`.
	 *
	 * @return void
	 */
	public function handle_admin_managment_forms() {

		if ( ! llms_verify_nonce( 'llms-admin-progression-nonce', 'llms-admin-lesson-progression', 'POST' ) ) {
			return;
		}

		$action     = llms_filter_input( INPUT_POST, 'llms-lesson-action' );
		$lesson_id  = absint( llms_filter_input( INPUT_POST, 'lesson_id' ) );
		$student_id = absint( llms_filter_input( INPUT_POST, 'student_id' ) );

		// Missing required data.
		if ( empty( $action ) || empty( $lesson_id ) || empty( $student_id ) ) {
			return;
		}

		$trigger = 'admin_' . get_current_user_id();

		if ( 'complete' === $action ) {
			$this->mark_complete( $student_id, $lesson_id, $trigger );
		} elseif ( 'incomplete' === $action ) {
			llms_mark_incomplete( $student_id, $lesson_id, 'lesson', $trigger );
		}

	}

	/**
	 * Mark Lesson as complete
	 *
	 * + Complete Lesson form post.
	 * + Marks lesson as complete and returns completion message to user.
	 * + Autoadvances to next lesson if completion is successful.
	 *
	 * @since 3.17.1
	 * @since 3.29.0 Unknown.
	 *
	 * @return void
	 */
	public function handle_complete_form() {

		$lesson_id = $this->get_lesson_id_from_form_data( 'complete' );

		if ( is_null( $lesson_id ) ) {
			return;
		}

		/**
		 * Filter to modify the user id instead of current logged in user id.
		 *
		 * @param int $user_id User id to mark lesson as complete.
		 *
		 * @since 5.4.0
		 */
		$user_id = apply_filters( 'llms_lesson_completion_user_id', get_current_user_id() );

		do_action( 'llms_trigger_lesson_completion', $user_id, $lesson_id, 'lesson_' . $lesson_id );

		if ( apply_filters( 'lifterlms_autoadvance', true ) ) {

			$lesson         = new LLMS_Lesson( $lesson_id );
			$next_lesson_id = $lesson->get_next_lesson();
			if ( $next_lesson_id ) {

				wp_redirect( apply_filters( 'llms_lesson_complete_redirect', get_permalink( $next_lesson_id ) ) );
				exit;

			}
		}

	}

	/**
	 * Mark Lesson as incomplete
	 *
	 * + Incomplete Lesson form post.
	 * + Marks lesson as incomplete and returns incompletion message to user.
	 *
	 * @since 3.17.1
	 * @since 3.29.0 Unknown.
	 *
	 * @return void
	 */
	public function handle_incomplete_form() {

		$lesson_id = $this->get_lesson_id_from_form_data( 'incomplete' );

		if ( is_null( $lesson_id ) ) {
			return;
		}

		/**
		 * Filter to modify the user id instead of current logged in user id.
		 *
		 * @param int $user_id User id to mark lesson as incomplete.
		 *
		 * @since 5.4.0
		 */
		$user_id = apply_filters( 'llms_lesson_incomplete_user_id', get_current_user_id() );

		// Mark incomplete and add a notice on success.
		if ( llms_mark_incomplete( $user_id, $lesson_id, 'lesson', 'lesson_' . $lesson_id ) ) {
			// Translators: %s is the title of the lesson.
			llms_add_notice( sprintf( __( 'The lesson %s is now marked as incomplete.', 'lifterlms' ), get_the_title( $lesson_id ) ) );
		}

	}

	/**
	 * Handle completion of lesson via `llms_trigger_lesson_completion` action
	 *
	 * @since 3.17.1
	 * @since 3.29.0 Unknown.
	 *
	 * @param int    $user_id   User ID.
	 * @param int    $lesson_id Lesson ID.
	 * @param string $trigger   Optional trigger description string.
	 * @param array  $args      Optional arguments.
	 * @return void
	 */
	public function mark_complete( $user_id, $lesson_id, $trigger = '', $args = array() ) {

		if ( llms_allow_lesson_completion( $user_id, $lesson_id, $trigger, $args ) ) {

			llms_mark_complete( $user_id, $lesson_id, 'lesson', $trigger );

		}

	}

	/**
	 * Trigger lesson completion when a quiz is completed
	 *
	 * @since 3.17.1
	 *
	 * @param int $student_id WP User ID.
	 * @param int $quiz_id    WP Post ID of the quiz.
	 * @param obj $attempt    Instance of the LLMS_Quiz_Attempt.
	 * @return void
	 */
	public function quiz_complete( $student_id, $quiz_id, $attempt ) {

		do_action(
			'llms_trigger_lesson_completion',
			$student_id,
			$attempt->get( 'lesson_id' ),
			'quiz_' . $quiz_id,
			array(
				'attempt' => $attempt,
			)
		);

	}

	/**
	 * Before a lesson is marked as complete, check if all the lesson's quiz requirements are met
	 *
	 * @since 3.17.1
	 *
	 * @param bool   $allow_completion Whether or not to allow completion (true by default, false if something else has already prevented).
	 * @param int    $user_id          WP User ID of the student completing the lesson.
	 * @param int    $lesson_id        WP Post ID of the lesson to be completed.
	 * @param string $trigger          Text string to record the reason why the lesson is being completed.
	 * @param array  $args             Optional additional arguments from the triggering function.
	 * @return bool
	 */
	public function quiz_maybe_prevent_lesson_completion( $allow_completion, $user_id, $lesson_id, $trigger, $args ) {

		// If allow completion is already false, we don't need to run any quiz checks.
		if ( ! $allow_completion ) {
			return $allow_completion;
		}

		$lesson           = llms_get_post( $lesson_id );
		$passing_required = llms_parse_bool( $lesson->get( 'require_passing_grade' ) );

		// If the lesson is being completed by a quiz.
		if ( 0 === strpos( $trigger, 'quiz_' ) ) {

			// Passing is required AND the attempt was a failure.
			if ( $passing_required && ! $args['attempt']->is_passing() ) {
				$allow_completion = false;
			}
		} elseif ( $lesson->is_quiz_enabled() ) {

			$quiz_id = $lesson->get( 'quiz' );
			$student = llms_get_student( $user_id );
			$attempt = $student->quizzes()->get_best_attempt( $quiz_id );

			// Passing is not required but there's not attempts yet.
			// At least one attempt (passing or otherwise) is required!.
			if ( ! $passing_required && ! $attempt ) {
				$allow_completion = false;

				// Passing is required and there's no attempts or the best attempt is not passing.
			} elseif ( $passing_required && ( ! $attempt || ! $attempt->is_passing() ) ) {
				$allow_completion = false;
			}
		}

		return $allow_completion;

	}

}

return new LLMS_Controller_Lesson_Progression();