[ Avaa Bypassed ]




Upload:

Command:

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

namespace TUTOR;

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

use stdClass;
use TUTOR\Input;
use Tutor\Helpers\HttpHelper;
use Tutor\Models\CourseModel;
use Tutor\Ecommerce\Ecommerce;
use Tutor\Traits\JsonResponse;
use Tutor\Helpers\ValidationHelper;
use TutorPro\CourseBundle\Models\BundleModel;

/**
 * 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';


	/**
	 * 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 );

		/**
		 * Add course level to course settings
		 *
		 * @since v.1.4.1
		 */
		add_filter( 'tutor_course_settings_tabs', array( $this, 'add_course_level_to_settings' ) );

		/**
		 * 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, 'tutor_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( 'admin_init', array( $this, 'load_course_builder' ) );
		add_action( 'template_redirect', array( $this, 'load_course_builder' ) );
		add_action( 'tutor_before_course_builder_load', array( $this, 'enqueue_course_builder_assets' ) );
		add_action( 'tutor_course_builder_footer', array( $this, 'load_wp_link_modal' ) );
		add_action( 'admin_menu', array( $this, 'load_media_scripts' ) );
		add_action( 'init', array( $this, 'load_media_scripts' ) );

		/**
		 * 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' ) );

	}

	/**
	 * 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,
		);
	}

	/**
	 * 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 = isset( $params['video']['source'] ) ? $params['video']['source'] : '';

			if ( '' === $video_source ) {
				$errors['video_source'] = __( 'Video source is required', 'tutor' );
			} elseif ( ! $this->is_valid_video_source_type( $video_source ) ) {
					$errors['video_source'] = __( 'Invalid video source', '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'] ) ) {
			$category_names = array();

			foreach ( $params['course_categories'] as $category_id ) {
				$term = get_term( $category_id, CourseModel::COURSE_CATEGORY );

				if ( ! is_wp_error( $term ) && $term ) {
					$category_names[] = $term->name;
				}
			}

			// Set category names on the post.
			wp_set_object_terms( $post_id, $category_names, CourseModel::COURSE_CATEGORY );
		} else {
			wp_set_object_terms( $post_id, array(), CourseModel::COURSE_CATEGORY );
		}

		if ( isset( $params['course_tags'] ) && is_array( $params['course_tags'] ) ) {
			$tag_names = array();

			foreach ( $params['course_tags'] as $tag_id ) {
				$term = get_term( $tag_id, CourseModel::COURSE_TAG );

				if ( ! is_wp_error( $term ) && $term ) {
					$tag_names[] = $term->name;
				}
			}

			// Set tag names on the post.
			wp_set_object_terms( $post_id, $tag_names, 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'] );
				}
				if ( isset( $params['pricing']['product_id'] ) ) {
					update_post_meta( $post_id, '_tutor_course_product_id', $params['pricing']['product_id'] );
				}
			} 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'] );
	}

	/**
	 * 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 ( 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 );

		$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() {
		$this->check_access();

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

		$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(
			'posts_per_page' => $limit,
			'offset'         => $offset,
			's'              => $search_term,
		);

		$exclude = Input::post( 'exclude', array(), Input::TYPE_ARRAY );
		if ( count( $exclude ) ) {
			$exclude         = array_filter(
				$exclude,
				function ( $id ) {
					return is_numeric( $id );
				}
			);
			$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-pro' ),
			$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'];
		$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 );
		$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 );
		$builder   = Input::post( 'builder' );
		$this->check_access( $course_id );

		if ( 'elementor' === $builder ) {
			delete_post_meta( $course_id, '_elementor_edit_mode' );
		} elseif ( 'droip' === $builder ) {
			delete_post_meta( $course_id, 'droip_editor_mode' );
		}

		$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
	 */
	public function ajax_course_contents() {
		tutor_utils()->check_nonce();

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

		$this->check_access( $course_id );

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

		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 );

		$this->check_access( $course_id );

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

		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 ( 'wc' === $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 ( 'tutor' === $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,
			),
		);

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

		$this->json_response( __( 'Data retrieved successfully!' ), $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' );

		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-shared', tutor()->url . 'assets/js/tutor-shared.min.js', array( 'wp-i18n', 'wp-element' ), TUTOR_VERSION, true );
		wp_enqueue_script( 'tutor-course-builder', tutor()->url . 'assets/js/tutor-course-builder.min.js', array( 'wp-i18n', 'wp-element', 'tutor-shared' ), TUTOR_VERSION, true );

		$default_data = ( new Assets( false ) )->get_default_localized_data();

		if ( isset( $default_data['current_user']->data ) ) {
			$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(
			'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'] ?? '' );

		$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' );
		$data['max_upload_size']          = size_format( wp_max_upload_size() );

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

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

		wp_localize_script( 'tutor-course-builder', '_tutorobject', $data );
		wp_set_script_translations( 'tutor-course-builder', 'tutor', tutor()->path . 'languages/' );
	}

	/**
	 * Load wp editor modal
	 *
	 * @since 3.0.0
	 *
	 * @return void
	 */
	public function load_wp_link_modal() {
		if ( is_admin() ) {
			include_once tutor()->path . 'views/modal/wp-editor-link.php';
		}
	}

	/**
	 * Load view for course builder.
	 *
	 * @since 3.0.0
	 *
	 * @return void
	 */
	public function load_course_builder_view() {
		do_action( 'tutor_before_course_builder_load' );
		include_once tutor()->path . 'views/pages/course-builder.php';
		do_action( 'tutor_after_course_builder_load' );
		exit( 0 );
	}

	/**
	 * 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
	 * @return void
	 */
	public function tutor_update_course_content_order() {
		tutor_utils()->checking_nonce();

		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 );

			if ( ! tutor_utils()->can_user_manage( 'topic', $topic_id ) ) {
				wp_send_json_success( array( 'message' => __( 'Access Denied!', 'tutor' ) ) );
				exit;
			}

			// 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();

		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
	 * @return void
	 */
	private function save_course_content_order() {
		global $wpdb;

		$new_order = Input::post( 'tutor_topics_lessons_sorting' );
		if ( ! empty( $new_order ) ) {
			$order = json_decode( $new_order, true );

			if ( is_array( $order ) && count( $order ) ) {
				$i = 0;
				foreach ( $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 Topics and lesson
		 */
		$this->save_course_content_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' ),
				$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 );

		/**
		 * 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.
			tutor_utils()->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
	 * @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;
		}

		// 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' ) );
		}

		CourseModel::mark_course_as_completed( $course_id, $user_id );

		$permalink = get_the_permalink( $course_id );

		// Set temporary identifier to show review pop up.
		self::set_review_popup_data( $user_id, $course_id, $permalink );

		wp_safe_redirect( $permalink );
		exit;
	}

	/**
	 * 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() ) {
			$user_id          = get_current_user_id();
			$course_id        = get_the_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 ( ! current_user_can( 'administrator' ) ) {
			// 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' => '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 );

		if ( Ecommerce::MONETIZE_BY === $monetize_by ) {
			return;
		}

		/**
		 * Unlink product from course.
		 */
		if ( -1 === $product_id ) {
			delete_post_meta( $post_ID, self::COURSE_PRODUCT_ID_META );
			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 = ( $attached_product_id && wc_get_product( $attached_product_id ) ) ? true : false;

			if ( $is_update ) {
				$attached_product_id = $product_id;
				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, $attached_product_id );
				$product_obj = wc_get_product( $product_id );
				if ( $product_obj->is_type( 'subscription' ) ) {
					update_post_meta( $attached_product_id, '_subscription_price', $course_price );
				}

				// Set course regular & sale price.
				update_post_meta( $post_ID, self::COURSE_PRICE_META, $product_obj->get_regular_price() );
				update_post_meta( $post_ID, self::COURSE_SALE_PRICE_META, $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 );
					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' );

					// Set course regular & sale price.
					update_post_meta( $post_ID, self::COURSE_PRICE_META, $product_obj->get_regular_price() );
					update_post_meta( $post_ID, self::COURSE_SALE_PRICE_META, $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 );
					}
				}
			}
		}
	}

	/**
	 * Add Course level to course settings
	 *
	 * @since 1.4.1
	 *
	 * @param array $args arguments.
	 * @return array
	 */
	public function add_course_level_to_settings( $args ) {
		$course_id    = get_the_ID();
		$levels       = tutor_utils()->course_levels();
		$course_level = get_post_meta( $course_id, '_tutor_course_level', true );

		$args['general']['fields']['_tutor_course_level'] = array(
			'type'        => 'select',
			'label'       => __( 'Difficulty Level', 'tutor' ),
			'label_title' => __( 'Enable', 'tutor' ),
			'options'     => $levels,
			'value'       => $course_level ? $course_level : 'intermediate',
			'desc'        => __( 'Course difficulty level', 'tutor' ),
		);

		return $args;
	}

	/**
	 * 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 = tutor_utils()->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  = tutor_utils()->is_enrolled( $course_id );
			if ( $enrolled ) {
				$html = '';
			}
		}
		return $html;
	}

	/**
	 * 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 ) {

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

		$completed_lesson = tutor_utils()->get_completed_lesson_count_by_course();
		$lesson_count     = tutor_utils()->get_lesson_count_by_course();

		if ( $completed_lesson < $lesson_count ) {
			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>' . __( 'Complete all lessons to mark this course as complete', 'tutor' ) . '</span>
						</div>
					</div>';
		}

		$quizzes     = array();
		$assignments = array();

		$course_contents = tutor_utils()->get_course_contents_by_id();
		if ( tutor_utils()->count( $course_contents ) ) {
			foreach ( $course_contents as $content ) {
				if ( 'tutor_quiz' === $content->post_type ) {
					$quizzes[] = $content;
				}
				if ( 'tutor_assignments' === $content->post_type ) {
					$assignments[] = $content;
				}
			}
		}

		$required_assignment_pass = 0;

		foreach ( $assignments as $row ) {

			$submitted_assignment      = tutor_utils()->is_assignment_submitted( $row->ID );
			$is_reviewed_by_instructor = null === $submitted_assignment
											? false
											: get_comment_meta( $submitted_assignment->comment_ID, 'evaluate_time', true );

			if ( $submitted_assignment && $is_reviewed_by_instructor ) {
				$pass_mark  = tutor_utils()->get_assignment_option( $submitted_assignment->comment_post_ID, 'pass_mark' );
				$given_mark = get_comment_meta( $submitted_assignment->comment_ID, 'assignment_mark', true );

				if ( $given_mark < $pass_mark ) {
					$required_assignment_pass++;
				}
			} else {
				$required_assignment_pass++;
			}
		}

		$is_quiz_pass       = true;
		$required_quiz_pass = 0;

		if ( tutor_utils()->count( $quizzes ) ) {
			foreach ( $quizzes as $quiz ) {

				$attempt = tutor_utils()->get_quiz_attempt( $quiz->ID );
				if ( $attempt ) {
					$passing_grade     = tutor_utils()->get_quiz_option( $quiz->ID, 'passing_grade', 0 );
					$earned_percentage = $attempt->earned_marks > 0 ? ( number_format( ( $attempt->earned_marks * 100 ) / $attempt->total_marks ) ) : 0;

					if ( $earned_percentage < $passing_grade ) {
						$required_quiz_pass++;
						$is_quiz_pass = false;
					}
				} else {
					$required_quiz_pass++;
					$is_quiz_pass = false;
				}
			}
		}

		if ( ! $is_quiz_pass || $required_assignment_pass > 0 ) {
			$_msg           = '';
			$quiz_str       = _n( 'quiz', 'quizzes', $required_quiz_pass, 'tutor' );
			$assignment_str = _n( 'assignment', 'assignments', $required_assignment_pass, 'tutor' );

			if ( ! $is_quiz_pass && 0 == $required_assignment_pass ) {
				/* translators: %1$s: number of quiz/assignment pass required; %2$s: quiz/assignment string */
				$_msg = sprintf( __( 'You have to pass %1$s %2$s to complete this course.', 'tutor' ), $required_quiz_pass, $quiz_str );
			}

			if ( $is_quiz_pass && $required_assignment_pass > 0 ) {
				//phpcs:ignore
				$_msg = sprintf( __( 'You have to pass %1$s %2$s to complete this course.', 'tutor' ), $required_assignment_pass, $assignment_str );
			}

			if ( ! $is_quiz_pass && $required_assignment_pass > 0 ) {
				/* translators: %1$s: number of quiz pass required; %2$s: quiz string; %3$s: number of assignment pass required; %4$s: assignment string */
				$_msg = sprintf( __( 'You have to pass %1$s %2$s and %3$s %4$s to complete this course.', 'tutor' ), $required_quiz_pass, $quiz_str, $required_assignment_pass, $assignment_str );
			}

			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='_tutor_enrolled_by_order_id'
				AND meta_value = %d
			",
				$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' );

		if ( ! $course_id || ! is_numeric( $course_id ) || ! tutor_utils()->is_enrolled( $course_id ) ) {
			wp_send_json_error( array( 'message' => __( 'Invalid Course ID or Access Denied.', 'tutor' ) ) );
			return;
		}

		tutor_utils()->delete_course_progress( $course_id );
		wp_send_json_success( 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 );
		if ( $course_id ) {
			$is_purchasable = tutor_utils()->is_course_purchasable( $course_id );
			if ( ! $is_purchasable ) {
				tutor_utils()->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 ) {
			$enroll = tutor_utils()->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' ) );
			}
		} else {
			wp_send_json_error( __( 'Invalid course ID', '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();
	}

	/**
	 * Load media scripts
	 *
	 * @since 3.0.0
	 *
	 * @return void
	 */
	public static function load_media_scripts() {
		// Add style on the head tag.
		$screen_reader_text_style = '
			.screen-reader-text
			{
				position: absolute;
				top: -10000em;
				width: 1px;
				height: 1px;
				margin: -1px;
				padding: 0;
				overflow: hidden;
				clip: rect(0,0,0,0);
				border: 0;
			}
		';

		wp_add_inline_style(
			'media-views',
			$screen_reader_text_style
		);

		add_action(
			'wp_print_footer_scripts',
			function () {
				if ( function_exists( 'wp_print_media_templates' ) ) {
					wp_print_media_templates();
				}
			}
		);
	}

	/**
	 * 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,
		);

		if ( 'course-bundle' === $post->post_type && tutor_utils()->is_addon_enabled( 'tutor-pro/addons/course-bundle/course-bundle.php' ) ) {
			$info['total_course'] = count( BundleModel::get_bundle_course_ids( $post->ID ) );
		}

		$card_data = apply_filters( 'tutor_add_course_plan_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_add_course_plan_info', $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;
	}
}

Filemanager

Name Type Size Permission Actions
Addons.php File 11.6 KB 0644
Admin.php File 21.3 KB 0644
Ajax.php File 16.82 KB 0644
Announcements.php File 2.67 KB 0644
Assets.php File 23.25 KB 0644
Backend_Page_Trait.php File 4.39 KB 0644
BaseController.php File 1.47 KB 0644
Course.php File 85.39 KB 0644
Course_Embed.php File 2.55 KB 0644
Course_Filter.php File 8.67 KB 0644
Course_List.php File 13.7 KB 0644
Course_Settings_Tabs.php File 1.16 KB 0644
Course_Widget.php File 8.19 KB 0644
Custom_Validation.php File 513 B 0644
Dashboard.php File 1.23 KB 0644
Earnings.php File 9.53 KB 0644
FormHandler.php File 7.16 KB 0644
Frontend.php File 2.94 KB 0644
Gutenberg.php File 4.62 KB 0644
Input.php File 9.08 KB 0644
Instructor.php File 12.99 KB 0644
Instructors_List.php File 12.97 KB 0644
Lesson.php File 17.08 KB 0644
Options_V2.php File 63.19 KB 0644
Permalink.php File 2 KB 0644
Post_types.php File 18.3 KB 0644
Private_Course_Access.php File 2.52 KB 0644
Q_And_A.php File 10.66 KB 0644
Question_Answers_List.php File 2.54 KB 0644
Quiz.php File 62.02 KB 0644
QuizBuilder.php File 11.5 KB 0644
Quiz_Attempts_List.php File 7.32 KB 0644
RestAPI.php File 7.97 KB 0644
Reviews.php File 2.71 KB 0644
Rewrite_Rules.php File 5.18 KB 0644
Shortcode.php File 14.22 KB 0644
Singleton.php File 1.08 KB 0644
Student.php File 10.18 KB 0644
Students_List.php File 2.37 KB 0644
Taxonomies.php File 8.2 KB 0644
Template.php File 14.18 KB 0644
Theme_Compatibility.php File 683 B 0644
Tools.php File 3.33 KB 0644
Tools_V2.php File 18.18 KB 0644
Tutor.php File 36.06 KB 0644
TutorEDD.php File 4.63 KB 0644
Tutor_Base.php File 1.48 KB 0644
Tutor_Setup.php File 33.25 KB 0644
Upgrader.php File 7.49 KB 0644
User.php File 14.66 KB 0644
Utils.php File 263.33 KB 0644
Video_Stream.php File 3.94 KB 0644
WhatsNew.php File 4.07 KB 0644
Withdraw.php File 9.49 KB 0644
Withdraw_Requests_List.php File 6.15 KB 0644
WooCommerce.php File 23.15 KB 0644