<?php
/**
 * Functions used for managing page / post access
 *
 * @package LifterLMS/Functions
 *
 * @since 1.0.0
 * @version 7.7.0
 */

defined( 'ABSPATH' ) || exit;

/**
 * Determine if content should be restricted.
 *
 * Called during "template_include" to determine if redirects
 * or template overrides are in order.
 *
 * @since 1.0.0
 * @since 3.16.11 Unknown.
 * @since 5.7.0 Replaced the call to the deprecated `LLMS_Lesson::get_parent_course()` method with `LLMS_Lesson::get( 'parent_course' )`.
 *
 * @param int      $post_id WordPress Post ID of the content.
 * @param int|null $user_id Optional. WP User ID (will use get_current_user_id() if none supplied). Default `null`.
 * @return array Restriction check result data.
 */
function llms_page_restricted( $post_id, $user_id = null ) {

	$results = array(
		'content_id'     => $post_id,
		'is_restricted'  => false,
		'reason'         => 'accessible',
		'restriction_id' => 0,
	);

	if ( ! $user_id ) {
		$user_id = get_current_user_id();
	}

	$student = false;
	if ( $user_id ) {
		$student = new LLMS_Student( $user_id );
	}

	$post_type = get_post_type( $post_id );

	/**
	 * Do checks to determine if the content should be restricted.
	 */
	$sitewide_membership_id = llms_is_post_restricted_by_sitewide_membership( $post_id, $user_id );
	$membership_id          = llms_is_post_restricted_by_membership( $post_id, $user_id );

	if ( is_home() && $sitewide_membership_id ) {
		$restriction_id = $sitewide_membership_id;
		$reason         = 'sitewide_membership';
		// if it's a search page and the site isn't restricted to a membership bypass restrictions.
	} elseif ( ( is_search() ) && ! get_option( 'lifterlms_membership_required', '' ) ) {
		return apply_filters( 'llms_page_restricted', $results, $post_id );
	} elseif ( is_singular() && $sitewide_membership_id ) {
		$restriction_id = $sitewide_membership_id;
		$reason         = 'sitewide_membership';
	} elseif ( is_singular() && $membership_id ) {
		$restriction_id = $membership_id;
		$reason         = 'membership';
	} elseif ( is_singular() && 'lesson' === $post_type ) {
		$lesson = new LLMS_Lesson( $post_id );
		// if lesson is free, return accessible results and skip the rest of this function.
		if ( $lesson->is_free() ) {
			return $results;
		} else {
			$restriction_id = $lesson->get( 'parent_course' );
			$reason         = 'enrollment_lesson';
		}
	} elseif ( is_singular() && 'course' === $post_type ) {
		$restriction_id = $post_id;
		$reason         = 'enrollment_course';
	} elseif ( is_singular() && 'llms_membership' === $post_type ) {
		$restriction_id = $post_id;
		$reason         = 'enrollment_membership';
	} else {

		/**
		 * Allow filtering of results before checking if the student has access.
		 *
		 * @since Unknown.
		 *
		 * @param array $results Restriction check result data.
		 * @param int   $post_id WordPress Post ID of the content.
		 */
		$results = apply_filters( 'llms_page_restricted_before_check_access', $results, $post_id );
		extract( $results ); // phpcs:ignore

	}

	/**
	 * Content should be restricted, so we'll do the restriction checks
	 * and return restricted results.
	 *
	 * This is run if we have a restriction and a reason for restriction
	 * and we either don't have a logged in student or the logged in student doesn't have access.
	 */
	if ( ! empty( $restriction_id ) && ! empty( $reason ) && ( ! $student || ! $student->is_enrolled( $restriction_id ) ) ) {

		$results['is_restricted']  = true;
		$results['reason']         = $reason;
		$results['restriction_id'] = $restriction_id;

		/**
		 * Allow filtering of the restricted results.
		 *
		 * @since Unknown
		 *
		 * @param array $results Restriction check result data.
		 * @param int   $post_id WordPress Post ID of the content.
		 */
		return apply_filters( 'llms_page_restricted', $results, $post_id );

	}

	/**
	 * At this point student has access or the content isn't supposed to be restricted
	 * we need to do some additional checks for specific post types.
	 */
	if ( is_singular() ) {

		if ( 'llms_quiz' === $post_type ) {

			$quiz_id = llms_is_quiz_accessible( $post_id, $user_id );
			if ( $quiz_id ) {

				$results['is_restricted']  = true;
				$results['reason']         = 'quiz';
				$results['restriction_id'] = $post_id;
				/* This filter is documented above. */
				return apply_filters( 'llms_page_restricted', $results, $post_id );

			}
		}

		if ( 'lesson' === $post_type || 'llms_quiz' === $post_type ) {
			$course_id = llms_is_post_restricted_by_time_period( $post_id, $user_id );
			if ( $course_id ) {
				if ( 'lesson' === $post_type ) {
					$lesson = new LLMS_Lesson( $post_id );
				}

				// If the lesson is dripped based on enrollment, we don't want to restrict it based on course time period.
				if ( 'lesson' !== $post_type || ( $lesson && 'enrollment' !== $lesson->get( 'drip_method' ) ) ) {
					$results['is_restricted']  = true;
					$results['reason']         = 'course_time_period';
					$results['restriction_id'] = $course_id;
					/* This filter is documented above. */
					return apply_filters( 'llms_page_restricted', $results, $post_id );
				}
			}

			$lesson_id = llms_is_post_restricted_by_drip_settings( $post_id, $user_id );
			if ( $lesson_id ) {

				$results['is_restricted']  = true;
				$results['reason']         = 'lesson_drip';
				$results['restriction_id'] = $lesson_id;
				/* This filter is documented above. */
				return apply_filters( 'llms_page_restricted', $results, $post_id );
			}

			$prereq_data = llms_is_post_restricted_by_prerequisite( $post_id, $user_id );
			if ( $prereq_data ) {

				$results['is_restricted']  = true;
				$results['reason']         = sprintf( '%s_prerequisite', $prereq_data['type'] );
				$results['restriction_id'] = $prereq_data['id'];
				/* This filter is documented above. */
				return apply_filters( 'llms_page_restricted', $results, $post_id );
			}
		}
	}

	/* This filter is documented above. */
	return apply_filters( 'llms_page_restricted', $results, $post_id );
}

/**
 * Retrieve a message describing the reason why content is restricted.
 * Accepts an associative array of restriction data that can be retrieved from llms_page_restricted().
 *
 * This function doesn't handle all restriction types but it should in the future.
 * Currently it's being utilized for tooltips on lesson previews and some messages
 * output during LLMS_Template_Loader handling redirects.
 *
 * @since 3.2.4
 * @since 3.16.12 Unknown.
 *
 * @param array $restriction Array of data from `llms_page_restricted()`.
 * @return string
 */
function llms_get_restriction_message( $restriction ) {

	$msg = __( 'You do not have permission to access this content', 'lifterlms' );

	switch ( $restriction['reason'] ) {

		case 'course_prerequisite':
			$lesson      = new LLMS_Lesson( $restriction['content_id'] );
			$course_id   = $restriction['restriction_id'];
			$prereq_link = '<a href="' . get_permalink( $course_id ) . '">' . get_the_title( $course_id ) . '</a>';
			$msg         = sprintf(
				/* Translators: %$1s = lesson title; %2$s link of the course prerequisite */
				_x(
					'The lesson "%1$s" cannot be accessed until the required prerequisite course "%2$s" is completed.',
					'restricted by course prerequisite message',
					'lifterlms'
				),
				$lesson->get( 'title' ),
				$prereq_link
			);
			break;

		case 'course_track_prerequisite':
			$lesson      = new LLMS_Lesson( $restriction['content_id'] );
			$track       = new LLMS_Track( $restriction['restriction_id'] );
			$prereq_link = '<a href="' . $track->get_permalink() . '">' . $track->term->name . '</a>';
			$msg         = sprintf(
				/* Translators: %$1s = lesson title; %2$s link of the track prerequisite */
				_x(
					'The lesson "%1$s" cannot be accessed until the required prerequisite track "%2$s" is completed.',
					'restricted by course track prerequisite message',
					'lifterlms'
				),
				$lesson->get( 'title' ),
				$prereq_link
			);
			break;

		// this particular case is only utilized by lessons, courses do the check differently in the template.
		case 'course_time_period':
			$course = new LLMS_Course( $restriction['restriction_id'] );
			// if the start date hasn't passed yet.
			if ( ! $course->has_date_passed( 'start_date' ) ) {
				$msg = $course->get( 'course_opens_message' );
			} elseif ( $course->has_date_passed( 'end_date' ) ) {
				$msg = $course->get( 'course_closed_message' );
			}
			break;

		case 'enrollment_lesson':
			$course = new LLMS_Course( $restriction['restriction_id'] );
			$msg    = $course->get( 'content_restricted_message' );
			break;

		case 'lesson_drip':
			$lesson = new LLMS_Lesson( $restriction['restriction_id'] );
			$msg    = sprintf(
				/* Translators: %$1s = lesson title; %2$s available date */
				_x(
					'The lesson "%1$s" will be available on %2$s',
					'lesson restricted by drip settings message',
					'lifterlms'
				),
				$lesson->get( 'title' ),
				$lesson->get_available_date()
			);
			break;

		case 'lesson_prerequisite':
			$lesson        = new LLMS_Lesson( $restriction['content_id'] );
			$prereq_lesson = new LLMS_Lesson( $restriction['restriction_id'] );
			$prereq_link   = '<a href="' . get_permalink( $prereq_lesson->get( 'id' ) ) . '">' . $prereq_lesson->get( 'title' ) . '</a>';
			$msg           = sprintf(
				/* Translators: %$1s = lesson title; %2$s link of the lesson prerequisite */
				_x(
					'The lesson "%1$s" cannot be accessed until the required prerequisite "%2$s" is completed.',
					'lesson restricted by prerequisite message',
					'lifterlms'
				),
				$lesson->get( 'title' ),
				$prereq_link
			);
			break;

		default:
	}

	/**
	 * Allow filtering the restriction message.
	 *
	 * @since Unknown
	 *
	 * @param string $msg         Restriction message.
	 * @param array  $restriction Array of data from `llms_page_restricted()`.
	 */
	return apply_filters( 'llms_get_restriction_message', do_shortcode( $msg ), $restriction );
}

/**
 * Get a boolean out of llms_page_restricted for easy if checks.
 *
 * @since 3.0.0
 * @since 3.37.10 Made `$user_id` parameter optional. Default is `null`.
 *
 * @param int      $post_id WordPress Post ID of the content.
 * @param int|null $user_id Optional. WP User ID (will use get_current_user_id() if none supplied). Default `null`.
 * @return bool
 */
function llms_is_page_restricted( $post_id, $user_id = null ) {
	$restrictions = llms_page_restricted( $post_id, $user_id );
	return $restrictions['is_restricted'];
}

/**
 * Determine if a lesson/quiz is restricted by drip settings.
 *
 * @since 3.0.0
 * @since 3.16.11 Unknown.
 * @since 3.37.10 Use strict comparison '===' in place of '=='.
 * @since 6.5.0 Improve code readability turning if-elseif into a switch-case.
 *                Bypass drip content restriction on already completed lessons.
 *
 * @param int      $post_id WP Post ID of a lesson or quiz.
 * @param int|null $user_id Optional. WP User ID (will use get_current_user_id() if none supplied). Default `null`.
 * @return int|false False if the lesson is available.
 *                   WP Post ID of the lesson if it is not.
 */
function llms_is_post_restricted_by_drip_settings( $post_id, $user_id = null ) {

	$post_type = get_post_type( $post_id );

	switch ( $post_type ) {
		// If we're on a lesson, lesson id is the post id.
		case 'lesson':
			$lesson_id = $post_id;
			break;
		case 'llms_quiz':
			$quiz      = llms_get_post( $post_id );
			$lesson_id = $quiz->get( 'lesson_id' );
			if ( ! $lesson_id ) {
				return false;
			}
			break;
		default: // Don't pass other post types.
			return false;
	}

	$lesson  = new LLMS_Lesson( $lesson_id );
	$user_id = $user_id ?? get_current_user_id();
	/**
	 * Filters whether or not to bypass drip restrictions on completed lessons.
	 *
	 * @since 6.5.0
	 *
	 * @param boolean $drip_bypass Whether or not to bypass drip restrictions on completed lessons.
	 * @param int     $post_id     WP Post ID of a lesson or quiz potentially restricted by drip settings.
	 * @param int     $user_id     WP User ID.
	 */
	$drip_bypass  = apply_filters( 'llms_lesson_drip_bypass_if_completed', true, $post_id, $user_id );
	$is_available = ( $drip_bypass && $user_id && llms_is_complete( $user_id, $lesson_id, 'lesson' ) ) || $lesson->is_available();

	return $is_available ? false : $lesson_id;
}

/**
 * Determine if a lesson/quiz is restricted by a prerequisite lesson.
 *
 * @since 3.0.0
 * @since 3.16.11 Unknown.
 * @since 6.5.0 Improve code readability turning if-elseif into a switch-case.
 *
 * @param int      $post_id WP Post ID of a lesson or quiz.
 * @param int|null $user_id Optional. WP User ID (will use get_current_user_id() if none supplied). Default `null`.
 * @return array|false False if the post is not restricted or the user has completed the prereq
 *                     associative array with prereq type and prereq id
 *                     array(
 *                         type => [course|course_track|lesson]
 *                         id => int (object id)
 *                     ).
 */
function llms_is_post_restricted_by_prerequisite( $post_id, $user_id = null ) {

	$post_type = get_post_type( $post_id );

	switch ( $post_type ) {
		// If we're on a lesson, lesson id is the post id.
		case 'lesson':
			$lesson_id = $post_id;
			break;
		case 'llms_quiz':
			$quiz      = llms_get_post( $post_id );
			$lesson_id = $quiz->get( 'lesson_id' );
			if ( ! $lesson_id ) {
				return false;
			}
			break;
		default: // Don't pass other post types.
			return false;
	}

	$lesson = llms_get_post( $lesson_id );
	$course = $lesson->get_course();

	if ( ! $course ) {
		return false;
	}

	// Get an array of all possible prerequisites.
	$prerequisites = array();

	if ( $course->has_prerequisite( 'course' ) ) {
		$prerequisites[] = array(
			'id'   => $course->get_prerequisite_id( 'course' ),
			'type' => 'course',
		);
	}

	if ( $course->has_prerequisite( 'course_track' ) ) {
		$prerequisites[] = array(
			'id'   => $course->get_prerequisite_id( 'course_track' ),
			'type' => 'course_track',
		);
	}

	if ( $lesson->has_prerequisite() ) {
		$prerequisites[] = array(
			'id'   => $lesson->get_prerequisite(),
			'type' => 'lesson',
		);
	}

	// Prerequisites exist and user is not logged in, return the first prereq id.
	if ( $prerequisites && ! $user_id ) {

		return array_shift( $prerequisites );

		// If incomplete, send the prereq id.
	} else {

		$student = new LLMS_Student( $user_id );
		foreach ( $prerequisites as $prereq ) {
			if ( ! $student->is_complete( $prereq['id'], $prereq['type'] ) ) {
				return $prereq;
			}
		}
	}

	// Otherwise return false: no prerequisite.
	return false;
}

/**
 * Determine if a course (or lesson/quiz) is "open" according to course time period settings.
 *
 * @since 3.0.0
 * @since 3.16.11 Unknown.
 * @since 5.7.0 Replaced the call to the deprecated `LLMS_Lesson::get_parent_course()` method with `LLMS_Lesson::get( 'parent_course' )`.
 * @since 6.5.0 Improve code readability turning if-elseif into a switch-case.
 *
 * @param int      $post_id WP Post ID of a course, lesson, or quiz.
 * @param int|null $user_id Optional. WP User ID (will use get_current_user_id() if none supplied). Default `null`.
 * @return int|false False if the post is not restricted by course time period,
 *                   WP Post ID of the course if it is.
 */
function llms_is_post_restricted_by_time_period( $post_id, $user_id = null ) {

	$post_type = get_post_type( $post_id );

	switch ( $post_type ) {
		// If we're on a lesson, get course information.
		case 'lesson':
			$lesson    = new LLMS_Lesson( $post_id );
			$course_id = $lesson->get( 'parent_course' );
			break;
		case 'llms_quiz':
			$quiz      = llms_get_post( $post_id );
			$lesson_id = $quiz->get( 'lesson_id' );
			if ( ! $lesson_id ) {
				return false;
			}
			$lesson = llms_get_post( $lesson_id );
			if ( ! $lesson_id ) {
				return false;
			}
			$course_id = $lesson->get( 'parent_course' );
			break;
		case 'course':
			$course_id = $post_id;
			break;
		default: // Don't pass other post types.
			return false;
	}

	$course = new LLMS_Course( $course_id );

	return $course->is_open() ? false : $course_id;
}

/**
 * Determine if a WordPress post (of any type) is restricted to at least one LifterLMS Membership level.
 *
 * This function replaces the now deprecated page_restricted_by_membership() (and has slightly different functionality).
 *
 * @since 3.0.0
 * @since 3.16.14 Unknown.
 * @since 3.37.10 Call `in_array()` with strict comparison.
 *
 * @param int      $post_id WP_Post ID.
 * @param int|null $user_id Optional. WP User ID (will use get_current_user_id() if none supplied). Default `null`.
 * @return bool|int WP_Post ID of the membership if a restriction is found.
 *                  False if no restrictions found.
 */
function llms_is_post_restricted_by_membership( $post_id, $user_id = null ) {

	// don't check these posts types.
	$skip = apply_filters(
		'llms_is_post_restricted_by_membership_skip_post_types',
		array(
			'course',
			'lesson',
			'llms_quiz',
			'llms_membership',
			'llms_question',
			'llms_certificate',
			'llms_my_certificate',
		)
	);

	if ( in_array( get_post_type( $post_id ), $skip, true ) ) {
		return false;
	}

	$memberships = get_post_meta( $post_id, '_llms_restricted_levels', true );
	$restricted  = get_post_meta( $post_id, '_llms_is_restricted', true );

	if ( 'yes' === $restricted && $memberships && is_array( $memberships ) ) {

		// if no user, return the first membership from the array as the restriction id.
		if ( ! $user_id ) {

			$restriction_id = array_shift( $memberships );

		} else {

			$student = llms_get_student( $user_id );
			if ( ! $student ) {

				$restriction_id = array_shift( $memberships );

			} else {

				// reverse so to ensure that if user is in none of the memberships,
				// they'd encounter the same restriction settings as a visitor.
				$memberships = array_reverse( $memberships );

				// loop through the memberships.
				foreach ( $memberships as $mid ) {

					// set this as the restriction id.
					$restriction_id = $mid;

					// once we find the student has access break the loop,
					// this will be the restriction that the template loader will check against later.
					if ( $student->is_enrolled( $mid ) ) {
						break;
					}
				}
			}
		}

		return absint( $restriction_id );

	}

	return false;
}

/**
 * Determine if a post should bypass sitewide membership restrictions.
 *
 * If sitewide membership restriction is disabled, this will always return false.
 * This function replaces the now deprecated site_restricted_by_membership() (and has slightly different functionality).
 *
 * @since 3.0.0
 * @since 3.37.10 Do not apply membership restrictions on the page set as membership's restriction redirect page.
 *                  Exclude the privacy policy from the sitewide restriction.
 *                  Call `in_array()` with strict comparison.
 *
 * @param int      $post_id WP Post ID.
 * @param int|null $user_id Optional. WP User ID (will use get_current_user_id() if none supplied). Default `null`.
 * @return bool|int If the post is not restricted (or there are not sitewide membership restrictions) returns false.
 *                  If the post is restricted, returns the membership id required.
 */
function llms_is_post_restricted_by_sitewide_membership( $post_id, $user_id = null ) {

	$membership_id = absint( get_option( 'lifterlms_membership_required', '' ) );

	// site is restricted to a membership.
	if ( ! empty( $membership_id ) ) {

		$membership = new LLMS_Membership( $membership_id );

		if ( ! $membership || ! is_a( $membership, 'LLMS_Membership' ) ) {
			return false;
		}

		// Restricted contents redirection page id, if any.
		$redirect_page_id = 'page' === $membership->get( 'restriction_redirect_type' ) ? absint( $membership->get( 'redirect_page_id' ) ) : 0;

		/**
		 * Pages that can be bypassed when sitewide restrictions are enabled.
		 */
		$allowed = apply_filters(
			'lifterlms_sitewide_restriction_bypass_ids',
			array_filter(
				array(
					absint( $membership_id ), // the membership page the site is restricted to.
					absint( get_option( 'lifterlms_terms_page_id' ) ), // terms and conditions.
					llms_get_page_id( 'memberships' ), // membership archives.
					llms_get_page_id( 'myaccount' ), // lifterlms account page.
					llms_get_page_id( 'checkout' ), // lifterlms checkout page.
					absint( get_option( 'wp_page_for_privacy_policy' ) ), // wp privacy policy page.
					$redirect_page_id, // Restricted contents redirection page id.
				)
			)
		);

		if ( in_array( $post_id, $allowed, true ) ) {
			return false;
		}

		return $membership_id;

	} else {

		return false;

	}
}

/**
 * Determine if a quiz should be accessible by a user.
 *
 * @since 3.1.6
 * @since 3.16.1 Unknown.
 *
 * @param int      $post_id WP Post ID.
 * @param int|null $user_id Optional. WP User ID (will use get_current_user_id() if none supplied). Default `null`.
 * @return bool|int If the post is not restricted returns false.
 *                  If the post is restricted, returns the quiz id.
 */
function llms_is_quiz_accessible( $post_id, $user_id = null ) {

	$quiz      = llms_get_post( $post_id );
	$lesson_id = $quiz->get( 'lesson_id' );

	// No lesson or the user is not enrolled.
	if ( ! $lesson_id || ! llms_is_user_enrolled( $user_id, $lesson_id ) ) {
		return $post_id;
	}

	return false;
}
