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