/home/nbcgowuy/tnclms.com/wp-content/plugins/tutor/models/CourseModel.php
<?php
/**
* Course Model
*
* @package Tutor\Models
* @author Themeum <support@themeum.com>
* @link https://themeum.com
* @since 2.0.6
*/
namespace Tutor\Models;
use TUTOR\Course;
use Tutor\Ecommerce\Tax;
use Tutor\Helpers\QueryHelper;
use TUTOR_ASSIGNMENTS\Assignments;
/**
* CourseModel Class
*
* @since 2.0.6
*/
class CourseModel {
/**
* WordPress course type name
*
* @var string
*/
const POST_TYPE = 'courses';
const COURSE_CATEGORY = 'course-category';
const COURSE_TAG = 'course-tag';
const STATUS_PUBLISH = 'publish';
const STATUS_DRAFT = 'draft';
const STATUS_AUTO_DRAFT = 'auto-draft';
const STATUS_PENDING = 'pending';
const STATUS_PRIVATE = 'private';
const STATUS_FUTURE = 'future';
const STATUS_TRASH = 'trash';
/**
* Course completion modes
*/
const MODE_FLEXIBLE = 'flexible';
const MODE_STRICT = 'strict';
/**
* Course attachment/downloadable resources meta key
*
* @var string
*/
const ATTACHMENT_META_KEY = '_tutor_attachments';
/**
* Course benefits meta key
*
* @var string
*/
const BENEFITS_META_KEY = '_tutor_course_benefits';
/**
* The constant representing the status when a course is completed.
*
* @since 3.8.1
*
* @var string
*/
const COURSE_COMPLETED = 'course_completed';
/**
* Get available status list.
*
* @since 3.0.0
*
* @return array
*/
public static function get_status_list() {
return array(
self::STATUS_DRAFT,
self::STATUS_AUTO_DRAFT,
self::STATUS_PUBLISH,
self::STATUS_PRIVATE,
self::STATUS_FUTURE,
self::STATUS_PENDING,
self::STATUS_TRASH,
);
}
/**
* Course record count
*
* @since 2.0.7
*
* @since 3.6.0 $post_type param added
*
* @param string $status Post status.
* @param string $post_type Post type.
*
* @return int
*/
public static function count( $status = self::STATUS_PUBLISH, $post_type = self::POST_TYPE ) {
$count_obj = wp_count_posts( $post_type );
if ( 'all' === $status ) {
return array_sum( (array) $count_obj );
}
return (int) $count_obj->{$status};
}
/**
* Get tutor post types
*
* @since 3.5.0
*
* @param int|\WP_POST $post the post id or object.
*
* @return bool
*/
public static function get_post_types( $post ) {
return apply_filters( 'tutor_check_course_post_type', self::POST_TYPE === get_post_type( $post ), get_post_type( $post ) );
}
/**
* Get courses
*
* @since 1.0.0
*
* @param array $excludes exclude course ids.
* @param array $post_status post status array.
*
* @return array|null|object
*/
public static function get_courses( $excludes = array(), $post_status = array( 'publish' ) ) {
global $wpdb;
$excludes = (array) $excludes;
$exclude_query = '';
if ( count( $excludes ) ) {
$exclude_query = implode( "','", $excludes );
}
$post_status = array_map(
function ( $element ) {
return "'" . $element . "'";
},
$post_status
);
$post_status = implode( ',', $post_status );
$course_post_type = tutor()->course_post_type;
//phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$query = $wpdb->get_results(
$wpdb->prepare(
"SELECT ID,
post_author,
post_title,
post_name,
post_status,
menu_order
FROM {$wpdb->posts}
WHERE post_status IN ({$post_status})
AND ID NOT IN('$exclude_query')
AND post_type = %s;
",
$course_post_type
)
);
//phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return $query;
}
/**
* Get courses using provided args
*
* If user is not admin then it will return only current user's post
*
* @since 3.0.0
*
* @param array $args Args.
*
* @return \WP_Query
*/
public static function get_courses_by_args( array $args = array() ) {
$default_args = array(
'post_type' => tutor()->course_post_type,
'posts_per_page' => -1,
'post_status' => 'publish',
);
if ( ! current_user_can( 'manage_options' ) ) {
$default_args['author'] = get_current_user_id();
}
$args = wp_parse_args( $args, apply_filters( 'tutor_get_course_list_filter_args', $default_args ) );
return new \WP_Query( $args );
}
/**
* Get course count by instructor
*
* @since 1.0.0
*
* @param int $instructor_id instructor ID.
*
* @return null|string
*/
public static function get_course_count_by_instructor( $instructor_id ) {
global $wpdb;
$course_post_type = tutor()->course_post_type;
$count = $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(ID)
FROM {$wpdb->posts}
INNER JOIN {$wpdb->usermeta}
ON user_id = %d
AND meta_key = %s
AND meta_value = ID
WHERE post_status = %s
AND post_type = %s;
",
$instructor_id,
'_tutor_instructor_course_id',
'publish',
$course_post_type
)
);
return $count;
}
/**
* Get course by quiz
*
* @since 1.0.0
*
* @param int $quiz_id quiz id.
*
* @return array|bool|null|object|void
*/
public static function get_course_by_quiz( $quiz_id ) {
$quiz_id = tutils()->get_post_id( $quiz_id );
$post = get_post( $quiz_id );
if ( $post ) {
$course = get_post( $post->post_parent );
if ( $course ) {
if ( tutor()->course_post_type !== $course->post_type ) {
$course = get_post( $course->post_parent );
}
return $course;
}
}
return false;
}
/**
* Get courses by a instructor
*
* @since 1.0.0
* @since 3.5.0 param $post_types added.
*
* @param integer $instructor_id instructor id.
* @param array|string $post_status post status.
* @param integer $offset offset.
* @param integer $limit limit.
* @param boolean $count_only count or not.
* @param array $post_types array of post types.
*
* @return array|null|object
*/
public static function get_courses_by_instructor( $instructor_id = 0, $post_status = array( 'publish' ), int $offset = 0, int $limit = PHP_INT_MAX, $count_only = false, $post_types = array() ) {
global $wpdb;
$offset = sanitize_text_field( $offset );
$limit = sanitize_text_field( $limit );
$instructor_id = tutils()->get_user_id( $instructor_id );
if ( ! count( $post_types ) ) {
$post_types = array( tutor()->course_post_type );
}
$post_types = QueryHelper::prepare_in_clause( $post_types );
if ( empty( $post_status ) || 'any' == $post_status ) {
$where_post_status = '';
} else {
! is_array( $post_status ) ? $post_status = array( $post_status ) : 0;
$statuses = "'" . implode( "','", $post_status ) . "'";
$where_post_status = "AND $wpdb->posts.post_status IN({$statuses}) ";
}
$select_col = $count_only ? " COUNT(DISTINCT $wpdb->posts.ID) " : " $wpdb->posts.* ";
$limit_offset = $count_only ? '' : " LIMIT $offset, $limit ";
//phpcs:disable
$query = $wpdb->prepare(
"SELECT $select_col
FROM $wpdb->posts
LEFT JOIN {$wpdb->usermeta}
ON $wpdb->usermeta.user_id = %d
AND $wpdb->usermeta.meta_key = %s
AND $wpdb->usermeta.meta_value = $wpdb->posts.ID
WHERE 1 = 1 {$where_post_status}
AND $wpdb->posts.post_type IN ({$post_types})
AND ($wpdb->posts.post_author = %d OR $wpdb->usermeta.user_id = %d)
ORDER BY $wpdb->posts.post_date DESC $limit_offset",
$instructor_id,
'_tutor_instructor_course_id',
$instructor_id,
$instructor_id
);
//phpcs:enable
$query = apply_filters( 'modify_get_courses_by_instructor_query', $query, $instructor_id, $where_post_status, $post_types, $limit_offset, $select_col );
//phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
return $count_only ? $wpdb->get_var( $query ) : $wpdb->get_results( $query, OBJECT );
}
/**
* Get courses for instructors
*
* @since 1.0.0
*
* @param int $instructor_id Instructor ID.
* @return array|null|object
*/
public function get_courses_for_instructors( $instructor_id = 0 ) {
$instructor_id = tutor_utils()->get_user_id( $instructor_id );
$course_post_type = tutor()->course_post_type;
$courses = get_posts(
array(
'post_type' => $course_post_type,
'author' => $instructor_id,
'post_status' => array( 'publish', 'pending' ),
'posts_per_page' => 5,
)
);
return $courses;
}
/**
* Check a user is main instructor of a course
*
* @since 2.1.6
*
* @param integer $course_id course id.
* @param integer $user_id instructor id ( optional ) default: current user id.
*
* @return boolean
*/
public static function is_main_instructor( $course_id, $user_id = 0 ) {
$course = get_post( $course_id );
$user_id = tutor_utils()->get_user_id( $user_id );
if ( ! $course || ! self::get_post_types( $course_id ) || $user_id !== (int) $course->post_author ) {
return false;
}
return true;
}
/**
* Mark the course as completed
*
* @since 2.0.7
*
* @param int $course_id course id which is completed.
* @param int $user_id student id who completed the course.
*
* @return bool
*/
public static function mark_course_as_completed( $course_id, $user_id ) {
if ( ! $course_id || ! $user_id ) {
return false;
}
do_action( 'tutor_course_complete_before', $course_id );
/**
* Marking course completed at Comment.
*/
global $wpdb;
$date = date( 'Y-m-d H:i:s', tutor_time() ); //phpcs:ignore
// Making sure that, hash is unique.
do {
$hash = substr( md5( wp_generate_password( 32 ) . $date . $course_id . $user_id ), 0, 16 );
$has_hash = (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(comment_ID) from {$wpdb->comments}
WHERE comment_agent = 'TutorLMSPlugin' AND comment_type = 'course_completed' AND comment_content = %s ",
$hash
)
);
} while ( $has_hash > 0 );
$data = array(
'comment_post_ID' => $course_id,
'comment_author' => $user_id,
'comment_date' => $date,
'comment_date_gmt' => get_gmt_from_date( $date ),
'comment_content' => $hash, // Identification Hash.
'comment_approved' => 'approved',
'comment_agent' => 'TutorLMSPlugin',
'comment_type' => 'course_completed',
'user_id' => $user_id,
);
$wpdb->insert( $wpdb->comments, $data );
do_action( 'tutor_course_complete_after', $course_id, $user_id );
return true;
}
/**
* Delete a course by ID
*
* @since 2.0.9
*
* @param int $post_id course id that need to delete.
* @return bool
*/
public static function delete_course( $post_id ) {
if ( ! self::get_post_types( $post_id ) ) {
return false;
}
wp_delete_post( $post_id, true );
return true;
}
/**
* Get post ids by post type and parent_id
*
* @since 1.6.6
*
* @param string $post_type post type.
* @param integer $post_parent post parent ID.
*
* @return array
*/
private function get_post_ids( $post_type, $post_parent ) {
$args = array(
'fields' => 'ids',
'post_type' => $post_type,
'post_parent' => $post_parent,
'post_status' => 'any',
'posts_per_page' => -1,
);
return get_posts( $args );
}
/**
* Delete course data when permanently deleting a course.
*
* @since 1.6.6
* @since 2.0.9 updated
*
* @param integer $post_id post ID.
* @return bool
*/
public function delete_course_data( $post_id ) {
$course_post_type = tutor()->course_post_type;
if ( get_post_type( $post_id ) !== $course_post_type ) {
return false;
}
do_action( 'tutor_before_delete_course_content', $post_id, 0 );
global $wpdb;
$lesson_post_type = tutor()->lesson_post_type;
$assignment_post_type = tutor()->assignment_post_type;
$quiz_post_type = tutor()->quiz_post_type;
$topic_ids = $this->get_post_ids( 'topics', $post_id );
// Course > Topic > ( Lesson | Quiz | Assignment ).
if ( ! empty( $topic_ids ) ) {
foreach ( $topic_ids as $topic_id ) {
$content_post_type = array( $lesson_post_type, $assignment_post_type, $quiz_post_type );
$topic_content_ids = $this->get_post_ids( $content_post_type, $topic_id );
foreach ( $topic_content_ids as $content_id ) {
/**
* Delete Quiz data
*/
if ( get_post_type( $content_id ) === 'tutor_quiz' ) {
$wpdb->delete( $wpdb->prefix . 'tutor_quiz_attempts', array( 'quiz_id' => $content_id ) );
$wpdb->delete( $wpdb->prefix . 'tutor_quiz_attempt_answers', array( 'quiz_id' => $content_id ) );
do_action( 'tutor_before_delete_quiz_content', $content_id, null );
$questions_ids = $wpdb->get_col( $wpdb->prepare( "SELECT question_id FROM {$wpdb->prefix}tutor_quiz_questions WHERE quiz_id = %d ", $content_id ) );
if ( is_array( $questions_ids ) && count( $questions_ids ) ) {
$in_question_ids = "'" . implode( "','", $questions_ids ) . "'";
//phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$wpdb->query( "DELETE FROM {$wpdb->prefix}tutor_quiz_question_answers WHERE belongs_question_id IN({$in_question_ids}) " );
}
$wpdb->delete( $wpdb->prefix . 'tutor_quiz_questions', array( 'quiz_id' => $content_id ) );
}
/**
* Delete assignment data ( Assignments, Assignment Submit, Assignment Evalutation )
*
* @since 2.0.9
*/
if ( get_post_type( $content_id ) === $assignment_post_type ) {
QueryHelper::delete_comment_with_meta(
array(
'comment_type' => 'tutor_assignment',
'comment_post_ID' => $content_id,
)
);
}
wp_delete_post( $content_id, true );
}
// Delete zoom meeting.
$wpdb->delete(
$wpdb->posts,
array(
'post_parent' => $topic_id,
'post_type' => 'tutor_zoom_meeting',
)
);
/**
* Delete Google Meet Record Related to Course Topic
*
* @since 2.1.0
*/
$wpdb->delete(
$wpdb->posts,
array(
'post_parent' => $topic_id,
'post_type' => 'tutor-google-meet',
)
);
wp_delete_post( $topic_id, true );
}
}
$child_post_ids = $this->get_post_ids( array( 'tutor_announcements', 'tutor_enrolled', 'tutor_zoom_meeting', 'tutor-google-meet' ), $post_id );
if ( ! empty( $child_post_ids ) ) {
foreach ( $child_post_ids as $child_post_id ) {
wp_delete_post( $child_post_id, true );
}
}
/**
* Delete earning, gradebook result, course complete data
*
* @since 2.0.9
*/
$wpdb->delete( $wpdb->prefix . 'tutor_earnings', array( 'course_id' => $post_id ) );
$wpdb->delete( $wpdb->prefix . 'tutor_gradebooks_results', array( 'course_id' => $post_id ) );
$wpdb->delete(
$wpdb->comments,
array(
'comment_type' => 'course_completed',
'comment_post_ID' => $post_id,
)
);
/**
* Delete onsite notification record & _tutor_instructor_course_id user meta
*
* @since 2.1.0
*/
$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}tutor_notifications WHERE post_id=%d AND type IN ('Announcements','Q&A','Enrollments')", $post_id ) );
$wpdb->delete(
$wpdb->usermeta,
array(
'meta_key' => '_tutor_instructor_course_id',
'meta_value' => $post_id,
)
);
/**
* Delete Course rating and review
*
* @since 2.0.9
*/
QueryHelper::delete_comment_with_meta(
array(
'comment_type' => 'tutor_course_rating',
'comment_post_ID' => $post_id,
)
);
/**
* Delete Q&A and its status ( read, replied etc )
*
* @since 2.0.9
*/
QueryHelper::delete_comment_with_meta(
array(
'comment_type' => 'tutor_q_and_a',
'comment_post_ID' => $post_id,
)
);
/**
* Delete caches
*/
$attempt_cache = new \Tutor\Cache\QuizAttempts();
if ( $attempt_cache->has_cache() ) {
$attempt_cache->delete_cache();
}
return true;
}
/**
* Get paid courses
*
* To identify course is connected with any product
* like WC Product or EDD product meta key will be used
*
* @since 2.2.0
*
* @since 3.0.0
*
* Meta key removed and default meta query updated
*
* @since 3.0.1
* Course::COURSE_PRICE_META meta key exists clause added
*
* @param array $args wp_query args.
*
* @return \WP_Query
*/
public static function get_paid_courses( array $args = array() ) {
$current_user = wp_get_current_user();
$default_args = array(
'post_type' => tutor()->course_post_type,
'posts_per_page' => -1,
'offset' => 0,
'post_status' => 'publish',
'meta_query' => array(
'relation' => 'AND',
array(
'key' => Course::COURSE_PRICE_TYPE_META,
'value' => Course::PRICE_TYPE_SUBSCRIPTION,
'compare' => '!=',
),
array(
'key' => Course::COURSE_PRICE_META,
'compare' => 'EXISTS',
),
),
);
// Check if the current user is an admin.
if ( ! current_user_can( 'administrator' ) ) {
$args['author'] = $current_user->ID;
}
$args = wp_parse_args( $args, $default_args );
return new \WP_Query( $args );
}
/**
* Check the course is completeable or not
*
* @since 2.4.0
*
* @param int $course_id course id.
* @param int $user_id user id.
*
* @return boolean
*/
public static function can_complete_course( $course_id, $user_id ) {
$mode = tutor_utils()->get_option( 'course_completion_process' );
if ( self::MODE_FLEXIBLE === $mode ) {
return true;
}
if ( self::MODE_STRICT === $mode ) {
$completed_lesson = tutor_utils()->get_completed_lesson_count_by_course( $course_id, $user_id );
$lesson_count = tutor_utils()->get_lesson_count_by_course( $course_id, $user_id );
if ( $completed_lesson < $lesson_count ) {
return false;
}
$quizzes = array();
$assignments = array();
$course_contents = tutor_utils()->get_course_contents_by_id( $course_id );
if ( tutor_utils()->count( $course_contents ) ) {
foreach ( $course_contents as $content ) {
if ( 'tutor_quiz' === $content->post_type ) {
$quizzes[] = $content;
}
if ( 'tutor_assignments' === $content->post_type ) {
$assignments[] = $content;
}
}
}
foreach ( $quizzes as $row ) {
$result = QuizModel::get_quiz_result( $row->ID );
if ( 'pass' !== $result ) {
return false;
}
}
if ( tutor()->has_pro ) {
foreach ( $assignments as $row ) {
$result = \TUTOR_ASSIGNMENTS\Assignments::get_assignment_result( $row->ID, $user_id );
if ( 'pass' !== $result ) {
return false;
}
}
}
return true;
}
return false;
}
/**
* Check a course can be auto complete by an enrolled student.
*
* @since 2.4.0
*
* @param int $course_id course id.
* @param int $user_id user id.
*
* @return boolean
*/
public static function can_autocomplete_course( $course_id, $user_id ) {
$auto_course_complete_option = (bool) tutor_utils()->get_option( 'auto_course_complete_on_all_lesson_completion' );
if ( ! $auto_course_complete_option ) {
return false;
}
$is_course_completed = tutor_utils()->is_completed_course( $course_id, $user_id );
if ( $is_course_completed ) {
return false;
}
$course_stats = tutor_utils()->get_course_completed_percent( $course_id, $user_id, true );
if ( $course_stats['total_count'] && $course_stats['completed_count'] === $course_stats['total_count'] ) {
return self::can_complete_course( $course_id, $user_id );
} else {
return false;
}
}
/**
* Get review progress link when course progress 100% and
* User has pending or fail quiz or assignment
*
* @since 2.4.0
*
* @param int $course_id course id.
* @param int $user_id user id.
*
* @return string course content permalink.
*/
public static function get_review_progress_link( $course_id, $user_id ) {
$course_progress = tutor_utils()->get_course_completed_percent( $course_id, $user_id, true );
$completed_percent = (int) $course_progress['completed_percent'];
$course_contents = tutor_utils()->get_course_contents_by_id( $course_id );
$permalink = '';
if ( tutor_utils()->count( $course_contents ) && 100 === $completed_percent ) {
foreach ( $course_contents as $content ) {
if ( 'tutor_quiz' === $content->post_type ) {
$result = QuizModel::get_quiz_result( $content->ID, $user_id );
if ( 'pass' !== $result ) {
$permalink = get_the_permalink( $content->ID );
break;
}
}
if ( tutor()->has_pro && 'tutor_assignments' === $content->post_type ) {
$result = \TUTOR_ASSIGNMENTS\Assignments::get_assignment_result( $content->ID, $user_id );
if ( 'pass' !== $result ) {
$permalink = get_the_permalink( $content->ID );
break;
}
}
}
}
// Fallback link.
if ( empty( $permalink ) ) {
$permalink = tutils()->get_course_first_lesson( $course_id );
}
return $permalink;
}
/**
* Get course preview image placeholder
*
* @since 3.0.0
*
* @return string
*/
public static function get_course_preview_image_placeholder() {
return tutor()->url . 'assets/images/placeholder.svg';
}
/**
* Retrieve the courses or course bundles that a given coupon code applies to.
*
* This function fetches published courses or course bundles from the database
* based on the specified type. For each course, it retrieves the course prices
* and the course thumbnail URL. If the user has Tutor Pro, it additionally
* retrieves the total number of courses in a course bundle.
*
* @since 3.0.0
*
* @param string $applies_to The type of items the coupon applies to. Accepts 'specific_courses'
* for individual courses or any other value for course bundles.
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @return array An array of course objects. Each course object contains:
* - int $id: The ID of the course.
* - string $title: The title of the course.
* - string $type: The post type of the course (e.g., 'courses', 'course-bundle').
* - float $price: The regular price of the course.
* - float $sale_price: The sale price of the course.
* - string $image: The URL of the course's thumbnail image.
* - int|null $total_courses: The total number of courses in the bundle
* (only if the user has Tutor Pro and the course type is 'course-bundle').
*/
public function get_coupon_applies_to_courses( string $applies_to ) {
global $wpdb;
$post_type = 'specific_courses' === $applies_to ? 'courses' : 'course-bundle';
$where = array(
'post_status' => 'publish',
'post_type' => $post_type,
);
$courses = QueryHelper::get_all( $wpdb->posts, $where, 'ID' );
if ( tutor()->has_pro ) {
$bundle_model = new \TutorPro\CourseBundle\Models\BundleModel();
}
$final_data = array();
if ( ! empty( $courses ) ) {
foreach ( $courses as $course ) {
$data = new \stdClass();
if ( tutor()->has_pro && 'course-bundle' === $course->type ) {
$data->total_courses = count( $bundle_model->get_bundle_course_ids( $course->ID ) );
}
$author_name = get_the_author_meta( 'display_name', $course->post_author );
$course_prices = tutor_utils()->get_raw_course_price( $course->ID );
$data->id = (int) $course->ID;
$data->title = $course->post_title;
$data->price = $course_prices->regular_price;
$data->sale_price = $course_prices->sale_price;
$data->image = get_the_post_thumbnail_url( $course->ID );
$data->author = $author_name;
$final_data[] = $data;
}
}
return ! empty( $final_data ) ? $final_data : array();
}
/**
* Get course instructor IDs.
*
* @since 3.0.0
*
* @param int $course_id course id.
*
* @return array
*/
public static function get_course_instructor_ids( $course_id ) {
global $wpdb;
$instructor_ids = $wpdb->get_col(
$wpdb->prepare(
"SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key=%s AND meta_value=%s",
'_tutor_instructor_course_id',
$course_id
)
);
return $instructor_ids;
}
/**
* Check tax collection is enabled for single purchase course/bundle
*
* @since 3.7.0
*
* @param int $post_id course or bundle id.
*
* @return boolean
*/
public static function is_tax_enabled_for_single_purchase( $post_id ) {
if ( ! Tax::is_individual_control_enabled() ) {
return true;
}
$data = get_post_meta( $post_id, Course::TAX_ON_SINGLE_META, true );
return ( '1' === $data || '' === $data );
}
/**
* Count total attachments available in all courses or specific
*
* @since 3.6.0
*
* @param int|array $course_id Course id or array of ids, to get course's attachment.
*
* @return int
*/
public static function count_attachment( $course_id = 0 ) {
global $wpdb;
$total_count = 0;
$primary_table = "$wpdb->posts p";
$joining_tables = array(
array(
'type' => 'INNER',
'table' => "{$wpdb->postmeta} pm",
'on' => "p.ID = pm.post_id AND pm.meta_key = '_tutor_attachments' AND pm.meta_value != 'a:0:{}'",
),
);
// Prepare query.
$select = array( 'pm.meta_value' );
$where = array();
if ( $course_id ) {
$where['p.ID'] = is_array( $course_id ) ? array( 'IN', $course_id ) : $course_id;
}
$search = array();
$limit = 0; // Get all.
$offset = 0;
$order_by = '';
$results = QueryHelper::get_joined_data(
$primary_table,
$joining_tables,
$select,
$where,
$search,
$order_by,
$limit,
$offset
)['results'];
if ( $results ) {
foreach ( $results as $row ) {
$attachment_ids = maybe_unserialize( $row->meta_value );
if ( ! is_array( $attachment_ids ) || empty( $attachment_ids ) ) {
continue;
}
$attachments = get_posts(
array(
'post_type' => 'attachment',
'post__in' => $attachment_ids,
'posts_per_page' => -1,
)
);
$total_count += count( $attachments );
}
}
return $total_count;
}
/**
* Count total questions available in all or specific courses
*
* @since 3.7.1
*
* @param int|array $course_id Course id or array of ids, to get course's attachment.
*
* @return int
*/
public static function count_questions( $course_id = 0 ) {
global $wpdb;
$total_count = 0;
$quiz_post_type = tutor()->quiz_post_type;
$topic_post_type = tutor()->topics_post_type;
$course_post_type = tutor()->course_post_type;
$primary_table = "{$wpdb->prefix}tutor_quiz_questions question";
$joining_tables = array(
array(
'type' => 'INNER',
'table' => "{$wpdb->posts} q",
'on' => "q.ID = question.quiz_id AND q.post_type='{$quiz_post_type}'",
),
array(
'type' => 'INNER',
'table' => "{$wpdb->posts} t",
'on' => "q.post_parent = t.ID AND t.post_type='{$topic_post_type}'",
),
array(
'type' => 'INNER',
'table' => "{$wpdb->posts} c",
'on' => "c.ID = t.post_parent AND c.post_type='{$course_post_type}'",
),
);
// Prepare query.
$where = array();
if ( $course_id ) {
$where['c.ID'] = is_array( $course_id ) ? array( 'IN', $course_id ) : $course_id;
}
$search = array();
$total_count = QueryHelper::get_joined_count(
$primary_table,
$joining_tables,
$where,
$search,
'*'
);
return $total_count;
}
/**
* Count course content
*
* @since 3.6.0
*
* @param string $content_type Content type.
* @param array $course_ids Course ids.
*
* @return int
*/
public static function count_course_content( string $content_type, array $course_ids = array() ): int {
$total_count = 0;
switch ( $content_type ) {
case tutor()->lesson_post_type:
$total_count = tutor_utils()->get_total_lesson( $course_ids );
break;
case tutor()->quiz_post_type:
$total_count = tutor_utils()->get_total_quiz( $course_ids );
break;
case tutor()->assignment_post_type:
if ( tutor_utils()->is_addon_enabled( 'tutor-assignments' ) ) {
$total_count = ( new Assignments( false ) )->get_total_assignment( $course_ids );
}
break;
case 'attachments':
$total_count = self::count_attachment( $course_ids );
break;
case 'questions':
$total_count = self::count_questions( $course_ids );
break;
default:
break;
}
return (int) $total_count;
}
/**
* Get course dropdown options
*
* @since 3.7.0
*
* @return array
*/
public static function get_course_dropdown_options() {
$course_options = array(
array(
'key' => '',
'title' => __( 'All Courses', 'tutor' ),
),
);
$courses = current_user_can( 'administrator' ) ? self::get_courses() : self::get_courses_by_instructor();
if ( ! empty( $courses ) ) {
foreach ( $courses as $course ) {
$course_options[] = array(
'key' => $course->ID,
'title' => $course->post_title,
);
}
}
return $course_options;
}
/**
* Get category dropdown options
*
* @since 3.7.0
*
* @return array
*/
public static function get_category_dropdown_options() {
$category_options = array(
array(
'key' => '',
'title' => __( 'All Categories', 'tutor' ),
),
);
$categories = get_terms(
array(
'taxonomy' => self::COURSE_CATEGORY,
'orderby' => 'term_id',
'order' => 'DESC',
)
);
if ( ! is_wp_error( $categories ) && ! empty( $categories ) ) {
foreach ( $categories as $category ) {
$category_options[] = array(
'key' => $category->slug,
'title' => $category->name,
);
}
}
return $category_options;
}
/**
* Retrieve all course completion records for a specific course, including meta data.
*
* @since 3.8.1
*
* @param int $course_id The ID of the course.
*
* @return array Array of course completion objects with meta, empty array if none found, or on database error.
*/
public function get_course_completion_data_by_course_id( int $course_id ): array {
global $wpdb;
$where = array(
'comment_type' => self::COURSE_COMPLETED,
'comment_post_ID' => $course_id,
'comment_agent' => 'TutorLMSPlugin',
);
$result = QueryHelper::get_all( $wpdb->comments, $where, 'comment_post_ID', -1 );
if ( empty( $result ) ) {
return array();
}
return array_map(
function ( $item ) {
return array(
'completion' => $item,
'completion_meta' => get_comment_meta( $item->comment_ID ),
);
},
$result
);
}
/**
* 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.
* @param array $args Args to override the defaults.
*
* @return bool|\WP_Query
*/
public static function get_completed_courses_by_user( $user_id = 0, $offset = 0, $posts_per_page = -1, $args = array() ) {
$user_id = tutor_utils()->get_user_id( $user_id );
$course_ids = tutor_utils()->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,
);
$args = apply_filters( 'tutor_get_completed_courses_by_user', $args, $user_id, $course_post_type );
$course_args = wp_parse_args( $args, $course_args );
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.
* @param array $args Args to override the defaults.
*
* @return bool|\WP_Query
*/
public static function get_active_courses_by_user( $user_id = 0, $offset = 0, $posts_per_page = -1, $args = array() ) {
$user_id = tutor_utils()->get_user_id( $user_id );
$course_ids = tutor_utils()->get_completed_courses_ids_by_user( $user_id );
$enrolled_course_ids = tutor_utils()->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' => apply_filters( 'tutor_active_courses_post_types', array( $course_post_type ) ),
'post_status' => 'publish',
'post__in' => $active_courses,
'posts_per_page' => $posts_per_page,
'offset' => $offset,
);
$args = apply_filters( 'tutor_get_active_courses_by_user', $args, $user_id, $course_post_type );
$course_args = wp_parse_args( $args, $course_args );
return new \WP_Query( $course_args );
}
return false;
}
/**
* 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 mixed $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 static function get_enrolled_courses_by_user( $user_id = 0, $post_status = 'publish', $offset = 0, $posts_per_page = -1, $args = array() ) {
$user_id = tutor_utils()->get_user_id( $user_id );
$course_ids = array_unique( tutor_utils()->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,
);
$args = apply_filters( 'tutor_get_enrolled_courses_by_user', $args, $user_id, $course_post_type );
$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;
}
}