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

use LearnDash\Core\Utilities\Cast;

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

// cspell:ignore prerequities .

/**
 * Gets the course ID for a resource.
 *
 * Determine the type of ID being passed. Should be the ID of
 * anything that belongs to a course (Lesson, Topic, Quiz, etc)
 *
 * @since 2.1.0
 * @since 2.5.0 Added the `$bypass_cb` parameter.
 *
 * @param  WP_Post|int|null $id        Optional. ID of the resource. Default null.
 * @param  boolean          $bypass_cb Optional. If true will bypass course_builder logic. Default false.
 *
 * @return int|bool ID of the course.
 */
function learndash_get_course_id( $id = null, $bypass_cb = false ) {
	if ( is_object( $id ) && $id->ID ) {
		$p  = $id;
		$id = $p->ID;
	} elseif ( is_numeric( $id ) ) {
		$p = get_post( $id );
	}

	if ( empty( $id ) ) {
		if ( ! defined( 'REST_REQUEST' ) ) {
			if ( is_admin() ) {
				global $parent_file, $post_type, $pagenow;
				if ( ( ! in_array( $pagenow, array( 'post.php', 'post-new.php' ), true ) ) || ( ! in_array( $post_type, learndash_get_post_types( 'course' ), true ) ) ) {
					return false;
				}
			} elseif ( ! is_single() || is_home() ) {
				return false;
			}
		}

		$post = get_post( get_the_id() );
		if ( ( $post ) && ( $post instanceof WP_Post ) ) {
			$id = $post->ID;
			$p  = $post;
		} else {
			return false;
		}
	}

	if ( empty( $p->ID ) ) {
		return 0;
	}

	if ( learndash_get_post_type_slug( 'course' ) === $p->post_type ) {
		return $p->ID;
	}

	// Somewhat a kludge. Here we try and assume the course_id being handled.
	if ( ( learndash_is_course_shared_steps_enabled() ) && ( false === $bypass_cb ) ) {
		if ( ! is_admin() ) {
			$course_slug = get_query_var( 'sfwd-courses' );
			if ( ! empty( $course_slug ) ) {
				$course_post = learndash_get_page_by_path( $course_slug, 'sfwd-courses' );
				if ( ( $course_post ) && ( $course_post instanceof WP_Post ) ) {
					return $course_post->ID;
				}
			}
		}

		if ( ( isset( $_GET['course_id'] ) ) && ( ! empty( $_GET['course_id'] ) ) ) {
			return absint( $_GET['course_id'] );
		} elseif ( ( isset( $_GET['course'] ) ) && ( ! empty( $_GET['course'] ) ) ) {
			return absint( $_GET['course'] );
		} elseif ( ( isset( $_POST['course_id'] ) ) && ( ! empty( $_POST['course_id'] ) ) ) {
			return absint( $_POST['course_id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- legacy code.
		} elseif ( ( isset( $_POST['course'] ) ) && ( ! empty( $_POST['course'] ) ) ) {
			return absint( $_POST['course'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- legacy code.
		} elseif ( ( isset( $_GET['post'] ) ) && ( ! empty( $_GET['post'] ) ) ) {
			if ( get_post_type( absint( $_GET['post'] ) ) == 'sfwd-courses' ) {
				return absint( $_GET['post'] );
			}
		}
	}

	if ( learndash_get_post_type_slug( LDLMS_Post_Types::EXAM ) === $p->post_type ) {
		return (int) get_post_meta( intval( $id ), 'exam_challenge_course_show', true );
	}

	return (int) get_post_meta( $id, 'course_id', true );
}

/**
 * Gets the lesson ID of a resource.
 *
 * @global WP_Post $post Global post object.
 *
 * @since 2.1.0
 *
 * @param int|null $post_id   Optional. ID of the resource. Default null.
 * @param int|null $course_id Optional. ID of the course. Default null.
 *
 * @return string Lesson ID.
 */
function learndash_get_lesson_id( $post_id = null, $course_id = null ) {
	$post_id   = absint( $post_id );
	$course_id = absint( $course_id );

	if ( empty( $post_id ) ) {
		$the_id = get_the_id();
		if ( empty( $the_id ) ) {
			return false;
		}
		$post = get_post( $the_id );
	} else {
		$post = get_post( $post_id );
	}

	if ( ( ! $post ) || ( ! is_a( $post, 'WP_Post' ) ) || ( ! in_array( $post->post_type, learndash_get_post_types( 'course' ), true ) ) ) {
		return false;
	}

	if ( learndash_get_post_type_slug( 'lesson' ) === $post->post_type ) {
		return $post->ID;
	}

	if ( learndash_is_course_shared_steps_enabled() ) {
		$lesson_slug = get_query_var( learndash_get_post_type_slug( 'lesson' ) );
		if ( ! empty( $lesson_slug ) ) {
			$lesson_post = learndash_get_page_by_path( $lesson_slug, learndash_get_post_type_slug( 'lesson' ) );
			if ( ( $lesson_post ) && ( is_a( $lesson_post, 'WP_Post' ) ) ) {
				return $lesson_post->ID;
			}
		} else {
			if ( empty( $course_id ) ) {
				$course_id = learndash_get_course_id( $post->ID );
			}

			if ( ! empty( $course_id ) ) {
				return learndash_course_get_single_parent_step( $course_id, $post->ID );
			}
		}
	} else {
		if ( in_array( $post->post_type, learndash_get_post_type_slug( array( 'topic', 'quiz' ) ), true ) ) {
			return get_post_meta( $post->ID, 'lesson_id', true );
		}
	}

	return '';
}

/**
 * Checks if the user's course prerequisites are completed for a given course.
 *
 * @since 3.2.0
 * @since 3.2.3 Added `$user_id` parameter.
 *
 * @param int $post_id Optional. The ID of the course. Default 0.
 * @param int $user_id Optional. The ID of the user. Default 0.
 *
 * @return boolean Returns true if the prerequisites are completed.
 */
function learndash_is_course_prerequities_completed( $post_id = 0, $user_id = 0 ) {
	$course_pre_complete = true;

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

	if ( ! empty( $post_id ) ) {
		$course_id = learndash_get_course_id( $post_id );
		if ( ( ! empty( $course_id ) ) && ( ! empty( $user_id ) ) && ( learndash_get_course_prerequisite_enabled( $course_id ) ) ) {
			$course_pre = learndash_get_course_prerequisites( $course_id, $user_id );
			if ( ! empty( $course_pre ) ) {
				$course_pre_compare = learndash_get_course_prerequisite_compare( $course_id );
				if ( 'ANY' === $course_pre_compare ) {
					$s_pre = array_search( true, $course_pre, true );
					if ( false !== $s_pre ) {
						$course_pre_complete = true;
					} else {
						$course_pre_complete = false;
					}
				} elseif ( 'ALL' === $course_pre_compare ) {
					$s_pre = array_search( false, $course_pre, true );
					if ( false === array_search( false, $course_pre, true ) ) {
						$course_pre_complete = true;
					} else {
						$course_pre_complete = false;
					}
				}
			}
		}
	}

	return $course_pre_complete;
}

/**
 * Gets the list of course prerequisites and its status for a course.
 *
 * @since 2.4.0
 * @since 3.2.3 Added `$user_id` parameter.
 *
 * @param int $post_id Optional. The ID of the course. Default 0.
 * @param int $user_id Optional. The ID of the user. Default 0.
 *
 * @return array An array of course prerequisites.
 */
function learndash_get_course_prerequisites( $post_id = 0, $user_id = 0 ) {
	$courses_status_array = array();

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

	if ( ! empty( $post_id ) ) {
		$course_id = learndash_get_course_id( $post_id );
		if ( ( ! empty( $course_id ) ) && ( ! empty( $user_id ) ) && ( learndash_get_course_prerequisite_enabled( $course_id ) ) ) {

			$course_pre = learndash_get_course_prerequisite( $course_id );
			if ( ! empty( $course_pre ) ) {
				$course_pre_compare = learndash_get_course_prerequisite_compare( $course_id );

				if ( is_string( $course_pre ) ) {
					$course_pre = array( $course_pre );
				}

				foreach ( $course_pre as $c_id ) {
					// Now check if the prerequisites course is completed by user or not.
					$course_status = learndash_course_status( $c_id, $user_id, true );
					if ( 'completed' === $course_status ) {
						$courses_status_array[ $c_id ] = true;
					} else {
						$courses_status_array[ $c_id ] = false;
					}
				}
			}
		}
	}
	return $courses_status_array;
}

/**
 * Gets the list of course prerequisites for a given course.
 *
 * @since 2.1.0
 *
 * @param int $course_id Optional. The ID if the course. Default 0.
 *
 * @return array An array of course prerequisite.
 */
function learndash_get_course_prerequisite( $course_id = 0 ) {
	$course_pre = array();

	if ( ! empty( $course_id ) ) {
		$transient_key        = 'learndash_course_pre_' . $course_id;
		$course_pre_transient = LDLMS_Transients::get( $transient_key );

		if ( false !== $course_pre_transient ) {
			$course_pre = (array) $course_pre_transient;
		} else {
			$course_pre = learndash_get_setting( $course_id, 'course_prerequisite' );
			if ( empty( $course_pre ) ) {
				$course_pre = array();
			}
			$course_pre = array_map( 'absint', $course_pre );
			$course_pre = array_diff( $course_pre, array( 0 ) ); // Removes zeros.
			if ( ! empty( $course_pre ) ) {
				$post_status = learndash_get_step_post_statuses();

				$course_pre_query_args = array(
					'post_type'   => learndash_get_post_type_slug( 'course' ),
					'nopaging'    => true,
					'post_status' => array_keys( $post_status ),
					'fields'      => 'ids',
					'post__in'    => $course_pre,
				);

				$course_pre_query = new WP_Query( $course_pre_query_args );
				if ( ( is_a( $course_pre_query, 'WP_Query' ) ) && ( property_exists( $course_pre_query, 'posts' ) ) && ( ! empty( $course_pre_query->posts ) ) ) {
					$course_pre = $course_pre_query->posts;
					LDLMS_Transients::set( $transient_key, $course_pre, HOUR_IN_SECONDS );
				}
			}
		}
	}

	return $course_pre;
}

/**
 * Sets new prerequisites for a course.
 *
 * @since 2.4.4
 *
 * @param int   $course_id  Optional. ID of the course. Default 0.
 * @param array $course_pre Optional. An array of course prerequisites. Default empty array.
 *
 * @return boolean Returns true if update was successful otherwise false.
 */
function learndash_set_course_prerequisite( $course_id = 0, $course_pre = array() ) {
	if ( ! empty( $course_id ) ) {
		if ( ( ! empty( $course_pre ) ) && ( is_array( $course_pre ) ) ) {
			$course_pre = array_unique( $course_pre );
		}

		$transient_key        = 'learndash_course_pre_' . $course_id;
		$course_pre_transient = LDLMS_Transients::delete( $transient_key );

		return learndash_update_setting( $course_id, 'course_prerequisite', $course_pre );
	}

	return false;
}

/**
 * Checks whether the course prerequisites requirement for enrollment setting is enabled for a course.
 *
 * @since 2.4.0
 * @since 4.20.2 Add support for the new requirements_for_enrollment setting key.
 *
 * @param int $course_id The ID of the course.
 *
 * @return boolean Returns true if the course prerequisites requirement for enrollment setting is enabled, otherwise false.
 */
function learndash_get_course_prerequisite_enabled( $course_id ) {
	if ( empty( $course_id ) ) {
		return false;
	}

	$course_id = learndash_get_course_id( $course_id );

	if ( ! is_int( $course_id ) ) {
		return false;
	}

	/**
	 * We need to check if the course prerequisites setting is not empty. If the course prerequisite requirement for enrollment
	 * setting is enabled, we also need to make sure that the courses for prerequisite setting is not empty.
	 */

	$course_pre_courses = learndash_get_setting( $course_id, 'course_prerequisite' );

	if (
		! is_array( $course_pre_courses )
		|| empty( $course_pre_courses )
	) {
		return false;
	}

	// New setting key to check if the course prerequisites setting is enabled.
	$requirements_for_enrollment = learndash_get_setting( $course_id, 'requirements_for_enrollment' );

	if ( $requirements_for_enrollment === 'course_prerequisite_enabled' ) {
		return true;
	}

	// Legacy setting key to check if the course prerequisites setting is enabled.
	$course_pre_enabled = learndash_get_setting( $course_id, 'course_prerequisite_enabled' );

	// We need to check the legacy key, the old setting key still might being used even after upgrading to LD core v4.20.0+.
	if ( $course_pre_enabled === 'on' ) {
		return true;
	}

	return false;
}

/**
 * Sets the status of whether the course prerequisite is enabled or disabled.
 *
 * @since 2.4.4
 *
 * @param int     $course_id The ID of the course.
 * @param boolean $enabled   Optional. The value is true to enable course prerequisites. Any other
 *                           value will disable course prerequisites. Default true.
 *
 * @return boolean Returns true if the status was updated successfully otherwise false.
 */
function learndash_set_course_prerequisite_enabled( $course_id, $enabled = true ) {
	if ( true === $enabled ) {
		$enabled = 'on';
	}

	if ( 'on' !== $enabled ) {
		$enabled = '';
	}

	return learndash_update_setting( $course_id, 'course_prerequisite_enabled', $enabled );
}

/**
 * Gets the prerequisites compare value for a course.
 *
 * @since 2.4.0
 *
 * @param int $post_id The ID of the course.
 *
 * @return 'ANY'|'ALL' The compare value for the prerequisite. Value can be 'ALL' or 'ANY' by default.
 */
function learndash_get_course_prerequisite_compare( $post_id ) {

	$course_pre_compare = 'ANY';

	if ( ! empty( $post_id ) ) {
		$course_id = learndash_get_course_id( $post_id );
		if ( ! empty( $course_id ) ) {
			$course_prerequisite_compare = learndash_get_setting( $course_id, 'course_prerequisite_compare' );
			if ( ( 'ANY' === $course_prerequisite_compare ) || ( 'ALL' === $course_prerequisite_compare ) ) {
				$course_pre_compare = $course_prerequisite_compare;
			}
		}
	}
	return $course_pre_compare;
}

/**
 * Checks if the course points requirement for enrollment setting is enabled for a course.
 *
 * @since 2.4.0
 * @since 4.20.2 Add support for the new requirements_for_enrollment setting key.
 *
 * @param int $post_id Optional. The course ID. Default 0.
 *
 * @return bool Returns true if the course points requirement for enrollment setting is enabled, otherwise false.
 */
function learndash_get_course_points_enabled( $post_id = 0 ) {
	if ( empty( $post_id ) ) {
		return false;
	}

	$course_id = learndash_get_course_id( $post_id );

	if ( ! is_int( $course_id ) ) {
		return false;
	}

	// New setting key to check if the course points setting is enabled.
	$requirements_for_enrollment = learndash_get_setting( $course_id, 'requirements_for_enrollment' );

	if ( $requirements_for_enrollment === 'course_points_enabled' ) {
		return true;
	}

	// Legacy setting key to check if the course points setting is enabled.
	$course_points_enabled = learndash_get_setting( $course_id, 'course_points_enabled' );

	// We check the legacy key because the old setting key still might being used even after upgrading to LD core v4.20.0+.
	if ( $course_points_enabled === 'on' ) {
		return true;
	}

	return false;
}

/**
 * Retrieves the course completion points setting of a course.
 *
 * @since 2.4.0
 * @since 4.20.2 The value is no longer dependent on the course points requirement for enrollment setting.
 *
 * @param int $post_id  Optional. Course Step or Course post ID. Default 0.
 * @param int $decimals Optional. Number of decimal places to round. Default 1.
 *
 * @return float|false Course completion points of a course if it exists, false otherwise.
 */
function learndash_get_course_points( $post_id = 0, $decimals = 1 ) {
	if ( empty( $post_id ) ) {
		return false;
	}

	$course_id = learndash_get_course_id( $post_id );

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

	$course_points = learndash_get_setting( $course_id, 'course_points' );

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

	return learndash_format_course_points( $course_points, $decimals );
}

/**
 * Retrieves the course points required to access a course.
 *
 * The value returned from this function is dependent on the course points requirement for enrollment setting. The requirement for enrollment setting must be set to "Course Points".
 *
 * @since 2.4.0
 *
 * @param int $post_id Optional. The ID of the course. Default 0.
 *
 * @return float|false Required course points to access a course, false otherwise.
 */
function learndash_get_course_points_access( $post_id = 0 ) {
	if ( empty( $post_id ) ) {
		return false;
	}

	$course_id = learndash_get_course_id( $post_id );

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

	if ( ! learndash_get_course_points_enabled( $course_id ) ) {
		return false;
	}

	return learndash_format_course_points( learndash_get_setting( $course_id, 'course_points_access' ) );
}

/**
 * Checks if a user can access course points.
 *
 * @since 2.4.0
 *
 * @param int $post_id The ID of the post.
 * @param int $user_id Optional. The ID of the user. Default 0.
 *
 * @return boolean Whether a user can access course points.
 */
function learndash_check_user_course_points_access( $post_id, $user_id = 0 ) {
	$user_can_access = true;

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

	if ( ! empty( $post_id ) ) {
		$course_id = learndash_get_course_id( $post_id );
		if ( ( ! empty( $course_id ) ) && ( ! empty( $user_id ) ) ) {
			if ( learndash_get_course_points_enabled( $course_id ) ) {
				$course_access_points = learndash_get_course_points_access( $course_id );

				if ( ! empty( $course_access_points ) ) {
					$user_course_points = learndash_get_user_course_points( $user_id );

					if ( floatval( $user_course_points ) >= floatval( $course_access_points ) ) {
						return true;
					} else {
						return false;
					}
				}
			}
		}
	}

	return true;
}

/**
 * Handles the actions to be made when the user joins a course.
 *
 * Fires on `wp` hook.
 * Redirects user to login URL, adds course access to user.
 *
 * @since 2.1.0
 */
function learndash_process_course_join() {
	$user_id = get_current_user_id();

	if ( ( isset( $_POST['course_join'] ) ) && ( isset( $_POST['course_id'] ) ) ) {
		$post_label_prefix = 'course';
		$post_id           = intval( $_POST['course_id'] );
		$post              = get_post( $post_id );
		if ( ( ! $post ) || ( ! is_a( $post, 'WP_Post' ) ) || ( learndash_get_post_type_slug( 'course' ) !== $post->post_type ) ) {
			return;
		}
	} elseif ( ( isset( $_POST['group_join'] ) ) && ( isset( $_POST['group_id'] ) ) ) {
		$post_label_prefix = 'group';
		$post_id           = intval( $_POST['group_id'] );
		$post              = get_post( $post_id );
		if ( ( ! $post ) || ( ! is_a( $post, 'WP_Post' ) ) || ( learndash_get_post_type_slug( 'group' ) !== $post->post_type ) ) {
			return;
		}
	} else {
		return;
	}

	if ( empty( $user_id ) ) {
		$login_url = wp_login_url( get_permalink( $post_id ) );

		/**
		 * Filters URL that a user should be redirected to after joining a course.
		 *
		 * @since 2.1.0
		 *
		 * @param string $login_url Redirect URL.
		 * @param int    $post_id Course or Group ID.
		 */
		$login_url = apply_filters( 'learndash_' . $post_label_prefix . '_join_redirect', $login_url, $post_id );
		if ( ! empty( $login_url ) ) {
			learndash_safe_redirect( $login_url );
		}
	}

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

	$settings = learndash_get_setting( $post_id );

	if ( learndash_get_post_type_slug( 'group' ) === get_post_type( $post_id ) ) {
		if ( ! isset( $settings['group_price_type'] ) ) {
			if ( ! defined( 'LEARNDASH_DEFAULT_GROUP_PRICE_TYPE' ) ) {
				$settings['group_price_type'] = LEARNDASH_DEFAULT_GROUP_PRICE_TYPE;
			} else {
				$settings['group_price_type'] = '';
			}
		}

		if ( 'free' === $settings['group_price_type'] || 'paynow' === $settings['group_price_type'] && empty( $settings['group_price'] ) && ! empty( $_POST['group_join'] ) || learndash_is_user_in_group( $user_id, $post_id ) ) {
			ld_update_group_access( $user_id, $post_id );
		}
	} elseif ( learndash_get_post_type_slug( 'course' ) === get_post_type( $post_id ) ) {
		if ( ! isset( $settings['course_price_type'] ) ) {
			if ( ! defined( 'LEARNDASH_DEFAULT_COURSE_PRICE_TYPE' ) ) {
				$settings['course_price_type'] = LEARNDASH_DEFAULT_COURSE_PRICE_TYPE;
			} else {
				$settings['course_price_type'] = '';
			}
		}

		if ( 'free' === $settings['course_price_type'] || 'paynow' === $settings['course_price_type'] && empty( $settings['course_price'] ) && ! empty( $settings['course_join'] ) || sfwd_lms_has_access( $post_id, $user_id ) ) {
			ld_update_course_access( $user_id, $post_id );
		}
	}
}

add_action( 'wp', 'learndash_process_course_join' );

/**
 * Gets all the courses with the price type open.
 *
 * Logic for this query was taken from the `sfwd_lms_has_access_fn()` function
 *
 * @since 2.3.0
 *
 * @param boolean $bypass_transient Optional. Whether to bypass transient cache. Default false.
 *
 * @return array An array of course IDs.
 */
function learndash_get_open_courses( $bypass_transient = false ) {
	return learndash_get_posts_by_price_type( learndash_get_post_type_slug( 'course' ), 'open', $bypass_transient );
}

/**
 * Gets all the courses with the price type paynow.
 *
 * Logic for this query was taken from the `sfwd_lms_has_access_fn()` function.
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @since 2.3.0
 *
 * @param boolean $bypass_transient Optional. Whether to bypass the transient cache. Default false.
 *
 * @return array An array of course IDs.
 */
function learndash_get_paynow_courses( $bypass_transient = false ) {
	return learndash_get_posts_by_price_type( learndash_get_post_type_slug( 'course' ), 'paynow', $bypass_transient );
}

/**
 * Gets the list of users with expired course access from the user meta.
 *
 * @since 2.6.4
 *
 * @param int $course_id Optional. The ID of the course. Default 0.
 *
 * @return array An array of users with expired course access.
 */
function learndash_get_course_expired_access_from_meta( $course_id = 0 ) {
	global $wpdb;

	$expired_user_ids = array();

	if ( ! empty( $course_id ) ) {
		$expired_user_ids = $wpdb->get_col(
			$wpdb->prepare(
				"SELECT user_id FROM {$wpdb->usermeta} as usermeta WHERE meta_key = %s",
				'learndash_course_expired_' . $course_id
			)
		);
	}

	return array_map( 'absint', $expired_user_ids );
}

/**
 * Gets the course settings from the course meta.
 *
 * @since 2.6.4
 *
 * @TODO Need to convert all references to get_post_meta for '_sfwd-courses' to use this function.
 *
 * @param int    $course_id   Optional. The ID of the course. Default 0.
 * @param string $setting_key Optional. The slug of the setting to get. Default empty.
 *
 * @return mixed Returns course settings. Passing empty setting key gets all the settings.
 */
function learndash_get_course_meta_setting( $course_id = 0, $setting_key = '' ) {
	$course_settings = array();

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

	$meta = get_post_meta( $course_id, '_sfwd-courses', true );
	if ( ( is_null( $meta ) ) || ( ! is_array( $meta ) ) ) {
		$meta = array();
	}

	// we only want/need to reformat the access list of we are returning ALL setting or just the access list.
	if ( ( empty( $setting_key ) ) || ( 'course_access_list' === $setting_key ) ) {
		if ( ! isset( $meta['sfwd-courses_course_access_list'] ) ) {
			$meta['sfwd-courses_course_access_list'] = '';
		}

		if ( ! empty( $meta['sfwd-courses_course_access_list'] ) ) {
			if ( is_string( $meta['sfwd-courses_course_access_list'] ) ) {
				$meta['sfwd-courses_course_access_list'] = array_map( 'absint', explode( ',', $meta['sfwd-courses_course_access_list'] ) );
			} elseif ( is_array( $meta['sfwd-courses_course_access_list'] ) ) {
				$meta['sfwd-courses_course_access_list'] = array_map( 'absint', $meta['sfwd-courses_course_access_list'] );
			} else {
				// Not sure how we can get here. Just in case.
				$meta['sfwd-courses_course_access_list'] = array();
			}
		} else {
			$meta['sfwd-courses_course_access_list'] = array();
		}

		// Need to remove the empty '0' items.
		$meta['sfwd-courses_course_access_list'] = array_diff( $meta['sfwd-courses_course_access_list'], array( 0, '' ) );
	}

	if ( empty( $setting_key ) ) {
		return $meta;
	} elseif ( isset( $meta[ 'sfwd-courses_' . $setting_key ] ) ) {
		return $meta[ 'sfwd-courses_' . $setting_key ];
	}
}

add_filter(
	'sfwd-courses_display_options',
	function( $options, $location ) {
		if ( ( ! isset( $options[ $location . '_course_prerequisite_enabled' ] ) ) || ( empty( $options[ $location . '_course_prerequisite_enabled' ] ) ) ) {
			global $post;
			if ( $post instanceof WP_Post ) {
				$settings = get_post_meta( $post->ID, '_sfwd-courses', true );

				if ( ( isset( $settings[ $location . '_course_prerequisite' ] ) ) && ( ! empty( $settings[ $location . '_course_prerequisite' ] ) ) ) {
					$options[ $location . '_course_prerequisite_enabled' ]  = 'on';
					$settings[ $location . '_course_prerequisite_enabled' ] = 'on';
					update_post_meta( $post->ID, '_sfwd-courses', $settings );
				}
			}
		}

		return $options;
	},
	1,
	2
);

/**
 * Updates the users group course access.
 *
 * Fires on `learndash_update_course_access` hook.
 *
 * @since 2.4.0
 *
 * @param int     $user_id     The ID of the user.
 * @param int     $course_id   The ID of the course.
 * @param array   $access_list An array of course access list.
 * @param boolean $remove      Whether to user group from course access.
 */
function learndash_update_course_users_groups( $user_id, $course_id, $access_list, $remove ) {
	if ( ( ! empty( $user_id ) ) && ( ! empty( $course_id ) ) && ( true !== (bool) $remove ) ) {

		$groups = learndash_get_course_groups( $course_id, true );
		if ( ! empty( $groups ) ) {
			foreach ( $groups as $group_id ) {
				$ld_auto_enroll_group_courses = get_post_meta( $group_id, 'ld_auto_enroll_group_courses', true );
				/**
				 * See settings in includes/settings/settings-metaboxes/class-ld-settings-metabox-group-courses-enroll.php
				 * If the checkbox is set then ALL courses can be used to enroll into group.
				 */
				if ( 'yes' === $ld_auto_enroll_group_courses ) {
					/**
					 * Filters whether to enroll into group for the course.
					 *
					 * @since 3.2.0
					 *
					 * @param boolean $enroll_in_group Whether to enroll the user into the group.
					 * @param integer $group_id        The Group ID.
					 * @param integer $course_id       The Course ID.
					 */
					if ( apply_filters( 'learndash_group_course_auto_enroll', true, $group_id, $course_id ) ) {
						ld_update_group_access( $user_id, $group_id );
					}
				} else {
					/**
					 * Else if the checkbox is not set and there are entries for the selective course enroll. Use those.
					 */
					$ld_auto_enroll_group_course_ids = get_post_meta( $group_id, 'ld_auto_enroll_group_course_ids', true );
					if ( ( is_array( $ld_auto_enroll_group_course_ids ) ) && ( ! empty( $ld_auto_enroll_group_course_ids ) ) ) {
						$ld_auto_enroll_group_course_ids = array_map( 'absint', $ld_auto_enroll_group_course_ids );

						if ( in_array( $course_id, $ld_auto_enroll_group_course_ids, true ) ) {
							/** This filter is documented in includes/course/ld-course-functions.php */
							if ( apply_filters( 'learndash_group_course_auto_enroll', true, $group_id, $course_id ) ) {
								ld_update_group_access( $user_id, $group_id );
							}
						}
					}
				}
			}
		}
	}
}
add_action( 'learndash_update_course_access', 'learndash_update_course_users_groups', 50, 4 );

/**
 * Gets the course completion date for a user.
 *
 * @since 2.4.7
 *
 * @param int $user_id   Optional. The ID of the user. Default 0.
 * @param int $course_id Optional. The ID of the course. Default 0.
 *
 * @return int The timestamp of when the course was completed. The value is 0 if the course is not completed.
 */
function learndash_user_get_course_completed_date( $user_id = 0, $course_id = 0 ) {
	$completed_on_timestamp = 0;
	if ( ( ! empty( $user_id ) ) && ( ! empty( $course_id ) ) ) {
		$completed_on_timestamp = get_user_meta( $user_id, 'course_completed_' . $course_id, true );

		if ( empty( $completed_on_timestamp ) ) {
			$activity_query_args = array(
				'post_ids'       => $course_id,
				'user_ids'       => $user_id,
				'activity_types' => 'course',
				'per_page'       => 1,
			);

			$activity = learndash_reports_get_activity( $activity_query_args );
			if ( ! empty( $activity['results'] ) ) {
				foreach ( $activity['results'] as $activity_item ) {
					if ( property_exists( $activity_item, 'activity_completed' ) ) {
						$completed_on_timestamp = $activity_item->activity_completed;

						// To make the next check easier we update the user meta.
						update_user_meta( $user_id, 'course_completed_' . $course_id, $completed_on_timestamp );
						break;
					}
				}
			}
		}
	}

	return $completed_on_timestamp;
}

/**
 * Gets the page data by page path.
 *
 * @since 2.5.2
 *
 * @param string $slug      Optional. The slug of the page. Default empty.
 * @param string $post_type Optional. The post type slug. Default empty.
 *
 * @return WP_Post|array|null `WP_Post` object or array on success, null on failure.
 */
function learndash_get_page_by_path( $slug = '', $post_type = '' ) {
	$course_post = null;

	if ( ( ! empty( $slug ) ) && ( ! empty( $post_type ) ) ) {

		$course_post = get_page_by_path( $slug, OBJECT, $post_type );

		if ( ( defined( 'ICL_LANGUAGE_CODE' ) ) && ( ICL_LANGUAGE_CODE != '' ) ) {
			if ( function_exists( 'icl_object_id' ) ) {
				$course_post = get_page( icl_object_id( $course_post->ID, $post_type, true, ICL_LANGUAGE_CODE ) );
			}
		}
	}

	return $course_post;
}

/**
 * Gets the course lessons per page setting.
 *
 * This function will initially source the per_page from the course. But if we are using the
 * default lesson options setting we will use that. Then if the lessons options
 * is not set for some reason we use the default system option 'posts_per_page'.
 *
 * @since 2.5.5
 *
 * @param int $course_id Optional. The ID of the course. Default 0.
 *
 * @return int The number of lessons per page or 0.
 */
function learndash_get_course_lessons_per_page( $course_id = 0 ) {
	$course_lessons_per_page = 0;

	// From the WP > Settings > Reading > Posts per page.
	$course_lessons_per_page = (int) get_option( 'posts_per_page' );

	// From the LearnDash > Settings > General > Global Pagination Settings > Shortcodes & Widgets per page.
	$course_lessons_per_page = LearnDash_Settings_Section::get_section_setting( 'LearnDash_Settings_Section_General_Per_Page', 'per_page', $course_lessons_per_page );

	// From the LearnDash > Courses > Settings > Global Course Management > Course Table Pagination > Lessons per page.
	$course_settings = LearnDash_Settings_Section::get_section_settings_all( 'LearnDash_Settings_Courses_Management_Display' );
	if ( ( isset( $course_settings['course_pagination_enabled'] ) ) && ( 'yes' === $course_settings['course_pagination_enabled'] ) ) {
		if ( isset( $course_settings['course_pagination_lessons'] ) ) {
			$course_lessons_per_page = $course_settings['course_pagination_lessons'];
		} elseif ( isset( $course_settings['posts_per_page'] ) ) {
			$course_lessons_per_page = $course_settings['posts_per_page'];
		}
	}

	// From the specific Course Settings > Custom Pagination.
	if ( ! empty( $course_id ) ) {
		$course_settings = learndash_get_setting( intval( $course_id ) );
		if ( ( isset( $course_settings['course_lesson_per_page'] ) ) && ( 'CUSTOM' === $course_settings['course_lesson_per_page'] ) && ( isset( $course_settings['course_lesson_per_page_custom'] ) ) ) {
			$course_lessons_per_page = $course_settings['course_lesson_per_page_custom'];
		}
	}

	return absint( $course_lessons_per_page );
}

/**
 * Called from within the Course Lessons List processing query SFWD_CPT::loop_shortcode.
 * This action will setup a global pager array to be used in templates.
 */

$course_pager_results = array( 'pager' => array() );
global $course_pager_results;

/**
 * Handles the course lessons list pager.
 *
 * @since 2.5.5
 *
 * Fires on `learndash_course_lessons_list_pager` hook.
 *
 * @global array $course_pager_results
 *
 * @param WP_Query|null $query_result  Optional. Course lesson list `WP_Query` object. Default null.
 * @param string        $pager_context Optional. The context where pagination is shown. Default empty.
 */
function learndash_course_lessons_list_pager( $query_result = null, $pager_context = '' ) {
	global $course_pager_results;

	$course_pager_results['pager']['paged'] = 1;
	if ( ( isset( $query_result->query_vars['paged'] ) ) && ( $query_result->query_vars['paged'] > 1 ) ) {
		$course_pager_results['pager']['paged'] = $query_result->query_vars['paged'];
	}

	$course_pager_results['pager']['total_items'] = absint( $query_result->found_posts );
	$course_pager_results['pager']['total_pages'] = absint( $query_result->max_num_pages );
}
add_action( 'learndash_course_lessons_list_pager', 'learndash_course_lessons_list_pager', 10, 2 );

/**
 * Gets the lesson topic pagination values from HTTP get global array.
 *
 * @since 3.0.0
 *
 * @return array An array of lesson topic pagination values.
 */
function learndash_get_lesson_topic_paged_values() {
	$paged_values = array(
		'lesson' => 0,
		'paged'  => 1,
	);
	if ( ( isset( $_GET['ld-topic-page'] ) ) && ( ! empty( $_GET['ld-topic-page'] ) ) ) {
		list( $paged_values['lesson'], $paged_values['paged'] ) = explode( '-', $_GET['ld-topic-page'] );
		$paged_values['lesson']                                 = absint( $paged_values['lesson'] );
		$paged_values['paged']                                  = absint( $paged_values['paged'] );
		if ( $paged_values['paged'] < 1 ) {
			$paged_values['paged'] = 1;
		}
		if ( ( empty( $paged_values['lesson'] ) ) || ( empty( $paged_values['paged'] ) ) ) {
			$paged_values = array(
				'lesson' => 0,
				'paged'  => 1,
			);
		}
	}

	return $paged_values;
}

/**
 * Processes the lesson topics pagination.
 *
 * @since 3.0.0
 *
 * @global array $course_pager_results
 *
 * @param array $topics Optional. An array of topics. Default empty array.
 * @param array $args {
 *    An array of lesson topic pager arguments. Default empty array.
 *
 *    @type int $course_id Course ID.
 *    @type int $lesson_id Lesson ID.
 * }
 *
 * @return array An array of paged topics.
 */
function learndash_process_lesson_topics_pager( $topics = array(), $args = array() ) {
	global $course_pager_results;

	$paged_values = learndash_get_lesson_topic_paged_values();

	if ( ! empty( $topics ) ) {
		if ( ! isset( $args['per_page'] ) ) {
			$topics_per_page = learndash_get_course_topics_per_page( $args['course_id'], $args['lesson_id'] );
		} else {
			$topics_per_page = intval( $args['per_page'] );
		}
		if ( ( $topics_per_page > 0 ) && ( count( $topics ) > $topics_per_page ) ) {
			$topics_chunks = array_chunk( $topics, $topics_per_page );

			$course_pager_results[ $args['lesson_id'] ]          = array();
			$course_pager_results[ $args['lesson_id'] ]['pager'] = array();

			$topics_paged = 1;

			if ( ( ! empty( $paged_values['lesson'] ) ) && ( $paged_values['lesson'] == $args['lesson_id'] ) ) {
				$topics_paged = $paged_values['paged'];
			} elseif ( get_post_type() === learndash_get_post_type_slug( 'topic' ) ) {
				/**
				 * If we are viewing a Topic and the page is empty we load the
				 * paged set to show the current topic item.
				 */
				foreach ( $topics_chunks as $topics_chunk_page => $topics_chunk_set ) {
					$topics_ids = array_values( wp_list_pluck( $topics_chunk_set, 'ID' ) );

					if ( ( ! empty( $topics_ids ) ) && ( in_array( (int) get_the_ID(), array_map( 'absint', $topics_ids ), true ) ) ) {
						$topics_paged = ++$topics_chunk_page;
						break;
					}
				}
			} elseif ( get_post_type() === learndash_get_post_type_slug( 'quiz' ) ) {
				$parent_step_ids = learndash_course_get_all_parent_step_ids( $args['course_id'], get_the_ID() );
				if ( ! empty( $parent_step_ids ) ) {
					$parent_step_ids = array_map( 'absint', $parent_step_ids );
					$parent_step_ids = array_reverse( $parent_step_ids );

					if ( get_post_type( $parent_step_ids[0] ) === learndash_get_post_type_slug( 'topic' ) ) {
						// If the Quiz has a Topic parent we loop through the topic chunks to find the parent.
						foreach ( $topics_chunks as $topics_chunk_page => $topics_chunk_set ) {
							$topics_ids = array_values( wp_list_pluck( $topics_chunk_set, 'ID' ) );
							if ( ( ! empty( $topics_ids ) ) && ( in_array( $parent_step_ids[0], $topics_ids ) ) ) {
								$topics_paged = ++$topics_chunk_page;
								break;
							}
						}
					} elseif ( get_post_type( $parent_step_ids[0] ) === learndash_get_post_type_slug( 'lesson' ) ) {
						/**
						 * If the Quiz has a LEsson parent we just set the last Topic chunk set because
						 * Lesson Quizzes are shown at the end.
						 */
						$topics_paged = count( $topics_chunks );
					}
				}
			}

			$course_pager_results[ $args['lesson_id'] ]['pager']['paged'] = $topics_paged;

			$course_pager_results[ $args['lesson_id'] ]['pager']['total_items'] = count( $topics );
			$course_pager_results[ $args['lesson_id'] ]['pager']['total_pages'] = count( $topics_chunks );

			$topics = $topics_chunks[ $topics_paged - 1 ];
		}
	}

	return $topics;
}

/**
 * Gets the course lessons order query arguments.
 *
 * The course lessons order can be set in the course or globally defined in
 * the lesson options. This function will check all logic and return the
 * correct setting.
 *
 * @since 2.5.8
 *
 * @param int $course_id Optional. The ID of the course. Default 0.
 *
 * @return array An array of course lessons order query arguments.
 */
function learndash_get_course_lessons_order( $course_id = 0 ) {
	$course_lessons_args = array(
		'order'   => '',
		'orderby' => '',
	);

	if ( learndash_is_course_shared_steps_enabled() ) {
		$course_lessons_args['orderby'] = 'post__in';
		return $course_lessons_args;

	} else {
		$lessons_options = learndash_get_option( 'sfwd-lessons' );
		if ( ( isset( $lessons_options['order'] ) ) && ( ! empty( $lessons_options['order'] ) ) ) {
			$course_lessons_args['order'] = $lessons_options['order'];
		}

		if ( ( isset( $lessons_options['orderby'] ) ) && ( ! empty( $lessons_options['orderby'] ) ) ) {
			$course_lessons_args['orderby'] = $lessons_options['orderby'];
		}
	}

	if ( ! empty( $course_id ) ) {
		$course_settings = learndash_get_setting( $course_id );
		if ( ( isset( $course_settings['course_lesson_order'] ) ) && ( ! empty( $course_settings['course_lesson_order'] ) ) ) {
			$course_lessons_args['order'] = $course_settings['course_lesson_order'];
		}

		if ( ( isset( $course_settings['course_lesson_orderby'] ) ) && ( ! empty( $course_settings['course_lesson_orderby'] ) ) ) {
			$course_lessons_args['orderby'] = $course_settings['course_lesson_orderby'];
		}
	}

	/**
	 * Filters course lessons order query arguments.
	 *
	 * @param array $course_lesson_args An array of course lesson order arguments.
	 * @param int   $course_id          Course ID.
	 */
	return apply_filters( 'learndash_course_lessons_order', $course_lessons_args, $course_id );
}

/**
 * Converts and gets the course access list.
 *
 * The function converts the standard comma-separated list of user IDs
 * used for the course_access_list field. The conversion is to trim and ensure
 * the values are integer and not empty.
 *
 * @since 2.5.9
 *
 * @param string|array $course_access_list Optional. String of comma separated user IDs or array. Default empty.
 * @param boolean      $return_array       Optional. Whether to return array. True to return array and false to return string. Default false.
 *
 * @return string|array The list of user IDs.
 */
function learndash_convert_course_access_list( $course_access_list = '', $return_array = false ) {
	if ( ! empty( $course_access_list ) ) {

		// Convert the comma separated list into an array.
		if ( is_string( $course_access_list ) ) {
			$course_access_list = explode( ',', $course_access_list );
		}

		// Now normalize the array elements.
		if ( is_array( $course_access_list ) ) {
			$course_access_list = array_map( 'absint', $course_access_list );
			$course_access_list = array_unique( $course_access_list, SORT_NUMERIC );
			$course_access_list = array_diff( $course_access_list, array( 0 ) );
		}

		// Prepare the return value.
		if ( true !== $return_array ) {
			$course_access_list = implode( ',', $course_access_list );
		}
	} elseif ( true === $return_array ) {
		$course_access_list = array();
	}

	return $course_access_list;
}

/**
 * Determines the number of lesson topics to display per page.
 *
 * @since 3.0.0
 *
 * @param int $course_id Optional. Parent Course ID. Default 0.
 * @param int $lesson_id Optional. Parent Lesson ID. Default 0.
 *
 * @return int The number of lesson topics per page.
 */
function learndash_get_course_topics_per_page( $course_id = 0, $lesson_id = 0 ) {
	$course_topics_per_page = 0;

	// From the WP > Settings > Reading > Posts per page.
	$course_topics_per_page = intval( get_option( 'posts_per_page' ) );

	// From the LearnDash > Settings > General > Global Pagination Settings > Shortcodes & Widgets per page.
	$course_topics_per_page = LearnDash_Settings_Section::get_section_setting( 'LearnDash_Settings_Section_General_Per_Page', 'per_page', $course_topics_per_page );

	// From the LearnDash > Courses > Settings > Global Course Management > Course Table Pagination > Lessons per page.
	$course_settings = LearnDash_Settings_Section::get_section_settings_all( 'LearnDash_Settings_Courses_Management_Display' );
	if ( ( isset( $course_settings['course_pagination_enabled'] ) ) && ( 'yes' === $course_settings['course_pagination_enabled'] ) ) {
		if ( isset( $course_settings['course_pagination_topics'] ) ) {
			$course_topics_per_page = absint( $course_settings['course_pagination_topics'] );
		} elseif ( isset( $course_settings['posts_per_page'] ) ) {
			$course_topics_per_page = absint( $course_settings['posts_per_page'] );
		}
	}

	// From the specific Course Settings > Custom Pagination.
	if ( ! empty( $course_id ) ) {
		$course_settings = learndash_get_setting( intval( $course_id ) );
		if ( ( isset( $course_settings['course_lesson_per_page'] ) ) && ( 'CUSTOM' === $course_settings['course_lesson_per_page'] ) && ( isset( $course_settings['course_topic_per_page_custom'] ) ) ) {
			$course_topics_per_page = absint( $course_settings['course_topic_per_page_custom'] );
		}
	}

	return $course_topics_per_page;
}

/**
 * Checks whether to use the legacy course access list.
 *
 * @since 3.1.0
 *
 * @return boolean Returns true to use legacy course access list otherwise false.
 */
function learndash_use_legacy_course_access_list() {
	$use_legacy_course_access_list = true;

	$data_course_access_convert = learndash_data_upgrades_setting( 'course-access-lists-convert' );
	if ( $data_course_access_convert ) {
		$use_legacy_course_access_list = false;

	}
	/**
	 * Filters whether to use legacy course access list or not.
	 *
	 * @param boolean $use_legacy_course_access_list Whether to use legacy course access list.
	 */
	return apply_filters( 'learndash_use_legacy_course_access_list', $use_legacy_course_access_list );
}

/**
 * Gets the user's last active (last updated) course ID.
 *
 * @since 3.1.4
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param int $user_id Optional. User ID. Default 0.
 *
 * @return int The last active course ID.
 */
function learndash_get_last_active_course( $user_id = 0 ) {
	global $wpdb;

	$last_course_id = 0;

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

	if ( ! empty( $user_id ) ) {
		$query_result   = $wpdb->get_var(
			$wpdb->prepare(
				'SELECT post_id FROM ' . esc_sql( LDLMS_DB::get_table_name( 'user_activity' ) ) . " WHERE user_id=%d AND activity_type='course' AND activity_status = 0 AND activity_completed = '' ORDER BY activity_updated DESC",
				$user_id
			)
		);
		$last_course_id = absint( $query_result );
	}

	return $last_course_id;
}


/**
 * Gets the user's last active step for a course.
 *
 * @since 3.1.4
 *
 * @param int $user_id   Optional. User ID. Default 0.
 * @param int $course_id Optional. Course ID. Default 0.
 *
 * @return int The last active course step ID.
 */
function learndash_user_course_last_step( $user_id = 0, $course_id = 0 ) {
	global $wpdb;

	$last_course_step_id = 0;

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

	if ( ! empty( $user_id ) ) {
		if ( empty( $course_id ) ) {
			$course_id = learndash_get_last_active_course( $user_id );
		}
		if ( ! empty( $course_id ) ) {
			$query_result        = $wpdb->get_var(
				$wpdb->prepare(
					'SELECT user_activity_meta.activity_meta_value FROM ' . esc_sql( LDLMS_DB::get_table_name( 'user_activity' ) ) . ' as user_activity INNER JOIN ' . esc_sql( LDLMS_DB::get_table_name( 'user_activity_meta' ) ) . " as user_activity_meta ON user_activity.activity_id = user_activity_meta.activity_id WHERE user_activity.user_id=%d AND user_activity.post_id=%d AND user_activity.activity_type='course' AND user_activity_meta.activity_meta_key= 'steps_last_id' ORDER BY activity_updated DESC",
					$user_id,
					$course_id
				)
			);
			$last_course_step_id = absint( $query_result );
		}
	}

	return $last_course_step_id;
}


/**
 * Check if user can bypass action ($context).
 *
 * @since 3.2.0
 *
 * @param int    $user_id User ID.
 * @param string $context The specific action to check for.
 * @param array  $args Optional array of args related to the
 * context. Typically starting with an step ID, Course ID, etc.
 * @return bool True if user can bypass. Otherwise false.
 */
function learndash_can_user_bypass( $user_id = 0, $context = 'learndash_course_progression', $args = array() ) {
	if ( empty( $user_id ) ) {
		if ( is_user_logged_in() ) {
			$user_id = get_current_user_id();
		}
	}

	$can_bypass = false;
	if ( ! empty( $user_id ) ) {
		if ( ( learndash_is_admin_user( $user_id ) ) && ( LearnDash_Settings_Section::get_section_setting( 'LearnDash_Settings_Section_General_Admin_User', 'bypass_course_limits_admin_users' ) ) ) {
			$can_bypass = true;
		} elseif ( ( learndash_is_group_leader_user( $user_id ) ) && ( LearnDash_Settings_Section::get_section_setting( 'LearnDash_Settings_Section_Groups_Group_Leader_User', 'bypass_course_limits' ) ) ) {
			$can_bypass = true;
		}
	}

	/**
	 * Filters user can bypass logic.
	 *
	 * @since 3.2.0
	 *
	 * @param boolean $can_bypass Whether the user can bypass $context.
	 * @param int     $user_id    User ID.
	 * @param string  $context The specific action to check for.
	 * @param array  $args Optional array of args related to the
	 * context. Typically starting with an step ID, Course ID, etc.
	 */
	$can_bypass = apply_filters( 'learndash_user_can_bypass', $can_bypass, $user_id, $context, $args );

	return $can_bypass;
}

/**
 * Check if user can auto-enroll in courses..
 *
 * @since 3.2.3
 *
 * @param int $user_id User ID.
 * @return bool True if user can auto-enroll.
 */
function learndash_can_user_autoenroll_courses( $user_id = 0 ) {
	if ( empty( $user_id ) ) {
		if ( is_user_logged_in() ) {
			$user_id = get_current_user_id();
		}
	}

	$auto_enroll = false;
	if ( ! empty( $user_id ) ) {
		if ( learndash_is_admin_user( $user_id ) ) {
			$auto_enroll = LearnDash_Settings_Section::get_section_setting( 'LearnDash_Settings_Section_General_Admin_User', 'courses_autoenroll_admin_users' );
		} elseif ( learndash_is_group_leader_user( $user_id ) ) {
			if ( 'yes' === LearnDash_Settings_Section::get_section_setting( 'LearnDash_Settings_Section_Groups_Group_Leader_User', 'courses_autoenroll' ) ) {
				$auto_enroll = 'yes';
			}
		}
	}

	if ( 'yes' === $auto_enroll ) {
		$auto_enroll = true;
	} else {
		$auto_enroll = false;
	}

	/**
	 * Filters whether to auto enroll a user into a course or not.
	 *
	 * @since 2.3.0
	 *
	 * @param boolean $auto_enroll Whether to auto enroll user or not.
	 * @param int     $user_id     ID of the logged in user to check.
	 */
	return apply_filters( 'learndash_override_course_auto_enroll', $auto_enroll, $user_id );
}

/**
 * Utility function to check if Course Builder is enabled.
 *
 * @since 3.4.0
 */
function learndash_is_course_builder_enabled() {
	if ( 'yes' === LearnDash_Settings_Section::get_section_setting( 'LearnDash_Settings_Courses_Management_Display', 'course_builder_enabled' ) ) {
		return true;
	}

	return false;
}

/**
 * Utility function to check if Course Shared steps is enabled.
 *
 * @since 3.4.0
 */
function learndash_is_course_shared_steps_enabled() {
	if ( ( true === learndash_is_course_builder_enabled() ) && ( 'yes' === LearnDash_Settings_Section::get_section_setting( 'LearnDash_Settings_Courses_Management_Display', 'course_builder_shared_steps' ) ) ) {
		return true;
	}

	return false;
}

/**
 * Get Courses/Groups by Price Type.
 *
 * @since 3.4.1
 *
 * @param string  $post_type        Post Type slug: sfwd-courses or group.
 * @param string  $price_type       Price Type: open, free, closed, paynow, etc.
 * @param boolean $bypass_transient Optional. Whether to bypass transient cache. Default false.
 *
 * @return @array Array of Course IDs.
 */
function learndash_get_posts_by_price_type( $post_type = '', $price_type = '', $bypass_transient = false ) {
	global $wpdb;

	$post_ids   = array();
	$post_type  = esc_attr( $post_type );
	$price_type = esc_attr( $price_type );

	if ( ( ! empty( $post_type ) ) && ( in_array( $post_type, learndash_get_post_type_slug( array( 'course', 'group' ) ), true ) ) ) {

		$new_logic = false;
		if ( learndash_get_post_type_slug( 'course' ) === $post_type ) {
			if ( empty( $price_type ) ) {
				$price_type = LEARNDASH_DEFAULT_COURSE_PRICE_TYPE;
			}

			$transient_key = 'learndash_' . $price_type . '_courses';

		} elseif ( learndash_get_post_type_slug( 'group' ) === $post_type ) {
			if ( empty( $price_type ) ) {
				$price_type = LEARNDASH_DEFAULT_GROUP_PRICE_TYPE;
			}

			$transient_key = 'learndash_' . $price_type . '_groups';
		}

		if ( ! $bypass_transient ) {
			$post_ids_transient = LDLMS_Transients::get( $transient_key );
		} else {
			$post_ids_transient = false;
		}

		if ( false === $post_ids_transient ) {
			if ( learndash_post_meta_processed( $post_type ) ) {
				$price_query_args = array(
					'post_type'    => $post_type,
					'fields'       => 'ids',
					'nopaging'     => true,
					'meta_key'     => '_ld_price_type',
					'meta_value'   => $price_type,
					'meta_compare' => '=',
				);

				$price_query = new WP_Query( $price_query_args );
				if ( ( property_exists( $price_query, 'posts' ) ) && ( ! empty( $price_query->posts ) ) ) {
					$post_ids = $price_query->posts;
					$post_ids = array_map( 'absint', $post_ids );
				}
			} else {
				$sql_str = $wpdb->prepare(
					"SELECT postmeta.post_id as post_id FROM {$wpdb->postmeta} as postmeta
						INNER JOIN {$wpdb->posts} as posts ON posts.ID = postmeta.post_id
						WHERE posts.post_status='publish' AND posts.post_type=%s AND postmeta.meta_key=%s
						AND ( postmeta.meta_value REGEXP '\"" . $post_type . '_' . learndash_get_post_type_key( $post_type ) . '_price_type";s:' . strlen( $price_type ) . ':"' . $price_type . "\";' )",
					$post_type,
					'_' . $post_type
				);

				$post_ids = $wpdb->get_col( $sql_str );
			}
			if ( ! empty( $post_ids ) ) {
				$post_ids = array_map( 'absint', $post_ids );
			}
			LDLMS_Transients::set( $transient_key, $post_ids, MINUTE_IN_SECONDS );

		} else {
			$post_ids = $post_ids_transient;
		}
	}

	return $post_ids;
}

/**
 * Checks if Post Meta Data Upgrade has completed.
 *
 * @since 3.4.1
 *
 * @param string $post_type The post type slug to check.
 *
 * @return boolean.
 */
function learndash_post_meta_processed( $post_type = '' ) {
	if ( ( ! empty( $post_type ) ) && ( learndash_is_valid_post_type( $post_type ) ) ) {
		$data_settings = learndash_data_upgrades_setting( learndash_get_post_type_key( $post_type ) . '-post-meta' );
		if ( ( ! isset( $data_settings['process_status'] ) ) || ( 'complete' !== $data_settings['process_status'] ) ) {
			$post_meta_processed = false;
		} else {
			$post_meta_processed = true;
		}

		/**
		 * Filters whether to post meta is processed or not.
		 *
		 * @since 3.4.1
		 *
		 * @param boolean $process   True or False to process post meta.
		 * @param string  $post_type The post type slug.
		 */
		return apply_filters( 'learndash_post_meta_processed', $post_meta_processed, $post_type );
	}
	return false;
}

/**
 * Returns true if it's a course post.
 *
 * @param WP_Post|int|null $post Post or Post ID.
 *
 * @since 4.1.0
 *
 * @return bool
 */
function learndash_is_course_post( $post ): bool {
	if ( empty( $post ) ) {
		return false;
	}

	$post_type = is_a( $post, WP_Post::class ) ? $post->post_type : get_post_type( $post );

	return LDLMS_Post_Types::get_post_type_slug( 'course' ) === $post_type;
}

/**
 * Returns the page url a user is redirected to after completing a course.
 * Priority is: Course level setting -> Courses level setting -> Course page url.
 *
 * @since 4.11.0
 *
 * @param int $course_id Course ID.
 *
 * @return string
 */
function learndash_course_get_completion_url( int $course_id ): string {
	// Get the course completion page.
	$page_id = learndash_get_setting( $course_id, 'course_completion_page' );

	// If the course completion page is not set, then get the default course completion page.
	if ( empty( $page_id ) ) {
		$page_id = LearnDash_Settings_Section::get_section_setting(
			'LearnDash_Settings_Courses_Management_Display',
			'course_completion_page'
		);
	}

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

	/**
	 * Filters whether to redirect to the course completion page.
	 *
	 * @since 4.11.0
	 *
	 * @param bool $redirect_enabled Whether to redirect to the course completion page. Default true.
	 * @param int  $course_id        Course ID.
	 *
	 * @return bool Whether to redirect to the course completion page.
	 */
	$course_completion_redirect_enabled = apply_filters(
		'learndash_course_completion_page_redirect_enabled',
		true,
		$course_id
	);

	$page_url = get_permalink(
		$course_completion_redirect_enabled && $page_id > 0
			? $page_id
			: $course_id
	);
	$page_url = Cast::to_string( $page_url );

	/**
	 * Filters the course completion URL.
	 *
	 * Before version 4.11.0, it was used to filter the ID/URL of the next course quiz which was incorrect.
	 *
	 * @since 2.1.0
	 * @since 4.11.0 Added the `$page_id` parameter and `$page_url` parameter is always a string.
	 *
	 * @param string $page_url  The URL of the course completion page.
	 * @param int    $course_id Course ID.
	 * @param int    $page_id   The ID of the course completion page.
	 *
	 * @return string The URL of the course completion page.
	 */
	return apply_filters(
		'learndash_course_completion_url',
		$page_url,
		$course_id,
		$page_id
	);
}
