PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.7
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.7
4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.9.1 4.3.9 4.3.8 4.3.7 4.1.6.9 4.1.6.9.1 4.1.6.9.2 4.1.6.9.3 4.1.6.9.4 4.1.7 4.1.7.1 4.1.7.2 4.1.7.3 4.1.7.3.1 4.1.7.3.2 4.2.0 4.2.1 All 138 releases
← All changes | inc/Models/UserItems/UserCourseModel.php +1183 -1140 4.3.84.4.7 View file →
@@ -1,1140 +1,1183 @@
1 -<?php
2 -
3 -/**
4 - * Class UserItemModel
5 - *
6 - * @package LearnPress/Classes
7 - * @version 1.0.1
8 - * @since 4.2.5
9 - */
10 -
11 -namespace LearnPress\Models\UserItems;
12 -
13 -use Exception;
14 -use LearnPress\Filters\UserItemsFilter;
15 -use LearnPress\Models\CourseModel;
16 -use LearnPress\Models\CoursePostModel;
17 -use LearnPress\Models\QuizPostModel;
18 -use LP_Cache;
19 -use LP_Course_Cache;
20 -use LP_Course_Item;
21 -use LP_Courses_Cache;
22 -use LP_Datetime;
23 -use LP_Debug;
24 -use LP_User;
25 -use LP_User_Item_Course;
26 -use LP_User_Items_Cache;
27 -use LP_User_Items_DB;
28 -use LP_User_Items_Filter;
29 -use LP_User_Items_Result_DB;
30 -use stdClass;
31 -use Throwable;
32 -use WP_Error;
33 -
34 -class UserCourseModel extends UserItemModel {
35 - /**
36 - * Item type Course
37 - *
38 - * @var string Item type
39 - */
40 - public $item_type = LP_COURSE_CPT;
41 - /**
42 - * Ref type Order
43 - *
44 - * @var string
45 - */
46 - public $ref_type = LP_ORDER_CPT;
47 -
48 - // Constants status
49 - const STATUS_PURCHASED = 'purchased';
50 -
51 - /**
52 - * Constant key meta
53 - */
54 - const META_KEY_RETAKEN_COUNT = '_lp_retaken_count';
55 -
56 - public function __construct( $data = null ) {
57 - parent::__construct( $data );
58 -
59 - if ( $data ) {
60 - $this->get_course_model();
61 - }
62 - }
63 -
64 - /**
65 - * Get course model
66 - *
67 - * @return bool|CourseModel
68 - */
69 - public function get_course_model() {
70 - return CourseModel::find( $this->item_id, true );
71 - }
72 -
73 - /**
74 - * Find User Item by user_id, item_id, item_type.
75 - *
76 - * @param int $user_id
77 - * @param int $course_id
78 - * @param bool $check_cache
79 - *
80 - * @return false|static
81 - * @since 4.2.7
82 - * @version 1.0.2
83 - */
84 - public static function find( int $user_id, int $course_id, bool $check_cache = false ) {
85 - static $staticData = [];
86 -
87 - $filter = new LP_User_Items_Filter();
88 - $filter->user_id = $user_id;
89 - $filter->item_id = $course_id;
90 - $filter->item_type = LP_COURSE_CPT;
91 - $key_cache = "userCourseModel/find/{$user_id}/{$course_id}/{$filter->item_type}";
92 - $lpUserCourseCache = new LP_Cache();
93 -
94 - // Check cache
95 - if ( $check_cache ) {
96 - $userCourseModel = $lpUserCourseCache->get_cache( $key_cache );
97 - if ( $userCourseModel instanceof UserCourseModel ) {
98 - return $userCourseModel;
99 - }
100 -
101 - if ( isset( $staticData[ $key_cache ] ) ) {
102 - return $staticData[ $key_cache ];
103 - }
104 - }
105 -
106 - $userCourseModel = static::get_user_item_model_from_db( $filter );
107 -
108 - // Set cache
109 - if ( $userCourseModel instanceof UserCourseModel ) {
110 - $lpUserCourseCache->set_cache( $key_cache, $userCourseModel );
111 - }
112 -
113 - $staticData[ $key_cache ] = $userCourseModel;
114 -
115 - return $userCourseModel;
116 - }
117 -
118 - /**
119 - * Get user_item is child of user course.
120 - *
121 - * @param int $item_id
122 - * @param string $item_type
123 - *
124 - * @return false|UserItemModel|UserQuizModel|mixed
125 - * @since 4.2.5
126 - * @version 1.0.1
127 - */
128 - public function get_item_attend( int $item_id, string $item_type = '' ) {
129 - $item = false;
130 -
131 - try {
132 - $item = UserItemModel::find_user_item(
133 - $this->user_id,
134 - $item_id,
135 - $item_type,
136 - $this->item_id,
137 - $this->item_type,
138 - true
139 - );
140 -
141 - if ( $item ) {
142 - switch ( $item_type ) {
143 - case LP_LESSON_CPT:
144 - $item = new UserLessonModel( $item );
145 - break;
146 - case LP_QUIZ_CPT:
147 - $item = new UserQuizModel( $item );
148 - break;
149 - default:
150 - $item = apply_filters( 'learn-press/userCourseModel/get-item-attend', $item, $this, $item_id, $item_type );
151 - break;
152 - }
153 - }
154 - } catch ( Throwable $e ) {
155 - error_log( __METHOD__ . ': ' . $e->getMessage() );
156 - }
157 -
158 - return $item;
159 - }
160 -
161 - /**
162 - * Get item continue to learn.
163 - *
164 - * @return mixed|null
165 - * @since 4.2.7.6
166 - * @version 1.0.0
167 - */
168 - public function get_item_continue() {
169 - if ( isset( $this->meta_data->item_continue ) ) {
170 - return $this->meta_data->item_continue;
171 - }
172 -
173 - $itemModel = null;
174 -
175 - try {
176 - $courseModel = $this->get_course_model();
177 - $totalItemsObj = $courseModel->count_items();
178 - if ( empty( $totalItemsObj ) ) {
179 - return $itemModel;
180 - }
181 -
182 - $item_id = 0;
183 - $item_type = '';
184 - $sections_items = $courseModel->get_section_items();
185 - foreach ( $sections_items as $section_items ) {
186 - if ( $itemModel ) {
187 - break;
188 - }
189 -
190 - foreach ( $section_items->items as $item ) {
191 - $item_id = $item->id ?? $item->item_id;
192 - $item_type = $item->type ?? $item->item_type;
193 -
194 - $userItemModel = UserItemModel::find_user_item(
195 - $this->user_id,
196 - $item_id,
197 - $item_type,
198 - $courseModel->get_id(),
199 - LP_COURSE_CPT,
200 - true
201 - );
202 - if ( ! $userItemModel || $userItemModel->get_status() !== LP_ITEM_COMPLETED ) {
203 - $itemModel = $courseModel->get_item_model( $item_id, $item_type );
204 - break;
205 - }
206 - }
207 - }
208 -
209 - // For all items are completed
210 - if ( empty( $itemModel ) && ! empty( $item_id ) && ! empty( $item_type ) ) {
211 - $itemModel = $courseModel->get_item_model( $item_id, $item_type );
212 - }
213 -
214 - $this->meta_data->item_continue = $itemModel;
215 - } catch ( Throwable $e ) {
216 - error_log( __METHOD__ . ': ' . $e->getMessage() );
217 - }
218 -
219 - return $itemModel;
220 - }
221 -
222 - /**
223 - * Find next item when have just completed lesson.
224 - * Logic: find item next not completed, circle to find next item, if not found return current item.
225 - *
226 - * @param int $item_id_current
227 - *
228 - * @since 4.3.2
229 - * @version 1.0.0
230 - * @return mixed|null
231 - */
232 - public function get_item_next_when_complete_lesson( int $item_id_current ) {
233 - $itemModel = null;
234 - $item_before_found = 0;
235 - $item_after_found = 0;
236 -
237 - try {
238 - $courseModel = $this->get_course_model();
239 - $course_items = $courseModel->get_only_items();
240 - $course_item_keys = array_keys( $course_items );
241 -
242 - $index_of_current = array_search( $item_id_current, $course_item_keys, true );
243 -
244 - foreach ( $course_item_keys as $index => $item_id ) {
245 - $course_item = $course_items[ $item_id ];
246 - $item_type = $course_item->item_type;
247 -
248 - $userItemModel = UserItemModel::find_user_item(
249 - $this->user_id,
250 - $item_id,
251 - $item_type,
252 - $courseModel->get_id(),
253 - LP_COURSE_CPT,
254 - true
255 - );
256 - $status_compare = apply_filters(
257 - 'learn-press/user-course-model/get-item-next-when-complete-lesson/status-compare',
258 - [
259 - UserItemModel::STATUS_COMPLETED,
260 - ]
261 - );
262 -
263 - // Found item before current item not completed
264 - if ( $index < $index_of_current ) {
265 - if ( ! $item_before_found &&
266 - ( ! $userItemModel || ! in_array( $userItemModel->get_status(), $status_compare ) ) ) {
267 - $item_before_found = $course_item;
268 - }
269 - } elseif ( $index > $index_of_current ) {
270 - // Found item after current item not completed
271 - if ( ! $item_after_found &&
272 - ( ! $userItemModel || ! in_array( $userItemModel->get_status(), $status_compare ) ) ) {
273 - $item_after_found = $course_item;
274 - break;
275 - }
276 - }
277 - }
278 -
279 - if ( $item_after_found ) {
280 - $itemModel = $courseModel->get_item_model( $item_after_found->item_id, $item_after_found->item_type );
281 - } elseif ( $item_before_found ) {
282 - $itemModel = $courseModel->get_item_model( $item_before_found->item_id, $item_before_found->item_type );
283 - } else {
284 - $itemModel = $courseModel->get_item_model( $item_id_current, LP_LESSON_CPT );
285 - }
286 - } catch ( Throwable $e ) {
287 - error_log( __METHOD__ . ': ' . $e->getMessage() );
288 - }
289 -
290 - return $itemModel;
291 - }
292 -
293 - /**
294 - * Count students.
295 - *
296 - * @param LP_User_Items_Filter|UserItemsFilter $filter
297 - *
298 - * @return int
299 - * @since 4.2.5.4
300 - * @version 1.0.0
301 - */
302 - public static function count_students( $filter ): int {
303 - // Check cache
304 - $key_cache = 'count-courses-student-' . md5( json_encode( $filter ) );
305 - $count = LP_Cache::cache_load_first( 'get', $key_cache );
306 - if ( false !== $count ) {
307 - return $count;
308 - }
309 -
310 - $lp_courses_cache = new LP_Courses_Cache( true );
311 - $count = $lp_courses_cache->get_cache( $key_cache );
312 - if ( false !== $count ) {
313 - LP_Cache::cache_load_first( 'set', $key_cache, $count );
314 -
315 - return $count;
316 - }
317 -
318 - $lp_user_items_db = LP_User_Items_DB::getInstance();
319 - $count = $lp_user_items_db->count_students( $filter );
320 -
321 - // Set cache
322 - $lp_courses_cache->set_cache( $key_cache, $count );
323 - $lp_courses_cache_keys = new LP_Courses_Cache( true );
324 - $lp_courses_cache_keys->save_cache_keys_count_student_courses( $key_cache );
325 - LP_Cache::cache_load_first( 'set', $key_cache, $count );
326 -
327 - return $count;
328 - }
329 -
330 - /**
331 - * Check course of user is enrolled or finished
332 - *
333 - * @return bool
334 - */
335 - public function has_enrolled_or_finished(): bool {
336 - return $this->has_enrolled() || $this->has_finished();
337 - }
338 -
339 - /**
340 - * Check user has enrolled course
341 - *
342 - * @return bool
343 - */
344 - public function has_enrolled(): bool {
345 - return $this->status === UserItemModel::STATUS_ENROLLED;
346 - }
347 -
348 - /**
349 - * Check user has purchased course
350 - *
351 - * @return bool
352 - */
353 - public function has_purchased(): bool {
354 - return $this->status === UserItemModel::STATUS_PURCHASED;
355 - }
356 -
357 - /**
358 - * Check user has finished course
359 - *
360 - * @return bool
361 - */
362 - public function has_finished(): bool {
363 - return $this->status === UserItemModel::STATUS_FINISHED;
364 - }
365 -
366 - /**
367 - * Check course has finished or not.
368 - *
369 - * @return bool
370 - * @move from class-lp-user-item-course.php
371 - * @since 3.0.0
372 - * @version 1.0.2
373 - */
374 - public function is_finished(): bool {
375 - return $this->has_finished();
376 - }
377 -
378 - /**
379 - * Calculate course result
380 - *
381 - * @move from class-lp-user-item-course.php
382 - *
383 - * @param bool $force_cache
384 - *
385 - * @return array [ 'count_items' => int, 'completed_items' => int, 'items' => array, 'evaluate_type' => string, 'pass' => int, 'result' => float ]
386 - * @since 4.1.4
387 - * @version 1.0.3
388 - */
389 - public function calculate_course_results( bool $force_cache = false ): array {
390 - $items = array();
391 - $results = array(
392 - 'count_items' => 0,
393 - 'completed_items' => 0,
394 - 'items' => array(),
395 - 'evaluate_type' => '',
396 - 'pass' => 0,
397 - 'result' => 0,
398 - );
399 -
400 - try {
401 - $courseModel = $this->get_course_model();
402 - if ( ! $courseModel instanceof CourseModel ) {
403 - throw new Exception( 'Course invalid!' );
404 - }
405 -
406 - $key_first_cache = 'calculate_course/' . $this->user_id . '/' . $courseModel->get_id();
407 - $results_cache = LP_Cache::cache_load_first( 'get', $key_first_cache );
408 - if ( false !== $results_cache && ! $force_cache ) {
409 - return $results_cache;
410 - }
411 -
412 - if ( $this->is_finished() ) {
413 - // Get result from lp_user_item_results
414 - // Todo: tungnx - set cache
415 - return LP_User_Items_Result_DB::instance()->get_result( $this->get_user_item_id() );
416 - }
417 -
418 - if ( $this->status !== LP_COURSE_ENROLLED ) {
419 - return $results;
420 - }
421 -
422 - $count_items = $courseModel->count_items();
423 - $count_items_completed = $this->count_items_completed();
424 -
425 - $evaluate_type = $courseModel->get_meta_value_by_key( CoursePostModel::META_KEY_EVALUATION_TYPE, 'evaluate_lesson' );
426 - switch ( $evaluate_type ) {
427 - case 'evaluate_lesson':
428 - $results_evaluate = $this->evaluate_course_by_lesson( $count_items_completed, $courseModel->count_items( LP_LESSON_CPT ) );
429 - break;
430 - case 'evaluate_final_quiz':
431 - $results_evaluate = $this->evaluate_course_by_final_quiz();
432 - break;
433 - case 'evaluate_quiz':
434 - $results_evaluate = $this->evaluate_course_by_quizzes_passed( $count_items_completed, $courseModel->count_items( LP_QUIZ_CPT ) );
435 - break;
436 - case 'evaluate_questions':
437 - case 'evaluate_mark':
438 - $results_evaluate = $this->evaluate_course_by_question( $evaluate_type );
439 - break;
440 - default:
441 - // Old Hook
442 - if ( has_filter( 'learn-press/evaluate_passed_conditions' ) ) {
443 - $user_course_old = new LP_User_Item_Course( $this );
444 - $results = apply_filters( 'learn-press/evaluate_passed_conditions', $results, $evaluate_type, $user_course_old );
445 - }
446 -
447 - $results_evaluate = apply_filters( 'learn-press/evaluate/calculate', $results, $evaluate_type, $this );
448 - break;
449 - }
450 -
451 - if ( ! is_array( $results_evaluate ) ) {
452 - $results_evaluate = array(
453 - 'result' => 0,
454 - 'pass' => 0,
455 - );
456 - }
457 -
458 - $results_evaluate['result'] = round( $results_evaluate['result'], 2 );
459 - if ( $results_evaluate['result'] > 100 ) {
460 - $results_evaluate['result'] = 100;
461 - }
462 -
463 - $completed_items = intval( $count_items_completed->count_status ?? 0 );
464 -
465 - $item_types = CourseModel::item_types_support();
466 - foreach ( $item_types as $item_type ) {
467 - $item_type_key = str_replace( 'lp_', '', $item_type );
468 -
469 - $items[ $item_type_key ] = array(
470 - 'completed' => $count_items_completed->{$item_type . '_status_completed'} ?? 0,
471 - 'passed' => $count_items_completed->{$item_type . '_graduation_passed'} ?? 0,
472 - 'total' => $courseModel->count_items( $item_type ),
473 - );
474 - }
475 -
476 - $results = array_merge(
477 - $results_evaluate,
478 - compact( 'count_items', 'completed_items', 'items', 'evaluate_type' )
479 - );
480 -
481 - $results = apply_filters(
482 - 'learn-press/update-course-results',
483 - $results,
484 - $this->item_id,
485 - $this->user_id,
486 - $this
487 - );
488 -
489 - LP_Cache::cache_load_first( 'set', $key_first_cache, $results );
490 - } catch ( Throwable $e ) {
491 - LP_Debug::error_log( $e );
492 - }
493 -
494 - return $results;
495 - }
496 -
497 - /**
498 - * Check user can retake course or not.
499 - *
500 - * @move from class-lp-user.php method can_retry_course since 4.0.0
501 - * @use LP_User::can_retry_course
502 - * @return int|mixed|null
503 - * @since 4.2.7.3
504 - * @version 1.0.0
505 - */
506 - public function can_retake() {
507 - $flag = 0;
508 -
509 - try {
510 - $course = $this->get_course_model();
511 - if ( ! $course ) {
512 - return $flag;
513 - }
514 -
515 - $retake_option = (int) $course->get_meta_value_by_key( CoursePostModel::META_KEY_RETAKE_COUNT, 0 );
516 - if ( $retake_option > 0 ) {
517 - /**
518 - * Check course is finished
519 - * Check duration is blocked
520 - */
521 - if ( ! $this->has_finished() ) {
522 - if ( 0 !== $this->timestamp_remaining_duration() ) {
523 - throw new Exception();
524 - }
525 - }
526 -
527 - $retaken = $this->get_retaken_count();
528 - $can_retake_times = $retake_option - $retaken;
529 -
530 - if ( $can_retake_times > 0 ) {
531 - $flag = $can_retake_times;
532 - }
533 - }
534 - } catch ( Exception $e ) {
535 -
536 - }
537 -
538 - return apply_filters( 'learn-press/user/course/can-retake', $flag, $this );
539 - }
540 -
541 - /**
542 - * Get retaken count of user attend course.
543 - *
544 - * @return int
545 - */
546 - public function get_retaken_count(): int {
547 - return (int) $this->get_meta_value_from_key( self::META_KEY_RETAKEN_COUNT, 0 );
548 - }
549 -
550 - /**
551 - * Check time remaining course when enable duration expire
552 - * Value: -1 is no limit (default)
553 - * Value: 0 is block
554 - * Administrator || (is instructor && is author course) will be not block.
555 - *
556 - * @return int second
557 - * @since 4.0.0
558 - * @author tungnx
559 - * @version 1.0.1
560 - * @depecated 4.2.7.6, instead of get_time_remaining
561 - */
562 - public function timestamp_remaining_duration(): int {
563 - $timestamp_remaining = - 1;
564 - $user = $this->get_user_model();
565 - /**
566 - * @var CourseModel $course
567 - */
568 - $course = $this->get_course_model();
569 - if ( ! $user || ! $course ) {
570 - return $timestamp_remaining;
571 - }
572 -
573 - $author = $course->get_author_model();
574 - if ( ! $author ) {
575 - return $timestamp_remaining;
576 - }
577 -
578 - $user_id = $user->get_id();
579 -
580 - if ( current_user_can( 'administrator' ) ||
581 - ( current_user_can( LP_TEACHER_ROLE ) && $author->get_id() === $user_id ) ) {
582 - return $timestamp_remaining;
583 - }
584 -
585 - if ( 0 === (int) $course->get_duration() ) {
586 - return $timestamp_remaining;
587 - }
588 -
589 - if ( ! $course->enable_block_when_expire() ) {
590 - return $timestamp_remaining;
591 - }
592 -
593 - $course_start_time_str = $this->get_start_time();
594 - $course_start_time = new LP_Datetime( $course_start_time_str );
595 - $course_start_time = $course_start_time->get_raw_date();
596 - $duration = $course->get_duration();
597 - $timestamp_expire = strtotime( $course_start_time . ' +' . $duration );
598 - $timestamp_current = time();
599 - $timestamp_remaining = $timestamp_expire - $timestamp_current;
600 -
601 - if ( $timestamp_remaining < 0 ) {
602 - $timestamp_remaining = 0;
603 - }
604 -
605 - return apply_filters( 'learnpress/course/block_duration_expire/timestamp_remaining', $timestamp_remaining );
606 - }
607 -
608 - /**
609 - * Check time remaining course when enable duration expire
610 - * Value: -1 is no limit (default)
611 - * Value: 0 is block
612 - * Administrator || (is instructor && is author course) will be not block.
613 - *
614 - * @return int second
615 - * @since 4.2.7.6
616 - * @version 1.0.0
617 - */
618 - public function get_time_remaining(): int {
619 - $timestamp_remaining = - 1;
620 - $userModel = $this->get_user_model();
621 - $courseModel = $this->get_course_model();
622 - if ( ! $userModel || ! $courseModel ) {
623 - return $timestamp_remaining;
624 - }
625 -
626 - $author = $courseModel->get_author_model();
627 - if ( ! $author ) {
628 - return $timestamp_remaining;
629 - }
630 -
631 - /*$user_id = $userModel->get_id();
632 - $user_wp = new \WP_User( $userModel );
633 - if ( user_can( $user_wp, ADMIN_ROLE ) ||
634 - ( user_can( $user_wp, LP_TEACHER_ROLE ) && $author->get_id() === $user_id ) ) {
635 - return $timestamp_remaining;
636 - }*/
637 -
638 - if ( 0 === (int) $courseModel->get_duration() ) {
639 - return $timestamp_remaining;
640 - }
641 -
642 - if ( ! $courseModel->enable_block_when_expire() ) {
643 - return $timestamp_remaining;
644 - }
645 -
646 - $course_start_time = new LP_Datetime( $this->get_start_time() );
647 - $course_start_time = $course_start_time->get_raw_date();
648 - $duration = $courseModel->get_duration();
649 - $timestamp_expire = strtotime( $course_start_time . ' +' . $duration );
650 - $timestamp_current = time();
651 - $timestamp_remaining = $timestamp_expire - $timestamp_current;
652 -
653 - if ( $timestamp_remaining < 0 ) {
654 - $timestamp_remaining = 0;
655 - }
656 -
657 - return apply_filters( 'learnpress/course/block_duration_expire/timestamp_remaining', $timestamp_remaining );
658 - }
659 -
660 - /**
661 - * Get completed items.
662 - *
663 - * @return object
664 - * @modify 4.1.4.1
665 - * @since 4.0.0
666 - * @version 4.0.3
667 - */
668 - public function count_items_completed() {
669 - $lp_user_items_db = LP_User_Items_DB::getInstance();
670 - $count_items_completed = new stdClass();
671 -
672 - try {
673 - $key_cache_first = "userCourseModel/count_items_completed/{$this->user_id}/{$this->item_id}";
674 - $count_items_completed = LP_Cache::cache_load_first( 'get', $key_cache_first );
675 - if ( false !== $count_items_completed ) {
676 - return $count_items_completed;
677 - }
678 -
679 - $filter_count = new LP_User_Items_Filter();
680 - $filter_count->parent_id = $this->get_user_item_id();
681 - $filter_count->item_id = $this->item_id;
682 - $filter_count->user_id = $this->user_id;
683 - $filter_count->status = LP_ITEM_COMPLETED;
684 - $filter_count->graduation = LP_COURSE_GRADUATION_PASSED;
685 - $count_items_completed = $lp_user_items_db->count_items_of_course_with_status( $filter_count );
686 -
687 - // Set cache first
688 - LP_Cache::cache_load_first( 'set', $key_cache_first, $count_items_completed );
689 - } catch ( Throwable $e ) {
690 - error_log( __METHOD__ . ': ' . $e->getMessage() );
691 - }
692 -
693 - return $count_items_completed;
694 - }
695 -
696 - /**
697 - * Get graduation of course.
698 - *
699 - * @return string
700 - * @since 4.2.7.2
701 - * @version 1.0.1
702 - */
703 - public function get_graduation(): string {
704 - return (string) $this->graduation;
705 - }
706 -
707 - /**
708 - * Check course is passed or not.
709 - *
710 - * @return bool
711 - * @since 4.2.7.2
712 - * @version 1.0.0
713 - */
714 - public function is_passed(): bool {
715 - return $this->graduation === UserItemModel::GRADUATION_PASSED;
716 - }
717 -
718 - /**
719 - * Check course is canceled or not.
720 - *
721 - * @return bool
722 - * @since 4.2.7.6
723 - * @version 1.0.0
724 - */
725 - public function has_canceled(): bool {
726 - return $this->status === self::STATUS_CANCEL;
727 - }
728 -
729 - /**
730 - * Evaluate course result by lessons.
731 - *
732 - * @param $count_items_completed
733 - * @param int $total_item_lesson
734 - *
735 - * @return array
736 - * @author tungnx
737 - * @since 4.1.4.1
738 - * @version 1.0.0
739 - */
740 - protected function evaluate_course_by_lesson( $count_items_completed, int $total_item_lesson = 0 ): array {
741 - $evaluate = array(
742 - 'result' => 0,
743 - 'pass' => 0,
744 - );
745 -
746 - $count_items_completed = intval( $count_items_completed->{LP_LESSON_CPT . '_status_completed'} ?? 0 );
747 -
748 - if ( $total_item_lesson && $count_items_completed ) {
749 - $evaluate['result'] = $count_items_completed * 100 / $total_item_lesson;
750 - }
751 -
752 - $passing_condition = $this->get_course_model()->get_passing_condition();
753 - if ( $evaluate['result'] >= $passing_condition ) {
754 - $evaluate['pass'] = 1;
755 - }
756 -
757 - return $evaluate;
758 - }
759 -
760 - /**
761 - * Evaluate course result by final quiz.
762 - *
763 - * @return array
764 - */
765 - protected function evaluate_course_by_final_quiz(): array {
766 - $lp_user_items_db = LP_User_Items_DB::getInstance();
767 - $lp_user_item_result_db = LP_User_Items_Result_DB::instance();
768 - $evaluate = array(
769 - 'result' => 0,
770 - 'pass' => 0,
771 - );
772 -
773 - try {
774 - $courseModel = $this->get_course_model();
775 - if ( ! $courseModel ) {
776 - return $evaluate;
777 - }
778 -
779 - $quiz_final_id = $courseModel->get_meta_value_by_key( CoursePostModel::META_KEY_FINAL_QUIZ, 0 );
780 - if ( ! $quiz_final_id ) {
781 - return $evaluate;
782 - }
783 -
784 - $quizPostModel = QuizPostModel::find( $quiz_final_id, true );
785 - if ( ! $quizPostModel ) {
786 - return $evaluate;
787 - }
788 -
789 - $filter = new LP_User_Items_Filter();
790 - $filter->query_type = 'get_row';
791 - $filter->parent_id = $this->get_user_item_id();
792 - $filter->item_type = LP_QUIZ_CPT;
793 - $filter->item_id = $quiz_final_id;
794 - $user_quiz = $lp_user_items_db->get_user_course_items_by_item_type( $filter );
795 - if ( ! $user_quiz ) {
796 - throw new Exception();
797 - }
798 -
799 - // Get result did quiz
800 - $quiz_result = $lp_user_item_result_db->get_result( $user_quiz->user_item_id );
801 -
802 - if ( $quiz_result ) {
803 - if ( ! isset( $quiz_result['result'] ) ) {
804 - $evaluate['result'] = $quiz_result['user_mark'] * 100 / $quiz_result['mark'];
805 - } else {
806 - $evaluate['result'] = $quiz_result['result'];
807 - }
808 -
809 - $passing_condition = $quizPostModel->get_passing_grade();
810 - if ( $evaluate['result'] >= $passing_condition ) {
811 - $evaluate['pass'] = 1;
812 - }
813 - }
814 - } catch ( Throwable $e ) {
815 -
816 - }
817 -
818 - return $evaluate;
819 - }
820 -
821 - /**
822 - * Evaluate course results by count quizzes passed/all quizzes.
823 - *
824 - * @author tungnx
825 - * @since 4.1.4.1
826 - * @version 1.0.0
827 - */
828 - protected function evaluate_course_by_quizzes_passed( $count_items_completed, $total_item_quizzes ): array {
829 - $evaluate = array(
830 - 'result' => 0,
831 - 'pass' => 0,
832 - );
833 -
834 - $count_items_completed = intval( $count_items_completed->{LP_QUIZ_CPT . '_graduation_passed'} ?? 0 );
835 -
836 - if ( $total_item_quizzes && $count_items_completed ) {
837 - $evaluate['result'] = $count_items_completed * 100 / $total_item_quizzes;
838 -
839 - $passing_condition = $this->get_course_model()->get_passing_condition();
840 - if ( $evaluate['result'] >= $passing_condition ) {
841 - $evaluate['pass'] = 1;
842 - }
843 - }
844 -
845 - return $evaluate;
846 - }
847 -
848 - /**
849 - * Evaluate course results by count questions true/all questions.
850 - *
851 - * @author tungnx
852 - * @since 4.1.4.1
853 - * @version 1.0.0
854 - */
855 - private function evaluate_course_by_questions( &$evaluate, $lp_quizzes, $total_questions ) {
856 - $lp_user_item_results_db = LP_User_Items_Result_DB::instance();
857 - $count_questions_correct = 0;
858 -
859 - // get questions correct
860 - foreach ( $lp_quizzes as $lp_quiz ) {
861 - $lp_quiz_result = $lp_user_item_results_db->get_result( $lp_quiz->user_item_id );
862 - if ( $lp_quiz_result ) {
863 - $count_questions_correct += $lp_quiz_result['question_correct'];
864 - }
865 - }
866 -
867 - if ( $total_questions && $count_questions_correct ) {
868 - $evaluate['result'] = $count_questions_correct * 100 / $total_questions;
869 -
870 - $passing_condition = $this->get_course_model()->get_passing_condition();
871 - if ( $evaluate['result'] >= $passing_condition ) {
872 - $evaluate['pass'] = 1;
873 - }
874 - }
875 - }
876 -
877 - /**
878 - * Evaluate course results by total mark of questions.
879 - *
880 - * @author tungnx
881 - * @since 4.1.4.1
882 - * @version 1.0.0
883 - */
884 - private function evaluate_course_by_mark( &$evaluate, $lp_quizzes, $total_mark_questions ) {
885 - $lp_user_item_results_db = LP_User_Items_Result_DB::instance();
886 - $count_mark_questions_receiver = 0;
887 -
888 - foreach ( $lp_quizzes as $lp_quiz ) {
889 - $lp_quiz_result = $lp_user_item_results_db->get_result( $lp_quiz->user_item_id );
890 - if ( $lp_quiz_result ) {
891 - $count_mark_questions_receiver += $lp_quiz_result['user_mark'];
892 - }
893 - }
894 -
895 - if ( $count_mark_questions_receiver && $total_mark_questions ) {
896 - $evaluate['result'] = $count_mark_questions_receiver * 100 / $total_mark_questions;
897 -
898 - $passing_condition = $this->get_course_model()->get_passing_condition();
899 - if ( $evaluate['result'] >= $passing_condition ) {
900 - $evaluate['pass'] = 1;
901 - }
902 - }
903 - }
904 -
905 - /**
906 - * Evaluate course results by total mark of questions.
907 - *
908 - * @author tungnx
909 - * @since 4.1.4.1
910 - * @version 1.0.2
911 - */
912 - protected function evaluate_course_by_question( string $evaluate_type ): array {
913 - $lp_user_items_db = LP_User_Items_DB::getInstance();
914 - $evaluate = array(
915 - 'result' => 0,
916 - 'pass' => 0,
917 - );
918 -
919 - try {
920 - // get quiz_ids
921 - $filter_get_quiz_ids = new LP_User_Items_Filter();
922 - $filter_get_quiz_ids->parent_id = $this->get_user_item_id();
923 - $filter_get_quiz_ids->item_type = LP_QUIZ_CPT;
924 - $lp_quizzes = $lp_user_items_db->get_user_course_items_by_item_type( $filter_get_quiz_ids );
925 -
926 - // Get total questions, mark
927 - $course = $this->get_course_model();
928 -
929 - $total_questions = 0;
930 - $total_mark_question = 0;
931 -
932 - // Get all items' course
933 - $sections_items = $course->get_section_items();
934 -
935 - foreach ( $sections_items as $section_items ) {
936 - foreach ( $section_items->items as $item ) {
937 - $itemObj = LP_Course_Item::get_item( $item->id );
938 - if ( ! $itemObj instanceof LP_Course_Item ) {
939 - continue;
940 - }
941 -
942 - if ( $item->type == LP_QUIZ_CPT ) {
943 - $total_questions += count( $itemObj->get_question_ids() );
944 - $total_mark_question += $itemObj->get_mark();
945 - }
946 - }
947 - }
948 - // End get total questions, mark
949 -
950 - switch ( $evaluate_type ) {
951 - case 'evaluate_questions':
952 - $this->evaluate_course_by_questions( $evaluate, $lp_quizzes, $total_questions );
953 - break;
954 - case 'evaluate_mark':
955 - $this->evaluate_course_by_mark( $evaluate, $lp_quizzes, $total_mark_question );
956 - break;
957 - default:
958 - break;
959 - }
960 -
961 - // Get results of each quiz - has questions
962 - } catch ( Throwable $e ) {
963 - error_log( __FUNCTION__ . ': ' . $e->getMessage() );
964 - }
965 -
966 - return $evaluate;
967 - }
968 -
969 - protected function count_item_completed() {
970 - }
971 -
972 - /**
973 - * Check user can finish course or not.
974 - *
975 - * @return bool|WP_Error
976 - *
977 - * @since 4.2.7.5
978 - * @version 1.0.0
979 - */
980 - public function can_finish() {
981 - $can_finish = true;
982 -
983 - try {
984 - $courseModel = $this->get_course_model();
985 - if ( ! $courseModel ) {
986 - throw new Exception( __( 'Course not exists!', 'learnpress' ) );
987 - }
988 -
989 - $can_impact_item = $this->can_impact_item();
990 - if ( $can_impact_item instanceof WP_Error ) {
991 - throw new Exception( $can_impact_item->get_error_message() );
992 - }
993 -
994 - $course_results = $this->calculate_course_results();
995 - if ( empty( $course_results['pass'] ) ) {
996 - $allow_finish_when_all_item_completed = $courseModel->get_meta_value_by_key(
997 - CoursePostModel::META_KEY_HAS_FINISH,
998 - 'yes'
999 - );
1000 - if ( $allow_finish_when_all_item_completed ) {
1001 - $course_total_items_obj = $courseModel->get_total_items();
1002 - if ( $course_total_items_obj && $course_results['completed_items'] < $course_total_items_obj->count_items ) {
1003 - throw new Exception( __( 'You must complete all items in course', 'learnpress' ) );
1004 - }
1005 - } else {
1006 - throw new Exception( __( 'You must passed course', 'learnpress' ) );
1007 - }
1008 - }
1009 - } catch ( Throwable $e ) {
1010 - $can_finish = new WP_Error( 'lp_user_course_can_finish_err', $e->getMessage() );
1011 - }
1012 -
1013 - return apply_filters( 'learn-press/user-course/can-finish', $can_finish, $this );
1014 - }
1015 -
1016 - /**
1017 - * Handle finish course.
1018 - *
1019 - * @throws Exception
1020 - * @version 1.0.1
1021 - * @since 4.2.7.6
1022 - */
1023 - public function handle_finish() {
1024 - $can_finish = $this->can_finish();
1025 - if ( is_wp_error( $can_finish ) ) {
1026 - throw new Exception( $can_finish->get_error_message() );
1027 - }
1028 -
1029 - $course_results = $this->calculate_course_results();
1030 - $this->graduation = $course_results['pass'] ? LP_COURSE_GRADUATION_PASSED : LP_COURSE_GRADUATION_FAILED;
1031 - $this->status = LP_COURSE_FINISHED;
1032 - $this->end_time = gmdate( LP_Datetime::$format, time() );
1033 - $this->save();
1034 -
1035 - // Save result for course
1036 - LP_User_Items_Result_DB::instance()->update( $this->get_user_item_id(), wp_json_encode( $course_results ) );
1037 -
1038 - do_action( 'learn-press/user-course-finished', $this->item_id, $this->user_id, $this->get_user_item_id() );
1039 - do_action( 'learn-press/user-course/finished', $this );
1040 - }
1041 -
1042 - /**
1043 - * Handle retake course
1044 - *
1045 - * @throws Exception
1046 - * @since 4.2.7.6
1047 - * @version 1.0.0
1048 - */
1049 - public function handle_retake() {
1050 - $remaining_retake = $this->can_retake();
1051 - if ( $remaining_retake === 0 ) {
1052 - throw new Exception( __( 'You can not retake this course!', 'learnpress' ) );
1053 - }
1054 -
1055 - $this->status = self::STATUS_ENROLLED;
1056 - $this->graduation = self::GRADUATION_IN_PROGRESS;
1057 - $this->start_time = gmdate( LP_Datetime::$format, time() );
1058 - $this->end_time = null;
1059 - $this->set_meta_value_for_key( self::META_KEY_RETAKEN_COUNT, $this->get_retaken_count() + 1 );
1060 - $this->save();
1061 -
1062 - $courseModel = $this->get_course_model();
1063 -
1064 - if ( $courseModel->count_items() > 0 ) {
1065 - // Remove items' course user learned.
1066 - $filter_remove = new LP_User_Items_Filter();
1067 - $filter_remove->parent_id = $this->get_user_item_id();
1068 - $filter_remove->user_id = $this->user_id;
1069 - $filter_remove->limit = - 1;
1070 - LP_User_Items_DB::getInstance()->remove_items_of_user_course( $filter_remove );
1071 -
1072 - // Clean cache items.
1073 - foreach ( $courseModel->get_section_items() as $section_items ) {
1074 - foreach ( $section_items->items as $item ) {
1075 - $key_cache = "userItemModel/find/{$this->user_id}/{$item->id}/{$item->type}/{$this->item_id}/" . LP_COURSE_CPT;
1076 - $lpUserItemCache = new LP_User_Items_Cache();
1077 - $lpUserItemCache->clear( $key_cache );
1078 - }
1079 - }
1080 - }
1081 -
1082 - // Create new result in table learnpress_user_item_results.
1083 - LP_User_Items_Result_DB::instance()->insert( $this->get_user_item_id() );
1084 - }
1085 -
1086 - /**
1087 - * Check can impact item.
1088 - *
1089 - * @return bool|WP_Error
1090 - * @since 4.2.7.6
1091 - * @version 1.0.1
1092 - */
1093 - public function can_impact_item() {
1094 - $can_impact_item = true;
1095 -
1096 - // For case user Guest
1097 - $userModel = $this->get_user_model();
1098 - if ( ! $userModel ) {
1099 - $can_impact_item = new WP_Error( 'user_not_exists', __( 'User not exists!', 'learnpress' ) );
1100 - }
1101 -
1102 - $status = $this->get_status();
1103 - if ( $this->has_canceled() || ! $this->has_enrolled() ) {
1104 - $can_impact_item = new WP_Error( 'user_not_enroll_course', __( 'You have not enroll this course!', 'learnpress' ) );
1105 - }
1106 -
1107 - if ( $this->has_finished() ) {
1108 - $can_impact_item = new WP_Error( 'user_finished_course', __( 'You have finished this course!', 'learnpress' ) );
1109 - }
1110 -
1111 - if ( $this->get_time_remaining() === 0 ) {
1112 - $can_impact_item = new WP_Error( 'course_is_blocked', __( 'Course was blocked by expire!', 'learnpress' ) );
1113 - }
1114 -
1115 - return apply_filters( 'learn-press/user-course/can-impact-item', $can_impact_item, $this );
1116 - }
1117 -
1118 - /**
1119 - * Clean caches.
1120 - *
1121 - * @return void
1122 - *
1123 - * @since 4.2.5.4
1124 - * @version 1.0.1
1125 - */
1126 - public function clean_caches() {
1127 - $key_cache = "userCourseModel/find/{$this->user_id}/{$this->item_id}/{$this->item_type}";
1128 - $lpUserCourseCache = new LP_Cache();
1129 - $lpUserCourseCache->clear( $key_cache );
1130 -
1131 - parent::clean_caches();
1132 - // Clear cache total students enrolled of a course.
1133 - $lp_course_cache = new LP_Course_Cache( true );
1134 - $lp_course_cache->clean_total_students_enrolled( $this->item_id );
1135 - $lp_course_cache->clean_total_students_enrolled_or_purchased( $this->item_id );
1136 - // Clear cache count students of many course
1137 - $lp_courses_cache = new LP_Courses_Cache( true );
1138 - $lp_courses_cache->clear_cache_on_group( LP_Courses_Cache::KEYS_COUNT_STUDENT_COURSES );
1139 - }
1140 -}
1 +<?php
2 +
3 +/**
4 + * Class UserItemModel
5 + *
6 + * @package LearnPress/Classes
7 + * @version 1.0.1
8 + * @since 4.2.5
9 + */
10 +
11 +namespace LearnPress\Models\UserItems;
12 +
13 +use Exception;
14 +use LearnPress\Filters\UserItemsFilter;
15 +use LearnPress\Models\CourseModel;
16 +use LearnPress\Models\CoursePostModel;
17 +use LearnPress\Models\QuizPostModel;
18 +use LP_Cache;
19 +use LP_Course_Cache;
20 +use LP_Course_Item;
21 +use LP_Courses_Cache;
22 +use LP_Datetime;
23 +use LP_Debug;
24 +use LP_User;
25 +use LP_User_Item_Course;
26 +use LP_User_Items_Cache;
27 +use LP_User_Items_DB;
28 +use LP_User_Items_Filter;
29 +use LP_User_Items_Result_DB;
30 +use stdClass;
31 +use Throwable;
32 +use WP_Error;
33 +
34 +class UserCourseModel extends UserItemModel {
35 + /**
36 + * Item type Course
37 + *
38 + * @var string Item type
39 + */
40 + public $item_type = LP_COURSE_CPT;
41 + /**
42 + * Ref type Order
43 + *
44 + * @var string
45 + */
46 + public $ref_type = LP_ORDER_CPT;
47 +
48 + // Constants status
49 + const STATUS_PURCHASED = 'purchased';
50 +
51 + /**
52 + * Constant key meta
53 + */
54 + const META_KEY_RETAKEN_COUNT = '_lp_retaken_count';
55 +
56 + public function __construct( $data = null ) {
57 + parent::__construct( $data );
58 +
59 + if ( $data ) {
60 + $this->get_course_model();
61 + }
62 + }
63 +
64 + /**
65 + * Get course model
66 + *
67 + * @return bool|CourseModel
68 + */
69 + public function get_course_model() {
70 + return CourseModel::find( $this->item_id, true );
71 + }
72 +
73 + /**
74 + * Find User Item by user_id, item_id, item_type.
75 + *
76 + * @param int $user_id
77 + * @param int $course_id
78 + * @param bool $check_cache
79 + *
80 + * @return false|static
81 + * @since 4.2.7
82 + * @version 1.0.2
83 + */
84 + public static function find( int $user_id, int $course_id, bool $check_cache = false ) {
85 + static $staticData = [];
86 +
87 + $filter = new LP_User_Items_Filter();
88 + $filter->user_id = $user_id;
89 + $filter->item_id = $course_id;
90 + $filter->item_type = LP_COURSE_CPT;
91 + $key_cache = "userCourseModel/find/{$user_id}/{$course_id}/{$filter->item_type}";
92 + $lpUserCourseCache = new LP_Cache();
93 +
94 + // Check cache
95 + if ( $check_cache ) {
96 + $userCourseModel = $lpUserCourseCache->get_cache( $key_cache );
97 + if ( $userCourseModel instanceof UserCourseModel ) {
98 + return $userCourseModel;
99 + }
100 +
101 + if ( isset( $staticData[ $key_cache ] ) ) {
102 + return $staticData[ $key_cache ];
103 + }
104 + }
105 +
106 + $userCourseModel = static::get_user_item_model_from_db( $filter );
107 +
108 + // Set cache
109 + if ( $userCourseModel instanceof UserCourseModel ) {
110 + $lpUserCourseCache->set_cache( $key_cache, $userCourseModel );
111 + }
112 +
113 + $staticData[ $key_cache ] = $userCourseModel;
114 +
115 + return $userCourseModel;
116 + }
117 +
118 + /**
119 + * Get user_item is child of user course.
120 + *
121 + * @param int $item_id
122 + * @param string $item_type
123 + *
124 + * @return false|UserItemModel|UserQuizModel|mixed
125 + * @since 4.2.5
126 + * @version 1.0.1
127 + */
128 + public function get_item_attend( int $item_id, string $item_type = '' ) {
129 + $item = false;
130 +
131 + try {
132 + $item = UserItemModel::find_user_item(
133 + $this->user_id,
134 + $item_id,
135 + $item_type,
136 + $this->item_id,
137 + $this->item_type,
138 + true
139 + );
140 +
141 + if ( $item ) {
142 + switch ( $item_type ) {
143 + case LP_LESSON_CPT:
144 + $item = new UserLessonModel( $item );
145 + break;
146 + case LP_QUIZ_CPT:
147 + $item = new UserQuizModel( $item );
148 + break;
149 + default:
150 + $item = apply_filters( 'learn-press/userCourseModel/get-item-attend', $item, $this, $item_id, $item_type );
151 + break;
152 + }
153 + }
154 + } catch ( Throwable $e ) {
155 + error_log( __METHOD__ . ': ' . $e->getMessage() );
156 + }
157 +
158 + return $item;
159 + }
160 +
161 + /**
162 + * Get item continue to learn.
163 + *
164 + * @return mixed|null
165 + * @since 4.2.7.6
166 + * @version 1.0.0
167 + */
168 + public function get_item_continue() {
169 + if ( isset( $this->meta_data->item_continue ) ) {
170 + return $this->meta_data->item_continue;
171 + }
172 +
173 + $itemModel = null;
174 +
175 + try {
176 + $courseModel = $this->get_course_model();
177 + $totalItemsObj = $courseModel->count_items();
178 + if ( empty( $totalItemsObj ) ) {
179 + return $itemModel;
180 + }
181 +
182 + $item_id = 0;
183 + $item_type = '';
184 + $sections_items = $courseModel->get_section_items();
185 + foreach ( $sections_items as $section_items ) {
186 + if ( $itemModel ) {
187 + break;
188 + }
189 +
190 + foreach ( $section_items->items as $item ) {
191 + $item_id = $item->id ?? $item->item_id;
192 + $item_type = $item->type ?? $item->item_type;
193 +
194 + $userItemModel = UserItemModel::find_user_item(
195 + $this->user_id,
196 + $item_id,
197 + $item_type,
198 + $courseModel->get_id(),
199 + LP_COURSE_CPT,
200 + true
201 + );
202 + if ( ! $userItemModel || $userItemModel->get_status() !== LP_ITEM_COMPLETED ) {
203 + $itemModel = $courseModel->get_item_model( $item_id, $item_type );
204 + break;
205 + }
206 + }
207 + }
208 +
209 + // For all items are completed
210 + if ( empty( $itemModel ) && ! empty( $item_id ) && ! empty( $item_type ) ) {
211 + $itemModel = $courseModel->get_item_model( $item_id, $item_type );
212 + }
213 +
214 + $this->meta_data->item_continue = $itemModel;
215 + } catch ( Throwable $e ) {
216 + error_log( __METHOD__ . ': ' . $e->getMessage() );
217 + }
218 +
219 + return $itemModel;
220 + }
221 +
222 + /**
223 + * Find next item when have just completed lesson.
224 + * Logic: find item next not completed, circle to find next item, if not found return current item.
225 + *
226 + * @param int $item_id_current
227 + *
228 + * @since 4.3.2
229 + * @version 1.0.0
230 + * @return mixed|null
231 + */
232 + public function get_item_next_when_complete_lesson( int $item_id_current ) {
233 + $itemModel = null;
234 + $item_before_found = 0;
235 + $item_after_found = 0;
236 +
237 + try {
238 + $courseModel = $this->get_course_model();
239 + $course_items = $courseModel->get_only_items();
240 + $course_item_keys = array_keys( $course_items );
241 +
242 + $index_of_current = array_search( $item_id_current, $course_item_keys, true );
243 +
244 + foreach ( $course_item_keys as $index => $item_id ) {
245 + $course_item = $course_items[ $item_id ];
246 + $item_type = $course_item->item_type;
247 +
248 + $userItemModel = UserItemModel::find_user_item(
249 + $this->user_id,
250 + $item_id,
251 + $item_type,
252 + $courseModel->get_id(),
253 + LP_COURSE_CPT,
254 + true
255 + );
256 + $status_compare = apply_filters(
257 + 'learn-press/user-course-model/get-item-next-when-complete-lesson/status-compare',
258 + [
259 + UserItemModel::STATUS_COMPLETED,
260 + ]
261 + );
262 +
263 + // Found item before current item not completed
264 + if ( $index < $index_of_current ) {
265 + if ( ! $item_before_found &&
266 + ( ! $userItemModel || ! in_array( $userItemModel->get_status(), $status_compare ) ) ) {
267 + $item_before_found = $course_item;
268 + }
269 + } elseif ( $index > $index_of_current ) {
270 + // Found item after current item not completed
271 + if ( ! $item_after_found &&
272 + ( ! $userItemModel || ! in_array( $userItemModel->get_status(), $status_compare ) ) ) {
273 + $item_after_found = $course_item;
274 + break;
275 + }
276 + }
277 + }
278 +
279 + if ( $item_after_found ) {
280 + $itemModel = $courseModel->get_item_model( $item_after_found->item_id, $item_after_found->item_type );
281 + } elseif ( $item_before_found ) {
282 + $itemModel = $courseModel->get_item_model( $item_before_found->item_id, $item_before_found->item_type );
283 + } else {
284 + $itemModel = $courseModel->get_item_model( $item_id_current, LP_LESSON_CPT );
285 + }
286 + } catch ( Throwable $e ) {
287 + error_log( __METHOD__ . ': ' . $e->getMessage() );
288 + }
289 +
290 + return $itemModel;
291 + }
292 +
293 + /**
294 + * Count students.
295 + *
296 + * @param LP_User_Items_Filter|UserItemsFilter $filter
297 + *
298 + * @return int
299 + * @since 4.2.5.4
300 + * @version 1.0.0
301 + */
302 + public static function count_students( $filter ): int {
303 + // Check cache
304 + $key_cache = 'count-courses-student-' . md5( json_encode( $filter ) );
305 + $count = LP_Cache::cache_load_first( 'get', $key_cache );
306 + if ( false !== $count ) {
307 + return $count;
308 + }
309 +
310 + $lp_courses_cache = new LP_Courses_Cache( true );
311 + $count = $lp_courses_cache->get_cache( $key_cache );
312 + if ( false !== $count ) {
313 + LP_Cache::cache_load_first( 'set', $key_cache, $count );
314 +
315 + return $count;
316 + }
317 +
318 + $lp_user_items_db = LP_User_Items_DB::getInstance();
319 + $count = $lp_user_items_db->count_students( $filter );
320 +
321 + // Set cache
322 + $lp_courses_cache->set_cache( $key_cache, $count );
323 + $lp_courses_cache_keys = new LP_Courses_Cache( true );
324 + $lp_courses_cache_keys->save_cache_keys_count_student_courses( $key_cache );
325 + LP_Cache::cache_load_first( 'set', $key_cache, $count );
326 +
327 + return $count;
328 + }
329 +
330 + /**
331 + * Check course of user is enrolled or finished
332 + *
333 + * @return bool
334 + */
335 + public function has_enrolled_or_finished(): bool {
336 + return $this->has_enrolled() || $this->has_finished();
337 + }
338 +
339 + /**
340 + * Check user has enrolled course
341 + *
342 + * @return bool
343 + */
344 + public function has_enrolled(): bool {
345 + return $this->status === UserItemModel::STATUS_ENROLLED;
346 + }
347 +
348 + /**
349 + * Check user has purchased course
350 + *
351 + * @return bool
352 + */
353 + public function has_purchased(): bool {
354 + return $this->status === UserItemModel::STATUS_PURCHASED;
355 + }
356 +
357 + /**
358 + * Check user has finished course
359 + *
360 + * @return bool
361 + */
362 + public function has_finished(): bool {
363 + return $this->status === UserItemModel::STATUS_FINISHED;
364 + }
365 +
366 + /**
367 + * Check course has finished or not.
368 + *
369 + * @return bool
370 + * @move from class-lp-user-item-course.php
371 + * @since 3.0.0
372 + * @version 1.0.2
373 + */
374 + public function is_finished(): bool {
375 + return $this->has_finished();
376 + }
377 +
378 + /**
379 + * Calculate course result
380 + *
381 + * @move from class-lp-user-item-course.php
382 + *
383 + * @param bool $force_cache
384 + *
385 + * @return array [ 'count_items' => int, 'completed_items' => int, 'items' => array, 'evaluate_type' => string, 'pass' => int, 'result' => float ]
386 + * @since 4.1.4
387 + * @version 1.0.3
388 + */
389 + public function calculate_course_results( bool $force_cache = false ): array {
390 + $items = array();
391 + $results = array(
392 + 'count_items' => 0,
393 + 'completed_items' => 0,
394 + 'items' => array(),
395 + 'evaluate_type' => '',
396 + 'pass' => 0,
397 + 'result' => 0,
398 + );
399 +
400 + try {
401 + $courseModel = $this->get_course_model();
402 + if ( ! $courseModel instanceof CourseModel ) {
403 + throw new Exception( 'Course invalid!' );
404 + }
405 +
406 + $key_first_cache = 'calculate_course/' . $this->user_id . '/' . $courseModel->get_id();
407 + $results_cache = LP_Cache::cache_load_first( 'get', $key_first_cache );
408 + if ( false !== $results_cache && ! $force_cache ) {
409 + return $results_cache;
410 + }
411 +
412 + if ( $this->is_finished() ) {
413 + // Get result from lp_user_item_results
414 + // Todo: tungnx - set cache
415 + return LP_User_Items_Result_DB::instance()->get_result( $this->get_user_item_id() );
416 + }
417 +
418 + if ( $this->status !== LP_COURSE_ENROLLED ) {
419 + return $results;
420 + }
421 +
422 + $count_items = $courseModel->count_items();
423 + $count_items_completed = $this->count_items_completed();
424 +
425 + $evaluate_type = $courseModel->get_meta_value_by_key( CoursePostModel::META_KEY_EVALUATION_TYPE, 'evaluate_lesson' );
426 + switch ( $evaluate_type ) {
427 + case 'evaluate_lesson':
428 + $results_evaluate = $this->evaluate_course_by_lesson( $count_items_completed, $courseModel->count_items( LP_LESSON_CPT ) );
429 + break;
430 + case 'evaluate_final_quiz':
431 + $results_evaluate = $this->evaluate_course_by_final_quiz();
432 + break;
433 + case 'evaluate_quiz':
434 + $results_evaluate = $this->evaluate_course_by_quizzes_passed( $count_items_completed, $courseModel->count_items( LP_QUIZ_CPT ) );
435 + break;
436 + case 'evaluate_questions':
437 + case 'evaluate_mark':
438 + $results_evaluate = $this->evaluate_course_by_question( $evaluate_type );
439 + break;
440 + default:
441 + // Old Hook
442 + if ( has_filter( 'learn-press/evaluate_passed_conditions' ) ) {
443 + $user_course_old = new LP_User_Item_Course( $this );
444 + $results = apply_filters( 'learn-press/evaluate_passed_conditions', $results, $evaluate_type, $user_course_old );
445 + }
446 +
447 + $results_evaluate = apply_filters( 'learn-press/evaluate/calculate', $results, $evaluate_type, $this );
448 + break;
449 + }
450 +
451 + if ( ! is_array( $results_evaluate ) ) {
452 + $results_evaluate = array(
453 + 'result' => 0,
454 + 'pass' => 0,
455 + );
456 + }
457 +
458 + $results_evaluate['result'] = round( $results_evaluate['result'], 2 );
459 + if ( $results_evaluate['result'] > 100 ) {
460 + $results_evaluate['result'] = 100;
461 + }
462 +
463 + $completed_items = intval( $count_items_completed->count_status ?? 0 );
464 +
465 + $item_types = CourseModel::item_types_support();
466 + foreach ( $item_types as $item_type ) {
467 + $item_type_key = str_replace( 'lp_', '', $item_type );
468 +
469 + $items[ $item_type_key ] = array(
470 + 'completed' => $count_items_completed->{$item_type . '_status_completed'} ?? 0,
471 + 'passed' => $count_items_completed->{$item_type . '_graduation_passed'} ?? 0,
472 + 'total' => $courseModel->count_items( $item_type ),
473 + );
474 + }
475 +
476 + $results = array_merge(
477 + $results_evaluate,
478 + compact( 'count_items', 'completed_items', 'items', 'evaluate_type' )
479 + );
480 +
481 + $results = apply_filters(
482 + 'learn-press/update-course-results',
483 + $results,
484 + $this->item_id,
485 + $this->user_id,
486 + $this
487 + );
488 +
489 + LP_Cache::cache_load_first( 'set', $key_first_cache, $results );
490 + } catch ( Throwable $e ) {
491 + LP_Debug::error_log( $e );
492 + }
493 +
494 + return $results;
495 + }
496 +
497 + /**
498 + * Check user can retake course or not.
499 + *
500 + * @move from class-lp-user.php method can_retry_course since 4.0.0
501 + * @use LP_User::can_retry_course
502 + * @return int|mixed|null
503 + * @since 4.2.7.3
504 + * @version 1.0.0
505 + */
506 + public function can_retake() {
507 + $flag = 0;
508 +
509 + try {
510 + $course = $this->get_course_model();
511 + if ( ! $course ) {
512 + return $flag;
513 + }
514 +
515 + $retake_option = (int) $course->get_meta_value_by_key( CoursePostModel::META_KEY_RETAKE_COUNT, 0 );
516 + if ( $retake_option > 0 ) {
517 + /**
518 + * Check course is finished
519 + * Check duration is blocked
520 + */
521 + if ( ! $this->has_finished() ) {
522 + if ( 0 !== $this->timestamp_remaining_duration() ) {
523 + throw new Exception();
524 + }
525 + }
526 +
527 + $retaken = $this->get_retaken_count();
528 + $can_retake_times = $retake_option - $retaken;
529 +
530 + if ( $can_retake_times > 0 ) {
531 + $flag = $can_retake_times;
532 + }
533 + }
534 + } catch ( Exception $e ) {
535 +
536 + }
537 +
538 + return apply_filters( 'learn-press/user/course/can-retake', $flag, $this );
539 + }
540 +
541 + /**
542 + * Get retaken count of user attend course.
543 + *
544 + * @return int
545 + */
546 + public function get_retaken_count(): int {
547 + return (int) $this->get_meta_value_from_key( self::META_KEY_RETAKEN_COUNT, 0 );
548 + }
549 +
550 + /**
551 + * Check time remaining course when enable duration expire
552 + * Value: -1 is no limit (default)
553 + * Value: 0 is block
554 + * Administrator || (is instructor && is author course) will be not block.
555 + *
556 + * @return int second
557 + * @since 4.0.0
558 + * @author tungnx
559 + * @version 1.0.1
560 + * @depecated 4.2.7.6, instead of get_time_remaining
561 + */
562 + public function timestamp_remaining_duration(): int {
563 + $timestamp_remaining = - 1;
564 + $user = $this->get_user_model();
565 + /**
566 + * @var CourseModel $course
567 + */
568 + $course = $this->get_course_model();
569 + if ( ! $user || ! $course ) {
570 + return $timestamp_remaining;
571 + }
572 +
573 + $author = $course->get_author_model();
574 + if ( ! $author ) {
575 + return $timestamp_remaining;
576 + }
577 +
578 + $user_id = $user->get_id();
579 +
580 + if ( current_user_can( 'administrator' ) ||
581 + ( current_user_can( LP_TEACHER_ROLE ) && $author->get_id() === $user_id ) ) {
582 + return $timestamp_remaining;
583 + }
584 +
585 + if ( 0 === (int) $course->get_duration() ) {
586 + return $timestamp_remaining;
587 + }
588 +
589 + if ( ! $course->enable_block_when_expire() ) {
590 + return $timestamp_remaining;
591 + }
592 +
593 + $course_start_time_str = $this->get_start_time();
594 + $course_start_time = new LP_Datetime( $course_start_time_str );
595 + $course_start_time = $course_start_time->get_raw_date();
596 + $duration = $course->get_duration();
597 + $timestamp_expire = strtotime( $course_start_time . ' +' . $duration );
598 + $timestamp_current = time();
599 + $timestamp_remaining = $timestamp_expire - $timestamp_current;
600 +
601 + if ( $timestamp_remaining < 0 ) {
602 + $timestamp_remaining = 0;
603 + }
604 +
605 + return apply_filters( 'learnpress/course/block_duration_expire/timestamp_remaining', $timestamp_remaining );
606 + }
607 +
608 + /**
609 + * Check time remaining course when enable duration expire
610 + * Value: -1 is no limit (default)
611 + * Value: 0 is block
612 + * Administrator || (is instructor && is author course) will be not block.
613 + *
614 + * @return int second
615 + * @since 4.2.7.6
616 + * @version 1.0.0
617 + */
618 + public function get_time_remaining(): int {
619 + $timestamp_remaining = - 1;
620 + $userModel = $this->get_user_model();
621 + $courseModel = $this->get_course_model();
622 + if ( ! $userModel || ! $courseModel ) {
623 + return $timestamp_remaining;
624 + }
625 +
626 + $author = $courseModel->get_author_model();
627 + if ( ! $author ) {
628 + return $timestamp_remaining;
629 + }
630 +
631 + /*$user_id = $userModel->get_id();
632 + $user_wp = new \WP_User( $userModel );
633 + if ( user_can( $user_wp, ADMIN_ROLE ) ||
634 + ( user_can( $user_wp, LP_TEACHER_ROLE ) && $author->get_id() === $user_id ) ) {
635 + return $timestamp_remaining;
636 + }*/
637 +
638 + if ( 0 === (int) $courseModel->get_duration() ) {
639 + return $timestamp_remaining;
640 + }
641 +
642 + if ( ! $courseModel->enable_block_when_expire() ) {
643 + return $timestamp_remaining;
644 + }
645 +
646 + $course_start_time = new LP_Datetime( $this->get_start_time() );
647 + $course_start_time = $course_start_time->get_raw_date();
648 + $duration = $courseModel->get_duration();
649 + $timestamp_expire = strtotime( $course_start_time . ' +' . $duration );
650 + $timestamp_current = time();
651 + $timestamp_remaining = $timestamp_expire - $timestamp_current;
652 +
653 + if ( $timestamp_remaining < 0 ) {
654 + $timestamp_remaining = 0;
655 + }
656 +
657 + return apply_filters( 'learnpress/course/block_duration_expire/timestamp_remaining', $timestamp_remaining );
658 + }
659 +
660 + /**
661 + * Get completed items.
662 + *
663 + * @return object
664 + * @modify 4.1.4.1
665 + * @since 4.0.0
666 + * @version 4.0.3
667 + */
668 + public function count_items_completed() {
669 + $lp_user_items_db = LP_User_Items_DB::getInstance();
670 + $count_items_completed = new stdClass();
671 +
672 + try {
673 + $key_cache_first = "userCourseModel/count_items_completed/{$this->user_id}/{$this->item_id}";
674 + $count_items_completed = LP_Cache::cache_load_first( 'get', $key_cache_first );
675 + if ( false !== $count_items_completed ) {
676 + return $count_items_completed;
677 + }
678 +
679 + $filter_count = new LP_User_Items_Filter();
680 + $filter_count->parent_id = $this->get_user_item_id();
681 + $filter_count->item_id = $this->item_id;
682 + $filter_count->user_id = $this->user_id;
683 + $filter_count->status = LP_ITEM_COMPLETED;
684 + $filter_count->graduation = LP_COURSE_GRADUATION_PASSED;
685 + $count_items_completed = $lp_user_items_db->count_items_of_course_with_status( $filter_count );
686 +
687 + // Set cache first
688 + LP_Cache::cache_load_first( 'set', $key_cache_first, $count_items_completed );
689 + } catch ( Throwable $e ) {
690 + error_log( __METHOD__ . ': ' . $e->getMessage() );
691 + }
692 +
693 + return $count_items_completed;
694 + }
695 +
696 + /**
697 + * Get graduation of course.
698 + *
699 + * @return string
700 + * @since 4.2.7.2
701 + * @version 1.0.1
702 + */
703 + public function get_graduation(): string {
704 + return (string) $this->graduation;
705 + }
706 +
707 + /**
708 + * Check course is passed or not.
709 + *
710 + * @return bool
711 + * @since 4.2.7.2
712 + * @version 1.0.0
713 + */
714 + public function is_passed(): bool {
715 + return $this->graduation === UserItemModel::GRADUATION_PASSED;
716 + }
717 +
718 + /**
719 + * Check course is canceled or not.
720 + *
721 + * @return bool
722 + * @since 4.2.7.6
723 + * @version 1.0.0
724 + */
725 + public function has_canceled(): bool {
726 + return $this->status === self::STATUS_CANCEL;
727 + }
728 +
729 + /**
730 + * Evaluate course result by lessons.
731 + *
732 + * @param $count_items_completed
733 + * @param int $total_item_lesson
734 + *
735 + * @return array
736 + * @author tungnx
737 + * @since 4.1.4.1
738 + * @version 1.0.0
739 + */
740 + protected function evaluate_course_by_lesson( $count_items_completed, int $total_item_lesson = 0 ): array {
741 + $evaluate = array(
742 + 'result' => 0,
743 + 'pass' => 0,
744 + );
745 +
746 + $count_items_completed = intval( $count_items_completed->{LP_LESSON_CPT . '_status_completed'} ?? 0 );
747 +
748 + if ( $total_item_lesson && $count_items_completed ) {
749 + $evaluate['result'] = $count_items_completed * 100 / $total_item_lesson;
750 + }
751 +
752 + $passing_condition = $this->get_course_model()->get_passing_condition();
753 + if ( $evaluate['result'] >= $passing_condition ) {
754 + $evaluate['pass'] = 1;
755 + }
756 +
757 + return $evaluate;
758 + }
759 +
760 + /**
761 + * Evaluate course result by final quiz.
762 + *
763 + * @return array
764 + */
765 + protected function evaluate_course_by_final_quiz(): array {
766 + $lp_user_items_db = LP_User_Items_DB::getInstance();
767 + $lp_user_item_result_db = LP_User_Items_Result_DB::instance();
768 + $evaluate = array(
769 + 'result' => 0,
770 + 'pass' => 0,
771 + );
772 +
773 + try {
774 + $courseModel = $this->get_course_model();
775 + if ( ! $courseModel ) {
776 + return $evaluate;
777 + }
778 +
779 + $quiz_final_id = $courseModel->get_meta_value_by_key( CoursePostModel::META_KEY_FINAL_QUIZ, 0 );
780 + if ( ! $quiz_final_id ) {
781 + return $evaluate;
782 + }
783 +
784 + $quizPostModel = QuizPostModel::find( $quiz_final_id, true );
785 + if ( ! $quizPostModel ) {
786 + return $evaluate;
787 + }
788 +
789 + $filter = new LP_User_Items_Filter();
790 + $filter->query_type = 'get_row';
791 + $filter->parent_id = $this->get_user_item_id();
792 + $filter->item_type = LP_QUIZ_CPT;
793 + $filter->item_id = $quiz_final_id;
794 + $user_quiz = $lp_user_items_db->get_user_course_items_by_item_type( $filter );
795 + if ( ! $user_quiz ) {
796 + throw new Exception();
797 + }
798 +
799 + // Get result did quiz
800 + $quiz_result = $lp_user_item_result_db->get_result( $user_quiz->user_item_id );
801 +
802 + if ( $quiz_result ) {
803 + if ( ! isset( $quiz_result['result'] ) ) {
804 + $evaluate['result'] = $quiz_result['user_mark'] * 100 / $quiz_result['mark'];
805 + } else {
806 + $evaluate['result'] = $quiz_result['result'];
807 + }
808 +
809 + $passing_condition = $quizPostModel->get_passing_grade();
810 + if ( $evaluate['result'] >= $passing_condition ) {
811 + $evaluate['pass'] = 1;
812 + }
813 + }
814 + } catch ( Throwable $e ) {
815 +
816 + }
817 +
818 + return $evaluate;
819 + }
820 +
821 + /**
822 + * Evaluate course results by count quizzes passed/all quizzes.
823 + *
824 + * @author tungnx
825 + * @since 4.1.4.1
826 + * @version 1.0.0
827 + */
828 + protected function evaluate_course_by_quizzes_passed( $count_items_completed, $total_item_quizzes ): array {
829 + $evaluate = array(
830 + 'result' => 0,
831 + 'pass' => 0,
832 + );
833 +
834 + $count_items_completed = intval( $count_items_completed->{LP_QUIZ_CPT . '_graduation_passed'} ?? 0 );
835 +
836 + if ( $total_item_quizzes && $count_items_completed ) {
837 + $evaluate['result'] = $count_items_completed * 100 / $total_item_quizzes;
838 +
839 + $passing_condition = $this->get_course_model()->get_passing_condition();
840 + if ( $evaluate['result'] >= $passing_condition ) {
841 + $evaluate['pass'] = 1;
842 + }
843 + }
844 +
845 + return $evaluate;
846 + }
847 +
848 + /**
849 + * Evaluate course results by count questions true/all questions.
850 + *
851 + * @author tungnx
852 + * @since 4.1.4.1
853 + * @version 1.0.0
854 + */
855 + private function evaluate_course_by_questions( &$evaluate, $lp_quizzes, $total_questions ) {
856 + $lp_user_item_results_db = LP_User_Items_Result_DB::instance();
857 + $count_questions_correct = 0;
858 +
859 + // get questions correct
860 + foreach ( $lp_quizzes as $lp_quiz ) {
861 + $lp_quiz_result = $lp_user_item_results_db->get_result( $lp_quiz->user_item_id );
862 + if ( $lp_quiz_result ) {
863 + $count_questions_correct += $lp_quiz_result['question_correct'];
864 + }
865 + }
866 +
867 + if ( $total_questions && $count_questions_correct ) {
868 + $evaluate['result'] = $count_questions_correct * 100 / $total_questions;
869 +
870 + $passing_condition = $this->get_course_model()->get_passing_condition();
871 + if ( $evaluate['result'] >= $passing_condition ) {
872 + $evaluate['pass'] = 1;
873 + }
874 + }
875 + }
876 +
877 + /**
878 + * Evaluate course results by total mark of questions.
879 + *
880 + * @author tungnx
881 + * @since 4.1.4.1
882 + * @version 1.0.0
883 + */
884 + private function evaluate_course_by_mark( &$evaluate, $lp_quizzes, $total_mark_questions ) {
885 + $lp_user_item_results_db = LP_User_Items_Result_DB::instance();
886 + $count_mark_questions_receiver = 0;
887 +
888 + foreach ( $lp_quizzes as $lp_quiz ) {
889 + $lp_quiz_result = $lp_user_item_results_db->get_result( $lp_quiz->user_item_id );
890 + if ( $lp_quiz_result ) {
891 + $count_mark_questions_receiver += $lp_quiz_result['user_mark'];
892 + }
893 + }
894 +
895 + if ( $count_mark_questions_receiver && $total_mark_questions ) {
896 + $evaluate['result'] = $count_mark_questions_receiver * 100 / $total_mark_questions;
897 +
898 + $passing_condition = $this->get_course_model()->get_passing_condition();
899 + if ( $evaluate['result'] >= $passing_condition ) {
900 + $evaluate['pass'] = 1;
901 + }
902 + }
903 + }
904 +
905 + /**
906 + * Evaluate course results by total mark of questions.
907 + *
908 + * @author tungnx
909 + * @since 4.1.4.1
910 + * @version 1.0.2
911 + */
912 + protected function evaluate_course_by_question( string $evaluate_type ): array {
913 + $lp_user_items_db = LP_User_Items_DB::getInstance();
914 + $evaluate = array(
915 + 'result' => 0,
916 + 'pass' => 0,
917 + );
918 +
919 + try {
920 + // get quiz_ids
921 + $filter_get_quiz_ids = new LP_User_Items_Filter();
922 + $filter_get_quiz_ids->parent_id = $this->get_user_item_id();
923 + $filter_get_quiz_ids->item_type = LP_QUIZ_CPT;
924 + $lp_quizzes = $lp_user_items_db->get_user_course_items_by_item_type( $filter_get_quiz_ids );
925 +
926 + // Get total questions, mark
927 + $course = $this->get_course_model();
928 +
929 + $total_questions = 0;
930 + $total_mark_question = 0;
931 +
932 + // Get all items' course
933 + $sections_items = $course->get_section_items();
934 +
935 + foreach ( $sections_items as $section_items ) {
936 + foreach ( $section_items->items as $item ) {
937 + $itemObj = LP_Course_Item::get_item( $item->id );
938 + if ( ! $itemObj instanceof LP_Course_Item ) {
939 + continue;
940 + }
941 +
942 + if ( $item->type == LP_QUIZ_CPT ) {
943 + $total_questions += count( $itemObj->get_question_ids() );
944 + $total_mark_question += $itemObj->get_mark();
945 + }
946 + }
947 + }
948 + // End get total questions, mark
949 +
950 + switch ( $evaluate_type ) {
951 + case 'evaluate_questions':
952 + $this->evaluate_course_by_questions( $evaluate, $lp_quizzes, $total_questions );
953 + break;
954 + case 'evaluate_mark':
955 + $this->evaluate_course_by_mark( $evaluate, $lp_quizzes, $total_mark_question );
956 + break;
957 + default:
958 + break;
959 + }
960 +
961 + // Get results of each quiz - has questions
962 + } catch ( Throwable $e ) {
963 + error_log( __FUNCTION__ . ': ' . $e->getMessage() );
964 + }
965 +
966 + return $evaluate;
967 + }
968 +
969 + protected function count_item_completed() {
970 + }
971 +
972 + /**
973 + * Check user can finish course or not.
974 + *
975 + * @return bool|WP_Error
976 + *
977 + * @since 4.2.7.5
978 + * @version 1.0.0
979 + */
980 + public function can_finish() {
981 + $can_finish = true;
982 +
983 + try {
984 + $courseModel = $this->get_course_model();
985 + if ( ! $courseModel ) {
986 + throw new Exception( __( 'Course not exists!', 'learnpress' ) );
987 + }
988 +
989 + $can_impact_item = $this->can_impact_item();
990 + if ( $can_impact_item instanceof WP_Error ) {
991 + throw new Exception( $can_impact_item->get_error_message() );
992 + }
993 +
994 + $course_results = $this->calculate_course_results();
995 + if ( empty( $course_results['pass'] ) ) {
996 + $allow_finish_when_all_item_completed = $courseModel->get_meta_value_by_key(
997 + CoursePostModel::META_KEY_HAS_FINISH,
998 + 'yes'
999 + );
1000 + if ( $allow_finish_when_all_item_completed ) {
1001 + $course_total_items_obj = $courseModel->get_total_items();
1002 + if ( $course_total_items_obj && $course_results['completed_items'] < $course_total_items_obj->count_items ) {
1003 + throw new Exception( __( 'You must complete all items in course', 'learnpress' ) );
1004 + }
1005 + } else {
1006 + throw new Exception( __( 'You must passed course', 'learnpress' ) );
1007 + }
1008 + }
1009 + } catch ( Throwable $e ) {
1010 + $can_finish = new WP_Error( 'lp_user_course_can_finish_err', $e->getMessage() );
1011 + }
1012 +
1013 + return apply_filters( 'learn-press/user-course/can-finish', $can_finish, $this );
1014 + }
1015 +
1016 + /**
1017 + * Handle finish course.
1018 + *
1019 + * @throws Exception
1020 + * @version 1.0.1
1021 + * @since 4.2.7.6
1022 + */
1023 + public function handle_finish() {
1024 + $can_finish = $this->can_finish();
1025 + if ( is_wp_error( $can_finish ) ) {
1026 + throw new Exception( $can_finish->get_error_message() );
1027 + }
1028 +
1029 + $course_results = $this->calculate_course_results();
1030 + $this->graduation = $course_results['pass'] ? LP_COURSE_GRADUATION_PASSED : LP_COURSE_GRADUATION_FAILED;
1031 + $this->status = LP_COURSE_FINISHED;
1032 + $this->end_time = gmdate( LP_Datetime::$format, time() );
1033 + $this->save();
1034 +
1035 + // Save result for course
1036 + LP_User_Items_Result_DB::instance()->update( $this->get_user_item_id(), wp_json_encode( $course_results ) );
1037 +
1038 + do_action( 'learn-press/user-course-finished', $this->item_id, $this->user_id, $this->get_user_item_id() );
1039 + do_action( 'learn-press/user-course/finished', $this );
1040 + }
1041 +
1042 + /**
1043 + * Handle retake course
1044 + *
1045 + * @throws Exception
1046 + * @since 4.2.7.6
1047 + * @version 1.0.0
1048 + */
1049 + public function handle_retake() {
1050 + $remaining_retake = $this->can_retake();
1051 + if ( $remaining_retake === 0 ) {
1052 + throw new Exception( __( 'You can not retake this course!', 'learnpress' ) );
1053 + }
1054 +
1055 + $this->status = self::STATUS_ENROLLED;
1056 + $this->graduation = self::GRADUATION_IN_PROGRESS;
1057 + $this->start_time = gmdate( LP_Datetime::$format, time() );
1058 + $this->end_time = null;
1059 + $this->set_meta_value_for_key( self::META_KEY_RETAKEN_COUNT, $this->get_retaken_count() + 1 );
1060 + $this->save();
1061 +
1062 + $courseModel = $this->get_course_model();
1063 +
1064 + if ( $courseModel->count_items() > 0 ) {
1065 + // Remove items' course user learned.
1066 + $filter_remove = new LP_User_Items_Filter();
1067 + $filter_remove->parent_id = $this->get_user_item_id();
1068 + $filter_remove->user_id = $this->user_id;
1069 + $filter_remove->limit = - 1;
1070 + LP_User_Items_DB::getInstance()->remove_items_of_user_course( $filter_remove );
1071 +
1072 + // Clean cache items.
1073 + foreach ( $courseModel->get_section_items() as $section_items ) {
1074 + foreach ( $section_items->items as $item ) {
1075 + $key_cache = "userItemModel/find/{$this->user_id}/{$item->id}/{$item->type}/{$this->item_id}/" . LP_COURSE_CPT;
1076 + $lpUserItemCache = new LP_User_Items_Cache();
1077 + $lpUserItemCache->clear( $key_cache );
1078 + }
1079 + }
1080 + }
1081 +
1082 + // Create new result in table learnpress_user_item_results.
1083 + LP_User_Items_Result_DB::instance()->insert( $this->get_user_item_id() );
1084 + }
1085 +
1086 + /**
1087 + * Reset course progress for the enrolled user.
1088 + *
1089 + * Sets status back to enrolled, graduation to in-progress, removes all child
1090 + * items (lessons, quizzes), clears cached item data and deletes the result.
1091 + *
1092 + * @return void
1093 + * @throws Exception
1094 + * @since 4.4.5
1095 + * @version 1.0.0
1096 + */
1097 + public function reset_progress() {
1098 + $this->status = self::STATUS_ENROLLED;
1099 + $this->graduation = self::GRADUATION_IN_PROGRESS;
1100 + $this->start_time = gmdate( LP_Datetime::$format, time() );
1101 + $this->end_time = null;
1102 + $this->set_meta_value_for_key( self::META_KEY_RETAKEN_COUNT, 0 );
1103 + $this->save();
1104 +
1105 + $courseModel = $this->get_course_model();
1106 +
1107 + if ( $courseModel && $courseModel->count_items() > 0 ) {
1108 + // Remove items' course user learned.
1109 + $filter_remove = new LP_User_Items_Filter();
1110 + $filter_remove->parent_id = $this->get_user_item_id();
1111 + $filter_remove->user_id = $this->user_id;
1112 + $filter_remove->limit = - 1;
1113 + LP_User_Items_DB::getInstance()->remove_items_of_user_course( $filter_remove );
1114 +
1115 + // Clean cache items.
1116 + foreach ( $courseModel->get_section_items() as $section_items ) {
1117 + foreach ( $section_items->items as $item ) {
1118 + $key_cache = "userItemModel/find/{$this->user_id}/{$item->id}/{$item->type}/{$this->item_id}/" . LP_COURSE_CPT;
1119 + $lpUserItemCache = new LP_User_Items_Cache();
1120 + $lpUserItemCache->clear( $key_cache );
1121 + }
1122 + }
1123 + }
1124 +
1125 + // Delete result in table learnpress_user_item_results.
1126 + LP_User_Items_Result_DB::instance()->delete( $this->get_user_item_id() );
1127 + }
1128 +
1129 + /**
1130 + * Check can impact item.
1131 + *
1132 + * @return bool|WP_Error
1133 + * @since 4.2.7.6
1134 + * @version 1.0.1
1135 + */
1136 + public function can_impact_item() {
1137 + $can_impact_item = true;
1138 +
1139 + // For case user Guest
1140 + $userModel = $this->get_user_model();
1141 + if ( ! $userModel ) {
1142 + $can_impact_item = new WP_Error( 'user_not_exists', __( 'User not exists!', 'learnpress' ) );
1143 + }
1144 +
1145 + $status = $this->get_status();
1146 + if ( $this->has_canceled() || ! $this->has_enrolled() ) {
1147 + $can_impact_item = new WP_Error( 'user_not_enroll_course', __( 'You have not enroll this course!', 'learnpress' ) );
1148 + }
1149 +
1150 + if ( $this->has_finished() ) {
1151 + $can_impact_item = new WP_Error( 'user_finished_course', __( 'You have finished this course!', 'learnpress' ) );
1152 + }
1153 +
1154 + if ( $this->get_time_remaining() === 0 ) {
1155 + $can_impact_item = new WP_Error( 'course_is_blocked', __( 'Course was blocked by expire!', 'learnpress' ) );
1156 + }
1157 +
1158 + return apply_filters( 'learn-press/user-course/can-impact-item', $can_impact_item, $this );
1159 + }
1160 +
1161 + /**
1162 + * Clean caches.
1163 + *
1164 + * @return void
1165 + *
1166 + * @since 4.2.5.4
1167 + * @version 1.0.1
1168 + */
1169 + public function clean_caches() {
1170 + $key_cache = "userCourseModel/find/{$this->user_id}/{$this->item_id}/{$this->item_type}";
1171 + $lpUserCourseCache = new LP_Cache();
1172 + $lpUserCourseCache->clear( $key_cache );
1173 +
1174 + parent::clean_caches();
1175 + // Clear cache total students enrolled of a course.
1176 + $lp_course_cache = new LP_Course_Cache( true );
1177 + $lp_course_cache->clean_total_students_enrolled( $this->item_id );
1178 + $lp_course_cache->clean_total_students_enrolled_or_purchased( $this->item_id );
1179 + // Clear cache count students of many course
1180 + $lp_courses_cache = new LP_Courses_Cache( true );
1181 + $lp_courses_cache->clear_cache_on_group( LP_Courses_Cache::KEYS_COUNT_STUDENT_COURSES );
1182 + }
1183 +}