<?php
/**
 * Manage Course Related Logic
 *
 * @package Tutor
 * @author Themeum <support@themeum.com>
 * @link https://themeum.com
 * @since 1.0.0
 */

namespace TUTOR;

defined( 'ABSPATH' ) || exit;

use Tutor\Components\Button;
use Tutor\Components\Constants\Size;
use Tutor\Components\Constants\Variant;
use Tutor\Components\Progress;
use Tutor\Ecommerce\Tax;
use Tutor\Models\QuizModel;
use Tutor\Helpers\HttpHelper;
use Tutor\Models\CourseModel;
use Tutor\Components\Tooltip;
use Tutor\Ecommerce\Ecommerce;
use Tutor\Helpers\DateTimeHelper;
use Tutor\Traits\JsonResponse;
use Tutor\Helpers\ValidationHelper;
use Tutor\Models\EnrollmentModel;
use Tutor\Options_V2;
use TUTOR_ASSIGNMENTS\Assignments;

/**
 * Course Class
 *
 * @since 1.0.0
 */
class Course extends Tutor_Base {
	use JsonResponse;

	/**
	 * Course Price type
	 *
	 * @since 3.0.0
	 *
	 * @var string
	 */
	const PRICE_TYPE_FREE         = 'free';
	const PRICE_TYPE_PAID         = 'paid';
	const PRICE_TYPE_SUBSCRIPTION = 'subscription';

	/**
	 * Course price and sale price
	 *
	 * @since 3.0.0
	 */
	const COURSE_PRICE_TYPE_META     = '_tutor_course_price_type';
	const COURSE_PRICE_META          = 'tutor_course_price';
	const COURSE_SALE_PRICE_META     = 'tutor_course_sale_price';
	const COURSE_SELLING_OPTION_META = 'tutor_course_selling_option';
	const COURSE_PRODUCT_ID_META     = '_tutor_course_product_id';

	/**
	 * Selling option constants
	 *
	 * @since 3.0.0
	 */
	const SELLING_OPTION_ONE_TIME     = 'one_time';
	const SELLING_OPTION_SUBSCRIPTION = 'subscription';
	const SELLING_OPTION_BOTH         = 'both'; // onetime and subscription.
	const SELLING_OPTION_MEMBERSHIP   = 'membership';
	const SELLING_OPTION_ALL          = 'all';

	/**
	 * Tax collection settings meta
	 *
	 * @since 3.7.0
	 */
	const TAX_ON_SINGLE_META       = 'tutor_tax_on_single';
	const TAX_ON_SUBSCRIPTION_META = 'tutor_tax_on_subscription';

	/**
	 * Additional data meta
	 *
	 * @since 3.6.0
	 */
	const COURSE_BENEFITS_META         = '_tutor_course_benefits';
	const COURSE_REQUIREMENTS_META     = '_tutor_course_requirements';
	const COURSE_TARGET_AUDIENCE_META  = '_tutor_course_target_audience';
	const COURSE_MATERIAL_INCLUDE_META = '_tutor_course_material_includes';
	const COURSE_DURATION_META         = '_course_duration';

	/**
	 * Course settings meta
	 *
	 * @since 3.6.0
	 */
	const COURSE_ENABLE_QA_META = '_tutor_enable_qa';
	const PUBLIC_COURSE_META    = '_tutor_is_public_course';
	const COURSE_SETTINGS_META  = '_tutor_course_settings';
	const COURSE_LEVEL_META     = '_tutor_course_level';

	/**
	 * Meta keys used to identify and store Tutor course order data.
	 *
	 * @since 4.0.0
	 */
	const IS_TUTOR_ORDER_FOR_COURSE_META = '_is_tutor_order_for_course';
	const TUTOR_ORDER_FOR_COURSE_ID_META = '_tutor_order_for_course_id_';

	/**
	 * Additional course meta info
	 *
	 * @var array
	 */
	private $additional_meta = array(
		'_tutor_enable_qa',
		'_tutor_is_public_course',
	);

	/**
	 * Constructor
	 *
	 * @since 1.0.0
	 * @since 3.0.0 $register_hooks param added to reuse this class.
	 *
	 * @param bool $register_hooks register hooks.
	 *
	 * @return void
	 */
	public function __construct( $register_hooks = true ) {
		parent::__construct();

		if ( ! $register_hooks ) {
			return;
		}

		add_action( 'save_post_' . $this->course_post_type, array( $this, 'save_course_meta' ), 10, 2 );

		add_action( 'wp_ajax_tutor_save_topic', array( $this, 'tutor_save_topic' ) );
		add_action( 'wp_ajax_tutor_delete_topic', array( $this, 'tutor_delete_topic' ) );

		/**
		 * Frontend Action
		 */
		add_action( 'template_redirect', array( $this, 'enroll_now' ) );
		add_action( 'init', array( $this, 'mark_course_complete' ) );

		/**
		 * Frontend Dashboard
		 */
		add_action( 'wp_ajax_tutor_delete_dashboard_course', array( $this, 'tutor_delete_dashboard_course' ) );

		/**
		 * Gutenberg author support
		 */
		add_filter( 'wp_insert_post_data', array( $this, 'tutor_add_gutenberg_author' ), 99, 2 );

		/**
		 * Do Stuff for the course save from frontend
		 */
		add_action( 'save_tutor_course', array( $this, 'attach_product_with_course' ), 10, 2 );

		/**
		 * Enable Disable Course Details Page Feature
		 *
		 * @since v.1.4.8
		 */
		$this->course_elements_enable_disable();

		/**
		 * Check if course starting, set meta if starting
		 *
		 * @since v.1.4.8
		 */
		add_action( 'tutor_lesson_load_before', array( $this, 'tutor_lesson_load_before' ) );

		/**
		 * Filter product in shop page
		 *
		 * @since v.1.4.9
		 */
		$this->filter_product_in_shop_page();

		/**
		 * Remove the course price if enrolled
		 *
		 * @since 1.5.8
		 */
		add_filter( 'tutor_course_price', array( $this, 'remove_price_if_enrolled' ) );

		/**
		 * Remove course complete button if course completion is strict mode
		 *
		 * @since v.1.6.1
		 */
		add_filter( 'tutor_course/single/complete_form', array( $this, 'tutor_lms_hide_course_complete_btn' ) );
		add_filter( 'get_gradebook_generate_form_html', array( $this, 'get_generate_greadbook' ) );

		/**
		 * Add social share content in header
		 *
		 * @since v.1.6.3
		 */
		add_action( 'wp_head', array( $this, 'social_share_content' ) );

		/**
		 * Delete course data after deleted course
		 *
		 * @since v.1.6.6
		 */
		add_action( 'deleted_post', array( new CourseModel(), 'delete_course_data' ) );

		/**
		 * Delete course data after deleted course
		 *
		 * @since v.1.8.2
		 */
		add_action( 'before_delete_post', array( $this, 'delete_associated_enrollment' ) );

		/**
		 * Show only own uploads in media library if user is instructor
		 *
		 * @since v1.8.9
		 */
		add_filter( 'posts_where', array( $this, 'restrict_media' ) );

		/**
		 * Restrict new enrol/purchase button if course member limit reached
		 *
		 * @since v1.9.0
		 */
		add_filter( 'tutor_course_restrict_new_entry', array( $this, 'restrict_new_student_entry' ) );

		/**
		 * Reset course progress on retake
		 *
		 * @since v1.9.5
		 */
		add_action( 'wp_ajax_tutor_reset_course_progress', array( $this, 'tutor_reset_course_progress' ) );

		/**
		 * Popup for review
		 *
		 * @since v1.9.7
		 */
		add_action( 'wp_footer', array( $this, 'popup_review_form' ) );
		add_action( 'wp_ajax_tutor_clear_review_popup_data', array( $this, 'clear_review_popup_data' ) );

		/**
		 * Do enroll after login if guest take enroll attempt
		 *
		 * @since 1.9.8
		 */
		add_action( 'tutor_do_enroll_after_login_if_attempt', array( $this, 'enroll_after_login_if_attempt' ), 10, 2 );

		add_action( 'wp_ajax_tutor_update_course_content_order', array( $this, 'ajax_update_course_content_order' ) );

		add_action( 'wp_ajax_tutor_get_wc_product', array( $this, 'get_wc_product' ) );
		add_action( 'wp_ajax_tutor_get_wc_products', array( $this, 'get_wc_products' ) );

		add_action( 'wp_ajax_tutor_course_enrollment', array( $this, 'course_enrollment' ) );

		/**
		 * After trash a course redirect to course list page
		 *
		 * @since 2.1.7
		 */
		add_action( 'trashed_post', __CLASS__ . '::redirect_to_course_list_page' );

		add_filter( 'tutor_enroll_required_login_class', array( $this, 'add_enroll_required_login_class' ) );

		/**
		 * Remove wp trash button if instructor settings is disabled
		 *
		 * @since 2.7.3
		 */
		add_action( 'tutor_option_save_after', array( $this, 'disable_course_trash_instructor' ) );

		/**
		 * New course builder
		 *
		 * @since 3.0.0
		 */
		add_action( 'template_redirect', array( $this, 'load_course_builder' ) );
		add_action( 'tutor_before_course_builder_load', array( $this, 'enqueue_course_builder_assets' ) );
		add_filter( 'tutor_localize_data', array( $this, 'localize_course_builder_data' ) );

		/**
		 * Ajax list
		 *
		 * @since 3.0.0
		 */
		add_action( 'wp_ajax_tutor_create_new_draft_course', array( $this, 'ajax_create_new_draft_course' ) );
		add_action( 'wp_ajax_tutor_course_list', array( $this, 'ajax_course_list' ) );
		add_action( 'wp_ajax_tutor_create_course', array( $this, 'ajax_create_course' ) );
		add_action( 'wp_ajax_tutor_course_details', array( $this, 'ajax_course_details' ) );
		add_action( 'wp_ajax_tutor_course_contents', array( $this, 'ajax_course_contents' ) );
		add_action( 'wp_ajax_tutor_update_course', array( $this, 'ajax_update_course' ) );
		add_action( 'wp_ajax_tutor_unlink_page_builder', array( $this, 'ajax_unlink_page_builder' ) );

		add_filter( 'tutor_user_list_access', array( $this, 'user_list_access_for_instructor' ) );
		add_filter( 'tutor_user_list_args', array( $this, 'user_list_args_for_instructor' ) );

		add_filter( 'template_include', array( $this, 'handle_password_protected' ) );
		add_action( 'login_form_postpass', array( $this, 'handle_password_submit' ) );
		add_filter( 'post_password_required', array( $this, 'bypass_password_for_enrolled' ), 10, 2 );
		add_filter( 'the_preview', array( $this, 'handle_schedule_courses' ) );

		add_action( 'tutor_course_action_btn', array( $this, 'render_course_action_btn' ) );
		add_action( 'wp_ajax_tutor_complete_course', array( $this, 'ajax_tutor_complete_course' ) );
	}

	/**
	 * Handle schedule courses preview for instructors.
	 *
	 * @since 3.5.0
	 *
	 * @param \WP_Post $content the preview post content.
	 *
	 * @return \WP_Post
	 */
	public function handle_schedule_courses( $content ) {
		global $wp_query;
		$course_coming_soon_enabled = (int) get_post_meta( $content->ID, '_tutor_course_enable_coming_soon', true );
		$is_instructor              = tutor_utils()->is_instructor_of_this_course( get_current_user_id(), $content->ID, true );
		if ( ! CourseModel::get_post_types( $content ) || current_user_can( 'administrator' ) || $is_instructor || $course_coming_soon_enabled ) {
			return $content;
		}

		$wp_query->set_404();
		status_header( 404 );
		nocache_headers();
		return $content;
	}

	/**
	 * Handle password protected course and bundle form submission.
	 *
	 * @since 3.2.1
	 *
	 * @return void
	 */
	public function handle_password_submit() {
		if ( Input::has( 'post_password' ) && Input::has( 'course_id' ) ) {
			$course_id         = Input::post( 'course_id', 0, Input::TYPE_NUMERIC );
			$password_required = post_password_required( $course_id );
			if ( $password_required ) {
				set_transient( 'tutor_post_password_error', __( 'Invalid password', 'tutor' ) );
			}
		}
	}

	/**
	 * Bypass password protection for enrolled users.
	 *
	 * @since 4.0.0
	 *
	 * @param bool    $required Whether the password is required.
	 * @param WP_Post $post     The post object.
	 *
	 * @return bool false if the current user is enrolled, original value otherwise.
	 */
	public function bypass_password_for_enrolled( $required, $post ) {
		if ( ! $required ) {
			return $required;
		}

		$post_types = array( tutor()->course_post_type, tutor()->bundle_post_type );
		if ( in_array( $post->post_type, $post_types, true ) && EnrollmentModel::is_enrolled( $post->ID ) ) {
			return false;
		}

		return $required;
	}

	/**
	 * Handle password protected course/bundle.
	 *
	 * @since 3.0.0
	 *
	 * @param string $template template path.
	 *
	 * @return string template path.
	 */
	public function handle_password_protected( $template ) {
		if ( is_single() ) {
			$current_post_type = get_post_type( get_the_ID() );
			$post_types        = array( tutor()->course_post_type, tutor()->bundle_post_type );
			if ( in_array( $current_post_type, $post_types, true ) && post_password_required() ) {
				remove_all_filters( 'template_include' );
				return tutor()->path . '/templates/single/password-protected.php';
			}
		}

		return $template;
	}

	/**
	 * Remove move to trash button on WordPress editor for instructor.
	 *
	 * @since 2.7.3
	 *
	 * @return void
	 */
	public function disable_course_trash_instructor() {
		$can_trash_post = tutor_utils()->get_option( 'instructor_can_delete_course' );
		$role           = get_role( tutor()->instructor_role );
		if ( ! $can_trash_post ) {
			$role->remove_cap( 'delete_tutor_courses' );
			$role->remove_cap( 'delete_tutor_course' );
		} else {
			$role->add_cap( 'delete_tutor_courses' );
			$role->add_cap( 'delete_tutor_course' );
		}
	}

	/**
	 * Check if the video source type is valid
	 *
	 * @since 3.0.0
	 *
	 * @param string $source_type source type.
	 *
	 * @return boolean
	 */
	private function is_valid_video_source_type( string $source_type ): bool {
		$supported_types = tutor_utils()->get_option( 'supported_video_sources', array() );
		if ( is_string( $supported_types ) ) {
			$supported_types = array( $supported_types );
		}

		return in_array( $source_type, $supported_types, true );
	}

	/**
	 * Get course selling options.
	 *
	 * @since 3.0.0
	 *
	 * @return array
	 */
	public static function get_selling_options() {
		return array(
			self::SELLING_OPTION_ONE_TIME,
			self::SELLING_OPTION_SUBSCRIPTION,
			self::SELLING_OPTION_BOTH,
			self::SELLING_OPTION_MEMBERSHIP,
			self::SELLING_OPTION_ALL,
		);
	}

	/**
	 * Get course selling option
	 *
	 * @since 3.0.0
	 *
	 * @param int $course_id course id.
	 *
	 * @return string
	 */
	public static function get_selling_option( $course_id ) {
		return get_post_meta( $course_id, self::COURSE_SELLING_OPTION_META, true );
	}

	/**
	 * Validate video source
	 *
	 * @since 3.0.0
	 *
	 * @param array $params array of params.
	 * @param array $errors array of errors.
	 *
	 * @return void
	 */
	public function validate_video_source( $params, &$errors ) {
		if ( isset( $params['video'] ) ) {
			$video_source_type = isset( $params['video']['source'] ) ? $params['video']['source'] : '';
			if ( tutor_is_rest() ) {
				$video_source_type = isset( $params['video']['source_type'] ) ? $params['video']['source_type'] : '';
			}

			if ( '' === $video_source_type ) {
				$errors['video_source'] = __( 'Video source is required', 'tutor' );
			} elseif ( ! $this->is_valid_video_source_type( $video_source_type ) ) {
					$errors['video_source'] = __( 'Invalid video source', 'tutor' );
			}
		}
	}

	/**
	 * Validate scheduled courses
	 *
	 * @since 3.3.0
	 *
	 * @param array $params array of params.
	 * @param array $errors array of errors.
	 *
	 * @return void
	 */
	public function validate_scheduled_course( $params, &$errors ) {
		if ( isset( $params['post_status'] ) && isset( $params['course_settings'] ) ) {
			if ( 'future' !== $params['post_status'] ) {
				return;
			}

			$course_settings = $params['course_settings'];

			if ( $course_settings['course_enrollment_period'] && 'no' === $course_settings['course_enrollment_period'] ) {
				return;
			}

			if ( isset( $course_settings['enrollment_starts_at'] ) && ! empty( $course_settings['enrollment_starts_at'] ) ) {
				$enrollment_start = strtotime( $course_settings['enrollment_starts_at'] );
				$scheduled_date   = strtotime( $params['post_date_gmt'] );

				if ( $enrollment_start < $scheduled_date ) {
					$errors['scheduled_course'] = __( 'The enrollment start date cannot be earlier than the course start date', 'tutor' );
				}
			}
		}
	}

	/**
	 * Prepare course categories & tags
	 *
	 * @since 3.0.0
	 *
	 * @param array $params post params.
	 * @param array $errors array of errors.
	 *
	 * @return void
	 */
	public function prepare_course_cats_tags( &$params, &$errors ) {
		if ( isset( $params['course_categories'] ) ) {
			if ( ! is_array( $params['course_categories'] ) || empty( $params['course_categories'] ) ) {
				$errors['course_categories'] = __( 'Invalid course categories', 'tutor' );
			} else {
				$params['course_categories'] = $params['course_categories'];
			}
		}

		if ( isset( $params['course_tags'] ) ) {
			if ( ! is_array( $params['course_tags'] ) || empty( $params['course_tags'] ) ) {
				$errors['course_tags'] = __( 'Invalid course tags', 'tutor' );
			} else {
				$params['course_tags'] = $params['course_tags'];
			}
		}
	}

	/**
	 * Setup course categories and tags.
	 *
	 * @since 3.0.0
	 *
	 * @param int   $post_id Post ID.
	 * @param array $params  Array of params.
	 *
	 * @return void
	 */
	public function setup_course_categories_tags( $post_id, $params ) {
		if ( isset( $params['course_categories'] ) && is_array( $params['course_categories'] ) ) {
			$valid_category_ids = ValidationHelper::validate_term_ids(
				$params['course_categories'],
				CourseModel::COURSE_CATEGORY
			);
			wp_set_object_terms( $post_id, $valid_category_ids, CourseModel::COURSE_CATEGORY );
		} else {
			wp_set_object_terms( $post_id, array(), CourseModel::COURSE_CATEGORY );
		}

		if ( isset( $params['course_tags'] ) && is_array( $params['course_tags'] ) ) {
			$valid_tag_ids = ValidationHelper::validate_term_ids(
				$params['course_tags'],
				CourseModel::COURSE_TAG
			);
			wp_set_object_terms( $post_id, $valid_tag_ids, CourseModel::COURSE_TAG );
		} else {
			wp_set_object_terms( $post_id, array(), CourseModel::COURSE_TAG );
		}
	}

	/**
	 * Validate price for course create
	 *
	 * @since 3.0.0
	 *
	 * @param array $params array of params.
	 * @param array $errors array of errors.
	 *
	 * @return void
	 */
	public function validate_price( $params, &$errors ) {
		if ( isset( $params['pricing'] ) ) {
			$type = $params['pricing']['type'] ?? '';

			if ( '' === $type || ! in_array( $type, array( self::PRICE_TYPE_FREE, self::PRICE_TYPE_PAID ), true ) ) {
				$errors['pricing'] = __( 'Invalid price type', 'tutor' );
			}

			if ( self::PRICE_TYPE_PAID === $type ) {
				$monetize_by = tutor_utils()->get_option( 'monetize_by' );
				if ( 'wc' === $monetize_by ) {
					$product_id = (int) isset( $params['pricing']['product_id'] ) ? $params['pricing']['product_id'] : 0;
					// $product_id = 0 then new WC product will be created.
					if ( $product_id ) {
						$product = wc_get_product( $product_id );
						if ( is_a( $product, 'WC_Product' ) ) {
							$is_linked_with_course = tutor_utils()->product_belongs_with_course( $product_id );
							if ( $is_linked_with_course ) {
								$errors['pricing'] = __( 'Product already linked with course', 'tutor' );
							}
						}
					}
				}
			}
		}
	}

	/**
	 * Validate price
	 *
	 * @since 3.0.0
	 *
	 * @param array $params array of params.
	 * @param array $errors array of errors.
	 * @param int   $course_id course id.
	 *
	 * @return void
	 */
	public function validate_price_for_update( $params, &$errors, $course_id ) {
		if ( isset( $params['pricing'] ) ) {
			$type = $params['pricing']['type'] ?? '';

			if ( '' === $type || ! in_array( $type, array( self::PRICE_TYPE_FREE, self::PRICE_TYPE_PAID, self::PRICE_TYPE_SUBSCRIPTION ), true ) ) {
				$errors['pricing'] = __( 'Invalid price type', 'tutor' );
			}

			if ( self::PRICE_TYPE_PAID === $type ) {
				$monetize_by = tutor_utils()->get_option( 'monetize_by' );
				if ( 'wc' === $monetize_by ) {
					$course_product_id = tutor_utils()->get_course_product_id( $course_id );
					$product_id        = isset( $params['pricing']['product_id'] ) ? (int) $params['pricing']['product_id'] : 0;

					if ( $product_id ) {
						$product = wc_get_product( $product_id );
						if ( is_a( $product, 'WC_Product' ) ) {
							if ( $course_product_id !== $product_id ) {
								$is_linked_with_course = tutor_utils()->product_belongs_with_course( $product_id );
								if ( $is_linked_with_course ) {
									$errors['pricing'] = __( 'Product already linked with course', 'tutor' );
								}
							}
						} else {
							$errors['pricing'] = __( 'Invalid product', 'tutor' );
						}
					} else {
						/**
						 * If user does not select WC product
						 * Then automatic WC product will be create name with course title.
						 */
						if ( ! isset( $params['course_price'] ) || ! floatval( $params['course_price'] ) ) {
							$errors['pricing'] = __( 'Price is required', 'tutor' );
						}
					}
				}
			}
		}
	}

	/**
	 * Prepare course meta data for update
	 *
	 * @param array $params params.
	 *
	 * @return void
	 */
	public function prepare_create_post_meta( $params ) {
		$additional_content = isset( $params['additional_content'] ) ? $params['additional_content'] : array();

		$course_benefits = isset( $additional_content['course_benefits'] ) ? $additional_content['course_benefits'] : '';

		$course_target_audience = isset( $additional_content['course_target_audience'] ) ? $additional_content['course_target_audience'] : '';

		$course_duration = isset( $additional_content['course_duration'] ) ? array(
			'hours'   => $additional_content['course_duration']['hours'] ?? '',
			'minutes' => $additional_content['course_duration']['minutes'] ?? '',
		) : array();

		$course_materials = isset( $additional_content['course_material_includes'] ) ? $additional_content['course_material_includes'] : '';

		$course_requirements = isset( $additional_content['course_requirements'] ) ? $additional_content['course_requirements'] : '';

		$pricing = isset( $params['pricing'] ) ? array(
			'type'       => $params['pricing']['type'] ?? self::PRICE_TYPE_FREE,
			'product_id' => (int) $params['pricing']['product_id'] ?? -1,
		) : array(
			'type'       => self::PRICE_TYPE_FREE,
			'product_id' => -1,
		);

		// Setup global $_POST array.
		$_POST['_tutor_course_additional_data_edit'] = true;

		$_POST['tutor_course_price_type']  = $pricing['type'];
		$_POST['course_duration']          = $course_duration;
		$_POST['tutor_course_price_type']  = $pricing['type'];
		$_POST['_tutor_course_product_id'] = $pricing['product_id'];
		$_POST['_tutor_course_level']      = $params['course_level'];
		$_POST['course_benefits']          = $course_benefits;
		$_POST['course_requirements']      = $course_requirements;
		$_POST['course_target_audience']   = $course_target_audience;
		$_POST['course_material_includes'] = $course_materials;

		if ( isset( $params['enable_qna'] ) && 'yes' === $params['enable_qna'] ) {
			$_POST['_tutor_enable_qa'] = 'yes';
		}

		if ( isset( $params['_tutor_is_public_course'] ) && 'yes' === $params['_tutor_is_public_course'] ) {
			$_POST['_tutor_is_public_course'] = 'yes';
		}

		// Set course price.
		if ( -1 !== $pricing['product_id'] ) {
			$product = wc_get_product( $pricing['product_id'] );
			if ( is_a( $product, 'WC_Product' ) ) {
				$regular_price = $product->get_regular_price();
				$sale_price    = $product->get_sale_price();

				$_POST['course_price']      = $regular_price;
				$_POST['course_sale_price'] = $sale_price;
			}
		}
	}

	/**
	 * Prepare course meta data for update
	 *
	 * @since 3.0.0
	 *
	 * @param array $params params.
	 *
	 * @throws \Exception Throw new exception.
	 *
	 * @return mixed
	 */
	public function prepare_update_post_meta( $params ) {
		$post_id = (int) $params['ID'];

		$additional_content = isset( $params['additional_content'] ) ? $params['additional_content'] : array();

		if ( ! empty( $additional_content ) ) {

			$course_benefits = isset( $additional_content['course_benefits'] ) ? $additional_content['course_benefits'] : '';

			$course_target_audience = isset( $additional_content['course_target_audience'] ) ? $additional_content['course_target_audience'] : '';

			$course_duration = isset( $additional_content['course_duration'] ) ? array(
				'hours'   => $additional_content['course_duration']['hours'] ?? '',
				'minutes' => $additional_content['course_duration']['minutes'] ?? '',
			) : array();

			$course_materials = isset( $additional_content['course_material_includes'] ) ? $additional_content['course_material_includes'] : '';

			$course_requirements = isset( $additional_content['course_requirements'] ) ? $additional_content['course_requirements'] : '';

			if ( '' !== $course_benefits ) {
				update_post_meta( $post_id, '_tutor_course_benefits', $course_benefits );
			}

			if ( '' !== $course_requirements ) {
				update_post_meta( $post_id, '_tutor_course_requirements', $course_requirements );
			}

			if ( '' !== $course_target_audience ) {
				update_post_meta( $post_id, '_tutor_course_target_audience', $course_target_audience );
			}

			if ( '' !== $course_materials ) {
				update_post_meta( $post_id, '_tutor_course_material_includes', $course_materials );
			}

			if ( ! empty( $course_duration ) ) {
				update_post_meta( $post_id, '_course_duration', $course_duration );
			}
		}

		if ( isset( $params['pricing'] ) && ! empty( $params['pricing'] ) ) {
			try {
				if ( isset( $params['pricing']['type'] ) ) {
					update_post_meta( $post_id, self::COURSE_PRICE_TYPE_META, $params['pricing']['type'] );
				}
			} catch ( \Throwable $th ) {
				throw new \Exception( $th->getMessage() );
			}
		}

		update_post_meta( $post_id, '_tutor_enable_qa', $params['enable_qna'] ?? 'yes' );
		update_post_meta( $post_id, '_tutor_is_public_course', $params['is_public_course'] ?? 'no' );
		update_post_meta( $post_id, '_tutor_course_level', $params['course_level'] );

		/**
		 * Save tax collection settings
		 *
		 * @since 3.7.0
		 */
		if ( isset( $params['tax_on_single'] ) ) {
			update_post_meta( $post_id, self::TAX_ON_SINGLE_META, $params['tax_on_single'] );
		}

		if ( isset( $params['tax_on_subscription'] ) ) {
			update_post_meta( $post_id, self::TAX_ON_SUBSCRIPTION_META, $params['tax_on_subscription'] );
		}

		do_action( 'tutor_after_prepare_update_post_meta', $post_id, $params );
	}

	/**
	 * Prepare course settings meta
	 *
	 * @since 3.0.0
	 *
	 * @param array $params params.
	 *
	 * @return void
	 */
	public function prepare_course_settings( $params ) {
		if ( isset( $params['course_settings'] ) ) {
			$_POST['_tutor_course_settings'] = $params['course_settings'];
		}
	}

	/**
	 * Check access before course builder ajax request.
	 *
	 * @since 3.0.0
	 *
	 * @param int $course_id course id.
	 *
	 * @return void
	 */
	public function check_access( $course_id = null ) {
		$has_access = false;

		if ( $course_id ) {
			$has_access = tutor_utils()->can_user_edit_course( get_current_user_id(), $course_id );
		} else {
			$has_access = User::is_admin() || User::is_instructor();
		}

		if ( ! $has_access ) {
			$this->json_response(
				tutor_utils()->error_message( HttpHelper::STATUS_UNAUTHORIZED ),
				null,
				HttpHelper::STATUS_UNAUTHORIZED
			);
		}
	}

	/**
	 * Validate request inputs.
	 *
	 * @param array $params input params.
	 * @param array $exclude exclude key from rules.
	 *
	 * @return object
	 */
	public function validate_inputs( $params, $exclude = array() ) {
		$status_str = implode( ',', CourseModel::get_status_list() );
		$rules      = array(
			'course_id'        => 'required|numeric',
			'post_title'       => 'required',
			'post_author'      => 'user_exists',
			'post_status'      => "required|match_string:{$status_str}",
			'enable_qna'       => 'if_input|match_string:yes,no',
			'is_public_course' => 'if_input|match_string:yes,no',
		);

		foreach ( $exclude as $key ) {
			if ( isset( $rules[ $key ] ) ) {
				unset( $rules[ $key ] );
			}
		}

		return ValidationHelper::validate( $rules, $params );
	}

	/**
	 * Create new draft course
	 *
	 * @since 3.0.0
	 *
	 * @return void  JSON response
	 */
	public function ajax_create_new_draft_course() {
		tutor_utils()->check_nonce();

		$this->check_access();

		$course_id = wp_insert_post(
			array(
				'post_title'  => __( 'New Course', 'tutor' ),
				'post_type'   => tutor()->course_post_type,
				'post_status' => 'draft',
				'post_name'   => 'new-course',
			)
		);

		if ( is_wp_error( $course_id ) ) {
			$this->json_response( $course_id->get_error_message(), null, HttpHelper::STATUS_INTERNAL_SERVER_ERROR );
		}

		update_post_meta( $course_id, self::COURSE_PRICE_TYPE_META, self::PRICE_TYPE_FREE );

		$link = admin_url( 'admin.php?page=create-course' );
		if ( tutor()->has_pro && Input::post( 'from_dashboard', false, Input::TYPE_BOOL ) ) {
			$link = tutor_utils()->tutor_dashboard_url( 'create-course' );
		}

		$link = add_query_arg( array( 'course_id' => $course_id ), $link );

		do_action( 'tutor_draft_course_created', $course_id );

		$this->json_response(
			__( 'Draft course created', 'tutor' ),
			$link,
			HttpHelper::STATUS_CREATED
		);
	}

	/**
	 * Get course list
	 *
	 * @since 3.0.0
	 *
	 * @since 3.2.0 Refactor the arguments & response as per new design.
	 *
	 * @return void
	 */
	public function ajax_course_list() {
		tutor_utils()->check_nonce();
		$this->check_access();

		$limit       = Input::post( 'limit', 10, Input::TYPE_INT );
		$offset      = Input::post( 'offset', 0, Input::TYPE_INT );
		$search_term = '';
		$post_status = Input::post( 'post_status', null );

		$filter = json_decode( wp_unslash( $_POST['filter'] ) ); //phpcs:ignore --sanitized already
		if ( ! empty( $filter ) && property_exists( $filter, 'search' ) ) {
			$search_term = Input::sanitize( $filter->search );
		}

		$args = array(
			'post_status'    => is_null( $post_status ) ? 'publish' : $post_status,
			'posts_per_page' => $limit,
			'offset'         => $offset,
			's'              => $search_term,
		);

		$exclude = Input::post( 'exclude', array(), Input::TYPE_ARRAY );
		if ( count( $exclude ) ) {
			$exclude              = array_filter( $exclude, fn( $id ) => is_numeric( $id ) && $id > 0 );
			$args['post__not_in'] = $exclude;
		}

		$courses = CourseModel::get_courses_by_args( $args );

		$response = array(
			'results'     => array(),
			'total_items' => 0,
		);

		$response['total_items'] = is_a( $courses, 'WP_Query' ) ? $courses->found_posts : 0;

		if ( is_a( $courses, 'WP_Query' ) && $courses->have_posts() ) {
			$courses = $courses->get_posts();
			foreach ( $courses as $course ) {
				$response['results'][] = self::get_mini_info( $course );
			}
		}

		$this->json_response(
			__( 'Course list retrieved successfully!', 'tutor' ),
			$response
		);
	}

	/**
	 * Create course by ajax request.
	 *
	 * @since 3.0.0
	 *
	 * @return void
	 */
	public function ajax_create_course() {
		tutor_utils()->check_nonce();

		$this->check_access();

		$params = Input::sanitize_array(
			//phpcs:ignore WordPress.Security.NonceVerification.Missing
			$_POST,
			array(
				'post_content'             => 'wp_kses_post',
				'course_material_includes' => 'sanitize_textarea_field',
			)
		);

		$params['post_type'] = tutor()->course_post_type;

		// Validate inputs.
		$errors     = array();
		$validation = $this->validate_inputs( $params, array( 'course_id' ) );
		if ( ! $validation->success ) {
			$errors = $validation->errors;
		}

		if ( User::is_instructor() ) {
			$params['post_author'] = get_current_user_id();
		}

		// Validate video source if user set video.
		$this->validate_video_source( $params, $errors );

		// Validate WC product.
		$this->validate_price( $params, $errors );

		// Set course categories and tags.
		$this->prepare_course_cats_tags( $params, $errors );
		$this->setup_course_price( $params );

		if ( ! empty( $errors ) ) {
			$this->json_response( __( 'Invalid input', 'tutor' ), $errors, HttpHelper::STATUS_UNPROCESSABLE_ENTITY );
		}

		$this->prepare_course_settings( $params );

		try {
			$this->prepare_create_post_meta( $params );
		} catch ( \Exception $e ) {
			$this->json_response( $e->getMessage(), null, HttpHelper::STATUS_INTERNAL_SERVER_ERROR );
		}

		$course_id = wp_insert_post( $params );
		if ( is_wp_error( $course_id ) ) {
			$this->json_response( $course_id->get_error_message(), null, HttpHelper::STATUS_INTERNAL_SERVER_ERROR );
		}

		// Set course cats & tags.
		$this->setup_course_categories_tags( $course_id, $params );

		// Update course thumb.
		if ( isset( $params['thumbnail_id'] ) ) {
			set_post_thumbnail( $course_id, $params['thumbnail_id'] );
		}

		$this->json_response(
			__( 'Course created successfully', 'tutor' ),
			$course_id,
			HttpHelper::STATUS_CREATED
		);
	}

	/**
	 * Setup course price
	 *
	 * @since 3.0.0
	 *
	 * @param array $params params.
	 *
	 * @return void
	 */
	public function setup_course_price( $params ) {
		if ( isset( $params['pricing'] )
			&& isset( $params['pricing']['product_id'] )
			&& is_numeric( $params['pricing']['product_id'] ) ) {
			$_POST['_tutor_course_product_id'] = $params['pricing']['product_id'];
		}
	}

	/**
	 * Update course by ajax request.
	 *
	 * @since 3.0.0
	 *
	 * @return void
	 */
	public function ajax_update_course() {
		tutor_utils()->check_nonce();

		$params = Input::sanitize_array(
			//phpcs:ignore WordPress.Security.NonceVerification.Missing
			wp_slash( $_POST ),
			array(
				'post_content'             => 'wp_kses_post',
				'course_benefits'          => 'sanitize_textarea_field',
				'course_target_audience'   => 'sanitize_textarea_field',
				'course_material_includes' => 'sanitize_textarea_field',
				'course_requirements'      => 'sanitize_textarea_field',
			)
		);

		$course_id = (int) $params['course_id'];
		if ( ! $course_id ) {
			$this->response_bad_request( __( 'Invalid course id', 'tutor' ) );
		}

		$this->check_access( $course_id );

		$errors     = array();
		$validation = $this->validate_inputs( $params );
		if ( ! $validation->success ) {
			$errors = $validation->errors;
		}

		// Validate video source if user set video.
		$this->validate_video_source( $params, $errors );

		// Validate WC product.
		$this->validate_price_for_update( $params, $errors, $course_id );

		// Set course categories and tags.
		$this->prepare_course_cats_tags( $params, $errors );

		$this->prepare_course_settings( $params );

		// Validate scheduled courses.
		$this->validate_scheduled_course( $params, $errors );

		$this->setup_course_price( $params );

		if ( ! empty( $errors ) ) {
			$this->json_response( __( 'Invalid input', 'tutor' ), $errors, HttpHelper::STATUS_UNPROCESSABLE_ENTITY );
		}

		/**
		 * Can trash a course when user is admin or option `instructor_can_delete_course` is turned on.
		 */
		if ( CourseModel::STATUS_TRASH === $params['post_status'] ) {
			if ( User::is_admin() || tutor_utils()->get_option( 'instructor_can_delete_course', false ) ) {
				$params['post_status'] = CourseModel::STATUS_TRASH;
			} else {
				unset( $params['post_status'] );
			}
		}

		/**
		 * Can publish a course when user is admin or option `instructor_can_publish_course` is turned on.
		 * If instructor_can_publish_course is turned off then course status will be pending.
		 */
		if ( CourseModel::STATUS_PUBLISH === $params['post_status'] ) {
			$is_instructor_allowed_to_publish = (bool) tutor_utils()->get_option( 'instructor_can_publish_course', false );
			if ( ! User::is_admin() && ! $is_instructor_allowed_to_publish ) {
				$params['post_status'] = CourseModel::STATUS_PENDING;
			}
		}

		$is_error = apply_filters( 'tutor_is_error_before_course_update', false, $params );
		if ( is_wp_error( $is_error ) ) {
			$this->response_bad_request( $is_error->get_error_message() );
		}

		$params['ID'] = $course_id;
		$update_id    = wp_update_post( $params, true );
		if ( is_wp_error( $update_id ) ) {
			$this->json_response( $update_id->get_error_message(), null, HttpHelper::STATUS_INTERNAL_SERVER_ERROR );
		}

		$this->setup_course_categories_tags( $update_id, $params );
		$this->prepare_update_post_meta( $params );

		// Update course thumb.
		$thumbnail_id = Input::post( 'thumbnail_id', 0, Input::TYPE_INT );
		if ( $thumbnail_id ) {
			set_post_thumbnail( $update_id, $thumbnail_id );
		} else {
			delete_post_meta( $update_id, '_thumbnail_id' );
		}

		$this->json_response(
			__( 'Course updated successfully.', 'tutor' ),
			$update_id,
			HttpHelper::STATUS_OK
		);
	}

	/**
	 * Unlink page builder from editor.
	 *
	 * @since 3.0.0
	 *
	 * @return void
	 */
	public function ajax_unlink_page_builder() {
		tutor_utils()->check_nonce();

		$course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
		if ( ! $course_id ) {
			$this->response_bad_request( __( 'Invalid course id', 'tutor' ) );
		}

		$this->check_access( $course_id );

		$builder = Input::post( 'builder' );
		if ( 'elementor' === $builder ) {
			delete_post_meta( $course_id, '_elementor_edit_mode' );
		} elseif ( 'droip' === $builder ) {
			delete_post_meta( $course_id, 'droip_editor_mode' );
		} elseif ( 'divi' === $builder ) {
			$old_post_content     = get_post_meta( $course_id, '_et_pb_old_content', true );
			$course               = get_post( $course_id );
			$course->post_content = $old_post_content;
			$result               = wp_update_post( $course );

			if ( $result && ! is_wp_error( $result ) ) {
				update_post_meta( $course_id, '_et_pb_use_builder', 'off' );
				update_post_meta( $course_id, '_et_pb_old_content', '' );
				delete_post_meta( $course_id, '_et_dynamic_cached_shortcodes' );
				delete_post_meta( $course_id, '_et_dynamic_cached_attributes' );
				delete_post_meta( $course_id, '_et_builder_module_features_cache' );
			}
		}

		$this->json_response(
			__( 'Builder unlinked successfully.', 'tutor' ),
			$course_id,
			HttpHelper::STATUS_OK
		);
	}

	/**
	 * Get all course contents by course id.
	 *
	 * @since 3.0.0
	 *
	 * @param int $course_id course id.
	 *
	 * @return array
	 */
	public function get_course_contents( $course_id ) {
		$data   = array();
		$topics = tutor_utils()->get_topics( $course_id );

		if ( $topics->have_posts() ) {
			foreach ( $topics->get_posts() as $post ) {
				$current_topic = array(
					'id'       => $post->ID,
					'title'    => $post->post_title,
					'summary'  => $post->post_content,
					'contents' => array(),
				);

				$topic_contents = tutor_utils()->get_course_contents_by_topic( $post->ID, -1 );

				if ( $topic_contents->have_posts() ) {
					foreach ( $topic_contents->get_posts() as $post ) {
						if ( tutor()->quiz_post_type === $post->post_type ) {
							$questions            = tutor_utils()->get_questions_by_quiz( $post->ID );
							$post->total_question = is_array( $questions ) ? count( $questions ) : 0;
						}

						array_push( $current_topic['contents'], $post );
					}
				}

				$current_topic = apply_filters( 'tutor_filter_course_content', $current_topic );

				array_push( $data, $current_topic );
			}
		}

		return $data;
	}

	/**
	 * Get course contents
	 *
	 * @since 3.0.0
	 *
	 * @return void
	 */
	public function ajax_course_contents() {
		tutor_utils()->check_nonce();

		$errors    = array();
		$course_id = Input::post( 'course_id', 0, Input::TYPE_INT );

		if ( ! $course_id || tutor()->course_post_type !== get_post_type( $course_id ) ) {
			$errors['course_id'] = __( 'Invalid course id', 'tutor' );
		}

		$this->check_access( $course_id );

		if ( ! empty( $errors ) ) {
			$this->json_response( __( 'Invalid input', 'tutor' ), $errors, HttpHelper::STATUS_UNPROCESSABLE_ENTITY );
		}

		$contents = $this->get_course_contents( $course_id );

		$this->json_response(
			__( 'Course contents fetched successfully', 'tutor' ),
			$contents
		);
	}

	/**
	 * Get course details by ID
	 *
	 * @since 3.0.0
	 *
	 * @return void
	 */
	public function ajax_course_details() {
		tutor_utils()->check_nonce();

		$errors    = array();
		$course_id = Input::post( 'course_id', 0, Input::TYPE_INT );

		if ( ! $course_id || tutor()->course_post_type !== get_post_type( $course_id ) ) {
			$errors['course_id'] = __( 'Invalid course id', 'tutor' );
		}

		$this->check_access( $course_id );

		if ( ! empty( $errors ) ) {
			$this->json_response( __( 'Invalid input', 'tutor' ), $errors, HttpHelper::STATUS_UNPROCESSABLE_ENTITY );
		}

		$price_type  = tutor_utils()->price_type( $course_id );
		$monetize_by = tutor_utils()->get_option( 'monetize_by' );

		$product_name = '';
		$price        = 0;
		$sale_price   = 0;
		$product_id   = tutor_utils()->get_course_product_id( $course_id );

		if ( WooCommerce::MONETIZE_BY === $monetize_by ) {
			$product = wc_get_product( $product_id );
			if ( $product ) {
				$product_name = $product->get_name();
				$price        = $product->get_regular_price();
				$sale_price   = $product->get_sale_price();
			}
		}

		if ( Ecommerce::MONETIZE_BY === $monetize_by ) {
			$price      = get_post_meta( $course_id, self::COURSE_PRICE_META, true );
			$sale_price = get_post_meta( $course_id, self::COURSE_SALE_PRICE_META, true );
		}

		$course_pricing = array(
			'type'         => $price_type,
			'product_id'   => $product_id,
			'product_name' => $product_name,
			'price'        => $price,
			'sale_price'   => $sale_price,
		);

		$video_intro = get_post_meta( $course_id, '_video', true );
		if ( $video_intro ) {
			$source = $video_intro['source'] ?? '';
			if ( 'html5' === $source ) {
					$poster_url            = wp_get_attachment_url( $video['poster'] ?? 0 );
					$source_html5          = wp_get_attachment_url( $video['source_video_id'] ?? 0 );
					$video['poster_url']   = $poster_url;
					$video['source_html5'] = $source_html5;
			}
		}

		$course = get_post( $course_id, ARRAY_A );
		if ( $course ) {
			$course['post_name'] = urldecode( $course['post_name'] );
		}

		$editors = tutor_utils()->get_editor_list( $course_id );

		$data = array(
			'editors'                  => array_values( $editors ),
			'editor_used'              => tutor_utils()->get_editor_used( $course_id ),
			'preview_link'             => get_preview_post_link( $course_id ),
			'post_author'              => tutor_utils()->get_tutor_user( $course['post_author'] ),
			'course_categories'        => wp_get_post_terms( $course_id, CourseModel::COURSE_CATEGORY ),
			'course_tags'              => wp_get_post_terms( $course_id, CourseModel::COURSE_TAG ),
			'thumbnail_id'             => get_post_meta( $course_id, '_thumbnail_id', true ),
			'thumbnail'                => get_the_post_thumbnail_url( $course_id ),

			'enable_qna'               => get_post_meta( $course_id, '_tutor_enable_qa', true ),
			'is_public_course'         => get_post_meta( $course_id, '_tutor_is_public_course', true ),
			'course_level'             => get_post_meta( $course_id, '_tutor_course_level', true ),
			'video'                    => $video_intro,
			'course_duration'          => get_post_meta( $course_id, '_course_duration', true ),
			'course_benefits'          => get_post_meta( $course_id, '_tutor_course_benefits', true ),
			'course_requirements'      => get_post_meta( $course_id, '_tutor_course_requirements', true ),
			'course_target_audience'   => get_post_meta( $course_id, '_tutor_course_target_audience', true ),
			'course_material_includes' => get_post_meta( $course_id, '_tutor_course_material_includes', true ),
			'monetize_by'              => $monetize_by,
			'course_pricing'           => $course_pricing,
			'course_settings'          => get_post_meta( $course_id, '_tutor_course_settings', true ),
			'step_completion_status'   => array(
				'basic'       => true,
				'curriculum'  => false,
				'additional'  => false,
				'certificate' => false,
			),
		);

		$tax_on_single       = get_post_meta( $course_id, self::TAX_ON_SINGLE_META, true );
		$tax_on_subscription = get_post_meta( $course_id, self::TAX_ON_SUBSCRIPTION_META, true );

		$data['tax_collection'] = array(
			'tax_on_single'       => '' === $tax_on_single ? '1' : $tax_on_single,
			'tax_on_subscription' => '' === $tax_on_subscription ? '1' : $tax_on_subscription,
		);

		$data = apply_filters( 'tutor_course_details_response', array_merge( $course, $data ) );

		$this->json_response( __( 'Data retrieved successfully!', 'tutor' ), $data );
	}

	/**
	 * Load course builder.
	 *
	 * @since 3.0.0
	 *
	 * @return void
	 */
	public function load_course_builder() {
		global $pagenow;

		$has_pro         = tutor()->has_pro;
		$has_access_role = User::has_any_role( array( User::ADMIN, User::INSTRUCTOR ) );

		$course_id       = Input::get( 'course_id', 0, Input::TYPE_INT );
		$backend_builder = is_admin() && 'admin.php' === $pagenow && 'create-course' === Input::get( 'page' );
		$backend_edit    = $backend_builder && $course_id;

		$is_frontend_builder = tutor_utils()->is_tutor_frontend_dashboard( 'create-course' );
		$frontend_edit       = $is_frontend_builder && $course_id;

		if ( $has_access_role && ( $backend_edit || ( $has_pro && $frontend_edit ) ) ) {
			$post_type       = get_post_type( $course_id );
			$can_edit_course = tutor_utils()->can_user_edit_course( get_current_user_id(), $course_id );

			if ( tutor()->course_post_type === $post_type && ( User::is_admin() || $can_edit_course ) ) {
				/**
				 * Edit trash course behavior
				 *
				 * @since 3.0.0
				 */
				if ( CourseModel::STATUS_TRASH === get_post_status( $course_id ) ) {
					$message = User::is_admin()
								? __( 'You cannot edit this course because it is in the Trash. Please restore it and try again', 'tutor' )
								: tutor_utils()->error_message();
					wp_die( esc_html( $message ) );
				}

				$this->load_course_builder_view();
			}
		}
	}

	/**
	 * Enqueue course builder assets like CSS, JS
	 *
	 * @since 3.0.0
	 *
	 * @return void
	 */
	public function enqueue_course_builder_assets() {
		// Fix: function print_emoji_styles is deprecated since version 6.4.0!
		remove_action( 'wp_print_styles', 'print_emoji_styles' );
		remove_action( 'wp_head', 'wp_admin_bar_header' );
		add_action( 'wp_head', 'wp_enqueue_admin_bar_header_styles' );

		do_action( 'tutor_course_builder_before_wp_editor_load' );
		wp_enqueue_script( 'wp-tinymce' );
		wp_enqueue_script( 'mce-view' );
		wp_enqueue_editor();

		wp_enqueue_media();
		wp_enqueue_script( 'tutor-course-builder', tutor()->url . 'assets/js/tutor-course-builder.js', array( 'wp-date', 'wp-i18n', 'wp-element', 'wp-api' ), TUTOR_VERSION, true );
		wp_set_script_translations( 'tutor-course-builder', 'tutor', tutor()->path . 'languages/' );

		wp_localize_script(
			'mce-view',
			'mceViewL10n',
			array(
				'shortcodes' => ! empty( $GLOBALS['shortcode_tags'] ) ? array_keys( $GLOBALS['shortcode_tags'] ) : array(),
			)
		);
	}

	/**
	 * Localize custom course builder data for _tutorobject.
	 *
	 * @since 3.3.1
	 *
	 * @param array $data the localized data.
	 *
	 * @return array
	 */
	public function localize_course_builder_data( $data ) {
		global $pagenow;

		$course_id       = Input::get( 'course_id', 0, Input::TYPE_INT );
		$backend_builder = is_admin() && 'admin.php' === $pagenow && 'create-course' === Input::get( 'page' );
		$backend_edit    = $backend_builder && $course_id;

		$is_frontend_builder = tutor_utils()->is_tutor_frontend_dashboard( 'create-course' );
		$frontend_edit       = $is_frontend_builder && $course_id;

		if ( ! $backend_edit && ! $frontend_edit ) {
			return $data;
		}

		/**
		 * Prepare course builder data.
		 */
		$default_data = ( new Assets( false ) )->get_default_localized_data();

		if ( isset( $default_data['current_user']['data']['id'] ) ) {
			$tutor_user = tutor_utils()->get_tutor_user( $default_data['current_user']['data']['id'] );
			$default_data['current_user']['data']['tutor_profile_photo_url'] = $tutor_user->tutor_profile_photo_url;
		}

		/**
		 * Localized only options to protect sensitive info like API keys.
		 */
		$required_options = array(
			'learning_mode',
			'monetize_by',
			'enable_course_marketplace',
			'course_permalink_base',
			'supported_video_sources',
			'enrollment_expiry_enabled',
			'enable_q_and_a_on_course',
			'instructor_can_delete_course',
			'chatgpt_enable',
			'hide_admin_bar_for_users',
			'enable_redirect_on_course_publish_from_frontend',
			'instructor_can_publish_course',
			'instructor_can_change_course_author',
			'instructor_can_manage_co_instructors',
		);

		$full_settings                       = get_option( 'tutor_option', array() );
		$settings                            = Options_V2::get_only( $required_options );
		$settings['course_builder_logo_url'] = wp_get_attachment_image_url( $full_settings['tutor_frontend_course_page_logo_id'] ?? 0, 'full' );
		$settings['chatgpt_key_exist']       = tutor()->has_pro && ! empty( $full_settings['chatgpt_api_key'] ?? '' );
		$settings['youtube_api_key_exist']   = ! empty( $full_settings['lesson_video_duration_youtube_api_key'] ?? '' );

		$settings['enable_tax']                    = Tax::get_setting( 'enable_tax', true );
		$settings['is_tax_included_in_price']      = Tax::is_tax_included_in_price();
		$settings['enable_individual_tax_control'] = Tax::get_setting( 'enable_individual_tax_control' );

		$new_data = array( 'settings' => $settings );

		$data = array_merge( $default_data, $new_data );

		/**
		 * Course builder dashboard URL based on role and settings.
		 */
		$dashboard_url = tutor_utils()->tutor_dashboard_url();
		if ( User::is_admin() ) {
			$dashboard_url = get_admin_url();
		}

		/**
		 * EDD product list
		 */
		$monetize_by = tutor_utils()->get_option( 'monetize_by' );
		if ( 'edd' === $monetize_by && tutor_utils()->has_edd() ) {
			$data['edd_products'] = tutor_utils()->get_edd_products();
		}

		$difficulty_levels = array();
		foreach ( tutor_utils()->course_levels() as $value => $label ) {
			$difficulty_levels[] = array(
				'label' => $label,
				'value' => $value,
			);
		}

		$supported_video_sources = array();
		$saved_video_source_list = (array) ( $settings['supported_video_sources'] ?? array() );

		foreach ( tutor_utils()->get_video_sources( true ) as $value => $label ) {
			if ( in_array( $value, $saved_video_source_list, true ) ) {
				$supported_video_sources[] = array(
					'label' => $label,
					'value' => $value,
				);
			}
		}

		$data['dashboard_url']            = $dashboard_url;
		$data['backend_course_list_url']  = get_admin_url( null, 'admin.php?page=tutor' );
		$data['frontend_course_list_url'] = tutor_utils()->tutor_dashboard_url( 'my-courses' );
		$data['timezones']                = tutor_global_timezone_lists();
		$data['difficulty_levels']        = $difficulty_levels;
		$data['supported_video_sources']  = $supported_video_sources;
		$data['wp_rest_nonce']            = wp_create_nonce( 'wp_rest' );

		if ( 'en_US' !== $data['local'] ) {
			$data['course_builder_basic_locales']      = tutils()->get_script_locale_data( 'tutor-course-builder-basic', $data['local'] );
			$data['course_builder_curriculum_locales'] = tutils()->get_script_locale_data( 'tutor-course-builder-curriculum', $data['local'] );
			$data['course_builder_additional_locales'] = tutils()->get_script_locale_data( 'tutor-course-builder-additional', $data['local'] );
		}

		$data = apply_filters( 'tutor_course_builder_localized_data', $data );

		return $data;
	}

	/**
	 * Load view for course builder.
	 *
	 * @since 3.0.0
	 *
	 * @return void
	 */
	public function load_course_builder_view() {
		/**
		 * Hide admin menu and footer.
		 *
		 * @since 3.3.0
		 */
		echo '<style>
			#adminmenumain, #wpfooter, .notice, #tutor-page-wrap { display: none !important; }
			#wpcontent { margin: 0 !important; padding: 0 !important; }
			#wpbody-content { padding-bottom: 0px !important; float: none; }
		</style>';

		do_action( 'tutor_before_course_builder_load' );
		include_once tutor()->path . 'views/pages/course-builder.php';
		do_action( 'tutor_after_course_builder_load' );
	}

	/**
	 * Add enroll require login class
	 *
	 * @since 2.6.0
	 *
	 * @param string $class_name css class name.
	 *
	 * @return string
	 */
	public function add_enroll_required_login_class( $class_name ) {
		$enabled_tutor_login = tutor_utils()->get_option( 'enable_tutor_native_login', null, true, true );
		if ( ! $enabled_tutor_login ) {
			return '';
		}

		return $class_name;
	}

	/**
	 * Get list of WC products.
	 *
	 * @since 2.5.0
	 * @since 3.0.0 exclude_linked_products, course_id are added.
	 *
	 * @return void
	 */
	public function get_wc_products() {
		$exclude                 = array();
		$exclude_linked_products = Input::has( 'exclude_linked_products' );
		$course_id               = Input::post( 'course_id', 0, Input::TYPE_INT );

		if ( $exclude_linked_products ) {
			$exclude = tutor_utils()->get_linked_product_ids();
		}

		if ( $course_id ) {
			$linked_product_id = tutor_utils()->get_course_product_id( $course_id );
			if ( $linked_product_id ) {
				$exclude = array_filter( $exclude, fn( $id )=> $linked_product_id !== (int) $id );
			}
		}

		$exclude = array_unique( $exclude );

		$this->json_response(
			__( 'Products retrieved successfully!', 'tutor' ),
			tutor_utils()->get_wc_products_db( $exclude ),
			HttpHelper::STATUS_OK
		);
	}

	/**
	 * Get course associate WC product info by Ajax request
	 *
	 * @since 2.0.7
	 *
	 * @return void
	 */
	public function get_wc_product() {
		tutor_utils()->checking_nonce();
		$product_id = Input::post( 'product_id' );
		$product    = wc_get_product( $product_id );
		$course_id  = Input::post( 'course_id', 0, Input::TYPE_INT );

		$is_linked_with_course = tutor_utils()->product_belongs_with_course( $product_id );

		/**
		 * If selected product is already linked with
		 * a course & it is not the current course the
		 * return error
		 *
		 * @since 2.1.0
		 */
		if ( is_object( $is_linked_with_course ) && $is_linked_with_course->post_id != $course_id ) {
			wp_send_json_error(
				__( 'One product can not be added to multiple course!', 'tutor' )
			);
		}

		if ( $product ) {
			$data = array(
				'name'          => $product->get_name(),
				'regular_price' => $product->get_regular_price(),
				'sale_price'    => $product->get_sale_price(),
			);
			wp_send_json_success( $data );
		} else {
			wp_send_json_error( __( 'Product not found', 'tutor' ) );
		}
	}

	/**
	 * Update course content order
	 *
	 * @since 1.0.0
	 * @since 3.9.9 Check if user can manage course before updating order.
	 *
	 * @return void
	 */
	public function ajax_update_course_content_order() {
		tutor_utils()->checking_nonce();

		$sorting_order = Input::post( 'tutor_topics_lessons_sorting', '' );
		$sorting_order = json_decode( $sorting_order, true ) ?? array();

		if ( ! tutor_utils()->count( $sorting_order ) ) {
			wp_send_json_error( __( 'Sorting order is required', 'tutor' ) );
		}

		$topic_id  = (int) isset( $sorting_order[0], $sorting_order[0]['topic_id'] ) ? $sorting_order[0]['topic_id'] : 0;
		$course_id = wp_get_post_parent_id( $topic_id );

		if ( ! $topic_id || ! $course_id ) {
			wp_send_json_error( tutor_utils()->error_message( 'invalid_req' ) );
		}

		if ( ! tutor_utils()->can_user_manage( 'course', $course_id ) || ! User::is_admin() ) {
			wp_send_json_error( tutor_utils()->error_message() );
		}

		if ( Input::has( 'content_parent' ) ) {
			$content_parent = Input::post( 'content_parent', array(), Input::TYPE_ARRAY );
			$topic_id       = tutor_utils()->array_get( 'parent_topic_id', $content_parent );
			$content_id     = tutor_utils()->array_get( 'content_id', $content_parent );

			// Update the parent topic id of the content.
			global $wpdb;
			$wpdb->update( $wpdb->posts, array( 'post_parent' => $topic_id ), array( 'ID' => $content_id ) );
		}

		// Save course content order.
		$this->save_course_content_order( $sorting_order );

		wp_send_json_success();
	}

	/**
	 * Restrict new student entry
	 *
	 * @since 1.0.0
	 * @param mixed $content content.
	 *
	 * @return mixed
	 */
	public function restrict_new_student_entry( $content ) {

		if ( ! tutor_utils()->is_course_fully_booked() ) {
			// No restriction if not fully booked.
			return $content;
		}

		return '<div class="list-item-booking booking-full tutor-d-flex tutor-align-center"><div class="booking-progress tutor-d-flex"><span class="tutor-mr-8 tutor-color-warning tutor-icon-circle-info"></span></div><div class="tutor-fs-7 tutor-fw-medium">' .
		__( 'Fully Booked', 'tutor' )
		. '</div></div>';
	}

	/**
	 * Restrict media
	 *
	 * @since 1.0.0
	 *
	 * @param string $where where clause.
	 *
	 * @return string
	 */
	public function restrict_media( $where ) {
		$action = Input::post( 'action' );
		if ( 'query-attachments' === $action && tutor_utils()->is_instructor() ) {
			if ( ! tutor_utils()->has_user_role( array( 'administrator', 'editor' ) ) ) {
				$where .= ' AND post_author=' . get_current_user_id();
			}
		}

		return $where;
	}

	/**
	 * Save course content order
	 *
	 * @since 1.0.0
	 * @since 3.9.8 param $order added.
	 *
	 * @param array $sort_order the lesson and topic order array.
	 *
	 * @return void
	 */
	private function save_course_content_order( $sort_order = array() ) {
		global $wpdb;

		if ( ! tutor_utils()->count( $sort_order ) ) {
			return;
		}

		$i = 0;
		foreach ( $sort_order as $topic ) {
			++$i;
			$wpdb->update(
				$wpdb->posts,
				array( 'menu_order' => $i ),
				array( 'ID' => $topic['topic_id'] )
			);

			/**
			* Removing All lesson with topic
			*/
			$wpdb->update(
				$wpdb->posts,
				array( 'post_parent' => 0 ),
				array( 'post_parent' => $topic['topic_id'] )
			);

			/**
			* Lesson Attaching with topic ID
			* Sorting lesson
			*/
			if ( isset( $topic['lesson_ids'] ) ) {
				$lesson_ids = $topic['lesson_ids'];
			} else {
				$lesson_ids = array();
			}
			if ( count( $lesson_ids ) ) {
				foreach ( $lesson_ids as $lesson_key => $lesson_id ) {
					$wpdb->update(
						$wpdb->posts,
						array(
							'post_parent' => $topic['topic_id'],
							'menu_order'  => $lesson_key,
						),
						array( 'ID' => $lesson_id )
					);
				}
			}
		}
	}

	/**
	 * Insert Topic and attached it with Course
	 *
	 * @since 1.0.0
	 *
	 * @param integer $post_ID post ID.
	 * @param object  $post post object.
	 *
	 * @return void
	 */
	public function save_course_meta( $post_ID, $post ) {
		global $wpdb;

		do_action( 'tutor_save_course', $post_ID, $post );

		/**
		 * Save course price type
		 */
		$price_type = Input::post( 'tutor_course_price_type' );
		if ( $price_type ) {
			update_post_meta( $post_ID, self::COURSE_PRICE_TYPE_META, $price_type );
		}

		//phpcs:disable WordPress.Security.NonceVerification.Missing
		// Course Duration.
		if ( ! empty( $_POST['course_duration'] ) ) {
			$video = Input::post( 'course_duration', array(), Input::TYPE_ARRAY );
			update_post_meta( $post_ID, '_course_duration', $video );
		}

		if ( ! empty( $_POST['_tutor_course_level'] ) ) {
			$course_level = Input::post( '_tutor_course_level' );
			update_post_meta( $post_ID, '_tutor_course_level', $course_level );
		}

		$additional_data_edit = Input::post( '_tutor_course_additional_data_edit' );
		if ( $additional_data_edit ) {
			if ( ! empty( $_POST['course_benefits'] ) ) {
				$course_benefits = Input::post( 'course_benefits', '', Input::TYPE_KSES_POST );
				update_post_meta( $post_ID, '_tutor_course_benefits', $course_benefits );
			} elseif ( ! tutor_is_rest() ) {
					delete_post_meta( $post_ID, '_tutor_course_benefits' );
			}

			if ( ! empty( $_POST['course_requirements'] ) ) {
				$requirements = Input::post( 'course_requirements', '', Input::TYPE_KSES_POST );
				update_post_meta( $post_ID, '_tutor_course_requirements', $requirements );
			} elseif ( ! tutor_is_rest() ) {
					delete_post_meta( $post_ID, '_tutor_course_requirements' );
			}

			if ( ! empty( $_POST['course_target_audience'] ) ) {
				$target_audience = Input::post( 'course_target_audience', '', Input::TYPE_KSES_POST );
				update_post_meta( $post_ID, '_tutor_course_target_audience', $target_audience );
			} elseif ( ! tutor_is_rest() ) {
					delete_post_meta( $post_ID, '_tutor_course_target_audience' );
			}

			if ( ! empty( $_POST['course_material_includes'] ) ) {
				$material_includes = Input::post( 'course_material_includes', '', Input::TYPE_KSES_POST );
				update_post_meta( $post_ID, '_tutor_course_material_includes', $material_includes );
			} elseif ( ! tutor_is_rest() ) {
					delete_post_meta( $post_ID, '_tutor_course_material_includes' );
			}
			//phpcs:enable WordPress.Security.NonceVerification.Missing
		}

		$sorting_order = Input::post( 'tutor_topics_lessons_sorting', '' );
		$sorting_order = json_decode( $sorting_order, true ) ?? array();
		/**
		 * Sorting Topics and lesson
		 */
		$this->save_course_content_order( $sorting_order );

		// Additional data like course intro video.
		if ( $additional_data_edit ) {
			// Sanitize data through helper method.
			$video        = Input::sanitize_array(
				$_POST['video'] ?? array(), //phpcs:ignore
				array(
					'source_external_url' => 'esc_url',
					'source_embedded'     => 'wp_kses_post',
				),
				true
			);
			$video_source = tutor_utils()->array_get( 'source', $video );
			if ( -1 !== $video_source ) {
				update_post_meta( $post_ID, '_video', $video );
			} elseif ( ! tutor_is_rest() ) {
					delete_post_meta( $post_ID, '_video' );
			}
		}

		/**
		 * Adding author to instructor automatically
		 */

		// Override post author id.
		$author_id = isset( $_POST['post_author_override'] ) ? $_POST['post_author_override'] : $post->post_author; //phpcs:ignore
		$attached  = (int) $wpdb->get_var(
			$wpdb->prepare(
				"SELECT COUNT(umeta_id) FROM {$wpdb->usermeta}
					WHERE user_id = %d
						AND meta_key = '_tutor_instructor_course_id'
						AND meta_value = %d ",
				$author_id,
				$post_ID
			)
		);

		if ( ! $attached ) {
			add_user_meta( $author_id, '_tutor_instructor_course_id', $post_ID );
		}

		/**
		 * Disable question and answer for this course
		 *
		 * @since 1.7.0
		 */
		if ( $additional_data_edit ) {
			foreach ( $this->additional_meta as $key ) {
				//phpcs:ignore WordPress.Security.NonceVerification.Missing
				update_post_meta( $post_ID, $key, ( isset( $_POST[ $key ] ) ? 'yes' : 'no' ) );
			}
		}

		do_action( 'tutor_save_course_after', $post_ID, $post );
	}

	/**
	 * Save course topic
	 *
	 * @since 1.0.0
	 * @since 3.0.0 response and input name updated.
	 *
	 * @return void
	 */
	public function tutor_save_topic() {
		tutor_utils()->check_nonce();

		$is_update   = false;
		$errors      = array();
		$topic_title = Input::post( 'title' );

		if ( empty( $topic_title ) ) {
			$errors['topic_title'] = __( 'Topic title is required!', 'tutor' );
			$this->json_response(
				__( 'Invalid inputs', 'tutor' ),
				$errors,
				HttpHelper::STATUS_UNPROCESSABLE_ENTITY
			);
		}

		// Gather parameters.
		$course_id     = Input::post( 'course_id', 0, Input::TYPE_INT );
		$topic_id      = Input::post( 'topic_id', 0, Input::TYPE_INT );
		$topic_summary = Input::post( 'summary', '', Input::TYPE_KSES_POST );

		$next_topic_order_id = tutor_utils()->get_next_topic_order_id( $course_id, $topic_id );

		// Validate if user can manage the topic.
		if ( ! tutor_utils()->can_user_manage( 'course', $course_id ) || ( $topic_id && ! tutor_utils()->can_user_manage( 'topic', $topic_id ) ) ) {
			wp_send_json_error( array( 'message' => __( 'Access Denied', 'tutor' ) ) );
		}

		// Create payload to create/update the topic.
		$post_arr = array(
			'post_type'    => 'topics',
			'post_title'   => $topic_title,
			'post_content' => $topic_summary,
			'post_status'  => 'publish',
			'post_author'  => get_current_user_id(),
			'post_parent'  => $course_id,
			'menu_order'   => $next_topic_order_id,
		);

		if ( $topic_id ) {
			$is_update      = true;
			$post_arr['ID'] = $topic_id;
		}

		$current_topic_id = wp_insert_post( $post_arr );

		if ( $is_update ) {
			$this->json_response(
				__( 'Topic updated successfully!', 'tutor' ),
				$current_topic_id
			);
		} else {
			$this->json_response(
				__( 'Topic created successfully!', 'tutor' ),
				$current_topic_id,
				HttpHelper::STATUS_CREATED
			);
		}
	}

	/**
	 * Delete a course topic
	 *
	 * @since 1.0.0
	 * @since 3.0.0 code refactor and response updated.
	 *
	 * @return void
	 */
	public function tutor_delete_topic() {
		tutor_utils()->check_nonce();

		$topic_id = Input::post( 'topic_id', 0, Input::TYPE_INT );
		if ( ! $topic_id || ! is_numeric( $topic_id ) || ! tutor_utils()->can_user_manage( 'topic', $topic_id ) ) {
			$this->json_response(
				tutor_utils()->error_message(),
				null,
				HttpHelper::STATUS_FORBIDDEN
			);
		}

		global $wpdb;

		// Assign course ID to orphan content IDs since the topic will be deleted.
		$course_id   = tutor_utils()->get_course_id_by( 'topic', $topic_id );
		$content_ids = tutor_utils()->get_course_content_ids_by( null, 'topic', $topic_id );
		foreach ( $content_ids as $content_id ) {
			update_post_meta( $content_id, '_tutor_course_id_for_lesson', $course_id );
			// Actually all kind of contents.
			// This keyword '_tutor_course_id_for_lesson' used just to support backward compatibility.
		}

		// Set contents under the topic orphan.
		$wpdb->update( $wpdb->posts, array( 'post_parent' => 0 ), array( 'post_parent' => $topic_id ) );

		// Then delete the topic from database.
		$wpdb->delete( $wpdb->postmeta, array( 'post_id' => $topic_id ) );
		wp_delete_post( $topic_id );

		$this->json_response(
			__( 'Topic deleted successfully!', 'tutor' )
		);
	}

	/**
	 * Handle enroll now action
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 */
	public function enroll_now() {

		if ( '_tutor_course_enroll_now' !== Input::post( 'tutor_course_action' ) || ! Input::has( 'tutor_course_id' ) ) {
			return;
		}

		// Checking Nonce.
		tutor_utils()->checking_nonce();

		$user_id = get_current_user_id();
		if ( ! $user_id ) {
			exit( esc_html__( 'Please Sign In first', 'tutor' ) );
		}

		$course_id = Input::post( 'tutor_course_id', 0, Input::TYPE_INT );

		/**
		 * TODO: need to check purchase information
		 */

		$is_purchasable = tutor_utils()->is_course_purchasable( $course_id );

		$course = get_post( $course_id );

		if ( 'private' === $course->post_status && ! current_user_can( 'read_private_tutor_courses' ) ) {
			wp_send_json_error( __( 'You do not have permission to enroll in this course', 'tutor' ) );
		}

		/**
		 * If is is not purchasable, it's free, and enroll right now
		 * If purchasable, then process purchase.
		 *
		 * @since: v.1.0.0
		 */
		if ( $is_purchasable ) { //phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedIf
			// Process purchase.

		} else {
			// Free enroll.
			EnrollmentModel::do_enroll( $course_id );
		}

		$referer_url = wp_get_referer();
		wp_safe_redirect( tutor_utils()->get_nocache_url( $referer_url ) );
		exit;
	}

	/**
	 * Mark complete completed
	 *
	 * @since 1.0.0
	 *
	 * @since 3.7.1 Filter hook: tutor_user_can_complete_course added
	 *
	 * @return void
	 */
	public function mark_course_complete() {
		$tutor_action = Input::post( 'tutor_action' );
		$course_id    = Input::post( 'course_id', 0, Input::TYPE_INT );
		if ( 'tutor_complete_course' !== $tutor_action || ! $course_id ) {
			return;
		}

		$permalink = get_the_permalink( $course_id );

		// Checking nonce.
		tutor_utils()->checking_nonce();

		$user_id = get_current_user_id();

		// TODO: need to show view if not signed_in.
		if ( ! $user_id ) {
			die( esc_html__( 'Please Sign-In', 'tutor' ) );
		}

		if ( ! EnrollmentModel::is_enrolled( $course_id, $user_id ) ) {
			die( esc_html__( 'User is not enrolled in course', 'tutor' ) );
		}

		/**
		 * Filter hook provided to restrict course completion. This is useful
		 * for specific cases like prerequisites. WP_Error should be returned
		 * from the filter value to prevent the completion.
		 */
		$can_complete = apply_filters( 'tutor_user_can_complete_course', CourseModel::can_complete_course( $course_id, $user_id ), $user_id, $course_id );

		if ( is_wp_error( $can_complete ) ) {
			tutor_utils()->redirect_to( $permalink, $can_complete->get_error_message(), 'error' );
		} elseif ( ! $can_complete ) {
			tutor_utils()->redirect_to( $permalink, __( 'You do not have permission to complete this course.', 'tutor' ), 'error' );
		} else {
			CourseModel::mark_course_as_completed( $course_id, $user_id );
			// Set temporary identifier to show review pop up.
			self::set_review_popup_data( $user_id, $course_id, $permalink );

			wp_safe_redirect( $permalink );
			exit;
		}
	}

	/**
	 * Ajax course complete handler
	 *
	 * @since 4.0.0
	 *
	 * @return void
	 */
	public function ajax_tutor_complete_course() {
		$course_id = Input::post( 'course_id', 0, Input::TYPE_INT );

		// Checking nonce.
		if ( ! tutor_utils()->is_nonce_verified() ) {
			$this->response_bad_request( tutor_utils()->error_message( 'nonce' ) );
		}

		if ( ! $course_id ) {
			$this->response_bad_request( __( 'Invalid course', 'tutor' ) );
		}

		$user_id = get_current_user_id();
		if ( ! EnrollmentModel::is_enrolled( $course_id, $user_id ) ) {
			$this->response_bad_request( __( 'You are not enrolled in this course', 'tutor' ) );
		}

		/**
		 * Filter hook provided to restrict course completion. This is useful
		 * for specific cases like prerequisites. WP_Error should be returned
		 * from the filter value to prevent the completion.
		 */
		$can_complete = apply_filters( 'tutor_user_can_complete_course', CourseModel::can_complete_course( $course_id, $user_id ), $user_id, $course_id );

		if ( is_wp_error( $can_complete ) ) {
			$this->response_bad_request( $can_complete->get_error_message() );
		} elseif ( ! $can_complete ) {
			$this->response_bad_request( __( 'You do not have permission to complete this course.', 'tutor' ) );
		} else {
			CourseModel::mark_course_as_completed( $course_id, $user_id );
			// Set temporary identifier to show review pop up.
			self::set_review_popup_data( $user_id, $course_id );

			$this->response_success( __( 'Course completed successfully', 'tutor' ) );
		}
	}

	/**
	 * Set data for review popup.
	 *
	 * @since 2.2.5
	 * @since 2.4.0 removed $permalink param. store user meta instead of option data.
	 *
	 * @param int $user_id user id.
	 * @param int $course_id course id.
	 *
	 * @return void
	 */
	public static function set_review_popup_data( $user_id, $course_id ) {
		if ( get_tutor_option( 'enable_course_review' ) ) {
			$rating = tutor_utils()->get_course_rating_by_user( $course_id, $user_id );
			if ( ! $rating || ( empty( $rating->rating ) && empty( $rating->review ) ) ) {
				$meta_key = User::get_review_popup_meta( $course_id );
				add_user_meta( $user_id, $meta_key, $course_id, true );
			}
		}
	}

	/**
	 * Popup review form on course details
	 *
	 * @since 1.0.0
	 * @return void
	 */
	public function popup_review_form() {
		if ( ! is_user_logged_in() ) {
			return;
		}

		$is_learning_area   = tutor_utils()->is_learning_area();
		$is_legacy_learning = tutor_utils()->is_legacy_learning_mode();
		if ( $is_legacy_learning && $is_learning_area ) {
			return;
		}

		$course_id = get_the_ID();

		if ( $is_learning_area && ! Input::has( 'subpage' ) ) {
			$course_id = wp_get_post_parent_id( wp_get_post_parent_id( get_the_ID() ) );
		}

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

		$user_id          = get_current_user_id();
		$meta_key         = User::get_review_popup_meta( $course_id );
		$review_course_id = (int) get_user_meta( $user_id, $meta_key, true );
		if ( is_single() && $course_id === $review_course_id ) {
			include tutor()->path . 'views/modal/review.php';
		}
	}

	/**
	 * Review popup data clear
	 *
	 * @since 2.4.0
	 *
	 * @return void
	 */
	public function clear_review_popup_data() {
		tutils()->checking_nonce();

		if ( is_user_logged_in() ) {
			$user_id   = get_current_user_id();
			$course_id = Input::post( 'course_id', 0, Input::TYPE_INT );

			if ( $course_id ) {
				$meta_key = User::get_review_popup_meta( $course_id );
				delete_user_meta( $user_id, $meta_key, $course_id );
			}

			wp_send_json_success();
		}
	}

	/**
	 * Delete course delete from frontend dashboard
	 *
	 * @since 2.0.0
	 *
	 * @return void
	 */
	public function tutor_delete_dashboard_course() {
		tutor_utils()->checking_nonce();

		$course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
		if ( ! tutor_utils()->can_user_manage( 'course', $course_id ) ) {
			wp_send_json_error( array( 'message' => __( 'Access Denied', 'tutor' ) ) );
		}

		/**
		 * Co-instructor can not delete a course
		 *
		 * @since 2.1.6
		 */
		if ( false === CourseModel::is_main_instructor( $course_id ) ) {
			wp_send_json_error( array( 'message' => __( 'Only main instructor can delete this course', 'tutor' ) ) );
		}

		// Check if user is only an instructor.
		if ( ! User::is_admin() ) {
			// Check if instructor can trash course.
			$can_trash_post = tutor_utils()->get_option( 'instructor_can_delete_course' );

			if ( ! $can_trash_post ) {
				wp_send_json_error( tutor_utils()->error_message() );
			}
		}

		$trash_course = wp_update_post(
			array(
				'ID'          => $course_id,
				'post_status' => CourseModel::STATUS_TRASH,
			)
		);

		if ( $trash_course ) {
			wp_send_json_success( __( 'Course has been trashed successfully ', 'tutor' ) );
		}
		wp_send_json_success();
	}

	/**
	 * Main author change from gutenberg editor
	 *
	 * @since 2.0.0
	 *
	 * @param array $data data.
	 * @param array $postarr post array.
	 *
	 * @return mixed
	 */
	public function tutor_add_gutenberg_author( $data, $postarr ) {
		$gutenberg_enabled = tutor_utils()->get_option( 'enable_gutenberg_course_edit' );
		$post_type         = $postarr['post_type'];
		$courses_post_type = tutor()->course_post_type;

		if ( false === is_admin() || false === $gutenberg_enabled || $post_type !== $courses_post_type ) {
			return $data;
		}

		/**
		 * Only admin can change main author
		 */
		if ( $courses_post_type === $post_type && ! current_user_can( 'administrator' ) ) {
			global $wpdb;
			$post_ID     = (int) tutor_utils()->avalue_dot( 'ID', $postarr );
			$post_author = (int) $wpdb->get_var( $wpdb->prepare( "SELECT post_author FROM {$wpdb->posts} WHERE ID = %d ", $post_ID ) );

			if ( $post_author > 0 ) {
				$data['post_author'] = $post_author;
			} else {
				$data['post_author'] = get_current_user_id();
			}
		}

		return $data;
	}


	/**
	 * Attach product with course when course save from frontend or backend.
	 *
	 * @since 1.3.4
	 *
	 * @since 3.0.0 Store regular & sale price in meta to make compatible with Tutor monetization
	 *
	 * @param integer $post_ID  course ID.
	 * @param array   $post_data created course post details.
	 *
	 * @return void
	 */
	public function attach_product_with_course( $post_ID, $post_data ) {
		$monetize_by = tutor_utils()->get_option( 'monetize_by' );
		$product_id  = Input::post( '_tutor_course_product_id', 0, Input::TYPE_INT );

		/**
		 * For native monetization, just return
		 * No need to attach anything.
		 */
		if ( Ecommerce::MONETIZE_BY === $monetize_by ) {
			return;
		}

		/**
		 * When course moved paid to free
		 * Keep the product linked and return.
		 */
		if ( -1 === $product_id ) {
			return;
		}

		/**
		 * Free user can only select product from dropdown
		 */
		if ( tutor()->has_pro === false && 'wc' === $monetize_by ) {
			if ( $product_id > 0 ) {
				update_post_meta( $post_ID, self::COURSE_PRODUCT_ID_META, $product_id );
			}

			return;
		}

		$attached_product_id = tutor_utils()->get_course_product_id( $post_ID );
		$course_price        = Input::post( 'course_price', 0, Input::TYPE_NUMERIC );
		$sale_price          = Input::post( 'course_sale_price', 0, Input::TYPE_NUMERIC );

		if ( ! $course_price || $sale_price >= $course_price ) {
			return;
		}

		$course = get_post( $post_ID );

		update_post_meta( $post_ID, self::COURSE_PRICE_TYPE_META, self::PRICE_TYPE_PAID );

		if ( 'wc' === $monetize_by ) {

			$is_update = ( $product_id && wc_get_product( $product_id ) ) ? true : false;

			if ( $is_update ) {
				update_post_meta( $post_ID, self::COURSE_PRODUCT_ID_META, $product_id );

				$product_id  = self::create_wc_product( $course->post_title, $course_price, $sale_price, $product_id );
				$product_obj = wc_get_product( $product_id );
				if ( $product_obj->is_type( 'subscription' ) ) {
					update_post_meta( $product_id, '_subscription_price', $course_price );
				}

				// Set course regular & sale price.
				self::set_course_regular_and_sale_price( $post_ID, $product_obj->get_regular_price(), $product_obj->get_sale_price() );
			} else {
				// Create new WC product name with course title.
				$product_id = self::create_wc_product( $course->post_title, $course_price, $sale_price );
				if ( $product_id ) {
					$product_obj = wc_get_product( $product_id );

					self::sync_course_with_wc_product( $post_ID, $product_id );

					// Set course regular & sale price.
					self::set_course_regular_and_sale_price( $post_ID, $product_obj->get_regular_price(), $product_obj->get_sale_price() );
				}
			}

			$course_post_thumbnail = Input::post( 'thumbnail_id', 0, Input::TYPE_INT );
			if ( $product_id && $course_post_thumbnail ) {
				set_post_thumbnail( $product_id, $course_post_thumbnail );
			}
		} elseif ( 'edd' === $monetize_by ) {

			$is_update = false;

			if ( $attached_product_id ) {
				$edd_price = get_post_meta( $attached_product_id, 'edd_price', true );
				if ( $edd_price ) {
					$is_update = true;
				}
			}

			if ( $is_update ) {
				// Update the product.
				update_post_meta( $attached_product_id, 'edd_price', $course_price );
			} else {
				// Create new product.

				$post_arr    = array(
					'post_type'   => 'download',
					'post_title'  => $course->post_title,
					'post_status' => 'publish',
					'post_author' => get_current_user_id(),
				);
				$download_id = wp_insert_post( $post_arr );
				if ( $download_id ) {
					// EDD edd_price.
					update_post_meta( $download_id, 'edd_price', $course_price );

					update_post_meta( $post_ID, self::COURSE_PRODUCT_ID_META, $download_id );
					// Mark product for EDD.
					update_post_meta( $download_id, '_tutor_product', 'yes' );

					$course_post_thumbnail = get_post_meta( $post_ID, '_thumbnail_id', true );
					if ( $course_post_thumbnail ) {
						set_post_thumbnail( $download_id, $course_post_thumbnail );
					}
				}
			}
		}
	}

	/**
	 * Check if course starting
	 *
	 * @since 1.4.8
	 * @return void
	 */
	public function tutor_lesson_load_before() {
		$course_id         = tutor_utils()->get_course_id_by_content( get_the_ID() );
		$completed_lessons = tutor_utils()->get_completed_lesson_count_by_course( $course_id );
		if ( is_user_logged_in() ) {
			$is_course_started = get_post_meta( $course_id, '_tutor_course_started', true );
			if ( ! $completed_lessons && ! $is_course_started ) {
				update_post_meta( $course_id, '_tutor_course_started', tutor_time() );
				do_action( 'tutor/course/started', $course_id );
			}
		}
	}

	/**
	 * Add Course level to course settings
	 *
	 * @since 1.4.8
	 *
	 * @return void
	 */
	public function course_elements_enable_disable() {
		add_filter( 'tutor_course/single/completing-progress-bar', array( $this, 'enable_disable_course_progress_bar' ) );
		add_filter( 'tutor_course/single/material_includes', array( $this, 'enable_disable_material_includes' ) );
		add_filter( 'tutor_course/single/content', array( $this, 'enable_disable_course_content' ) );
		add_filter( 'tutor_course/single/benefits_html', array( $this, 'enable_disable_course_benefits' ) );
		add_filter( 'tutor_course/single/requirements_html', array( $this, 'enable_disable_course_requirements' ) );
		add_filter( 'tutor_course/single/audience_html', array( $this, 'enable_disable_course_target_audience' ) );
		add_filter( 'tutor_course/single/nav_items', array( $this, 'enable_disable_course_nav_items' ), 999, 2 );
	}

	/**
	 * Enable disable course progress bar
	 *
	 * @since 1.4.8
	 *
	 * @param string $html HTML string.
	 * @return string
	 */
	public function enable_disable_course_progress_bar( $html ) {
		$disable_option = ! (bool) tutor_utils()->get_option( 'enable_course_progress_bar', true, true );
		if ( $disable_option ) {
			return '';
		}
		return $html;
	}

	/**
	 * Enable disable material includes
	 *
	 * @since 1.4.8
	 *
	 * @param string $html HTML string.
	 *
	 * @return string
	 */
	public function enable_disable_material_includes( $html ) {
		$disable_option = ! (bool) get_tutor_option( 'enable_course_material', true, true );
		if ( $disable_option ) {
			return '';
		}
		return $html;
	}

	/**
	 * Enable disable course content
	 *
	 * @since 1.4.8
	 *
	 * @param string $html HTML string.
	 *
	 * @return string
	 */
	public function enable_disable_course_content( $html ) {
		$disable_option = ! (bool) tutor_utils()->get_option( 'enable_course_description', true, true );
		if ( $disable_option ) {
			return '';
		}
		return $html;
	}

	/**
	 * Enable disable course benefits
	 *
	 * @since 1.4.8
	 *
	 * @param string $html HTML string.
	 *
	 * @return string
	 */
	public function enable_disable_course_benefits( $html ) {
		$disable_option = ! (bool) tutor_utils()->get_option( 'enable_course_benefits', true, true );
		if ( $disable_option ) {
			return '';
		}
		return $html;
	}

	/**
	 * Enable disable course requirements
	 *
	 * @since 1.4.8
	 *
	 * @param string $html HTML string.
	 *
	 * @return string
	 */
	public function enable_disable_course_requirements( $html ) {
		$disable_option = ! (bool) tutor_utils()->get_option( 'enable_course_requirements', true, true );
		if ( $disable_option ) {
			return '';
		}
		return $html;
	}

	/**
	 * Enable disable course target audience
	 *
	 * @since 1.4.8
	 *
	 * @param string $html HTML string.
	 *
	 * @return string
	 */
	public function enable_disable_course_target_audience( $html ) {
		$disable_option = ! (bool) tutor_utils()->get_option( 'enable_course_target_audience', true, true );
		if ( $disable_option ) {
			return '';
		}
		return $html;
	}

	/**
	 * Enable disable course nav items
	 *
	 * @since 1.4.8
	 *
	 * @param array   $items item list.
	 * @param integer $course_id course ID.
	 *
	 * @return array
	 */
	public function enable_disable_course_nav_items( $items, $course_id ) {
		global $wp_query, $post;
		$enable_q_and_a_on_course     = (bool) get_tutor_option( 'enable_q_and_a_on_course' );
		$disable_course_announcements = ! (bool) tutor_utils()->get_option( 'enable_course_announcements', true, true );
		$disable_qa_for_this_course   = ( $wp_query->is_single && ! empty( $post ) ) ? get_post_meta( $post->ID, '_tutor_enable_qa', true ) != 'yes' : false;

		// Whether Q&A enabled.
		if ( ! $enable_q_and_a_on_course || $disable_qa_for_this_course ) {
			if ( tutor_utils()->array_get( 'questions', $items ) ) {
				unset( $items['questions'] );
			}
		}

		// Whether announcment enabled.
		if ( $disable_course_announcements ) {
			if ( tutor_utils()->array_get( 'announcements', $items ) ) {
				unset( $items['announcements'] );
			}
		}

		// Hide review section if disabled.
		if ( ! get_tutor_option( 'enable_course_review' ) ) {
			unset( $items['reviews'] );
		}

		// Whether enrollment require.
		$is_enrolled = EnrollmentModel::is_enrolled();

		return array_filter(
			$items,
			function ( $item ) use ( $is_enrolled ) {
				if ( isset( $item['require_enrolment'] ) && $item['require_enrolment'] ) {
					return $is_enrolled;
				}
				return true;
			}
		);
	}

	/**
	 * Filter product in shop page
	 *
	 * @since 1.4.9
	 *
	 * @return void|null
	 */
	public function filter_product_in_shop_page() {
		$hide_course_from_shop_page = (bool) get_tutor_option( 'hide_course_from_shop_page' );
		if ( ! $hide_course_from_shop_page ) {
			return;
		}
		add_action( 'woocommerce_product_query', array( $this, 'filter_woocommerce_product_query' ) );
		add_filter( 'edd_downloads_query', array( $this, 'filter_edd_downloads_query' ), 10, 2 );
		add_action( 'pre_get_posts', array( $this, 'filter_archive_meta_query' ), 1 );
	}


	/**
	 * Tutor product meta query
	 *
	 * @since 1.4.9
	 *
	 * @return array
	 */
	public function tutor_product_meta_query() {
		$meta_query = array(
			'key'     => '_tutor_product',
			'compare' => 'NOT EXISTS',
		);
		return $meta_query;
	}

	/**
	 * Filter product in woocommerce shop page
	 *
	 * @since 1.4.9
	 *
	 * @param \WP_Query $wp_query WP Query instance.
	 *
	 * @return \WP_Query
	 */
	public function filter_woocommerce_product_query( $wp_query ) {
		$product_ids = $this->get_connected_wc_product_ids();
		$wp_query->set( 'post__not_in', $product_ids );
		return $wp_query;
	}

	/**
	 * Get connected woocommerce product ids for course and course bundle
	 *
	 * @since 2.7.2
	 *
	 * @return array
	 */
	public function get_connected_wc_product_ids() {
		global $wpdb;

		$results = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT DISTINCT pm.meta_value product_id
				FROM {$wpdb->posts} p
				INNER JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID
				AND pm.meta_key = %s
				WHERE post_type IN( 'courses','course-bundle' )",
				'_tutor_course_product_id'
			)
		);

		$ids = array();
		if ( is_array( $results ) && count( $results ) ) {
			$ids = array_column( $results, 'product_id' );
		}

		return $ids;
	}

	/**
	 * Filter product in edd downloads shortcode page
	 *
	 * @since 1.4.9
	 *
	 * @param \WP_Query $query WP Query instance.
	 *
	 * @return \WP_Query
	 */
	public function filter_edd_downloads_query( $query ) {
		$query['meta_query'][] = $this->tutor_product_meta_query();
		return $query;
	}

	/**
	 * Filter product in edd downloads archive page
	 *
	 * @since 1.4.9
	 *
	 * @param \WP_Query $wp_query WP Query instance.
	 *
	 * @return \WP_Query
	 */
	public function filter_archive_meta_query( $wp_query ) {
		if ( ! is_admin() && $wp_query->is_archive && $wp_query->get( 'post_type' ) === 'download' ) {
			$wp_query->set( 'meta_query', array( $this->tutor_product_meta_query() ) );
		}
		return $wp_query;
	}

	/**
	 * Removed course price if already enrolled at single course
	 *
	 * @since 1.5.8
	 *
	 * @param string $html HTML string.
	 *
	 * @return string
	 */
	public function remove_price_if_enrolled( $html ) {
		$should_removed = apply_filters( 'should_remove_price_if_enrolled', true );

		if ( $should_removed ) {
			$course_id = get_the_ID();
			$enrolled  = EnrollmentModel::is_enrolled( $course_id );
			if ( $enrolled ) {
				$html = '';
			}
		}
		return $html;
	}

	/**
	 * Get course completion missing requirements message.
	 *
	 * @since 4.0.0
	 *
	 * @param int $course_id Course ID.
	 * @param int $user_id   User ID.
	 *
	 * @return string|null
	 */
	public static function get_course_completion_restrict_msg( $course_id = 0, $user_id = 0 ) {
		$course_id = tutor_utils()->get_post_id( $course_id );
		$user_id   = tutor_utils()->get_user_id( $user_id );

		if ( 'strict' !== tutor_utils()->get_option( 'course_completion_process' ) ) {
			return null;
		}

		$completed_lessons = tutor_utils()->get_completed_lesson_count_by_course( $course_id, $user_id );
		$total_lessons     = tutor_utils()->get_lesson_count_by_course( $course_id );

		if ( $completed_lessons < $total_lessons ) {
			return __( 'Complete all lessons to mark this course as complete', 'tutor' );
		}

		$course_contents = tutor_utils()->get_course_contents_by_id( $course_id );

		$required_quiz_pass_count       = 0;
		$required_assignment_pass_count = 0;

		$is_assignment_addon_enabled = tutor_utils()->is_addon_enabled( 'tutor-assignments' );

		foreach ( $course_contents as $content ) {

			if ( tutor()->quiz_post_type === $content->post_type ) {
				if ( ! QuizModel::is_quiz_passed( $content->ID, $user_id ) ) {
					++$required_quiz_pass_count;
				}
			}

			if ( $is_assignment_addon_enabled && tutor()->assignment_post_type === $content->post_type ) {
				if ( ! Assignments::is_assignment_passed( $content->ID, $user_id ) ) {
					++$required_assignment_pass_count;
				}
			}
		}

		if ( ! $required_quiz_pass_count && ! $required_assignment_pass_count ) {
			return null;
		}

		return self::get_course_completion_requirement_message(
			$required_quiz_pass_count,
			$required_assignment_pass_count
		);
	}

	/**
	 * Build missing course completion requirements message.
	 *
	 * @since 4.0.0
	 *
	 * @param int $quiz_count Total quiz count.
	 * @param int $assignment_count Total assignment count.
	 *
	 * @return string
	 */
	private static function get_course_completion_requirement_message( $quiz_count, $assignment_count ) {
		$quiz_label       = _n( 'quiz', 'quizzes', $quiz_count, 'tutor' );
		$assignment_label = _n( 'assignment', 'assignments', $assignment_count, 'tutor' );

		if ( $quiz_count && ! $assignment_count ) {
			return sprintf(
				/* translators: %1$s: item count; %2$s: item label */
				__( 'You have to pass %1$s %2$s to complete this course.', 'tutor' ),
				$quiz_count,
				$quiz_label
			);
		}

		if ( ! $quiz_count && $assignment_count ) {
			return sprintf(
				/* translators: %1$s: item count; %2$s: item label */
				__( 'You have to pass %1$s %2$s to complete this course.', 'tutor' ),
				$assignment_count,
				$assignment_label
			);
		}

		return sprintf(
			/* translators: %1$s: quiz count; %2$s: quiz label; %3$s: assignment count; %4$s: assignment label */
			__( 'You have to pass %1$s %2$s and %3$s %4$s to complete this course.', 'tutor' ),
			$quiz_count,
			$quiz_label,
			$assignment_count,
			$assignment_label
		);
	}

	/**
	 * Check if all lessons and quizzes done before mark course complete.
	 *
	 * @since 1.5.8
	 *
	 * @param string $html HTML string.
	 *
	 * @return string
	 */
	public function tutor_lms_hide_course_complete_btn( $html ) {
		$_msg = self::get_course_completion_restrict_msg();

		if ( $_msg ) {
			return '<div class="tutor-alert tutor-warning tutor-mt-28">
						<div class="tutor-alert-text">
							<span class="tutor-alert-icon tutor-fs-4 tutor-icon-circle-info tutor-mr-12"></span>
							<span>' . $_msg . '</span>
						</div>
					</div>';
		}

		return $html;
	}

	/**
	 * Generate Gradebook
	 *
	 * @since 1.5.8
	 *
	 * @param string $html HTML string.
	 *
	 * @return string
	 */
	public function get_generate_greadbook( $html ) {
		if ( ! tutor_utils()->is_completed_course() ) {
			return '';
		}
		return $html;
	}

	/**
	 * Add social share content in header
	 *
	 * @since 1.6.3
	 *
	 * @return void
	 */
	public function social_share_content() {
		global $wp_query, $post;
		if ( $wp_query->is_single && ! empty( $wp_query->query_vars['post_type'] ) && $wp_query->query_vars['post_type'] === $this->course_post_type ) { ?>
			<!--Facebook-->
			<meta property="og:type" content="website"/>
			<meta property="og:image" content="<?php echo esc_url( get_tutor_course_thumbnail_src() ); ?>" />
			<meta property="og:description" content="<?php echo esc_html( $post->post_content ); ?>" />
			<!--Twitter-->
			<meta name="twitter:image" content="<?php echo esc_url( get_tutor_course_thumbnail_src() ); ?>">
			<meta name="twitter:description" content="<?php echo esc_html( $post->post_content ); ?>">
			<!--Google+-->
			<meta itemprop="image" content="<?php echo esc_url( get_tutor_course_thumbnail_src() ); ?>">
			<meta itemprop="description" content="<?php echo esc_html( $post->post_content ); ?>">
			<?php
		}
	}

	/**
	 * Delete associated enrollment
	 *
	 * @since 1.8.2
	 *
	 * @param integer $post_id post ID.
	 *
	 * @return void
	 */
	public function delete_associated_enrollment( $post_id ) {
		global $wpdb;

		$enroll_id = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT
				post_id
			FROM
				{$wpdb->postmeta}
			WHERE
				meta_key=%s
				AND meta_value = %d
			",
				EnrollmentModel::ENROLLMENT_ORDER_ID_META,
				$post_id
			)
		);

		if ( is_numeric( $enroll_id ) && $enroll_id > 0 ) {

			$course_id = get_post_field( 'post_parent', $enroll_id );
			$user_id   = get_post_field( 'post_author', $enroll_id );

			tutor_utils()->cancel_course_enrol( $course_id, $user_id );
		}
	}

	/**
	 * Reset course progress.
	 *
	 * @since 1.5.8
	 *
	 * @return void
	 */
	public function tutor_reset_course_progress() {
		tutor_utils()->checking_nonce();
		$course_id             = Input::post( 'course_id', 0, Input::TYPE_INT );
		$context               = Input::post( 'context', '' );
		$course_reset_progress = tutor_utils()->get_option( 'course_reset_progress', false );
		$course_retake_feature = tutor_utils()->get_option( 'course_retake_feature', false );

		if ( ! $course_reset_progress && 'learning-area-sidebar' === $context ) {
			$this->response_bad_request( __( 'You do not have permission to reset this course.', 'tutor' ) );
			return;
		}

		if ( ! $course_retake_feature && ( 'course-landing' === $context || 'learning-area' === $context ) ) {
			$this->response_bad_request( __( 'You do not have permission to retake this course.', 'tutor' ) );
			return;
		}

		if ( ! $course_id || ! is_numeric( $course_id ) || ! EnrollmentModel::is_enrolled( $course_id ) ) {
			$this->response_bad_request( __( 'Invalid Course ID or Access Denied.', 'tutor' ) );
			return;
		}

		tutor_utils()->delete_course_progress( $course_id );
		$this->json_response( '', array( 'redirect_to' => tutor_utils()->get_course_first_lesson( $course_id ) ) );
	}

	/**
	 * Do enroll if guest attempt to enroll and course is free
	 *
	 * @since 1.9.8
	 *
	 * @param integer $course_id course ID.
	 * @param integer $user_id user ID.
     *
	 * @return void
	 */
	public function enroll_after_login_if_attempt( int $course_id, int $user_id ) {
		$course_id  = sanitize_text_field( $course_id );
		$is_allowed = apply_filters( 'tutor_allow_guest_attempt_enrollment', true, $course_id, $user_id );

		if ( $course_id && $is_allowed ) {
			$is_purchasable = tutor_utils()->is_course_purchasable( $course_id );
			if ( ! $is_purchasable ) {
				EnrollmentModel::do_enroll( $course_id, $order_id = 0, $user_id );
				do_action( 'guest_attempt_after_enrollment', $course_id );
			}
		}
	}

	/**
	 * Handle course enrollment
	 *
	 * @since 2.1.0
	 *
	 * @return void
	 */
	public function course_enrollment() {
		tutor_utils()->checking_nonce();

		$course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
		$user_id   = get_current_user_id();

		if ( ! $course_id || ! $user_id ) {
			wp_send_json_error( tutor_utils()->error_message( 'invalid_req' ) );
		}

		$password_protected = post_password_required( $course_id );
		if ( $password_protected ) {
			wp_send_json_error( __( 'This course is password protected', 'tutor' ) );
		}

		$course = get_post( $course_id );

		if ( CourseModel::STATUS_PRIVATE === $course->post_status && ! current_user_can( 'read_private_tutor_courses' ) ) {
			wp_send_json_error( __( 'You do not have permission to enroll in this course', 'tutor' ) );
		}

		/**
		 * This check was added to address a security issue where users could
		 * enroll in a course via an AJAX call without purchasing it.
		 *
		 * To prevent this, we now verify whether the course is paid.
		 * Additionally, we check if the user is already enrolled, since
		 * Tutor's default behavior enrolls users automatically upon purchase.
		 *
		 * @since 3.9.4
		 */
		if ( tutor_utils()->is_course_purchasable( $course_id ) ) {
			$is_enrolled = (bool) EnrollmentModel::is_enrolled( $course_id, $user_id );

			if ( ! $is_enrolled ) {
				wp_send_json_error( __( 'Please purchase the course before enrolling', 'tutor' ) );
			}
		}

		$enroll = EnrollmentModel::do_enroll( $course_id, 0, $user_id );
		if ( $enroll ) {
			wp_send_json_success( __( 'Enrollment successfully done!', 'tutor' ) );
		} else {
			wp_send_json_error( __( 'Enrollment failed, please try again!', 'tutor' ) );
		}
	}

	/**
	 * After trash a course direct to the course list page
	 *
	 * @since 2.1.7
	 *
	 * @param integer $post_id int course id.
	 *
	 * @return void
	 */
	public static function redirect_to_course_list_page( int $post_id ): void {
		$post = get_post( $post_id );
		if ( tutor()->course_post_type === $post->post_type ) {
			$is_gutenberg_enabled = tutor_utils()->get_option( 'enable_gutenberg_course_edit' );
			if ( ! $is_gutenberg_enabled ) {
				wp_safe_redirect( admin_url( 'admin.php?page=tutor' ) );
				exit;
			}
		}
	}

	/**
	 * Create or update WooCommerce product
	 *
	 * If product id not set it will create new one.
	 *
	 * @since 2.2.0
	 *
	 * @param string $title product title.
	 * @param string $reg_price product price.
	 * @param string $sale_price product sale price.
	 * @param int    $product_id product ID.
	 * @param string $status product status.
	 *
	 * @return integer Product id or return 0 if WC not exists
	 */
	public static function create_wc_product( $title, $reg_price, $sale_price, $product_id = 0, $status = 'publish' ) {
		if ( ! tutor_utils()->has_wc() ) {
			return 0;
		}

		$product_obj = new \WC_Product();
		if ( $product_id ) {
			$product_obj = wc_get_product( $product_id );
		}

		$product_obj->set_name( $title );
		$product_obj->set_status( $status );
		$product_obj->set_price( $reg_price );
		$product_obj->set_regular_price( $reg_price );

		if ( $sale_price > 0 ) {
			$product_obj->set_sale_price( $sale_price );
		} else {
			$product_obj->set_sale_price( null );
		}

		$product_obj->set_sold_individually( true );

		return $product_obj->save();
	}

	/**
	 * Get course/bundle mini info
	 *
	 * @since 3.0.0
	 *
	 * @param object $post Course or bundle post.
	 *
	 * @return array
	 */
	public static function get_mini_info( object $post ) {
		$is_purchasable = tutor_utils()->is_course_purchasable( $post->ID );
		$course_price   = tutor_utils()->get_raw_course_price( $post->ID );
		$regular_price  = tutor_get_formatted_price( $course_price->regular_price );
		$sale_price     = ! empty( $course_price->sale_price ) ? tutor_get_formatted_price( $course_price->sale_price ) : null;

		$info = array(
			'id'             => $post->ID,
			'title'          => $post->post_title,
			'image'          => get_tutor_course_thumbnail_src( 'post-thumbnail', $post->ID ),
			'is_purchasable' => $is_purchasable,
			'regular_price'  => $regular_price,
			'sale_price'     => $sale_price,
		);

		$card_data = apply_filters( 'tutor_course_mini_info', $info, $post );

		return $card_data;
	}

	/**
	 * Get course/bundle card data
	 *
	 * This method will return all data that contain in
	 * course card
	 *
	 * @since 3.0.0
	 *
	 * @param object $post Course or bundle post.
	 *
	 * @return array
	 */
	public static function get_card_data( object $post ) {
		$info = self::get_mini_info( $post );

		$info['last_updated']    = tutor_i18n_get_formated_date( $post->post_modified_at );
		$info['course_duration'] = tutor_utils()->get_course_duration( $post->ID, false );
		$info['total_enrolled']  = tutor_utils()->count_enrolled_users_by_course( $post->ID );

		$card_data = apply_filters( 'tutor_course_card_data', $info, $post );

		return $card_data;
	}

	/**
	 * Filter user list access for instructor
	 *
	 * @since 3.0.0
	 *
	 * @param bool $access access.
	 *
	 * @return bool
	 */
	public function user_list_access_for_instructor( $access ) {
		$is_instructor = User::is_instructor();
		return $access || $is_instructor;
	}

	/**
	 * Filter user list args for instructor
	 *
	 * @since 3.0.0
	 *
	 * @param array $args args.
	 *
	 * @return array
	 */
	public function user_list_args_for_instructor( $args ) {
		if ( User::is_instructor() ) {
			if ( isset( $args['fields'] ) && isset( $args['fields']['user_email'] ) ) {
				unset( $args['fields']['user_email'] );
			}
		}

		$filter = json_decode( wp_unslash( $_POST['filter'] ?? '{}' ) );//phpcs:ignore
		if ( isset( $filter->role ) && is_array( $filter->role ) ) {
			$args['role__in'] = array_map( 'sanitize_text_field', $filter->role );
		}

		return $args;
	}

	/**
	 * Get a list of possible course status.
	 *
	 * @since 3.6.2
	 *
	 * @return array
	 */
	public static function course_status_list() {
		return array(
			'publish',
			'private',
			'draft',
			'trash',
			'pending',
			'future',
		);
	}

	/**
	 * Link a course/bundle post to a WooCommerce product.
	 *
	 * @since 3.8.2
	 *
	 * @param int $post_ID    The WordPress post ID of the course.
	 * @param int $product_id The WooCommerce product ID to associate with the course.
	 *
	 * @return void
	 */
	public static function sync_course_with_wc_product( $post_ID, $product_id ) {

		update_post_meta( $post_ID, self::COURSE_PRODUCT_ID_META, $product_id );

		// Mark product for woocommerce.
		update_post_meta( $product_id, '_virtual', 'yes' );
		update_post_meta( $product_id, '_tutor_product', 'yes' );
	}

	/**
	 * Map Tutor's course prices to WooCommerce.
	 *
	 * @since 3.8.2
	 *
	 * @param int              $post_ID       The WordPress post ID of the course.
	 * @param string|int|float $regular_price The regular price.
	 * @param string|int|float $sale_price    The sale price.
	 * @return void
	 */
	private static function set_course_regular_and_sale_price( $post_ID, $regular_price, $sale_price ) {

		// Set course regular & sale price.
		update_post_meta( $post_ID, self::COURSE_PRICE_META, $regular_price );
		update_post_meta( $post_ID, self::COURSE_SALE_PRICE_META, $sale_price );
	}

	/**
	 * Render start/resume button in enrolled course action btn.
	 *
	 * @since 4.0.0
	 *
	 * @param int $course_id course id.
	 *
	 * @return void
	 */
	public function render_course_action_btn( int $course_id ) {
		$is_completed = tutor_utils()->is_completed_course( $course_id );
		if ( $is_completed ) {
			$certificate_addon_active = tutor_utils()->is_addon_enabled( 'tutor-certificate' );
			if ( ! $certificate_addon_active ) {
				Button::make()
					->tag( 'a' )
					->label( __( 'Completed', 'tutor' ) )
					->icon( Icon::COMPLETED_CIRCLE )
					->variant( Variant::PRIMARY )
					->size( Size::X_SMALL )
					->attr( 'href', esc_url( get_the_permalink( $course_id ) ) )
					->render();
			}
			return;
		}

		$course_progress = tutor_utils()->get_course_completed_percent( $course_id, 0, true );
		$button_text     = $course_progress['completed_percent'] > 0 ? __( 'Resume', 'tutor' ) : __( 'Start', 'tutor' );
		$button_url      = tutor_utils()->get_course_first_lesson( $course_id );

		if ( ! $button_url ) {
			$button_url = get_the_permalink( $course_id );
		}

		Button::make()
			->tag( 'a' )
			->label( $button_text )
			->icon( Icon::PLAY_2 )
			->variant( Variant::PRIMARY )
			->size( Size::X_SMALL )
			->attr( 'href', esc_url( $button_url ) )
			->render();
	}

	/**
	 * Get the content for the course completion modal.
	 *
	 * This method returns HTML for displaying a modal that informs the user
	 * that they have not completed all required lessons and assessments. It includes
	 * the user's current progress shown as a percentage and a progress bar.
	 *
	 * @since 4.0.0
	 *
	 * @param float $course_progress The completion percentage of the course.
	 *
	 * @return string The rendered HTML content for the modal.
	 */
	public static function get_complete_modal_content( float $course_progress = 0 ): string {
		ob_start();
		?>
		<div>
			<p class="tutor-p3 tutor-text-secondary tutor-text-center tutor-mb-7 tutor-px-11">
				<?php esc_html_e( 'You have not completed all required lessons and assessments. ', 'tutor' ); ?>
			</p>
			<div class="tutor-finish-course-progress tutor-border tutor-p-5 tutor-flex tutor-flex-column tutor-gap-4 tutor-surface-base tutor-rounded-md">
				<div class="tutor-flex tutor-items-center tutor-justify-between">
					<div class="tutor-p3 tutor-text-secondary">
						<?php esc_html_e( 'Your Progress', 'tutor' ); ?>
					</div>
					<div class="tutor-p1 tutor-font-bold">
						<?php echo esc_html( number_format_i18n( $course_progress ) . '%' ); ?>
					</div>
				</div>
				<?php
					Progress::make()->type( 'bar' )->value( $course_progress )->render();
				?>
			</div>
		</div>
		<?php
		return ob_get_clean();
	}

	/**
	 * Render the course complete button.
	 *
	 * Displays a button that allows the user to complete the course. If the course
	 * progress is less than 100%, clicking the button shows a modal informing the user
	 * that not all requirements are fulfilled. If the course progress is 100%,
	 * clicking the button attempts to complete the course.
	 *
	 * @since 4.0.0
	 *
	 * @param string $modal_id        The HTML ID of the modal to display if not complete.
	 * @param int    $course_id       The ID of the course.
	 * @param float  $course_progress The current completion percentage of the course.
	 * @param string $size            The button size.
	 * @param string $tooltip         Optional. Tooltip message.
	 * @param bool   $block           Optional. Whether the button is full-width.
	 *
	 * @return void
	 */
	public static function render_course_complete_btn( string $modal_id, int $course_id, float $course_progress = 0, string $size = Size::MEDIUM, string $tooltip = '', bool $block = false ): void {
		$button = Button::make()
		->variant( Variant::SECONDARY )
		->label( __( 'Complete the Course', 'tutor' ) )
		->icon( Icon::TICK_MARK )
		->size( $size )
		->block( $block )
		->attr( 'type', 'button' );

		if ( ! empty( $tooltip ) ) {
			$button->disabled();
		}

		if ( $course_progress < 100 ) {
			$button->attr( '@click', "TutorCore.modal.showModal('{$modal_id}')" );
		} else {
			$button->attr( '@click', "handleCourseComplete({$course_id})" );
			$button->attr( ':class', "courseCompleteMutation?.isPending ? 'tutor-btn-loading' : ''" );
			$button->attr( ':disabled', 'courseCompleteMutation?.isPending' );
		}

		if ( ! empty( $tooltip ) ) {
			Tooltip::make()
				->content( $tooltip )
				->placement( Tooltip::PLACEMENT_BOTTOM )
				->arrow( Tooltip::ARROW_CENTER )
				->trigger_element( $button->get() )
				->render();
		} else {
			$button->render();
		}
	}

	/**
	 * Render course retake button
	 *
	 * @since 4.0.0
	 *
	 * @param string $modal_id Modal id.
	 * @param string $size     The button size.
	 * @param bool   $block    Optional. Whether the button is full-width.
	 *
	 * @return void
	 */
	public static function render_course_retake_btn( string $modal_id, string $size = Size::MEDIUM, bool $block = false ): void {
		Button::make()
		->variant( Variant::SECONDARY )
		->label( __( 'Retake this Course', 'tutor' ) )
		->icon( Icon::RELOAD_4 )
		->size( $size )
		->block( $block )
		->attr( 'type', 'button' )
		->attr( '@click', "TutorCore.modal.showModal('{$modal_id}')" )
		->render();
	}
}
