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