<?php
/**
 * Person Functions
 *
 * Functions for managing users in the LifterLMS system.
 *
 * @package LifterLMS/Functions
 *
 * @since 1.0.0
 * @version 5.9.0
 */

defined( 'ABSPATH' ) || exit;

/**
 * Determines whether or not a user can bypass enrollment, drip, and prerequisite restrictions.
 *
 * @since 3.7.0
 * @since 3.9.0 Unknown.
 * @since 5.9.0 Added optional second parameter `$post_id`.
 *
 * @param LLMS_Student|WP_User|int $user    LLMS_Student, WP_User, or WP User ID, if none supplied get_current_user() will be used.
 * @param integer                  $post_id A WP_Post ID to check permissions against. If supplied, in addition to the user's role
 *                                          being allowed to bypass the restrictions, the user must also have `edit_post` capabilities
 *                                          for the requested post.
 * @return boolean
 */
function llms_can_user_bypass_restrictions( $user = null, $post_id = null ) {

	$user = llms_get_student( $user );

	if ( ! $user ) {
		return false;
	}

	$roles = get_option( 'llms_grant_site_access', '' );
	if ( ! $roles ) {
		$roles = array();
	}

	if ( ! array_intersect( $user->get_user()->roles, $roles ) ) {
		return false;
	}

	if ( $post_id && ! user_can( $user->get( 'id' ), 'edit_post', $post_id ) ) {
		return false;
	}

	return true;

}

/**
 * Checks LifterLMS user capabilities against an object
 *
 * @since 3.13.0
 * @since 4.5.0 Use strict array comparison.
 *
 * @param string $cap    Capability name.
 * @param int    $obj_id WP_Post or WP_User ID.
 * @return boolean
 */
function llms_current_user_can( $cap, $obj_id = null ) {

	$caps  = LLMS_Roles::get_all_core_caps();
	$grant = false;

	if ( in_array( $cap, $caps, true ) ) {

		// If the user has the cap, maybe do some additional checks.
		if ( current_user_can( $cap ) ) {

			switch ( $cap ) {

				case 'view_lifterlms_reports':
					// Can view others reports so its okay.
					if ( current_user_can( 'view_others_lifterlms_reports' ) ) {
						$grant = true;

						// Can only view their own reports check if the student is their instructor.
					} elseif ( $obj_id ) {

						$instructor = llms_get_instructor();
						$student    = llms_get_student( $obj_id );
						if ( $instructor && $student ) {
							foreach ( $instructor->get_posts(
								array(
									'posts_per_page' => -1,
								),
								'ids'
							) as $id ) {
								if ( $student->get_enrollment_status( $id ) ) {
									$grant = true;
									break;
								}
							}
						}
					}

					break;

				// No other checks needed.
				default:
					$grant = true;

			}
		}
	}

	/**
	 * Filters whether or not the current user can perform the requested action
	 *
	 * The dynamic portion of this hook, `$cap`, refers to the requested user capability.
	 *
	 * @since 3.13.0
	 *
	 * @param boolean $grant Whether or not the requested capability is granted to the user.
	 * @param int     $obj_id WP_Post or WP_User ID.
	 */
	return apply_filters( "llms_current_user_can_{$cap}", $grant, $obj_id );

}

/**
 * Delete LifterLMS Student's Enrollment record related to a given product.
 *
 * @since 3.33.0
 *
 * @see `LLMS_Student->delete_enrollment()` the class method wrapped by this function.
 *
 * @param int    $user_id    WP User ID.
 * @param int    $product_id WP Post ID of the Course or Membership.
 * @param string $trigger    Optional. Only delete the student enrollment if the original enrollment trigger matches the submitted value.
 *                           Passing "any" will remove regardless of enrollment trigger.
 * @return boolean Whether or not the enrollment records have been successfully removed.
 */
function llms_delete_student_enrollment( $user_id, $product_id, $trigger = 'any' ) {
	$student = new LLMS_Student( $user_id );
	return $student->delete_enrollment( $product_id, $trigger );
}

/**
 * Disables admin bar on front end
 *
 * @since 1.0.0
 * @since 3.27.0 Unknown
 *
 * @param bool $show_admin_bar default value (true).
 * @return bool
 */
function llms_disable_admin_bar( $show_admin_bar ) {
	/**
	 * Filter whether or not the WP Admin Bar is disabled for users
	 *
	 * By default, the admin bar is disabled for all users except those with the `edit_posts` or `manage_lifterlms` capabilities.
	 *
	 * @since Unknown
	 *
	 * @param boolean $disabled Whether or not the admin bar should be disabled.
	 */
	if ( apply_filters( 'lifterlms_disable_admin_bar', true ) && ! ( current_user_can( 'edit_posts' ) || current_user_can( 'manage_lifterlms' ) ) ) {
		$show_admin_bar = false;
	}
	return $show_admin_bar;
}
add_filter( 'show_admin_bar', 'llms_disable_admin_bar', 10 );


/**
 * Enroll a WordPress user in a course or membership
 *
 * @since 2.2.3
 * @since 3.0.0 Added `$trigger` parameter.
 *
 * @see LLMS_Student->enroll() the class method wrapped by this function
 *
 * @param int    $user_id    WP User ID.
 * @param int    $product_id WP Post ID of the Course or Membership.
 * @param string $trigger    String describing the event that triggered the enrollment.
 * @return boolean
 */
function llms_enroll_student( $user_id, $product_id, $trigger = 'unspecified' ) {
	$student = new LLMS_Student( $user_id );
	return $student->enroll( $product_id, $trigger );
}

/**
 * Get an LLMS_Instructor
 *
 * @since 3.13.0
 *
 * @param mixed $user WP_User ID, instance of WP_User, or instance of any instructor class extending this class.
 * @return LLMS_Instructor|false LLMS_Instructor instance on success, false if user not found
 */
function llms_get_instructor( $user = null ) {
	$student = new LLMS_Instructor( $user );
	return $student->exists() ? $student : false;
}

/**
 * Retrieve the translated name of minimum accepted password strength for student passwords
 *
 * @since 3.0.0
 * @since 5.0.0 Remove database call to deprecated option and add the $strength parameter.
 *
 * @param string $strength Optional. Password strength value to translate. Default is 'strong'.
 * @return string
 */
function llms_get_minimum_password_strength_name( $strength = 'strong' ) {

	$opts = array(
		'strong'    => __( 'strong', 'lifterlms' ),
		'medium'    => __( 'medium', 'lifterlms' ),
		'weak'      => __( 'weak', 'lifterlms' ),
		'very-weak' => __( 'very weak', 'lifterlms' ),
	);

	$name = isset( $opts[ $strength ] ) ? $opts[ $strength ] : $strength;

	/**
	 * Filter the name of the password strength
	 *
	 * The dynamic portion of this hook, `$strength`, can be either "strong", "medium", "weak" or "very-weak".
	 *
	 * @since 5.0.0
	 *
	 * @param $string $name Translated name of the password strength value.
	 */
	return apply_filters( 'llms_get_minimum_password_strength_name_' . $strength, $name );

}

/**
 * Get an LLMS_Student
 *
 * @since 3.8.0
 * @since 3.9.0 Unknown
 *
 * @param mixed $user  WP_User ID, instance of WP_User, or instance of any student class extending this class.
 * @return LLMS_Student|false LLMS_Student instance on success, false if user not found.
 */
function llms_get_student( $user = null ) {
	$student = new LLMS_Student( $user );
	return $student->exists() ? $student : false;
}

/**
 * Retrieve a list of disallowed usernames.
 *
 * @since 5.0.0
 *
 * @return string[]
 */
function llms_get_usernames_blocklist() {

	$list = array( 'admin', 'test', 'administrator', 'password', 'testing' );

	/**
	 * Deprecated filter.
	 *
	 * @since Unknown
	 * @deprecated 5.0.0 Filter `llms_usernames_blacklist` is deprecated, use `llms_usernames_blocklist` instead.
	 *
	 * @param string[] $list List of banned usernames.
	 */
	$list = apply_filters_deprecated( 'llms_usernames_blacklist', array( $list ), '5.0.0', 'llms_usernames_blocklist' );

	/**
	 * Modify the list of disallowed usernames
	 *
	 * If a user attempts to create a new account with any username found in this list they will receive an error and will not
	 * be able to register the account.
	 *
	 * @since 5.0.0
	 *
	 * @param string[] $list List of banned usernames.
	 */
	return apply_filters( 'llms_usernames_blocklist', $list );

}

/**
 * Checks if user is currently enrolled in cours
 *
 * @since Unknown
 * @since 3.3.1 Updated to use `LLMS_Student->is_enrolled()`.
 *
 * @see LLMS_Student->is_complete()
 *
 * @param int $user_id      WP User ID of the user.
 * @param int $object_id    WP Post ID of a Course, Section, or Lesson.
 * @param int $object_type  Type, either Course, Section, or Lesson.
 * @return boolean Returns `true` if complete, otherwise `false`.
 */
function llms_is_complete( $user_id, $object_id, $object_type = 'course' ) {
	$s = new LLMS_Student( $user_id );
	return $s->is_complete( $object_id, $object_type );
}

/**
 * Checks if user is currently enrolled in course
 *
 * @since Unknown
 * @since 3.25.0 Unknown.
 *
 * @see LLMS_Student->is_enrolled()
 *
 * @param int       $user_id    WP_User ID.
 * @param int|int[] $product_id WP Post ID of a Course, Lesson, or Membership or array of multiple IDs.
 * @param string    $relation   Comparator for enrollment check.
 *                              All = user must be enrolled in all $product_ids.
 *                              Any = user must be enrolled in at least one of the $product_ids.
 * @param bool      $use_cache  If true, returns cached data if available, if false will run a db query.
 * @return boolean
 */
function llms_is_user_enrolled( $user_id, $product_id, $relation = 'all', $use_cache = true ) {
	$student = new LLMS_Student( $user_id );
	return $student->is_enrolled( $product_id, $relation, $use_cache );
}

/**
 * Mark a lesson, section, course, or track as complete
 *
 * @since 3.3.1
 *
 * @see LLMS_Student->mark_complete()
 *
 * @param int    $user_id     WP User ID.
 * @param int    $object_id   WP Post ID of the Lesson, Section, Track, or Course.
 * @param int    $object_type Object type [lesson|section|course|track].
 * @param string $trigger     String describing the event that triggered marking the object as complete.
 * @return boolean
 */
function llms_mark_complete( $user_id, $object_id, $object_type, $trigger = 'unspecified' ) {
	$student = new LLMS_Student( $user_id );
	return $student->mark_complete( $object_id, $object_type, $trigger );
}

/**
 * Mark a lesson, section, course, or track as incomplete
 *
 * @since 3.5.0
 *
 * @see LLMS_Student->mark_incomplete()
 *
 * @param int    $user_id     WP User ID.
 * @param int    $object_id   WP Post ID of the Lesson, Section, Track, or Course.
 * @param int    $object_type Object type [lesson|section|course|track].
 * @param string $trigger     String describing the event that triggered marking the object as incomplete.
 * @return boolean
 */
function llms_mark_incomplete( $user_id, $object_id, $object_type, $trigger = 'unspecified' ) {
	$student = new LLMS_Student( $user_id );
	return $student->mark_incomplete( $object_id, $object_type, $trigger );
}

/**
 * Parses the password reset cookie.
 *
 * This is the cookie set when a user uses the password reset link found in a reset password email. The query string
 * vars in the link (user login and reset key) are parsed and stored in this cookie.
 *
 * @since 5.0.0
 * @since 5.1.2 Fixed typos in error messages.
 *
 * @return array|WP_Error On success, returns an associative array containing the keys "key" and "login", on error
 *                        returns a WP_Error.
 */
function llms_parse_password_reset_cookie() {

	if ( ! isset( $_COOKIE[ 'wp-resetpass-' . COOKIEHASH ] ) ) {
		return new WP_Error( 'llms_password_reset_no_cookie', __( 'The password reset key could not be found. Please reset your password again if needed.', 'lifterlms' ) );
	}

	$parsed = array_map( 'sanitize_text_field', explode( ':', wp_unslash( $_COOKIE[ 'wp-resetpass-' . COOKIEHASH ] ), 2 ) );  // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
	if ( 2 !== count( $parsed ) ) {
		return new WP_Error( 'llms_password_reset_invalid_cookie', __( 'The password reset key is in an invalid format. Please reset your password again if needed.', 'lifterlms' ) );
	}

	$uid = $parsed[0];
	$key = $parsed[1];

	$user  = get_user_by( 'ID', $uid );
	$login = $user ? $user->user_login : '';
	$user  = check_password_reset_key( $key, $login );

	if ( is_wp_error( $user ) ) {
		// Error code is either "llms_password_reset_invalid_key" or "llms_password_reset_expired_key".
		return new WP_Error( sprintf( 'llms_password_reset_%s', $user->get_error_code() ), __( 'This password reset key is invalid or has already been used. Please reset your password again if needed.', 'lifterlms' ) );
	}

	// Success.
	return compact( 'key', 'login' );

}

/**
 * Register a new user
 *
 * @since 3.0.0
 * @since 5.0.0 Use `LLMS_Form_Handler()` for registration.
 *
 * @param array  $data   Array of registration data.
 * @param string $screen The screen to be used for the validation template, accepts "registration" or "checkout"
 * @param bool   $signon If true, signon the newly created user
 * @param array  $args   Additional arguments passed to the short-circuit filter.
 * @return int|WP_Error
 */
function llms_register_user( $data = array(), $screen = 'registration', $signon = true, $args = array() ) {

	$user_id = LLMS_Form_Handler::instance()->submit( $data, $screen, $args );

	if ( is_wp_error( $user_id ) ) {
		return $user_id;
	}

	// Signon.
	if ( $signon && ! empty( $data['password'] ) ) {

		$user = get_user_by( 'ID', $user_id );

		/**
		 * Filters whether or not a new user should be "remembered" when signing on during account creation
		 *
		 * @since 5.0.0
		 *
		 * @param boolean $remember If `true` (default), the user signon will be set to "remember".
		 * @param string  $screen   Current validation template, either "registration" or "checkout".
		 * @param WP_User $user     User object for the newly registered user.
		 */
		$remember = apply_filters( 'llms_user_registration_remember', true, $screen, $user );

		wp_signon(
			array(
				'user_login'    => $user->user_login,
				'user_password' => $data['password'],
				'remember'      => $remember,
			),
			is_ssl()
		);

	}

	return $user_id;

}

/**
 * Set or unset a user's password reset cookie.
 *
 * @since 5.0.0
 *
 * @param string $val Cookie value.
 * @return boolean
 */
function llms_set_password_reset_cookie( $val = '' ) {

	$cookie  = sprintf( 'wp-resetpass-%s', COOKIEHASH );
	$expires = $val ? 0 : time() - YEAR_IN_SECONDS;
	$path    = isset( $_SERVER['REQUEST_URI'] ) ? current( explode( '?', wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized

	return llms_setcookie( $cookie, $val, $expires, $path, COOKIE_DOMAIN, is_ssl(), true );

}

/**
 * Sets user auth cookie by id and records the date/time of the login in the usermeta table
 *
 * @since  Unknown
 * @since  3.0.0 Use `wp_set_current_user()` rather than overriding the global manually.
 * @since  3.36.0 Pass the `$remember` param to `wp_set_auth_cookie()`.
 * @deprecated 4.5.0 Use WP core methods such as `wp_signon()`, `wp_set_current_user()`, and/or `wp_set_auth_cookie()`.
 *
 * @param int  $user_id  WP_User ID.
 * @param bool $remember Whether to remember the user.
 * @return void
 */
function llms_set_person_auth_cookie( $user_id, $remember = false ) {
	llms_deprecated_function( 'llms_set_person_auth_cookie', '4.5.0' );
	wp_set_current_user( $user_id );
	wp_set_auth_cookie( $user_id, $remember );
	update_user_meta( $user_id, 'llms_last_login', current_time( 'mysql' ) );
}

/**
 * Set/Update user login time
 *
 * @since 4.5.0
 *
 * @param string  $user_login Username.
 * @param WP_User $user       WP_User object of the logged-in user.
 * @return void
 */
function llms_set_user_login_time( $user_login, $user ) {
	update_user_meta( $user->ID, 'llms_last_login', llms_current_time( 'mysql' ) );
}
add_action( 'wp_login', 'llms_set_user_login_time', 10, 2 );

/**
 * Remove a LifterLMS Student from a course or membership
 *
 * @since 3.0.0
 *
 * @see LLMS_Student->unenroll() the class method wrapped by this function
 *
 * @param int    $user_id     WP User ID.
 * @param int    $product_id  WP Post ID of the Course or Membership.
 * @param string $new_status  The value to update the new status with after removal is complete.
 * @param string $trigger     Only remove the student if the original enrollment trigger matches the submitted value.
 *                            Passing "any" will remove regardless of enrollment trigger.
 * @return boolean
 */
function llms_unenroll_student( $user_id, $product_id, $new_status = 'expired', $trigger = 'any' ) {
	$student = new LLMS_Student( $user_id );
	return $student->unenroll( $product_id, $trigger, $new_status );
}

/**
 * Performs validations for the user.
 *
 * @since 3.0.0
 * @since 3.7.0 Unknown.
 * @since 5.0.0 Updated to utilize LLMS_Form_Handler class.
 *
 * @param array  $data Array of user data.
 * @param string $location (Optional) screen to perform validations for, accepts "account" or "checkout". Default value: 'account'
 * @param array  $args   Additional arguments passed to the short-circuit filter.
 * @return int|WP_Error WP_User ID on success or error object on failure.
 */
function llms_update_user( $data = array(), $location = 'account', $args = array() ) {
	return LLMS_Form_Handler::instance()->submit( $data, $location, $args );
}