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