PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 2.2.1
Tutor LMS – eLearning and online course solution v2.2.1
3.9.14 3.9.13 3.9.12 3.9.11 trunk 1.0.0 1.0.0-alpha 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.2.0 1.2.1 1.2.11 1.2.12 1.2.13 1.2.20 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 1.7.7 1.7.8 1.7.9 1.8.0 1.8.1 1.8.10 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.14 1.9.15 1.9.16 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.10 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.1.1 2.1.10 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.3.0 2.4.0 2.5.0 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 3.0.0 3.0.1 3.0.2 3.1.0 3.2.0 3.2.1 3.2.2 3.2.3 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.7.0 3.7.1 3.7.2 3.7.3 3.7.4 3.8.0 3.8.1 3.8.2 3.8.3 3.9.0 3.9.1 3.9.10 3.9.2 3.9.3 3.9.4 3.9.5 3.9.6 3.9.7 3.9.8 3.9.9
tutor / models / CourseModel.php
tutor / models Last commit date
CourseModel.php 3 years ago LessonModel.php 3 years ago QuizModel.php 3 years ago WithdrawModel.php 3 years ago
CourseModel.php
582 lines
1 <?php
2 /**
3 * Course Model
4 *
5 * @package Tutor\Models
6 * @author Themeum <support@themeum.com>
7 * @link https://themeum.com
8 * @since 2.0.6
9 */
10
11 namespace Tutor\Models;
12
13 use Tutor\Helpers\QueryHelper;
14
15 /**
16 * CourseModel Class
17 *
18 * @since 2.0.6
19 */
20 class CourseModel {
21 /**
22 * WordPress course type name
23 *
24 * @var string
25 */
26 const POST_TYPE = 'courses';
27
28 const STATUS_PUBLISH = 'publish';
29 const STATUS_DRAFT = 'draft';
30 const STATUS_AUTO_DRAFT = 'auto-draft';
31 const STATUS_PENDING = 'pending';
32
33 /**
34 * Course mapped with the product using this meta key
35 *
36 * @var string
37 */
38 const WC_PRODUCT_META_KEY = '_tutor_course_product_id';
39
40 /**
41 * Course attachment/downloadable resources meta key
42 *
43 * @var string
44 */
45 const ATTACHMENT_META_KEY = '_tutor_attachments';
46
47 /**
48 * Course benefits meta key
49 *
50 * @var string
51 */
52 const BENEFITS_META_KEY = '_tutor_course_benefits';
53
54 /**
55 * Course record count
56 *
57 * @since 2.0.7
58 *
59 * @param string $status course status.
60 * @return int
61 */
62 public static function count( $status = self::STATUS_PUBLISH ) {
63 $count_obj = wp_count_posts( self::POST_TYPE );
64 if ( 'all' === $status ) {
65 return array_sum( (array) $count_obj );
66 }
67
68 return (int) $count_obj->{$status};
69 }
70
71 /**
72 * Get courses
73 *
74 * @since 1.0.0
75 *
76 * @param array $excludes exclude course ids.
77 * @param array $post_status post status array.
78 *
79 * @return array|null|object
80 */
81 public static function get_courses( $excludes = array(), $post_status = array( 'publish' ) ) {
82 global $wpdb;
83
84 $excludes = (array) $excludes;
85 $exclude_query = '';
86
87 if ( count( $excludes ) ) {
88 $exclude_query = implode( "','", $excludes );
89 }
90
91 $post_status = array_map(
92 function ( $element ) {
93 return "'" . $element . "'";
94 },
95 $post_status
96 );
97
98 $post_status = implode( ',', $post_status );
99 $course_post_type = tutor()->course_post_type;
100
101 //phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
102 $query = $wpdb->get_results(
103 $wpdb->prepare(
104 "SELECT ID,
105 post_author,
106 post_title,
107 post_name,
108 post_status,
109 menu_order
110 FROM {$wpdb->posts}
111 WHERE post_status IN ({$post_status})
112 AND ID NOT IN('$exclude_query')
113 AND post_type = %s;
114 ",
115 $course_post_type
116 )
117 );
118 //phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
119
120 return $query;
121 }
122
123 /**
124 * Get course count by instructor
125 *
126 * @since 1.0.0
127 *
128 * @param $instructor_id
129 *
130 * @return null|string
131 */
132 public static function get_course_count_by_instructor( $instructor_id ) {
133 global $wpdb;
134
135 $course_post_type = tutor()->course_post_type;
136
137 $count = $wpdb->get_var(
138 $wpdb->prepare(
139 "SELECT COUNT(ID)
140 FROM {$wpdb->posts}
141 INNER JOIN {$wpdb->usermeta}
142 ON user_id = %d
143 AND meta_key = %s
144 AND meta_value = ID
145 WHERE post_status = %s
146 AND post_type = %s;
147 ",
148 $instructor_id,
149 '_tutor_instructor_course_id',
150 'publish',
151 $course_post_type
152 )
153 );
154
155 return $count;
156 }
157
158 /**
159 * Get course by quiz
160 *
161 * @since 1.0.0
162 *
163 * @param $quiz_id quiz id.
164 *
165 * @return array|bool|null|object|void
166 */
167 public static function get_course_by_quiz( $quiz_id ) {
168 $quiz_id = tutils()->get_post_id( $quiz_id );
169 $post = get_post( $quiz_id );
170
171 if ( $post ) {
172 $course = get_post( $post->post_parent );
173 if ( $course ) {
174 if ( $course->post_type !== tutor()->course_post_type ) {
175 $course = get_post( $course->post_parent );
176 }
177 return $course;
178 }
179 }
180
181 return false;
182 }
183
184 /**
185 * Get courses by a instructor
186 *
187 * @since 1.0.0
188 *
189 * @param integer $instructor_id
190 * @param array|string $post_status
191 * @param integer $offset
192 * @param integer $limit
193 * @param boolean $count_only
194 *
195 * @return array|null|object
196 */
197 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 ) {
198 global $wpdb;
199 $offset = sanitize_text_field( $offset );
200 $limit = sanitize_text_field( $limit );
201 $instructor_id = tutils()->get_user_id( $instructor_id );
202 $course_post_type = tutor()->course_post_type;
203
204 if ( empty( $post_status ) || $post_status == 'any' ) {
205 $where_post_status = '';
206 } else {
207 ! is_array( $post_status ) ? $post_status = array( $post_status ) : 0;
208 $statuses = "'" . implode( "','", $post_status ) . "'";
209 $where_post_status = "AND $wpdb->posts.post_status IN({$statuses}) ";
210 }
211
212 $select_col = $count_only ? " COUNT(DISTINCT $wpdb->posts.ID) " : " $wpdb->posts.* ";
213 $limit_offset = $count_only ? '' : " LIMIT $offset, $limit ";
214
215 $query = $wpdb->prepare(
216 "SELECT $select_col
217 FROM $wpdb->posts
218 LEFT JOIN {$wpdb->usermeta}
219 ON $wpdb->usermeta.user_id = %d
220 AND $wpdb->usermeta.meta_key = %s
221 AND $wpdb->usermeta.meta_value = $wpdb->posts.ID
222 WHERE 1 = 1 {$where_post_status}
223 AND $wpdb->posts.post_type = %s
224 AND ($wpdb->posts.post_author = %d OR $wpdb->usermeta.user_id = %d)
225 ORDER BY $wpdb->posts.post_date DESC $limit_offset",
226 $instructor_id,
227 '_tutor_instructor_course_id',
228 $course_post_type,
229 $instructor_id,
230 $instructor_id
231 );
232
233 return $count_only ? $wpdb->get_var( $query ) : $wpdb->get_results( $query, OBJECT );
234 }
235
236 /**
237 * Get courses for instructors
238 *
239 * @since 1.0.0
240 *
241 * @param int $instructor_id Instructor ID.
242 * @return array|null|object
243 */
244 public function get_courses_for_instructors( $instructor_id = 0 ) {
245 $instructor_id = tutor_utils()->get_user_id( $instructor_id );
246 $course_post_type = tutor()->course_post_type;
247
248 $courses = get_posts(
249 array(
250 'post_type' => $course_post_type,
251 'author' => $instructor_id,
252 'post_status' => array( 'publish', 'pending' ),
253 'posts_per_page' => 5,
254 )
255 );
256
257 return $courses;
258 }
259
260 /**
261 * Check a user is main instructor of a course
262 *
263 * @since 2.1.6
264 *
265 * @param integer $course_id course id.
266 * @param integer $user_id instructor id ( optional ) default: current user id.
267 *
268 * @return boolean
269 */
270 public static function is_main_instructor( $course_id, $user_id = 0 ) {
271 $course = get_post( $course_id );
272 $user_id = tutor_utils()->get_user_id( $user_id );
273
274 if ( ! $course || self::POST_TYPE !== $course->post_type || $user_id !== (int) $course->post_author ) {
275 return false;
276 }
277
278 return true;
279 }
280
281 /**
282 * Mark the course as completed
283 *
284 * @since 2.0.7
285 *
286 * @param int $course_id course id which is completed.
287 * @param int $user_id student id who completed the course.
288 *
289 * @return bool
290 */
291 public static function mark_course_as_completed( $course_id, $user_id ) {
292 if ( ! $course_id || ! $user_id ) {
293 return false;
294 }
295
296 do_action( 'tutor_course_complete_before', $course_id );
297
298 /**
299 * Marking course completed at Comment.
300 */
301 global $wpdb;
302
303 $date = date( 'Y-m-d H:i:s', tutor_time() );
304
305 // Making sure that, hash is unique.
306 do {
307 $hash = substr( md5( wp_generate_password( 32 ) . $date . $course_id . $user_id ), 0, 16 );
308 $has_hash = (int) $wpdb->get_var(
309 $wpdb->prepare(
310 "SELECT COUNT(comment_ID) from {$wpdb->comments}
311 WHERE comment_agent = 'TutorLMSPlugin' AND comment_type = 'course_completed' AND comment_content = %s ",
312 $hash
313 )
314 );
315
316 } while ( $has_hash > 0 );
317
318 $data = array(
319 'comment_post_ID' => $course_id,
320 'comment_author' => $user_id,
321 'comment_date' => $date,
322 'comment_date_gmt' => get_gmt_from_date( $date ),
323 'comment_content' => $hash, // Identification Hash.
324 'comment_approved' => 'approved',
325 'comment_agent' => 'TutorLMSPlugin',
326 'comment_type' => 'course_completed',
327 'user_id' => $user_id,
328 );
329
330 $wpdb->insert( $wpdb->comments, $data );
331
332 do_action( 'tutor_course_complete_after', $course_id, $user_id );
333
334 return true;
335 }
336
337 /**
338 * Delete a course by ID
339 *
340 * @since 2.0.9
341 *
342 * @param int $post_id course id that need to delete.
343 * @return bool
344 */
345 public static function delete_course( $post_id ) {
346 if ( get_post_type( $post_id ) !== tutor()->course_post_type ) {
347 return false;
348 }
349
350 wp_delete_post( $post_id, true );
351 return true;
352 }
353
354 /**
355 * Get post ids by post type and parent_id
356 *
357 * @since 1.6.6
358 *
359 * @param string $post_type post type.
360 * @param integer $post_parent post parent ID.
361 *
362 * @return array
363 */
364 private function get_post_ids( $post_type, $post_parent ) {
365 $args = array(
366 'fields' => 'ids',
367 'post_type' => $post_type,
368 'post_parent' => $post_parent,
369 'post_status' => 'any',
370 'posts_per_page' => -1,
371 );
372 return get_posts( $args );
373 }
374
375 /**
376 * Delete course data when permanently deleting a course.
377 *
378 * @since 1.6.6
379 * @since 2.0.9 updated
380 *
381 * @param integer $post_id post ID.
382 * @return bool
383 */
384 public function delete_course_data( $post_id ) {
385 $course_post_type = tutor()->course_post_type;
386 if ( get_post_type( $post_id ) !== $course_post_type ) {
387 return false;
388 }
389
390 global $wpdb;
391
392 $lesson_post_type = tutor()->lesson_post_type;
393 $assignment_post_type = tutor()->assignment_post_type;
394 $quiz_post_type = tutor()->quiz_post_type;
395
396 $topic_ids = $this->get_post_ids( 'topics', $post_id );
397
398 // Course > Topic > ( Lesson | Quiz | Assignment ).
399 if ( ! empty( $topic_ids ) ) {
400 foreach ( $topic_ids as $topic_id ) {
401 $content_post_type = array( $lesson_post_type, $assignment_post_type, $quiz_post_type );
402 $topic_content_ids = $this->get_post_ids( $content_post_type, $topic_id );
403
404 foreach ( $topic_content_ids as $content_id ) {
405 /**
406 * Delete Quiz data
407 */
408 if ( get_post_type( $content_id ) === 'tutor_quiz' ) {
409 $wpdb->delete( $wpdb->prefix . 'tutor_quiz_attempts', array( 'quiz_id' => $content_id ) );
410 $wpdb->delete( $wpdb->prefix . 'tutor_quiz_attempt_answers', array( 'quiz_id' => $content_id ) );
411
412 $questions_ids = $wpdb->get_col( $wpdb->prepare( "SELECT question_id FROM {$wpdb->prefix}tutor_quiz_questions WHERE quiz_id = %d ", $content_id ) );
413 if ( is_array( $questions_ids ) && count( $questions_ids ) ) {
414 $in_question_ids = "'" . implode( "','", $questions_ids ) . "'";
415 //phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
416 $wpdb->query( "DELETE FROM {$wpdb->prefix}tutor_quiz_question_answers WHERE belongs_question_id IN({$in_question_ids}) " );
417 }
418 $wpdb->delete( $wpdb->prefix . 'tutor_quiz_questions', array( 'quiz_id' => $content_id ) );
419 }
420
421 /**
422 * Delete assignment data ( Assignments, Assignment Submit, Assignment Evalutation )
423 *
424 * @since 2.0.9
425 */
426 if ( get_post_type( $content_id ) === $assignment_post_type ) {
427 QueryHelper::delete_comment_with_meta(
428 array(
429 'comment_type' => 'tutor_assignment',
430 'comment_post_ID' => $content_id,
431 )
432 );
433 }
434
435 wp_delete_post( $content_id, true );
436
437 }
438
439 // Delete zoom meeting.
440 $wpdb->delete(
441 $wpdb->posts,
442 array(
443 'post_parent' => $topic_id,
444 'post_type' => 'tutor_zoom_meeting',
445 )
446 );
447
448 /**
449 * Delete Google Meet Record Related to Course Topic
450 *
451 * @since 2.1.0
452 */
453 $wpdb->delete(
454 $wpdb->posts,
455 array(
456 'post_parent' => $topic_id,
457 'post_type' => 'tutor-google-meet',
458 )
459 );
460
461 wp_delete_post( $topic_id, true );
462 }
463 }
464
465 $child_post_ids = $this->get_post_ids( array( 'tutor_announcements', 'tutor_enrolled', 'tutor_zoom_meeting', 'tutor-google-meet' ), $post_id );
466 if ( ! empty( $child_post_ids ) ) {
467 foreach ( $child_post_ids as $child_post_id ) {
468 wp_delete_post( $child_post_id, true );
469 }
470 }
471
472 /**
473 * Delete earning, gradebook result, course complete data
474 *
475 * @since 2.0.9
476 */
477 $wpdb->delete( $wpdb->prefix . 'tutor_earnings', array( 'course_id' => $post_id ) );
478 $wpdb->delete( $wpdb->prefix . 'tutor_gradebooks_results', array( 'course_id' => $post_id ) );
479 $wpdb->delete(
480 $wpdb->comments,
481 array(
482 'comment_type' => 'course_completed',
483 'comment_post_ID' => $post_id,
484 )
485 );
486
487 /**
488 * Delete onsite notification record & _tutor_instructor_course_id user meta
489 *
490 * @since 2.1.0
491 */
492 $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}tutor_notifications WHERE post_id=%d AND type IN ('Announcements','Q&A','Enrollments')", $post_id ) );
493 $wpdb->delete(
494 $wpdb->usermeta,
495 array(
496 'meta_key' => '_tutor_instructor_course_id',
497 'meta_value' => $post_id,
498 )
499 );
500
501 /**
502 * Delete Course rating and review
503 *
504 * @since 2.0.9
505 */
506 QueryHelper::delete_comment_with_meta(
507 array(
508 'comment_type' => 'tutor_course_rating',
509 'comment_post_ID' => $post_id,
510 )
511 );
512
513 /**
514 * Delete Q&A and its status ( read, replied etc )
515 *
516 * @since 2.0.9
517 */
518 QueryHelper::delete_comment_with_meta(
519 array(
520 'comment_type' => 'tutor_q_and_a',
521 'comment_post_ID' => $post_id,
522 )
523 );
524
525 /**
526 * Delete caches
527 */
528 $attempt_cache = new \Tutor\Cache\QuizAttempts();
529 if ( $attempt_cache->has_cache() ) {
530 $attempt_cache->delete_cache();
531 }
532
533 return true;
534 }
535
536
537 /**
538 * Get paid courses
539 *
540 * To identify course is connected with any product
541 * like WC Product or EDD product meta key will be used
542 *
543 * @since 2.2.0
544 *
545 * @param string $meta_key course product id meta key.
546 * @param array $args wp_query args.
547 *
548 * @return array
549 */
550 public static function get_paid_courses( string $meta_key, array $args = array() ): array {
551 $current_user = wp_get_current_user();
552 $default_args = array(
553 'post_type' => 'courses',
554 'post_status' => 'publish',
555 'no_found_rows' => true,
556 'posts_per_page' => -1,
557 'relation' => 'AND',
558 'meta_query' => array(
559 array(
560 'key' => sanitize_text_field( $meta_key ),
561 'value' => 0,
562 'compare' => '!=',
563 'type' => 'NUMERIC',
564 ),
565 ),
566 );
567
568 // Check if the current user is an admin.
569 if ( ! current_user_can( 'administrator' ) ) {
570 $args['author'] = $current_user->ID;
571 }
572
573 $query = new \WP_Query( wp_parse_args( $args, $default_args ) );
574
575 if ( $query->have_posts() ) {
576 return $query->posts;
577 }
578
579 return array();
580 }
581 }
582