芝麻web文件管理V1.00
编辑当前文件:/home/sditechnicalteam/socialdigivlms.com/wp-content/plugins/tutor/classes/Utils.php
* @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 ''; } 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 ) ) { ?> $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 '
'; print_r( $value ); echo '
'; } /** * 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. * * @since 3.3.0 param $is_complete added to cache key. * * @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}_{$is_complete}"; 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. * * @since 3.4.0 $filters replaced with $args to override the defaults. * * @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 $args Args to override the defaults. * * @return bool|\WP_Query */ public function get_enrolled_courses_by_user( $user_id = 0, $post_status = 'publish', $offset = 0, $posts_per_page = -1, $args = array() ) { $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, ); $course_args = wp_parse_args( $args, $course_args ); $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 * @since 3.3.0 Added $fire_hook parameter. * * @param int $course_id course id. * @param int $order_id order id. * @param int $user_id user id. * @param bool $fire_hook fire hook. * * @return int enrolled id */ public function do_enroll( $course_id = 0, $order_id = 0, $user_id = 0, $fire_hook = true ) { $enrolled_id = 0; if ( ! $course_id ) { return $enrolled_id; } $fire_hook ? do_action( 'tutor_before_enroll', $course_id ) : null; $user_id = $this->get_user_id( $user_id ); $title = __( 'Course Enrolled', 'tutor' ) . ' – ' . 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. $fire_hook ? do_action( 'tutor_after_enroll', $course_id, $is_enrolled ) : null; // 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 ( $fire_hook && '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. * * @since 3.4.0 * * @param array $post_status the post status. * * @return array */ public function get_students_by_instructor( int $instructor_id, int $offset, int $limit, $search_filter = '', $course_id = '', $date_filter = '', $order_by = '', $order = '', $post_status = array( 'publish' ) ): 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"; } $post_status = QueryHelper::prepare_in_clause( $post_status ); $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 IN ({$post_status}) 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, '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 IN ({$post_status}) 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, '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. * * @since 3.4.0 * * @param array $post the post status. * * @return array */ public function get_courses_by_student_instructor_id( int $student_id, int $instructor_id, $post_status = array( 'publish' ) ): array { global $wpdb; $course_post_type = tutor()->course_post_type; $post_status = QueryHelper::prepare_in_clause( $post_status ); $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 IN ({$post_status}) 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, '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 = '
'; for ( $i = 1; $i <= 5; $i++ ) { if ( (int) $current_rating >= $i ) { $output .= '
'; } elseif ( ( $current_rating - $i ) >= -0.5 ) { $output .= '
'; } else { $output .= '
'; } } $output .= '
'; $output .= '
'; 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}"; ?>
'; } ?>
0 ) : ?>
( 1 ? esc_html__( 'Ratings', 'tutor' ) : esc_html__( 'Rating', 'tutor' ) ); ?>)
= $i ) { $output .= '
'; } elseif ( ( $current_rating - $i ) >= -0.5 ) { $output .= '
'; } else { $output .= '
'; } } 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 = '
'; $output .= '
'; if ( is_object( $user ) && $user->tutor_profile_photo && wp_get_attachment_image_url( $user->tutor_profile_photo ) ) { $output .= '
'; } 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 .= '
' . $initial_avatar . '
'; } $output .= '
'; $output .= '
'; 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 '
'; } /** * 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 ); $question->answer_explanation = stripslashes( $question->answer_explanation ); } 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' => '
', 'is_pro' => false, ), 'single_choice' => array( 'name' => __( 'Single Choice', 'tutor' ), 'icon' => '
', 'is_pro' => false, ), 'multiple_choice' => array( 'name' => __( 'Multiple Choice', 'tutor' ), 'icon' => '
', 'is_pro' => false, ), 'open_ended' => array( 'name' => __( 'Open Ended', 'tutor' ), 'icon' => '
', 'is_pro' => false, ), 'fill_in_the_blank' => array( 'name' => __( 'Fill In The Blanks', 'tutor' ), 'icon' => '
', 'is_pro' => false, ), 'short_answer' => array( 'name' => __( 'Short Answer', 'tutor' ), 'icon' => '
', 'is_pro' => true, ), 'matching' => array( 'name' => __( 'Matching', 'tutor' ), 'icon' => '
', 'is_pro' => true, ), 'image_matching' => array( 'name' => __( 'Image Matching', 'tutor' ), 'icon' => '
', 'is_pro' => true, ), 'image_answering' => array( 'name' => __( 'Image Answering', 'tutor' ), 'icon' => '
', 'is_pro' => true, ), 'ordering' => array( 'name' => __( 'Ordering', 'tutor' ), 'icon' => '
', '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' => '
', 'text' => '', 'color' => '#3877EA', ), 'twitter' => array( 'share_class' => 's_twitter', 'icon_html' => '
', 'text' => '', 'color' => '#000000', ), 'linkedin' => array( 'share_class' => 's_linkedin', 'icon_html' => '
', '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 = '$'; 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 '
' . $status_name . '
'; } /** * 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 '
'; } /** * 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 = '
' . __( 'Completed', 'tutor' ) . '
'; } else { $is_in_progress = $this->get_completed_lesson_count_by_course( $course_id, $user_id ); if ( $is_in_progress ) { $html = '
' . __( 'In Progress', 'tutor' ) . '
'; } else { $html = '
' . __( 'Not Taken', 'tutor' ) . '
'; } } 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 * @since 3.4.0 param $is_approved added. * * @param int $instructor_id instructor id. * @param int $course_id course id. * @param bool $is_approved is approved. * * @return bool|int */ public function is_instructor_of_this_course( $instructor_id = 0, $course_id = 0, $is_approved = true ) { 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}_{$is_approved}"; $instructor = TutorCache::get( $cache_key ); if ( false === $instructor ) { if ( $is_approved ) { $is_approved_instructor = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(umeta_id) FROM {$wpdb->usermeta} WHERE user_id = %d AND meta_key = %s AND meta_value = %s", $instructor_id, '_tutor_instructor_status', 'approved', ) ); if ( ! $is_approved_instructor ) { return false; } } //phpcs:disable $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 ) ); //phpcs:enable 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 * @since 3.4.0 param $student_id and $course_id added. * * @param int $assignment_id assignment id. * @param mixed $format format. * @param mixed $fallback fallback. * @param int $student_id the student id. * @param int $course_id the course id. * * @return string|false */ public function get_assignment_deadline_date( $assignment_id, $format = null, $fallback = null, $student_id = 0, $course_id = 0 ) { ! $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; } $deadline_date = null; $enrolled_date_gmt = ''; if ( $course_id && $student_id ) { $enrolled_info = $this->is_enrolled( $course_id, $student_id ); if ( $enrolled_info ) { $enrolled_date_gmt = $enrolled_info->post_date_gmt; } } $publish_date_gmt = get_post_field( 'post_date_gmt', $assignment_id ); $deadline_date = strtotime( $enrolled_date_gmt ) < strtotime( $publish_date_gmt ) ? $publish_date_gmt : $enrolled_date_gmt; $date = date_create( $deadline_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 current_user_can( 'edit_tutor_course', $course_id ) || $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!' ) { ?>
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' ), 'today' => __( 'Today', 'tutor' ), 'last30days' => __( 'Last 30 Days', 'tutor' ), 'last90days' => __( 'Last 90 Days', 'tutor' ), 'last365days' => __( 'Last 365 Days', 'tutor' ), 'custom' => __( 'Custom', 'tutor' ), ); 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' => '', ); 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() ) { ?> >
>
get( 'TextDomain' ); echo do_blocks( '' ); } 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( '' ); echo '
'; wp_footer(); echo ''; echo ''; } 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 = '
'; 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; } /* translators: %s: timestamp */ 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; } /** * Delete enrollment record by providing the student and course id * * @since 3.4.0 * * @param int $student_id Student id. * @param int $course_id Course id * * @return bool */ public function delete_enrollment_record( int $student_id, int $course_id ):bool { global $wpdb; return QueryHelper::delete( $wpdb->posts, array( 'post_author' => $student_id, 'post_parent' => $course_id, 'post_type' => tutor()->enrollment_post_type, ) ); } /** * Delete student course comment by providing the student and course id * * @since 3.4.0 * * @param int $student_id Student id. * @param int $course_id Course id * * @return bool */ public function delete_student_course_comment( int $student_id, int $course_id ): bool { global $wpdb; return QueryHelper::delete( $wpdb->comments, array( 'user_id' => $student_id, 'comment_post_ID' => $course_id, ) ); } }