[ Avaa Bypassed ]




Upload:

Command:

hmhc3928@3.144.110.15: ~ $
<?php
/**
 * Tutor Utils Helper functions
 *
 * @package Tutor\Utils
 * @author Themeum <support@themeum.com>
 * @link https://themeum.com
 * @since 1.0.0
 */

namespace TUTOR;

use Tutor\Cache\TutorCache;
use Tutor\Ecommerce\Ecommerce;
use Tutor\Ecommerce\Tax;
use Tutor\Helpers\HttpHelper;
use Tutor\Helpers\QueryHelper;
use Tutor\Models\CourseModel;
use Tutor\Models\QuizModel;
use Tutor\Traits\JsonResponse;

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

/**
 * Utility methods
 *
 * @since 1.0.0
 */
class Utils {
	use JsonResponse;

	/**
	 * Compatibility for splitting utils functions to specific model
	 *
	 * @since 2.0.6
	 *
	 * @param string $method method name.
	 * @param array  $args   args.
	 *
	 * @return mixed
	 */
	public function __call( $method, $args ) {
		$classes = array(
			'Tutor\Models\CourseModel',
			'Tutor\Models\LessonModel',
			'Tutor\Models\QuizModel',
			'Tutor\Models\WithdrawModel',
		);

		foreach ( $classes as $class ) {
			//phpcs:ignore
			if ( method_exists( $obj = new $class(), $method ) ) {
				return $obj->$method( ...$args );
			}
		}
	}

	/**
	 * Check an array is sequential or associative
	 *
	 * @since 2.0.9
	 *
	 * @param  array $array The array to check.
	 *
	 * @return bool   true if the array is associative, false if it's sequential.
	 */
	public function is_assoc( array $array ) {
		return array_keys( $array ) !== range( 0, count( $array ) - 1 );
	}

	/**
	 * Redirect to URL
	 *
	 * @since 2.1.0
	 *
	 * @param string $url URL.
	 * @param string $flash_message flash message.
	 * @param string $flash_type flash type.
	 *
	 * @return void
	 */
	public function redirect_to( string $url, $flash_message = null, $flash_type = 'success' ) {
		$url = esc_url( trim( $url ) );

		$available_types = array( 'success', 'error' );
		if ( ! empty( $flash_message ) && in_array( $flash_type, $available_types ) ) {
			set_transient( 'tutor_flash_type', $flash_type );
			set_transient( 'tutor_flash_message', $flash_message );
		}

		if ( ! headers_sent() ) {
			wp_safe_redirect( $url );
		} else {
			echo '<script>window.location.href = ' . "'" . esc_url( $url ) . "';" . '</script>';
		}

		exit;
	}

	/**
	 * Handle flash message for redirect_to util helper
	 *
	 * @since 2.1.0
	 *
	 * @return void
	 */
	public function handle_flash_message() {
		if ( false !== get_transient( 'tutor_flash_type' ) && false !== get_transient( 'tutor_flash_message' ) ) {
			$type    = get_transient( 'tutor_flash_type' );
			$message = get_transient( 'tutor_flash_message' );
			if ( 'success' === $type && ! empty( $message ) ) {
				?>
				<script type="text/javascript">
					window.onload = function(){
						const { __ } = wp.i18n;
						tutor_toast( __( 'Success!', 'tutor' ), '<?php echo esc_html( $message ); ?>', 'success' )
					};
				</script>
				<?php
			}
			if ( 'error' === $type && ! empty( $message ) ) {
				?>
				<script type="text/javascript">
					window.onload = function(){
						const { __ } = wp.i18n;
						tutor_toast( __( 'Error!', 'tutor' ), '<?php echo esc_html( $message ); ?>', 'error' )
					};
				</script>
				<?php
			}

			// Delete flash message.
			delete_transient( 'tutor_flash_type' );
			delete_transient( 'tutor_flash_message' );
		}
	}

	/**
	 * Add setting's option after a setting key
	 *
	 * @since 2.1.0
	 *
	 * @param string $target_key    setting's key name like 'tutor_version'.
	 * @param array  $arr           an multi-dimentional settings option array.
	 * @param array  $new_item      new setting array. a 'key' needed.
	 *
	 * @return int|null             inserted index number or null
	 */
	public function add_option_after( string $target_key, array &$arr, array $new_item ) {
		if ( ! is_array( $arr ) || ! is_array( $new_item ) ) {
			return;
		}

		$found_index = null;
		foreach ( $arr as $index => $inner_arr ) {
			if ( is_array( $inner_arr ) && array_key_exists( 'key', $inner_arr ) && $inner_arr['key'] == $target_key ) {
				$found_index = $index;
				break;
			}
		}

		if ( null !== $found_index && array_key_exists( 'key', $new_item ) ) {
			$target_index = $found_index + 1;
			array_splice( $arr, $target_index, 0, array( $new_item ) );
			return $target_index;
		}
	}

	/**
	 * Get human readable file size from file path
	 *
	 * @since 2.1.0
	 *
	 * @param string $file_path file path.
	 *
	 * @return string
	 */
	public function get_readable_filesize( string $file_path ) {
		return size_format( file_exists( $file_path ) ? filesize( $file_path ) : 0 );
	}

	/**
	 * Option recursive
	 *
	 * @since 1.0.0
	 *
	 * @param array  $array array.
	 * @param string $key option key.
	 *
	 * @return mixed
	 */
	private function option_recursive( $array, $key ) {
		foreach ( $array as $option ) {
			$is_array = is_array( $option );

			if ( $is_array && isset( $option['key'], $option['default'] ) && $option['key'] == $key ) {
				$value                                = $option['default'];
				'on' === $option['default'] ? $value  = true : 0;
				'off' === $option['default'] ? $value = false : 0;

				return $value;
			}

			$value = $is_array ? $this->option_recursive( $option, $key ) : null;

			if ( ! ( null === $value ) ) {
				return $value;
			}
		}

		return null;
	}

	/**
	 * Get default value for a tutor option.
	 *
	 * @since 1.0.0
	 *
	 * @param string $key option key.
	 * @param mixed  $fallback fallback value.
	 * @param mixed  $from_options from option.
	 *
	 * @return mixed
	 */
	private function get_option_default( $key, $fallback, $from_options ) {
		if ( ! $from_options ) {
			// Avoid infinity recursion.
			return $fallback;
		}

		$tutor_options_array                                      = ( new Options_V2( false ) )->get_setting_fields();
		! is_array( $tutor_options_array ) ? $tutor_options_array = array() : 0;

		$default_value = $this->option_recursive( $tutor_options_array, $key );

		return null === $default_value ? $fallback : $default_value;
	}

	/**
	 * Get option data
	 *
	 * @since 1.0.0
	 *
	 * @param string $key key.
	 * @param bool   $default default.
	 * @param bool   $type if false return string.
	 * @param bool   $from_options from option.
	 *
	 * @return array|bool|mixed
	 */
	public function get_option( $key, $default = false, $type = true, $from_options = false ) {
		$option = (array) maybe_unserialize( get_option( 'tutor_option' ) );

		if ( empty( $option ) || ! is_array( $option ) ) {
			// If the option array is not yet stored on database, then return default/fallback.
			return $this->get_option_default( $key, $default, $from_options );
		}

		// Get option value by option key.
		if ( array_key_exists( $key, $option ) ) {
			// Convert off/on switch values to boolean.
			$value = $option[ $key ];

			if ( true == $type ) {
				'off' === $value ? $value = false : 0;
				'on' === $value ? $value  = true : 0;
			}

			return apply_filters( $key, $value );
		}

		// Access array value via dot notation, such as option->get('value.subvalue').
		if ( strpos( $key, '.' ) ) {
			$option_key_array = explode( '.', $key );

			$new_option = $option;
			foreach ( $option_key_array as $dot_key ) {
				if ( isset( $new_option[ $dot_key ] ) ) {
					$new_option = $new_option[ $dot_key ];
				} else {
					return $this->get_option_default( $key, $default, $from_options );
				}
			}

			// Convert off/on switch values to boolean.
			$value = $new_option;

			if ( true == $type ) {
				'off' === $value ? $value = false : 0;
				'on' === $value ? $value  = true : 0;
			}

			return apply_filters( $key, $value );
		}

		return $this->get_option_default( $key, $default, $from_options );
	}

	/**
	 * Update Option
	 *
	 * @since 1.0.0
	 *
	 * @param null|string $key option key.
	 * @param mixed       $value option value.
	 *
	 * @return void
	 */
	public function update_option( $key = null, $value = false ) {
		$option         = (array) maybe_unserialize( get_option( 'tutor_option' ) );
		$option[ $key ] = $value;
		update_option( 'tutor_option', $option );
	}

	/**
	 * Get array value by dot notation
	 *
	 * @since 1.0.0
	 * @since 1.4.1 default parameter added
	 *
	 * @param null  $key option key.
	 * @param array $array array.
	 * @param mixed $default default value.
	 *
	 * @return array|bool|mixed
	 */
	public function avalue_dot( $key = null, $array = array(), $default = false ) {
		$array = (array) $array;
		if ( ! $key || ! count( $array ) ) {
			return $default;
		}
		$option_key_array = explode( '.', $key );

		$value = $array;

		foreach ( $option_key_array as $dot_key ) {
			if ( isset( $value[ $dot_key ] ) ) {
				$value = $value[ $dot_key ];
			} else {
				return $default;
			}
		}
		return $value;
	}

	/**
	 * Alias of avalue_dot method of utils
	 * Get array value by key and recursive array value by dot notation key
	 *
	 * Ex: $this->array_get('key.child_key', $array);
	 *
	 * @since 1.3.3
	 *
	 * @param null  $key key name.
	 * @param array $array array.
	 * @param mixed $default default value.
	 *
	 * @return array|bool|mixed
	 */
	public function array_get( $key = null, $array = array(), $default = false ) {
		return $this->avalue_dot( $key, $array, $default );
	}

	/**
	 * Get all pages
	 *
	 * @since 1.0.0
	 *
	 * @return array
	 */
	public function get_pages() {
		do_action( 'tutor_utils/get_pages/before' );

		$pages    = array();
		$wp_pages = get_posts(
			array(
				'post_type'   => 'page',
				'post_status' => 'publish',
				'numberposts' => -1,
			)
		);

		if ( is_array( $wp_pages ) && count( $wp_pages ) ) {
			foreach ( $wp_pages as $page ) {
				$pages[ $page->ID ] = $page->post_title;
			}
		}

		do_action( 'tutor_utils/get_pages/after' );

		return $pages;
	}

	/**
	 * Get all pages which are not translated.
	 *
	 * @since 1.0.0
	 *
	 * @return array
	 */
	public function get_not_translated_pages() {
		do_action( 'tutor_utils/get_pages/before' );

		$pages = array();

		$wp_pages = get_posts(
			array(
				'post_type'        => 'page',
				'suppress_filters' => true,
				'post_status'      => 'publish',
				'numberposts'      => -1,
			)
		);

		if ( is_array( $wp_pages ) && count( $wp_pages ) ) {
			foreach ( $wp_pages as $page ) {
				$translate_id = icl_object_id( $page->ID, 'page', true, ICL_LANGUAGE_CODE );
				if ( $page->ID === $translate_id ) {
					$pages[ $page->ID ] = $page->post_title;
				}
			}
		}

		do_action( 'tutor_utils/get_pages/after' );

		return $pages;
	}

	/**
	 * Get course archive URL
	 *
	 * @since 1.0.0
	 *
	 * @return string
	 */
	public function course_archive_page_url() {
		$course_post_type = tutor()->course_post_type;
		$course_page_url  = home_url( $this->get_option( 'course_permalink_base', $course_post_type ) );

		$course_archive_page = $this->get_option( 'course_archive_page' );
		if ( $course_archive_page && '-1' !== $course_archive_page ) {
			$course_archive_page = apply_filters( 'tutor_filter_course_archive_page', $course_archive_page );
			$course_page_url     = get_permalink( $course_archive_page );
		}
		return trailingslashit( $course_page_url );
	}

	/**
	 * Get profile URL.
	 *
	 * @since 1.0.0
	 * @since 2.1.7 changed param $student_id to $user.
	 *
	 * @param int|object $user              student ID or object.
	 * @param bool       $instructor_view   instractior view.
	 * @param string     $fallback_url      fallback URL.
	 *
	 * @return string
	 */
	public function profile_url( $user = 0, $instructor_view = false, $fallback_url = '#' ) {
		$instructor_profile = $this->get_option( 'public_profile_layout' ) != 'private';
		$student_profile    = $this->get_option( 'student_public_profile_layout' ) != 'private';
		if ( ( $instructor_view && ! $instructor_profile ) || ( ! $instructor_view && ! $student_profile ) ) {
			return $fallback_url;
		}

		$site_url = trailingslashit( home_url() ) . 'profile/';
		if ( ! is_object( $user ) ) {
			$user = get_userdata( $this->get_user_id( $user ) );
		}

		$user_name = ( is_object( $user ) && isset( $user->user_nicename ) ) ? $user->user_nicename : 'user_name';

		return add_query_arg( array( 'view' => $instructor_view ? 'instructor' : 'student' ), $site_url . $user_name );
	}

	/**
	 * Get user by user login
	 *
	 * @since 1.0.0
	 *
	 * @param string $user_nicename user nicename.
	 *
	 * @return array|null|object
	 */
	public function get_user_by_login( $user_nicename = '' ) {
		global $wpdb;
		$user_nicename = sanitize_text_field( $user_nicename );
		$user          = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT *
							FROM 	{$wpdb->users}
							WHERE 	user_nicename = %s;
							",
				$user_nicename
			)
		);
		return $user;
	}

	/**
	 * Check if WooCommerce Activated
	 *
	 * @since 1.0.0
	 *
	 * @return bool
	 */
	public function has_wc() {
		return class_exists( 'WooCommerce' );
	}

	/**
	 * Determine if EDD plugin activated
	 *
	 * @since 1.0.0
	 *
	 * @return bool
	 */
	public function has_edd() {
		return class_exists( 'Easy_Digital_Downloads' );
	}

	/**
	 * Determine if PMPro is activated
	 *
	 * @since 1.3.6
	 *
	 * @param bool $check_monetization check monetization.
	 *
	 * @return bool
	 */
	public function has_pmpro( $check_monetization = false ) {
		$has_pmpro = $this->is_plugin_active( 'paid-memberships-pro/paid-memberships-pro.php' );
		return $has_pmpro && ( ! $check_monetization || get_tutor_option( 'monetize_by' ) == 'pmpro' );
	}

	/**
	 * Check is monetize by tutor e-commerce
	 *
	 * @since 3.0.0
	 *
	 * @return boolean
	 */
	public function is_monetize_by_tutor() {
		$monetize_by = $this->get_option( 'monetize_by' );
		return Ecommerce::MONETIZE_BY === $monetize_by;
	}

	/**
	 * Check plugin active status.
	 *
	 * @since 1.0.0
	 *
	 * @param string $plugin_path plugin path.
	 *
	 * @return boolean
	 */
	public function is_plugin_active( $plugin_path ) {
		$activated_plugins = apply_filters( 'active_plugins', get_option( 'active_plugins' ) );
		$depends           = is_array( $plugin_path ) ? $plugin_path : array( $plugin_path );
		$has_plugin        = count( array_intersect( $depends, $activated_plugins ) ) == count( $depends );

		return $has_plugin;
	}

	/**
	 * Check WC subscription activated.
	 *
	 * @since 1.0.0
	 *
	 * @return boolean
	 */
	public function has_wcs() {
		$has_wcs = $this->is_plugin_active( 'woocommerce-subscriptions/woocommerce-subscriptions.php' );
		return $has_wcs;
	}

	/**
	 * Check addon status.
	 *
	 * @since 1.0.0
	 *
	 * @param string $basename addon base name.
	 *
	 * @return boolean
	 */
	public function is_addon_enabled( $basename ) {
		if ( $this->is_plugin_active( 'tutor-pro/tutor-pro.php' ) ) {
			$addon_config = $this->get_addon_config( $basename );

			return (bool) $this->avalue_dot( 'is_enable', $addon_config );
		}

		return false;
	}

	/**
	 * Checking if BuddyPress exists and activated.
	 *
	 * @since 1.4.8
	 *
	 * @return bool
	 */
	public function has_bp() {
		$activated_plugins = apply_filters( 'active_plugins', get_option( 'active_plugins' ) );
		$depends           = array( 'buddypress/bp-loader.php' );
		$has_bp            = count( array_intersect( $depends, $activated_plugins ) ) == count( $depends );
		return $has_bp;
	}

	/**
	 * Get languages list.
	 *
	 * @since 1.0.0
	 *
	 * @return array
	 */
	public function languages() {
		$language_codes = array(
			'en' => 'English',
			'aa' => 'Afar',
			'ab' => 'Abkhazian',
			'af' => 'Afrikaans',
			'am' => 'Amharic',
			'ar' => 'Arabic',
			'as' => 'Assamese',
			'ay' => 'Aymara',
			'az' => 'Azerbaijani',
			'ba' => 'Bashkir',
			'be' => 'Byelorussian',
			'bg' => 'Bulgarian',
			'bh' => 'Bihari',
			'bi' => 'Bislama',
			'bn' => 'Bengali/Bangla',
			'bo' => 'Tibetan',
			'br' => 'Breton',
			'ca' => 'Catalan',
			'co' => 'Corsican',
			'cs' => 'Czech',
			'cy' => 'Welsh',
			'da' => 'Danish',
			'de' => 'German',
			'dz' => 'Bhutani',
			'el' => 'Greek',
			'eo' => 'Esperanto',
			'es' => 'Spanish',
			'et' => 'Estonian',
			'eu' => 'Basque',
			'fa' => 'Persian',
			'fi' => 'Finnish',
			'fj' => 'Fiji',
			'fo' => 'Faeroese',
			'fr' => 'French',
			'fy' => 'Frisian',
			'ga' => 'Irish',
			'gd' => 'Scots/Gaelic',
			'gl' => 'Galician',
			'gn' => 'Guarani',
			'gu' => 'Gujarati',
			'ha' => 'Hausa',
			'hi' => 'Hindi',
			'hr' => 'Croatian',
			'hu' => 'Hungarian',
			'hy' => 'Armenian',
			'ia' => 'Interlingua',
			'ie' => 'Interlingue',
			'ik' => 'Inupiak',
			'in' => 'Indonesian',
			'is' => 'Icelandic',
			'it' => 'Italian',
			'iw' => 'Hebrew',
			'ja' => 'Japanese',
			'ji' => 'Yiddish',
			'jw' => 'Javanese',
			'ka' => 'Georgian',
			'kk' => 'Kazakh',
			'kl' => 'Greenlandic',
			'km' => 'Cambodian',
			'kn' => 'Kannada',
			'ko' => 'Korean',
			'ks' => 'Kashmiri',
			'ku' => 'Kurdish',
			'ky' => 'Kirghiz',
			'la' => 'Latin',
			'ln' => 'Lingala',
			'lo' => 'Laothian',
			'lt' => 'Lithuanian',
			'lv' => 'Latvian/Lettish',
			'mg' => 'Malagasy',
			'mi' => 'Maori',
			'mk' => 'Macedonian',
			'ml' => 'Malayalam',
			'mn' => 'Mongolian',
			'mo' => 'Moldavian',
			'mr' => 'Marathi',
			'ms' => 'Malay',
			'mt' => 'Maltese',
			'my' => 'Burmese',
			'na' => 'Nauru',
			'ne' => 'Nepali',
			'nl' => 'Dutch',
			'no' => 'Norwegian',
			'oc' => 'Occitan',
			'om' => '(Afan)/Oromoor/Oriya',
			'pa' => 'Punjabi',
			'pl' => 'Polish',
			'ps' => 'Pashto/Pushto',
			'pt' => 'Portuguese',
			'qu' => 'Quechua',
			'rm' => 'Rhaeto-Romance',
			'rn' => 'Kirundi',
			'ro' => 'Romanian',
			'ru' => 'Russian',
			'rw' => 'Kinyarwanda',
			'sa' => 'Sanskrit',
			'sd' => 'Sindhi',
			'sg' => 'Sangro',
			'sh' => 'Serbo-Croatian',
			'si' => 'Singhalese',
			'sk' => 'Slovak',
			'sl' => 'Slovenian',
			'sm' => 'Samoan',
			'sn' => 'Shona',
			'so' => 'Somali',
			'sq' => 'Albanian',
			'sr' => 'Serbian',
			'ss' => 'Siswati',
			'st' => 'Sesotho',
			'su' => 'Sundanese',
			'sv' => 'Swedish',
			'sw' => 'Swahili',
			'ta' => 'Tamil',
			'te' => 'Tegulu',
			'tg' => 'Tajik',
			'th' => 'Thai',
			'ti' => 'Tigrinya',
			'tk' => 'Turkmen',
			'tl' => 'Tagalog',
			'tn' => 'Setswana',
			'to' => 'Tonga',
			'tr' => 'Turkish',
			'ts' => 'Tsonga',
			'tt' => 'Tatar',
			'tw' => 'Twi',
			'uk' => 'Ukrainian',
			'ur' => 'Urdu',
			'uz' => 'Uzbek',
			'vi' => 'Vietnamese',
			'vo' => 'Volapuk',
			'wo' => 'Wolof',
			'xh' => 'Xhosa',
			'yo' => 'Yoruba',
			'zh' => 'Chinese',
			'zu' => 'Zulu',
		);

		return apply_filters( 'tutor/utils/languages', $language_codes );
	}

	/**
	 * Check raw data.
	 *
	 * @since 1.0.0
	 *
	 * @param string $value value.
	 *
	 * @return void
	 */
	public function print_view( $value = '' ) {
		echo '<pre>';
		print_r( $value );
		echo '</pre>';
	}

	/**
	 * Get completed lesson total number by a course
	 *
	 * @since 1.0.0
	 *
	 * @param int $course_id course ID.
	 * @param int $user_id user ID.
	 *
	 * @return int
	 */
	public function get_completed_lesson_count_by_course( $course_id = 0, $user_id = 0 ) {
		global $wpdb;
		$course_id = $this->get_post_id( $course_id );
		$user_id   = $this->get_user_id( $user_id );

		$lesson_ids = $this->get_course_content_ids_by( tutor()->lesson_post_type, tutor()->course_post_type, $course_id );
		$count      = 0;
		if ( count( $lesson_ids ) ) {
			$completed_lesson_meta_ids = array();
			foreach ( $lesson_ids as $lesson_id ) {
				$completed_lesson_meta_ids[] = '_tutor_completed_lesson_id_' . $lesson_id;
			}
			$in_ids = implode( "','", $completed_lesson_meta_ids );

			$prepare_ids = str_replace( "','", '', $in_ids );
			$cache_key   = "tutor_get_completed_lesson_count_by{$user_id}_{$prepare_ids}";
			$count       = TutorCache::get( $cache_key );

			if ( false === $count ) {
				$count = (int) $wpdb->get_var(
					$wpdb->prepare(
						"SELECT count(umeta_id)
					FROM	{$wpdb->usermeta}
					WHERE	user_id = %d
							AND meta_key IN ('{$in_ids}')
					",
						$user_id
					)
				);
				TutorCache::set( $cache_key, $count );
			}
		}

		return $count;
	}

	/**
	 * Get course completed percentage.
	 *
	 * @since 1.0.0
	 * @since 1.6.1 get status param added.
	 *
	 * @param int  $course_id course ID.
	 * @param int  $user_id user ID.
	 * @param bool $get_stats get status.
	 *
	 * @return mixed
	 */
	public function get_course_completed_percent( $course_id = 0, $user_id = 0, $get_stats = false ) {
		$course_id        = $this->get_post_id( $course_id );
		$user_id          = $this->get_user_id( $user_id );
		$completed_lesson = $this->get_completed_lesson_count_by_course( $course_id, $user_id );
		$course_contents  = $this->get_course_contents_by_id( $course_id );
		$total_contents   = $this->count( $course_contents );
		$total_contents   = $total_contents ? $total_contents : 0;
		$completed_count  = $completed_lesson;

		$quiz_ids       = array();
		$assignment_ids = array();

		foreach ( $course_contents as $content ) {
			if ( 'tutor_quiz' === $content->post_type ) {
				$quiz_ids[] = (int) $content->ID;
			}
			if ( 'tutor_assignments' === $content->post_type ) {
				$assignment_ids[] = (int) $content->ID;
			}
		}

		global $wpdb;

		if ( count( $quiz_ids ) ) {
			$quiz_ids_str = QueryHelper::prepare_in_clause( $quiz_ids );

			// Get data from cache.
			$prepare_quiz_ids_str     = str_replace( ',', '_', $quiz_ids_str );
			$quiz_completed_cache_key = "tutor_quiz_completed_{$user_id}_{$prepare_quiz_ids_str}";
			$quiz_completed           = TutorCache::get( $quiz_completed_cache_key );

			if ( false === $quiz_completed ) {
				//phpcs:disable
				$quiz_completed = (int) $wpdb->get_var(
					$wpdb->prepare(
						"SELECT count(quiz_id) completed 
						FROM (
							SELECT  DISTINCT quiz_id 
							FROM 	{$wpdb->tutor_quiz_attempts} 
							WHERE 	quiz_id IN ({$quiz_ids_str}) 
									AND user_id = % d 
									AND attempt_status != %s
						) a",
						$user_id,
						QuizModel::ATTEMPT_STARTED
					)
				);
				//phpcs:enable
				TutorCache::set( $quiz_completed_cache_key, $quiz_completed );
			}
			$completed_count += $quiz_completed;
		}

		if ( count( $assignment_ids ) ) {
			$assignment_ids_str = QueryHelper::prepare_in_clause( $assignment_ids );

			// Get data from cache.
			$prepare_assignment_ids_str     = str_replace( ',', '_', $assignment_ids_str );
			$assignment_submitted_cache_key = "tutor_assignment_submitted{$user_id}_{$prepare_assignment_ids_str}";
			$assignment_submitted           = TutorCache::get( $assignment_submitted_cache_key );

			if ( false === $assignment_submitted ) {
				$assignment_submitted = (int) $wpdb->get_var(
					$wpdb->prepare(
						"SELECT count(*) completed
						FROM 	{$wpdb->comments}
						WHERE 	comment_type = %s
								AND comment_approved = %s
								AND user_id = %d
								AND comment_post_ID IN({$assignment_ids_str});
						",
						'tutor_assignment',
						'submitted',
						$user_id
					)
				);
				TutorCache::set( $assignment_submitted_cache_key, $assignment_submitted );
			}
			$completed_count += $assignment_submitted;
		}

		if ( $this->count( $course_contents ) ) {
			foreach ( $course_contents as $content ) {
				if ( 'tutor_zoom_meeting' === $content->post_type ) {
					/**
					 * Count zoom lesson completion for course progress
					 *
					 * @since 2.0.0
					 */
					$is_completed = apply_filters( 'tutor_is_zoom_lesson_done', false, $content->ID, $user_id );
					if ( $is_completed ) {
						++$completed_count;
					}
				} elseif ( 'tutor-google-meet' === $content->post_type ) {
					/**
					 * Count zoom lesson completion for course progress
					 *
					 * @since 2.0.0
					 */
					$is_completed = apply_filters( 'tutor_google_meet_lesson_done', false, $content->ID, $user_id );
					if ( $is_completed ) {
						++$completed_count;
					}
				}
			}
		}

		$percent_complete = 0;

		if ( $total_contents > 0 && $completed_count > 0 ) {
			$percent_complete = number_format( ( $completed_count * 100 ) / $total_contents );
		}

		if ( $get_stats ) {
			return array(
				'completed_percent' => $percent_complete,
				'completed_count'   => $completed_count,
				'total_count'       => $total_contents,
			);
		}

		return $percent_complete;
	}

	/**
	 * Get all topics by given course ID
	 *
	 * @since 1.0.0
	 *
	 * @param int $course_id course ID.
	 *
	 * @return \WP_Query
	 */
	public function get_topics( $course_id = 0 ) {
		$course_id = $this->get_post_id( $course_id );

		$args = array(
			'post_type'      => 'topics',
			'post_parent'    => $course_id,
			'orderby'        => 'menu_order',
			'order'          => 'ASC',
			'posts_per_page' => -1,
		);

		$query = new \WP_Query( $args );

		return $query;
	}

	/**
	 * Get next topic order id
	 *
	 * @since 1.0.0
	 *
	 * @param int   $course_id course ID.
	 * @param mixed $content_id content ID.
	 *
	 * @return int
	 */
	public function get_next_topic_order_id( $course_id, $content_id = null ) {
		global $wpdb;

		if ( $content_id ) {
			$existing_order = get_post_field( 'menu_order', $content_id );

			if ( $existing_order >= 0 ) {
				return $existing_order;
			}
		}

		$last_order = (int) $wpdb->get_var(
			$wpdb->prepare(
				"SELECT MAX(menu_order)
			FROM 	{$wpdb->posts}
			WHERE 	post_parent = %d
					AND post_type = %s;
			",
				$course_id,
				'topics'
			)
		);

		return $last_order + 1;
	}

	/**
	 * Get next course content order id
	 *
	 * @since 1.0.0
	 *
	 * @param int   $topic_id topic ID.
	 * @param mixed $content_id content ID.
	 *
	 * @return int
	 */
	public function get_next_course_content_order_id( $topic_id, $content_id = null ) {
		global $wpdb;

		if ( $content_id ) {
			$existing_order = get_post_field( 'menu_order', $content_id );

			if ( $existing_order >= 0 ) {
				return $existing_order;
			}
		}

		$last_order = (int) $wpdb->get_var(
			$wpdb->prepare(
				"SELECT MAX(menu_order)
			FROM	{$wpdb->posts}
			WHERE	post_parent = %d;
			",
				$topic_id
			)
		);

		return is_numeric( $last_order ) ? $last_order + 1 : 0;
	}

	/**
	 * Get course content by topic
	 *
	 * @since 1.0.0
	 *
	 * @param int $topics_id topics ID.
	 * @param int $limit limit.
	 *
	 * @return \WP_Query
	 */
	public function get_course_contents_by_topic( $topics_id = 0, $limit = 10 ) {
		$topics_id        = $this->get_post_id( $topics_id );
		$lesson_post_type = tutor()->lesson_post_type;
		$post_type        = array_unique( apply_filters( 'tutor_course_contents_post_types', array( $lesson_post_type, 'tutor_quiz' ) ) );

		$args = array(
			'post_type'      => $post_type,
			'post_parent'    => $topics_id,
			'posts_per_page' => $limit,
			'orderby'        => 'menu_order',
			'order'          => 'ASC',
		);

		return new \WP_Query( $args );
	}

	/**
	 * Check tutor nonce is verified.
	 *
	 * @since 3.0.0
	 *
	 * @param string $request_method request method.
	 *
	 * @return bool.
	 */
	public function is_nonce_verified( $request_method = null ) {
		! $request_method ? $request_method = sanitize_text_field( $_SERVER['REQUEST_METHOD'] ) : 0; //phpcs:ignore

		$data        = strtolower( $request_method ) === 'post' ? $_POST : $_GET; //phpcs:ignore
		$nonce_value = sanitize_text_field( $this->array_get( tutor()->nonce, $data, null ) );
		$is_matched  = $nonce_value && wp_verify_nonce( $nonce_value, tutor()->nonce_action );

		return $is_matched;
	}

	/**
	 * Check actions nonce.
	 *
	 * @since 1.0.0
	 *
	 * @param string $request_method request method.
	 *
	 * @return void.
	 */
	public function checking_nonce( $request_method = null ) {
		if ( ! $this->is_nonce_verified( $request_method ) ) {
			wp_send_json_error( array( 'message' => $this->error_message( 'nonce' ) ) );
			exit;
		}
	}

	/**
	 * Check nonce
	 *
	 * @since 3.0.0
	 *
	 * @return void JSON response.
	 */
	public function check_nonce() {
		if ( ! $this->is_nonce_verified() ) {
			$this->json_response( $this->error_message( 'nonce' ), null, HttpHelper::STATUS_BAD_REQUEST );
		}
	}


	/**
	 * Check current user capability and send json response
	 *
	 * @since 3.0.0
	 *
	 * @param string $capability User capability, default manage_options.
	 * @param int    $object_id post id to check with capability.
	 *
	 * @return void JSON response.
	 */
	public function check_current_user_capability( string $capability = 'manage_options', int $object_id = 0 ) {
		$can = $object_id ? current_user_can( $capability, $object_id ) : current_user_can( $capability );
		if ( ! $can ) {
			$this->json_response( $this->error_message(), null, HttpHelper::STATUS_UNAUTHORIZED );
		}
	}

	/**
	 * Check is course purchaseable.
	 *
	 * @since 1.0.0
	 *
	 * @param int $course_id course ID.
	 *
	 * @return bool
	 */
	public function is_course_purchasable( $course_id = 0 ) {

		$is_purchaseable = false;

		$course_id  = $this->get_post_id( $course_id );
		$price_type = $this->price_type( $course_id );

		if ( Course::PRICE_TYPE_PAID === $price_type ) {
			$is_purchaseable = true;
		} elseif ( Course::PRICE_TYPE_FREE === $price_type ) {
			$is_purchaseable = apply_filters( 'is_course_paid', $is_purchaseable, $course_id );
		}

		return apply_filters( 'is_course_purchasable', $is_purchaseable, $course_id );
	}

	/**
	 * Get course price in digits format if any.
	 *
	 * @since 1.0.0
	 *
	 * @since 3.0.0
	 *
	 * If monetize by is Tutor then it will return course
	 * formatted price
	 *
	 * @see tutor_get_course_formatted_price
	 *
	 * @param int $course_id course ID.
	 *
	 * @return null|string
	 */
	public function get_course_price( $course_id = 0 ) {
		$price      = null;
		$course_id  = $this->get_post_id( $course_id );
		$product_id = $this->get_course_product_id( $course_id );
		if ( $this->is_course_purchasable( $course_id ) ) {
			$monetize_by = $this->get_option( 'monetize_by' );
			if ( $this->has_wc() && 'wc' === $monetize_by ) {
				$product = wc_get_product( $product_id );
				if ( $product ) {
					$price = $product->get_price();
				}
			} elseif ( 'edd' === $monetize_by && function_exists( 'edd_price' ) ) {
				$download = new \EDD_Download( $product_id );
				$price    = \edd_price( $download->ID, false );
			} elseif ( $this->is_monetize_by_tutor() ) {
				$price = \tutor_get_course_formatted_price_html( $course_id, false );
			}
		}

		return apply_filters( 'get_tutor_course_price', $price, $course_id );
	}

	/**
	 * Get raw course price and sale price of a course
	 * It could help you to calculate something
	 * Such as Calculate discount by regular price and sale price
	 *
	 * @since 1.3.1
	 * @since 3.0.0 tax support added for monetized by tutor.
	 *
	 * @param int $course_id courrse ID.
	 *
	 * @return object
	 */
	public function get_raw_course_price( $course_id = 0 ) {
		$course_id = $this->get_post_id( $course_id );

		$prices = array(
			'regular_price' => 0,
			'sale_price'    => 0,
		);

		$monetize_by = $this->get_option( 'monetize_by' );

		if ( $this->is_monetize_by_tutor() ) {
			$regular_price = (float) get_post_meta( $course_id, Course::COURSE_PRICE_META, true );
			$sale_price    = (float) get_post_meta( $course_id, Course::COURSE_SALE_PRICE_META, true );

			$prices = $this->get_prices_with_tax_info( $regular_price, $sale_price );
		} else {
			$product_id = $this->get_course_product_id( $course_id );
			if ( $product_id ) {
				if ( 'wc' === $monetize_by && $this->has_wc() ) {
					$product = wc_get_product( $product_id );
					if ( $product ) {
						$prices['regular_price'] = $product->get_regular_price();
						$prices['sale_price']    = $product->get_sale_price();
					}
				} elseif ( 'edd' === $monetize_by && $this->has_edd() ) {
					$prices['regular_price'] = get_post_meta( $product_id, 'edd_price', true );
					$prices['sale_price']    = get_post_meta( $product_id, 'edd_price', true );
				}
			}
		}

		return (object) $prices;
	}

	/**
	 * Get prices with tax info
	 *
	 * @since 3.0.0
	 *
	 * @param int|float $regular_price regular price.
	 * @param int|float $sale_price sale price.
	 *
	 * @return object
	 */
	public function get_prices_with_tax_info( $regular_price, $sale_price = null ) {

		$display_price       = $sale_price ? $sale_price : $regular_price;
		$show_price_with_tax = Tax::show_price_with_tax();
		$user_logged_in      = is_user_logged_in();

		$tax_amount = 0;
		$tax_rate   = 0;
		if ( $show_price_with_tax && is_numeric( $display_price ) && ! Tax::is_tax_included_in_price() ) {
			$tax_rate       = $user_logged_in ? Tax::get_user_tax_rate() : 0;
			$tax_amount     = Tax::calculate_tax( $display_price, $tax_rate );
			$display_price += $tax_amount;
		}

		$price_info = array();

		$price_info['regular_price']       = $regular_price;
		$price_info['sale_price']          = $sale_price;
		$price_info['display_price']       = $display_price;
		$price_info['tax_rate']            = $tax_rate;
		$price_info['tax_amount']          = $tax_amount;
		$price_info['show_price_with_tax'] = $user_logged_in && $show_price_with_tax;

		return (object) $price_info;
	}

	/**
	 * Get the course price type
	 *
	 * @since 1.3.5
	 *
	 * @param int $course_id course ID.
	 *
	 * @return mixed
	 */
	public function price_type( $course_id = 0 ) {
		$course_id = $this->get_post_id( $course_id );

		$price_type = get_post_meta( $course_id, Course::COURSE_PRICE_TYPE_META, true );
		return $price_type;
	}

	/**
	 * Check if current user has been enrolled or not
	 *
	 * @since 1.0.0
	 *
	 * @since 3.0.0
	 *
	 * $is_complete parameter added to check with completed status
	 * Default value set true for backward compatibility. It set
	 * false then it will just check record.
	 *
	 * @param int  $course_id course id.
	 * @param int  $user_id user id.
	 * @param bool $is_complete Whether to enrollment completed or not.
	 *
	 * @return array|bool|null|object
	 */
	public function is_enrolled( $course_id = 0, $user_id = 0, bool $is_complete = true ) {
		global $wpdb;
		$course_id = $this->get_post_id( $course_id );
		$user_id   = $this->get_user_id( $user_id );
		$cache_key = "tutor_is_enrolled_{$course_id}_{$user_id}";

		do_action( 'tutor_is_enrolled_before', $course_id, $user_id );

		$get_enrolled_info = TutorCache::get( $cache_key );
		if ( ! $get_enrolled_info ) {
			$status_clause = '';
			if ( $is_complete ) {
				$status_clause = "AND post_status = 'completed' ";
			}

			$get_enrolled_info = $wpdb->get_row(
				$wpdb->prepare(
					"SELECT ID,
					post_author,
					post_date,
					post_date_gmt,
					post_title
				FROM {$wpdb->posts}
				WHERE post_author > 0 
					AND post_parent > 0
					AND post_type = %s
					AND post_parent = %d
					AND post_author = %d
					{$status_clause};
				",
					'tutor_enrolled',
					$course_id,
					$user_id
				)
			);
			TutorCache::set( $cache_key, $get_enrolled_info );
		}

		if ( $get_enrolled_info ) {
			return apply_filters( 'tutor_is_enrolled', $get_enrolled_info, $course_id, $user_id );
		}

		return false;
	}

	/**
	 * Delete course progress
	 *
	 * @since 1.9.5
	 *
	 * @param int $course_id course ID.
	 * @param int $user_id user id.
	 *
	 * @return void
	 */
	public function delete_course_progress( $course_id = 0, $user_id = 0 ) {
		global $wpdb;
		$course_id = $this->get_post_id( $course_id );
		$user_id   = $this->get_user_id( $user_id );

		// Delete Quiz submissions.
		$attempts = \Tutor\Models\QuizModel::get_quiz_attempts_by_course_ids( $start = 0, $limit = 99999999, $course_ids = array( $course_id ), $search_filter = '', $course_filter = '', $date_filter = '', $order_filter = '', $user_id = $user_id, false, true );

		if ( is_array( $attempts ) ) {
			$attempt_ids = array_map(
				function ( $attempt ) {
					return is_object( $attempt ) ? $attempt->attempt_id : 0;
				},
				$attempts
			);

			$this->delete_quiz_attempt( $attempt_ids );
		}

		// Delete Course completion row.
		$del_where = array(
			'user_id'         => $user_id,
			'comment_post_ID' => $course_id,
			'comment_type'    => 'course_completed',
			'comment_agent'   => 'TutorLMSPlugin',
		);
		$wpdb->delete( $wpdb->comments, $del_where );

		// Delete Completed lesson count.
		$lesson_ids = $this->get_course_content_ids_by( tutor()->lesson_post_type, tutor()->course_post_type, $course_id );
		foreach ( $lesson_ids as $id ) {
			delete_user_meta( $user_id, '_tutor_completed_lesson_id_' . $id );
			delete_user_meta( $user_id, '_lesson_reading_info' );
		}

		// Delete other addon-wise stuffs by hook, specially assignment.
		do_action( 'delete_tutor_course_progress', $course_id, $user_id );
	}

	/**
	 * Has any enrolled for a user in a course
	 *
	 * @since 1.0.0
	 *
	 * @param int $course_id course ID.
	 * @param int $user_id user ID.
	 *
	 * @return array|bool|null|object|void
	 */
	public function has_any_enrolled( $course_id = 0, $user_id = 0 ) {
		$course_id = $this->get_post_id( $course_id );
		$user_id   = $this->get_user_id( $user_id );

		if ( is_user_logged_in() ) {
			global $wpdb;

			$enrolled_info = $wpdb->get_row(
				$wpdb->prepare(
					"SELECT ID,
						post_author,
						post_date,
						post_date_gmt,
						post_title
				FROM 	{$wpdb->posts}
				WHERE 	post_type = %s
						AND post_parent = %d
						AND post_author = %d;
				",
					'tutor_enrolled',
					$course_id,
					$user_id
				)
			);

			if ( $enrolled_info ) {
				return $enrolled_info;
			}
		}

		return false;
	}


	/**
	 * Get course by enrol id
	 *
	 * @since 1.6.1
	 *
	 * @param int $enrol_id enrol ID.
	 *
	 * @return array|bool|\WP_Post|null
	 */
	public function get_course_by_enrol_id( $enrol_id = 0 ) {
		if ( ! $enrol_id ) {
			return false;
		}

		global $wpdb;

		$course_id = (int) $wpdb->get_var(
			$wpdb->prepare(
				"SELECT post_parent
			FROM	{$wpdb->posts}
			WHERE	post_type = %s
					AND ID = %d
			",
				'tutor_enrolled',
				$enrol_id
			)
		);

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

		return null;
	}

	/**
	 * Get the course Enrolled confirmation by lesson ID
	 *
	 * @since 1.0.0
	 *
	 * @param int $lesson_id lesson ID.
	 * @param int $user_id user ID.
	 *
	 * @return array|bool|null|object
	 */
	public function is_course_enrolled_by_lesson( $lesson_id = 0, $user_id = 0 ) {
		$lesson_id = $this->get_post_id( $lesson_id );
		$user_id   = $this->get_user_id( $user_id );
		$course_id = $this->get_course_id_by( 'lesson', $lesson_id );

		return $this->is_enrolled( $course_id );
	}

	/**
	 * Get the course ID by Lesson
	 *
	 * @since 1.0.0
	 * @since 1.4.8 Legacy Supports Added.
	 *
	 * @param int $lesson_id lesson id.
	 *
	 * @return bool|mixed
	 */
	public function get_course_id_by_lesson( $lesson_id = 0 ) {
		$lesson_id = $this->get_post_id( $lesson_id );
		$course_id = $this->get_course_id_by( 'lesson', $lesson_id );

		if ( ! $course_id ) {
			$course_id = $this->get_course_id_by_content( $lesson_id );
		}
		if ( ! $course_id ) {
			$course_id = 0;
		}

		return $course_id;
	}

	/**
	 * Get first lesson of a course
	 *
	 * @since 1.0.0
	 *
	 * @param int   $course_id course ID.
	 * @param mixed $post_type post type.
	 *
	 * @return bool|false|string
	 */
	public function get_course_first_lesson( $course_id = 0, $post_type = null ) {
		global $wpdb;

		$course_id = $this->get_post_id( $course_id );
		$user_id   = get_current_user_id();

		$lessons = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT items.ID
			FROM 	{$wpdb->posts} topic
					INNER JOIN {$wpdb->posts} items
							ON topic.ID = items.post_parent
			WHERE 	topic.post_parent = %d
					AND items.post_status = %s
					" . ( $post_type ? " AND items.post_type='{$post_type}' " : '' ) . '
			ORDER BY topic.menu_order ASC,
					items.menu_order ASC;
			',
				$course_id,
				'publish'
			)
		);

		$first_lesson = false;

		if ( $this->count( $lessons ) ) {
			if ( ! empty( $lessons[0] ) ) {
				$first_lesson = $lessons[0];
			}

			foreach ( $lessons as $lesson ) {
				$is_complete = get_user_meta( $user_id, "_tutor_completed_lesson_id_{$lesson->ID}", true );
				if ( ! $is_complete && ! $this->has_attempted_quiz( $user_id, $lesson->ID ) ) {
					$first_lesson = $lesson;
					break;
				}
			}

			if ( ! empty( $first_lesson->ID ) ) {
				return get_permalink( $first_lesson->ID );
			}
		}

		return false;
	}

	/**
	 * Get post video.
	 *
	 * @since 1.0.0
	 *
	 * @param int $post_id post ID.
	 *
	 * @return bool|array
	 */
	public function get_video( $post_id = 0 ) {
		$post_id     = $this->get_post_id( $post_id );
		$attachments = get_post_meta( $post_id, '_video', true );
		if ( $attachments ) {
			$attachments = maybe_unserialize( $attachments );
		}
		return $attachments;
	}

	/**
	 * Update the video Info
	 *
	 * @since 1.0.0
	 *
	 * @param int   $post_id post ID.
	 * @param array $video_data video data.
	 *
	 * @return void
	 */
	public function update_video( $post_id = 0, $video_data = array() ) {
		$post_id = $this->get_post_id( $post_id );

		if ( is_array( $video_data ) && count( $video_data ) ) {
			update_post_meta( $post_id, '_video', $video_data );
		}
	}

	/**
	 * Get tutor attachment
	 *
	 * @since 1.0.0
	 *
	 * @since 2.2.0
	 * count param added to count attachment.
	 *
	 * @param int    $post_id post id.
	 * @param string $meta_key meta key.
	 * @param bool   $count set true to get only count.
	 *
	 * @return array
	 */
	public function get_attachments( $post_id = 0, $meta_key = '_tutor_attachments', $count = false ) {
		$post_id         = $this->get_post_id( $post_id );
		$attachments     = maybe_unserialize( get_post_meta( $post_id, $meta_key, true ) );
		$attachments_arr = array();

		// Since 2.2.0 get only count if required.
		if ( $count ) {
			return is_array( $attachments ) ? count( $attachments ) : 0;
		}

		if ( is_array( $attachments ) && count( $attachments ) ) {
			foreach ( $attachments as $attachment ) {
				$data              = (array) $this->get_attachment_data( $attachment );
				$attachments_arr[] = (object) apply_filters( 'tutor/posts/attachments', $data );
			}
		}

		return $attachments_arr;
	}

	/**
	 * Get attachment data.
	 *
	 * @since 1.0.0
	 *
	 * @param mixed $attachment_id attachment id.
	 *
	 * @return object
	 */
	public function get_attachment_data( $attachment_id ) {
		$url       = wp_get_attachment_url( $attachment_id );
		$file_type = wp_check_filetype( $url );
		$ext       = $file_type['ext'];
		$title     = get_the_title( $attachment_id );

		$file_path  = get_attached_file( $attachment_id );
		$size_bytes = file_exists( $file_path ) ? filesize( $file_path ) : 0;
		$size       = size_format( $size_bytes, 2 );
		$type       = wp_ext2type( $ext );

		$icon       = 'default';
		$font_icons = apply_filters(
			'tutor_file_types_icon',
			array(
				'archive',
				'audio',
				'code',
				'default',
				'document',
				'interactive',
				'spreadsheet',
				'text',
				'video',
				'image',
			)
		);

		if ( $type && in_array( $type, $font_icons ) ) {
			$icon = $type;
		}

		$data = array(
			'post_id'    => $attachment_id,
			'id'         => $attachment_id,
			'url'        => $url,
			'name'       => $title . '.' . $ext,
			'title'      => $title,
			'ext'        => $ext,
			'size'       => $size,
			'size_bytes' => $size_bytes,
			'icon'       => $icon,
		);

		return (object) $data;
	}

	/**
	 * Return seconds to formatted playtime.
	 *
	 * @since 1.0.0
	 *
	 * @param int $seconds seconds.
	 *
	 * @return string
	 */
	public function playtime_string( $seconds ) {
		$sign    = ( ( $seconds < 0 ) ? '-' : '' );
		$seconds = round( abs( $seconds ) );
		$H       = (int) floor( $seconds / 3600 );
		$M       = (int) floor( ( $seconds - ( 3600 * $H ) ) / 60 );
		$S       = (int) round( $seconds - ( 3600 * $H ) - ( 60 * $M ) );
		return $sign . ( $H ? $H . ':' : '' ) . ( $H ? str_pad( $M, 2, '0', STR_PAD_LEFT ) : intval( $M ) ) . ':' . str_pad( $S, 2, 0, STR_PAD_LEFT );
	}

	/**
	 * Get the playtime in array
	 *
	 * @since 1.0.0
	 *
	 * @param int $seconds seconds.
	 *
	 * @return array
	 */
	public function playtime_array( $seconds ) {
		$run_time_format = array(
			'hours'   => '00',
			'minutes' => '00',
			'seconds' => '00',
		);

		if ( $seconds <= 0 ) {
			return $run_time_format;
		}

		$playTimeString = $this->playtime_string( $seconds );
		$timeInArray    = explode( ':', $playTimeString );

		$run_time_size = count( $timeInArray );
		if ( $run_time_size === 3 ) {
			$run_time_format['hours']   = $timeInArray[0];
			$run_time_format['minutes'] = $timeInArray[1];
			$run_time_format['seconds'] = $timeInArray[2];
		} elseif ( $run_time_size === 2 ) {
			$run_time_format['minutes'] = $timeInArray[0];
			$run_time_format['seconds'] = $timeInArray[1];
		}

		return $run_time_format;
	}

	/**
	 * Convert seconds to human readable time
	 *
	 * @since 1.0.0
	 *
	 * @param int $seconds seconds.
	 *
	 * @return string
	 */
	public function seconds_to_time_context( $seconds ) {
		$sign    = ( ( $seconds < 0 ) ? '-' : '' );
		$seconds = round( abs( $seconds ) );
		$H       = (int) floor( $seconds / 3600 );
		$M       = (int) floor( ( $seconds - ( 3600 * $H ) ) / 60 );
		$S       = (int) round( $seconds - ( 3600 * $H ) - ( 60 * $M ) );

		return $sign . ( $H ? $H . 'h ' : '' ) . ( $H ? str_pad( $M, 2, '0', STR_PAD_LEFT ) : intval( $M ) ) . 'm ' . str_pad( $S, 2, 0, STR_PAD_LEFT ) . 's';
	}

	/**
	 * Get human readable time
	 *
	 * @since 2.0.7
	 *
	 * @param string $from                  date time string value. Example: 2022-06-24 22:00:00
	 * @param string $to                    (optional) date time string value. Default value is current.
	 * @param string $format                format you want to print. Default: '%ad %hh %im %ss' Help: https://www.php.net/manual/en/dateinterval.format.php
	 * @param bool   $show_postfix_text     show postfix text like 'ago', 'left'
	 *
	 * @return string
	 */
	public function get_human_readable_time( $from, $to = null, $format = null, $show_postfix_text = true ) {
		$postfix_text = '';
		$wp_tz        = new \DateTimeZone( wp_timezone_string() );
		$fromDateTime = new \DateTime( $from, $wp_tz );
		$toDateTime   = $to === null ? new \DateTime( 'now', $wp_tz ) : new \DateTime( $to, $wp_tz );
		$format       = $format === null ? '%ad %hh %im %ss' : $format;

		if ( $toDateTime > $fromDateTime ) {
			$postfix_text = __( ' ago', 'tutor' );
		} else {
			$postfix_text = __( ' left', 'tutor' );
		}

		$timeSpan     = $toDateTime->diff( $fromDateTime );
		$postfix_text = $show_postfix_text === true ? $postfix_text : '';

		return $timeSpan->format( $format ) . $postfix_text;
	}

	/**
	 * Get video info
	 *
	 * @since 1.0.0
	 *
	 * @param int $lesson_id lesson id.
	 *
	 * @return mixed bool return if video does not exits otherwise object return.
	 */
	public function get_video_info( $lesson_id = 0 ) {
		$lesson_id = $this->get_post_id( $lesson_id );
		$video     = $this->get_video( $lesson_id );

		if ( ! $video ) {
			return false;
		}

		$info = array(
			'playtime' => '00:00',
		);

		$types = apply_filters(
			'tutor_video_types',
			array(
				'mp4'  => 'video/mp4',
				'webm' => 'video/webm',
				'ogg'  => 'video/ogg',
			)
		);

		$videoSource = $this->avalue_dot( 'source', $video );

		if ( $videoSource === 'html5' ) {
			$sourceVideoID = $this->avalue_dot( 'source_video_id', $video );
			$video_info    = get_post_meta( $sourceVideoID, '_wp_attachment_metadata', true );

			if ( $video_info && in_array( $this->array_get( 'mime_type', $video_info ), $types ) ) {
				$path             = get_attached_file( $sourceVideoID );
				$info['playtime'] = $video_info['length_formatted'];
				$info['path']     = $path;
				$info['url']      = wp_get_attachment_url( $sourceVideoID );
				$info['ext']      = strtolower( pathinfo( $path, PATHINFO_EXTENSION ) );
				$info['type']     = $types[ $info['ext'] ];
			}
		}

		if ( $videoSource !== 'html5' ) {
			$video          = maybe_unserialize( get_post_meta( $lesson_id, '_video', true ) );
			$runtimeHours   = $this->avalue_dot( 'runtime.hours', $video );
			$runtimeMinutes = $this->avalue_dot( 'runtime.minutes', $video );
			$runtimeSeconds = $this->avalue_dot( 'runtime.seconds', $video );

			$runtimeHours   = $runtimeHours ? $runtimeHours : '00';
			$runtimeMinutes = $runtimeMinutes ? $runtimeMinutes : '00';
			$runtimeSeconds = $runtimeSeconds ? $runtimeSeconds : '00';

			$info['playtime'] = "$runtimeHours:$runtimeMinutes:$runtimeSeconds";
		}

		$info = array_merge( $info, $video );

		return (object) $info;
	}

	/**
	 * Get optimized duration.
	 *
	 * @since 1.0.0
	 *
	 * @param mixed $duration duration.
	 *
	 * @return mixed
	 */
	public function get_optimized_duration( $duration ) {
		return $this->course_content_time_format( $duration );
	}

	/**
	 * Ensure if attached video is self hosted or not.
	 *
	 * @since 1.0.0
	 *
	 * @param int $post_id post ID.
	 *
	 * @return bool
	 */
	public function is_html5_video( $post_id = 0 ) {
		$post_id = $this->get_post_id( $post_id );
		$video   = $this->get_video( $post_id );

		if ( ! $video ) {
			return false;
		}

		return 'html5' === $this->avalue_dot( 'source', $video );
	}

	/**
	 * Check lesson is completed.
	 *
	 * @since 1.0.0
	 *
	 * @param int $lesson_id lesson id.
	 * @param int $user_id user id.
	 *
	 * @return bool|mixed
	 */
	public function is_completed_lesson( $lesson_id = 0, $user_id = 0 ) {
		$lesson_id    = $this->get_post_id( $lesson_id );
		$user_id      = $this->get_user_id( $user_id );
		$is_completed = get_user_meta( $user_id, '_tutor_completed_lesson_id_' . $lesson_id, true );

		if ( $is_completed ) {
			return $is_completed;
		}

		return false;
	}

	/**
	 * Determine if a course completed
	 *
	 * @since 1.0.0
	 * @since 2.2.3 $enable_cache param added.
	 *
	 * @param int  $course_id course id.
	 * @param int  $user_id user id.
	 * @param bool $enable_cache enable or disable cache for particular function call.
	 *
	 * @return array|bool|null|object
	 */
	public function is_completed_course( $course_id = 0, $user_id = 0, $enable_cache = true ) {

		global $wpdb;
		$course_id = $this->get_post_id( $course_id );
		$user_id   = $this->get_user_id( $user_id );

		$cache_key    = "tutor_is_completed_course_{$course_id}_{$user_id}";
		$is_completed = TutorCache::get( $cache_key );

		if ( false === $is_completed || false === $enable_cache ) {
			$is_completed = $wpdb->get_row(
				$wpdb->prepare(
					"SELECT comment_ID,
						comment_post_ID AS course_id,
						comment_author AS completed_user_id,
						comment_date AS completion_date,
						comment_content AS completed_hash
				FROM	{$wpdb->comments}
				WHERE 	comment_agent = %s
						AND comment_type = %s
						AND comment_post_ID = %d
						AND user_id = %d;
				",
					'TutorLMSPlugin',
					'course_completed',
					$course_id,
					$user_id
				)
			);
			TutorCache::set( $cache_key, $is_completed );
		}

		if ( $is_completed ) {
			return apply_filters( 'is_completed_course', $is_completed, $course_id, $user_id );
		}

		return apply_filters( 'is_completed_course', false, $course_id, $user_id );
	}

	/**
	 * Sanitize input array
	 *
	 * @since 1.0.0
	 *
	 * @param array $input input.
	 *
	 * @return array
	 */
	public function sanitize_array( $input = array() ) {
		$array = array();

		if ( is_array( $input ) && count( $input ) ) {
			foreach ( $input as $key => $value ) {
				if ( is_array( $value ) ) {
					$array[ $key ] = $this->sanitize_array( $value );
				} else {
					$key           = sanitize_text_field( $key );
					$value         = sanitize_text_field( $value );
					$array[ $key ] = $value;
				}
			}
		}

		return $array;
	}

	/**
	 * Determine if has any video in single
	 *
	 * @since 1.0.0
	 *
	 * @param int $post_id post id.
	 *
	 * @return array|bool
	 */
	public function has_video_in_single( $post_id = 0 ) {
		if ( is_single() ) {
			$post_id = $this->get_post_id( $post_id );

			$video = $this->get_video( $post_id );
			if ( $video && $this->array_get( 'source', $video ) !== '-1' ) {

				$not_empty = ! empty( $video['source_video_id'] ) ||
					! empty( $video['source_external_url'] ) ||
					! empty( $video['source_youtube'] ) ||
					! empty( $video['source_vimeo'] ) ||
					! empty( $video['source_embedded'] ) ||
					! empty( $video['source_shortcode'] ) ||
					( isset( $video['source_bunnynet'] ) && ! empty( $video['source_bunnynet'] ) );

				return $not_empty ? $video : false;
			}
		}
		return false;
	}

	/**
	 * Get the enrolled students for all courses.
	 * Pass course id in 4th parameter to get students course wise.
	 *
	 * @since v.1.0.0
	 *
	 * @param int    $start start.
	 * @param int    $limit limit.
	 * @param string $search_term search term.
	 * @param int    $course_id course id.
	 * @param string $date data.
	 * @param string $order order.
	 *
	 * @return array|null|object
	 */
	public function get_students( $start = 0, $limit = 10, $search_term = '', $course_id = '', $date = '', $order = 'DESC' ) {
		global $wpdb;

		$start       = sanitize_text_field( $start );
		$limit       = sanitize_text_field( $limit );
		$search_term = sanitize_text_field( $search_term );
		$course_id   = sanitize_text_field( $course_id );
		$date        = sanitize_text_field( $date );

		$course_query = '';
		if ( '' !== $course_id ) {
			$course_id    = (int) $course_id;
			$course_query = "AND posts.post_parent = {$course_id}";
		}

		$date_query = '';
		if ( '' !== $date ) {
			$date_query = "AND DATE(user.user_registered) = CAST('$date' AS DATE)";
		}

		$order_query = '';
		if ( '' !== $order ) {
			$is_valid_sql = sanitize_sql_orderby( $order );
			if ( $is_valid_sql ) {
				$order_query = "ORDER BY posts.post_date {$order}";
			}
		}
		$search_term_raw = $search_term;
		$search_term     = '%' . $wpdb->esc_like( $search_term ) . '%';

		$students = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT user.* FROM {$wpdb->posts} AS posts
				INNER JOIN {$wpdb->users} AS user
				 	ON user.ID = posts.post_author
				WHERE posts.post_type = %s
					AND posts.post_status = %s
					{$course_query}
					{$date_query}
					AND (user.display_name LIKE %s OR user.user_email = %s OR user.user_login LIKE %s)
				GROUP BY post_author
				{$order_query}
				LIMIT %d, %d
			",
				'tutor_enrolled',
				'completed',
				$search_term,
				$search_term_raw,
				$search_term,
				$start,
				$limit
			)
		);

		return $students;
	}

	/**
	 * Get the total students
	 * pass course id to get course wise total students
	 *
	 * @since 1.0.0
	 *
	 * @param string $search_term search term.
	 * @param string $course_id course id.
	 * @param string $date date.
	 *
	 * @return int
	 */
	public function get_total_students( $search_term = '', $course_id = '', $date = '' ): int {
		global $wpdb;

		$search_term = sanitize_text_field( $search_term );
		$course_id   = sanitize_text_field( $course_id );
		$date        = sanitize_text_field( $date );

		$course_query = '';
		if ( '' !== $course_id ) {
			$course_id    = (int) $course_id;
			$course_query = "AND posts.post_parent = {$course_id}";
		}

		$date_query = '';
		if ( '' !== $date ) {
			$date_query = "AND DATE(user.user_registered) = CAST('$date' AS DATE)";
		}
		$search_term_raw = $search_term;
		$search_term     = '%' . $wpdb->esc_like( $search_term ) . '%';

		$students = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT user.ID FROM {$wpdb->posts} AS posts
				INNER JOIN {$wpdb->users} AS user
				 	ON user.ID = posts.post_author
				WHERE posts.post_type = %s
					AND posts.post_status = %s
					{$course_query}
					{$date_query}
					AND (user.display_name LIKE %s OR user.user_email = %s OR user.user_login LIKE %s)
				GROUP BY user.ID
			",
				'tutor_enrolled',
				'completed',
				$search_term,
				$search_term_raw,
				$search_term
			)
		);

		return is_array( $students ) ? count( $students ) : 0;
	}

	/**
	 * Get complete courses ids by user
	 *
	 * @since 1.0.0
	 *
	 * @param int $user_id user id.
	 *
	 * @return array
	 */
	public function get_completed_courses_ids_by_user( $user_id = 0 ) {
		global $wpdb;

		$user_id = $this->get_user_id( $user_id );

		$course_ids = (array) $wpdb->get_col(
			$wpdb->prepare(
				"SELECT comment_post_ID AS course_id
			FROM 	{$wpdb->comments}
			WHERE 	comment_agent = %s
					AND comment_type = %s
					AND user_id = %d
					AND comment_post_ID IN (
						select post_parent AS course_id from {$wpdb->posts} where post_type=%s AND post_author = %d
					)
			",
				'TutorLMSPlugin',
				'course_completed',
				$user_id,
				'tutor_enrolled',
				$user_id
			)
		);

		return $course_ids;
	}

	/**
	 * Return completed courses by user_id
	 *
	 * @since 1.0.0
	 *
	 * @param int $user_id user id.
	 * @param int $offset offset.
	 * @param int $posts_per_page posts per page.
	 *
	 * @return bool|\WP_Query
	 */
	public function get_courses_by_user( $user_id = 0, $offset = 0, $posts_per_page = -1 ) {
		$user_id    = $this->get_user_id( $user_id );
		$course_ids = $this->get_completed_courses_ids_by_user( $user_id );

		if ( count( $course_ids ) ) {
			$course_post_type = tutor()->course_post_type;
			$course_args      = array(
				'post_type'      => $course_post_type,
				'post_status'    => 'publish',
				'post__in'       => $course_ids,
				'posts_per_page' => $posts_per_page,
				'offset'         => $offset,
			);

			return new \WP_Query( $course_args );
		}

		return false;
	}

	/**
	 * Get the active course by user
	 *
	 * @since 1.0.0
	 *
	 * @param int $user_id user id.
	 * @param int $offset offset.
	 * @param int $posts_per_page posts per page.
	 *
	 * @return bool|\WP_Query
	 */
	public function get_active_courses_by_user( $user_id = 0, $offset = 0, $posts_per_page = -1 ) {
		$user_id             = $this->get_user_id( $user_id );
		$course_ids          = $this->get_completed_courses_ids_by_user( $user_id );
		$enrolled_course_ids = $this->get_enrolled_courses_ids_by_user( $user_id );
		$active_courses      = array_diff( $enrolled_course_ids, $course_ids );

		if ( count( $active_courses ) ) {
			$course_post_type = tutor()->course_post_type;
			$course_args      = array(
				'post_type'      => $course_post_type,
				'post_status'    => 'publish',
				'post__in'       => $active_courses,
				'posts_per_page' => $posts_per_page,
				'offset'         => $offset,
			);

			return new \WP_Query( $course_args );
		}

		return false;
	}

	/**
	 * Get enrolled course ids by a user
	 *
	 * @since 1.0.0
	 *
	 * @param int $user_id user id.
	 *
	 * @return array
	 */
	public function get_enrolled_courses_ids_by_user( $user_id = 0 ) {
		global $wpdb;
		$user_id    = $this->get_user_id( $user_id );
		$course_ids = $wpdb->get_col(
			$wpdb->prepare(
				"SELECT DISTINCT post_parent
			FROM 	{$wpdb->posts}
			WHERE 	post_type = %s
					AND post_status = %s
					AND post_author = %d
				ORDER BY post_date DESC;
			",
				'tutor_enrolled',
				'completed',
				$user_id
			)
		);

		return $course_ids;
	}

	/**
	 * Get single or list of enrolled course data by a user
	 *
	 * @since 2.0.5
	 *
	 * @param integer $user_id user id.
	 * @param integer $course_id cousrs id.
	 *
	 * @return object|mixed
	 */
	public function get_enrolled_data( $user_id = 0, $course_id = 0 ) {
		global $wpdb;
		// If course ID provided, it will return single row data.
		if ( 0 != $course_id ) {
			return $wpdb->get_row(
				$wpdb->prepare(
					"SELECT * FROM 	{$wpdb->posts} 
						WHERE post_type = %s
						AND post_parent = %d
						AND post_status = %s
						AND post_author = %d;",
					'tutor_enrolled',
					$course_id,
					'completed',
					$user_id
				)
			);
		} else {
			// Return all enrolled data by user ID.
			return $wpdb->get_results(
				$wpdb->prepare(
					"SELECT * FROM 	{$wpdb->posts} 
						WHERE post_type = %s
						AND post_status = %s
						AND post_author = %d;",
					'tutor_enrolled',
					'completed',
					$user_id
				)
			);
		}
	}

	/**
	 * Get total enrolled students by course id.
	 *
	 * @since 1.0.0
	 * @since 1.9.9 $period param added.
	 *
	 * @param int    $course_id course id.
	 * @param string $period period ( optional ).
	 *
	 * @return int
	 */
	public function count_enrolled_users_by_course( $course_id = 0, $period = '' ) {

		$course_id = $this->get_post_id( $course_id );
		// Set period wise query.
		$period_filter = '';
		if ( 'today' === $period ) {
			$period_filter = 'AND DATE(post_date) = CURDATE()';
		}
		if ( 'monthly' === $period ) {
			$period_filter = 'AND MONTH(post_date) = MONTH(CURDATE()) ';
		}
		if ( 'yearly' === $period ) {
			$period_filter = 'AND YEAR(post_date) = YEAR(CURDATE()) ';
		}

		$cache_key  = "tutor_enroll_count_for_course_{$course_id}_{$period}";
		$course_ids = TutorCache::get( $cache_key );

		if ( false === $course_ids ) {
			global $wpdb;
			$course_ids = $wpdb->get_var(
				$wpdb->prepare(
					"SELECT COUNT(ID)
				FROM	{$wpdb->posts}
				WHERE 	post_type = %s
						AND post_status = %s
						AND post_parent = %d;
						{$period_filter}
				",
					'tutor_enrolled',
					'completed',
					$course_id
				)
			);

			TutorCache::set( $cache_key, (int) $course_ids );
		}

		return (int) $course_ids;
	}

	/**
	 * Get the enrolled courses by user
	 *
	 * @since 1.0.0
	 * @since 2.5.0 $filters param added to query enrolled courses with additional filters.
	 *
	 * @param integer $user_id user id.
	 * @param string  $post_status post status.
	 * @param integer $offset offset.
	 * @param integer $posts_per_page post per page.
	 * @param array   $filters additional filters with key value for \WP_Query.
	 *
	 * @return bool|\WP_Query
	 */
	public function get_enrolled_courses_by_user( $user_id = 0, $post_status = 'publish', $offset = 0, $posts_per_page = -1, $filters = array() ) {
		global $wpdb;

		$user_id    = $this->get_user_id( $user_id );
		$course_ids = array_unique( $this->get_enrolled_courses_ids_by_user( $user_id ) );

		if ( count( $course_ids ) ) {
			$course_post_type = tutor()->course_post_type;
			$course_args      = array(
				'post_type'      => $course_post_type,
				'post_status'    => $post_status,
				'post__in'       => $course_ids,
				'offset'         => $offset,
				'posts_per_page' => $posts_per_page,
			);

			if ( count( $filters ) ) {
				$keys = array_keys( $course_args );
				foreach ( $filters as $key => $value ) {
					if ( ! in_array( $key, $keys ) ) {
						$course_args[ $key ] = $value;
					}
				}
			}

			$result = new \WP_Query( $course_args );

			if ( is_object( $result ) && is_array( $result->posts ) ) {

				// Sort courses according to the id list.
				$new_array = array();

				foreach ( $course_ids as $id ) {
					foreach ( $result->posts as $post ) {
						$post->ID == $id ? $new_array[] = $post : 0;
					}
				}

				$result->posts = $new_array;
			}

			return $result;
		}

		return false;
	}

	/**
	 * Get the video streaming URL by post/lesson/course ID
	 *
	 * @since 1.0.0
	 *
	 * @param int $post_id post id.
	 *
	 * @return string
	 */
	public function get_video_stream_url( $post_id = 0 ) {
		$post_id = $this->get_post_id( $post_id );
		$post    = get_post( $post_id );

		if ( tutor()->lesson_post_type === $post->post_type ) {
			$video_url = trailingslashit( home_url() ) . 'video-url/' . $post->post_name;
		} else {
			$video_info = $this->get_video_info( $post_id );
			$video_url  = $video_info->url;
		}

		return $video_url;
	}

	/**
	 * Get current post id or given post id
	 *
	 * @since 1.0.0
	 *
	 * @param int $post_id post id.
	 *
	 * @return bool|false|int
	 */
	public function get_post_id( $post_id = 0 ) {
		if ( ! $post_id ) {
			$post_id = get_the_ID();
			if ( ! $post_id ) {
				return false;
			}
		}

		return $post_id;
	}

	/**
	 * Get current user ID or given user ID
	 *
	 * @since 1.0.0
	 *
	 * @param mixed $user_id user ID.
	 *
	 * @return int  when $user_id = 0, return 0 or current user ID
	 *              otherwise return given ID
	 */
	public function get_user_id( $user_id = 0 ) {
		if ( ! $user_id ) {
			return get_current_user_id();
		}

		return $user_id;
	}

	/**
	 * Get user name for e-mail salutation.
	 *
	 * @since 2.0.9
	 *
	 * @param mixed $user user object.
	 *
	 * @return string
	 */
	public function get_user_name( $user ) {
		if ( ! is_a( $user, 'WP_User' ) ) {
			return '';
		}
		$name = '';

		if ( empty( trim( $user->first_name ) ) ) {
			$name = $user->user_login;
		} else {
			$name = $user->first_name;
			if ( ! empty( trim( $user->last_name ) ) ) {
				$name .= " {$user->last_name}";
			}
		}

		return $name;
	}

	/**
	 * Get the Youtube Video ID from URL
	 *
	 * @since 1.0.0
	 *
	 * @param string $url URL.
	 *
	 * @return bool
	 */
	public function get_youtube_video_id( $url = '' ) {
		if ( ! $url ) {
			return false;
		}

		preg_match( '%(?:youtube(?:-nocookie)?\.com/(?:[^/]+/.+/|(?:v|e(?:mbed)?)/|.*[?&]v=)|youtu\.be/)([^"&?/ ]{11})%i', $url, $match );

		if ( isset( $match[1] ) ) {
			$youtube_id = $match[1];
			return $youtube_id;
		}

		return false;
	}

	/**
	 * Saving enroll information to posts table
	 * post_author = enrolled_student_id (wp_users id)
	 * post_parent = enrolled course id
	 *
	 * @since 1.0.0
	 * @since 2.6.0 Return enrolled id
	 *
	 * @param int $course_id course id.
	 * @param int $order_id order id.
	 * @param int $user_id user id.
	 *
	 * @return int enrolled id
	 */
	public function do_enroll( $course_id = 0, $order_id = 0, $user_id = 0 ) {
		$enrolled_id = 0;
		if ( ! $course_id ) {
			return $enrolled_id;
		}

		do_action( 'tutor_before_enroll', $course_id );
		$user_id = $this->get_user_id( $user_id );
		$title   = __( 'Course Enrolled', 'tutor' ) . ' &ndash; ' . gmdate( get_option( 'date_format' ) ) . ' @ ' . gmdate( get_option( 'time_format' ) );

		if ( $course_id && $user_id ) {
			$enrolled_info = $this->is_enrolled( $course_id, $user_id );
			if ( $enrolled_info ) {
				return $enrolled_info->ID;
			}
		}

		$enrolment_status = 'completed';

		if ( $this->is_course_purchasable( $course_id ) ) {
			$enrolment_status = 'pending';
		}

		$enroll_data = apply_filters(
			'tutor_enroll_data',
			array(
				'post_type'     => 'tutor_enrolled',
				'post_title'    => $title,
				'post_status'   => $enrolment_status,
				'post_author'   => $user_id,
				'post_parent'   => $course_id,
				'post_date_gmt' => current_time( 'mysql', true ),
			)
		);

		// Insert the post into the database.
		$is_enrolled = wp_insert_post( $enroll_data );
		if ( $is_enrolled ) {

			// Run this hook for both of pending and completed enrollment.
			do_action( 'tutor_after_enroll', $course_id, $is_enrolled );

			// Mark Current User as Students with user meta data.
			update_user_meta( $user_id, '_is_tutor_student', tutor_time() );

			if ( $order_id ) {
				// Mark order for course and user.
				$product_id = $this->get_course_product_id( $course_id );
				update_post_meta( $is_enrolled, '_tutor_enrolled_by_order_id', $order_id );
				update_post_meta( $is_enrolled, '_tutor_enrolled_by_product_id', $product_id );

				$monetize_by = $this->get_option( 'monetize_by' );
				if ( 'wc' === $monetize_by ) {
					$order = wc_get_order( $order_id );
					$order->update_meta_data( '_is_tutor_order_for_course', tutor_time() );
					$order->update_meta_data( '_tutor_order_for_course_id_' . $course_id, $is_enrolled );
					$order->save();
				} else {
					update_post_meta( $order_id, '_is_tutor_order_for_course', tutor_time() );
					update_post_meta( $order_id, '_tutor_order_for_course_id_' . $course_id, $is_enrolled );
				}
			}

			$enrolled_id = $is_enrolled;

			// Run this hook for completed enrollment regardless of payment provider and free/paid mode.
			if ( 'completed' === $enroll_data['post_status'] ) {
				do_action( 'tutor_after_enrolled', $course_id, $user_id, $enrolled_id );
			}
		}

		return $enrolled_id;
	}

	/**
	 * Enrol Status change
	 *
	 * @since 1.6.1
	 *
	 * @param bool   $enrol_id enrol id.
	 * @param string $new_status new status.
	 *
	 * @return mixed
	 */
	public function course_enrol_status_change( $enrol_id = false, $new_status = '' ) {
		if ( ! $enrol_id ) {
			return;
		}

		global $wpdb;

		do_action( 'tutor/course/enrol_status_change/before', $enrol_id, $new_status );
		$wpdb->update( $wpdb->posts, array( 'post_status' => $new_status ), array( 'ID' => $enrol_id ) );
		do_action( 'tutor/course/enrol_status_change/after', $enrol_id, $new_status );
	}

	/**
	 * Cancel course enrol
	 *
	 * @since 1.0.0
	 *
	 * @param int    $course_id course id.
	 * @param int    $user_id user id.
	 * @param string $cancel_status cancel status.
	 *
	 * @return void
	 */
	public function cancel_course_enrol( $course_id = 0, $user_id = 0, $cancel_status = 'canceled' ) {
		$course_id = $this->get_post_id( $course_id );
		$user_id   = $this->get_user_id( $user_id );
		$enrolled  = $this->is_enrolled( $course_id, $user_id );

		if ( $enrolled ) {
			global $wpdb;

			if ( 'delete' === $cancel_status ) {
				$wpdb->delete(
					$wpdb->posts,
					array(
						'post_type'   => 'tutor_enrolled',
						'post_author' => $user_id,
						'post_parent' => $course_id,
					)
				);

				// Delete Related Meta Data.
				delete_post_meta( $enrolled->ID, '_tutor_enrolled_by_product_id' );
				$order_id = get_post_meta( $enrolled->ID, '_tutor_enrolled_by_order_id', true );
				if ( $order_id ) {
					delete_post_meta( $enrolled->ID, '_tutor_enrolled_by_order_id' );

					$monetize_by = $this->get_option( 'monetize_by' );
					if ( 'wc' === $monetize_by ) {
						// Delete WC order meta.
						$order = wc_get_order( $order_id );
						$order->delete_meta_data( '_is_tutor_order_for_course' );
						$order->delete_meta_data( '_tutor_order_for_course_id_' . $course_id );
						$order->save();
					} else {
						delete_post_meta( $order_id, '_is_tutor_order_for_course' );
						delete_post_meta( $order_id, '_tutor_order_for_course_id_' . $course_id );
					}
				}

				/**
				 * Added for third-party
				 *
				 * @since 2.2.3
				 */
				do_action( 'tutor_after_enrollment_deleted', $course_id, $user_id );

			} else {
				$wpdb->update(
					$wpdb->posts,
					array( 'post_status' => $cancel_status ),
					array(
						'post_type'   => 'tutor_enrolled',
						'post_author' => $user_id,
						'post_parent' => $course_id,
					)
				);

				/**
				 * Added for third-party
				 *
				 * @since 2.2.3
				 */
				do_action( 'tutor_after_enrollment_cancelled', $course_id, $user_id );

				if ( 'cancel' === $cancel_status ) {
					die( esc_html( $cancel_status ) );
				}
			}
		}
	}

	/**
	 * Complete course enrollment and do some task
	 *
	 * @since 1.0.0
	 *
	 * @param int $order_id order id.
	 *
	 * @return mixed
	 */
	public function complete_course_enroll( $order_id ) {
		if ( ! $this->is_tutor_order( $order_id ) ) {
			return;
		}

		global $wpdb;

		$enrolled_ids_with_course = $this->get_course_enrolled_ids_by_order_id( $order_id );
		if ( $enrolled_ids_with_course ) {
			$enrolled_ids = wp_list_pluck( $enrolled_ids_with_course, 'enrolled_id' );

			if ( is_array( $enrolled_ids ) && count( $enrolled_ids ) ) {
				foreach ( $enrolled_ids as $enrolled_id ) {
					$wpdb->update( $wpdb->posts, array( 'post_status' => 'completed' ), array( 'ID' => $enrolled_id ) );
				}
			}
		}
	}

	/**
	 * Get enrol ids by order id.
	 *
	 * @since 1.0.0
	 *
	 * @param int $order_id order id.
	 *
	 * @return array|bool
	 */
	public function get_course_enrolled_ids_by_order_id( $order_id ) {
		global $wpdb;

		if ( 'wc' === $this->get_option( 'monetize_by' ) && WooCommerce::hpos_enabled() ) {
			// phpcs:disable WordPress.DB.PreparedSQLPlaceholders.LikeWildcardsInQuery
			$courses_ids = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT *
					FROM 	{$wpdb->prefix}wc_orders_meta
					WHERE	order_id = %d
							AND meta_key LIKE '_tutor_order_for_course_id_%'",
					$order_id
				)
			);
		} else {
			$courses_ids = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT *
					FROM 	{$wpdb->postmeta}
					WHERE	post_id = %d
							AND meta_key LIKE '_tutor_order_for_course_id_%'
				",
					$order_id
				)
			);
		}
		// phpcs:enable WordPress.DB.PreparedSQLPlaceholders.LikeWildcardsInQuery

		if ( is_array( $courses_ids ) && count( $courses_ids ) ) {
			$course_enrolled_by_order = array();
			foreach ( $courses_ids as $courses_id ) {
				$course_id                  = str_replace( '_tutor_order_for_course_id_', '', $courses_id->meta_key );
				$course_enrolled_by_order[] = array(
					'course_id'   => $course_id,
					'enrolled_id' => $courses_id->meta_value,
					'order_id'    => $courses_id->post_id ?? $courses_id->order_id,
				);
			}
			return $course_enrolled_by_order;
		}
		return false;
	}

	/**
	 * Get wc product in efficient query
	 *
	 * @since 1.0.0
	 * @since 3.0.0 $exclude param added.
	 *
	 * @param array $exclude exclude ids.
	 *
	 * @return array|null|object
	 */
	public function get_wc_products_db( $exclude = array() ) {
		global $wpdb;

		$exclude = array_filter( $exclude, 'is_numeric' );

		$where_clause = 'post_status = %s';
		if ( count( $exclude ) ) {
			$ids           = QueryHelper::prepare_in_clause( $exclude );
			$where_clause .= " AND ID NOT IN ({$ids})";
		}

		$where_clause .= ' AND post_type = %s';

		$query = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT ID,
					post_title
			FROM 	{$wpdb->posts}
			WHERE 	{$where_clause}", //phpcs:ignore
				'publish',
				'product'
			)
		);

		return $query;
	}

	/**
	 * Get EDD Products
	 *
	 * @since 1.0.0
	 *
	 * @return array|null|object
	 */
	public function get_edd_products() {
		global $wpdb;
		$query = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT ID,
					post_title
			FROM 	{$wpdb->posts}
			WHERE 	post_status = %s
					AND post_type = %s;
			",
				'publish',
				'download'
			)
		);

		return $query;
	}

	/**
	 * Get course productID
	 *
	 * @since 1.0.0
	 *
	 * @param int $course_id course id.
	 *
	 * @return int
	 */
	public function get_course_product_id( $course_id = 0 ) {
		$course_id  = $this->get_post_id( $course_id );
		$product_id = (int) get_post_meta( $course_id, Course::COURSE_PRODUCT_ID_META, true );

		return $product_id;
	}

	/**
	 * Get all WC product ids which are linked with course.
	 *
	 * @since 3.0.0
	 *
	 * @return array
	 */
	public function get_linked_product_ids() {
		global $wpdb;
		$ids = $wpdb->get_col(
			$wpdb->prepare(
				"SELECT meta_value 
				FROM 	{$wpdb->postmeta} 
				WHERE	meta_key = %s",
				Course::COURSE_PRODUCT_ID_META
			)
		);

		return array_filter( $ids, 'is_numeric' );
	}

	/**
	 * Get Product belongs with course
	 *
	 * @since 1.0.0
	 *
	 * @param int $product_id product id.
	 *
	 * @return array|null|object|void
	 */
	public function product_belongs_with_course( $product_id = 0 ) {
		global $wpdb;

		$query = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT *
			FROM 	{$wpdb->postmeta}
			WHERE	meta_key = %s
					AND meta_value = %d
			limit 1
			",
				Course::COURSE_PRODUCT_ID_META,
				$product_id
			)
		);

		return $query;
	}

	/**
	 * Get enroll status
	 *
	 * @since 1.0.0
	 *
	 * @return array
	 */
	public function get_enrolled_statuses() {
		return apply_filters(
			'tutor_get_enrolled_statuses',
			array(
				'pending',
				'processing',
				'on-hold',
				'completed',
				'cancelled',
				'refunded',
				'failed',
			)
		);
	}

	/**
	 * Determine is this a tutor order
	 *
	 * @since 1.0.0
	 *
	 * @param int $order_id order id.
	 *
	 * @return mixed
	 */
	public function is_tutor_order( $order_id ) {
		$monetize_by = $this->get_option( 'monetize_by' );
		if ( 'wc' === $monetize_by ) {
			$order = wc_get_order( $order_id );
			return $order->get_meta( '_is_tutor_order_for_course', true );
		} else {
			return get_post_meta( $order_id, '_is_tutor_order_for_course', true );
		}
	}

	/**
	 * Tutor Dashboard Pages, supporting for the URL rewriting
	 *
	 * @since 1.0.0
	 *
	 * @return mixed
	 */
	public function tutor_dashboard_pages() {
		$nav_items = apply_filters( 'tutor_dashboard/nav_items', $this->default_menus() );

		$instructor_nav_items = apply_filters( 'tutor_dashboard/instructor_nav_items', $this->instructor_menus() );

		$nav_items = array_merge( $nav_items, $instructor_nav_items );

		$new_navs      = apply_filters(
			'tutor_dashboard/bottom_nav_items',
			array(
				'separator-2' => array(
					'title' => '',
					'type'  => 'separator',
				),
				'settings'    => array(
					'title' => __( 'Settings', 'tutor' ),
					'icon'  => 'tutor-icon-gear',
				),
				'logout'      => array(
					'title' => __( 'Logout', 'tutor' ),
					'icon'  => 'tutor-icon-signout',
				),
			)
		);
		$all_nav_items = array_merge( $nav_items, $new_navs );

		return apply_filters( 'tutor_dashboard/nav_items_all', $all_nav_items );
	}

	/**
	 * Get tutor dashboard permalinks
	 *
	 * @since 1.0.0
	 *
	 * @return array
	 */
	public function tutor_dashboard_permalinks() {
		$dashboard_pages = $this->tutor_dashboard_pages();

		$dashboard_permalinks = apply_filters(
			'tutor_dashboard/permalinks',
			array(
				'retrieve-password' => array(
					'title'         => __( 'Retrieve Password', 'tutor' ),
					'login_require' => false,
				),
			)
		);

		$dashboard_pages = array_merge( $dashboard_pages, $dashboard_permalinks );

		return $dashboard_pages;
	}

	/**
	 * Tutor Dashboard UI nav, only for using in the nav, it's handling user permission based
	 * Dashboard nav items
	 *
	 * @since 1.3.4
	 *
	 * @return mixed
	 */
	public function tutor_dashboard_nav_ui_items() {
		$nav_items = $this->tutor_dashboard_pages();

		foreach ( $nav_items as $key => $nav_item ) {
			if ( is_array( $nav_item ) ) {

				if ( isset( $nav_item['show_ui'] ) && ! $this->array_get( 'show_ui', $nav_item ) ) {
					unset( $nav_items[ $key ] );
				}
				if ( isset( $nav_item['auth_cap'] ) && ! current_user_can( $nav_item['auth_cap'] ) ) {
					unset( $nav_items[ $key ] );
				}
			}
		}

		return apply_filters( 'tutor_dashboard/nav_ui_items', $nav_items );
	}

	/**
	 * Get tutor dashboard page single URL
	 *
	 * @since 1.0.0
	 *
	 * @param string $page_key page key.
	 * @param int    $page_id page id.
	 *
	 * @return string
	 */
	public function get_tutor_dashboard_page_permalink( $page_key = '', $page_id = 0 ) {
		if ( 'index' === $page_key ) {
			$page_key = '';
		}
		if ( ! $page_id ) {
			$page_id = (int) $this->get_option( 'tutor_dashboard_page_id' );
		}
		return trailingslashit( get_permalink( $page_id ) ) . $page_key;
	}

	/**
	 * Get old input
	 *
	 * @since 1.0.0
	 * @since 1.4.2 updated.
	 *
	 * @param string $input input.
	 * @param mixed  $old_data old data.
	 *
	 * @return array|bool|mixed|string
	 */
	public function input_old( $input = '', $old_data = null ) {
		if ( ! $old_data ) {
			$old_data = tutor_sanitize_data( $_REQUEST );
		}
		$value = $this->avalue_dot( $input, $old_data );
		if ( $value ) {
			return $value;
		}

		return '';
	}

	/**
	 * Determine if is instructor or not
	 *
	 * @since 1.0.0
	 *
	 * @param int  $user_id user id.
	 * @param bool $is_approved is approved.
	 *
	 * @return mixed
	 */
	public function is_instructor( $user_id = 0, $is_approved = false ) {
		$user_id = $this->get_user_id( $user_id );
		if ( $is_approved ) {
			$user_status            = get_user_meta( $user_id, '_tutor_instructor_status', true );
			$is_approved_instructor = 'approved' === $user_status ? true : false;
			return $is_approved_instructor && get_user_meta( $user_id, '_is_tutor_instructor', true );
		}
		return get_user_meta( $user_id, '_is_tutor_instructor', true );
	}

	/**
	 * Instructor status
	 *
	 * @since 1.0.0
	 *
	 * @param int  $user_id user id.
	 * @param bool $status_name status name.
	 *
	 * @return bool|mixed
	 */
	public function instructor_status( $user_id = 0, $status_name = true ) {
		$user_id = $this->get_user_id( $user_id );

		$instructor_status = apply_filters(
			'tutor_instructor_statuses',
			array(
				'pending'  => __( 'Pending', 'tutor' ),
				'approved' => __( 'Approved', 'tutor' ),
				'blocked'  => __( 'Blocked', 'tutor' ),
			)
		);

		$status = get_user_meta( $user_id, '_tutor_instructor_status', true );

		if ( isset( $instructor_status[ $status ] ) ) {
			if ( ! $status_name ) {
				return $status;
			}
			return $instructor_status[ $status ];
		}
		return false;
	}

	/**
	 * Get Total number of instructor
	 *
	 * @since 1.0.0
	 *
	 * @param string $search_filter serach filter.
	 * @param string $status (approved | pending | blocked).
	 * @param string $course_id course id.
	 * @param string $date user_registered date.
	 *
	 * @return int
	 */
	public function get_total_instructors( $search_filter = '', $status = array(), $course_id = '', $date = '' ): int {
		global $wpdb;
		$search_filter = sanitize_text_field( $search_filter );
		$course_id     = sanitize_text_field( $course_id );
		$date          = sanitize_text_field( $date );

		$search_term_raw = $search_filter;
		$search_filter   = '%' . $wpdb->esc_like( $search_filter ) . '%';

		$status_query = '';
		if ( is_array( $status ) && count( $status ) ) {
			$status = array_map(
				function ( $str ) {
					return "'{$str}'";
				},
				$status
			);

			$status_query = ' AND inst_status.meta_value IN (' . implode( ',', $status ) . ')';
		}

		$course_query = '';
		if ( '' !== $course_id ) {
			$course_query = "AND umeta.meta_value = $course_id ";
		}

		$date_query = '';
		if ( '' !== $date ) {
			$date       = tutor_get_formated_date( 'Y-m-d', $date );
			$date_query = "AND  DATE(user.user_registered) = CAST('$date' AS DATE)";
		}

		$count = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT COUNT(DISTINCT user.ID )
				FROM 	{$wpdb->users} user
						INNER JOIN {$wpdb->usermeta} user_meta
								ON ( user.ID = user_meta.user_id )
						INNER JOIN {$wpdb->usermeta} inst_status
								ON ( user.ID = inst_status.user_id )
						LEFT JOIN {$wpdb->usermeta} AS umeta
								ON umeta.user_id = user.ID AND umeta.meta_key = '_tutor_instructor_course_id'

				WHERE 	user_meta.meta_key = %s
						AND ( user.display_name LIKE %s OR user.user_email = %s )
						{$status_query}
						{$course_query}
						{$date_query}
			",
				'_is_tutor_instructor',
				$search_filter,
				$search_term_raw
			)
		);
		return $count ? $count : 0;
	}

	/**
	 * Get instructor with optional filters.
	 * Available instructor status ( approved | blocked | pending )
	 *
	 * @since 1.0.0
	 *
	 * @param int    $start start.
	 * @param int    $limit limit.
	 * @param string $search_filter search term.
	 * @param string $course_filter course filter.
	 * @param string $date_filter date filter.
	 * @param string $order_filter order filter.
	 * @param mixed  $status status.
	 * @param array  $cat_ids cat ids.
	 * @param mixed  $rating rating.
	 * @param bool   $count_only count only or not.
	 *
	 * @return array|null|object
	 */
	public function get_instructors( $start = 0, $limit = 10, $search_filter = '', $course_filter = '', $date_filter = '', $order_filter = '', $status = null, $cat_ids = array(), $rating = '', $count_only = false ) {
		global $wpdb;

		$search_filter = sanitize_text_field( $search_filter );
		$course_filter = sanitize_text_field( $course_filter );
		$date_filter   = sanitize_text_field( $date_filter );
		$order_filter  = sanitize_sql_orderby( $order_filter );
		$rating        = sanitize_text_field( $rating );

		$search_term_raw = $search_filter;
		$search_filter   = '%' . $wpdb->esc_like( $search_filter ) . '%';
		$course_filter   = $course_filter != '' ? " AND umeta.meta_value = $course_filter " : '';

		if ( '' != $date_filter ) {
			$date_filter = tutor_get_formated_date( 'Y-m-d', $date_filter );
		}

		$date_filter = $date_filter != '' ? " AND  DATE(user.user_registered) = CAST('$date_filter' AS DATE) " : '';

		$category_join  = '';
		$category_where = '';

		if ( $status ) {
			! is_array( $status ) ? $status = array( $status ) : 0;

			$status = array_map(
				function ( $str ) {
					return "'{$str}'";
				},
				$status
			);

			$status = ' AND inst_status.meta_value IN (' . implode( ',', $status ) . ')';
		}

		$cat_ids = array_filter(
			$cat_ids,
			function ( $id ) {
				return is_numeric( $id );
			}
		);

		if ( count( $cat_ids ) ) {

			$category_join =
				"INNER JOIN {$wpdb->posts} course
					ON course.post_author = user.ID
			INNER JOIN {$wpdb->prefix}term_relationships term_rel
					ON term_rel.object_id = course.ID
			INNER JOIN {$wpdb->prefix}term_taxonomy taxonomy
					ON taxonomy.term_taxonomy_id=term_rel.term_taxonomy_id
			INNER JOIN {$wpdb->prefix}terms term
					ON term.term_id=taxonomy.term_id";

			$cat_ids        = implode( ',', $cat_ids );
			$category_where = " AND term.term_id IN ({$cat_ids})";
		}

		// Rating wise sorting @since 2.0.0.
		$res_rat = array( 1, 2, 3, 4, 5 );
		$rating  = isset( $_POST['rating_filter'] ) && in_array( $rating, $res_rat ) ? $rating : '';

		$rating_having = '';
		if ( '' !== $rating ) {
			$max_rating = (int) $rating + 1;
			if ( 5 === (int) $rating ) {
				$max_rating = 5;
			}
			$rating_having = $wpdb->prepare( " HAVING rating >= %d AND rating <= %d ", $rating, $max_rating );
		}

		/**
		 * Handle Sort by Relevant | New | Popular & Order Shorting
		 * from instructor list backend
		 *
		 * @since 2.0.0
		 */
		$order_query = '';
		if ( 'new' === $order_filter ) {
			$order_query = ' ORDER BY user_meta.meta_value DESC ';
		} elseif ( 'popular' === $order_filter ) {
			$order_query = ' ORDER BY rating DESC ';
		} else {
			$order_query = " ORDER BY user_meta.meta_value {$order_filter} ";
		}

		$limit_offset = $count_only ? '' : " LIMIT {$start}, {$limit} ";
		$select_col   = $count_only ?
						' COUNT(DISTINCT user.ID) ' :
						' DISTINCT user.*, user_meta.meta_value AS instructor_from_date, IFNULL(Avg(cmeta.meta_value), 0) AS rating, inst_status.meta_value AS status ';

		$query = $wpdb->prepare(
			"SELECT {$select_col}
			FROM {$wpdb->users} user
				INNER JOIN {$wpdb->usermeta} user_meta
						ON ( user.ID = user_meta.user_id )
				INNER JOIN {$wpdb->usermeta} inst_status
						ON ( user.ID = inst_status.user_id )
				{$category_join}
				LEFT JOIN {$wpdb->usermeta} AS umeta
					ON umeta.user_id = user.ID AND umeta.meta_key = '_tutor_instructor_course_id'
				LEFT JOIN {$wpdb->comments} AS c
					ON c.comment_post_ID = umeta.meta_value
				LEFT JOIN {$wpdb->commentmeta} AS cmeta
					ON cmeta.comment_id = c.comment_ID
					AND cmeta.meta_key = 'tutor_rating'
			WHERE 	user_meta.meta_key = '_is_tutor_instructor'
				AND ( user.display_name LIKE %s OR user.user_email = %s )
				{$status}
				{$category_where}
				{$course_filter}
				{$date_filter}
			GROUP BY user.ID {$rating_having} {$order_query} {$limit_offset}",
			$search_filter,
			$search_term_raw
		);

		$results = $wpdb->get_results( $query );
		return $count_only ? count( $results ) : $results;
	}

	/**
	 * Get all instructors by course
	 *
	 * @since 1.0.0
	 *
	 * @param int $course_id course id.
	 *
	 * @return array|bool|null|object
	 */
	public function get_instructors_by_course( $course_id = 0 ) {
		global $wpdb;
		$course_id = $this->get_post_id( $course_id );

		$instructors = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT ID,
					display_name,
					_user.user_email,
					get_course.meta_value AS taught_course_id,
					tutor_job_title.meta_value AS tutor_profile_job_title,
					tutor_bio.meta_value AS tutor_profile_bio,
					tutor_photo.meta_value AS tutor_profile_photo
				FROM {$wpdb->users} _user
					INNER JOIN {$wpdb->usermeta} get_course
							ON ID = get_course.user_id
						   AND get_course.meta_key = %s
						   AND get_course.meta_value = %d
					LEFT  JOIN {$wpdb->usermeta} tutor_job_title
						    ON ID = tutor_job_title.user_id
						   AND tutor_job_title.meta_key = %s
					LEFT  JOIN {$wpdb->usermeta} tutor_bio
						    ON ID = tutor_bio.user_id
						   AND tutor_bio.meta_key = %s
					LEFT  JOIN {$wpdb->usermeta} tutor_photo
						    ON ID = tutor_photo.user_id
						   AND tutor_photo.meta_key = %s
			",
				'_tutor_instructor_course_id',
				$course_id,
				'_tutor_profile_job_title',
				'_tutor_profile_bio',
				'_tutor_profile_photo'
			)
		);
		// Get main instructor.
		$main_instructor = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT _user.ID,
					display_name,
					_user.user_email,
					course.ID AS taught_course_id,
					tutor_job_title.meta_value AS tutor_profile_job_title,
					tutor_bio.meta_value AS tutor_profile_bio,
					tutor_photo.meta_value AS tutor_profile_photo
				FROM {$wpdb->users} _user
					INNER JOIN {$wpdb->posts} course
							ON _user.ID = course.post_author
						   AND course.ID = %d
					LEFT  JOIN {$wpdb->usermeta} tutor_job_title
						    ON _user.ID = tutor_job_title.user_id
						   AND tutor_job_title.meta_key = %s
					LEFT  JOIN {$wpdb->usermeta} tutor_bio
						    ON _user.ID = tutor_bio.user_id
						   AND tutor_bio.meta_key = %s
					LEFT  JOIN {$wpdb->usermeta} tutor_photo
						    ON _user.ID = tutor_photo.user_id
						   AND tutor_photo.meta_key = %s
			",
				$course_id,
				'_tutor_profile_job_title',
				'_tutor_profile_bio',
				'_tutor_profile_photo'
			)
		);
		if ( is_array( $instructors ) && count( $instructors ) ) {
			// Exclude instructor if already in main instructor.
			$instructors = array_filter(
				$instructors,
				function ( $instructor ) use ( $main_instructor ) {
					if ( $instructor->ID !== $main_instructor[0]->ID ) {
						return true;
					}
				}
			);
			return array_merge( $main_instructor, $instructors );
		}
		return $main_instructor;
	}

	/**
	 * Get total Students by instructor
	 * 1 enrollment = 1 student, so total enrolled for a equivalent total students (Tricks)
	 *
	 * @since 1.0.0
	 *
	 * @param int $instructor_id instructor id.
	 *
	 * @return int
	 */
	public function get_total_students_by_instructor( $instructor_id ) {
		global $wpdb;

		$course_post_type = tutor()->course_post_type;

		$count = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT COUNT(enrollment.ID)
			FROM 	{$wpdb->posts} enrollment
					INNER  JOIN {$wpdb->posts} course
							ON enrollment.post_parent=course.ID
			WHERE 	course.post_author = %d
					AND course.post_type = %s
					AND course.post_status = %s
					AND enrollment.post_type = %s
					AND enrollment.post_status = %s;
			",
				$instructor_id,
				$course_post_type,
				'publish',
				'tutor_enrolled',
				'completed'
			)
		);

		return (int) $count;
	}

	/**
	 * Get all students by instructor_id
	 *
	 * @since 1.9.9
	 *
	 * @param integer $instructor_id instructor id.
	 * @param integer $offset offset.
	 * @param integer $limit limit.
	 * @param string  $search_filter search filter.
	 * @param string  $course_id course id.
	 * @param string  $date_filter date filter.
	 * @param string  $order_by order by.
	 * @param string  $order order.
	 *
	 * @return array
	 */
	public function get_students_by_instructor( int $instructor_id, int $offset, int $limit, $search_filter = '', $course_id = '', $date_filter = '', $order_by = '', $order = '' ): array {
		global $wpdb;
		$instructor_id = sanitize_text_field( $instructor_id );
		$limit         = sanitize_text_field( $limit );
		$offset        = sanitize_text_field( $offset );
		$course_id     = sanitize_text_field( $course_id );
		$date_filter   = sanitize_text_field( $date_filter );
		$search_filter = sanitize_text_field( $search_filter );

		$order_by = 'user.ID';
		if ( 'registration_date' === $order_by ) {
			$order_by = 'enrollment.post_date';
		} elseif ( 'course_taken' === $order_by ) {
			$order_by = 'course_taken';
		} else {
			$order_by = 'user.ID';
		}

		$order = sanitize_sql_orderby( $order );

		if ( '' !== $date_filter ) {
			$date_filter = \tutor_get_formated_date( 'Y-m-d', $date_filter );
		}

		$course_post_type = tutor()->course_post_type;

		$search_term_raw = $search_filter;
		$search_query    = '%' . $wpdb->esc_like( $search_filter ) . '%';
		$course_query    = '';
		$date_query      = '';
		$author_query    = '';

		if ( $course_id ) {
			$course_query = " AND course.ID = $course_id ";
		}
		if ( '' !== $date_filter ) {
			$date_query = " AND DATE(user.user_registered) = CAST( '$date_filter' AS DATE ) ";
		}
		/**
		 * If instructor id set then by only students that belongs to instructor
		 * otherwise get all
		 *
		 * @since 2.0.0
		 */
		if ( $instructor_id ) {
			$author_query = "AND course.post_author = $instructor_id";
		}

		$students       = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT COUNT(enrollment.post_author) AS course_taken, user.*, (SELECT post_date FROM {$wpdb->posts} WHERE post_author = user.ID LIMIT 1) AS enroll_date
				FROM 	{$wpdb->posts} enrollment
					INNER  JOIN {$wpdb->posts} AS course
							ON enrollment.post_parent=course.ID
					INNER  JOIN {$wpdb->users} AS user
							ON user.ID = enrollment.post_author
				WHERE course.post_type = %s
					AND course.post_status = %s
					AND enrollment.post_type = %s
					AND enrollment.post_status = %s
					{$author_query}
					{$course_query}
					{$date_query}
					AND ( user.display_name LIKE %s OR user.user_nicename LIKE %s OR user.user_email = %s OR user.user_login LIKE %s )

				GROUP BY enrollment.post_author
				ORDER BY {$order_by} {$order}
				LIMIT %d, %d
			",
				$course_post_type,
				'publish',
				'tutor_enrolled',
				'completed',
				$search_query,
				$search_query,
				$search_term_raw,
				$search_query,
				$offset,
				$limit
			)
		);
		$total_students = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT COUNT(enrollment.post_author) AS course_taken, user.*, enrollment.post_date AS enroll_date
				FROM 	{$wpdb->posts} enrollment
					INNER  JOIN {$wpdb->posts} AS course
							ON enrollment.post_parent=course.ID
					INNER  JOIN {$wpdb->users} AS user
							ON user.ID = enrollment.post_author
				WHERE course.post_type = %s
					AND course.post_status = %s
					AND enrollment.post_type = %s
					AND enrollment.post_status = %s
					AND ( user.display_name LIKE %s OR user.user_nicename LIKE %s OR user.user_email = %s OR user.user_login LIKE %s )
					{$author_query}
					{$course_query}
					{$date_query}
				GROUP BY enrollment.post_author
				ORDER BY {$order_by} {$order}

			",
				$course_post_type,
				'publish',
				'tutor_enrolled',
				'completed',
				$search_query,
				$search_query,
				$search_term_raw,
				$search_query
			)
		);

		return array(
			'students'       => $students,
			'total_students' => count( $total_students ),
		);
	}

	/**
	 * Get all course for a give student & instructor id
	 *
	 * @since 1.9.9
	 *
	 * @param int $student_id student id.
	 * @param int $instructor_id instructor id.
	 *
	 * @return array
	 */
	public function get_courses_by_student_instructor_id( int $student_id, int $instructor_id ): array {
		global $wpdb;
		$course_post_type = tutor()->course_post_type;
		$students         = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT course.*
				FROM 	{$wpdb->posts} enrollment
					INNER  JOIN {$wpdb->posts} AS course
							ON enrollment.post_parent=course.ID
				WHERE 	course.post_author = %d
					AND course.post_type = %s
					AND course.post_status = %s
					AND enrollment.post_type = %s
					AND enrollment.post_status = %s
					AND enrollment.post_author = %d
				ORDER BY course.post_date DESC
			",
				$instructor_id,
				$course_post_type,
				'publish',
				'tutor_enrolled',
				'completed',
				$student_id
			)
		);
		return $students;
	}

	/**
	 * Get total number of completed assignment
	 *
	 * @since 1.9.9
	 *
	 * @param int $course_id course id.
	 * @param int $student_id student id.
	 *
	 * @return int
	 */
	public function get_completed_assignment( int $course_id, int $student_id ): int {
		global $wpdb;
		$course_id  = sanitize_text_field( $course_id );
		$student_id = sanitize_text_field( $student_id );
		$count      = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT COUNT(ID) FROM {$wpdb->posts}
				INNER JOIN {$wpdb->comments} c ON c.comment_post_ID = ID  AND c.user_id = %d AND c.comment_approved = %s
				WHERE post_parent IN (SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_parent = %d AND post_status = %s)
					AND post_type =%s
					AND post_status = %s
			",
				$student_id,
				'submitted',
				'topics',
				$course_id,
				'publish',
				'tutor_assignments',
				'publish'
			)
		);
		return (int) $count;
	}

	/**
	 * Get total number of completed quiz
	 *
	 * @since 1.9.9
	 *
	 * @param int $course_id course id.
	 * @param int $student_id student id.
	 *
	 * @return int
	 */
	public function get_completed_quiz( int $course_id, int $student_id ): int {
		global $wpdb;
		$course_id  = sanitize_text_field( $course_id );
		$student_id = sanitize_text_field( $student_id );
		$count      = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT COUNT(DISTINCT quiz_id) AS total
				FROM {$wpdb->prefix}tutor_quiz_attempts
				WHERE course_id = %d
				AND user_id = %d
				AND attempt_status = %s
			",
				$course_id,
				$student_id,
				'attempt_ended'
			)
		);
		return (int) $count;
	}

	/**
	 * Get rating format from value
	 *
	 * @since 1.0.0
	 *
	 * @param float $input input.
	 *
	 * @return float|string
	 */
	public function get_rating_value( $input = 0.00 ) {

		if ( $input > 0 ) {
			$input     = number_format( $input, 2 );
			$int_value = (int) $input;
			$fraction  = $input - $int_value;

			if ( 0 == $fraction ) {
				$fraction = 0.00;
			} elseif ( $fraction > 0.5 ) {
				$fraction = 1;
			} else {
				$fraction = 0.5;
			}

			return number_format( ( $int_value + $fraction ), 2 );
		}

		return 0.00;
	}

	/**
	 * Generate star rating based in given rating value
	 *
	 * @since 1.0.0
	 *
	 * @param float $current_rating current rating.
	 * @param bool  $echo print output.
	 *
	 * @return string
	 */
	public function star_rating_generator( $current_rating = 0.00, $echo = true ) {

		$output = '<div class="tutor-ratings-stars">';

		for ( $i = 1; $i <= 5; $i++ ) {
			if ( (int) $current_rating >= $i ) {
				$output .= '<i class="tutor-icon-star-bold" data-rating-value="' . $i . '"></i>';
			} elseif ( ( $current_rating - $i ) >= -0.5 ) {
					$output .= '<i class="tutor-icon-star-half-bold" data-rating-value="' . $i . '"></i>';
			} else {
				$output .= '<i class="tutor-icon-star-line" data-rating-value="' . $i . '"></i>';
			}
		}

		$output .= '</div>';

		$output .= '<input type="hidden" name="tutor_rating_gen_input" value="' . $current_rating . '" />';

		if ( $echo ) {
			echo tutor_kses_html( $output );
		}

		return $output;
	}

	/**
	 * Generate star rating.
	 *
	 * @since 1.0.0
	 *
	 * @param mixed   $current_rating current rating.
	 * @param mixed   $total_count total count.
	 * @param boolean $show_avg_rate show avg rate.
	 * @param string  $parent_class perent class.
	 * @param string  $screen_size screen size.
	 *
	 * @return void
	 */
	public function star_rating_generator_v2( $current_rating, $total_count = null, $show_avg_rate = false, $parent_class = '', $screen_size = '' ) {
		$current_rating = number_format( $current_rating, 2, '.', '' );
		$css_class      = isset( $screen_size ) ? "{$parent_class} tutor-ratings-{$screen_size}" : "{$parent_class}";
		?>
		<div class="tutor-ratings<?php echo esc_attr( $css_class ); ?>">
			<div class="tutor-ratings-stars">
				<?php
				for ( $i = 1; $i <= 5; $i++ ) {
					$class = 'tutor-icon-star-line';

					if ( $i <= round( $current_rating ) ) {
						$class = 'tutor-icon-star-bold';
					}

					// Todo: Add half start later. tutor-icon-star-half-bold.
					echo '<span class="' . $class . '"></span>';
				}
				?>
			</div>
			<?php if ( $show_avg_rate && $total_count > 0 ) : ?>
				<div class="tutor-ratings-average">
					<?php echo esc_html( $current_rating ); ?>
				</div>
				<div class="tutor-ratings-count">
					(<?php echo esc_html( $total_count ) . ' ' . ( $total_count > 1 ? esc_html__( 'Ratings', 'tutor' ) : esc_html__( 'Rating', 'tutor' ) ); ?>)
				</div>
			<?php endif; ?>
		</div>
		<?php
	}

	/**
	 * Generate course star rating.
	 *
	 * @since 1.0.0
	 *
	 * @param float   $current_rating current rating.
	 * @param boolean $echo output print.
	 *
	 * @return mixed
	 */
	public function star_rating_generator_course( $current_rating = 0.00, $echo = true ) {
		$output = '';
		for ( $i = 1; $i <= 5; $i++ ) {
			if ( (int) $current_rating >= $i ) {
				$output .= '<span class="tutor-icon-star-bold" data-rating-value="' . $i . '"></span>';
			} elseif ( ( $current_rating - $i ) >= -0.5 ) {
					$output .= '<span class="tutor-icon-star-half-bold" data-rating-value="' . $i . '"></span>';
			} else {
				$output .= '<span class="tutor-icon-star-line" data-rating-value="' . $i . '"></span>';
			}
		}

		if ( $echo ) {
			echo wp_kses(
				$output,
				array(
					'span' => array(
						'class'             => true,
						'data-rating'       => true,
						'data-rating-value' => true,
					),
				)
			);
		}

		return $output;
	}

	/**
	 * Split string regardless of ASCI, Unicode
	 *
	 * @since 1.0.0
	 *
	 * @param string $string string.
	 *
	 * @return string
	 */
	public function str_split( $string ) {
		$strlen = mb_strlen( $string );
		while ( $strlen ) {
			$array[] = mb_substr( $string, 0, 1, 'UTF-8' );
			$string  = mb_substr( $string, 1, $strlen, 'UTF-8' );
			$strlen  = mb_strlen( $string );
		}
		return $array;
	}

	/**
	 * Generate avatar for user
	 *
	 * @since 1.0.0
	 * @since 2.1.7   changed param $user_id to $user for reduce query.
	 * @since 2.1.8   Get user data using get_userdata API
	 *
	 * @param integer|object $user user id or object.
	 * @param string         $size size of avatar like sm, md, lg.
	 * @param bool           $echo whether to echo or return.
	 *
	 * @return string
	 */
	public function get_tutor_avatar( $user = null, $size = '', $echo = false ) {

		if ( ! $user ) {
			return '';
		}

		if ( ! is_object( $user ) ) {
			$user = get_userdata( $user );
		}

		if ( is_a( $user, 'WP_User' ) ) {
			// Get & set user profile photo.
			$profile_photo             = get_user_meta( $user->ID, '_tutor_profile_photo', true );
			$user->tutor_profile_photo = $profile_photo;
		}

		$name  = is_object( $user ) ? $user->display_name : '';
		$arr   = explode( ' ', trim( $name ) );
		$class = $size ? ' tutor-avatar-' . $size : '';

		$output  = '<div class="tutor-avatar' . $class . '">';
		$output .= '<div class="tutor-ratio tutor-ratio-1x1">';

		if ( is_object( $user ) && $user->tutor_profile_photo && wp_get_attachment_image_url( $user->tutor_profile_photo ) ) {
			$output .= '<img src="' . wp_get_attachment_image_url( $user->tutor_profile_photo, 'thumbnail' ) . '" alt="' . esc_attr( $name ) . '" /> ';
		} else {
			$first_char     = ! empty( $arr[0] ) ? $this->str_split( $arr[0] )[0] : '';
			$second_char    = ! empty( $arr[1] ) ? $this->str_split( $arr[1] )[0] : '';
			$initial_avatar = strtoupper( $first_char . $second_char );
			$output        .= '<span class="tutor-avatar-text">' . $initial_avatar . '</span>';
		}

		$output .= '</div>';
		$output .= '</div>';

		if ( $echo ) {
			echo wp_kses( $output, $this->allowed_avatar_tags() );
		} else {
			return apply_filters( 'tutor_text_avatar', $output );
		}
	}

	/**
	 * Get tutor user.
	 *
	 * @since 1.0.0
	 * @since 3.0.0 tutor_profile_photo_url property added.
	 *
	 * @param int $user_id user id.
	 *
	 * @return array|null|object|void
	 */
	public function get_tutor_user( $user_id ) {
		$cache_key   = 'tutor_user_' . $user_id;
		$cached_data = TutorCache::get( $cache_key );

		if ( false !== $cached_data ) {
			return $cached_data;
		}

		global $wpdb;

		$user = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT ID,
					display_name,
					user_email,
					user_login,
					user_nicename,
					tutor_job_title.meta_value AS tutor_profile_job_title,
					tutor_bio.meta_value AS tutor_profile_bio,
					tutor_photo.meta_value AS tutor_profile_photo
			FROM	{$wpdb->users}
					LEFT  JOIN {$wpdb->usermeta} tutor_job_title
							ON ID = tutor_job_title.user_id
						   AND tutor_job_title.meta_key = '_tutor_profile_job_title'
					LEFT  JOIN {$wpdb->usermeta} tutor_bio
							ON ID = tutor_bio.user_id
						   AND tutor_bio.meta_key = '_tutor_profile_bio'
					LEFT  JOIN {$wpdb->usermeta} tutor_photo
							ON ID = tutor_photo.user_id
						   AND tutor_photo.meta_key = '_tutor_profile_photo'
			WHERE 	ID = %d
			",
				$user_id
			)
		);

		if ( $user ) {
			$user->tutor_profile_photo_url = wp_get_attachment_image_url( $user->tutor_profile_photo );
		}

		TutorCache::set( $cache_key, $user );

		return $user;
	}

	/**
	 * Get course reviews
	 *
	 * @since 1.0.0
	 *
	 * @param int   $course_id course id.
	 * @param int   $start offset.
	 * @param int   $limit limit.
	 * @param bool  $count_only count only.
	 * @param array $status_in status list.
	 * @param int   $include_user_id include user id.
	 *
	 * @return array|null|object
	 */
	public function get_course_reviews( $course_id = 0, $start = 0, $limit = 10, $count_only = false, $status_in = array( 'approved' ), $include_user_id = 0 ) {
		$course_id = $this->get_post_id( $course_id );
		global $wpdb;

		$limit_offset    = $count_only ? '' : ' LIMIT ' . $limit . ' OFFSET ' . $start;
		$status_in       = '"' . implode( '","', $status_in ) . '"';
		$include_user_id = is_array( $include_user_id ) ? $include_user_id : array( $include_user_id );
		$include_user_id = implode( ',', $include_user_id );

		$select_columns = $count_only ? ' COUNT(DISTINCT _reviews.comment_ID) ' :
			'_reviews.comment_ID,
			_reviews.comment_post_ID,
			_reviews.comment_author,
			_reviews.comment_author_email,
			_reviews.comment_date,
			_reviews.comment_content,
			_reviews.comment_approved AS comment_status,
			_reviews.user_id,
			_rev_meta.meta_value AS rating,
			_reviewer.display_name';

		$query = $wpdb->prepare(
			"SELECT {$select_columns}
			FROM 	{$wpdb->comments} _reviews
					INNER JOIN {$wpdb->commentmeta} _rev_meta
						ON _reviews.comment_ID = _rev_meta.comment_id
					LEFT JOIN {$wpdb->users} _reviewer
						ON _reviews.user_id = _reviewer.ID
			WHERE 	_reviews.comment_post_ID = %d
					AND _reviews.comment_type = 'tutor_course_rating' 
					AND (_reviews.comment_approved IN ({$status_in}) OR _reviews.user_id IN ({$include_user_id}))
					AND _rev_meta.meta_key = 'tutor_rating'
			ORDER BY _reviews.comment_ID DESC {$limit_offset}",
			$course_id
		);

		return $count_only ? $wpdb->get_var( $query ) : $wpdb->get_results( $query );
	}

	/**
	 * Get course rating
	 *
	 * @since 1.0.0
	 *
	 * @param int $course_id course ID.
	 *
	 * @return object
	 */
	public function get_course_rating( $course_id = 0 ) {
		global $wpdb;
		$course_id = $this->get_post_id( $course_id );

		$ratings = array(
			'rating_count'   => 0,
			'rating_sum'     => 0,
			'rating_avg'     => 0.00,
			'count_by_value' => array(
				5 => 0,
				4 => 0,
				3 => 0,
				2 => 0,
				1 => 0,
			),
		);

		$rating = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT COUNT(meta_value) AS rating_count,
					SUM(meta_value) AS rating_sum
			FROM	{$wpdb->comments}
					INNER JOIN {$wpdb->commentmeta}
							ON {$wpdb->comments}.comment_ID = {$wpdb->commentmeta}.comment_id
			WHERE 	{$wpdb->comments}.comment_post_ID = %d
					AND {$wpdb->comments}.comment_type = %s
					AND {$wpdb->comments}.comment_approved = %s
					AND meta_key = %s;
			",
				$course_id,
				'tutor_course_rating',
				'approved',
				'tutor_rating'
			)
		);

		if ( $rating->rating_count ) {
			$avg_rating = number_format( ( $rating->rating_sum / $rating->rating_count ), 2 );

			$stars = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT CAST(commentmeta.meta_value AS SIGNED) AS rating,
						COUNT(commentmeta.meta_value) as rating_count
				FROM	{$wpdb->comments} comments
						INNER JOIN {$wpdb->commentmeta} commentmeta
								ON comments.comment_ID = commentmeta.comment_id
				WHERE	comments.comment_post_ID = %d
						AND comments.comment_type = %s
						AND commentmeta.meta_key = %s
				GROUP BY CAST(commentmeta.meta_value AS SIGNED);
				",
					$course_id,
					'tutor_course_rating',
					'tutor_rating'
				)
			);

			$ratings = array(
				5 => 0,
				4 => 0,
				3 => 0,
				2 => 0,
				1 => 0,
			);
			foreach ( $stars as $star ) {
				$index = (int) $star->rating;
				array_key_exists( $index, $ratings ) ? $ratings[ $index ] = $star->rating_count : 0;
			}

			$ratings = array(
				'rating_count'   => $rating->rating_count,
				'rating_sum'     => $rating->rating_sum,
				'rating_avg'     => $avg_rating,
				'count_by_value' => $ratings,
			);
		}

		return (object) $ratings;
	}

	/**
	 * Get reviews by a user (Given by the user)
	 *
	 * @since 1.0.0
	 *
	 * @param int   $user_id user id.
	 * @param int   $offset offset.
	 * @param int   $limit limit.
	 * @param bool  $get_object get object.
	 * @param mixed $course_id course id.
	 * @param array $status_in status.
	 *
	 * @return array|null|object
	 */
	public function get_reviews_by_user( $user_id = 0, $offset = 0, $limit = null, $get_object = false, $course_id = null, $status_in = array( 'approved' ) ) {
		global $wpdb;

		if ( ! $limit ) {
			$limit = $this->get_option( 'pagination_per_page', 10 );
		}

		$course_filter = '';
		if ( $course_id ) {
			$course_ids    = is_array( $course_id ) ? $course_id : array( $course_id );
			$course_ids    = implode( ',', $course_ids );
			$course_filter = " AND _comment.comment_post_ID IN ($course_ids)";
		}

		$user_filter = '';
		if ( null !== $user_id ) {
			$user_id     = $this->get_user_id( $user_id );
			$user_filter = ' AND _comment.user_id=' . $user_id;
		}

		$status_in     = '"' . implode( '","', $status_in ) . '"';
		$status_filter = ' AND _comment.comment_approved IN (' . $status_in . ')';

		$reviews = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT _comment.comment_ID,
					_comment.comment_post_ID,
					_comment.comment_author,
					_comment.comment_author_email,
					_comment.comment_date,
					_comment.comment_content,
					_comment.comment_approved AS comment_status,
					_comment.user_id,
					_meta.meta_value as rating,
					_course.post_title AS course_title,
					_student.display_name
			FROM 	{$wpdb->comments} _comment
					INNER JOIN {$wpdb->commentmeta} _meta
							ON _comment.comment_ID = _meta.comment_id
					INNER JOIN {$wpdb->posts} _course
							ON _comment.comment_post_ID=_course.ID
					INNER  JOIN {$wpdb->users} _student
							ON _comment.user_id = _student.ID
			WHERE 	_comment.comment_type = %s
					AND _meta.meta_key = %s
					{$user_filter}
					{$course_filter}
					{$status_filter}
			ORDER BY _comment.comment_ID DESC
			LIMIT %d, %d;",
				'tutor_course_rating',
				'tutor_rating',
				$offset,
				$limit
			)
		);

		if ( $get_object ) {
			// Prepare other data for multiple reviews case.
			$count = (int) $wpdb->get_var(
				$wpdb->prepare(
					"SELECT COUNT({$wpdb->comments}.comment_ID)
						FROM 	{$wpdb->comments}
								INNER JOIN {$wpdb->commentmeta}
										ON {$wpdb->comments}.comment_ID = {$wpdb->commentmeta}.comment_id
								INNER  JOIN {$wpdb->users}
										ON {$wpdb->comments}.user_id = {$wpdb->users}.ID
								INNER JOIN {$wpdb->posts} AS course
									ON course.ID = comment_post_ID
						WHERE 	{$wpdb->comments}.user_id = %d
								AND comment_type = %s
								AND meta_key = %s
								AND comment_approved = 'approved'
					",
					$user_id,
					'tutor_course_rating',
					'tutor_rating'
				)
			);

			return (object) array(
				'count'   => $count,
				'results' => $reviews,
			);
		}

		// Return single review for single course.
		if ( $course_id && ! is_array( $course_id ) ) {
			return count( $reviews ) ? $reviews[0] : null;
		}

		return $reviews;
	}

	/**
	 * Get reviews by instructor (Received by the instructor)
	 *
	 * @since 1.0.0
	 * @since 1.4.0 $course_id $date_filter param added.
	 * @since 1.9.9 Course id & date filter is sorting with specific course and date.
	 *
	 * @param int    $instructor_id user id.
	 * @param int    $offset offset.
	 * @param int    $limit limit.
	 * @param string $course_id course id.
	 * @param string $date_filter date filter.
	 *
	 * @return array|null|object
	 */
	public function get_reviews_by_instructor( $instructor_id = 0, $offset = 0, $limit = 150, $course_id = '', $date_filter = '' ) {
		global $wpdb;
		$instructor_id = sanitize_text_field( $instructor_id );
		$offset        = sanitize_text_field( $offset );
		$limit         = sanitize_text_field( $limit );
		$course_id     = sanitize_text_field( $course_id );
		$date_filter   = sanitize_text_field( $date_filter );
		$instructor_id = $this->get_user_id( $instructor_id );

		$course_query = '';
		$date_query   = '';

		if ( '' !== $course_id ) {
			$course_query = " AND {$wpdb->comments}.comment_post_ID = {$course_id} ";
		}
		if ( '' !== $date_filter ) {
			$date_filter = \tutor_get_formated_date( 'Y-m-d', $date_filter );
			$date_query  = " AND DATE({$wpdb->comments}.comment_date) = CAST( '$date_filter' AS DATE ) ";
		}

		$results = array(
			'count'   => 0,
			'results' => false,
		);

		$cours_ids = (array) $this->get_assigned_courses_ids_by_instructors( $instructor_id );

		if ( $this->count( $cours_ids ) ) {
			$implode_ids = implode( ',', $cours_ids );

			// Count.
			$results['count'] = $wpdb->get_var(
				$wpdb->prepare(
					"SELECT COUNT({$wpdb->comments}.comment_ID)
				FROM 	{$wpdb->comments}
						INNER JOIN {$wpdb->commentmeta}
								ON {$wpdb->comments}.comment_ID = {$wpdb->commentmeta}.comment_id
						INNER JOIN {$wpdb->users}
								ON {$wpdb->comments}.user_id = {$wpdb->users}.ID
				WHERE 	{$wpdb->comments}.comment_post_ID IN({$implode_ids})
						AND comment_type = %s
						AND meta_key = %s
						{$course_query}
						{$date_query}
				",
					'tutor_course_rating',
					'tutor_rating'
				)
			);

			// Results.
			$results['results'] = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT {$wpdb->comments}.comment_ID,
						{$wpdb->comments}.comment_post_ID,
						{$wpdb->comments}.comment_author,
						{$wpdb->comments}.comment_author_email,
						{$wpdb->comments}.comment_date,
						{$wpdb->comments}.comment_content,
						{$wpdb->comments}.user_id,
						{$wpdb->commentmeta}.meta_value AS rating,
						{$wpdb->users}.display_name,
						{$wpdb->posts}.post_title as course_title

				FROM 	{$wpdb->comments}
						INNER JOIN {$wpdb->commentmeta}
								ON {$wpdb->comments}.comment_ID = {$wpdb->commentmeta}.comment_id
						INNER JOIN {$wpdb->users}
								ON {$wpdb->comments}.user_id = {$wpdb->users}.ID
						INNER JOIN {$wpdb->posts}
								ON {$wpdb->posts}.ID = {$wpdb->comments}.comment_post_ID
				WHERE 	{$wpdb->comments}.comment_post_ID IN({$implode_ids})
						AND comment_type = %s
						AND meta_key = %s
						{$course_query}
						{$date_query}
				ORDER BY comment_ID DESC
				LIMIT %d, %d;
				",
					'tutor_course_rating',
					'tutor_rating',
					$offset,
					$limit
				)
			);
		}

		return (object) $results;
	}

	/**
	 * Get instructors rating
	 *
	 * @since 1.0.0
	 *
	 * @param int $instructor_id instructor id.
	 *
	 * @return object
	 */
	public function get_instructor_ratings( $instructor_id ) {
		global $wpdb;

		$ratings = array(
			'rating_count' => 0,
			'rating_sum'   => 0,
			'rating_avg'   => 0.00,
		);

		$rating = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT COUNT(rating.meta_value) as rating_count, SUM(rating.meta_value) as rating_sum
			FROM 	{$wpdb->usermeta} courses
					INNER JOIN {$wpdb->comments} reviews
							ON courses.meta_value = reviews.comment_post_ID
						   AND reviews.comment_type = 'tutor_course_rating'
					INNER JOIN {$wpdb->commentmeta} rating
							ON reviews.comment_ID = rating.comment_id
						   AND rating.meta_key = 'tutor_rating'
			WHERE 	courses.user_id = %d
					AND courses.meta_key = %s
			",
				$instructor_id,
				'_tutor_instructor_course_id'
			)
		);

		if ( $rating->rating_count ) {
			$avg_rating = number_format( ( $rating->rating_sum / $rating->rating_count ), 2 );

			$ratings = array(
				'rating_count' => $rating->rating_count,
				'rating_sum'   => $rating->rating_sum,
				'rating_avg'   => $avg_rating,
			);
		}

		return (object) $ratings;
	}

	/**
	 * Get course rating by user
	 *
	 * @since 1.0.0
	 *
	 * @param int $course_id course id.
	 * @param int $user_id user id.
	 *
	 * @return object
	 */
	public function get_course_rating_by_user( $course_id = 0, $user_id = 0 ) {
		global $wpdb;

		$course_id = $this->get_post_id( $course_id );
		$user_id   = $this->get_user_id( $user_id );

		$ratings = array(
			'rating' => 0,
			'review' => '',
		);

		$rating = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT meta_value AS rating,
					comment_content AS review
			FROM	{$wpdb->comments}
					INNER JOIN {$wpdb->commentmeta}
							ON {$wpdb->comments}.comment_ID = {$wpdb->commentmeta}.comment_id
			WHERE	{$wpdb->comments}.comment_post_ID = %d
					AND user_id = %d
					AND meta_key = %s;
			",
				$course_id,
				$user_id,
				'tutor_rating'
			)
		);

		if ( $rating ) {
			$rating_format = number_format( $rating->rating, 2 );

			$ratings = array(
				'rating' => $rating_format,
				'review' => $rating->review,
			);
		}

		return (object) $ratings;
	}

	/**
	 * Count reviews wrote by user
	 *
	 * @since 1.0.0
	 *
	 * @param int $user_id user id.
	 *
	 * @return null|string
	 */
	public function count_reviews_wrote_by_user( $user_id = 0 ) {
		global $wpdb;

		$user_id = $this->get_user_id( $user_id );

		$count_reviews = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT COUNT(comment_ID)
			FROM	{$wpdb->comments}
			WHERE 	user_id = %d
					AND comment_type = %s
			",
				$user_id,
				'tutor_course_rating'
			)
		);

		return $count_reviews;
	}

	/**
	 * This function transforms the php.ini notation for numbers (like '2M') to an integer.
	 *
	 * @since 1.0.0
	 *
	 * @param mixed $size size.
	 *
	 * @return bool|int|string
	 */
	public function let_to_num( $size ) {
		$l    = substr( $size, -1 );
		$ret  = substr( $size, 0, -1 );
		$byte = 1024;

		switch ( strtoupper( $l ) ) {
			case 'P':
				$ret *= 1024;
				// No break.
			case 'T':
				$ret *= 1024;
				// No break.
			case 'G':
				$ret *= 1024;
				// No break.
			case 'M':
				$ret *= 1024;
				// No break.
			case 'K':
				$ret *= 1024;
				// No break.
		}
		return $ret;
	}

	/**
	 * Get Database version
	 *
	 * @since 1.0.0
	 *
	 * @return array
	 */
	public function get_db_version() {
		global $wpdb;

		if ( empty( $wpdb->is_mysql ) ) {
			return array(
				'string' => '',
				'number' => '',
			);
		}

		if ( $wpdb->use_mysqli ) {
			$server_info = mysqli_get_server_info($wpdb->dbh); // @codingStandardsIgnoreLine.
		} else {
			$server_info = mysql_get_server_info($wpdb->dbh); // @codingStandardsIgnoreLine.
		}

		return array(
			'string' => $server_info,
			'number' => preg_replace( '/([^\d.]+).*/', '', $server_info ),
		);
	}

	/**
	 * Get help tip
	 *
	 * @since 1.0.0
	 *
	 * @param string $tip tip name.
	 *
	 * @return string
	 */
	public function help_tip( $tip = '' ) {
		return '<span class="tutor-help-tip" data-tip="' . $tip . '"></span>';
	}

	/**
	 * Get question and answer query
	 *
	 * @since 1.0.0
	 *
	 * @param integer $start start.
	 * @param integer $limit limit.
	 * @param string  $search_term search term.
	 * @param mixed   $question_id question id.
	 * @param mixed   $meta_query meta query.
	 * @param mixed   $asker_id asker id.
	 * @param mixed   $question_status question status.
	 * @param boolean $count_only count only.
	 * @param array   $args args.
	 *
	 * @return array|null|object
	 */
	public function get_qa_questions( $start = 0, $limit = 10, $search_term = '', $question_id = null, $meta_query = null, $asker_id = null, $question_status = null, $count_only = false, $args = array() ) {
		global $wpdb;

		$user_id            = get_current_user_id();
		$course_type        = tutor()->course_post_type;
		$search_term        = '%' . $wpdb->esc_like( $search_term ) . '%';
		$question_clause    = $question_id ? ' AND _question.comment_ID=' . $question_id : '';
		$order_condition    = ' ORDER BY _question.comment_ID DESC ';
		$meta_clause        = '';
		$in_course_id_query = '';
		$qna_types_caluse   = '';
		$filter_clause      = '';

		// Sanitize args before process.
		$args = Input::sanitize_array( $args );

		/**
		 * Get only assinged  courses questions if current user is not admin
		 * User query.
		 */
		if ( $asker_id ) {
			$question_clause .= ' AND _question.user_id=' . $asker_id;
		}

		if ( isset( $args['course_id'] ) ) {
			// Get qa for specific course.
			$args['course_id']   = intval( $args['course_id'] );
			$in_course_id_query .= ' AND _question.comment_post_ID=' . $args['course_id'] . ' ';

		} elseif ( ! $asker_id && $question_id === null && ! $this->has_user_role( 'administrator', $user_id ) && current_user_can( tutor()->instructor_role ) ) {
			// If current user is simple instructor (non admin), then get qa from their courses only.
			$my_course_ids       = $this->get_course_id_by( 'instructor', $user_id );
			$in_ids              = count( $my_course_ids ) ? implode( ',', $my_course_ids ) : '0';
			$in_course_id_query .= " AND _question.comment_post_ID IN($in_ids) ";
		}

		// Add more filters to the query.
		if ( isset( $args['course-id'] ) && is_numeric( $args['course-id'] ) ) {
			$filter_clause .= ' AND _course.ID=' . $args['course-id'];
		}

		if ( isset( $args['date'] ) ) {
			$date           = esc_sql( $args['date'] );
			$filter_clause .= ' AND DATE(_question.comment_date)=\'' . $date . '\'';
		}

		if ( isset( $args['order'] ) ) {
			$order = strtolower( $args['order'] );
			if ( 'asc' === $order || 'desc' === $order ) {
				$order_condition = ' ORDER BY _question.comment_ID ' . $order . ' ';
			}
		}

		// Meta query.
		if ( $meta_query ) {
			$meta_array = array();
			foreach ( $meta_query as $key => $value ) {
				$meta_array[] = "_meta.meta_key='{$key}' AND _meta.meta_value='{$value}'";
			}
			$meta_clause .= ' AND ' . implode( ' AND ', $meta_array );
		}

		$asker_prefix    = null === $asker_id ? '' : '_' . $asker_id;
		$exclude_archive = ' AND NOT EXISTS (SELECT meta_key FROM ' . $wpdb->commentmeta . ' WHERE meta_key = \'tutor_qna_archived' . $asker_prefix . '\' AND meta_value=1 AND comment_id = _meta.comment_id) ';

		// Assign read, unread, archived, important identifier.
		switch ( $question_status ) {
			case null:
			case 'all':
				if ( ! $question_id ) {
					$qna_types_caluse = $exclude_archive;
				}
				break;

			case 'read':
				$qna_types_caluse = ' AND (_meta.meta_key=\'tutor_qna_read' . $asker_prefix . '\' AND _meta.meta_value=1) ' . $exclude_archive;
				break;

			case 'unread':
				$qna_types_caluse = ' AND (_meta.meta_key=\'tutor_qna_read' . $asker_prefix . '\' AND _meta.meta_value!=1) ' . $exclude_archive;
				break;

			case 'archived':
				$qna_types_caluse = ' AND (_meta.meta_key=\'tutor_qna_archived' . $asker_prefix . '\' AND _meta.meta_value=1) ';
				break;

			case 'important':
				$qna_types_caluse = ' AND (_meta.meta_key=\'tutor_qna_important' . $asker_prefix . '\' AND _meta.meta_value=1) ' . $exclude_archive;
				break;
		}

		$columns_select = $count_only ? 'COUNT(DISTINCT _question.comment_ID)' :
			"DISTINCT _question.comment_ID,
					_question.comment_post_ID,
					_question.comment_author,
					_question.comment_date,
					_question.comment_date_gmt,
					_question.comment_content,
					_question.user_id,
					_user.user_email,
					_user.display_name,
					_course.ID as course_id,
					_course.post_title,
					(	SELECT  COUNT(answers_t.comment_ID)
						FROM 	{$wpdb->comments} answers_t
						WHERE 	answers_t.comment_parent = _question.comment_ID
					) AS answer_count";

		$limit_offset = $count_only ? '' : ' LIMIT ' . $limit . ' OFFSET ' . $start;

		$query = $wpdb->prepare(
			"SELECT  {$columns_select}
			FROM {$wpdb->comments} _question
					INNER JOIN {$wpdb->posts} _course
							ON _question.comment_post_ID = _course.ID
					INNER JOIN {$wpdb->users} _user
							ON _question.user_id = _user.ID
					LEFT JOIN {$wpdb->commentmeta} _meta
							ON _question.comment_ID = _meta.comment_id
					LEFT JOIN {$wpdb->commentmeta} _meta_archive
							ON _question.comment_ID = _meta_archive.comment_id
			WHERE  	_question.comment_type = 'tutor_q_and_a'
					AND _question.comment_parent = 0
					AND _question.comment_content LIKE %s
					{$in_course_id_query}
					{$question_clause}
					{$meta_clause}
					{$qna_types_caluse}
					{$filter_clause}
			{$order_condition}
			{$limit_offset}",
			$search_term
		);
		if ( $count_only ) {
			return $wpdb->get_var( $query );
		}

		$query = $wpdb->get_results( $query );

		// Collect question IDs and create empty meta array placeholder.
		$question_ids = array();
		foreach ( $query as $index => $q ) {
			$question_ids[]        = $q->comment_ID;
			$query[ $index ]->meta = array();
		}

		// Assign meta data.
		if ( count( $question_ids ) ) {
			$q_ids      = implode( ',', $question_ids );
			$meta_array = $wpdb->get_results(
				"SELECT comment_id, meta_key, meta_value
				FROM {$wpdb->commentmeta}
				WHERE comment_id IN ({$q_ids})"
			);
			// Loop through meta array.
			foreach ( $meta_array as $meta ) {
				// Loop through questions.
				foreach ( $query as $index => $question ) {

					if ( $query[ $index ]->comment_ID == $meta->comment_id ) {

						$query[ $index ]->meta[ $meta->meta_key ] = $meta->meta_value;
					}
				}
			}
		}

		if ( $question_id ) {
			return isset( $query[0] ) ? $query[0] : null;
		}

		return $query;
	}

	/**
	 * Get question for Q&A
	 *
	 * @since 1.0.0
	 *
	 * @param int $question_id question id.
	 *
	 * @return array|null|object|void
	 */
	public function get_qa_question( $question_id ) {
		return $this->get_qa_questions( 0, 1, '', $question_id );
	}

	/**
	 * Get question and asnwer by question
	 *
	 * @since 1.0.0
	 *
	 * @param int $question_id question id.
	 *
	 * @return array|null|object
	 */
	public function get_qa_answer_by_question( $question_id ) {
		global $wpdb;
		$query = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT _chat.comment_ID,
					_chat.comment_post_ID,
					_chat.comment_author,
					_chat.comment_date,
					_chat.comment_date_gmt,
					_chat.comment_content,
					_chat.comment_parent,
					_chat.user_id,
					{$wpdb->users}.display_name
			FROM	{$wpdb->comments} _chat
					INNER JOIN {$wpdb->users} ON _chat.user_id = {$wpdb->users}.ID
			WHERE 	comment_type = 'tutor_q_and_a'
					AND ( _chat.comment_ID=%d OR _chat.comment_parent = %d)
			ORDER BY _chat.comment_ID ASC;",
				$question_id,
				$question_id
			)
		);

		return $query;
	}

	/**
	 * Get question and asnwer by answer_id
	 *
	 * @since 1.6.9
	 *
	 * @param int $answer_id answer id.
	 *
	 * @return array|null|object
	 */
	public function get_qa_answer_by_answer_id( $answer_id ) {
		global $wpdb;
		$answer = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT answer.comment_post_ID,
					answer.comment_content,
					users.display_name,
					question.user_id AS question_by,
					question.comment_content AS question,
					question.comment_ID AS question_id
			FROM   {$wpdb->comments} answer
					INNER JOIN {$wpdb->users} users
							ON answer.user_id = users.id
					INNER JOIN {$wpdb->comments} question
							ON answer.comment_parent = question.comment_ID
			WHERE  	answer.comment_ID = %d
					AND answer.comment_type = %s;
			",
				$answer_id,
				'tutor_q_and_a'
			)
		);

		if ( $answer ) {
			return $answer;
		}

		return false;
	}

	/**
	 * Funcion to check if a user can delete qa by id
	 *
	 * @param int $user_id
	 * @param int $question_id
	 * @return boolean
	 */
	public function can_delete_qa( $user_id, $question_id ) {
		global $wpdb;

		$is_admin = $this->has_user_role( 'administrator', $user_id );

		if ( $is_admin ) {
			return true;
		}

		$result = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT  *
				FROM 	{$wpdb->comments} qa
				WHERE 	qa.comment_ID = %d
			",
				$question_id
			)
		);

		if ( $result && (int) $result->user_id === $user_id ) {
			return true;
		}

		return false;
	}

	/**
	 * Get total number of un-answered question.
	 *
	 * @since 1.0.0
	 *
	 * @return int
	 */
	public function unanswered_question_count() {
		global $wpdb;
		/**
		 * Q & A unanswered showing wrong number when login as
		 * instructor as it was count unanswered question from all courses
		 * from now on it will check if tutor instructor and count
		 * from instructor's course
		 *
		 * @since 1.9.0
		 */
		$user_id     = get_current_user_id();
		$course_type = tutor()->course_post_type;

		$in_question_id_query = '';
		/**
		 * Get only assinged  courses questions if current user is a
		 */
		if ( ! current_user_can( 'administrator' ) && current_user_can( tutor()->instructor_role ) ) {

			$get_course_ids = $wpdb->get_col(
				$wpdb->prepare(
					"SELECT ID
				FROM 	{$wpdb->posts}
				WHERE 	post_author = %d
						AND post_type = %s
						AND post_status = %s
				",
					$user_id,
					$course_type,
					'publish'
				)
			);

			$get_assigned_courses_ids = $wpdb->get_col(
				$wpdb->prepare(
					"SELECT meta_value
				FROM	{$wpdb->usermeta}
				WHERE 	meta_key = %s
						AND user_id = %d
				",
					'_tutor_instructor_course_id',
					$user_id
				)
			);

			$my_course_ids = array_unique( array_merge( $get_course_ids, $get_assigned_courses_ids ) );

			if ( $this->count( $my_course_ids ) ) {
				$implode_ids          = implode( ',', $my_course_ids );
				$in_question_id_query = " AND {$wpdb->comments}.comment_post_ID IN($implode_ids) ";
			}
		}

		$count = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT COUNT({$wpdb->comments}.comment_ID)
			FROM    {$wpdb->comments}
					INNER JOIN {$wpdb->posts}
							ON {$wpdb->comments}.comment_post_ID = {$wpdb->posts}.ID
					INNER JOIN {$wpdb->users}
							ON {$wpdb->comments}.user_id = {$wpdb->users}.ID
			WHERE   {$wpdb->comments}.comment_type = %s
					AND {$wpdb->comments}.comment_approved = %s
					AND {$wpdb->comments}.comment_parent = 0 {$in_question_id_query};
			",
				'tutor_q_and_a',
				'waiting_for_answer'
			)
		);
		return (int) $count;
	}

	/**
	 * Return all of announcements for a course
	 *
	 * @since 1.0.0
	 *
	 * @param int $course_id course id.
	 *
	 * @return array|null|object
	 */
	public function get_announcements( $course_id = 0 ) {
		$course_id = $this->get_post_id( $course_id );
		global $wpdb;
		$query = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT 	{$wpdb->posts}.ID,
						post_author,
						post_date,
						post_date_gmt,
						post_content,
						post_title,
						display_name
			FROM  		{$wpdb->posts}
						INNER JOIN {$wpdb->users}
								ON post_author = {$wpdb->users}.ID
			WHERE   	post_type = %s
						AND post_parent = %d
			ORDER BY 	{$wpdb->posts}.ID DESC;
			",
				'tutor_announcements',
				$course_id
			)
		);
		return $query;
	}

	/**
	 * Announcement content
	 *
	 * @since 1.0.0
	 *
	 * @param string $content content.
	 *
	 * @return mixed
	 */
	public function announcement_content( $content = '' ) {
		$search = array( '{user_display_name}' );

		$user_display_name = 'User';
		if ( is_user_logged_in() ) {
			$user              = wp_get_current_user();
			$user_display_name = $user->display_name;
		}

		$replace = array( $user_display_name );

		return str_replace( $search, $replace, $content );
	}

	/**
	 * Get the quiz option from meta
	 *
	 * @since 1.0.0
	 *
	 * @param int    $post_id post id.
	 * @param string $option_key option key.
	 * @param bool   $default default.
	 *
	 * @return array|bool|mixed
	 */
	public function get_quiz_option( $post_id = 0, $option_key = '', $default = false ) {
		$post_id         = $this->get_post_id( $post_id );
		$get_option_meta = maybe_unserialize( get_post_meta( $post_id, 'tutor_quiz_option', true ) );

		if ( ! $option_key && ! empty( $get_option_meta ) ) {
			return $get_option_meta;
		}

		$value = $this->avalue_dot( $option_key, $get_option_meta );
		if ( $value > 0 || false !== $value ) {
			return $value;
		}

		return $default;
	}

	/**
	 * Get the questions by quiz ID
	 *
	 * @since 1.0.0
	 *
	 * @param int $quiz_id quiz id.
	 *
	 * @return array|bool|null|object
	 */
	public function get_questions_by_quiz( $quiz_id = 0 ) {
		$quiz_id = $this->get_post_id( $quiz_id );
		global $wpdb;

		$questions = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT *
			FROM	{$wpdb->prefix}tutor_quiz_questions
			WHERE	quiz_id = %d
			ORDER BY question_order ASC
			",
				$quiz_id
			)
		);

		foreach ( $questions as $question ) {
			$question->question_title       = stripslashes( $question->question_title );
			$question->question_description = stripslashes( $question->question_description );
		}

		return ( is_array( $questions ) && count( $questions ) ) ? $questions : false;
	}

	/**
	 * Get all question types
	 *
	 * @since 1.0.0
	 *
	 * @param mixed $type type.
	 *
	 * @return array|mixed
	 */
	public function get_question_types( $type = null ) {
		$types = array(
			'true_false'        => array(
				'name'   => __( 'True/False', 'tutor' ),
				'icon'   => '<span class="tooltip-btn" ><i class="tutor-quiz-type-icon tutor-quiz-type-boolean tutor-icon-circle-half"></i></span>',
				'is_pro' => false,
			),
			'single_choice'     => array(
				'name'   => __( 'Single Choice', 'tutor' ),
				'icon'   => '<span class="tooltip-btn"><i class="tutor-quiz-type-icon tutor-quiz-type-single-choice tutor-icon-mark"></i></span>',
				'is_pro' => false,
			),
			'multiple_choice'   => array(
				'name'   => __( 'Multiple Choice', 'tutor' ),
				'icon'   => '<span class="tooltip-btn"><i class="tutor-quiz-type-icon tutor-quiz-type-multiple-choices tutor-icon-double-mark"></i></span>',
				'is_pro' => false,
			),
			'open_ended'        => array(
				'name'   => __( 'Open Ended', 'tutor' ),
				'icon'   => '<span class="tooltip-btn"><i class="tutor-quiz-type-icon tutor-quiz-type-open-ended tutor-icon-text-width"></i></span>',
				'is_pro' => false,
			),
			'fill_in_the_blank' => array(
				'name'   => __( 'Fill In The Blanks', 'tutor' ),
				'icon'   => '<span class="tooltip-btn" ><i class="tutor-quiz-type-icon tutor-quiz-type-fill-blanks tutor-icon-hourglass"></i></span>',
				'is_pro' => false,
			),
			'short_answer'      => array(
				'name'   => __( 'Short Answer', 'tutor' ),
				'icon'   => '<span class="tooltip-btn"><i class="tutor-quiz-type-icon tutor-quiz-type-short-answer tutor-icon-minimize"></i></span>',
				'is_pro' => true,
			),
			'matching'          => array(
				'name'   => __( 'Matching', 'tutor' ),
				'icon'   => '<span class="tooltip-btn"><i class="tutor-quiz-type-icon tutor-quiz-type-matching tutor-icon-arrow-right-left"></i></span>',
				'is_pro' => true,
			),
			'image_matching'    => array(
				'name'   => __( 'Image Matching', 'tutor' ),
				'icon'   => '<span class="tooltip-btn"><i class="tutor-quiz-type-icon tutor-quiz-type-image-matching tutor-icon-images"></i></span>',
				'is_pro' => true,
			),
			'image_answering'   => array(
				'name'   => __( 'Image Answering', 'tutor' ),
				'icon'   => '<span class="tooltip-btn"><i class="tutor-quiz-type-icon tutor-quiz-type-image-answering tutor-icon-camera"></i></span>',
				'is_pro' => true,
			),
			'ordering'          => array(
				'name'   => __( 'Ordering', 'tutor' ),
				'icon'   => '<span class="tooltip-btn"><i class="tutor-quiz-type-icon tutor-quiz-type-ordering tutor-icon-ordering-z-a"></i></span>',
				'is_pro' => true,
			),
		);

		if ( isset( $types[ $type ] ) ) {
			return $types[ $type ];
		}

		return $types;
	}

	/**
	 * Get attached quiz.
	 *
	 * @since 1.0.0
	 *
	 * @param int $post_id post id.
	 *
	 * @return array|bool|null|object
	 */
	public function get_attached_quiz( $post_id = 0 ) {
		global $wpdb;

		$post_id = $this->get_post_id( $post_id );

		$questions = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT ID,
					post_content,
					post_title,
					post_parent
			FROM 	{$wpdb->posts}
			WHERE 	post_type = %s
					AND post_status = %s
					AND post_parent = %d;
			",
				'tutor_quiz',
				'publish',
				$post_id
			)
		);

		if ( is_array( $questions ) && count( $questions ) ) {
			return $questions;
		}

		return false;
	}

	/**
	 * Total questions for student by quiz.
	 *
	 * @since 1.0.0
	 *
	 * @param int $quiz_id quiz id.
	 *
	 * @return int
	 */
	public function total_questions_for_student_by_quiz( $quiz_id ) {
		$quiz_id = $this->get_post_id( $quiz_id );
		global $wpdb;

		$max_questions_count = (int) $this->get_quiz_option( get_the_ID(), 'max_questions_for_answer' );
		$total_question      = (int) $wpdb->get_var(
			$wpdb->prepare(
				"SELECT count(question_id)
			FROM	{$wpdb->tutor_quiz_questions}
			WHERE 	quiz_id = %d;
			",
				$quiz_id
			)
		);

		return min( $max_questions_count, $total_question );
	}

	/**
	 * Determine if there is any started quiz exists.
	 *
	 * @since 1.0.0
	 *
	 * @param int $quiz_id quiz id.
	 *
	 * @return array|null|object|void
	 */
	public function is_started_quiz( $quiz_id = 0 ) {
		global $wpdb;

		$quiz_id = $this->get_post_id( $quiz_id );
		$user_id = get_current_user_id();

		$cache_key  = "tutor_is_started_quiz_{$user_id}_{$quiz_id}";
		$is_started = TutorCache::get( $cache_key );

		if ( false === $is_started ) {
			$is_started = $wpdb->get_row(
				$wpdb->prepare(
					"SELECT *
				FROM 	{$wpdb->prefix}tutor_quiz_attempts
				WHERE 	user_id =  %d
						AND quiz_id = %d
						AND attempt_status = %s;
				",
					$user_id,
					$quiz_id,
					'attempt_started'
				)
			);
			TutorCache::set( $cache_key, $is_started );
		}

		return $is_started;
	}

	/**
	 * Method for get the total amount of question for a quiz
	 * Student will answer this amount of question, one quiz have many question
	 * but student will answer a specific amount of questions
	 *
	 * @since 1.0.0
	 *
	 * @param int $quiz_id quiz id.
	 *
	 * @return int
	 */
	public function max_questions_for_take_quiz( $quiz_id ) {
		$quiz_id = $this->get_post_id( $quiz_id );
		global $wpdb;

		$max_questions = (int) $wpdb->get_var(
			$wpdb->prepare(
				"SELECT count(question_id)
			FROM 	{$wpdb->prefix}tutor_quiz_questions
			WHERE 	quiz_id = %d;
			",
				$quiz_id
			)
		);

		$max_mentioned = (int) $this->get_quiz_option( $quiz_id, 'max_questions_for_answer', 10 );

		if ( $max_mentioned < $max_questions ) {
			return $max_mentioned;
		}

		return $max_questions;
	}

	/**
	 * Get single quiz attempt
	 *
	 * @since 1.0.0
	 *
	 * @param int $attempt_id attempt id.
	 *
	 * @return array|bool|null|object|void
	 */
	public function get_attempt( $attempt_id = 0 ) {
		global $wpdb;
		if ( ! $attempt_id ) {
			return false;
		}

		$attempt = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT *
			FROM 	{$wpdb->prefix}tutor_quiz_attempts
			WHERE 	attempt_id = %d;
			",
				$attempt_id
			)
		);

		return $attempt;
	}

	/**
	 * Get unserialize attempt info
	 *
	 * @since 1.0.0
	 *
	 * @param mixed $attempt_info attempt info.
	 *
	 * @return mixed
	 */
	public function quiz_attempt_info( $attempt_info ) {
		return maybe_unserialize( $attempt_info );
	}

	/**
	 * Update attempt for various action
	 *
	 * @since 1.0.0
	 *
	 * @param int   $quiz_attempt_id    quiz attempt id.
	 * @param array $attempt_info       attempt info.
	 *
	 * @return bool|int
	 */
	public function quiz_update_attempt_info( $quiz_attempt_id, $attempt_info = array() ) {
		$answers             = $this->avalue_dot( 'answers', $attempt_info );
		$total_marks         = array_sum( wp_list_pluck( $answers, 'question_mark' ) );
		$earned_marks        = $this->avalue_dot( 'marks_earned', $attempt_info );
		$earned_mark_percent = $earned_marks > 0 ? ( number_format( ( $earned_marks * 100 ) / $total_marks ) ) : 0;
		update_comment_meta( $quiz_attempt_id, 'earned_mark_percent', $earned_mark_percent );

		return update_comment_meta( $quiz_attempt_id, 'quiz_attempt_info', $attempt_info );
	}

	/**
	 * Get random question by quiz id
	 *
	 * @since 1.0.0
	 *
	 * @param int $quiz_id quiz id.
	 *
	 * @return array|null|object
	 */
	public function get_random_question_by_quiz( $quiz_id = 0 ) {
		global $wpdb;

		$quiz_id    = $this->get_post_id( $quiz_id );
		$is_attempt = $this->is_started_quiz( $quiz_id );

		$temp_sql  = " AND question_type = 'matching' ";
		$questions = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT *
			FROM 	{$wpdb->prefix}tutor_quiz_questions
			WHERE 	quiz_id = %d
					{$temp_sql}
			ORDER BY RAND()
			LIMIT 0, 1
			",
				$quiz_id
			)
		);

		return $questions;
	}

	/**
	 * Get random questions by quiz
	 *
	 * @since 1.0.0
	 *
	 * @param int $quiz_id quiz id.
	 *
	 * @return array|null|object
	 */
	public function get_random_questions_by_quiz( $quiz_id = 0 ) {
		global $wpdb;

		$quiz_id         = $this->get_post_id( $quiz_id );
		$attempt         = $this->is_started_quiz( $quiz_id );
		$total_questions = (int) $attempt->total_questions;
		if ( ! $attempt ) {
			return false;
		}

		$questions_order = $this->get_quiz_option( get_the_ID(), 'questions_order', 'rand' );

		$order_by = '';
		if ( 'rand' === $questions_order ) {
			$order_by = 'ORDER BY RAND()';
		} elseif ( 'asc' === $questions_order ) {
			$order_by = 'ORDER BY question_id ASC';
		} elseif ( 'desc' === $questions_order ) {
			$order_by = 'ORDER BY question_id DESC';
		} elseif ( 'sorting' === $questions_order ) {
			$order_by = 'ORDER BY question_order ASC';
		}

		$limit = '';
		if ( $total_questions ) {
			$limit = "LIMIT {$total_questions} ";
		}

		$questions = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT *
			FROM 	{$wpdb->prefix}tutor_quiz_questions
			WHERE 	quiz_id = %d
			{$order_by}
			{$limit}
			",
				$quiz_id
			)
		);

		return $questions;
	}

	/**
	 * Get attempts by an user
	 *
	 * @since 1.0.0
	 *
	 * @param int $user_id user id.
	 *
	 * @return array|bool|null|object
	 */
	public function get_all_quiz_attempts_by_user( $user_id = 0 ) {
		global $wpdb;

		$user_id  = $this->get_user_id( $user_id );
		$attempts = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT 	*
			FROM 		{$wpdb->prefix}tutor_quiz_attempts
			WHERE 		user_id = %d
			ORDER BY 	attempt_id DESC
			",
				$user_id
			)
		);

		if ( is_array( $attempts ) && count( $attempts ) ) {
			return $attempts;
		}

		return false;
	}

	/**
	 * Get the users / students / course levels
	 *
	 * @since 1.0.0
	 *
	 * @param mixed $level level.
	 *
	 * @return mixed
	 */
	public function course_levels( $level = null ) {
		$levels = apply_filters(
			'tutor_course_level',
			array(
				'all_levels'   => __( 'All Levels', 'tutor' ),
				'beginner'     => __( 'Beginner', 'tutor' ),
				'intermediate' => __( 'Intermediate', 'tutor' ),
				'expert'       => __( 'Expert', 'tutor' ),
			)
		);

		if ( $level ) {
			if ( isset( $levels[ $level ] ) ) {
				return $levels[ $level ];
			} else {
				return '';
			}
		}

		return $levels;
	}

	/**
	 * Generate cache busting URL
	 *
	 * @since 2.8.0
	 *
	 * @param string $url url.
	 *
	 * @return string
	 */
	public function get_nocache_url( $url ) {
		return add_query_arg( 'nocache', time(), $url );
	}

	/**
	 * Student registration form
	 *
	 * @since 1.0.0
	 *
	 * @return bool|false|string
	 */
	public function student_register_url() {
		$student_register_page = (int) $this->get_option( 'student_register_page' );

		if ( $student_register_page ) {
			return apply_filters( 'tutor_student_register_url', get_the_permalink( $student_register_page ) );
		}

		return false;
	}

	/**
	 * Instructor registration form
	 *
	 * @since v.1.2.13
	 *
	 * @return bool|false|string
	 */
	public function instructor_register_url() {
		$instructor_register_page = (int) $this->get_option( 'instructor_register_page' );

		if ( $instructor_register_page ) {
			return apply_filters( 'tutor_instructor_register_url', get_the_permalink( $instructor_register_page ) );
		}

		return false;
	}

	/**
	 * Get frontend dashboard URL
	 *
	 * @since 1.0.0
	 *
	 * @param string $sub_url sub url.
	 *
	 * @return false|string
	 */
	public function tutor_dashboard_url( $sub_url = '' ) {
		$page_id = (int) $this->get_option( 'tutor_dashboard_page_id' );
		$page_id = apply_filters( 'tutor_dashboard_page_id', $page_id );
		return apply_filters( 'tutor_dashboard_url', trailingslashit( get_the_permalink( $page_id ) ) . $sub_url, $sub_url );
	}

	/**
	 * Get the tutor dashboard page ID
	 *
	 * @since 1.0.0
	 *
	 * @return int
	 */
	public function dashboard_page_id() {
		$page_id = (int) $this->get_option( 'tutor_dashboard_page_id' );
		$page_id = apply_filters( 'tutor_dashboard_page_id', $page_id );
		return $page_id;
	}

	/**
	 * Check is wishlisted.
	 *
	 * @since 1.0.0
	 *
	 * @param int $course_id course id.
	 * @param int $user_id user id.
	 *
	 * @return bool
	 */
	public function is_wishlisted( $course_id = 0, $user_id = 0 ) {
		$course_id = $this->get_post_id( $course_id );
		$user_id   = $this->get_user_id( $user_id );
		if ( ! $user_id ) {
			return false;
		}

		global $wpdb;
		$if_added_to_list = (bool) $wpdb->get_row(
			$wpdb->prepare(
				"SELECT *
			FROM	{$wpdb->usermeta}
			WHERE 	user_id = %d
					AND meta_key = '_tutor_course_wishlist'
					AND meta_value = %d;
			",
				$user_id,
				$course_id
			)
		);

		return $if_added_to_list;
	}

	/**
	 * Get the wish lists by an user
	 *
	 * @since 1.0.0
	 *
	 * @param int $user_id user id.
	 * @param int $offset offset.
	 * @param int $limit limit.
	 *
	 * @return array|null|object
	 */
	public function get_wishlist( $user_id = 0, int $offset = 0, int $limit = PHP_INT_MAX ) {
		global $wpdb;

		$user_id          = $this->get_user_id( $user_id );
		$post_types       = apply_filters( 'tutor_wishlist_post_types', array( tutor()->course_post_type ) );
		$post_type_clause = QueryHelper::prepare_in_clause( $post_types );

		$pageposts = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT $wpdb->posts.*
	    	FROM 	$wpdb->posts
	    			LEFT JOIN $wpdb->usermeta
						   ON ($wpdb->posts.ID = $wpdb->usermeta.meta_value)
	    	WHERE 	post_type IN ({$post_type_clause})
					AND post_status = %s
					AND $wpdb->usermeta.meta_key = %s
					AND $wpdb->usermeta.user_id = %d
	    	ORDER BY $wpdb->usermeta.umeta_id DESC LIMIT %d, %d;
			",
				'publish',
				'_tutor_course_wishlist',
				$user_id,
				$offset,
				$limit
			),
			OBJECT
		);

		return $pageposts;
	}

	/**
	 * Getting popular courses
	 *
	 * @since 1.0.0
	 *
	 * @param int   $limit limit.
	 * @param mixed $user_id user id.
	 *
	 * @return array|null|object
	 */
	public function most_popular_courses( $limit = 10, $user_id = '' ) {
		global $wpdb;
		$limit   = sanitize_text_field( $limit );
		$user_id = sanitize_text_field( $user_id );

		$author_query = '';
		if ( '' !== $user_id ) {
			$author_query = "AND course.post_author = $user_id";
		}

		$courses = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT COUNT(enrolled.ID) AS total_enrolled,
					enrolled.post_parent as course_id,
					course.*
			FROM 	{$wpdb->posts} enrolled
					INNER JOIN {$wpdb->posts} course
							ON enrolled.post_parent = course.ID
			WHERE 	enrolled.post_type = %s
					AND enrolled.post_status = %s
					AND course.post_type = %s
					{$author_query}
			GROUP BY course_id
			ORDER BY total_enrolled DESC
			LIMIT 0, %d;
			",
				'tutor_enrolled',
				'completed',
				tutor()->course_post_type,
				$limit
			)
		);

		return $courses;
	}

	/**
	 * Get most rated courses lists
	 *
	 * @since 1.0.0
	 *
	 * @param int $limit limit.
	 *
	 * @return array|bool|null|object
	 */
	public function most_rated_courses( $limit = 10 ) {
		global $wpdb;

		$result = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT 	COUNT(comment_ID) AS total_rating,
						comment_ID,
						comment_post_ID,
						course.*
			FROM 		{$wpdb->comments}
						INNER JOIN {$wpdb->posts} course
								ON comment_post_ID = course.ID
			WHERE 		{$wpdb->comments}.comment_type = %s
						AND {$wpdb->comments}.comment_approved = %s
			GROUP BY 	comment_post_ID
			ORDER BY 	total_rating DESC
			LIMIT 		0, %d
			;",
				'tutor_course_rating',
				'approved',
				$limit
			)
		);

		if ( is_array( $result ) && count( $result ) ) {
			return $result;
		}

		return false;
	}

	/**
	 * Get Addon config
	 *
	 * @since 1.0.0
	 * @since 3.0.0 make addon_field value based on param.
	 *
	 * @param mixed $addon_field addon field.
	 *
	 * @return mixed
	 */
	public function get_addon_config( $addon_field = null ) {
		if ( ! $addon_field ) {
			return false;
		}

		$addon_field = ( strpos( $addon_field, 'tutor-pro/addons/' ) === 0 )
						? $addon_field
						: "tutor-pro/addons/{$addon_field}/{$addon_field}.php";

		$addons_config = maybe_unserialize( get_option( 'tutor_addons_config' ) );

		if ( isset( $addons_config[ $addon_field ] ) ) {
			return $addons_config[ $addon_field ];
		}

		return false;
	}

	/**
	 * Get the IP from visitor
	 *
	 * @since 1.0.0
	 *
	 * @return array|false|string
	 */
	public function get_ip() {
		$ipaddress = '';
		if ( getenv( 'HTTP_CLIENT_IP' ) ) {
			$ipaddress = getenv( 'HTTP_CLIENT_IP' );
		} elseif ( getenv( 'HTTP_X_FORWARDED_FOR' ) ) {
			$ipaddress = getenv( 'HTTP_X_FORWARDED_FOR' );
		} elseif ( getenv( 'HTTP_X_FORWARDED' ) ) {
			$ipaddress = getenv( 'HTTP_X_FORWARDED' );
		} elseif ( getenv( 'HTTP_FORWARDED_FOR' ) ) {
			$ipaddress = getenv( 'HTTP_FORWARDED_FOR' );
		} elseif ( getenv( 'HTTP_FORWARDED' ) ) {
			$ipaddress = getenv( 'HTTP_FORWARDED' );
		} elseif ( getenv( 'REMOTE_ADDR' ) ) {
			$ipaddress = getenv( 'REMOTE_ADDR' );
		} else {
			$ipaddress = 'UNKNOWN';
		}
		return $ipaddress;
	}

	/**
	 * Get the social icons
	 *
	 * @since 1.0.4
	 *
	 * @return array $array
	 */
	public function tutor_social_share_icons() {
		$icons = array(
			'facebook' => array(
				'share_class' => 's_facebook',
				'icon_html'   => '<i class="tutor-valign-middle tutor-icon-brand-facebook"></i>',
				'text'        => '',
				'color'       => '#3877EA',
			),
			'twitter'  => array(
				'share_class' => 's_twitter',
				'icon_html'   => '<i class="tutor-valign-middle tutor-icon-brand-x-twitter"></i>',
				'text'        => '',
				'color'       => '#000000',
			),
			'linkedin' => array(
				'share_class' => 's_linkedin',
				'icon_html'   => '<i class="tutor-valign-middle tutor-icon-brand-linkedin"></i>',
				'text'        => '',
				'color'       => '#3967B6',
			),
		);

		return apply_filters( 'tutor_social_share_icons', $icons );
	}

	/**
	 * Get the user social icons
	 *
	 * @since 1.3.7
	 *
	 * @return array $array
	 */
	public function tutor_user_social_icons() {
		$icons = array(
			'_tutor_profile_facebook' => array(
				'label'        => __( 'Facebook', 'tutor' ),
				'placeholder'  => 'https://facebook.com/username',
				'icon_classes' => 'tutor-icon-brand-facebook',
			),
			'_tutor_profile_twitter'  => array(
				'label'        => __( 'Twitter', 'tutor' ),
				'placeholder'  => 'https://twitter.com/username',
				'icon_classes' => 'tutor-icon-brand-twitter',
			),
			'_tutor_profile_linkedin' => array(
				'label'        => __( 'Linkedin', 'tutor' ),
				'placeholder'  => 'https://linkedin.com/username',
				'icon_classes' => 'tutor-icon-brand-linkedin',
			),
			'_tutor_profile_website'  => array(
				'label'        => __( 'Website', 'tutor' ),
				'placeholder'  => 'https://example.com/',
				'icon_classes' => 'tutor-icon-earth',
			),
			'_tutor_profile_github'   => array(
				'label'        => __( 'Github', 'tutor' ),
				'placeholder'  => 'https://github.com/username',
				'icon_classes' => 'tutor-icon-brand-github',
			),
		);

		return apply_filters( 'tutor_user_social_icons', $icons );
	}

	/**
	 * Count method with check is_array
	 *
	 * @since 1.0.4
	 *
	 * @param array $array array.
	 *
	 * @return bool
	 */
	public function count( $array = array() ) {
		if ( is_array( $array ) && count( $array ) ) {
			return count( $array );
		}

		return false;
	}

	/**
	 * Get all screen ids
	 *
	 * @since 1.1.2
	 *
	 * @return array
	 */
	public function tutor_get_screen_ids() {
		$screen_ids = array(
			'edit-course',
			'course',
			'edit-course-category',
			'edit-course-tag',
			'tutor-lms_page_tutor-students',
			'tutor-lms_page_tutor-instructors',
			'tutor-lms_page_question_answer',
			'tutor-lms_page_tutor_quiz_attempts',
			'tutor-lms_page_tutor-addons',
			'tutor-lms_page_tutor-status',
			'tutor-lms_page_tutor_report',
			'tutor-lms_page_tutor_settings',
			'tutor-lms_page_tutor_emails',
		);

		return apply_filters( 'tutor_get_screen_ids', $screen_ids );
	}

	/**
	 * Get earning transaction completed status
	 *
	 * @since 1.1.2
	 *
	 * @return mixed
	 */
	public function get_earnings_completed_statuses() {
		return apply_filters(
			'tutor_get_earnings_completed_statuses',
			array(
				'wc-completed',
				'completed',
				'complete',
			)
		);
	}

	/**
	 * Change earning status.
	 *
	 * @since 2.2.0
	 *
	 * @param int    $order_id order id.
	 * @param string $status status.
	 *
	 * @return bool
	 */
	public static function change_earning_status( $order_id, $status ) {
		$is_updated = false;

		global $wpdb;
		$is_earning_data = (int) $wpdb->get_var(
			$wpdb->prepare(
				"SELECT COUNT(earning_id)
			FROM {$wpdb->prefix}tutor_earnings
			WHERE order_id = %d  ",
				$order_id
			)
		);

		if ( $is_earning_data ) {
			$update_earning_status = $wpdb->update(
				$wpdb->prefix . 'tutor_earnings',
				array( 'order_status' => $status ),
				array( 'order_id' => $order_id )
			);

			$is_updated = true;
			do_action( 'tutor_after_earning_status_change', $update_earning_status );
		}

		return $is_updated;
	}

	/**
	 * Get all time earning sum for an instructor with all commission
	 *
	 * @since 1.1.2
	 *
	 * @param int   $user_id user id.
	 * @param array $date_filter date filter.
	 *
	 * @return array|null|object
	 */
	public function get_earning_sum( $user_id = 0, $date_filter = array() ) {
		global $wpdb;

		$user_id    = $this->get_user_id( $user_id );
		$date_query = '';

		if ( $this->count( $date_filter ) ) {
			extract( $date_filter );

			if ( ! empty( $dataFor ) ) {
				if ( $dataFor === 'yearly' ) {
					if ( empty( $year ) ) {
						$year = date( 'Y' );
					}
					$date_query = "AND YEAR(created_at) = {$year} ";
				}
			} else {
				$date_query = " AND (created_at BETWEEN '{$start_date}' AND '{$end_date}') ";
			}
		}

		$complete_status = $this->get_earnings_completed_statuses();
		$complete_status = "'" . implode( "','", $complete_status ) . "'";

		$earning_sum = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT SUM(course_price_total) AS course_price_total,
                    SUM(course_price_grand_total) AS course_price_grand_total,
                    SUM(instructor_amount) AS instructor_amount,
                    (SELECT SUM(amount)
					FROM 	{$wpdb->prefix}tutor_withdraws
					WHERE 	user_id = {$user_id}
							AND status != 'rejected'
					) AS withdraws_amount,
                    SUM(admin_amount) AS admin_amount,
                    SUM(deduct_fees_amount)  AS deduct_fees_amount
            FROM 	{$wpdb->prefix}tutor_earnings
            WHERE 	user_id = %d
					AND order_status IN({$complete_status})
					{$date_query}
			",
				$user_id
			)
		);

		if ( $earning_sum->course_price_total ) {
			$earning_sum->balance = $earning_sum->instructor_amount - $earning_sum->withdraws_amount;
		} else {
			$earning_sum = (object) array(
				'course_price_total'       => 0,
				'course_price_grand_total' => 0,
				'instructor_amount'        => 0,
				'withdraws_amount'         => 0,
				'balance'                  => 0,
				'admin_amount'             => 0,
				'deduct_fees_amount'       => 0,
			);
		}

		return $earning_sum;
	}

	/**
	 * Get earning statements
	 *
	 * @since 1.1.2
	 *
	 * @param int   $user_id user id.
	 * @param array $filter_data  filter data.
	 *
	 * @return array|null|object
	 */
	public function get_earning_statements( $user_id = 0, $filter_data = array() ) {
		global $wpdb;

		$user_sql = '';
		if ( $user_id ) {
			$user_sql = " AND user_id='{$user_id}' ";
		}

		$date_query       = '';
		$query_by_status  = '';
		$pagination_query = '';

		/**
		 * Query by Date Filter
		 */
		if ( $this->count( $filter_data ) ) {
			extract( $filter_data );

			if ( ! empty( $dataFor ) ) {
				if ( $dataFor === 'yearly' ) {
					if ( empty( $year ) ) {
						$year = date( 'Y' );
					}
					$date_query = "AND YEAR(created_at) = {$year} ";
				}
			} else {
				$date_query = " AND (created_at BETWEEN '{$start_date}' AND '{$end_date}') ";
			}

			/**
			 * Query by order status related to this earning transaction
			 */
			if ( ! empty( $statuses ) ) {
				if ( $this->count( $statuses ) ) {
					$status          = "'" . implode( "','", $statuses ) . "'";
					$query_by_status = "AND order_status IN({$status})";
				} elseif ( $statuses === 'completed' ) {
					$get_earnings_completed_statuses = $this->get_earnings_completed_statuses();
					if ( $this->count( $get_earnings_completed_statuses ) ) {
						$status          = "'" . implode( "','", $get_earnings_completed_statuses ) . "'";
						$query_by_status = "AND order_status IN({$status})";
					}
				}
			}

			if ( ! empty( $per_page ) ) {
				$offset           = (int) ! empty( $offset ) ? $offset : 0;
				$pagination_query = " LIMIT {$offset}, {$per_page}  ";
			}
		}

		/**
		 * Delete duplicated earning rows that were created due to not checking if already added while creating new.
		 * New entries will check before insert.
		 *
		 * @since 1.9.7
		 */
		if ( ! get_option( 'tutor_duplicated_earning_deleted', false ) ) {

			// Get the duplicated order IDs.
			$del_rows  = array();
			$order_ids = $wpdb->get_col(
				"SELECT order_id
				FROM (SELECT order_id, COUNT(order_id) AS cnt
						FROM {$wpdb->prefix}tutor_earnings
						GROUP BY order_id) t
				WHERE cnt>1"
			);

			if ( is_array( $order_ids ) && count( $order_ids ) ) {
				$order_ids_string = implode( ',', $order_ids );
				$earnings         = $wpdb->get_results(
					"SELECT earning_id, course_id FROM {$wpdb->prefix}tutor_earnings
					WHERE order_id IN ({$order_ids_string})
					ORDER BY earning_id ASC"
				);

				$excluded_first = array();
				foreach ( $earnings as $earning ) {
					if ( ! in_array( $earning->course_id, $excluded_first ) ) {
						// Exclude first course ID from deletion.
						$excluded_first[] = $earning->course_id;
						continue;
					}

					$del_rows[] = $earning->earning_id;
				}
			}

			if ( count( $del_rows ) ) {
				$ids = implode( ',', $del_rows );
				$wpdb->query( "DELETE FROM {$wpdb->prefix}tutor_earnings WHERE earning_id IN ({$ids})" );
			}

			update_option( 'tutor_duplicated_earning_deleted', true );
		}

		$query = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT 	earning_tbl.*,
						course.post_title AS course_title
			FROM 		{$wpdb->prefix}tutor_earnings earning_tbl
						LEFT JOIN {$wpdb->posts} course
						   	   ON earning_tbl.course_id = course.ID
			WHERE 		1 = %d {$user_sql} {$date_query} {$query_by_status}
			ORDER BY 	created_at DESC {$pagination_query}
			",
				1
			)
		);

		$query_count = (int) $wpdb->get_var(
			$wpdb->prepare(
				"SELECT 	COUNT(earning_tbl.earning_id)
			FROM 		{$wpdb->prefix}tutor_earnings earning_tbl
            WHERE 		1 = %d {$user_sql} {$date_query} {$query_by_status}
			ORDER BY 	created_at DESC
			",
				1
			)
		);

		return (object) array(
			'count'   => $query_count,
			'results' => $query,
		);
	}

	/**
	 * Get the price format
	 *
	 * @since 1.1.2
	 *
	 * @param int $price price.
	 *
	 * @return int|string
	 */
	public function tutor_price( $price = 0 ) {
		if ( tutor_utils()->is_monetize_by_tutor() ) {
			return tutor_get_formatted_price( $price );
		} elseif ( function_exists( 'wc_price' ) ) {
			return wc_price( $price );
		} elseif ( function_exists( 'edd_currency_filter' ) ) {
			return edd_currency_filter( edd_format_amount( $price ) );
		} else {
			return number_format_i18n( $price );
		}
	}

	/**
	 * Get currency symbol from activated plugin, WC,EDD
	 *
	 * @since  1.3.4
	 *
	 * @return mixed
	 */
	public function currency_symbol() {
		$enable_tutor_edd = $this->get_option( 'enable_tutor_edd' );
		$monetize_by      = $this->get_option( 'monetize_by' );

		$symbol = '&#36;';
		if ( $enable_tutor_edd && function_exists( 'edd_currency_symbol' ) ) {
			$symbol = edd_currency_symbol();
		}

		if ( 'wc' === $monetize_by && function_exists( 'get_woocommerce_currency_symbol' ) ) {
			$symbol = get_woocommerce_currency_symbol();
		}

		return apply_filters( 'get_tutor_currency_symbol', $symbol );
	}

	/**
	 * Add Instructor role to any user by user ID
	 *
	 * @since 1.0.0
	 *
	 * @param int $instructor_id instructor id.
	 *
	 * @return void
	 */
	public function add_instructor_role( $instructor_id = 0 ) {
		if ( ! $instructor_id ) {
			return;
		}
		do_action( 'tutor_before_approved_instructor', $instructor_id );

		update_user_meta( $instructor_id, '_is_tutor_instructor', tutor_time() );
		update_user_meta( $instructor_id, '_tutor_instructor_status', 'approved' );
		update_user_meta( $instructor_id, '_tutor_instructor_approved', tutor_time() );

		$instructor = new \WP_User( $instructor_id );
		$instructor->add_role( tutor()->instructor_role );

		do_action( 'tutor_after_approved_instructor', $instructor_id );
	}

	/**
	 * Remove instructor role by instructor id
	 *
	 * @since 1.0.0
	 *
	 * @param int $instructor_id instructor id.
	 *
	 * @return void
	 */
	public function remove_instructor_role( $instructor_id = 0 ) {
		if ( ! $instructor_id ) {
			return;
		}

		do_action( 'tutor_before_blocked_instructor', $instructor_id );
		delete_user_meta( $instructor_id, '_is_tutor_instructor' );
		update_user_meta( $instructor_id, '_tutor_instructor_status', 'blocked' );

		$instructor = new \WP_User( $instructor_id );
		$instructor->remove_role( tutor()->instructor_role );
		do_action( 'tutor_after_blocked_instructor', $instructor_id );
	}

	/**
	 * Get purchase history by customer id
	 *
	 * @since 1.0.0
	 *
	 * @param integer $user_id user id.
	 * @param string  $period period.
	 * @param string  $start_date start date.
	 * @param string  $end_date end date.
	 * @param string  $offset offset.
	 * @param string  $per_page per page.
	 *
	 * @return mixed
	 */
	public function get_orders_by_user_id( $user_id = 0, $period = '', $start_date = '', $end_date = '', $offset = '', $per_page = '' ) {
		global $wpdb;

		$user_id     = $this->get_user_id( $user_id );
		$monetize_by = $this->get_option( 'monetize_by' );

		$post_type = '';
		$user_meta = '';
		$wc_hpos   = false;
		$dt_column = 'post_date';

		if ( 'wc' === $monetize_by ) {
			$post_type = 'shop_order';
			$user_meta = '_customer_user';
			$wc_hpos   = WooCommerce::hpos_enabled();
			$dt_column = $wc_hpos ? 'date_created_gmt' : 'post_date';
		} elseif ( 'edd' === $monetize_by ) {
			$post_type = 'edd_payment';
			$user_meta = '_edd_payment_user_id';
		}

		$period_query = '';

		if ( '' !== $period ) {
			if ( 'today' === $period ) {
				$period_query = ' AND  DATE(' . $dt_column . ') = CURDATE() ';
			} elseif ( 'monthly' === $period ) {
				$period_query = ' AND  MONTH(' . $dt_column . ') = MONTH(CURDATE()) ';
			} else {
				$period_query = ' AND  YEAR(' . $dt_column . ') = YEAR(CURDATE()) ';
			}
		}

		if ( '' !== $start_date && '' !== $end_date ) {
			$period_query = " AND  DATE($dt_column) BETWEEN CAST('$start_date' AS DATE) AND CAST('$end_date' AS DATE) ";
		}

		$offset_limit_query = '';
		if ( '' !== $offset && '' !== $per_page ) {
			$offset_limit_query = "LIMIT $offset, $per_page";
		}

		if ( $wc_hpos ) {
			$orders = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT orders.id AS ID, orders.status AS post_status, orders.date_created_gmt AS post_date, orders.* 
					FROM 	{$wpdb->prefix}wc_orders orders 
					  		INNER JOIN {$wpdb->prefix}wc_orders_meta order_meta 
									ON orders.id = order_meta.order_id
					  				AND order_meta.meta_key = '_is_tutor_order_for_course' 
					WHERE 	orders.type = %s 
					  		AND orders.customer_id = %d 
							{$period_query}
					ORDER BY orders.id DESC
					{$offset_limit_query}",
					$post_type,
					$user_id
				)
			);
		} else {
			$orders = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT {$wpdb->posts}.*
				FROM	{$wpdb->posts}
						INNER JOIN {$wpdb->postmeta} customer
								ON id = customer.post_id
							   AND customer.meta_key = '{$user_meta}'
						INNER JOIN {$wpdb->postmeta} tutor_order
								ON id = tutor_order.post_id
							   AND tutor_order.meta_key = '_is_tutor_order_for_course'
				WHERE	post_type = %s
						AND customer.meta_value = %d
						{$period_query}
				ORDER BY {$wpdb->posts}.id DESC
				{$offset_limit_query}
				",
					$post_type,
					$user_id
				)
			);
		}

		return $orders;
	}

	/**
	 * Get total purchase history by customer id
	 *
	 * @since 1.0.0
	 *
	 * @param int    $user_id user id.
	 * @param string $period period.
	 * @param string $start_date start date.
	 * @param string $end_date end date.
	 *
	 * @return mixed
	 */
	public function get_total_orders_by_user_id( $user_id, $period, $start_date, $end_date ) {
		global $wpdb;

		$user_id     = $this->get_user_id( $user_id );
		$monetize_by = $this->get_option( 'monetize_by' );

		$post_type = '';
		$user_meta = '';

		if ( 'wc' === $monetize_by ) {
			$post_type = 'shop_order';
			$user_meta = '_customer_user';
		} elseif ( 'edd' === $monetize_by ) {
			$post_type = 'edd_payment';
			$user_meta = '_edd_payment_user_id';
		}

		$period_query = '';

		if ( '' !== $period ) {
			if ( 'today' === $period ) {
				$period_query = ' AND  DATE(post_date) = CURDATE() ';
			} elseif ( 'monthly' === $period ) {
				$period_query = ' AND  MONTH(post_date) = MONTH(CURDATE()) ';
			} else {
				$period_query = ' AND  YEAR(post_date) = YEAR(CURDATE()) ';
			}
		}

		if ( '' !== $start_date && '' !== $end_date ) {
			$period_query = " AND  DATE(post_date) BETWEEN CAST('$start_date' AS DATE) AND CAST('$end_date' AS DATE) ";
		}

		$orders = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT {$wpdb->posts}.*
			FROM	{$wpdb->posts}
					INNER JOIN {$wpdb->postmeta} customer
							ON id = customer.post_id
						   AND customer.meta_key = '{$user_meta}'
					INNER JOIN {$wpdb->postmeta} tutor_order
							ON id = tutor_order.post_id
						   AND tutor_order.meta_key = '_is_tutor_order_for_course'
			WHERE	post_type = %s
					AND customer.meta_value = %d
					{$period_query}
			ORDER BY {$wpdb->posts}.id DESC
			",
				$post_type,
				$user_id
			)
		);

		return $orders;
	}

	/**
	 * Export purchased course data
	 *
	 * @since 1.0.0
	 *
	 * @param string $order_id order id.
	 * @param string $purchase_date purchase date.
	 *
	 * @return mixed
	 */
	public function export_purchased_course_data( $order_id = '', $purchase_date = '' ) {
		global $wpdb;

		$purchased_data = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT tutor_order.*, course.post_title
   			FROM {$wpdb->prefix}tutor_earnings AS tutor_order
   			INNER JOIN {$wpdb->posts} AS course
     			ON course.ID = tutor_order.course_id
  			WHERE tutor_order.order_id = %d",
				$order_id
			)
		);

		return $purchased_data;
	}

	/**
	 * Get status contact formatted for order
	 *
	 * @since 1.3.1
	 *
	 * @param mixed $status status.
	 *
	 * @return string
	 */
	public function order_status_context( $status = null ) {
		$status      = str_replace( 'wc-', '', $status );
		$status_name = ucwords( str_replace( '-', ' ', $status ) );

		return '<span class="label-order-status label-status-' . $status . '">' . $status_name . '</span>';
	}

	/**
	 * Get assignment options
	 *
	 * @since 1.3.3
	 *
	 * @param int    $assignment_id assignment id.
	 * @param string $option_key option key.
	 * @param bool   $default default.
	 *
	 * @return array|bool|mixed
	 */
	public function get_assignment_option( $assignment_id = 0, $option_key = '', $default = false ) {
		$assignment_id   = $this->get_post_id( $assignment_id );
		$get_option_meta = maybe_unserialize( get_post_meta( $assignment_id, 'assignment_option', true ) );

		if ( ! $option_key && ! empty( $get_option_meta ) ) {
			return $get_option_meta;
		}

		$value = $this->avalue_dot( $option_key, $get_option_meta );

		if ( false !== $value ) {
			return $value;
		}

		return $default;
	}

	/**
	 * Is running any assignment submitting
	 *
	 * @since 1.3.3
	 *
	 * @param int $assignment_id assignment id.
	 * @param int $user_id user id.
	 *
	 * @return int
	 */
	public function is_assignment_submitting( $assignment_id = 0, $user_id = 0 ) {
		global $wpdb;

		$assignment_id = $this->get_post_id( $assignment_id );
		$user_id       = $this->get_user_id( $user_id );

		$is_running_submit = (int) $wpdb->get_var(
			$wpdb->prepare(
				"SELECT comment_ID
			FROM 	{$wpdb->comments}
			WHERE	comment_type = %s
					AND comment_approved = %s
					AND user_id = %d
					AND comment_post_ID = %d;
			",
				'tutor_assignment',
				'submitting',
				$user_id,
				$assignment_id
			)
		);

		return $is_running_submit;
	}

	/**
	 * Determine if any assignment submitted by user to a assignment.
	 *
	 * @since 1.3.3
	 *
	 * @param int $assignment_id assignment id.
	 * @param int $user_id user id.
	 *
	 * @return array|null|object
	 */
	public function is_assignment_submitted( $assignment_id = 0, $user_id = 0 ) {
		global $wpdb;

		$assignment_id = $this->get_post_id( $assignment_id );
		$user_id       = $this->get_user_id( $user_id );

		$cache_key     = "tutor_is_assignment_submitted_{$user_id}_{$assignment_id}";
		$has_submitted = TutorCache::get( $cache_key );

		if ( false === $has_submitted ) {
			$has_submitted = $wpdb->get_row(
				$wpdb->prepare(
					"SELECT *
				FROM 	{$wpdb->comments}
				WHERE 	comment_type = %s
						AND comment_approved = %s
						AND user_id = %d
						AND comment_post_ID = %d;
				",
					'tutor_assignment',
					'submitted',
					$user_id,
					$assignment_id
				)
			);
			TutorCache::set( $cache_key, $has_submitted );
		}

		return $has_submitted;
	}

	/**
	 * Get assignment submitted info
	 *
	 * @since 1.0.0
	 *
	 * @param integer $assignment_submitted_id assignment submitted id.
	 *
	 * @return mixed
	 */
	public function get_assignment_submit_info( $assignment_submitted_id = 0 ) {
		global $wpdb;

		$assignment_submitted_id = $this->get_post_id( $assignment_submitted_id );

		$submitted_info = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT *
			FROM 	{$wpdb->comments}
			WHERE	comment_ID = %d
					AND comment_type = %s
					AND comment_approved = %s;
			",
				$assignment_submitted_id,
				'tutor_assignment',
				'submitted'
			)
		);

		return $submitted_info;
	}

	/**
	 * It is redundant and will be removed later
	 *
	 * @since 1.0.0
	 * @deprecated 1.9.8
	 *
	 * @return int
	 */
	public function get_total_assignments() {
		global $wpdb;

		$count = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT COUNT(comment_ID)
			FROM 	{$wpdb->comments}
			WHERE	comment_type = %s
					AND comment_approved = %s;
			",
				'tutor_assignment',
				'submitted'
			)
		);

		return (int) $count;
	}

	/**
	 * It is redundant and will be removed later
	 *
	 * @since 1.0.0
	 * @deprecated 1.9.8
	 *
	 * @return mixed
	 */
	public function get_assignments() {
		global $wpdb;

		$results = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT *
			FROM 	{$wpdb->comments}
			WHERE 	comment_type = %s
					AND comment_approved = %s;
			",
				'tutor_assignment',
				'submitted'
			)
		);

		return $results;
	}

	/**
	 * Get all courses id assigned or owned by an instructors
	 *
	 * @since 1.3.3
	 *
	 * @param int $user_id user id.
	 *
	 * @return array
	 */
	public function get_assigned_courses_ids_by_instructors( $user_id = 0 ) {
		global $wpdb;
		$user_id = $this->get_user_id( $user_id );

		$get_assigned_courses_ids = $wpdb->get_col(
			$wpdb->prepare(
				"SELECT meta.meta_value
			FROM {$wpdb->usermeta} meta
				INNER JOIN {$wpdb->posts} course ON meta.meta_value=course.ID
				WHERE meta.meta_key = '_tutor_instructor_course_id'
					AND meta.user_id = %d GROUP BY meta_value",
				$user_id
			)
		);

		return $get_assigned_courses_ids;
	}

	/**
	 * Get course categories in array with child
	 *
	 * @since 1.3.4
	 *
	 * @param int $parent parent.
	 *
	 * @return array
	 */
	public function get_course_categories( $parent = 0, $custom_args = array() ) {
		$default_args = array(
			'taxonomy'   => CourseModel::COURSE_CATEGORY,
			'hide_empty' => false,
		);

		if ( $parent > 0 ) {
			$default_args['parent'] = $parent;
		}

		$default = apply_filters(
			'tutor_get_course_categories_args',
			$default_args
		);

		$args = wp_parse_args( $custom_args, $default );

		$terms = get_terms( $args );

		$children = array();
		foreach ( $terms as $term ) {
			if ( is_object( $term ) ) {
				$term->children             = $this->get_course_categories( $term->term_id );
				$children[ $term->term_id ] = $term;
			}
		}

		return $children;
	}

	/**
	 * Get course tags in array with child
	 *
	 * @since 1.9.3
	 *
	 * @return array
	 */
	public function get_course_tags() {
		$args = apply_filters(
			'tutor_get_course_tags_args',
			array(
				'taxonomy'   => CourseModel::COURSE_TAG,
				'hide_empty' => false,
			)
		);

		$terms = get_terms( $args );

		$children = array();
		foreach ( $terms as $term ) {
			$term->children             = array();
			$children[ $term->term_id ] = $term;
		}

		return $children;
	}

	/**
	 * Get course categories terms in raw array
	 *
	 * @since 1.3.5
	 *
	 * @param int $parent_id parent id.
	 *
	 * @return array|int|\WP_Error
	 */
	public function get_course_categories_term( $parent_id = 0 ) {
		$args = apply_filters(
			'tutor_get_course_categories_terms_args',
			array(
				'taxonomy'   => CourseModel::COURSE_CATEGORY,
				'parent'     => $parent_id,
				'hide_empty' => false,
			)
		);

		$terms = get_terms( $args );

		return $terms;
	}

	/**
	 * Get back url from the request
	 *
	 * @since 1.3.4
	 *
	 * @return mixed
	 */
	public function referer() {
		$url = $this->array_get( '_wp_http_referer', $_REQUEST );
		return apply_filters( 'tutor_referer_url', $url );
	}

	/**
	 * Get HTTP referer field
	 *
	 * @since 2.5.0
	 *
	 * @param boolean $url_decode URL decode for unicode support.
	 *
	 * @return void|string
	 */
	public function referer_field( $url_decode = true ) {
		$url = remove_query_arg( '_wp_http_referer' );
		if ( $url_decode ) {
			$url = urldecode( $url );
		}

		echo '<input type="hidden" name="_wp_http_referer" value="' . esc_url( $url ) . '">';
	}

	/**
	 * Get the frontend dashboard course edit page
	 *
	 * @since 1.3.4
	 * @since 3.0.0 hide admin bar support and location param added.
	 *
	 * @param int $course_id course id.
	 * @param mixed $location possible values `null|backend|frontend`.
	 *
	 * @return false|string
	 */
	public function course_edit_link( $course_id = 0, $location = null ) {
		$course_id    = $this->get_post_id( $course_id );
		$frontend_url = $this->tutor_dashboard_url( 'create-course?course_id=' . $course_id );
		$backend_url  = admin_url( "admin.php?page=create-course&course_id={$course_id}" );

		$url = $frontend_url;
		if ( is_null( $location ) ) {
			if ( User::is_admin() || ! (bool) get_tutor_option( 'hide_admin_bar_for_users' ) ) {
				$url = $backend_url;
			}
		} elseif ( 'backend' === $location ) {
			$url = $backend_url;
		}

		return $url;
	}

	/**
	 * Get assignments by instructor
	 *
	 * @since 1.0.0
	 *
	 * @param integer $instructor_id instructor id.
	 * @param array   $filter_data filter data.
	 *
	 * @return mixed
	 */
	public function get_assignments_by_instructor( $instructor_id = 0, $filter_data = array() ) {
		global $wpdb;

		$instructor_id        = $this->get_user_id( $instructor_id );
		$course_ids           = $this->get_assigned_courses_ids_by_instructors( $instructor_id );
		$assignment_post_type = 'tutor_assignments';

		$in_course_ids = implode( "','", $course_ids );

		$pagination_query = $date_query = '';
		$sort_query       = 'ORDER BY ID DESC';
		if ( $this->count( $filter_data ) ) {
			extract( $filter_data );

			if ( ! empty( $course_id ) ) {
				$in_course_ids = $course_id;
			}
			if ( ! empty( $date_filter ) ) {
				$date_filter = tutor_get_formated_date( 'Y-m-d', $date_filter );
				$date_query  = " AND DATE(post_date) = '{$date_filter}'";
			}
			if ( ! empty( $order_filter ) ) {
				$sort_query = " ORDER BY ID {$order_filter} ";
			}
			if ( ! empty( $per_page ) ) {
				$offset           = (int) ! empty( $offset ) ? $offset : 0;
				$pagination_query = " LIMIT {$offset}, {$per_page}  ";
			}
		}

		$count = (int) $wpdb->get_var(
			$wpdb->prepare(
				"SELECT COUNT(ID)
			FROM 	{$wpdb->postmeta} post_meta
					INNER JOIN {$wpdb->posts} assignment
							ON post_meta.post_id = assignment.ID
						   AND post_meta.meta_key = '_tutor_course_id_for_assignments'
			WHERE 	post_type = %s
					AND assignment.post_parent>0
					AND post_meta.meta_value IN('$in_course_ids')
					{$date_query}
			",
				$assignment_post_type
			)
		);

		$query = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT *
			FROM	{$wpdb->postmeta} post_meta
					INNER JOIN {$wpdb->posts} assignment
							ON post_meta.post_id = assignment.ID
						   AND post_meta.meta_key = '_tutor_course_id_for_assignments'
			WHERE 	post_type = %s
					AND assignment.post_parent>0
					AND post_meta.meta_value IN('$in_course_ids')
					{$date_query}
					{$sort_query}
					{$pagination_query}
			",
				$assignment_post_type
			)
		);

		return (object) array(
			'count'   => $count,
			'results' => $query,
		);
	}

	/**
	 * Get assignments by course id
	 *
	 * @since 1.0.0
	 *
	 * @param int $course_id course id.
	 *
	 * @return bool|object
	 */
	public function get_assignments_by_course( $course_id = 0 ) {
		if ( ! $course_id ) {
			return false;
		}
		global $wpdb;

		$assignment_post_type = 'tutor_assignments';

		$count = (int) $wpdb->get_var(
			$wpdb->prepare(
				"SELECT 	COUNT(ID)
			FROM 		{$wpdb->postmeta} post_meta
 						INNER JOIN {$wpdb->posts} assignment
					 			ON post_meta.post_id = assignment.ID
						   	   AND post_meta.meta_key = '_tutor_course_id_for_assignments'
 			WHERE		post_type = %s
			 			AND post_meta.meta_value = %d
			ORDER BY 	ID DESC;
			",
				$assignment_post_type,
				$course_id
			)
		);

		$query = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT *
			FROM 	{$wpdb->postmeta} post_meta
 					INNER JOIN {$wpdb->posts} assignment
					 		ON post_meta.post_id = assignment.ID
						   AND post_meta.meta_key = '_tutor_course_id_for_assignments'
 			WHERE	post_type = %s
			 		AND post_meta.meta_value = %d
			ORDER BY ID DESC;
			",
				$assignment_post_type,
				$course_id
			)
		);

		return (object) array(
			'count'   => $count,
			'results' => $query,
		);
	}

	/**
	 * Determine if script debug
	 *
	 * @since 1.3.4
	 *
	 * @return bool
	 */
	public function is_script_debug() {
		return ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG );
	}

	/**
	 * Check lesson edit access by instructor
	 *
	 * @since  1.4.0
	 *
	 * @param int $lesson_id lesson id.
	 * @param int $instructor_id instructor id.
	 *
	 * @return bool
	 */
	public function has_lesson_edit_access( $lesson_id = 0, $instructor_id = 0 ) {
		$lesson_id     = $this->get_post_id( $lesson_id );
		$instructor_id = $this->get_user_id( $instructor_id );

		if ( user_can( $instructor_id, tutor()->instructor_role ) ) {
			$permitted_course_ids = $this->get_assigned_courses_ids_by_instructors();
			$course_id            = $this->get_course_id_by( 'lesson', $lesson_id );

			if ( in_array( $course_id, $permitted_course_ids ) ) {
				return true;
			}
		}

		return false;
	}


	/**
	 * Get total Enrolments
	 *
	 * @since 1.4.0
	 *
	 * @param string $status status.
	 * @param string $search_term search term.
	 * @param string $course_id course id.
	 * @param string $date date.
	 *
	 * @return int
	 */
	public function get_total_enrolments( $status, $search_term = '', $course_id = '', $date = '' ) {
		global $wpdb;
		$status      = sanitize_text_field( $status );
		$course_id   = sanitize_text_field( $course_id );
		$date        = sanitize_text_field( $date );
		$search_term = sanitize_text_field( $search_term );

		$search_term_raw = $search_term;
		$search_term     = '%' . $wpdb->esc_like( $search_term ) . '%';

		// Add course id in where clause.
		$course_query = '';
		if ( '' !== $course_id ) {
			$course_query = "AND course.ID = $course_id";
		}

		// Add date in where clause.
		$date_query = '';
		if ( '' !== $date ) {
			$date_query = "AND DATE(enrol.post_date) = CAST('$date' AS DATE) ";
		}

		// Add status in where clause.
		if ( 'approved' === $status ) {
			$status = 'completed';
		} elseif ( 'cancelled' === $status ) {
			$status = array( 'cancel', 'canceled', 'cancelled' );
		} elseif ( 'all' === $status ) {
			$status = '';
		}

		$status_query = "";
		if ( is_array( $status ) && count( $status ) ) {
			$in_clause    =  QueryHelper::prepare_in_clause( $status );
			$status_query = "AND enrol.post_status IN ({$in_clause})";
		} elseif ( ! empty( $status ) ) {
			$status_query = "AND enrol.post_status = '$status' ";
		}

		$count = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT COUNT(enrol.ID)
			FROM 	{$wpdb->posts} enrol
					INNER JOIN {$wpdb->posts} course
							ON enrol.post_parent = course.ID
							AND course.post_type != 'course-bundle'
					INNER JOIN {$wpdb->users} student
							ON enrol.post_author = student.ID
			WHERE 	enrol.post_type = %s
					{$status_query}
					{$course_query}
					{$date_query}
					AND ( enrol.ID LIKE %s OR student.display_name LIKE %s OR student.user_email = %s OR course.post_title LIKE %s );
			",
				'tutor_enrolled',
				$search_term,
				$search_term,
				$search_term_raw,
				$search_term
			)
		);

		return (int) $count;
	}

	public function get_enrolments( $status, $start = 0, $limit = 10, $search_term = '', $course_id = '', $date = '', $order = 'DESC' ) {
		global $wpdb;
		$status      = sanitize_text_field( $status );
		$course_id   = sanitize_text_field( $course_id );
		$date        = sanitize_text_field( $date );
		$search_term = sanitize_text_field( $search_term );

		$search_term_raw = $search_term;
		$search_term     = '%' . $wpdb->esc_like( $search_term ) . '%';

		// add course id in where clause.
		$course_query = '';
		if ( '' !== $course_id ) {
			$course_query = "AND course.ID = $course_id";
		}

		// add date in where clause.
		$date_query = '';
		if ( '' !== $date ) {
			$date_query = "AND DATE(enrol.post_date) = CAST('$date' AS DATE) ";
		}

		// add status in where clause.
		if ( 'approved' === $status ) {
			$status = 'completed';
		} elseif ( 'cancelled' === $status ) {
			$status = array( 'cancel', 'canceled', 'cancelled' );
		} elseif ( 'all' === $status ) {
			$status = '';
		}

		$status_query = "";
		if ( is_array( $status ) && count( $status ) ) {
			$in_clause    =  QueryHelper::prepare_in_clause( $status );
			$status_query = "AND enrol.post_status IN ({$in_clause})";
		} elseif ( ! empty( $status ) ) {
			$status_query = "AND enrol.post_status = '$status' ";
		}

		$enrolments = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT enrol.ID AS enrol_id,
					enrol.post_author AS student_id,
					enrol.post_date AS enrol_date,
					enrol.post_title AS enrol_title,
					enrol.post_status AS status,
					enrol.post_parent AS course_id,
					course.post_title AS course_title,
					course.guid,
					student.user_nicename,
					student.user_email,
					student.display_name
			FROM 	{$wpdb->posts} enrol
					INNER JOIN {$wpdb->posts} course
							ON enrol.post_parent = course.ID
							AND course.post_type != 'course-bundle'
					INNER JOIN {$wpdb->users} student
							ON enrol.post_author = student.ID
			WHERE 	enrol.post_type = %s
					{$status_query}
					{$course_query}
					{$date_query}
					AND ( enrol.ID LIKE %s OR student.display_name LIKE %s OR student.user_email = %s OR course.post_title LIKE %s )
			ORDER BY enrol_id {$order}
			LIMIT 	%d, %d;
			",
				'tutor_enrolled',
				$search_term,
				$search_term,
				$search_term_raw,
				$search_term,
				$start,
				$limit
			)
		);

		return $enrolments;
	}

	/**
	 * Get current URL
	 *
	 * @since 1.4.0
	 *
	 * @param int $post_id post ID.
	 *
	 * @return false|string
	 */
	public function get_current_url( $post_id = 0 ) {
		$page_id = $this->get_post_id( $post_id );

		if ( $page_id ) {
			return get_the_permalink( $page_id );
		} else {
			global $wp;
			$wp->parse_request();
			$current_url = home_url( $wp->request );
			return $current_url;
		}
	}

	/**
	 * Get rating by rating id|comment_ID
	 *
	 * @since 1.4.0
	 *
	 * @param int $rating_id rating id.
	 *
	 * @return object
	 */
	public function get_rating_by_id( $rating_id = 0 ) {
		global $wpdb;

		$ratings = array(
			'rating' => 0,
			'review' => '',
		);

		$rating = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT meta_value AS rating,
					comment_content AS review
			FROM 	{$wpdb->comments}
					INNER JOIN {$wpdb->commentmeta}
							ON {$wpdb->comments}.comment_ID = {$wpdb->commentmeta}.comment_id
			WHERE 	{$wpdb->comments}.comment_ID = %d;
			",
				$rating_id
			)
		);

		if ( $rating ) {
			$rating_format = number_format( $rating->rating, 2 );

			$ratings = array(
				'rating' => $rating_format,
				'review' => $rating->review,
			);
		}
		return (object) $ratings;
	}

	/**
	 * Get course settings by course ID
	 *
	 * @since 1.4.0
	 *
	 * @param int  $course_id course id.
	 * @param null $key key.
	 * @param bool $default default value.
	 *
	 * @return array|bool|mixed
	 */
	public function get_course_settings( $course_id = 0, $key = null, $default = false ) {
		$course_id     = $this->get_post_id( $course_id );
		$settings_meta = get_post_meta( $course_id, '_tutor_course_settings', true );
		$settings      = (array) maybe_unserialize( $settings_meta );

		return $this->array_get( $key, $settings, $default );
	}

	/**
	 * Get Lesson content drip settings
	 *
	 * @since 1.4.0
	 *
	 * @param int  $lesson_id lesson id.
	 * @param null $key key.
	 * @param bool $default default value.
	 *
	 * @return array|bool|mixed
	 */
	public function get_item_content_drip_settings( $lesson_id = 0, $key = null, $default = false ) {
		$lesson_id     = $this->get_post_id( $lesson_id );
		$settings_meta = get_post_meta( $lesson_id, '_content_drip_settings', true );
		$settings      = (array) maybe_unserialize( $settings_meta );

		return $this->array_get( $key, $settings, $default );
	}

	/**
	 * Get course previous content ID
	 *
	 * @since 1.4.0
	 *
	 * @param int   $current_id current id.
	 * @param array $exclude_type types.
	 *
	 * @return mixed
	 */
	public function get_course_previous_content_id( $current_id, $exclude_type = array() ) {
		$course_id = $this->get_course_id_by_content( $current_id );
		$topics    = $this->get_topics( $course_id );

		$content_ids = array();

		foreach ( $topics->posts as $topic ) {
			$contents = $this->get_course_contents_by_topic( $topic->ID, -1 );

			foreach ( $contents->posts as $content ) {
				if ( ! in_array( $content->post_type, $exclude_type ) ) {
					$content_ids[] = $content->ID;
				}
			}
		}

		foreach ( $content_ids as $key => $content_id ) {
			if ( $current_id == $content_id ) {
				if ( ! empty( $content_ids[ $key - 1 ] ) ) {
					return $content_ids[ $key - 1 ];
				}
			}
		}

		return false;
	}

	/**
	 * Get Course ID by any course content
	 *
	 * @since 1.0.0
	 *
	 * @param object $post post object.
	 *
	 * @return int
	 */
	public function get_course_id_by_content( $post ) {
		return $this->get_course_id_by_subcontent( is_numeric( $post ) ? $post : $post->ID );
	}

	/**
	 * Get Course contents by Course ID
	 *
	 * @since 1.4.1
	 * @since 3.0.0 filterable `post_type` and where clause support added.
	 *
	 * @param int $course_id course id.
	 *
	 * @return array|null|object
	 */
	public function get_course_contents_by_id( $course_id = 0 ) {
		global $wpdb;

		$course_id = $this->get_post_id( $course_id );

		$cache_key = "tutor_get_course_contents_by_{$course_id}";

		$contents = TutorCache::get( $cache_key );
		if ( false === $contents ) {
			$conditions = array(
				$wpdb->prepare( 'topic.post_parent = %d', $course_id ),
				$wpdb->prepare( 'items.post_status = %s', CourseModel::STATUS_PUBLISH ),
			);

			$default_post_types = array( tutor()->lesson_post_type, tutor()->quiz_post_type );
			$content_post_types = array_unique( apply_filters( 'tutor_course_contents_post_types', $default_post_types ) );

			if ( $this->count( $content_post_types ) ) {
				$placeholders = implode( ', ', array_fill( 0, count( $content_post_types ), '%s' ) );
				$conditions[] = $wpdb->prepare( "items.post_type IN ($placeholders)", ...$content_post_types );
			}

			$conditions   = apply_filters( 'tutor_course_contents_where_clause', $conditions, $course_id );
			$where_clause = 'WHERE ' . implode( ' AND ', $conditions );

			$contents = $wpdb->get_results(
				"SELECT items.*
				FROM 	{$wpdb->posts} topic
						INNER JOIN {$wpdb->posts} items
								ON topic.ID = items.post_parent
				{$where_clause}
				ORDER BY topic.menu_order ASC,
						items.menu_order ASC;
				"
			);

			TutorCache::set( $cache_key, $contents );
		}

		return $contents;
	}

	/**
	 * Get Gradebooks lists by type
	 *
	 * @since 1.4.2
	 *
	 * @return array|null|object
	 */
	public function get_gradebooks() {
		global $wpdb;
		$results = $wpdb->get_results( "SELECT * FROM {$wpdb->tutor_gradebooks} ORDER BY grade_point DESC " );
		return $results;
	}

	/**
	 * Print Course Status Context
	 *
	 * @param int $course_id course id.
	 * @param int $user_id user id.
	 *
	 * @return string
	 */
	public function course_progress_status_context( $course_id = 0, $user_id = 0 ) {
		$course_id    = $this->get_post_id( $course_id );
		$user_id      = $this->get_user_id( $user_id );
		$is_completed = $this->is_completed_course( $course_id, $user_id );

		$html = '';
		if ( $is_completed ) {
			$html = '<span class="course-completion-status course-completed"><i class="tutor-icon-mark"></i> ' . __( 'Completed', 'tutor' ) . ' </span>';
		} else {
			$is_in_progress = $this->get_completed_lesson_count_by_course( $course_id, $user_id );
			if ( $is_in_progress ) {
				$html = '<span class="course-completion-status course-inprogress"><i class="tutor-icon-refresh-o"></i> ' . __( 'In Progress', 'tutor' ) . ' </span>';
			} else {
				$html = '<span class="course-completion-status course-not-taken"><i class="tutor-icon-spinner"></i> ' . __( 'Not Taken', 'tutor' ) . ' </span>';
			}
		}
		return $html;
	}

	/**
	 * Reset Password
	 *
	 * @since 1.4.3
	 *
	 * @param object $user user object.
	 * @param string $new_pass new password.
	 *
	 * @return void
	 */
	public function reset_password( $user, $new_pass ) {
		do_action( 'password_reset', $user, $new_pass );

		wp_set_password( $new_pass, $user->ID );

		$rp_cookie = 'wp-resetpass-' . COOKIEHASH;
		$rp_path   = isset( $_SERVER['REQUEST_URI'] ) ? current( explode( '?', wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) : ''; // WPCS: input var ok, sanitization ok.

		setcookie( $rp_cookie, ' ', tutor_time() - YEAR_IN_SECONDS, $rp_path, COOKIE_DOMAIN, is_ssl(), true );
		wp_password_change_notification( $user );
	}

	/**
	 * Get tutor pages, required to show dashboard, and others forms
	 *
	 * @since 1.4.3
	 *
	 * @return array
	 */
	public function tutor_pages() {
		$pages = apply_filters(
			'tutor_pages',
			array(
				'tutor_dashboard_page_id'  => __( 'Dashboard Page', 'tutor' ),
				'instructor_register_page' => __( 'Instructor Registration Page', 'tutor' ),
				'student_register_page'    => __( 'Student Registration Page', 'tutor' ),
				'tutor_cart_page_id'       => __( 'Cart', 'tutor' ),
				'tutor_checkout_page_id'   => __( 'Checkout', 'tutor' ),
			)
		);

		$new_pages = array();
		foreach ( $pages as $key => $page ) {
			$page_id = (int) get_tutor_option( $key );

			$wp_page_name = '';
			$wp_page      = get_post( $page_id );
			$page_exists  = (bool) $wp_page;
			$page_visible = false;

			if ( $wp_page ) {
				$wp_page_name = $wp_page->post_title;
				$page_visible = $wp_page->post_status === 'publish';
			} else {
				$page_id = 0;
			}

			$new_pages[] = array(
				'option_key'   => $key,
				'page_name'    => $page,
				'wp_page_name' => $wp_page_name,
				'page_id'      => $page_id,
				'page_exists'  => $page_exists,
				'page_visible' => $page_visible,
			);
		}

		return $new_pages;
	}

	/**
	 * Get Course prev next lession contents by content ID
	 *
	 * @since 1.4.9
	 *
	 * @param int $content_id content id.
	 *
	 * @return array|null|object
	 */
	public function get_course_prev_next_contents_by_id( $content_id = 0 ) {

		$course_id       = $this->get_course_id_by_content( $content_id );
		$course_contents = $this->get_course_contents_by_id( $course_id );
		$previous_id     = 0;
		$next_id         = 0;

		if ( $this->count( $course_contents ) ) {
			$ids = wp_list_pluck( $course_contents, 'ID' );

			$i = 0;
			foreach ( $ids as $key => $id ) {
				$previous_i = $key - 1;
				$next_i     = $key + 1;

				if ( $id == $content_id ) {
					if ( isset( $ids[ $previous_i ] ) ) {
						$previous_id = $ids[ $previous_i ];
					}
					if ( isset( $ids[ $next_i ] ) ) {
						$next_id = $ids[ $next_i ];
					}
				}
				++$i;
			}
		}

		return (object) array(
			'previous_id' => $previous_id,
			'next_id'     => $next_id,
		);
	}

	/**
	 * Get a subset of the items from the given array.
	 *
	 * @since 1.5.2
	 *
	 * @param array        $array array.
	 * @param array|string $keys keys.
	 *
	 * @return array|bool
	 */
	public function array_only( $array = array(), $keys = null ) {
		if ( ! $this->count( $array ) || ! $keys ) {
			return false;
		}

		return array_intersect_key( $array, array_flip( (array) $keys ) );
	}

	/**
	 * Is instructor of this course
	 *
	 * @since 1.6.4
	 *
	 * @param int $instructor_id instructor id.
	 * @param int $course_id course id.
	 *
	 * @return bool|int
	 */
	public function is_instructor_of_this_course( $instructor_id = 0, $course_id = 0 ) {
		global $wpdb;

		$instructor_id = $this->get_user_id( $instructor_id );
		$course_id     = $this->get_post_id( $course_id );

		if ( ! $instructor_id || ! $course_id ) {
			return false;
		}

		$cache_key  = "tutor_is_instructor_of_the_course_{$instructor_id}_{$course_id}";
		$instructor = TutorCache::get( $cache_key );

		if ( false === $instructor ) {
			$instructor = $wpdb->get_col(
				$wpdb->prepare(
					"SELECT umeta_id
				FROM   {$wpdb->usermeta}
				WHERE  user_id = %d
					AND meta_key = '_tutor_instructor_course_id'
					AND meta_value = %d
				",
					$instructor_id,
					$course_id
				)
			);
			TutorCache::set( $cache_key, $instructor );
		}

		if ( is_array( $instructor ) && count( $instructor ) ) {
			return $instructor;
		}

		return false;
	}

	/**
	 * User profile completion
	 *
	 * @since 1.6.6
	 *
	 * @param int $user_id user id.
	 *
	 * @return array|object
	 */
	public function user_profile_completion( $user_id = 0 ) {
		$user_id           = $this->get_user_id( $user_id );
		$instructor        = $this->is_instructor( $user_id );
		$instructor_status = get_user_meta( $user_id, '_tutor_instructor_status', true );

		$settings_url          = $this->tutor_dashboard_url( 'settings' );
		$withdraw_settings_url = $this->tutor_dashboard_url( 'settings/withdraw-settings' );

		$required_fields = array(
			'_tutor_profile_photo' => __( 'Set Your Profile Photo', 'tutor' ),
			'_tutor_profile_bio'   => __( 'Set Your Bio', 'tutor' ),
		);

		// Add payment method as a required on if current user is an approved instructor.
		if ( 'approved' == $instructor_status ) {
			$required_fields['_tutor_withdraw_method_data'] = __( 'Set Withdraw Method', 'tutor' );
		}

		// url where user should redirect for profile completion.
		$profile_completion_urls = array(
			'_tutor_profile_photo'        => $settings_url,
			'_tutor_profile_bio'          => $settings_url,
			'_tutor_withdraw_method_data' => $withdraw_settings_url,
		);
		foreach ( $required_fields as $key => $field ) {
			$required_fields[ $key ] = array(
				'text'   => $field,
				'is_set' => get_user_meta( $user_id, $key, true ) ? true : false,
				'url'    => $profile_completion_urls[ $key ],
			);
		}

		// Apply fitlers on the list.
		return apply_filters( 'tutor/user/profile/completion', $required_fields );
	}

	/**
	 * Get enrollment by enrol_id
	 *
	 * @since 1.6.9
	 *
	 * @param int $enrol_id enrol id.
	 *
	 * @return array|object
	 */
	public function get_enrolment_by_enrol_id( $enrol_id = 0 ) {
		global $wpdb;

		$enrolment = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT enrol.id          AS enrol_id,
					enrol.post_author AS student_id,
					enrol.post_date   AS enrol_date,
					enrol.post_title  AS enrol_title,
					enrol.post_status AS status,
					enrol.post_parent AS course_id,
					course.post_title AS course_title,
					student.user_nicename,
					student.user_email,
					student.display_name,
					student.ID
			FROM   {$wpdb->posts} enrol
					INNER JOIN {$wpdb->posts} course
							ON enrol.post_parent = course.id
					INNER JOIN {$wpdb->users} student
							ON enrol.post_author = student.id
			WHERE  enrol.id = %d;
		",
				$enrol_id
			)
		);

		if ( $enrolment ) {
			return $enrolment;
		}

		return false;
	}

	/**
	 * Get students list based on course id
	 *
	 * @since 1.6.6
	 *
	 * @param integer $course_id course id.
	 * @param string  $field_name field name.
	 * @param boolean $all  if all is false it will return only $field_name column.
	 *
	 * @return array  of objects for student list or array
	 */
	public function get_students_data_by_course_id( $course_id = 0, $field_name = 'ID', $all = false ) {

		global $wpdb;
		$course_id = $this->get_post_id( $course_id );

		$student_data = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT student.{$field_name}, student.display_name as display_name, student.user_login as username, student.user_email
			FROM   	{$wpdb->posts} enrol
					INNER JOIN {$wpdb->users} student
						    ON enrol.post_author = student.id
			WHERE  	enrol.post_type = %s
					AND enrol.post_parent = %d
					AND enrol.post_status = %s;
			",
				'tutor_enrolled',
				$course_id,
				'completed'
			)
		);
		if ( $all ) {
			return $student_data;
		}
		return array_column( $student_data, $field_name );
	}

	/**
	 * Get student data by course id.
	 *
	 * @since 1.0.0
	 *
	 * @param integer $course_id course id.
	 *
	 * @return array
	 */
	public function get_students_all_data_by_course_id( $course_id = 0 ) {

		global $wpdb;
		$course_id = $this->get_post_id( $course_id );

		$student_data = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT *
			FROM   	{$wpdb->posts} enrol
					INNER JOIN {$wpdb->users} student
						    ON enrol.post_author = student.id
			WHERE  	enrol.post_type = %s
					AND enrol.post_parent = %d
					AND enrol.post_status = %s;
			",
				'tutor_enrolled',
				$course_id,
				'completed'
			)
		);

		return array_column( $student_data, $field_name );
	}

	/**
	 * Get students email by course id
	 *
	 * @since 1.6.9
	 *
	 * @param int $course_id course id.
	 *
	 * @return array
	 */
	public function get_student_emails_by_course_id( $course_id = 0 ) {
		return $this->get_students_data_by_course_id( $course_id, 'user_email' );
	}

	/**
	 * Get single comment user post id.
	 *
	 * @since 1.0.0
	 *
	 * @param int $post_id post id.
	 * @param int $user_id user id.
	 *
	 * @return mixed
	 */
	public function get_single_comment_user_post_id( $post_id, $user_id ) {
		global $wpdb;
		$table = $wpdb->prefix . 'comments';
		$query = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT *
			FROM 	$table
			WHERE 	comment_post_ID = %d
					AND user_id = %d
			LIMIT 	1
			",
				$post_id,
				$user_id
			)
		);
		return $query ? $query : false;
	}

	/**
	 * Check if course is in wc cart
	 *
	 * @since 1.7.5
	 *
	 * @param int  $course_or_product_id course or product id.
	 * @param bool $is_product_id is product id or not.
	 *
	 * @return bool
	 */
	public function is_course_added_to_cart( $course_or_product_id = 0, $is_product_id = false ) {

		switch ( $this->get_option( 'monetize_by' ) ) {
			case 'wc':
				global $woocommerce;
				$product_id = $is_product_id ? $course_or_product_id : $this->get_course_product_id( $course_or_product_id );

				if ( $woocommerce->cart ) {
					foreach ( $woocommerce->cart->get_cart() as $key => $val ) {
						if ( $product_id == $val['product_id'] ) {
							return true;
						}
					}
				}
				break;
		}
	}

	/**
	 * Get profile pic url
	 *
	 * @since 1.7.5
	 *
	 * @param int $user_id user id.
	 *
	 * @return string
	 */
	public function get_cover_photo_url( $user_id ) {
		$cover_photo_src = tutor()->url . 'assets/images/cover-photo.jpg';
		$cover_photo_id  = get_user_meta( $user_id, '_tutor_cover_photo', true );
		if ( $cover_photo_id ) {
			$url                               = wp_get_attachment_image_url( $cover_photo_id, 'full' );
			! empty( $url ) ? $cover_photo_src = $url : 0;
		}

		return $cover_photo_src;
	}

	/**
	 * Return the course ID(s) by lession, quiz, answer etc.
	 *
	 * @since 1.7.9
	 *
	 * @param string $content content like lession, quiz, answer etc.
	 * @param int    $object_id object id.
	 *
	 * @return int
	 */
	public function get_course_id_by( $content, $object_id ) {
		$cache_key = "tutor_get_course_id_by_{$content}_{$object_id}";
		$course_id = TutorCache::get( $cache_key );

		if ( false === $course_id ) {
			global $wpdb;
			switch ( $content ) {
				case 'course':
					$course_id = $object_id;
					break;

				case 'zoom_meeting':
				case 'tutor_gm_course':
				case 'topic':
				case 'announcement':
					$course_id = wp_get_post_parent_id( $object_id );
					break;

				case 'zoom_lesson':
				case 'tutor_gm_topic':
				case 'lesson':
				case 'quiz':
				case 'assignment':
					$topic_id = wp_get_post_parent_id( $object_id );
					if ( ! $topic_id ) {
						$course_id = $wpdb->get_var(
							$wpdb->prepare(
								"SELECT meta_value
							FROM {$wpdb->prefix}postmeta
							WHERE post_id=%d AND meta_key='_tutor_course_id_for_lesson'",
								$object_id
							)
						);
					} else {
						$course_id = wp_get_post_parent_id( $topic_id );
					}
					break;

				case 'assignment_submission':
					$course_id = $wpdb->get_var(
						$wpdb->prepare(
							"SELECT DISTINCT _course.ID
						FROM {$wpdb->posts} _course
							INNER JOIN {$wpdb->posts} _topic ON _topic.post_parent=_course.ID
							INNER JOIN {$wpdb->posts} _assignment ON _assignment.post_parent=_topic.ID
							INNER JOIN {$wpdb->comments} _submission ON _submission.comment_post_ID=_assignment.ID
						WHERE _submission.comment_ID=%d;",
							$object_id
						)
					);
					break;

				case 'question':
					$course_id = $wpdb->get_var(
						$wpdb->prepare(
							"SELECT topic.post_parent
						FROM 	{$wpdb->posts} topic
								INNER JOIN {$wpdb->posts} quiz
										ON quiz.post_parent=topic.ID
								INNER JOIN {$wpdb->prefix}tutor_quiz_questions question
										ON question.quiz_id=quiz.ID
						WHERE 	question.question_id = %d;
						",
							$object_id
						)
					);
					break;

				case 'quiz_answer':
					$course_id = $wpdb->get_var(
						$wpdb->prepare(
							"SELECT topic.post_parent
						FROM 	{$wpdb->posts} topic
								INNER JOIN {$wpdb->posts} quiz
										ON quiz.post_parent=topic.ID
								INNER JOIN {$wpdb->prefix}tutor_quiz_questions question
										ON question.quiz_id=quiz.ID
								INNER JOIN {$wpdb->prefix}tutor_quiz_question_answers answer
										ON answer.belongs_question_id=question.question_id
						WHERE 	answer.answer_id = %d;
						",
							$object_id
						)
					);
					break;

				case 'attempt':
					$course_id = $wpdb->get_var(
						$wpdb->prepare(
							"SELECT course_id
						FROM 	{$wpdb->prefix}tutor_quiz_attempts
						WHERE 	attempt_id=%d;
						",
							$object_id
						)
					);
					break;

				case 'attempt_answer':
					$course_id = $wpdb->get_var(
						$wpdb->prepare(
							"SELECT course_id
						FROM 	{$wpdb->prefix}tutor_quiz_attempts
						WHERE 	attempt_id = (SELECT quiz_attempt_id FROM {$wpdb->prefix}tutor_quiz_attempt_answers WHERE attempt_answer_id=%d)
						",
							$object_id
						)
					);
					break;

				case 'review':
				case 'qa_question':
					$question = get_comment( $object_id );
					if ( is_a( $question, 'WP_Comment' ) ) {
						$course_id = $question->comment_post_ID;
					}
					break;

				case 'instructor':
					$course_ids = get_user_meta( $object_id, '_tutor_instructor_course_id' );

					! is_array( $course_ids ) ? $course_ids = array() : 0;
					$course_id                              = array_filter(
						$course_ids,
						function ( $id ) {
							return ( $id && is_numeric( $id ) );
						}
					);
					break;
			}

			TutorCache::set( $cache_key, $course_id );
		}

		return $course_id;
	}


	/**
	 * Return the course ID(s) by lession, quiz, answer etc.
	 *
	 * @since 1.7.9
	 *
	 * @param int $content_id content id.
	 *
	 * @return int
	 */
	public function get_course_id_by_subcontent( $content_id ) {
		$mapping = array(
			'tutor_assignments'  => 'assignment',
			'tutor_quiz'         => 'quiz',
			'lesson'             => 'lesson',
			'tutor_zoom_meeting' => 'zoom_meeting',
			'tutor_zoom_lesson'  => 'zoom_lesson',
			'tutor_gm_course'    => 'tutor_gm_course',
			'tutor_gm_topic'     => 'tutor_gm_topic',
			'topics'             => 'topic',
		);

		$content_type = get_post_field( 'post_type', $content_id );

		// Differentiate standalone zoom meeting and zoom lesson.
		if ( $content_type == 'tutor_zoom_meeting' ) {
			$parent_id   = wp_get_post_parent_id( $content_id );
			$parent_type = get_post_field( 'post_type', $parent_id );

			$content_type = $parent_type == tutor()->course_post_type ? 'tutor_zoom_meeting' : 'tutor_zoom_lesson';
		}
		if ( $content_type == 'tutor-google-meet' ) {
			$parent_id   = wp_get_post_parent_id( $content_id );
			$parent_type = get_post_field( 'post_type', $parent_id );

			$content_type = $parent_type == tutor()->course_post_type ? 'tutor_gm_course' : 'tutor_gm_topic';
		}
		return $this->get_course_id_by( $mapping[ $content_type ], $content_id );
	}

	/**
	 * Check if user can create, edit, delete various tutor contents such as lesson, quiz, answer etc.
	 *
	 * @since 1.7.9
	 *
	 * @param string  $content content.
	 * @param int     $object_id object id.
	 * @param integer $user_id user id.
	 * @param boolean $allow_current_admin is allow current admin.
	 *
	 * @return boolean
	 */
	public function can_user_manage( $content, $object_id, $user_id = 0, $allow_current_admin = true ) {
		$user_id   = (int) $this->get_user_id( $user_id );
		$course_id = $this->get_course_id_by( $content, $object_id );

		if ( $course_id ) {
			if ( $allow_current_admin && current_user_can( 'administrator' ) ) {
				// Admin has access to everything.
				return true;
			}

			$instructors    = $this->get_instructors_by_course( $course_id );
			$instructor_ids = is_array( $instructors ) ? array_map(
				function ( $instructor ) {
					return (int) $instructor->ID;
				},
				$instructors
			) : array();

			$is_listed = in_array( $user_id, $instructor_ids );

			if ( $is_listed ) {
				return true;
			}
		}

		global $wpdb;
		switch ( $content ) {
			case 'review':
			case 'qa_question':
				// Just check if own content. Instructor privilege already checked in the earlier blocks.
				$id = $wpdb->get_var(
					$wpdb->prepare(
						"SELECT comment_ID
					FROM {$wpdb->comments} WHERE user_id = %d AND comment_ID=%d",
						$user_id,
						$object_id
					)
				);

				return $id ? true : false;
		}

		return false;
	}

	/**
	 * Check if user has access for content like lesson, quiz, assignment etc.
	 *
	 * @since 1.7.9
	 *
	 * @param string  $content content.
	 * @param integer $object_id object id.
	 * @param integer $user_id user id.
	 *
	 * @return boolean
	 */
	public function has_enrolled_content_access( $content, $object_id = 0, $user_id = 0 ) {
		$user_id   = $this->get_user_id( $user_id );
		$object_id = $this->get_post_id( $object_id );
		$course_id = $this->get_course_id_by( $content, $object_id );

		do_action( 'tutor_before_enrolment_check', $course_id, $user_id );

		if ( $this->is_enrolled( $course_id, $user_id ) || $this->has_user_course_content_access( $user_id, $course_id ) ) {
			return true;
		}

		// Check Lesson edit access to support page builders (eg: Oxygen).
		if ( current_user_can( tutor()->instructor_role ) && $this->has_lesson_edit_access() ) {
			return true;
		}

		return false;
	}

	/**
	 * Return the assignment deadline date based on duration and assignment creation date
	 *
	 * @since 1.8.0
	 *
	 * @param int   $assignment_id assignment id.
	 * @param mixed $format format.
	 * @param mixed $fallback fallback.
	 *
	 * @return string|false
	 */
	public function get_assignment_deadline_date( $assignment_id, $format = null, $fallback = null ) {

		! $format ? $format = 'j F, Y, g:i a' : 0;

		$value = $this->get_assignment_option( $assignment_id, 'time_duration.value' );
		$time  = $this->get_assignment_option( $assignment_id, 'time_duration.time' );

		if ( ! $value ) {
			return $fallback;
		}

		$publish_date = get_post_field( 'post_date', $assignment_id );

		$date = date_create( $publish_date );
		date_add( $date, date_interval_create_from_date_string( $value . ' ' . $time ) );

		return date_format( $date, $format );
	}

	/**
	 * Get earning chart data
	 *
	 * @since 1.8.2
	 *
	 * @param int    $user_id user id.
	 * @param string $start_date start date.
	 * @param string $end_date end date.
	 *
	 * @return array
	 */
	public function get_earning_chart( $user_id, $start_date, $end_date ) {
		global $wpdb;

		// Format Date Name.
		$begin    = new \DateTime( $start_date );
		$end      = new \DateTime( $end_date );
		$interval = \DateInterval::createFromDateString( '1 day' );
		$period   = new \DatePeriod( $begin, $interval, $end );

		$datesPeriod = array();
		foreach ( $period as $dt ) {
			$datesPeriod[ $dt->format( 'Y-m-d' ) ] = 0;
		}

		// Get statuses.
		$complete_status = $this->get_earnings_completed_statuses();
		$statuses        = $complete_status;
		$complete_status = "'" . implode( "','", $complete_status ) . "'";

		$salesQuery = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT  SUM(instructor_amount) AS total_earning,
					DATE(created_at) AS date_format
			FROM	{$wpdb->prefix}tutor_earnings
			WHERE 	user_id = %d
					AND order_status IN({$complete_status})
					AND (created_at BETWEEN %s AND %s)
			GROUP BY date_format
			ORDER BY created_at ASC;
			",
				$user_id,
				$start_date,
				$end_date
			)
		);

		$total_earning = wp_list_pluck( $salesQuery, 'total_earning' );
		$queried_date  = wp_list_pluck( $salesQuery, 'date_format' );
		$dateWiseSales = array_combine( $queried_date, $total_earning );
		$chartData     = array_merge( $datesPeriod, $dateWiseSales );

		foreach ( $chartData as $key => $salesCount ) {
			unset( $chartData[ $key ] );
			$formatDate               = date( 'd M', strtotime( $key ) );
			$chartData[ $formatDate ] = $salesCount;
		}

		$statements  = $this->get_earning_statements( $user_id, compact( 'start_date', 'end_date', 'statuses' ) );
		$earning_sum = $this->get_earning_sum( $user_id, compact( 'start_date', 'end_date' ) );

		return array(
			'chartData'   => $chartData,
			'statements'  => $statements,
			'statuses'    => $statuses,
			'begin'       => $begin,
			'end'         => $end,
			'earning_sum' => $earning_sum,
			'datesPeriod' => $datesPeriod,
		);
	}

	/**
	 * Get earning chart data yearly
	 *
	 * @since 1.8.2
	 *
	 * @param int $user_id user id.
	 * @param int $year year.
	 *
	 * @return array
	 */
	public function get_earning_chart_yearly( $user_id, $year ) {
		global $wpdb;

		$complete_status = $this->get_earnings_completed_statuses();
		$statuses        = $complete_status;
		$complete_status = "'" . implode( "','", $complete_status ) . "'";

		$salesQuery = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT SUM(instructor_amount) AS total_earning,
					MONTHNAME(created_at)  AS month_name
			FROM  	{$wpdb->prefix}tutor_earnings
			WHERE	user_id = %d
					AND order_status IN({$complete_status})
					AND YEAR(created_at) = %s
			GROUP BY MONTH (created_at)
			ORDER BY MONTH(created_at) ASC;
			",
				$user_id,
				$year
			)
		);

		$total_earning  = wp_list_pluck( $salesQuery, 'total_earning' );
		$months         = wp_list_pluck( $salesQuery, 'month_name' );
		$monthWiseSales = array_combine( $months, $total_earning );

		$dataFor = 'yearly';

		/**
		 * Format yearly
		 */
		$emptyMonths = array();
		for ( $m = 1; $m <= 12; $m++ ) {
			$emptyMonths[ date( 'F', mktime( 0, 0, 0, $m, 1, date( 'Y' ) ) ) ] = 0;
		}

		$chartData   = array_merge( $emptyMonths, $monthWiseSales );
		$statements  = $this->get_earning_statements( $user_id, compact( 'year', 'dataFor', 'statuses' ) );
		$earning_sum = $this->get_earning_sum( $user_id, compact( 'year', 'dataFor' ) );

		return array(
			'chartData'   => $chartData,
			'statements'  => $statements,
			'earning_sum' => $earning_sum,
		);
	}

	/**
	 * Return object from vendor package
	 *
	 * @since 1.8.4
	 *
	 * @return object
	 */
	function get_package_object() {
		$params = func_get_args();

		$is_pro     = $params[0];
		$class      = $params[1];
		$class_args = array_slice( $params, 2 );
		$root_path  = $is_pro ? tutor_pro()->path : tutor()->path;

		require_once $root_path . '/vendor/autoload.php';

		$reflector = new \ReflectionClass( $class );
		$object    = $reflector->newInstanceArgs( $class_args );

		return $object;
	}

	/**
	 * Check if user has specific role
	 *
	 * @since 1.8.9
	 *
	 * @param mixed   $roles roles.
	 * @param integer $user_id user id.
	 *
	 * @return boolean
	 */
	public function has_user_role( $roles, $user_id = 0 ) {

		// Prepare the user ID and roles array.
		! $user_id ? $user_id         = get_current_user_id() : 0;
		! is_array( $roles ) ? $roles = array( $roles ) : 0;

		// Get the user data and it's role array.
		$user      = get_userdata( $user_id );
		$role_list = ( is_object( $user ) && is_array( $user->roles ) ) ? $user->roles : array();

		// Check if at least one role exists.
		$without_roles = array_diff( $roles, $role_list );
		return count( $roles ) > count( $without_roles );
	}

	/**
	 * Check if user can edit course
	 *
	 * @since 1.8.9
	 *
	 * @param int $user_id user id.
	 * @param int $course_id course id.
	 *
	 * @return boolean
	 */
	public function can_user_edit_course( $user_id, $course_id ) {
		return $this->has_user_role( array( 'administrator', 'editor' ) ) || $this->is_instructor_of_this_course( $user_id, $course_id );
	}


	/**
	 * Check if course member limit full
	 *
	 * @since 1.9.0
	 *
	 * @param integer $course_id course id.
	 *
	 * @return boolean
	 */
	public function is_course_fully_booked( $course_id = 0 ) {

		$total_enrolled   = $this->count_enrolled_users_by_course( $course_id );
		$maximum_students = (int) $this->get_course_settings( $course_id, 'maximum_students' );

		return $maximum_students && $maximum_students <= $total_enrolled;
	}

	/**
	 * Check course is booked.
	 *
	 * @since 1.9.0
	 *
	 * @param integer $course_id course id.
	 *
	 * @return boolean
	 */
	function is_course_booked( $course_id = 0 ) {

		$total_enrolled   = $this->count_enrolled_users_by_course( $course_id );
		$maximum_students = (int) $this->get_course_settings( $course_id, 'maximum_students' );

		$total_booked = 100 / $maximum_students * $total_enrolled;

		return $total_booked;
	}

	/**
	 * Check if current screen is under tutor dashboard
	 *
	 * @since 1.0.0
	 *
	 * @param string $subpage subpage.
	 *
	 * @return boolean
	 */
	public function is_tutor_dashboard( $subpage = null ) {

		// To Do: Add subpage check later.

		if ( function_exists( 'is_admin' ) && is_admin() ) {
			$screen = get_current_screen();
			return is_object( $screen ) && $screen->parent_base == 'tutor';
		}

		return false;
	}

	/**
	 * Check if current screen tutor frontend dashboard
	 *
	 * @since 1.9.4
	 *
	 * @param string $subpage subpage.
	 *
	 * @return boolean
	 */
	public function is_tutor_frontend_dashboard( $subpage = null ) {

		global $wp_query;
		if ( $wp_query->is_page ) {
			$dashboard_page = $this->array_get( 'tutor_dashboard_page', $wp_query->query_vars );

			if ( $subpage ) {
				return $dashboard_page == $subpage;
			}

			if ( $wp_query->queried_object && $wp_query->queried_object->ID ) {
				$d_id = apply_filters( 'tutor_dashboard_page_id_filter', $this->get_option( 'tutor_dashboard_page_id' ) );
				return $wp_query->queried_object->ID == $d_id;
			}
		}

		return false;
	}

	/**
	 * Get unique slug.
	 *
	 * @since 1.9.4
	 *
	 * @param string  $slug slug.
	 * @param string  $post_type post type.
	 * @param boolean $num_assigned num of assigned.
	 *
	 * @return string
	 */
	public function get_unique_slug( $slug, $post_type = null, $num_assigned = false ) {

		global $wpdb;
		$existing_slug = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT post_name
			FROM {$wpdb->posts}
			WHERE post_name=%s" . ( $post_type ? " AND post_type='{$post_type}' LIMIT 1" : '' ),
				$slug
			)
		);

		if ( ! $existing_slug ) {
			return $slug;
		}

		if ( ! $num_assigned ) {
			$new_slug = $slug . '-' . 2;
		} else {
			$new_slug = explode( '-', $slug );
			$number   = end( $new_slug ) + 1;

			array_pop( $new_slug );

			$new_slug = implode( '-', $new_slug ) . '-' . $number;
		}

		return $this->get_unique_slug( $new_slug, $post_type, true );
	}

	/**
	 * Get post content ids
	 *
	 * @since 1.9.4
	 *
	 * @param string $content_type like: lesson, quiz.
	 * @param string $ancestor_type like: course, topics
	 * @param string $ancestor_ids ancestor like course or topic
	 *
	 * @return array of ID cols
	 */
	public function get_course_content_ids_by( $content_type, $ancestor_type, $ancestor_ids ) {
		global $wpdb;
		$ids = array();

		// Convert single id to array.
		! is_array( $ancestor_ids ) ? $ancestor_ids = array( $ancestor_ids ) : 0;
		$ancestor_ids                               = implode( ',', $ancestor_ids );

		$prepare_ancestor_ids = str_replace( ',', '_', $ancestor_ids );
		$cache_key            = "tutor_get_content_ids_{$content_type}_{$ancestor_type}_{$prepare_ancestor_ids}";
		$ids                  = TutorCache::get( $cache_key );

		if ( false === $ids ) {
			switch ( $content_type ) {

				// Get lesson, quiz, assignment IDs.
				case tutor()->lesson_post_type:
				case 'tutor_quiz':
				case 'tutor_assignments':
					switch ( $ancestor_type ) {

							// Get lesson, quiz, assignment IDs by course ID.
						case tutor()->course_post_type:
							$content_ids = $wpdb->get_col(
								$wpdb->prepare(
									"SELECT content.ID FROM {$wpdb->posts} course
									INNER JOIN {$wpdb->posts} topic ON course.ID=topic.post_parent
									INNER JOIN {$wpdb->posts} content ON topic.ID=content.post_parent
								WHERE course.ID IN ({$ancestor_ids}) AND content.post_type=%s",
									$content_type
								)
							);

							// Assign id array to the variable.
							is_array( $content_ids ) ? $ids = $content_ids : 0;
							break 2;
					}
					break;

				default:
					switch ( $ancestor_type ) {
						// Get lesson, quiz, assignment IDs by course ID.
						case 'topic':
							$content_ids = $wpdb->get_col(
								"SELECT content.ID FROM {$wpdb->posts} content
								INNER JOIN {$wpdb->posts} topic ON topic.ID=content.post_parent
								WHERE topic.ID IN ({$ancestor_ids})"
							);

							is_array( $content_ids ) ? $ids = $content_ids : 0;
							break;
					}
			}
			TutorCache::set( $cache_key, $ids );
		}

		return $ids;
	}

	/**
	 * Get course element list
	 *
	 * @since 2.0.0
	 *
	 * @param string $content_type, content type like: lesson, assignment, quiz
	 * @param string $ancestor_type, content type like: lesson, assignment, quiz
	 * @param int    $ancestor_ids, post_parent id
	 *
	 * @return array
	 */
	public function get_course_content_list( string $content_type, string $ancestor_type, string $ancestor_ids ) {
		global $wpdb;
		$ids = array();
		// Convert single id to array.
		! is_array( $ancestor_ids ) ? $ancestor_ids = array( $ancestor_ids ) : 0;
		$ancestor_ids                               = implode( ',', $ancestor_ids );
		switch ( $content_type ) {
				// Get lesson, quiz, assignment IDs.
			case tutor()->lesson_post_type:
			case 'tutor_quiz':
			case 'tutor_assignments':
				switch ( $ancestor_type ) {
						// Get lesson, quiz, assignment IDs by course ID.
					case tutor()->course_post_type:
						$content_ids = $wpdb->get_results(
							$wpdb->prepare(
								"SELECT content.* FROM {$wpdb->posts} course
									INNER JOIN {$wpdb->posts} topic ON course.ID = topic.post_parent
									INNER JOIN {$wpdb->posts} content ON topic.ID = content.post_parent AND content.post_type = %s
								WHERE course.ID IN ({$ancestor_ids})
							",
								$content_type
							)
						);

						// Assign id array to the variable.
						$ids = $content_ids;
						break 2;
				}
		}

		return $ids;
	}

	/**
	 * Sanitize array key abd values recursively
	 *
	 * @since 2.0.0
	 *
	 * @param array $array array.
	 * @param array $skip skip.
	 *
	 * @return array
	 */
	public function sanitize_recursively( $array, $skip = array() ) {
		$new_array = array();
		if ( is_array( $array ) && ! empty( $array ) ) {
			foreach ( $array as $key => $value ) {
				$key = is_numeric( $key ) ? $key : sanitize_text_field( $key );
				if ( in_array( $key, $skip ) ) {
					$new_array[ $key ] = wp_kses_post( $value );
					continue;
				} elseif ( is_array( $value ) ) {
					$new_array[ $key ] = $this->sanitize_recursively( $value );
					continue;
				}
				// Leave numeric as it is.
				$new_array[ $key ] = is_numeric( $value ) ? $value : sanitize_text_field( $value );
			}
		}
		return $array;
	}

	/**
	 * Get all courses along with topics & course materials for current student
	 *
	 * @since 1.9.10
	 *
	 * @return array
	 */
	public function course_with_materials(): array {
		$user_id          = get_current_user_id();
		$enrolled_courses = $this->get_enrolled_courses_by_user( $user_id );

		if ( false === $enrolled_courses ) {
			return array();
		}
		$data = array();
		foreach ( $enrolled_courses->posts as $key => $course ) {
			// Push courses.
			array_push( $data, array( 'course' => array( 'title' => $course->post_title ) ) );
			$topics = $this->get_topics( $course->ID );

			if ( ! is_null( $topics ) || count( $topics->posts ) ) {
				foreach ( $topics->posts as $topic_key => $topic ) {
					$materials = $this->get_course_contents_by_topic( $topic->ID, -1 );
					if ( count( $materials->posts ) || ! is_null( $materials->posts ) ) {
						$topic->materials = $materials->posts;
					}
					// Push topics.
					array_push( $data[ $key ]['course'], array( 'topics' => $topic ) );
				}
			}
		}
		return $data;
	}

	/**
	 * Get course duration
	 *
	 * @since 2.0.0
	 *
	 * @param int   $course_id course id.
	 * @param array $return_array return array.
	 * @param array $texts texts.
	 *
	 * @return string
	 */
	public function get_course_duration( $course_id, $return_array, $texts = array(
		'h' => 'hr',
		'm' => 'min',
		's' => 'sec',
	) ) {
		$duration        = maybe_unserialize( get_post_meta( $course_id, '_course_duration', true ) );
		$durationHours   = $this->avalue_dot( 'hours', $duration );
		$durationMinutes = $this->avalue_dot( 'minutes', $duration );
		$durationSeconds = $this->avalue_dot( 'seconds', $duration );

		if ( $return_array ) {
			return array(
				'duration'        => $duration,
				'durationHours'   => $durationHours,
				'durationMinutes' => $durationMinutes,
				'durationSeconds' => $durationSeconds,
			);
		}

		if ( ! $durationHours && ! $durationMinutes && ! $durationSeconds ) {
			return '';
		}

		return $durationHours . $texts['h'] . ' ' .
			$durationMinutes . $texts['m'] . ' ' .
			$durationSeconds . $texts['s'];
	}

	/**
	 * Prepare free addons data
	 *
	 * @since 2.0.0
	 *
	 * @return array
	 */
	public function prepare_free_addons_data() {
		$addons       = apply_filters( 'tutor_pro_addons_lists_for_display', array() );
		$plugins_data = $addons;

		$addons_config = get_option( 'tutor_addons_config' );
		$has_pro       = tutor()->has_pro;

		if ( is_array( $addons ) && count( $addons ) ) {
			foreach ( $addons as $base_name => $addon ) {

				$addons_path = trailingslashit( tutor()->path . "assets/images/addons/{$base_name}" );
				$addons_url  = trailingslashit( tutor()->url . "assets/images/addons/{$base_name}" );

				$thumbnailURL = tutor()->url . 'assets/images/tutor-plugin.png';
				if ( file_exists( $addons_path . 'thumbnail.png' ) ) {
					$thumbnailURL = $addons_url . 'thumbnail.png';
				} elseif ( file_exists( $addons_path . 'thumbnail.jpg' ) ) {
					$thumbnailURL = $addons_url . 'thumbnail.jpg';
				} elseif ( file_exists( $addons_path . 'thumbnail.svg' ) ) {
					$thumbnailURL = $addons_url . 'thumbnail.svg';
				}

				$plugins_data[ $base_name ]['url'] = $thumbnailURL;

				// Add add-on enable status.
				$addon_url = "tutor-pro/addons/{$base_name}/{$base_name}.php";

				$plugins_data[ $base_name ]['base_name']  = $base_name;
				$plugins_data[ $base_name ]['is_enabled'] = $has_pro && isset( $addons_config[ $addon_url ]['is_enable'] ) ? (int) $addons_config[ $addon_url ]['is_enable'] : 0;
			}
		}

		$prepared_addons = array();
		foreach ( $plugins_data as $tutor_addon ) {
			array_push( $prepared_addons, $tutor_addon );
		}

		return $prepared_addons;
	}

	/**
	 * Get completed assignment number
	 *
	 * @since 2.0.0
	 *
	 * @param int $course_id course id | required.
	 * @param int $student_id student id | required.
	 *
	 * @return int
	 */
	public function get_submitted_assignment_count( int $assignment_id, int $student_id ): int {
		global $wpdb;
		$assignments = $wpdb->get_var(
			$wpdb->prepare(
				" SELECT COUNT(*) FROM {$wpdb->posts} AS assignment
				INNER JOIN {$wpdb->posts} AS topic
					ON topic.ID = assignment.post_parent
				INNER JOIN {$wpdb->posts} AS course
					ON course.ID = topic.post_parent
				INNER JOIN {$wpdb->comments} AS submit
					ON submit.comment_post_ID = assignment.ID
				WHERE assignment.post_type = %s
					AND assignment.ID = %d
					AND submit.user_id = %d
			",
				'tutor_assignments',
				$assignment_id,
				$student_id
			)
		);
		return $assignments;
	}

	/**
	 * Get completed assignment number
	 *
	 * @since 2.0.0
	 *
	 * @param int $course_id course id | required.
	 * @param int $student_id student id | required.
	 *
	 * @return int
	 */
	public function count_completed_assignment( int $course_id, int $student_id ): int {
		global $wpdb;
		$count = $wpdb->get_var(
			$wpdb->prepare(
				" SELECT COUNT(*) FROM {$wpdb->posts} AS assignment
				INNER JOIN {$wpdb->posts} AS topic
					ON topic.ID = assignment.post_parent
				INNER JOIN {$wpdb->posts} AS course
					ON course.ID = topic.post_parent
				INNER JOIN {$wpdb->comments} AS submit
					ON submit.comment_post_ID = assignment.ID
				WHERE assignment.post_type = %s
					AND course.ID = %d
					AND submit.user_id = %d
			",
				'tutor_assignments',
				$course_id,
				$student_id
			)
		);
		return $count ? $count : 0;
	}

	/**
	 * Empty state template
	 *
	 * @since 2.0.0
	 *
	 * @param string $title title.
	 *
	 * @return mixed html
	 */
	public function tutor_empty_state( string $title = 'No data yet!' ) {
		?>
		<div class="tutor-empty-state td-empty-state tutor-p-32 tutor-text-center">
			<img src="<?php echo esc_url( tutor()->url . 'assets/images/emptystate.svg' ); ?>" alt="<?php esc_attr_e( $title ); ?>" width="85%" />
			<div class="tutor-fs-6 tutor-color-secondary tutor-text-center">
				<?php echo esc_html( $title, 'tutor' ); ?>
			</div>
		</div>
		<?php
	}

	/**
	 * Get tutor TOC page link
	 * Settings > General > Terms and Conditions Page
	 *
	 * @since 2.0.5
	 *
	 * @return null | string
	 */
	function get_toc_page_link() {
		$tutor_toc_page_id   = (int) get_tutor_option( 'tutor_toc_page_id' );
		$tutor_toc_page_link = null;

		if ( ! in_array( $tutor_toc_page_id, array( 0, -1 ) ) ) {
			$tutor_toc_page_link = get_page_link( $tutor_toc_page_id );
		}

		return $tutor_toc_page_link;
	}

	/**
	 * Get tutor Privacy Policay page link
	 * Settings > General > Privacy Policy
	 *
	 * @since 3.0.0
	 *
	 * @return null | string
	 */
	function get_privacy_page_link() {
		// Get wp privacy poicay page
		$privacy_policy_url = get_privacy_policy_url();

		$tutor_privacy_page_id   = (int) get_tutor_option( 'ecommerce_privacy_policy' );
		$tutor_privacy_page_link = null;

		if ( ! in_array( $tutor_privacy_page_id, array( 0, -1 ) ) ) {
			$tutor_privacy_page_link = get_page_link( $tutor_privacy_page_id );
		} elseif ( $privacy_policy_url ) {
			$tutor_privacy_page_link = $privacy_policy_url;
		}

		return $tutor_privacy_page_link;
	}

	/**
	 * Translate dynamic text, dynamic text is not translate while potting
	 * that's why define key here to make it translate able. It will put text in the pot file while compilling.
	 *
	 * @since 2.0.0
	 *
	 * @param string $key, pass key to get translate text | required.
	 *
	 * @return string
	 */
	public function translate_dynamic_text( $key, $add_badge = false, $badge_tag = 'span' ): string {
		$old_key = $key;
		$key     = trim( strtolower( $key ) );

		$key_value = tutor_get_translate_text();

		if ( $add_badge && isset( $key_value[ $key ] ) ) {
			return '<' . $badge_tag . ' class="tutor-badge-label label-' . $key_value[ $key ]['badge'] . '">' .
				$key_value[ $key ]['text'] .
				'</' . $badge_tag . '>';
		}

		// Revert to linear textual array.
		$key_value = array_map(
			function ( $kv ) {
				return $kv['text'];
			},
			$key_value
		);

		return isset( $key_value[ $key ] ) ? $key_value[ $key ] : $old_key;
	}

	/**
	 * Show character as asterisk symbol for email
	 * it will replace character with asterisk till @ symbol
	 *
	 * @since 2.0.0
	 *
	 * @param string $email | required.
	 *
	 * @return string
	 */
	function asterisks_email( string $email ): string {
		if ( '' === $email ) {
			return '';
		}
		$mail_part    = explode( '@', $email );
		$mail_part[0] = str_repeat( '*', strlen( $mail_part[0] ) );
		return $mail_part[0] . $mail_part[1];
	}

	/**
	 * Show some character as asterisk symbol
	 * it will replace character with asterisk from the beginning and ending
	 *
	 * @since 2.0.0
	 *
	 * @param string $text | required.
	 *
	 * @return string
	 */
	function asterisks_center_text( string $str ): string {
		if ( '' === $str ) {
			return '';
		}
		$str_length = strlen( $str );
		return substr( $str, 0, 2 ) . str_repeat( '*', $str_length - 2 ) . substr( $str, $str_length - 2, 2 );
	}

	/**
	 * Report frequencies that will be shown on the dropdown
	 *
	 * @since 2.0.0
	 *
	 * @return array
	 */
	public function report_frequencies() {
		$frequencies = array(
			'alltime'     => __( 'All Time', 'tutor-pro' ),
			'today'       => __( 'Today', 'tutor-pro' ),
			'last30days'  => __( 'Last 30 Days', 'tutor-pro' ),
			'last90days'  => __( 'Last 90 Days', 'tutor-pro' ),
			'last365days' => __( 'Last 365 Days', 'tutor-pro' ),
			'custom'      => __( 'Custom', 'tutor-pro' ),
		);
		return $frequencies;
	}

	/**
	 * Add interval days with today date. For ex: 10 days add with today
	 *
	 * @since 2.0.0
	 *
	 * @param string $interval | required.
	 */
	public function add_days_with_today( $interval ) {
		$today    = date_create( date( 'Y-m-d' ) );
		$add_days = date_add( $today, date_interval_create_from_date_string( $interval ) );
		return $add_days;
	}

	/**
	 * Subtract interval days from today date. For ex: 10 days back from today
	 *
	 * @since 2.0.0
	 *
	 * @param string $interval | required.
	 *
	 * @return mixed
	 */
	public function sub_days_with_today( $interval ) {
		$today    = date_create( date( 'Y-m-d' ) );
		$add_days = date_sub( $today, date_interval_create_from_date_string( $interval ) );
		return $add_days;
	}

	/**
	 * Get renderable column list for tables based on context
	 *
	 * @since 2.0.0
	 *
	 * @param string $page_key page key.
	 * @param string $context context.
	 * @param array  $contexts contexts.
	 * @param mixed  $filter_hook filter hook.
	 *
	 * @return array
	 */
	public function get_table_columns_from_context( $page_key, $context, $contexts, $filter_hook = null ) {

		$fields                 = array();
		$columns                = $contexts[ $page_key ]['columns'];
		$filter_hook ? $columns = apply_filters( $filter_hook, $contexts[ $page_key ]['columns'] ) : 0;

		$allowed                         = $contexts[ $page_key ]['contexts'][ $context ];
		is_string( $allowed ) ? $allowed = $contexts[ $page_key ]['contexts'][ $allowed ] : 0; // By reference.

		if ( $allowed === true ) {
			$fields = $columns;
		} else {
			foreach ( $columns as $key => $column ) {
				in_array( $key, $allowed ) ? $fields[ $key ] = $column : 0;
			}
		}

		return $fields;
	}

	/**
	 * Check a user has attempted a quiz
	 *
	 * @since 2.0.0
	 *
	 * @param string $user_id | user that taken course.
	 * @param string $quiz_id | quiz id that need to check wheather attempted or not.
	 *
	 * @return bool | true if attempted otherwise false.
	 */
	public function has_attempted_quiz( $user_id, $quiz_id, $row = false ) {
		global $wpdb;
		// Sanitize data.
		$user_id   = sanitize_text_field( $user_id );
		$quiz_id   = sanitize_text_field( $quiz_id );
		$attempted = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT quiz_id
				FROM {$wpdb->tutor_quiz_attempts}
				WHERE user_id = %d
					AND quiz_id = %d
			",
				$user_id,
				$quiz_id
			)
		);
		return $attempted ? true : false;
	}

	/**
	 * Course nav items
	 *
	 * @since 2.0.0
	 *
	 * @return mixed
	 */
	public function course_nav_items() {
		/**
		 * If current user has course content then enrollment is not
		 * required
		 *
		 * @since 2.0.6
		 */
		$is_require_enrollment = ! $this->has_user_course_content_access();
		$array                 = array(
			'info'          => array(
				'title'  => __( 'Course Info', 'tutor' ),
				'method' => 'tutor_course_info_tab',
			),
			'reviews'       => array(
				'title'  => __( 'Reviews', 'tutor' ),
				'method' => 'tutor_course_target_reviews_html',
			),
			'questions'     => array(
				'title'             => __( 'Q&A', 'tutor' ),
				'method'            => 'tutor_course_question_and_answer',
				'require_enrolment' => $is_require_enrollment,
			),
			'announcements' => array(
				'title'             => __( 'Announcements', 'tutor' ),
				'method'            => 'tutor_course_announcements',
				'require_enrolment' => $is_require_enrollment,
			),
		);
		return $array;
	}

	/**
	 * Second to formated time.
	 *
	 * @since 2.0.0
	 *
	 * @param string $seconds seconds.
	 * @param string $type type.
	 *
	 * @return DateInterval|false
	 */
	public function second_to_formated_time( $seconds, $type = null ) {

		$dtF = new \DateTime( '@0' );
		$dtT = new \DateTime( "@$seconds" );

		switch ( $type ) {

			case 'days':
				$format = '%ad %hh';
				break;

			case 'hours':
				$format = '%d' > 0 ? '%hh %im  %ss' : '%im %ss';
				$format = '%h' > 0 ? '%im  %ss' : $format;
				break;

			case 'minutes':
				$format = '%im  %ss';
				break;

			default:
				$format = '%im  %ss';
				break;
		}

		return $dtF->diff( $dtT )->format( $format );
	}

	/**
	 * Convert seconds to time.
	 *
	 * @since 2.0.0
	 *
	 * @param int $input_seconds seconds.
	 *
	 * @return string
	 */
	public function seconds_to_time( $input_seconds ) {
		$seconds_in_a_minute = 60;
		$seconds_in_an_hour  = 60 * $seconds_in_a_minute;
		$seconds_in_a_day    = 24 * $seconds_in_an_hour;

		// Extract days.
		$days = floor( $input_seconds / $seconds_in_a_day );

		// Extract hours.
		$hour_seconds = $input_seconds % $seconds_in_a_day;
		$hours        = floor( $hour_seconds / $seconds_in_an_hour );

		// Extract minutes.
		$minute_seconds = $hour_seconds % $seconds_in_an_hour;
		$minutes        = floor( $minute_seconds / $seconds_in_a_minute );

		// Extract the remaining seconds.
		$remaining_seconds = $minute_seconds % $seconds_in_a_minute;
		$seconds           = ceil( $remaining_seconds );

		// Format and return.
		$time_parts = array();
		$sections   = array(
			'day'    => (int) $days,
			'hour'   => (int) $hours,
			'minute' => (int) $minutes,
			'second' => (int) $seconds,
		);

		foreach ( $sections as $unit => $value ) {
			if ( $value > 0 ) {
				$unit_name    = $unit . ( $value == 1 ? '' : 's' );
				$time_parts[] = $value . ' ' . $this->translate_dynamic_text( $unit_name );
			}
		}

		return implode( ', ', $time_parts );
	}

	/**
	 * Get quiz time duration in seconds
	 *
	 * @since 2.0.0
	 *
	 * @param string $time_type | supported time type : seconds, minutes, hours, days, weeks.
	 * @param int    $time_value | quiz duration.
	 *
	 * @return int | quiz time duration in seconds
	 */
	public function quiz_time_duration_in_seconds( string $time_type, int $time_value ): int {
		if ( 'seconds' === $time_type ) {
			return (int) $time_value;
		}
		$time_unit_seconds = 0;
		switch ( $time_type ) {
			case 'minutes':
				$time_unit_seconds = 60;
			case 'hours':
				$time_unit_seconds = 3600;
			case 'days':
				$time_unit_seconds = 24 * 3600;
			case 'weeks':
				$time_unit_seconds = 7 * 86400;
			default:
				break;
		}
		$quiz_duration_in_seconds = $time_unit_seconds * $time_value;
		return (int) $quiz_duration_in_seconds;
	}

	/**
	 * Get all contents (lesosn, assignment, zoom, quiz etc) that belong to this topic
	 *
	 * @since 2.0.0
	 *
	 * @param int $topic_id | topic id.
	 *
	 * @return array of objects on success | false on failure.
	 */
	public function get_contents_by_topic( int $topic_id ) {
		global $wpdb;
		$topic_id = sanitize_text_field( $topic_id );
		$contents = $wpdb->get_results(
			$wpdb->prepare(
				" SELECT content.ID, content.post_title, content.post_type
				FROM {$wpdb->posts} AS topics
					INNER JOIN {$wpdb->posts} AS content
						ON content.post_parent = topics.ID
				WHERE topics.post_type = 'topics'
					AND topics.ID = %d
					AND content.post_status = %s
			",
				$topic_id,
				'publish'
			)
		);
		return $contents;
	}

	/**
	 * Get total number of contents & completed contents that belongs to this topic.
	 *
	 * @since 2.0.0
	 *
	 * @param int $topic_id | all contents will be checked that belong to this topic.
	 *
	 * @return array counted number of contents & completed contents number.
	 */
	public function count_completed_contents_by_topic( int $topic_id ): array {
		$topic_id  = sanitize_text_field( $topic_id );
		$contents  = $this->get_contents_by_topic( $topic_id );
		$user_id   = get_current_user_id();
		$completed = 0;

		$lesson_post_type      = 'lesson';
		$quiz_post_type        = 'tutor_quiz';
		$assignment_post_type  = 'tutor_assignments';
		$zoom_lesson_post_type = 'tutor_zoom_meeting';
		$google_meet_post_type = 'tutor-google-meet';

		if ( $contents ) {
			foreach ( $contents as $content ) {
				switch ( $content->post_type ) {
					case $lesson_post_type:
						$is_lesson_completed = $this->is_completed_lesson( $content->ID, $user_id );
						if ( $is_lesson_completed ) {
							++$completed;
						}
						break;
					case $quiz_post_type:
						$has_attempt = $this->has_attempted_quiz( $user_id, $content->ID );
						if ( $has_attempt ) {
							++$completed;
						}
						break;
					case $assignment_post_type:
						$is_assignment_completed = $this->is_assignment_submitted( $content->ID, $user_id );
						if ( $is_assignment_completed ) {
							++$completed;
						}
						break;
					case $zoom_lesson_post_type:
						if ( \class_exists( '\TUTOR_ZOOM\Zoom' ) ) {
							$is_zoom_lesson_completed = \TUTOR_ZOOM\Zoom::is_zoom_lesson_done( '', $content->ID, $user_id );
							if ( $is_zoom_lesson_completed ) {
								++$completed;
							}
						}
						break;
					case $google_meet_post_type:
						if ( \class_exists( '\TutorPro\GoogleMeet\Frontend\Frontend' ) ) {
							if ( \TutorPro\GoogleMeet\Validator\Validator::is_addon_enabled() ) {
								$is_completed = \TutorPro\GoogleMeet\Frontend\Frontend::is_lesson_completed( false, $content->ID, $user_id );
								if ( $is_completed ) {
									++$completed;
								}
							}
						}
						break;
					default:
						break;
				}
			}
		}
		return array(
			'contents'  => is_array( $contents ) ? count( $contents ) : 0,
			'completed' => $completed,
		);
	}

	/**
	 * Text message for the list tables that will be visible
	 * if no record found or filter data not found
	 *
	 * @since 2.0.0
	 *
	 * @return string | not found text
	 */
	public function not_found_text(): string {
		// phpcs:disable WordPress.Security.NonceVerification.Missing
		$course   = isset( $_GET['course-id'] ) ? true : false;
		$date     = isset( $_GET['date'] ) ? true : false;
		$search   = isset( $_GET['search'] ) ? true : false;
		$category = isset( $_GET['category'] ) ? true : false;
		$text     = array(
			'normal' => __( 'No Data Available in this Section', 'tutor' ),
			'filter' => __( 'No Data Found from your Search/Filter', 'tutor' ),
		);

		if ( $course || $date || $search || $category ) {
			return $text['filter'];
		} else {
			return $text['normal'];
		}
	}

	/**
	 * Separation of all menu items for providing ease of usage
	 *
	 * @since 2.0.0
	 *
	 * @return array array of menu items.
	 */
	public function instructor_menus(): array {
		$menus = array(
			'separator-1'   => array(
				'title'    => __( 'Instructor', 'tutor' ),
				'auth_cap' => tutor()->instructor_role,
				'type'     => 'separator',
			),
			'create-course' => array(
				'title'    => __( 'Create Course', 'tutor' ),
				'show_ui'  => false,
				'auth_cap' => tutor()->instructor_role,
			),
			'create-bundle' => array(
				'title'    => __( 'Create Bundle', 'tutor' ),
				'show_ui'  => false,
				'auth_cap' => tutor()->instructor_role,
			),
			'my-courses'    => array(
				'title'    => __( 'My Courses', 'tutor' ),
				'auth_cap' => tutor()->instructor_role,
				'icon'     => 'tutor-icon-rocket',
			),
		);

		$menus = apply_filters( 'tutor_after_instructor_menu_my_courses', $menus );

		$other_menus = array(
			'announcements' => array(
				'title'    => __( 'Announcements', 'tutor' ),
				'auth_cap' => tutor()->instructor_role,
				'icon'     => 'tutor-icon-bullhorn',
			),
			'withdraw'      => array(
				'title'    => __( 'Withdrawals', 'tutor' ),
				'auth_cap' => tutor()->instructor_role,
				'icon'     => 'tutor-icon-wallet',
			),
			'quiz-attempts' => array(
				'title'    => __( 'Quiz Attempts', 'tutor' ),
				'auth_cap' => tutor()->instructor_role,
				'icon'     => 'tutor-icon-quiz-o',
			),
		);

		return array_merge( $menus, $other_menus );
	}


	/**
	 * Separation of all menu items for providing ease of usage
	 *
	 * @since 2.0.0
	 *
	 * @return array array of menu items.
	 */
	public function default_menus(): array {
		$items = array(
			'index'            => array(
				'title' => __( 'Dashboard', 'tutor' ),
				'icon'  => 'tutor-icon-dashboard',
			),
			'my-profile'       => array(
				'title' => __( 'My Profile', 'tutor' ),
				'icon'  => 'tutor-icon-user-bold',
			),
			'enrolled-courses' => array(
				'title' => __( 'Enrolled Courses', 'tutor' ),
				'icon'  => 'tutor-icon-mortarboard-o',
			),
			'wishlist'         => array(
				'title' => __( 'Wishlist', 'tutor' ),
				'icon'  => 'tutor-icon-bookmark-bold',
			),
			'reviews'          => array(
				'title' => __( 'Reviews', 'tutor' ),
				'icon'  => 'tutor-icon-star-bold',
			),
			'my-quiz-attempts' => array(
				'title' => __( 'My Quiz Attempts', 'tutor' ),
				'icon'  => 'tutor-icon-quiz-attempt',
			),
		);

		$items['purchase_history'] = array(
			'title' => __( 'Order History', 'tutor' ),
			'icon'  => 'tutor-icon-cart-bold',
		);

		$items = apply_filters( 'tutor_after_order_history_menu', $items );

		$items['question-answer'] = array(
			'title' => __( 'Question & Answer', 'tutor' ),
			'icon'  => 'tutor-icon-question',
		);

		return $items;
	}

	/**
	 * Default config for tutor text editor
	 * Modify default param from here and pass to render_text_editor() method
	 *
	 * @since 2.0.0
	 *
	 * @param $args array  array of arguments.
	 *
	 * @return array default config.
	 */
	public function text_editor_config( $args = array() ) {
		$default_args = array(
			'textarea_name'     => 'tutor-global-text-editor',
			'plugins'           => 'image',
			'tinymce'           => array(
				'toolbar1' => 'bold,italic,underline,link,unlink,removeformat,image,bullist',
				'toolbar2' => '',
				'toolbar3' => '',
			),
			'file_picker_types' => 'image',
			'media_buttons'     => false,
			'drag_drop_upload'  => false,
			'quicktags'         => false,
			'elementpath'       => false,
			'wpautop'           => false,
			'statusbar'         => false,
			'editor_height'     => 112,
			'editor_css'        => '<style>
				#wp-tutor-global-text-editor-wrap div.mce-toolbar-grp {
					background-color: #fff;
				}
			</style>',
		);
		return wp_parse_args( $args, $default_args );
	}

	/**
	 * Get config for profile bio editor.
	 *
	 * @since 2.2.4
	 *
	 * @param string $textarea_name textarea name for post request.
	 *
	 * @return array
	 */
	public function get_profile_bio_editor_config( $textarea_name = 'tutor_profile_bio' ) {
		return $this->text_editor_config(
			array(
				'textarea_name' => $textarea_name,
				'tinymce'       => array(
					'toolbar1' => 'bold,italic,underline,blockquote,bullist,numlist,alignleft,aligncenter,alignright,undo,redo,removeformat',
					'toolbar2' => '',
					'toolbar3' => '',
				),
			)
		);
	}

	/**
	 * Get video sources.
	 *
	 * @since 2.0.0
	 *
	 * @param boolean $key_title_only key title only.
	 *
	 * @return array
	 */
	public function get_video_sources( bool $key_title_only ) {

		$video_sources = array(
			'html5'        => array(
				'title' => __( 'HTML 5 (mp4)', 'tutor' ),
				'icon'  => 'html5',
			),
			'external_url' => array(
				'title' => __( 'External URL', 'tutor' ),
				'icon'  => 'external_url',
			),
			'youtube'      => array(
				'title' => __( 'YouTube', 'tutor' ),
				'icon'  => 'youtube',
			),
			'vimeo'        => array(
				'title' => __( 'Vimeo', 'tutor' ),
				'icon'  => 'vimeo',
			),
			'embedded'     => array(
				'title' => __( 'Embedded', 'tutor' ),
				'icon'  => 'embedded',
			),
			'shortcode'    => array(
				'title' => __( 'Shortcode', 'tutor' ),
				'icon'  => 'code',
			),
		);
		$video_sources = apply_filters( 'tutor_preferred_video_sources', $video_sources );

		if ( $key_title_only ) {
			foreach ( $video_sources as $key => $data ) {
				$video_sources[ $key ] = $data['title'];
			}
		}
		return $video_sources;
	}

	/**
	 * Convert date to wp timezone compatible date. Timezone will be get from settings
	 * NOTE: date_i18n translate able string is not supported
	 *
	 * @since 2.0.0
	 * @since 2.2.5 $format param added to modify the format if required.
	 *
	 * @param string $date string date time to convert.
	 * @param string $format format of date time.
	 *
	 * @return string formated date-time.
	 */
	public function convert_date_into_wp_timezone( string $date, string $format = null ): string {
		$date = new \DateTime( $date );
		$date->setTimezone( wp_timezone() );
		return $date->format( ! is_null( $format ) ? $format : get_option( 'date_format' ) . ', ' . get_option( 'time_format' ) );
	}

	/**
	 * Tutor custom header.
	 *
	 * @since 2.0.0
	 *
	 * @return void
	 */
	public function tutor_custom_header() {
		global $wp_version;
		if ( version_compare( $wp_version, '5.9', '>=' ) && function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() ) {
			?>
			<!doctype html>
				<html <?php language_attributes(); ?>>
				<head>
					<meta charset="<?php bloginfo( 'charset' ); ?>">
					<?php wp_head(); ?>
				</head>
				<body <?php body_class(); ?>>
				<?php wp_body_open(); ?>
					<div class="wp-site-blocks">
					<?php
						$theme      = wp_get_theme();
						$theme_slug = $theme->get( 'TextDomain' );
						echo do_blocks( '<!-- wp:template-part {"slug":"header","theme":"' . $theme_slug . '","tagName":"header","className":"site-header","layout":{"inherit":true}} /-->' );
		} else {
			get_header();
		}
	}

	/**
	 * Tutor Custom Header
	 *
	 * @since 2.0.0
	 */
	public function tutor_custom_footer() {
		global $wp_version;
		if ( version_compare( $wp_version, '5.9', '>=' ) && function_exists( 'wp_is_block_theme' ) && true === wp_is_block_theme() ) {
			$theme      = wp_get_theme();
			$theme_slug = $theme->get( 'TextDomain' );
			echo do_blocks( '<!-- wp:template-part {"slug":"footer","theme":"' . $theme_slug . '","tagName":"footer","className":"site-footer","layout":{"inherit":true}} /-->' );
			echo '</div>';
			wp_footer();
			echo '</body>';
			echo '</html>';
		} else {
			get_footer();
		}
	}

	/**
	 * Can user retake course.
	 *
	 * @since 2.0.0
	 *
	 * @return boolean
	 */
	public function can_user_retake_course() {
		if ( ! $this->is_enrolled() ) {
			return false;
		}

		$completed_lessons   = $this->get_completed_lesson_count_by_course();
		$completed_percent   = $this->get_course_completed_percent();
		$is_completed_course = $this->is_completed_course();
		$retake_course       = $this->get_option( 'course_retake_feature', false ) && ( $is_completed_course || $completed_percent >= 100 );

		return $retake_course;
	}


	/**
	 * Clean unnecessary html code from the content
	 *
	 * @since 2.0.1
	 *
	 * @param string $content content.
	 * @param array  $allowed allowed.
	 *
	 * @return string
	 */
	public function clean_html_content( $content = '', $allowed = array() ) {

		$default = array(
			'div'    => array(
				'class' => 1,
				'style' => 1,
			),
			'b'      => array( 'style' => 1 ),
			'strong' => array( 'style' => 1 ),
			'i'      => array( 'style' => 1 ),
			'u'      => array( 'style' => 1 ),
			'h1'     => array( 'style' => 1 ),
			'h2'     => array( 'style' => 1 ),
			'h3'     => array( 'style' => 1 ),
			'h4'     => array( 'style' => 1 ),
			'h5'     => array( 'style' => 1 ),
			'h6'     => array( 'style' => 1 ),
			'a'      => array(
				'href'   => array(
					'minlen' => 3,
					'maxlen' => 100,
				),
				'target' => 1,
				'style'  => 1,
			),
			'p'      => array( 'style' => 1 ),
			'img'    => array(
				'src'   => 1,
				'alt'   => 1,
				'style' => 1,
			),
			'pre'    => array( 'style' => 1 ),
			'ul'     => array( 'style' => 1 ),
			'ol'     => array( 'style' => 1 ),
			'li'     => array( 'style' => 1 ),
		);

		$allowed = wp_parse_args( $allowed, $default );

		return wp_kses( $content, $allowed );
	}

	/**
	 * Get predefined icon
	 *
	 * @since 2.0.2
	 *
	 * @param string $name name.
	 *
	 * @return string
	 */
	public function get_svg_icon( $name = '' ) {

		$json = tutor()->path . 'assets/images/icons.json';

		if ( file_exists( $json ) ) {
			$icons = json_decode( file_get_contents( $json ), true );
			$icon  = isset( $icons[ $name ] ) ? $icons[ $name ] : '';

			if ( isset( $icon['viewBox'] ) && isset( $icon['path'] ) ) {
				$html = '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="' . esc_attr( $icon['viewBox'] ) . '"><path fill="currentColor" d="' . esc_attr( $icon['path'] ) . '" /></svg>';
				return $html;
			}
		}
	}

	/**
	 * Conver Hex to RGB
	 *
	 * @since 2.0.2
	 *
	 * @param string $color color.
	 *
	 * @return string
	 */
	public function hex2rgb( string $color ) {

		$default = '0, 0, 0';

		if ( $color === '' ) {
			return '';
		}

		if ( strpos( $color, 'var(--' ) === 0 ) {
			return preg_replace( '/[^A-Za-z0-9_)(\-,.]/', '', $color );
		}

		// Convert hex to rgb.
		if ( $color[0] == '#' ) {
			$color = substr( $color, 1 );
		} else {
			return $default;
		}

		// Check if color has 6 or 3 characters and get values.
		if ( strlen( $color ) == 6 ) {
			$hex = array( $color[0] . $color[1], $color[2] . $color[3], $color[4] . $color[5] );
		} elseif ( strlen( $color ) == 3 ) {
			$hex = array( $color[0] . $color[0], $color[1] . $color[1], $color[2] . $color[2] );
		} else {
			return $default;
		}

		$rgb = array_map( 'hexdec', $hex );

		return implode( ', ', $rgb );
	}

	/**
	 * Get course builder screen.
	 *
	 * @since 2.0.0
	 *
	 * @return void
	 */
	public function get_course_builder_screen() {
		$builder_screen = null;
		if ( is_admin() ) {
			$screen = get_current_screen();
			if ( is_object( $screen ) && $screen->base == 'post' && $screen->id == tutor()->course_post_type ) {
				$builder_screen = $screen->is_block_editor ? 'gutenberg' : 'classic';
			}
		} elseif ( $this->is_tutor_frontend_dashboard( 'create-course' ) ) {
			$builder_screen = 'frontend';
		}

		return apply_filters( 'tutor_builder_screen', $builder_screen );
	}

	/**
	 * Get total number of course
	 *
	 * @since 2.0.2
	 *
	 * @return int
	 */
	public function get_total_course() {
		global $wpdb;
		$course_post_type = tutor()->course_post_type;

		$sql = "SELECT COUNT(ID) 
		FROM {$wpdb->posts} 
		WHERE post_type = %s 
		AND post_status = %s";

		return $wpdb->get_var( $wpdb->prepare( $sql, $course_post_type, 'publish' ) );
	}

	/**
	 * Get total number of enrolled course
	 *
	 * @since 2.0.2
	 *
	 * @return int
	 */
	public function get_total_enrolled_course() {
		global $wpdb;

		$sql = "SELECT COUNT(DISTINCT enroll.ID)
		FROM {$wpdb->posts} enroll INNER JOIN {$wpdb->posts} course ON enroll.post_parent=course.ID
		WHERE enroll.post_type = 'tutor_enrolled'
		AND enroll.post_status = 'completed'
        AND course.post_type=%s";

		return $wpdb->get_var( $wpdb->prepare( $sql, tutor()->course_post_type ) );
	}

	/**
	 * Get total number of question
	 *
	 * @since 2.0.2
	 *
	 * @return int
	 */
	public function get_total_question() {
		global $wpdb;

		$sql = "SELECT COUNT(DISTINCT question.question_id) 
				FROM {$wpdb->prefix}tutor_quiz_questions question 
					INNER JOIN {$wpdb->posts} quiz ON question.quiz_id=quiz.ID 
					INNER JOIN {$wpdb->posts} topic ON quiz.post_parent=topic.ID 
					INNER JOIN {$wpdb->posts} course ON topic.post_parent=course.ID 
				WHERE course.post_type=%s
				 	AND quiz.post_type='tutor_quiz'";

		return $wpdb->get_var( $wpdb->prepare( $sql, tutor()->course_post_type ) );
	}

	/**
	 * Get total number of review
	 *
	 * @since 2.0.2
	 *
	 * @return int
	 */
	public function get_total_review() {
		global $wpdb;

		$sql = "SELECT COUNT(comment_ID)
		FROM {$wpdb->comments}
		WHERE comment_type = %s
		AND comment_approved = %s ";

		return $wpdb->get_var( $wpdb->prepare( $sql, 'tutor_course_rating', 'approved' ) );
	}

	/**
	 * Assign child count
	 *
	 * @since 2.0.0
	 *
	 * @param array  $course_meta course meta.
	 * @param string $post_type post type.
	 *
	 * @return array
	 */
	private function assign_child_count( array $course_meta, $post_type ) {
		global $wpdb;
		$id_array = array_keys( $course_meta );

		if ( ! count( $id_array ) ) {
			return $course_meta;
		}

		$course_ids = implode( ',', $id_array );

		$results = $wpdb->get_results(
			"SELECT ID, post_parent AS course_id 
			FROM {$wpdb->posts} 
			WHERE post_parent IN ({$course_ids}) 
				AND post_type='{$post_type}' 
				AND post_status IN ('completed', 'publish', 'approved')"
		);

		foreach ( $results as $result ) {
			++$course_meta[ $result->course_id ][ $post_type ];
		}

		return $course_meta;
	}

	/**
	 * Get course meta data.
	 *
	 * @param int $course_id course id.
	 *
	 * @return mixed
	 */
	public function get_course_meta_data( $course_id ) {
		global $wpdb;

		// Prepare course IDs to get quiz count based on.
		$course_ids = is_array( $course_id ) ? $course_id : array( $course_id );
		$course_ids = array_map(
			function ( $id ) {
				return (int) $id;
			},
			$course_ids
		);
		$course_ids = implode( ',', $course_ids );

		if ( empty( $course_ids ) ) {
			return array();
		}

		// Get course meta.
		$results = $wpdb->get_results(
			"SELECT DISTINCT course.ID AS course_id, 
					content.ID AS content_id,
					content.post_type AS content_type
			FROM {$wpdb->posts} course
				LEFT JOIN {$wpdb->posts} topic ON course.ID=topic.post_parent
				INNER JOIN {$wpdb->posts} content ON topic.ID=content.post_parent
				LEFT JOIN {$wpdb->posts} enrollment ON course.ID=enrollment.post_parent
			WHERE topic.post_parent IN ($course_ids)"
		);

		// Count contents by course IDs.
		$course_meta = array();
		foreach ( $results as $result ) {
			// Create course key.
			if ( ! array_key_exists( $result->course_id, $course_meta ) ) {
				$course_meta[ $result->course_id ] = array(
					'tutor_assignments' => array(),
					'tutor_quiz'        => array(),
					'lesson'            => array(),
					'topics'            => 0,
					'tutor_enrolled'    => 0,
				);
			}

			// Create content key.
			if ( ! array_key_exists( $result->content_type, $course_meta[ $result->course_id ] ) ) {
				$course_meta[ $result->course_id ][ $result->content_type ] = array();
			}

			try {
				if ( $result->content_id ) {
					$course_meta[ $result->course_id ][ $result->content_type ][] = $result->content_id;
				}
			} catch ( \Throwable $th ) {
				tutor_log( 'Affected course ID : ' . $result->course_id . ' Error : ' . $th->getMessage() );
			}
		}

		// Unify counts.
		foreach ( $course_meta as $index => $meta ) {
			foreach ( $meta as $key => $ids ) {
				$course_meta[ $index ][ $key ] = is_numeric( $ids ) ? $ids : count( array_unique( $ids ) );
			}
		}

		$course_meta = $this->assign_child_count( $course_meta, 'tutor_enrolled' );
		$course_meta = $this->assign_child_count( $course_meta, 'topics' );

		// Return single count if the course id was single.
		if ( ! is_array( $course_id ) ) {
			return isset( $course_meta[ $course_id ] ) ? $course_meta[ $course_id ] : 0;
		}

		return $course_meta;
	}

	/**
	 * Get local time from unix/gmt date
	 *
	 * @since 2.0.1
	 *
	 * @param string $time time.
	 * @param string $date_format date format.
	 *
	 * @return string
	 */
	public function get_local_time_from_unix( $time, $date_format = null ) {
		$output_format = $date_format ? $date_format : get_option( 'date_format' ) . ', ' . get_option( 'time_format' );
		return get_date_from_gmt( $time, $output_format );
	}

	/**
	 * Execute bulk action for enrollment list ex: complete | cancel
	 *
	 * @since 2.0.3
	 * @since 3.2.0 $trigger_hook param added.
	 *
	 * @param string $status hold status for updating.
	 * @param array  $enrollment_ids ids that need to update.
	 * @param bool   $trigger_hook optional - trigger hook or not.
	 *
	 * @return bool
	 */
	public function update_enrollments( string $status, array $enrollment_ids, bool $trigger_hook = true ): bool {
		global $wpdb;
		$enrollment_ids_in = QueryHelper::prepare_in_clause( $enrollment_ids );
		$status            = 'complete' === $status ? 'completed' : $status;
		$post_table        = $wpdb->posts;

		$wpdb->query(
			$wpdb->prepare(
				" UPDATE {$post_table}
				SET post_status = %s
				WHERE ID IN ($enrollment_ids_in)
			",
				$status
			)
		);

		if ( $trigger_hook ) {
			// Run action hook.
			foreach ( $enrollment_ids as $id ) {
				do_action( 'tutor_enrollment/after/' . $status, $id );
			}
		}

		return true;
	}

	/**
	 * Format course content time duration
	 * For ex: lesson video play time, quiz time, assignment time etc.
	 *
	 * @since 2.0.3
	 *
	 * @param string $time_duration time duration.
	 *
	 * @return string
	 */
	public function course_content_time_format( string $time_duration ): string {
		$new_formatted_time  = '';
		$time_duration_array = explode( ':', $time_duration );
		if ( is_array( $time_duration_array ) && count( $time_duration_array ) ) {
			$count_fraction = count( $time_duration_array );
			$first_fraction = (int) $time_duration_array[0];
			if ( 3 === $count_fraction && $first_fraction < 1 ) {
				unset( $time_duration_array[0] );
			}
			foreach ( $time_duration_array as $key => $value ) {
				// If exists hour fraction but not 00 then skip it.
				$new_formatted_time .= sprintf( '%02d', $value ) . ':';
			}
		}
		return rtrim( $new_formatted_time, ':' );
	}

	/**
	 * Check user has course content access.
	 *
	 * @since 2.0.6
	 *
	 * @param integer $user_id user id.
	 * @param integer $course_id course id.
	 *
	 * @return boolean
	 */
	public function has_user_course_content_access( $user_id = 0, $course_id = 0 ) {
		$user_id   = $this->get_user_id( $user_id );
		$course_id = $this->get_post_id( $course_id );

		$is_administrator = $this->has_user_role( 'administrator', $user_id );
		$is_instructor    = $this->is_instructor_of_this_course( $user_id, $course_id );

		$course_content_access = (bool) get_tutor_option( 'course_content_access_for_ia' );
		$has_access            = $course_content_access && ( $is_administrator || $is_instructor );

		return $has_access;
	}

	/**
	 * Get current page slug
	 *
	 * @since 2.1.3
	 *
	 * @return string current page slug
	 */
	public function get_current_page_slug() {
		global $wp_query;
		$current_page = '';
		$query_vars   = $wp_query->query_vars;
		if ( is_admin() && Input::has( 'page' ) ) {
			$current_page = Input::get( 'page' );
		} else {
			$current_page = isset( $query_vars['tutor_dashboard_page'] ) ? sanitize_text_field( $query_vars['tutor_dashboard_page'] ) : '';
		}
		return $current_page;
	}

	/**
	 * Get allowed tags for avatar, useful while using wp_kses
	 *
	 * @since 2.1.4
	 *
	 * @param array $tags additional tags.
	 *
	 * @return array allowed tags
	 */
	public function allowed_avatar_tags( array $tags = array() ): array {
		$defaults = array(
			'a'    => array(
				'href'   => true,
				'class'  => true,
				'id'     => true,
				'target' => true,
			),
			'img'  => array(
				'src'   => true,
				'class' => true,
				'id'    => true,
				'title' => true,
				'alt'   => true,
			),
			'div'  => array(
				'class' => true,
				'id'    => true,
			),
			'span' => array(
				'class' => true,
				'id'    => true,
			),
		);
		return wp_parse_args( $tags, $defaults );
	}

	/**
	 * Get allowed tags for avatar, useful while using wp_kses
	 *
	 * @since 2.1.4
	 *
	 * @param array $tags additional tags.
	 *
	 * @return array allowed tags
	 */
	public function allowed_icon_tags( array $tags = array() ): array {
		$defaults = array(
			'span' => array(
				'class' => true,
				'id'    => true,
			),
			'i'    => array(
				'class' => true,
				'id'    => true,
			),
		);
		return wp_parse_args( $tags, $defaults );
	}

	/**
	 * Get user name to display
	 *
	 * It will return display name if not empty, if empty
	 * then it will return first name & last name or if display
	 * name & user same it will return first & last name (if ot emtpy)
	 * if first & last name empty then it will return user_login name
	 *
	 * @since 2.1.6
	 *
	 * @param integer $user_id user id.
	 *
	 * @return string
	 */
	public function display_name( int $user_id ): string {
		$name      = '';
		$user_data = get_userdata( $user_id );

		if ( is_a( $user_data, 'WP_User' ) ) {
			$display_name = $user_data->display_name;
			$user_name    = $user_data->user_login;
			$custom_name  = trim( trim( $user_data->first_name ) . ' ' . trim( $user_data->last_name ) );

			if ( $display_name ) {
				$name = $display_name === $user_name && $custom_name ? $custom_name : $display_name;
			} else {
				$name = $custom_name ? $custom_name : $user_name;
			}
		}
		return $name;
	}

	/**
	 * Get error message by error code
	 *
	 * @since 2.1.9
	 *
	 * @param string $key error code.
	 *
	 * @return string error message.
	 */
	public function error_message( $key = '401' ) {
		$error_message = __( 'Something went wrong', 'tutor' );

		$error_messages = apply_filters(
			'tutor_default_error_messages',
			array(
				'401'                => __( 'You are not authorzied to perform this action', 'tutor' ),
				'nonce'              => __( 'Nonce not matched. Action failed!', 'tutor' ),
				'invalid_req'        => __( 'Invalid request', 'tutor' ),
				'authentication'     => __( 'Authentication failed', 'tutor' ),
				'authorization'      => __( 'Authorization required', 'tutor' ),
				'not_found'          => __( 'Requested resource not found', 'tutor' ),
				'server_error'       => __( 'Internal server error', 'tutor' ),
				'timeout'            => __( 'Request timed out', 'tutor' ),
				'forbidden'          => __( 'Access to this resource is forbidden', 'tutor' ),
				'method_not_allowed' => __( 'HTTP method not allowed', 'tutor' ),
				'too_many_requests'  => __( 'Too many requests', 'tutor' ),
				'validation_error'   => __( 'Validation error', 'tutor' ),
				'database_error'     => __( 'Database operation failed', 'tutor' ),
				'file_not_found'     => __( 'Requested file not found', 'tutor' ),
				'unsupported_media'  => __( 'Unsupported media type', 'tutor' ),
			)
		);

		if ( array_key_exists( $key, $error_messages ) ) {
			$error_message = $error_messages[ $key ];
		}

		return $error_message;
	}

	/**
	 * Get remote plugin information by plugin slug.
	 *
	 * @since 2.2.4
	 *
	 * @param string $plugin_slug
	 *
	 * @return object|bool if success return object otherwise return false;
	 */
	public function get_remote_plugin_info( $plugin_slug = 'tutor' ) {
		$response = wp_remote_get( "https://api.wordpress.org/plugins/info/1.0/{$plugin_slug}.json" );
		if ( is_wp_error( $response ) ) {
			return false;
		}

		return (object) json_decode( $response['body'], true );
	}

	/**
	 * Get editor list for post content.
	 *
	 * @since 3.0.0
	 *
	 * @param int $post_id post id.
	 *
	 * @return array
	 */
	public function get_editor_list( $post_id ) {
		$editors = array();

		$gutenberg_enabled = (bool) tutor_utils()->get_option( 'enable_gutenberg_course_edit' );
		if ( $gutenberg_enabled ) {
			$name             = 'gutenberg';
			$editors[ $name ] = array(
				'name'  => $name,
				'label' => __( 'Gutenberg', 'tutor' ),
				'link'  => add_query_arg(
					array(
						'post'   => $post_id,
						'action' => 'edit',
					),
					get_admin_url( null, 'post.php' )
				),
			);
		}

		if ( is_plugin_active( 'droip/droip.php' ) ) {
			$name             = 'droip';
			$editors[ $name ] = array(
				'name'  => $name,
				'label' => __( 'Droip', 'tutor' ),
				'link'  => add_query_arg(
					array(
						'action'  => 'droip',
						'post_id' => $post_id,
					),
					get_permalink( $post_id )
				),
			);
		}

		if ( is_plugin_active( 'elementor/elementor.php' ) ) {
			$name             = 'elementor';
			$editors[ $name ] = array(
				'name'  => $name,
				'label' => __( 'Elementor', 'tutor' ),
				'link'  => add_query_arg(
					array(
						'post'   => $post_id,
						'action' => $name,
					),
					get_admin_url( null, 'post.php' )
				),
			);
		}

		return apply_filters( 'tutor_course_builder_editor_list', $editors, $post_id );
	}

	/**
	 * Check which editor is used for edit content.
	 *
	 * @since 3.0.0
	 *
	 * @param int $post_id post id.
	 *
	 * @return string
	 */
	public function get_editor_used( $post_id ) {
		$name   = 'classic';
		$editor = array(
			'name'  => $name,
			'label' => __( 'Classic Editor', 'tutor' ),
			'link'  => '',
		);

		$content = get_post_field( 'post_content', $post_id );
		if ( has_blocks( $content ) ) {
			$name = 'gutenberg';
		}

		if ( 'builder' === get_post_meta( $post_id, '_elementor_edit_mode', true ) ) {
			$name = 'elementor';
		}
		 
		if ( 'droip' === get_post_meta( $post_id, 'droip_editor_mode', true ) ) {
			$name = 'droip';
		}

		$editor_list = $this->get_editor_list( $post_id );
		if ( isset( $editor_list[ $name ] ) ) {
			$editor = $editor_list[ $name ];
		}

		return apply_filters( 'tutor_course_builder_editor_used', $editor, $post_id );
	}

	/**
	 * Upload base64 string image.
	 *
	 * @since 3.0.0
	 *
	 * @param string $base64_image_str base64 image string.
	 * @param string $filename filename.
	 *
	 * @return object consist of id, title, url.
	 *
	 * @throws \Exception If upload failed.
	 */
	public function upload_base64_image( $base64_image_str, $filename = null ) {
		try {
			$arr = explode( ',', $base64_image_str, 2 );
			if ( ! isset( $arr[1] ) ) {
				throw new \Exception( 'Invalid base64 string' );
			}

			$filename   = empty( $filename ) ? uniqid( 'image-' ) . '.png' : $filename;
			$image_data = base64_decode( $arr[1] );
			$uploaded   = wp_upload_bits( $filename, null, $image_data );

			if ( ! empty( $uploaded['error'] ) ) {
				throw new \Exception( $uploaded['error'] );
			}

			$attachment = array(
				'guid'           => $uploaded['url'],
				'post_mime_type' => $uploaded['type'],
				'post_title'     => $filename,
				'post_content'   => '',
				'post_status'    => 'inherit',
			);

			$media_id    = wp_insert_attachment( $attachment, $uploaded['file'] );
			$attach_data = wp_generate_attachment_metadata( $media_id, $uploaded['file'] );
			wp_update_attachment_metadata( $media_id, $attach_data );

			return (object) array(
				'id'    => $media_id,
				'url'   => $uploaded['url'],
				'title' => $filename,
			);

		} catch ( \Exception $e ) {
			throw new \Exception( $e->getMessage() );
		}
	}

	/**
	 * Get readable next cron schedule time.
	 *
	 * @since 3.0.0
	 *
	 * @param string $cron_hook cron hook name.
	 * @param array  $args arguments.
	 *
	 * @return string
	 */
	public function get_readable_next_schedule( $cron_hook, $args = array() ) {
		$next_timestamp = wp_next_scheduled( $cron_hook, $args );
		if ( false === $next_timestamp ) {
			return null;
		}

		return sprintf( __( '%s left', 'tutor' ), human_time_diff( $next_timestamp ) );
	}

	/**
	 * Extract version details.
	 *
	 * @since 3.0.0
	 *
	 * @param string $version version number.
	 *
	 * @return object {
	 *     @property string $version version.
	 *     @property bool $is_stable is stable or not.
	 *     @property int $major marjor version part.
	 *     @property int $minor minor version part.
	 *     @property int $patch patch version part.
	 *     @property string $status status of version, can be beta, RC, alpha or stable.
	 * }
	 */
	public function extract_version_details( $version ) {
		$info = array(
			'version' => $version,
		);

		if ( strpos( $version, 'beta' ) !== false ) {
			$info['status'] = 'beta';
		} elseif ( strpos( $version, 'RC' ) !== false ) {
			$info['status'] = 'RC';
		} elseif ( strpos( $version, 'alpha' ) !== false ) {
			$info['status'] = 'alpha';
		} else {
			$info['status'] = 'stable';
		}

		$info['is_stable'] = 'stable' === $info['status'];

		if ( preg_match( '/^(\d+)\.(\d+)\.(\d+)/', $version, $matches ) ) {
			$info['major'] = (int) $matches[1];
			$info['minor'] = (int) $matches[2];
			$info['patch'] = (int) $matches[3];
		}

		return (object) $info;
	}
}

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