<?php
/**
 * Course Progress Functions
 *
 * @since 2.1.0
 *
 * @package LearnDash\Course
 */

use LearnDash\Core\Utilities\Cast;

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

// cspell:ignore accessable .

/**
 * Outputs the HTML output to mark a course complete.
 *
 * Must meet requirements of course to mark the course as complete.
 *
 * @since 2.1.0
 * @since 4.24.0 Added support for overriding the form action.
 *
 * @param WP_Post $post The `WP_Post` lesson or topic object.
 * @param array   $atts Optional. An array of attributes to mark course complete. Default empty array.
 *
 * @return string HTML output to mark course complete
 */
function learndash_mark_complete( $post, $atts = array() ) {
	if ( ! is_user_logged_in() ) {
		return '';
	}

	$user_id = get_current_user_id();

	if ( isset( $_POST['sfwd_mark_complete'] ) && isset( $_POST['post'] ) && intval( $_POST['post'] ) == $post->ID ) {
		return '';
	}

	$bypass_course_limits_admin_users = learndash_can_user_bypass( $user_id, 'learndash_course_progression', $post->ID, $post );

	/**
	 * Bypass prerequisites.
	 *
	 * @since 2.4.0
	 *
	 * @param boolean $bypass  True/False is user is allowed to bypass.
	 * @param integer $user_id User ID.
	 * @param integer $post_id Post ID.
	 * @param object  $post    WP_Post instance
	 */
	$bypass_course_limits_admin_users = apply_filters( 'learndash_prerequities_bypass', $bypass_course_limits_admin_users, $user_id, $post->ID, $post ); // cspell:disable-line -- prerequities are prerequisites...

	$course_id = learndash_get_course_id( $post->ID );

	if ( ( learndash_lesson_progression_enabled() ) && ( ! $bypass_course_limits_admin_users ) ) {
		if ( ! sfwd_lms_has_access( $course_id, $user_id ) ) {
			return '';
		}

		// Check Course Prerequisites.
		if ( ! learndash_is_course_prerequities_completed( $course_id, $user_id ) ) { // cspell:disable-line -- prerequities are prerequisites...
			return '';
		}

		if ( ! learndash_check_user_course_points_access( $course_id, $user_id ) ) {
			return '';
		}

		$step_quiz_list = learndash_get_lesson_quiz_list( $post->ID, $user_id, $course_id );

		if ( ! empty( $step_quiz_list ) ) {
			foreach ( $step_quiz_list as $quiz ) {
				if ( 'notcompleted' === $quiz['status'] ) {
					return '';
				}
			}
		}

		if ( 'sfwd-lessons' === $post->post_type ) {
			$progress = learndash_get_course_progress( $user_id, $post->ID );

			if ( ! empty( $progress['this']->completed ) ) {
				/** This filter is documented in includes/class-ld-cpt-instance.php */
				if ( ! apply_filters( 'learndash_previous_step_completed', false, $progress['this']->ID, $user_id ) ) {
					return learndash_show_mark_incomplete( $post, $atts );
				}
			}

			if ( ! empty( $progress['prev'] ) && empty( $progress['prev']->completed ) && learndash_lesson_progression_enabled() ) {
				/** This filter is documented in includes/class-ld-cpt-instance.php */
				if ( ! apply_filters( 'learndash_previous_step_completed', false, $progress['prev']->ID, $user_id ) ) {
					return '';
				}
			}

			if ( ! learndash_lesson_topics_completed( $post->ID ) ) {
				/** This filter is documented in includes/class-ld-cpt-instance.php */
				if ( ! apply_filters( 'learndash_previous_step_completed', false, $post->ID, $user_id ) ) {
					return '';
				}
			}
		}

		if ( 'sfwd-topic' === $post->post_type ) {
			$progress = learndash_get_course_progress( $user_id, $post->ID );

			if ( ! empty( $progress['this']->completed ) ) {
				/** This filter is documented in includes/class-ld-cpt-instance.php */
				if ( ! apply_filters( 'learndash_previous_step_completed', false, $progress['this']->ID, $user_id ) ) {
					return learndash_show_mark_incomplete( $post, $atts );
				}
			}

			if ( ! empty( $progress['prev'] ) && empty( $progress['prev']->completed ) && learndash_lesson_progression_enabled() ) {
				/** This filter is documented in includes/class-ld-cpt-instance.php */
				if ( ! apply_filters( 'learndash_previous_step_completed', false, $progress['prev']->ID, $user_id ) ) {
					return '';
				}
			}

			if ( learndash_lesson_progression_enabled() ) {
				if ( LearnDash_Settings_Section::get_section_setting( 'LearnDash_Settings_Courses_Builder', 'shared_steps' ) == 'yes' ) {
					$lesson_id = learndash_course_get_single_parent_step( $course_id, $post->ID );
				} else {
					$lesson_id = learndash_get_setting( $post, 'lesson' );
				}
				$lesson = get_post( $lesson_id );

				if ( ! learndash_is_previous_complete( $lesson ) ) {
					/** This filter is documented in includes/class-ld-cpt-instance.php */
					if ( ! apply_filters( 'learndash_previous_step_completed', false, $lesson->ID, $user_id ) ) {
						return '';
					}
				}
			}
		}

		$previous_item_id = learndash_user_progress_get_previous_incomplete_step( $user_id, $course_id, $post->ID );
		if ( ! empty( $previous_item_id ) && ( $previous_item_id !== $post->ID ) ) {
			return '';
		}
	} else {
		$progress = learndash_get_course_progress( $user_id, $post->ID );

		if ( ! empty( $progress['this']->completed ) ) {
			return '';
		}
	}

	if ( learndash_lesson_hasassignments( $post ) ) { // cspell:disable-line.
		global $learndash_assignment_upload_message;

		$ret = SFWD_LMS::get_template(
			'learndash_lesson_assignment_upload_form.php',
			array(
				'course_step_post'                => $post,
				'user_id'                         => $user_id,
				'assignment_upload_error_message' => $learndash_assignment_upload_message,
			)
		);
		if ( ! is_null( $ret ) ) {
			return $ret;
		}

		if ( ( ! learndash_is_admin_user( $user_id ) ) || ( ! $bypass_course_limits_admin_users ) ) {
			$assignments = learndash_get_user_assignments( $post->ID, $user_id, $course_id );
			if ( empty( $assignments ) ) {
				return false;
			} else {
				foreach ( $assignments as $assignment ) {
					if ( ! learndash_is_assignment_approved_by_meta( $assignment->ID ) ) {
						return false;
					}
				}
			}
		}
	}

	$return          = '';
	$button_disabled = '';
	$time            = 0;
	$time_cookie_key = '';
	$time_value      = learndash_forced_lesson_time( $post );

	if ( ! empty( $time_value ) ) {
		$time = learndash_convert_lesson_time_time( $time_value );
	}

	if ( ( ! learndash_is_admin_user( $user_id ) ) || ( ! $bypass_course_limits_admin_users ) ) {
		if ( ! empty( $time ) ) {
			$time_cookie_key = learndash_forced_lesson_time_cookie_key( $post );
			if ( ! empty( $time_cookie_key ) ) {
				/**
				 * Note this is not a 100% check. We are only checking if the cookie
				 * key exists and is zero. But this cookie could have been set from
				 * external.
				 */
				if ( ( isset( $_COOKIE[ 'learndash_timer_cookie_' . $time_cookie_key ] ) ) && ( '0' === $_COOKIE[ 'learndash_timer_cookie_' . $time_cookie_key ] ) ) {
					$time = 0;
				}
			}

			if ( ! empty( $time ) ) {
				// Set the mark complete button disabled.
				$button_disabled = " disabled='disabled' ";

				wp_enqueue_script(
					'jquery-cookie',
					plugins_url( 'js/jquery.cookie' . learndash_min_asset() . '.js', WPPROQUIZ_FILE ),
					array( 'jquery' ),
					'1.4.0',
					true
				);
				global $learndash_assets_loaded;
				$learndash_assets_loaded['scripts']['jquery-cookie'] = __FUNCTION__;
			}
		}
	}

	/**
	 * Filters attributes for mark a course complete form.
	 *
	 * @since 3.0.0
	 *
	 * @param array   $attributes An array of form, button, and timer attributes to override id and class.
	 * @param WP_Post $post       WP_Post object being displayed.
	 */
	$atts = apply_filters( 'learndash_mark_complete_form_atts', $atts, $post );

	if ( isset( $atts['form']['action'] ) ) {
		$form_action = ' action="' . esc_attr( $atts['form']['action'] ) . '" ';
	} else {
		$form_action = '';
	}

	if ( isset( $atts['form']['id'] ) ) {
		$form_id = ' id="' . esc_attr( $atts['form']['id'] ) . '" ';
	} else {
		$form_id = '';
	}

	if ( isset( $atts['form']['class'] ) ) {
		$form_class = ' class="sfwd-mark-complete ' . esc_attr( $atts['form']['class'] ) . '" ';
	} else {
		$form_class = ' class="sfwd-mark-complete" ';
	}

	if ( isset( $atts['button']['id'] ) ) {
		$button_id = ' id="' . esc_attr( $atts['button']['id'] ) . '" ';
	} else {
		$button_id = '';
	}

	if ( isset( $atts['button']['class'] ) ) {
		$button_class = ' class="learndash_mark_complete_button ' . esc_attr( $atts['button']['class'] ) . '" ';
	} else {
		$button_class = ' class="learndash_mark_complete_button" ';
	}

	$button_label = learndash_course_steps_is_external( $post->ID ) && ! learndash_course_steps_is_external_attendance_required( $post->ID )
					? LearnDash_Custom_Label::get_label( LearnDash_Custom_Label::$button_skip_event )
					: LearnDash_Custom_Label::get_label( 'button_mark_complete' );

	/**
	 * Filters the HTML of the mark complete input button.
	 *
	 * @since 4.24.0
	 *
	 * @param string  $input_button_html The HTML of the mark complete input button.
	 * @param WP_Post $post              WP_Post object being displayed.
	 * @param string  $button_id         The HTML ID attribute of the button.
	 * @param string  $button_class      The HTML class attribute of the button.
	 * @param string  $button_disabled   The HTML disabled attribute of the button.
	 * @param string  $button_label      The label of the button.
	 *
	 * @return string Returns the HTML of the mark complete input button.
	 */
	$input_button_html = apply_filters(
		'learndash_mark_complete_input_button_html',
		'<input type="submit" ' . $button_id . ' value="' . $button_label . '" ' . $button_class . ' ' . $button_disabled . '/>',
		$post,
		$button_id,
		$button_class,
		$button_disabled,
		$button_label
	);

	$form_fields = '<input type="hidden" value="' . $post->ID . '" name="post" />
				<input type="hidden" value="' . learndash_get_course_id( $post->ID ) . '" name="course_id" />
				<input type="hidden" value="' . wp_create_nonce( 'sfwd_mark_complete_' . get_current_user_id() . '_' . $post->ID ) . '" name="sfwd_mark_complete" />
				' . $input_button_html . ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- It's the button HTML.

	/**
	 * Filters form fields to mark a course complete.
	 *
	 * @since 3.0.0
	 *
	 * @param string  $form_fields HTML output for course mark complete form fields.
	 * @param WP_Post $post       WP_Post object being displayed.
	 */
	$form_fields = apply_filters( 'learndash_mark_complete_form_fields', $form_fields, $post );

	$return .= '<form ' . $form_id . ' ' . $form_class . ' method="post" ' . $form_action . '>' . $form_fields . '</form>';

	if ( ( ! learndash_is_admin_user( $user_id ) ) || ( ! $bypass_course_limits_admin_users ) ) {
		if ( ! empty( $time ) ) {
			if ( isset( $atts['timer']['id'] ) ) {
				$timer_id = ' id="' . esc_attr( $atts['timer']['id'] ) . '" ';
			} else {
				$timer_id = '';
			}

			$timer_class = ' class="learndash_timer';
			if ( isset( $atts['timer']['class'] ) ) {
				$timer_class .= ' ' . esc_attr( $atts['timer']['class'] );
			}
			$timer_class .= '" ';

			$timer_html = '<span ' . $timer_id . ' ' . $timer_class . ' data-timer-seconds="' . $time . '" data-button="form.sfwd-mark-complete .learndash_mark_complete_button" data-cookie-key="' . $time_cookie_key . '"></span>';

			/**
			 * Filters the HTML of the timer.
			 *
			 * @since 4.24.0
			 *
			 * @param string  $timer_html      The HTML of the timer.
			 * @param WP_Post $post            WP_Post object being displayed.
			 * @param string  $timer_id        The HTML ID attribute of the timer.
			 * @param string  $timer_class     The HTML class attribute of the timer.
			 * @param int     $time            The time in seconds.
			 * @param string  $time_cookie_key The cookie key for the timer.
			 *
			 * @return string Returns the HTML of the timer.
			 */
			$timer_html = apply_filters(
				'learndash_mark_complete_timer_html',
				$timer_html,
				$post,
				$timer_id,
				$timer_class,
				$time,
				$time_cookie_key
			);

			$return .= $timer_html;
		}
	}

	/**
	 * Filters HTML output to mark a course completion.
	 *
	 * @since 2.1.0
	 *
	 * @param string  $return HTML output to mark course complete.
	 * @param WP_Post $post   WP_Post object being displayed.
	 */
	return apply_filters( 'learndash_mark_complete', $return, $post );
}

/**
 * Checks if the lesson topics are completed.
 *
 * @since 2.1.0
 *
 * @param int     $lesson_id            Lesson ID.
 * @param boolean $mark_lesson_complete Optional. Whether to mark the lesson complete. Default false.
 *
 * @return boolean Returns true if the lesson is completed otherwise false.
 */
function learndash_lesson_topics_completed( $lesson_id, $mark_lesson_complete = false ) {
	$topics = learndash_get_topic_list( $lesson_id );

	if ( empty( $topics[0]->ID ) ) {
		return true;
	}

	$progress = learndash_get_course_progress( null, $topics[0]->ID );

	if ( empty( $progress['posts'] ) || ! is_array( $progress['posts'] ) ) {
		return false;
	}

	foreach ( $progress['posts'] as $topic ) {
		if ( empty( $topic->completed ) ) {
			return false;
		}
	}

	if ( $mark_lesson_complete ) {
		$user_id = get_current_user_id();
		learndash_process_mark_complete( null, $lesson_id );
	}

	return true;
}

/**
 * Processes a request to mark a step/course complete.
 *
 * @since 2.1.0
 * @since 4.11.0 Removed the $post argument.
 * @since 4.21.5. Updated to use the new 'Automatic Progression' setting.
 *
 * @return void
 */
function learndash_mark_complete_process() {
	if (
		! isset( $_POST['sfwd_mark_complete'] )
		|| empty( $_POST['post'] )
	) {
		return;
	}

	$post_id = Cast::to_int(
		sanitize_key( $_POST['post'] ) // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Checked below.
	);

	$course_id = isset( $_POST['course_id'] )
		? sanitize_key( $_POST['course_id'] )
		: learndash_get_course_id( $post_id );
	$course_id = Cast::to_int( $course_id );

	$user_id = isset( $_POST['userid'] )
		? Cast::to_int( sanitize_key( $_POST['userid'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Checked below.
		: get_current_user_id();

	if (
		$post_id <= 0
		|| $course_id <= 0
		|| $user_id <= 0
	) {
		return;
	}

	if (
		! wp_verify_nonce(
			sanitize_key( $_POST['sfwd_mark_complete'] ),
			'sfwd_mark_complete_' . $user_id . '_' . $post_id
		)
	) {
		return;
	}

	// Mark the step as completed.

	$marked_completed = learndash_process_mark_complete( $user_id, $post_id, false, $course_id );

	if ( ! $marked_completed ) {
		return;
	}

	// Remove needed cookies.

	// Remove the lesson/topic video progress cookie once the step is completed.
	learndash_video_delete_cookie_for_step( $post_id, $course_id, $user_id );

	// Remove the lesson/topic timer cookie once the step is completed.
	$timer_cookie_key = learndash_forced_lesson_time_cookie_key( $post_id );

	if ( ! empty( $timer_cookie_key ) ) {
		if ( isset( $_COOKIE[ 'learndash_timer_cookie_' . $timer_cookie_key ] ) ) {
			unset( $_COOKIE[ 'learndash_timer_cookie_' . $timer_cookie_key ] );
		}

		// empty value and expiration one hour before.
		setcookie( 'learndash_timer_cookie_' . $timer_cookie_key, '', time() - 3600 );
	}

	$is_automatic_progression_enabled = LearnDash_Settings_Section::get_section_setting(
		'LearnDash_Settings_Courses_Management_Display',
		'course_automatic_progression'
	) === 'yes';

	/**
	 * Filters whether to redirect the user immediately after the step is completed.
	 *
	 * @since 4.21.4
	 * @since 4.21.5. Changed the default value to true if automatic progression is enabled.
	 *
	 * @param boolean $redirect_immediately Whether to redirect the user immediately. True if automatic progression is enabled.
	 *
	 * @return boolean Returns true if the user should be redirected immediately, otherwise false.
	 */
	if ( apply_filters( 'learndash_step_completed_redirect_immediately', $is_automatic_progression_enabled ) ) {
		// Redirect immediately to the step completion URL.

		learndash_safe_redirect(
			learndash_course_get_step_completion_url( $post_id, $course_id, $user_id )
		);
	} else {
		// Set a transient to allow extra processing after the step is completed.

		set_transient(
			"learndash_step_completed_{$post_id}_{$course_id}_{$user_id}",
			true,
			/**
			 * Filters the transient expiration time for the step completed transient.
			 *
			 * @since 4.21.4
			 *
			 * @param int     $expiration_time The transient expiration time. Default 10 minutes.
			 * @param int     $post_id         The post ID.
			 * @param int     $course_id       The course ID.
			 * @param int     $user_id         The user ID.
			 */
			apply_filters(
				'learndash_step_completed_transient_expiration_time',
				10 * MINUTE_IN_SECONDS,
				$post_id,
				$course_id,
				$user_id
			)
		);
	}
}

add_action( 'wp', 'learndash_mark_complete_process' );

/**
 * Returns the step (Lesson or Topic) completed transient data when a step is completed by a user through the `learndash_mark_complete_process` function.
 *
 * It's a workaround to allow us to do some extra processing after the step is completed.
 * This function only returns the data if the `learndash_step_completed` transient exists. By default, the transient expires in 10 minutes.
 *
 * Note this function does not work for quizzes, as they don't use the `learndash_mark_complete_process` function to complete the step.
 *
 * If you need the step completion data in another context, you can use the `learndash_course_get_step_completion_data` function.
 *
 * @since 4.21.3
 *
 * @param int $step_id   Step ID.
 * @param int $course_id Course ID.
 * @param int $user_id   User ID.
 *
 * @return array{
 *     is_course_completed: bool,
 *     is_course_completion_url: bool,
 *     next_step_id: int,
 *     next_step_url: string,
 * }|false Returns the transient data or false if the transient does not exist.
 */
function learndash_get_step_completed_transient_data( $step_id, $course_id, $user_id ) {
	$transient_key = "learndash_step_completed_{$step_id}_{$course_id}_{$user_id}";

	// If the transient does not exist, return false.

	if ( false === get_transient( $transient_key ) ) {
		return false;
	}

	// Return the step completion data.

	return learndash_course_get_step_completion_data( $step_id, $course_id, $user_id );
}

/**
 * Gets the course permalink.
 *
 * @since 2.1.0
 *
 * @param int|null $id Optional. The ID of the resource like course, topic, lesson, quiz, etc. Default null.
 *
 * @return string The course permalink.
 */
function learndash_get_course_url( $id = null ) {
	if ( empty( $id ) ) {
		$id = learndash_get_course_id();
	}

	return get_permalink( $id );
}

/**
 * Redirects the user after quiz completion when a user clicks on the "continue" button.
 * It redirects to the next step if the quiz is completed otherwise redirects back to the quiz page.
 *
 * @since 2.1.0
 *
 * @return void
 */
function learndash_quiz_redirect() {
	if (
		! isset( $_GET['quiz_redirect'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- No nonce here.
		|| ! isset( $_GET['quiz_id'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- No nonce here.
	) {
		return;
	}

	$user_id = get_current_user_id();
	$quiz_id = Cast::to_int( sanitize_key( $_GET['quiz_id'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- No nonce here.

	if (
		$user_id <= 0
		|| $quiz_id <= 0
	) {
		return;
	}

	$course_id = Cast::to_int( learndash_get_course_id( $quiz_id ) );

	if ( $course_id <= 0 ) {
		return;
	}

	$redirect_url = learndash_is_quiz_complete( $user_id, $quiz_id, $course_id )
		? learndash_course_get_step_completion_url( $quiz_id, $course_id, $user_id )
		: Cast::to_string( learndash_get_step_permalink( $quiz_id, $course_id ) ); // Back to the quiz page.

	learndash_safe_redirect( $redirect_url );
}

add_action( 'wp', 'learndash_quiz_redirect' );

/**
 * Checks whether a user can attempt the quiz again.
 *
 * @since 2.1.0
 *
 * @param int $user_id User ID.
 * @param int $quiz_id Quiz ID.
 *
 * @return boolean Returns true if the user can attempt quiz again otherwise false.
 */
function learndash_can_attempt_again( $user_id, $quiz_id ) {
	$quizmeta = get_post_meta( $quiz_id, '_sfwd-quiz', true );

	if ( isset( $quizmeta['sfwd-quiz_repeats'] ) ) {
		$repeats = $quizmeta['sfwd-quiz_repeats'];
	} else {
		$repeats = '';
	}

	/**
	 * Filters number of quiz attempts allowed for a user.
	 *
	 * @param int $repeats Number of quiz attempts allowed.
	 * @param int $user_id User ID.
	 * @param int $quiz_id Quiz ID.
	 */
	$repeats = apply_filters( 'learndash_allowed_repeats', $repeats, $user_id, $quiz_id );

	if ( '' == $repeats ) {
		return true;
	}

	$quiz_results = get_user_meta( $user_id, '_sfwd-quizzes', true );

	$count = 0;

	if ( ! empty( $quiz_results ) ) {
		foreach ( $quiz_results as $quiz ) {
			if ( $quiz['quiz'] == $quiz_id ) {
				++$count;
			}
		}
	}

	if ( $repeats > $count - 1 ) {
		return true;
	} else {
		return false;
	}
}

/**
 * Checks if the previous topic or lesson is complete.
 *
 * @since 3.4.0
 *
 * @param WP_Post $post The `WP_Post` object of lesson or topic.
 *
 * @return int Returns 1 if the previous lesson or topic is completed otherwise 0.
 */
function learndash_is_previous_complete( $post ) {
	$progress = learndash_get_course_progress( null, $post->ID );

	if ( empty( $progress ) ) {
		return 1;
	}

	if ( ! empty( $progress['prev'] ) && empty( $progress['prev']->completed ) ) {
		return 0;
	} else {
		return 1;
	}
}

/**
 * Returns the previous lesson/topic to be completed.
 *
 * @since 2.3.0
 *
 * @param WP_Post $post The `WP_Post` object.
 *
 * @return WP_Post|null The `WP_Post` object of lesson/topic to be completed.
 */
function learndash_get_previous( $post ) {
	$progress = learndash_get_course_progress( null, $post->ID );
	if ( ! empty( $progress['prev'] ) ) {
		return $progress['prev'];
	}

	return null;
}

/**
 * Updates the user meta with completion status for any resource.
 *
 * @since 2.1.0
 * @since 4.11.0 Added the $force parameter.
 *
 * @param int|null $user_id       Optional. User ID. Default null.
 * @param int|null $postid        Optional. The ID of the resource like course, lesson, topic, etc. Default null.
 * @param bool     $onlycalculate Optional. Whether to mark the resource as complete. Default false.
 * @param int      $course_id     Optional. Course ID. Default 0.
 * @param bool     $force         Optional. Whether to force the completion when should not be completed.
 *                                Default false.
 *
 * @return bool Returns true if the meta is updated successfully, otherwise false.
 */
function learndash_process_mark_complete( $user_id = null, $postid = null, $onlycalculate = false, $course_id = 0, $force = false ) {
	if ( ( defined( 'LEARNDASH_COURSE_FUNCTIONS_LEGACY' ) ) && ( true === LEARNDASH_COURSE_FUNCTIONS_LEGACY ) ) {
		return learndash_process_mark_complete_legacy( $user_id, $postid, $onlycalculate, $course_id );
	}

	if ( empty( $user_id ) ) {
		if ( is_user_logged_in() ) {
			$current_user = wp_get_current_user();
			$user_id      = $current_user->ID;
		} else {
			return false;
		}
	} else {
		$current_user = get_user_by( 'id', $user_id );
	}

	$post = get_post( $postid );
	if ( ! ( $post instanceof WP_Post ) ) {
		return false;
	}

	// Allow admin users to bypass the validation.
	$force = $force || learndash_can_user_bypass( $user_id, 'learndash_course_progression' );

	// Check if the user can complete the step.
	if (
		! $force &&
		! learndash_can_complete_step( $user_id, $post->ID, $course_id )
	) {
		return false;
	}

	/**
	 * In case of video progression, we need to check if the video is completed.
	 *
	 * This check should be done in the `learndash_can_complete_step` function, but this function is used in other places with a different purpose.
	 * We needed to add a new parameter to the function, making it weirder. So, putting the check here for now until we refactor this logic.
	 */

	$is_video_enabled = learndash_get_setting( $post->ID, 'lesson_video_enabled' ) === 'on'
						&& ! empty( learndash_get_setting( $post->ID, 'lesson_video_url' ) );

	if (
		! $force
		&& $is_video_enabled
		&& ! learndash_video_complete_for_step( $post->ID, $course_id, $user_id )
	) {
		return false;
	}

	if ( ! $onlycalculate ) {
		/**
		 * Filters whether to mark a process complete.
		 *
		 * @since 2.1.0
		 *
		 * @param boolean $mark_complete Whether to mark a process complete.
		 * @param WP_Post $post          WP_Post object to be checked.
		 * @param WP_User $current_user  Current logged in WP_User object.
		 */
		$process_completion = apply_filters( 'learndash_process_mark_complete', true, $post, $current_user );

		if ( ! $process_completion ) {
			return false;
		}
	}

	if ( empty( $course_id ) ) {
		$course_id = learndash_get_course_id( $postid );
	}

	$course_id = Cast::to_int( $course_id );

	if ( empty( $course_id ) ) {
		return false;
	}

	// Get the lesson ID.

	$lesson_id = 0;

	if ( LDLMS_Post_Types::get_post_type_slug( LDLMS_Post_Types::TOPIC ) === $post->post_type ) {
		$lesson_id = learndash_is_course_builder_enabled()
					? learndash_course_get_single_parent_step( $course_id, $post->ID )
					: learndash_get_setting( $post, 'lesson' );
	}

	// Forcing quiz completion.

	if ( LDLMS_Post_Types::get_post_type_slug( LDLMS_Post_Types::QUIZ ) === $post->post_type ) {
		learndash_process_user_course_progress_update(
			$user_id,
			[
				'quiz' => [
					$course_id => [
						$postid => 1,
					],
				],
			],
			$force
		);

		// We need to force a reload of the quiz progress object.

		$quiz_progress_object = LDLMS_Factory_User::quiz_progress( $user_id );

		if ( $quiz_progress_object instanceof LDLMS_Model_User_Quiz_Progress ) {
			$quiz_progress_object->set_progress_unloaded();
		}
	}

	$course_progress = learndash_user_get_course_progress( $user_id, $course_id, 'legacy' );
	if ( ( empty( $course_progress ) ) || ( ! is_array( $course_progress ) ) ) {
		$course_progress = array(
			'lessons' => array(),
			'topics'  => array(),
		);
	}

	if ( ( ! isset( $course_progress['lessons'] ) ) || ( empty( $course_progress['lessons'] ) ) ) {
		$course_progress['lessons'] = array();
	}

	if ( ( ! isset( $course_progress['topics'] ) ) || ( empty( $course_progress['topics'] ) ) ) {
		$course_progress['topics'] = array();
	}

	if ( 'sfwd-topic' === $post->post_type && empty( $course_progress['topics'][ $lesson_id ] ) ) {
		$course_progress['topics'][ $lesson_id ] = array();
	}

	$lesson_completed = false;
	$topic_completed  = false;

	if ( ! $onlycalculate && 'sfwd-lessons' === $post->post_type && empty( $course_progress['lessons'][ $postid ] ) ) {
		$course_progress['lessons'][ $postid ] = 1;
		$lesson_completed                      = true;
	}

	if ( ! $onlycalculate && 'sfwd-topic' === $post->post_type && empty( $course_progress['topics'][ $lesson_id ][ $postid ] ) ) {
		$course_progress['topics'][ $lesson_id ][ $postid ] = 1;
		$topic_completed                                    = true;
	}

	$completed_old = isset( $course_progress['completed'] ) ? $course_progress['completed'] : 0;

	$completed = learndash_course_get_completed_steps( $user_id, $course_id, $course_progress );

	$course_progress['completed'] = $completed;
	$course_progress['total']     = learndash_get_course_steps_count( $course_id );

	/**
	 * Track the last post_id (Lesson, Topic, Quiz) seen by user.
	 *
	 * @since 2.1.0
	 */
	if ( in_array( $post->post_type, learndash_get_post_types( 'course_steps' ), true ) ) {
		$course_progress['last_id'] = $post->ID;
	}

	$course_completed_time = time();

	$course_earliest_completed_time = learndash_activity_course_get_earliest_started( $current_user->ID, $course_id, $course_completed_time );

	// If course is completed.
	if ( ( $course_progress['completed'] >= $completed_old ) && ( $course_progress['completed'] >= $course_progress['total'] ) ) {
		/**
		 * Fires before the course is marked completed.
		 *
		 * @since 2.1.0
		 *
		 * @param array $course_data An array of course complete data.
		 */
		do_action(
			'learndash_before_course_completed',
			array(
				'user'           => $current_user,
				'course'         => get_post( $course_id ),
				'progress'       => array( $course_id => $course_progress ),
				'completed_time' => $course_completed_time,
			)
		);
		add_user_meta( $current_user->ID, 'course_completed_' . $course_id, $course_completed_time, true );
	} else {
		delete_user_meta( $current_user->ID, 'course_completed_' . $course_id );
	}

	learndash_user_set_course_progress( $user_id, $course_id, $course_progress );

	if ( ! empty( $topic_completed ) ) {
		$course_activity = learndash_activity_start_course( $current_user->ID, $course_id, $course_earliest_completed_time );
		if ( $course_activity ) {
			learndash_activity_update_meta_set(
				$course_activity->activity_id,
				array(
					'steps_completed' => learndash_course_get_completed_steps( $current_user->ID, $course_id ),
					'steps_last_id'   => $post->ID,
				)
			);
		}
		learndash_activity_start_lesson( $current_user->ID, $course_id, $lesson_id, $course_completed_time );
		learndash_activity_complete_topic( $current_user->ID, $course_id, $post->ID, $course_completed_time );
	}

	if ( ! empty( $lesson_completed ) ) {
		$course_activity = learndash_activity_start_course( $current_user->ID, $course_id, $course_earliest_completed_time );
		if ( $course_activity ) {
			learndash_activity_update_meta_set(
				$course_activity->activity_id,
				array(
					'steps_completed' => learndash_course_get_completed_steps( $current_user->ID, $course_id ),
					'steps_last_id'   => $post->ID,
				)
			);
		}

		learndash_activity_complete_lesson( $current_user->ID, $course_id, $post->ID, $course_completed_time );
	}

	$course_args = array(
		'course_id'     => $course_id,
		'user_id'       => $current_user->ID,
		'post_id'       => $course_id,
		'activity_type' => 'course',
	);

	$course_activity = learndash_get_user_activity( $course_args );
	if ( ! empty( $course_activity ) ) {
		$course_activity = json_decode( wp_json_encode( $course_activity ), true );
	} else {
		$course_activity = $course_args;
	}

	if ( in_array( $post->post_type, learndash_get_post_types( 'course_steps' ), true ) ) {
		$course_activity['activity_meta'] = array(
			'steps_last_id' => $post->ID,
		);
	}

	$do_course_complete_action = false;
	if ( $course_progress['completed'] >= $completed_old && $course_progress['total'] == $course_progress['completed'] ) {
		if ( ! $course_activity['activity_status'] ) {
			$course_activity['activity_status']    = true;
			$course_activity['activity_completed'] = $course_completed_time;
			$course_activity['activity_updated']   = $course_completed_time;

			if ( empty( $course_activity['activity_started'] ) ) {
				$course_activity['activity_started'] = $course_earliest_completed_time;
			}

			$do_course_complete_action = true;
		}
	} else {
		$course_activity['activity_completed'] = 0;
		$course_activity['activity_status']    = false;
		$course_activity['activity_updated']   = $course_completed_time;
	}
	learndash_update_user_activity( $course_activity );

	$return = false;
	if ( ! empty( $lesson_completed ) ) {
		/**
		 * Fires after the lesson is marked completed.
		 *
		 * @since 2.1.0
		 *
		 * @param array $lesson_data An array of lesson complete data.
		 */
		do_action(
			'learndash_lesson_completed',
			array(
				'user'     => $current_user,
				'course'   => get_post( $course_id ),
				'lesson'   => $post,
				'progress' => $course_progress,
			)
		);

		$return = true;
	}

	if ( ! empty( $topic_completed ) ) {
		/**
		 * Fires after the topic is marked completed.
		 *
		 * @since 2.1.0
		 *
		 * @param array $topic_data An array of topic complete data.
		 */
		do_action(
			'learndash_topic_completed',
			array(
				'user'     => $current_user,
				'course'   => get_post( $course_id ),
				'lesson'   => get_post( $lesson_id ),
				'topic'    => $post,
				'progress' => $course_progress,
			)
		);

		$return = true;
	}

	if ( true == $do_course_complete_action ) {
		/**
		 * Fires after the course is marked completed.
		 *
		 * @since 2.1.0
		 *
		 * @param array $course_data An array of course complete data.
		 */
		do_action(
			'learndash_course_completed',
			array(
				'user'             => $current_user,
				'course'           => get_post( $course_id ),
				'progress'         => array( $course_id => $course_progress ),
				'course_completed' => $course_completed_time,
			)
		);

		set_transient(
			'learndash_course_completed_' . $course_id . '_' . $user_id,
			$course_completed_time,
			HOUR_IN_SECONDS // We only need this for the redirect.
		);

		$return = true;
	}

	/**
	 * Try to complete the parent step if all the child steps are completed.
	 *
	 * The learndash_process_mark_complete validates special cases, for example, if the parent step requires a video to be completed.
	 */

	$parent_step_id = learndash_course_get_single_parent_step( $course_id, $post->ID );
	if (
		$parent_step_id > 0
		&& empty( learndash_user_progression_get_incomplete_child_steps( $user_id, $course_id, $parent_step_id ) )
		&& ! learndash_course_steps_is_external( $parent_step_id ) // Don't auto-complete external steps.
	) {
		learndash_process_mark_complete( $user_id, $parent_step_id, false, $course_id );
	}

	/**
	 * LEARNDASH-5883 - Always return true if we've made it this far.
	 */
	$return = true;

	return $return;
}

/**
 * Checks whether a quiz is complete for a user.
 *
 * @since 2.1.0
 *
 * @param int|null $user_id   Optional. User ID. Default null.
 * @param int      $quiz_id   Quiz ID.
 * @param int      $course_id Optional. Course ID. Default 0.
 *
 * @return boolean Returns true if the quiz is completed for a user otherwise false.
 */
function learndash_is_quiz_complete( $user_id = null, $quiz_id = 0, $course_id = 0 ) {
	return ! learndash_is_quiz_notcomplete( $user_id, array( $quiz_id => 1 ), false, $course_id );
}

/**
 * Checks whether a quiz is not completed for a user.
 *
 * Checks against quizzes in user meta and passing percentage of the quiz itself
 *
 * @since 2.1.0
 * @since 2.3.1 Added $return_incomplete_quiz_ids parameter.
 *
 * @param int|null   $user_id                    Optional. User ID for quizzes. Default null.
 * @param array|null $quizzes                    Optional. Quiz ID to search user quizzes. Default null.
 * @param boolean    $return_incomplete_quiz_ids Optional. If true will return the array of incomplete quizzes. Default is false. Default false.
 * @param int        $course_id                  Optional. The Course ID to match. If -1 is passed then course match is not performed. Default 0.
 *
 * @return bool     Returns true if the quiz(es) NOT complete otherwise false.
 */
function learndash_is_quiz_notcomplete( $user_id = null, $quizzes = null, $return_incomplete_quiz_ids = false, $course_id = 0 ) {
	$user_id   = (int) $user_id;
	$course_id = (int) $course_id;

	if ( empty( $user_id ) ) {
		$user_id = get_current_user_id();
		if ( empty( $user_id ) ) {
			return true;
		}
	}

	if ( ( is_null( $quizzes ) ) || ( ! is_array( $quizzes ) ) || ( empty( $quizzes ) ) ) {
		return;
	}

	$quiz_results = get_user_meta( $user_id, '_sfwd-quizzes', true );

	if ( ( ! empty( $quiz_results ) ) && ( is_array( $quiz_results ) ) ) {
		foreach ( $quiz_results as $quiz ) {
			if ( ( ! isset( $quiz['quiz'] ) ) || ( empty( $quiz['quiz'] ) ) ) {
				continue;
			}

			if ( ! isset( $quizzes[ $quiz['quiz'] ] ) ) {
				continue;
			}

			// Because we don't want to alter the original $course_id function param.
			$quiz_course_id = $course_id;

			if ( ( -1 !== $quiz_course_id ) && ( empty( $quiz_course_id ) ) ) {
				$quiz_course_id = (int) learndash_get_course_id( intval( $quiz['quiz'] ) );
			}

			if ( ! isset( $quiz['course'] ) ) {
				if ( ! learndash_is_course_shared_steps_enabled() ) {
					/**
					 * If shared steps is not enabled we can determine the related
					 * course from the quiz post meta.
					 */
					$quiz['course'] = (int) learndash_get_setting( $quiz['quiz'], 'course' );
				} else {
					$quiz['course'] = learndash_get_course_id( $quiz['quiz'] );
				}
			}
			$quiz['course'] = intval( $quiz['course'] );

			$pass = false;

			if ( ( -1 === $course_id ) || ( $course_id === $quiz['course'] ) ) {
				if ( isset( $quiz['pass'] ) ) {
					$pass = ( 1 == $quiz['pass'] ) ? 1 : 0;
				} else {
					$passingpercentage = (int) learndash_get_setting( $quiz['quiz'], 'passingpercentage' );
					$pass              = ( ! empty( $quiz['count'] ) && $quiz['score'] * 100 / $quiz['count'] >= $passingpercentage ) ? 1 : 0;
				}
			}

			if ( $pass ) {
				unset( $quizzes[ $quiz['quiz'] ] );
			}

			// Break if empty. No need to loop through the rest of user quiz progress.
			if ( empty( $quizzes ) ) {
				break;
			}
		}
	}

	if ( empty( $quizzes ) ) {
		return 0;
	} elseif ( true == $return_incomplete_quiz_ids ) {
			return $quizzes;
	} else {
		return 1;
	}
}

/**
 * Gets the user's current course progress.
 *
 * @since 2.1.0
 *
 * @param int|null $user_id   Optional. User ID. Default null.
 * @param int|null $postid    Optional. Post ID. Default null.
 * @param int|null $course_id Optional. Course ID. Default null.
 *
 * @return array An array of user's current course progress.
 */
function learndash_get_course_progress( $user_id = null, $postid = null, $course_id = null ) {
	if ( ( defined( 'LEARNDASH_COURSE_FUNCTIONS_LEGACY' ) ) && ( true === LEARNDASH_COURSE_FUNCTIONS_LEGACY ) ) {
		return learndash_get_course_progress_legacy( $user_id, $postid, $course_id );
	}

	$user_id   = absint( $user_id );
	$course_id = absint( $course_id );

	if ( empty( $user_id ) ) {
		$current_user = wp_get_current_user();

		if ( empty( $current_user->ID ) ) {
			return null;
		}

		$user_id = $current_user->ID;
	}

	$posts = array();

	if ( empty( $course_id ) ) {
		$course_id = learndash_get_course_id( $postid );
	}

	$this_post = get_post( $postid );
	if ( ( ! $this_post ) || ( ! is_a( $this_post, 'WP_Post' ) ) ) {
		return null;
	}

	$ld_course_object = LDLMS_Factory_Post::course( intval( $course_id ) );
	if ( ! $ld_course_object ) {
		return null;
	}

	$course_progress = learndash_user_get_course_progress( $user_id, $course_id, 'legacy' );

	if ( empty( $course_progress ) ) {
		$course_progress = array();
	}

	if ( 'sfwd-lessons' === $this_post->post_type ) {
		$posts = $ld_course_object->get_lessons( array( 'nopaging' => true ) );

		if ( empty( $course_progress ) || empty( $course_progress['lessons'] ) ) {
			$completed_posts = array();
		} else {
			$completed_posts = $course_progress['lessons'];
		}
	} elseif ( 'sfwd-topic' === $this_post->post_type ) {
		$lesson_id = learndash_course_get_single_parent_step( $course_id, $this_post->ID );
		if ( ! $lesson_id ) {
			return null;
		}

		$posts = $ld_course_object->get_topics( $lesson_id, array( 'nopaging' => true ) );

		if ( empty( $course_progress ) || empty( $course_progress['topics'][ $lesson_id ] ) ) {
			$completed_posts = array();
		} else {
			$completed_posts = $course_progress['topics'][ $lesson_id ];
		}
	}

	$temp   = '';
	$prev_p = '';
	$next_p = '';
	$this_p = '';

	if ( ! empty( $posts ) ) {
		foreach ( $posts as $k => $post ) {
			if ( $post instanceof WP_Post ) {
				if ( ! empty( $completed_posts[ $post->ID ] ) ) {
					$posts[ $k ]->completed = 1;
				} else {
					$posts[ $k ]->completed = 0;
				}

				if ( $post->ID == $postid ) {
					$this_p = $post;
					$prev_p = $temp;
				}

				if ( ! empty( $temp->ID ) && $temp->ID == $postid ) {
					$next_p = $post;
				}

				$temp = $post;
			}
		}
	} else {
		$posts = array();
	}

	return array(
		'posts' => $posts,
		'this'  => $this_p,
		'prev'  => $prev_p,
		'next'  => $next_p,
	);
}

/**
 * Checks if a lesson is complete.
 *
 * @since 2.1.0
 *
 * @param int|null $user_id   Optional. User ID. Defaults to the current logged-in user. Default null.
 * @param int      $lesson_id Lesson ID.
 * @param int      $course_id Optional. Course ID. Default 0.
 *
 * @return boolean Return true if the lesson is complete otherwise false.
 */
function learndash_is_lesson_complete( $user_id = null, $lesson_id = 0, $course_id = 0 ) {
	return ! learndash_is_lesson_notcomplete( $user_id, array( $lesson_id => 1 ), $course_id );
}

/**
 * Checks if a lesson is not complete.
 *
 * @since 2.1.0
 *
 * @param int|null $user_id   Optional. User ID. Defaults to the current logged-in user. Default null.
 * @param array    $lessons   An array of lesson IDs.
 * @param int      $course_id Optional. Course ID. Default 0.
 *
 * @return boolean Returns true if the lesson is not complete otherwise false.
 */
function learndash_is_lesson_notcomplete( $user_id = null, $lessons = array(), $course_id = 0 ) {
	if ( ( defined( 'LEARNDASH_COURSE_FUNCTIONS_LEGACY' ) ) && ( true === LEARNDASH_COURSE_FUNCTIONS_LEGACY ) ) {
		return learndash_is_lesson_notcomplete_legacy( $user_id, $lessons, $course_id );
	}

	if ( empty( $user_id ) ) {
		$current_user = wp_get_current_user();
		$user_id      = $current_user->ID;
	}

	$course_id = absint( $course_id );

	if ( ! empty( $lessons ) ) {
		foreach ( $lessons as $lesson => $v ) {
			if ( empty( $course_id ) ) {
				$course_id = learndash_get_course_id( $lesson );
			}

			if ( ! empty( $course_id ) ) {
				$course_progress = learndash_user_get_course_progress( $user_id, $course_id, 'legacy' );

				if ( ( isset( $course_progress['lessons'][ $lesson ] ) ) && ( ! empty( $course_progress['lessons'][ $lesson ] ) ) ) {
					unset( $lessons[ $lesson ] );
				}
			}
		}
	}

	if ( empty( $lessons ) ) {
		return 0;
	} else {
		return 1;
	}
}

/**
 * Checks if a topic is complete.
 *
 * @since 2.3.1
 * @since 3.2.0 Added $course_id
 *
 * @param int $user_id  Optional. User ID. Defaults to the current logged-in user. Default null.
 * @param int $topic_id Topic ID.
 * @param int $course_id Course ID.
 *
 * @return boolean Returns true if the topic is completed otherwise false.
 */
function learndash_is_topic_complete( $user_id = null, $topic_id = 0, $course_id = 0 ) {
	return ! learndash_is_topic_notcomplete( $user_id, array( $topic_id => 1 ), $course_id );
}

/**
 * Checks if a topic is not complete.
 *
 * @since 2.3.1
 * @since 3.2.0 Added $course_id
 *
 * @param int|null $user_id Optional. User ID. Defaults to the current logged-in user. Default null.
 * @param array    $topics  An array of topic IDs.
 * @param int      $course_id Course ID.
 *
 * @return boolean Returns true if the topic is not completed otherwise false.
 */
function learndash_is_topic_notcomplete( $user_id = null, $topics = array(), $course_id = 0 ) {
	if ( ( defined( 'LEARNDASH_COURSE_FUNCTIONS_LEGACY' ) ) && ( true === LEARNDASH_COURSE_FUNCTIONS_LEGACY ) ) {
		return learndash_is_topic_notcomplete_legacy( $user_id, $topics, $course_id );
	}

	if ( empty( $user_id ) ) {
		$current_user = wp_get_current_user();
		$user_id      = $current_user->ID;
	}
	$user_id   = absint( $user_id );
	$course_id = absint( $course_id );

	if ( ! empty( $topics ) ) {
		foreach ( $topics as $topic_id => $v ) {
			if ( empty( $course_id ) ) {
				$course_id = learndash_get_course_id( $topic_id );
			}

			if ( ! empty( $course_id ) ) {
				$course_progress = learndash_user_get_course_progress( $user_id, $course_id, 'legacy' );

				$lesson_id = learndash_course_get_single_parent_step( $course_id, $topic_id );
				if ( ! empty( $lesson_id ) ) {
					if ( ( isset( $course_progress['topics'] ) )
						&& ( ! empty( $course_progress['topics'] ) )
						&& ( isset( $course_progress['topics'][ $lesson_id ][ $topic_id ] ) )
						&& ( ! empty( $course_progress['topics'][ $lesson_id ][ $topic_id ] ) ) ) {
						unset( $topics[ $topic_id ] );
					}
				}
			}
		}
	}

	if ( empty( $topics ) ) {
		return 0;
	} else {
		return 1;
	}
}

/**
 * Outputs the current status of the course.
 *
 * @since 2.1.0
 * @since 2.5.8 Added $return_slug parameter.
 *
 * @param int      $course_id   Course ID to get status.
 * @param int|null $user_id     Optional. User ID. Default null.
 * @param boolean  $return_slug Optional. If false will return translatable string otherwise the status slug. Default false.
 *
 * @return string The current status of the course.
 */
function learndash_course_status( $course_id, $user_id = null, $return_slug = false ) {
	global $learndash_course_statuses;

	if ( ( defined( 'LEARNDASH_COURSE_FUNCTIONS_LEGACY' ) ) && ( true === LEARNDASH_COURSE_FUNCTIONS_LEGACY ) ) {
		return learndash_course_status_legacy( $course_id, $user_id, $return_slug );
	}
	$course_status_slug = '';

	$course_id = absint( $course_id );
	$user_id   = absint( $user_id );

	if ( empty( $user_id ) ) {
		if ( is_user_logged_in() ) {
			$user_id = get_current_user_id();
		}
	}

	if ( ( empty( $course_id ) ) || ( empty( $user_id ) ) ) {
		return $course_status_slug;
	}

	$course_progress = learndash_user_get_course_progress( $user_id, $course_id, 'legacy' );
	if ( isset( $course_progress['status'] ) ) {
		$course_status_slug = $course_progress['status'];
	}

	/**
	 * Sort of a kludge for now. If the course is not complete and the 'total' steps is
	 * not zero. Call the the mark complete function.
	 *
	 * This logic should be within User Course Progression class.
	 */
	if ( in_array( $course_status_slug, array( 'in_progress', 'not_started' ), true ) ) {
		if ( ( $course_progress['total'] > 0 ) && ( $course_progress['completed'] === $course_progress['total'] ) ) {
			if ( learndash_process_mark_complete( $user_id, $course_id ) ) {
				// If the Mark Complete was success we call to get the progress again.
				$course_progress = learndash_user_get_course_progress( $user_id, $course_id, 'legacy' );
				if ( isset( $course_progress['status'] ) ) {
					$course_status_slug = $course_progress['status'];
				}
			}
		}
	}

	if ( true === $return_slug ) {
		return $course_status_slug;
	} elseif ( isset( $learndash_course_statuses[ $course_status_slug ] ) ) {
			/**
			 * Filters the current status of the course.
			 *
			 * @param string $course_status_str The translatable current course status string.
			 * @param int    $course_id         Course ID.
			 * @param int    $user_id           User ID.
			 * @param array  $course_progress   Current course progress.
			 */
			return apply_filters(
				'learndash_course_status',
				learndash_course_status_label( $course_status_slug ),
				$course_id,
				$user_id,
				isset( $course_progress ) ? $course_progress : array()
			);
	}

	return $course_status_slug;
}

/**
 * Gets the course status index from the course status label.
 *
 * In various places with LD the course status is expressed as a string as in 'Not Started', 'In Progress' or 'Complete'.
 * the problem with using this string is it will be translated depending on the locale(). This means comparative logic can
 * possible fails.
 * The purpose of this function is to help use an internal key to keep track of the course status value.
 *
 * @global array $learndash_course_statuses An array of course status.
 *
 * @since 2.3.0
 *
 * @param string $course_status_label Optional. The current translatable text for course status. Default empty.
 *
 * @return string The index/key of the course status string if found in the `$learndash_course_statuses` global array.
 */
function learndash_course_status_idx( $course_status_label = '' ) {
	global $learndash_course_statuses;

	return array_search( $course_status_label, $learndash_course_statuses, true );
}

/**
 * Get the Course Status label from slug.
 *
 * @since 3.4.0
 *
 * @param string $course_status_slug Course Status slug.
 *
 * @return string|null.
 */
function learndash_course_status_label( $course_status_slug = '' ) {
	global $learndash_course_statuses;

	if ( isset( $learndash_course_statuses[ $course_status_slug ] ) ) {
		return $learndash_course_statuses[ $course_status_slug ];
	}
}

/**
 * Checks if the quiz is accessible to the user.
 *
 * @since 3.4.0
 *
 * @param int|null     $user_id $user_id  Optional. The ID of User to check.  Defaults to the current logged-in user. Default null.
 * @param WP_Post|null $post              Optional. The `WP_Post` quiz object. Default null.
 * @param boolean      $return_incomplete Optional. Whether to return last incomplete step. Default false.
 * @param int          $course_id         Optional. Course ID. Default 0.
 *
 * @return int|WP_Post|void Returns 1 if the quiz is accessible by user otherwise 0. If the `$return_incomplete`
 *                          parameter is set to true it may return `WP_Post` object for incomplete step.
 */
function learndash_is_quiz_accessable( $user_id = null, $post = null, $return_incomplete = false, $course_id = 0 ) {
	// Allow using the legacy function in case of issues with new logic.
	if ( ( defined( 'LEARNDASH_IS_QUIZ_ACCESSABLE_LEGACY' ) && ( LEARNDASH_IS_QUIZ_ACCESSABLE_LEGACY === true ) ) ) {
		return learndash_is_quiz_accessable_legacy( $user_id, $post, $course_id );
	}

	if ( empty( $user_id ) ) {
		$current_user = wp_get_current_user();

		if ( empty( $current_user->ID ) ) {
			return 1;
		}

		$user_id = $current_user->ID;
	}

	if ( ( empty( $post ) ) || ( ! is_a( $post, 'WP_Post' ) ) ) {
		return;
	}

	if ( empty( $course_id ) ) {
		$course_id = learndash_get_course_id( $post );
	}
	$course_id = absint( $course_id );

	// If we have a Quiz but the Quiz is not part of a course then return 1 for valid.
	if ( empty( $course_id ) ) {
		return 1;
	}

	$bypass_course_limits_admin_users = learndash_can_user_bypass( $user_id, 'learndash_quiz_accessable', $post, $course_id );
	if ( true === $bypass_course_limits_admin_users ) {
		return 1;
	}

	$course_progress_co = learndash_user_get_course_progress( $user_id, $course_id, 'co' );

	if ( learndash_is_course_builder_enabled() ) {
		$quiz_parent_id = learndash_course_get_single_parent_step( $course_id, $post->ID );
	} else {
		$quiz_parent_id = learndash_get_setting( $post, 'lesson' );
	}
	$quiz_parent_id = absint( $quiz_parent_id );
	if ( ! empty( $quiz_parent_id ) ) {
		$quiz_parent_post = get_post( $quiz_parent_id );
		if ( is_a( $quiz_parent_post, 'WP_Post' ) ) {
			if ( learndash_get_post_type_slug( 'topic' ) === $quiz_parent_post->post_type ) {
				$quiz_parent_topic_post  = $quiz_parent_post;
				$quiz_parent_lesson_id   = learndash_get_setting( $quiz_parent_topic_post, 'lesson' );
				$quiz_parent_lesson_post = get_post( $quiz_parent_lesson_id );

				$parent_topic_quizzes = learndash_get_lesson_quiz_list( $quiz_parent_topic_post, $user_id, $course_id );
				if ( ! empty( $parent_topic_quizzes ) ) {
					// loop until we get to the first status == 'notcompleted'.
					foreach ( $parent_topic_quizzes as $quiz ) {
						if ( $quiz['post']->ID === $post->ID ) {
							break;
						} elseif ( 'completed' !== $quiz['status'] ) {
							if ( true === $return_incomplete ) {
								return $quiz['post'];
							} else {
								return 0;
							}
						}
					}
				}

				$lesson_topics_progress = learndash_get_course_progress( $user_id, $quiz_parent_topic_post->ID );
				if ( ( isset( $lesson_topics_progress['posts'] ) ) && ( ! empty( $lesson_topics_progress['posts'] ) ) ) {
					foreach ( $lesson_topics_progress['posts'] as $topic ) {
						if ( $topic->ID === $quiz_parent_topic_post->ID ) {
							if ( ! empty( $topic->completed ) ) {
								return 1;
							}
							break;
						}
						if ( empty( $topic->completed ) ) {
							if ( true === $return_incomplete ) {
								return $topic;
							} else {
								return 0;
							}
							break;
						}
					}
				}

				if ( 'on' === learndash_get_setting( $quiz_parent_topic_post->ID, 'lesson_video_enabled' ) ) {
					if ( ! empty( learndash_get_setting( $quiz_parent_topic_post->ID, 'lesson_video_url' ) ) ) {
						if ( 'BEFORE' === learndash_get_setting( $quiz_parent_topic_post->ID, 'lesson_video_shown' ) ) {
							if ( ! learndash_video_complete_for_step( $quiz_parent_topic_post->ID, $course_id, $user_id ) ) {
								if ( true === $return_incomplete ) {
									return $quiz_parent_topic_post;
								} else {
									return 0;
								}
							}
						}
					}
				}

				$lesson_progress = learndash_get_course_progress( $user_id, $quiz_parent_lesson_post->ID, $course_id );
				if ( ( isset( $lesson_progress['posts'] ) ) && ( ! empty( $lesson_progress['posts'] ) ) ) {
					foreach ( $lesson_progress['posts'] as $lesson ) {
						if ( $lesson->ID === $quiz_parent_lesson_post->ID ) {
							break;
						}
						if ( empty( $lesson->completed ) ) {
							if ( true === $return_incomplete ) {
								return $lesson;
							} else {
								return 0;
							}
							break;
						}
					}
				}

				return 1;
			} elseif ( learndash_get_post_type_slug( 'lesson' ) === $quiz_parent_post->post_type ) {
				$quiz_parent_lesson_post = $quiz_parent_post;
				$sibling_completed_steps = 0;

				$lesson_topics = learndash_get_topic_list( $quiz_parent_lesson_post->ID );
				if ( ! empty( $lesson_topics ) ) {
					$lesson_topics_progress = learndash_get_course_progress( $user_id, $lesson_topics[0]->ID );
					if ( ( isset( $lesson_topics_progress['posts'] ) ) && ( ! empty( $lesson_topics_progress['posts'] ) ) ) {
						foreach ( $lesson_topics_progress['posts'] as $topic ) {
							if ( empty( $topic->completed ) ) {
								if ( true === $return_incomplete ) {
									return $topic;
								} else {
									return 0;
								}
								break;
							} else {
								++$sibling_completed_steps;
							}
						}
					}
				}

				$quizzes = learndash_get_lesson_quiz_list( $quiz_parent_lesson_post, $user_id, $course_id );
				if ( ! empty( $quizzes ) ) {
					// loop until we get to the first status == 'notcompleted'.
					foreach ( $quizzes as $quiz ) {
						if ( $quiz['post']->ID === $post->ID ) {
							break;
						}

						if ( 'completed' !== $quiz['status'] ) {
							if ( true === $return_incomplete ) {
								return $quiz['post'];
							} else {
								return 0;
							}
						} else {
							++$sibling_completed_steps;
						}
					}
				}

				$lesson_progress = learndash_get_course_progress( $user_id, $quiz_parent_lesson_post->ID );
				if ( ( isset( $lesson_progress['posts'] ) ) && ( ! empty( $lesson_progress['posts'] ) ) ) {
					foreach ( $lesson_progress['posts'] as $lesson ) {
						if ( $lesson->ID === $quiz_parent_lesson_post->ID ) {
							if ( ! empty( $lesson->completed ) ) {
								return 1;
							}
							break;
						}
						if ( empty( $lesson->completed ) ) {
							if ( true === $return_incomplete ) {
								return $lesson;
							} else {
								return 0;
							}
							break;
						}
					}
				}

				if ( empty( $sibling_completed_steps ) ) {
					if ( 'on' === learndash_get_setting( $quiz_parent_lesson_post->ID, 'lesson_video_enabled' ) ) {
						if ( ! empty( learndash_get_setting( $quiz_parent_lesson_post->ID, 'lesson_video_url' ) ) ) {
							if ( 'BEFORE' === learndash_get_setting( $quiz_parent_lesson_post->ID, 'lesson_video_shown' ) ) {
								if ( ! learndash_video_complete_for_step( $quiz_parent_lesson_post->ID, $course_id, $user_id ) ) {
									if ( true === $return_incomplete ) {
										return $quiz_parent_lesson_post;
									} else {
										return 0;
									}
								}
							}
						}
					}
				}

				return 1;
			}
		}
	} else {
		// First we check if all course lessons are completed.
		$lessons = learndash_get_course_lessons_list( $course_id, $user_id, array( 'num' => 0 ) );
		if ( ! empty( $lessons ) ) {
			foreach ( $lessons as $lesson ) {
				if ( 'completed' !== $lesson['status'] ) {
					if ( true === $return_incomplete ) {
						return $lesson['post'];
					} else {
						return 0;
					}
				}
			}
		}

		// Next we check if other global quizzes are completed.
		$quizzes = learndash_get_global_quiz_list( $course_id );
		if ( ! empty( $quizzes ) ) {
			// loop until we get to the first status == 'notcompleted'.
			foreach ( $quizzes as $quiz ) {
				if ( $quiz->ID === $post->ID ) {
					return 1;
				} elseif ( ! learndash_is_quiz_complete( $user_id, $quiz->ID, $course_id ) ) {
					if ( true === $return_incomplete ) {
						return $quiz;
					} else {
						return 0;
					}
				}
			}
		}
	}
	return 0;
}

/**
 * Checks if all quizzes for a course are complete for the user.
 *
 * @since 3.4.0
 *
 * @param int|null $user_id Optional. User ID. Default null.
 * @param int|null $id      Optional. The ID of the resource. Default null.
 *
 * @return boolean
 */
function learndash_is_all_global_quizzes_complete( $user_id = null, $id = null ) {
	$quizzes = learndash_get_global_quiz_list( $id );
	$return  = true;

	if ( ! empty( $quizzes ) ) {
		foreach ( $quizzes as $quiz ) {
			if ( learndash_is_quiz_notcomplete( $user_id, array( $quiz->ID => 1 ), false, $id ) ) {
				$return = false;
			}
		}
	}

	return $return;
}

/**
 * Check if the user can complete the current step.
 *
 * This is mostly used before auto-completing a parent step like a quiz
 * parent lesson.
 *
 * @since 3.2.3
 * @since 4.0.3 Added `$ignore_lesson_timer` parameter.
 *
 * @param int  $user_id             User ID.
 * @param int  $step_id             Course Step ID.
 * @param int  $course_id           Course ID.
 * @param bool $ignore_lesson_timer Whether to ignore the lesson timer. Default false. @since 4.0.3.
 *
 * @return bool True if can complete.
 */
function learndash_can_complete_step( $user_id = 0, $step_id = 0, $course_id = 0, $ignore_lesson_timer = false ) {
	$user_id = absint( $user_id );
	if ( empty( $user_id ) ) {
		$user_id = get_current_user_id();
	}
	$step_id   = absint( $step_id );
	$course_id = absint( $course_id );
	if ( empty( $course_id ) ) {
		$course_id = learndash_get_course_id( $step_id );
	}

	if ( ( ! empty( $user_id ) ) && ( ! empty( $step_id ) ) && ( ! empty( $course_id ) ) ) {
		// If we have ANY previous incomplete steps then we can't complete this step.
		if ( ! learndash_lesson_progression_enabled( $course_id ) ) {
			$incomplete_child_steps = learndash_user_progression_get_incomplete_child_steps( $user_id, $course_id, $step_id );
			if ( empty( ! $incomplete_child_steps ) ) {
				return false;
			}
		} else {
			$incomplete_step_id = learndash_user_progress_get_previous_incomplete_step( $user_id, $course_id, $step_id );
			if ( ! empty( $incomplete_step_id ) && ( $incomplete_step_id !== $step_id ) ) {
				return false;
			}
		}

		if ( in_array( get_post_type( $step_id ), learndash_get_post_type_slug( array( 'lesson', 'topic' ) ), true ) ) {
			// Check the lesson/topic timer.

			if ( false === $ignore_lesson_timer ) {
				$step_timer_time = learndash_forced_lesson_time( $step_id );
				if ( ! empty( $step_timer_time ) ) {
					$time_cookie_key = learndash_forced_lesson_time_cookie_key( $step_id );
					if ( ! empty( $time_cookie_key ) ) {
						/**
						 * Note this is not a 100% check. We are only checking if the cookie
						 * key exists and is zero. But this cookie could have been set from
						 * external.
						 */
						if ( ( ! isset( $_COOKIE[ 'learndash_timer_cookie_' . $time_cookie_key ] ) ) || ( '0' !== $_COOKIE[ 'learndash_timer_cookie_' . $time_cookie_key ] ) ) {
							return false;
						}
					}
				}
			}

			// Next check the Lesson Assignment.

			if ( 'on' === learndash_get_setting( $step_id, 'lesson_assignment_upload' ) ) {
				$assignments = learndash_get_user_assignments( $step_id, $user_id, $course_id );
				if ( empty( $assignments ) ) {
					return false;
				} else {
					foreach ( $assignments as $assignment ) {
						if ( ! learndash_is_assignment_approved_by_meta( $assignment->ID ) ) {
							return false;
						}
					}
				}
			}

			// Next check if all child steps are completed.

			if ( ! learndash_user_is_course_children_progress_complete( $user_id, $course_id, $step_id ) ) {
				return false;
			}
		}

		// Check external steps.

		if ( learndash_course_steps_is_external_attendance_required( $step_id ) ) {
			return false;
		}

		return true;
	}

	return false;
}

/**
 * Checks if the resource has any quizzes.
 *
 * @since 3.4.0
 *
 * @param int|null $id Optional. The ID of the resource like course, lesson, topic, etc. Default null.
 *
 * @return boolean Returns true if the resource has quizzes otherwise false.
 */
function learndash_has_global_quizzes( $id = null ) {
	$quizzes = learndash_get_global_quiz_list( $id );
	return ! empty( $quizzes );
}

/**
 * Checks whether the lesson progression is enabled or not.
 *
 * @since 2.1.0
 *
 * @param int $course_id Optional. Course ID to check. Default 0.
 *
 * @return boolean Returns true if the lesson progression is enabled otherwise false.
 */
function learndash_lesson_progression_enabled( $course_id = 0 ) {
	$course_id = intval( $course_id );

	if ( empty( $course_id ) ) {
		$course_id = learndash_get_course_id();
	}

	if ( ! empty( $course_id ) ) {
		$setting = learndash_get_setting( $course_id, 'course_disable_lesson_progression' );

		/**
		 * Filter for Course Progression Enabled.
		 *
		 * @since 3.4.0
		 *
		 * @param bool $setting   true if course progression is enabled.
		 * @param int  $course_id Course ID.
		 */
		return apply_filters( 'learndash_course_progression_enabled', empty( $setting ), $course_id );
	}

	return false;
}

/**
 * Gets the lesson time for a lesson if it exists.
 *
 * @global WP_Post $post Global post object.
 *
 * @since 2.1.0
 *
 * @param string|int|WP_Post $lesson_topic_post Optional. The `WP_Post` lesson topic post object or ID. Defaults to global post object. Default empty.
 *
 * @return int|string Returns lesson time if it exists otherwise 0.
 */
function learndash_forced_lesson_time( $lesson_topic_post = '' ) {
	if ( empty( $lesson_topic_post ) ) {
		global $post;
		$lesson_topic_post = $post;
	}

	if ( ! is_a( $lesson_topic_post, 'WP_Post' ) ) {
		$post_id = absint( $lesson_topic_post );
		if ( empty( $post_id ) ) {
			return 0;
		}
		$lesson_topic_post = get_post( $post_id );
		if ( ( ! $lesson_topic_post ) || ( ! is_a( $lesson_topic_post, 'WP_Post' ) ) ) {
			return 0;
		}
	}

	if ( ! in_array( $lesson_topic_post->post_type, array( learndash_get_post_type_slug( 'lesson' ), learndash_get_post_type_slug( 'topic' ) ), true ) ) {
		return 0;
	}

	$meta = get_post_meta( $lesson_topic_post->ID, '_' . $lesson_topic_post->post_type, true );
	if ( ! is_array( $meta ) ) {
		$meta = array();
	}
	if ( ! isset( $meta[ $lesson_topic_post->post_type . '_forced_lesson_time_enabled' ] ) ) {
		if ( ( isset( $meta[ $lesson_topic_post->post_type . '_forced_lesson_time' ] ) ) && ( ! empty( $meta[ $lesson_topic_post->post_type . '_forced_lesson_time' ] ) ) ) {
			$meta[ $lesson_topic_post->post_type . '_forced_lesson_time_enabled' ] = 'on';
		} else {
			$meta[ $lesson_topic_post->post_type . '_forced_lesson_time_enabled' ] = '';
		}
	}

	if ( 'on' === $meta[ $lesson_topic_post->post_type . '_forced_lesson_time_enabled' ] ) {
		if ( ( isset( $meta[ $lesson_topic_post->post_type . '_forced_lesson_time' ] ) ) && ( ! empty( $meta[ $lesson_topic_post->post_type . '_forced_lesson_time' ] ) ) ) {
			return $meta[ $lesson_topic_post->post_type . '_forced_lesson_time' ];
		}
	}

	return 0;
}

/**
 * Gets the lesson time cookie key for lesson/topic.
 *
 * @global WP_Post $post Global post object.
 *
 * @since 3.0.0
 *
 * @param string|int|WP_Post $lesson_topic_post Optional. The `WP_Post` lesson topic post object or ID. Defaults to global post object.
 *
 * @return string The cookie key value or empty string.
 */
function learndash_forced_lesson_time_cookie_key( $lesson_topic_post = '' ) {
	if ( empty( $lesson_topic_post ) ) {
		global $post;
		$lesson_topic_post = $post;
	}

	if ( ! is_a( $lesson_topic_post, 'WP_Post' ) ) {
		$post_id = absint( $lesson_topic_post );
		if ( empty( $post_id ) ) {
			return 0;
		}
		$lesson_topic_post = get_post( $post_id );
		if ( ( ! $lesson_topic_post ) || ( ! is_a( $lesson_topic_post, 'WP_Post' ) ) ) {
			return 0;
		}
	}

	if ( ! in_array( $lesson_topic_post->post_type, array( learndash_get_post_type_slug( 'lesson' ), learndash_get_post_type_slug( 'topic' ) ), true ) ) {
		return 0;
	}

	$meta = get_post_meta( $lesson_topic_post->ID, '_' . $lesson_topic_post->post_type, true );
	if ( ! is_array( $meta ) ) {
		$meta = array();
	}
	if ( ! isset( $meta[ $lesson_topic_post->post_type . '_forced_lesson_time_enabled' ] ) ) {
		if ( ( isset( $meta[ $lesson_topic_post->post_type . '_forced_lesson_time' ] ) ) && ( ! empty( $meta[ $lesson_topic_post->post_type . '_forced_lesson_time' ] ) ) ) {
			$meta[ $lesson_topic_post->post_type . '_forced_lesson_time_enabled' ] = 'on';
		} else {
			$meta[ $lesson_topic_post->post_type . '_forced_lesson_time_enabled' ] = '';
		}
	}

	if ( 'on' === $meta[ $lesson_topic_post->post_type . '_forced_lesson_time_enabled' ] ) {
		if ( ( isset( $meta[ $lesson_topic_post->post_type . '_forced_lesson_time' ] ) ) && ( ! empty( $meta[ $lesson_topic_post->post_type . '_forced_lesson_time' ] ) ) ) {
			$cookie_key = get_current_user_id() . '_' . learndash_get_course_id( $lesson_topic_post ) . '_' . $lesson_topic_post->ID;

			if ( ( isset( $meta[ $lesson_topic_post->post_type . '_forced_lesson_time_cookie_key' ] ) ) && ( ! empty( $meta[ $lesson_topic_post->post_type . '_forced_lesson_time_cookie_key' ] ) ) ) {
				$cookie_key .= '_' . $meta[ $lesson_topic_post->post_type . '_forced_lesson_time_cookie_key' ];
			}
			return $cookie_key;
		}
	}

	return '';
}

/**
 * Checks if a course is completed for a user.
 *
 * @since 2.1.0
 *
 * @param int $user_id   User ID.
 * @param int $course_id Course ID.
 *
 * @return boolean Returns true if the course is completed otherwise false.
 */
function learndash_course_completed( $user_id, $course_id ) {
	if ( learndash_course_status( $course_id, $user_id ) == esc_html__( 'Completed', 'learndash' ) ) {
		return true;
	} else {
		return false;
	}
}

/**
 * Adds the course completion date to user meta.
 *
 * @since 2.1.0
 *
 * @param array $data An array of course completion data.
 */
function learndash_course_completed_store_time( $data ) {
	$user_id    = $data['user']->ID;
	$course_id  = $data['course']->ID;
	$meta_key   = 'course_completed_' . $course_id;
	$meta_value = time();

	$course_completed = get_user_meta( $user_id, $meta_key );

	if ( empty( $course_completed ) ) {
		update_user_meta( $user_id, $meta_key, $meta_value );
	}
}
add_action( 'learndash_before_course_completed', 'learndash_course_completed_store_time', 10, 1 );

/**
 * Deletes the course progress for a user.
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @since 2.1.0
 *
 * @param int $course_id Course ID.
 * @param int $user_id   User ID.
 */
function learndash_delete_course_progress( $course_id, $user_id ) {
	global $wpdb;
	$usermeta = get_user_meta( $user_id, '_sfwd-course_progress', true );

	if ( isset( $usermeta[ $course_id ] ) ) {
		unset( $usermeta[ $course_id ] );
		update_user_meta( $user_id, '_sfwd-course_progress', $usermeta );
	}

	delete_user_meta( $user_id, 'course_completed_' . $course_id );

	// The reason we don't use the methods above is we want to ensure all old data is removed
	// from the quiz attempt history not just for quizzes currently associated with the course.
	$quizzes          = array();
	$usermeta_quizzes = get_user_meta( $user_id, '_sfwd-quizzes', true );
	if ( ! is_array( $usermeta_quizzes ) ) {
		$usermeta_quizzes = array();
	}
	if ( ! empty( $usermeta_quizzes ) ) {
		foreach ( $usermeta_quizzes as $quiz_item ) {
			if ( ( isset( $quiz_item['course'] ) ) && ( intval( $course_id ) == intval( $quiz_item['course'] ) ) ) {
				if ( isset( $quiz_item['quiz'] ) ) {
					$quiz_id             = intval( $quiz_item['quiz'] );
					$quizzes[ $quiz_id ] = $quiz_id;
				}
			}
		}
	}

	if ( ! empty( $quizzes ) ) {
		foreach ( $quizzes as $quiz_id ) {
			learndash_delete_quiz_progress( $user_id, $quiz_id );
		}
	}
}

/**
 * Deletes the quiz progress for a user.
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @since 2.1.0
 *
 * @param int $user_id User ID.
 * @param int $quiz_id Quiz ID.
 */
function learndash_delete_quiz_progress( $user_id, $quiz_id ) {
	global $wpdb;

	// Clear User Meta.
	$usermeta = get_user_meta( $user_id, '_sfwd-quizzes', true );

	if ( ! empty( $usermeta ) && is_array( $usermeta ) ) {
		$usermeta_new = array();
		foreach ( $usermeta as $key => $quizmeta ) {
			if ( $quizmeta['quiz'] != $quiz_id ) {
				$usermeta_new[] = $quizmeta;
			}
		}
		update_user_meta( $user_id, '_sfwd-quizzes', $usermeta_new );
	}

	// ProQuiz Data.
	$pro_quiz_id = learndash_get_setting( $quiz_id, 'quiz_pro' );

	// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
	$ref_ids = $wpdb->get_col(
		$wpdb->prepare( 'SELECT statistic_ref_id FROM ' . esc_sql( LDLMS_DB::get_table_name( 'quiz_statistic_ref' ) ) . ' WHERE  user_id = %d AND quiz_id = %d ', $user_id, $pro_quiz_id )
	);

	if ( ! empty( $ref_ids[0] ) ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$wpdb->delete(
			LDLMS_DB::get_table_name( 'quiz_statistic_ref' ),
			array(
				'user_id' => $user_id,
				'quiz_id' => $pro_quiz_id,
			)
		);

		$ref_ids = array_map( 'absint', $ref_ids );
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$wpdb->query(
			// phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQL.NotPrepared -- IN clause
			$wpdb->prepare( 'DELETE FROM ' . esc_sql( LDLMS_DB::get_table_name( 'quiz_statistic' ) ) . ' WHERE statistic_ref_id IN (' . LDLMS_DB::escape_IN_clause_placeholders( $ref_ids ) . ')', LDLMS_DB::escape_IN_clause_values( $ref_ids ) )
		);
	}

	// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
	$wpdb->delete(
		LDLMS_DB::get_table_name( 'quiz_toplist' ),
		array(
			'user_id' => $user_id,
			'quiz_id' => $pro_quiz_id,
		)
	);
}

/**
 * Removes the user quiz statistics by the reference ID.
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @since 2.5.0
 *
 * @param int $ref_id Optional. Quiz statistic reference ID. Default 0.
 */
function learndash_quiz_remove_user_statistics_by_ref( $ref_id = 0 ) {
	global $wpdb;

	if ( ! empty( $ref_id ) ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$wpdb->delete( LDLMS_DB::get_table_name( 'quiz_statistic_ref' ), array( 'statistic_ref_id' => $ref_id ) );
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$wpdb->query(
			$wpdb->prepare( 'DELETE FROM ' . esc_sql( LDLMS_DB::get_table_name( 'quiz_statistic' ) ) . ' WHERE statistic_ref_id = %d', $ref_id )
		);
	}
}

/**
 * Removes the quiz user toplist.
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @since 3.1.0
 *
 * @param int $user_id    Optional. User ID. Default 0.
 * @param int $quiz_time  Optional. Quiz time. Default 0.
 * @param int $pro_quizid Optional. Pro quiz ID. Default 0.
 *
 * @return void|int|false The number of rows updated, or false on error.
 */
function learndash_quiz_remove_user_toplist( $user_id = 0, $quiz_time = 0, $pro_quizid = 0 ) {
	global $wpdb;

	if ( ( ! empty( $user_id ) ) && ( ! empty( $quiz_time ) ) && ( ! empty( $pro_quizid ) ) ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		return $wpdb->delete(
			LDLMS_DB::get_table_name( 'quiz_toplist' ),
			array(
				'user_id' => $user_id,
				'date'    => $quiz_time,
				'quiz_id' => $pro_quizid,
			),
			array( '%d', '%d', '%d' )
		);
	}
}

/**
 * Marks a course step incomplete for a course.
 *
 * Used to set a course step ( lesson or topic only ) back to not complete status.
 *
 * @global WP_Post $post Global post object.
 *
 * @since 2.5.0
 *
 * @param int     $user_id       Optional. User ID. Default 0.
 * @param int     $course_id     Optional. Course ID. Default 0.
 * @param int     $step_id       Optional. Step ID. Default 0.
 * @param boolean $step_complete Optional. Unused. Default false.
 *
 * @return int|boolean Returns true if the update is successful otherwise false or meta ID
 *                     if the meta does not exist.
 */
function learndash_process_mark_incomplete( $user_id = 0, $course_id = 0, $step_id = 0, $step_complete = false ) {
	$user_id = absint( $user_id );
	if ( empty( $user_id ) ) {
		return;
	}

	$course_id = absint( $course_id );
	if ( empty( $course_id ) ) {
		return;
	}

	$step_id = absint( $step_id );
	if ( empty( $step_id ) ) {
		global $post;
		if ( ( isset( $post ) ) && ( $post instanceof WP_Post ) && ( ( in_array( $post->post_type, array( 'sfwd-lessons', 'sfwd-topic', 'sfwd-quiz' ), true ) ) ) ) {
			$step_id = $post->ID;
		} else {
			return;
		}
	}

	$subtracted_completed_steps = 0;

	$course_step_parents = learndash_course_get_all_parent_step_ids( $course_id, $step_id );

	$user_course_progress = get_user_meta( $user_id, '_sfwd-course_progress', true );
	if ( ! isset( $user_course_progress[ $course_id ] ) ) {
		return;
	}

	$step_post_type = get_post_type( $step_id );
	if ( 'sfwd-quiz' === $step_post_type ) {
		if ( ! empty( $course_step_parents ) ) {
			if ( ( 2 == count( $course_step_parents ) ) && ( 'sfwd-lessons' == get_post_type( $course_step_parents[0] ) ) && ( 'sfwd-topic' == get_post_type( $course_step_parents[1] ) ) ) {
				$lesson_id = $course_step_parents[0];
				$topic_id  = $course_step_parents[1];

				if ( ( isset( $user_course_progress[ $course_id ]['topics'][ $lesson_id ][ $topic_id ] ) ) && ( true == $user_course_progress[ $course_id ]['topics'][ $lesson_id ][ $topic_id ] ) ) {
					$user_course_progress[ $course_id ]['topics'][ $lesson_id ][ $topic_id ] = 0;
					$user_course_progress[ $course_id ]['completed']                        -= 1;

					/**
					 * Fires on marking a course incomplete.
					 *
					 * @param int $user_id   User ID.
					 * @param int $course_id Course ID.
					 * @param int $topic_id  Topic ID.
					 */
					do_action( 'learndash_mark_incomplete_process', $user_id, $course_id, $topic_id );

					$topic_args     = array(
						'course_id'     => $course_id,
						'user_id'       => $user_id,
						'post_id'       => $topic_id,
						'activity_type' => 'topic',
					);
					$topic_activity = learndash_get_user_activity( $topic_args );
					if ( $topic_activity ) {
						$topic_activity = (array) $topic_activity;

						$topic_activity['activity_status']    = false;
						$topic_activity['activity_completed'] = false;
						learndash_update_user_activity( $topic_activity );
					}
				}

				if ( ( isset( $user_course_progress[ $course_id ]['lessons'][ $lesson_id ] ) ) && ( true == $user_course_progress[ $course_id ]['lessons'][ $lesson_id ] ) ) {
					$user_course_progress[ $course_id ]['lessons'][ $lesson_id ] = 0;
					$user_course_progress[ $course_id ]['completed']            -= 1;

					/** This filter is documented in includes/course/ld-course-progress.php */
					do_action( 'learndash_mark_incomplete_process', $user_id, $course_id, $lesson_id );

					$lesson_args     = array(
						'course_id'     => $course_id,
						'user_id'       => $user_id,
						'post_id'       => $lesson_id,
						'activity_type' => 'lesson',
					);
					$lesson_activity = learndash_get_user_activity( $lesson_args );
					if ( $lesson_args ) {
						$lesson_args                       = (array) $lesson_args;
						$lesson_args['activity_status']    = false;
						$lesson_args['activity_completed'] = false;
						learndash_update_user_activity( $lesson_args );
					}
				}
			} elseif ( ( 1 == count( $course_step_parents ) ) && ( 'sfwd-lessons' == get_post_type( $course_step_parents[0] ) ) ) {
				$lesson_id = $course_step_parents[0];

				if ( ( isset( $user_course_progress[ $course_id ]['lessons'][ $lesson_id ] ) ) && ( true == $user_course_progress[ $course_id ]['lessons'][ $lesson_id ] ) ) {
					$user_course_progress[ $course_id ]['lessons'][ $lesson_id ] = 0;
					$user_course_progress[ $course_id ]['completed']            -= 1;

					/** This filter is documented in includes/course/ld-course-progress.php */
					do_action( 'learndash_mark_incomplete_process', $user_id, $course_id, $lesson_id );

					$lesson_args     = array(
						'course_id'     => $course_id,
						'user_id'       => $user_id,
						'post_id'       => $lesson_id,
						'activity_type' => 'lesson',
					);
					$lesson_activity = learndash_get_user_activity( $lesson_args );
					if ( $lesson_args ) {
						$lesson_args                       = (array) $lesson_args;
						$lesson_args['activity_status']    = false;
						$lesson_args['activity_completed'] = false;
						learndash_update_user_activity( $lesson_args );
					}
				}
			}
		}
	} elseif ( 'sfwd-topic' === $step_post_type ) {
		$step_parent_id = learndash_course_get_single_parent_step( $course_id, $step_id );
		if ( ! empty( $step_parent_id ) ) {
			if ( ( isset( $user_course_progress[ $course_id ]['topics'][ $step_parent_id ][ $step_id ] ) ) && ( true == $user_course_progress[ $course_id ]['topics'][ $step_parent_id ][ $step_id ] ) ) {
				$user_course_progress[ $course_id ]['topics'][ $step_parent_id ][ $step_id ] = 0;
				$user_course_progress[ $course_id ]['completed']                            -= 1;

				/** This filter is documented in includes/course/ld-course-progress.php */
				do_action( 'learndash_mark_incomplete_process', $user_id, $course_id, $step_id );

				$topic_args     = array(
					'course_id'     => $course_id,
					'user_id'       => $user_id,
					'post_id'       => $step_id,
					'activity_type' => 'topic',
				);
				$topic_activity = learndash_get_user_activity( $topic_args );
				if ( $topic_activity ) {
					$topic_activity = (array) $topic_activity;

					$topic_activity['activity_status']    = false;
					$topic_activity['activity_completed'] = false;
					learndash_update_user_activity( $topic_activity );
				}
			}
			if ( ( isset( $user_course_progress[ $course_id ]['lessons'][ $step_parent_id ] ) ) && ( true == $user_course_progress[ $course_id ]['lessons'][ $step_parent_id ] ) ) {
				$user_course_progress[ $course_id ]['lessons'][ $step_parent_id ] = 0;
				$user_course_progress[ $course_id ]['completed']                 -= 1;

				/** This filter is documented in includes/course/ld-course-progress.php */
				do_action( 'learndash_mark_incomplete_process', $user_id, $course_id, $step_parent_id );

				$lesson_args     = array(
					'course_id'     => $course_id,
					'user_id'       => $user_id,
					'post_id'       => $step_parent_id,
					'activity_type' => 'lesson',
				);
				$lesson_activity = learndash_get_user_activity( $lesson_args );
				if ( $lesson_args ) {
					$lesson_args                       = (array) $lesson_args;
					$lesson_args['activity_status']    = false;
					$lesson_args['activity_completed'] = false;
					learndash_update_user_activity( $lesson_args );
				}
			}
		}
	} elseif ( 'sfwd-lessons' === $step_post_type ) {
		if ( ( isset( $user_course_progress[ $course_id ]['lessons'][ $step_id ] ) ) && ( true == $user_course_progress[ $course_id ]['lessons'][ $step_id ] ) ) {
			$user_course_progress[ $course_id ]['lessons'][ $step_id ] = 0;
			$user_course_progress[ $course_id ]['completed']          -= 1;

			/** This filter is documented in includes/course/ld-course-progress.php */
			do_action( 'learndash_mark_incomplete_process', $user_id, $course_id, $step_id );

			$lesson_args     = array(
				'course_id'     => $course_id,
				'user_id'       => $user_id,
				'post_id'       => $step_id,
				'activity_type' => 'lesson',
			);
			$lesson_activity = learndash_get_user_activity( $lesson_args );
			if ( $lesson_args ) {
				$lesson_args                       = (array) $lesson_args;
				$lesson_args['activity_status']    = false;
				$lesson_args['activity_completed'] = false;
				learndash_update_user_activity( $lesson_args );
			}
		}
	}

	if ( ! isset( $user_course_progress[ $course_id ] ) ) {
		return;
	}

	if ( isset( $user_course_progress[ $course_id ]['completed'] ) ) {
		$user_course_progress[ $course_id ]['completed'] = absint( $user_course_progress[ $course_id ]['completed'] );
	} else {
		$user_course_progress[ $course_id ]['completed'] = 0;
	}

	if ( isset( $user_course_progress[ $course_id ]['total'] ) ) {
		$user_course_progress[ $course_id ]['total'] = absint( $user_course_progress[ $course_id ]['total'] );
	} else {
		$user_course_progress[ $course_id ]['total'] = 0;
	}

	if ( $user_course_progress[ $course_id ]['completed'] !== $user_course_progress[ $course_id ]['total'] ) {
		delete_user_meta( $user_id, 'course_completed_' . $course_id );

		/** This filter is documented in includes/course/ld-course-progress.php */
		do_action( 'learndash_mark_incomplete_process', $user_id, $course_id, $course_id );
		$course_args     = array(
			'course_id'     => $course_id,
			'user_id'       => $user_id,
			'post_id'       => $course_id,
			'activity_type' => 'course',
		);
		$course_activity = learndash_get_user_activity( $course_args );
		if ( $course_args ) {
			$course_args                       = (array) $course_args;
			$course_args['activity_status']    = false;
			$course_args['activity_completed'] = false;
			learndash_update_user_activity( $course_args );
		}
	}

	// Remove the related transient completion data.

	delete_transient( "learndash_step_completed_{$step_id}_{$course_id}_{$user_id}" );

	return update_user_meta( $user_id, '_sfwd-course_progress', $user_course_progress );
}

/**
 * Gets the quiz attempt meta for a given user.
 *
 * @since 2.3.0
 *
 * @param int   $user_id Optional. User ID. Default 0.
 * @param array $args    Optional. An array of items to match. Default empty array.
 *
 * @return array An array of user quiz attempt meta.
 */
function learndash_get_user_quiz_attempt( $user_id = 0, $args = array() ) {
	if ( ( ! empty( $user_id ) ) && ( ! empty( $args ) ) ) {
		$user_quizzes = get_user_meta( $user_id, '_sfwd-quizzes', true );
		if ( ! empty( $user_quizzes ) ) {
			foreach ( $user_quizzes as $idx => $user_quiz ) {
				foreach ( $args as $arg_key => $arg_val ) {
					if ( ( ! isset( $user_quiz[ $arg_key ] ) ) || ( $user_quiz[ $arg_key ] != $arg_val ) ) {
						unset( $user_quizzes[ $idx ] );
					}
				}
			}
		}

		return $user_quizzes;
	}

	return array();
}

/**
 * Removes the quiz attempt meta for a given user.
 *
 * @since 2.5.0
 *
 * @param int   $user_id Optional. User ID. Default 0.
 * @param array $args    Optional. An array of items to match. Default empty array.
 *
 * @return array An array of updated quiz attempt meta.
 */
function learndash_remove_user_quiz_attempt( $user_id = 0, $args = array() ) {
	if ( ( ! empty( $user_id ) ) && ( ! empty( $args ) ) ) {
		$changes      = false;
		$user_quizzes = get_user_meta( $user_id, '_sfwd-quizzes', true );

		if ( ! empty( $user_quizzes ) ) {
			$changed_user_quizzes = array();

			foreach ( $user_quizzes as $idx => $user_quiz ) {
				$match_found = true;

				foreach ( $args as $arg_key => $arg_val ) {
					if ( ( ! isset( $user_quiz[ $arg_key ] ) ) || ( $user_quiz[ $arg_key ] != $arg_val ) ) {
						$match_found = false;
						break;
					}
				}

				if ( true === $match_found ) {
					/**
					 * Fires before user single quiz attempt has been removed.
					 *
					 * @since 3.2.3
					 *
					 * @param int   $user_id   User ID for the Quiz.
					 * @param array $user_quiz User meta quiz item to be deleted.
					 */
					do_action( 'learndash_user_quiz_delete_single_before', $user_id, $user_quiz );

					if ( ( isset( $user_quiz['time'] ) ) && ( ! empty( $user_quiz['time'] ) ) ) {
						if ( ( isset( $user_quiz['pro_quizid'] ) ) && ( ! empty( $user_quiz['pro_quizid'] ) ) ) {
							learndash_quiz_remove_user_toplist( $user_id, $user_quiz['time'], $user_quiz['pro_quizid'] );
						}
					}

					// If we have a statistics reference we need to remove this set of records.
					if ( ( isset( $user_quiz['statistic_ref_id'] ) ) && ( ! empty( $user_quiz['statistic_ref_id'] ) ) ) {
						learndash_quiz_remove_user_statistics_by_ref( $user_quiz['statistic_ref_id'] );
					}

					if ( ( ! isset( $user_quiz['course'] ) ) || ( empty( $user_quiz['course'] ) ) ) {
						$user_quiz['course'] = learndash_get_course_id( $user_quiz['quiz'] );
					}

					// If this quiz has graded items they all need to be moved to trash or deleted.
					if ( ( isset( $user_quiz['graded'] ) ) && ( ! empty( $user_quiz['graded'] ) ) ) {
						foreach ( $user_quiz['graded'] as $question_id => $graded_set ) {
							if ( ( isset( $graded_set['post_id'] ) ) && ( ! empty( $graded_set['post_id'] ) ) ) {
								wp_delete_post( $graded_set['post_id'], true );
							}
						}
					}

					// Remove the user activity record.
					$quiz_args     = array(
						'course_id'          => isset( $user_quiz['course'] ) ? absint( $user_quiz['course'] ) : 0,
						'user_id'            => $user_id,
						'post_id'            => $user_quiz['quiz'],
						'activity_type'      => 'quiz',
						'activity_completed' => isset( $user_quiz['completed'] ) ? absint( $user_quiz['completed'] ) : 0,
					);
					$quiz_activity = learndash_get_user_activity( $quiz_args );
					if ( ! empty( $quiz_activity ) ) {
						learndash_delete_user_activity( $quiz_activity->activity_id );
					}

					$changed_user_quizzes[] = $user_quiz;

					unset( $user_quizzes[ $idx ] );
					$changes = true;

					/**
					 * Fires after user single quiz attempt has been removed.
					 *
					 * @since 3.2.3
					 *
					 * @param int   $user_id   User ID for the Quiz.
					 * @param array $user_quiz User meta quiz item to be deleted.
					 */
					do_action( 'learndash_user_quiz_delete_single_after', $user_id, $user_quiz );
				}
			}

			if ( true === $changes ) {
				// If not empty then we reset the keys.
				if ( ! empty( $user_quizzes ) ) {
					$user_quizzes = array_values( $user_quizzes );
				}

				update_user_meta( $user_id, '_sfwd-quizzes', $user_quizzes );

				if ( ! empty( $changed_user_quizzes ) ) {
					/**
					 * Fires after user all quiz attempts have been removed.
					 *
					 * @since 3.2.3
					 *
					 * @param int   $user_id              User ID for the Quiz.
					 * @param array $changed_user_quizzes Array of all quiz items deleted.
					 */
					do_action( 'learndash_user_quiz_delete_all_after', $user_id, $changed_user_quizzes );

					foreach ( $changed_user_quizzes as $user_quiz ) {
						if ( ! learndash_is_quiz_complete( $user_id, $user_quiz['quiz'], $user_quiz['course'] ) ) {
							learndash_process_mark_incomplete( $user_id, $user_quiz['course'], $user_quiz['quiz'], false );
						}

						/**
						 * Legacy: Call the `learndash_process_mark_complete` function after the quiz entry is removed.
						 *
						 * The call to this function was removed as of LD 3.5.1 because it should not be needed. But
						 * retaining for legacy purposes.
						 *
						 * @since 3.5.1
						 *
						 * @param bool  $call_function Default is false.
						 * @param array $user_quiz     User meta quiz item to be deleted.
						 * @param int   $user_id       User ID for the Quiz.
						 */
						if ( true === apply_filters( 'learndash_quiz_call_mark_complete_after_remove', false, $user_quiz, $user_id ) ) {
							learndash_process_mark_complete( $user_id, $user_quiz['quiz'], false, $user_quiz['course'] );
						}
					}
				}
			}
		}

		return $user_quizzes;
	}

	return array();
}

/**
 * Outputs HTML output to mark a step incomplete.
 *
 * Must meet requirements of course to mark incomplete.
 *
 * @since 3.1.4
 *
 * @param WP_Post $post The `WP_Post` for lesson, topic.
 * @param array   $atts Optional. An array of attributes for mark incomplete output. Default empty array.
 *
 * @return string The HTML output to mark course incomplete.
 */
function learndash_show_mark_incomplete( $post, $atts = array() ) {
	$course_mark_incomplete_enabled = LearnDash_Settings_Section::get_section_setting( 'LearnDash_Settings_Courses_Management_Display', 'course_mark_incomplete_enabled', false );
	if ( empty( $course_mark_incomplete_enabled ) ) {
		return '';
	}

	if ( ! is_user_logged_in() ) {
		return '';
	}

	$user_id   = get_current_user_id();
	$course_id = learndash_get_course_id( $post->ID );

	/**
	 * Filters whether to show mark course incomplete form.
	 *
	 * @param boolean $show_form Whether to show mark incomplete form.
	 * @param int     $course_id Course ID.
	 * @param WP_Post $post      `WP_Post` object being displayed.
	 * @param array   $atts      An array of attributes to mark a course incomplete.
	 */
	if ( ! apply_filters( 'learndash_show_mark_incomplete_form', true, $course_id, $post, $atts ) ) {
		return '';
	}

	/**
	 * Filters attributes to mark a course incomplete.
	 *
	 * @param array   $atts An array of attributes to mark a course incomplete.
	 * @param WP_Post $post `WP_Post` object being displayed.
	 */
	$atts = apply_filters( 'learndash_mark_incomplete_form_atts', $atts, $post );

	if ( isset( $atts['form']['id'] ) ) {
		$form_id = ' id="' . esc_attr( $atts['form']['id'] ) . '" ';
	} else {
		$form_id = '';
	}

	if ( isset( $atts['form']['class'] ) ) {
		$form_class = ' class="sfwd-mark-incomplete sfwd-mark-complete ' . esc_attr( $atts['form']['class'] ) . '" ';
	} else {
		$form_class = ' class="sfwd-mark-incomplete sfwd-mark-complete" ';
	}

	if ( isset( $atts['button']['id'] ) ) {
		$button_id = ' id="' . esc_attr( $atts['button']['id'] ) . '" ';
	} else {
		$button_id = '';
	}

	$button_disabled = '';
	if ( isset( $atts['button']['class'] ) ) {
		$button_class = ' class="learndash_mark_incomplete_button learndash_mark_complete_button ' . esc_attr( $atts['button']['class'] ) . '" ';
	} else {
		$button_class = ' class="learndash_mark_incomplete_button learndash_mark_complete_button" ';
	}

	$button_label = LearnDash_Custom_Label::get_label( 'button_mark_incomplete' );
	if ( empty( $button_label ) ) {
		$button_label = esc_html__( 'Mark Incomplete', 'learndash' );
	}
	$form_fields = '<input type="hidden" value="' . $post->ID . '" name="post" />
				<input type="hidden" value="' . learndash_get_course_id( $post->ID ) . '" name="course_id" />
				<input type="hidden" value="' . wp_create_nonce( 'sfwd_mark_incomplete_' . $user_id . '_' . $post->ID ) . '" name="sfwd_mark_incomplete" />
				<input type="submit" ' . $button_id . ' value="' . esc_attr( $button_label ) . '" ' . $button_class . ' ' . $button_disabled . '/>';

	/** This filter is documented in includes/course/ld-course-progress.php */
	$form_fields = apply_filters( 'learndash_mark_complete_form_fields', $form_fields, $post );

	$return = '<form ' . $form_id . ' ' . $form_class . ' method="post" action="">' . $form_fields . '</form>';

	return $return;
}

/**
 * Processes the request to mark a course or step incomplete.
 *
 * @since 3.1.4
 *
 * @global WP_Post $post Global post object.
 */
function learndash_mark_incomplete_process() {
	if ( ( isset( $_POST['sfwd_mark_incomplete'] ) ) && ( ! empty( $_POST['sfwd_mark_incomplete'] ) ) && ( isset( $_POST['post'] ) ) && ( ! empty( $_POST['post'] ) ) ) {
		$post_id = intval( $_POST['post'] );

		if ( isset( $_POST['course_id'] ) ) {
			$course_id = intval( $_POST['course_id'] );
		} else {
			$course_id = learndash_get_course_id( $post_id );
		}

		if ( isset( $_POST['userid'] ) ) {
			$user_id = intval( $_POST['userid'] );
		} else {
			if ( ! is_user_logged_in() ) {
				return;
			}

			$user_id = get_current_user_id();
		}

		/**
		 * Verify the form is valid
		 *
		 * @since 3.1.4
		 */
		if ( ! wp_verify_nonce( $_POST['sfwd_mark_incomplete'], 'sfwd_mark_incomplete_' . $user_id . '_' . $post_id ) ) {
			return;
		}

		return learndash_process_mark_incomplete( $user_id, $course_id, $post_id, false );
	}
	return false;
}
add_action( 'wp', 'learndash_mark_incomplete_process' );

/**
 * Returns the page url a user is redirected to after completing a step.
 *
 * @since 4.11.0
 *
 * @param int $step_id   Step post ID.
 * @param int $course_id Course ID.
 * @param int $user_id   User ID.
 *
 * @return string
 */
function learndash_course_get_step_completion_url( int $step_id, int $course_id, int $user_id ): string {
	$step_completion_data = learndash_course_get_step_completion_data( $step_id, $course_id, $user_id );
	$redirect_url         = $step_completion_data['next_step_url'];

	/**
	 * Filters URL to redirect to after marking a step complete.
	 *
	 * @deprecated 4.11.0 Use {@see 'learndash_course_step_completion_url'} instead.
	 *
	 * @param string $redirect_url Next step redirect URL. Can be empty if post was not found for some reason.
	 * @param int    $step_id      Course step post ID.
	 *
	 * @return string Next step redirect URL.
	 */
	$redirect_url = apply_filters_deprecated(
		'learndash_completion_redirect',
		[ $redirect_url, $step_id ],
		'4.11.0',
		'learndash_course_step_completion_url'
	);

	/**
	 * Filters URL to redirect to after marking a step complete.
	 *
	 * @since 4.11.0
	 *
	 * @param string $redirect_url Next step redirect URL. Can be empty if post was not found for some reason.
	 * @param int    $step_id      Course step post ID.
	 * @param int    $course_id    Course ID.
	 *
	 * @return string Redirect URL.
	 */
	return apply_filters( 'learndash_course_step_completion_url', $redirect_url, $step_id, $course_id );
}

/**
 * Returns the step completion data when a step is completed by a user.
 *
 * @since 4.21.3
 * @since 4.24.0. Added the `$should_preserve_transient` parameter and the `is_course_completion_url` key to the return value.
 *
 * @param int  $step_id                   Step post ID.
 * @param int  $course_id                 Course ID.
 * @param int  $user_id                   User ID.
 * @param bool $should_preserve_transient If true, it will keep the transient data that affects the result, so it can be reused in the next call.
 *
 * @return array{
 *     is_course_completed: bool,
 *     is_course_completion_url: bool,
 *     next_step_id: int,
 *     next_step_url: string,
 * }
 */
function learndash_course_get_step_completion_data(
	int $step_id,
	int $course_id,
	int $user_id,
	bool $should_preserve_transient = false
): array {
	$step_completion_data = [
		'is_course_completed'      => false,
		'is_course_completion_url' => false,
		'next_step_id'             => 0,
		'next_step_url'            => '',
	];

	$course_completion_transient_key = 'learndash_course_completed_' . $course_id . '_' . $user_id;

	if ( false !== get_transient( $course_completion_transient_key ) ) {
		/**
		 * Case 1:
		 *
		 * If we have the course completion transient, the course is completed, and we should redirect to the completion page.
		 */

		if ( ! $should_preserve_transient ) {
			delete_transient( $course_completion_transient_key ); // Delete, as we need to do it once.
		}

		$step_completion_data['is_course_completed']      = true;
		$step_completion_data['is_course_completion_url'] = true;
		$step_completion_data['next_step_url']            = learndash_course_get_completion_url( $course_id );
	} else {
		$parent_step_id = learndash_course_get_single_parent_step( $course_id, $step_id );
		$parent_step    = get_post( $parent_step_id );

		$parent_step_is_completed = learndash_user_progress_is_step_complete( $user_id, $course_id, $parent_step_id );

		// If the parent step is completed, evaluate the grandparent step.
		// It happens when we have a quiz inside a topic. The topic (parent step) may not have any special requirements,
		// but the lesson (grandparent step) may have.

		if ( $parent_step_is_completed ) {
			$parent_step_id = learndash_course_get_single_parent_step( $course_id, $parent_step_id );
			$parent_step    = get_post( $parent_step_id );
		}

		/**
		 * Case 2:
		 *
		 * If the parent step is not completed and requires a video, an assignment, or wait for a timer to be completed, and all the child steps are completed,
		 * redirect back to the parent step.
		 */
		if (
			$parent_step instanceof WP_Post
			&& ! learndash_user_progress_is_step_complete( $user_id, $course_id, $parent_step_id )
			&& (
				learndash_course_steps_requires_watching_video_after_sub_steps( $parent_step_id )
				|| learndash_lesson_hasassignments( $parent_step ) // cspell: disable-line .
				|| learndash_forced_lesson_time( $parent_step_id )
				|| learndash_course_steps_is_external( $parent_step_id )
			)
			&& empty( learndash_user_progression_get_incomplete_child_steps( $user_id, $course_id, $parent_step_id ) )
		) {
			// Redirect back to the parent step.

			$step_completion_data['next_step_url'] = learndash_get_step_permalink( $parent_step_id, $course_id );
			$step_completion_data['next_step_id']  = $parent_step_id;
		} else {
			$step = get_post( $step_id );

			if (
				$step instanceof WP_Post
				&& (
					learndash_course_steps_requires_watching_video_after_sub_steps( $step_id )
					|| learndash_lesson_hasassignments( $step ) // cspell: disable-line .
					|| learndash_forced_lesson_time( $step_id )
					|| learndash_course_steps_is_external( $step_id )
				)
			) {
				/**
				 * Case 3:
				 *
				 * If the current step:
				 * - requires watching a video after sub steps, or
				 * - has assignments, or
				 * - has a timer, or
				 * - is external,
				 * and all child steps are completed, redirect to the next incomplete step.
				 */

				// Get the next incomplete step (an incomplete step after the current step).
				$next_step_id = learndash_user_progress_get_next_incomplete_step( $user_id, $course_id, $step_id );

				if ( $next_step_id > 0 ) {
					// Redirect to the next incomplete step.

					$step_completion_data['next_step_url'] = learndash_get_step_permalink( $next_step_id, $course_id );
					$step_completion_data['next_step_id']  = $next_step_id;
				}
			}
		}
	}

	// If there is no special case, redirect to the next step page or a course page.

	if ( empty( $step_completion_data['next_step_url'] ) ) {
		$next_step_id = Cast::to_int(
			learndash_next_post_link(
				'',
				'id',
				get_post( $step_id )
			)
		);

		if ( $next_step_id > 0 ) {
			$step_completion_data['next_step_url'] = learndash_get_step_permalink( $next_step_id, $course_id );
			$step_completion_data['next_step_id']  = $next_step_id;
		} else {
			$step_completion_data['next_step_url'] = get_permalink( $course_id );
			$step_completion_data['next_step_id']  = $course_id;
		}

		$step_completion_data['is_course_completed'] = learndash_course_completed( $user_id, $course_id );
	}

	return $step_completion_data;
}
