PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.8
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.8
4.4.8 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 All 139 releases
← All changes | inc/user/class-lp-user.php +941 -941 4.3.84.4.8 View file →
@@ -1,941 +1,941 @@
1 -<?php
2 -
3 -use LearnPress\Filters\FilterBase;
4 -use LearnPress\Filters\UserItemsFilter;
5 -use LearnPress\Models\UserModel;
6 -
7 -/**
8 - * Class LP_User
9 - *
10 - * @author ThimPress
11 - * @package LearnPress/Classes
12 - * @version 1.0.1
13 - */
14 -class LP_User extends LP_Abstract_User {
15 - /**
16 - * Check the user can view content items' course
17 - *
18 - * @param int $course_id
19 - *
20 - * @return LP_Model_User_Can_View_Course_Item
21 - * @throws Exception
22 - */
23 - public function can_view_content_course( int $course_id = 0 ): LP_Model_User_Can_View_Course_Item {
24 - $view = new LP_Model_User_Can_View_Course_Item();
25 - $view->message = esc_html__(
26 - 'This content is protected. Please enroll in the course to view this content!',
27 - 'learnpress'
28 - );
29 -
30 - $course = learn_press_get_course( $course_id );
31 - if ( ! $course ) {
32 - return $view;
33 - }
34 -
35 - if ( $this->is_admin() || $this->is_author_of( $course_id ) ) {
36 - $view->flag = true;
37 - $view->message = 'User can view because is Admin or author of course';
38 -
39 - return $view;
40 - }
41 -
42 - // Set view->flag is true if course is no required enroll
43 - if ( $course->is_no_required_enroll() ) {
44 - $view->flag = true;
45 -
46 - return $view;
47 - } elseif ( ! is_user_logged_in() ) {
48 - $view->message = __(
49 - 'This content is protected. Please log in or enroll in the course to view this content!',
50 - 'learnpress'
51 - );
52 -
53 - return $view;
54 - }
55 -
56 - if ( $course->is_publish() ) {
57 - $is_enrolled_or_finished = $this->has_enrolled_or_finished( $course_id );
58 -
59 - if ( $is_enrolled_or_finished ) {
60 - $is_finished_course = $this->has_finished_course( $course_id );
61 - $enable_block_item_when_finish = $course->enable_block_item_when_finish();
62 -
63 - if ( $is_finished_course && $enable_block_item_when_finish ) {
64 - $view->key = LP_BLOCK_COURSE_FINISHED;
65 - $view->message = __(
66 - 'You finished this course. This content is protected. Please enroll in the course to view this content!',
67 - 'learnpress'
68 - );
69 - } elseif ( 0 === $course->timestamp_remaining_duration() ) {
70 - $view->key = LP_BLOCK_COURSE_DURATION_EXPIRE;
71 - $view->message = __(
72 - 'The content of this item has been blocked because the course has exceeded its duration.',
73 - 'learnpress'
74 - );
75 - } elseif ( $this->get_course_status( $course_id ) === LP_COURSE_PURCHASED ) {
76 - $view->key = LP_BLOCK_COURSE_PURCHASE;
77 - $view->message = __(
78 - 'This content is protected. Please enroll in the course to view this content!',
79 - 'learnpress'
80 - );
81 - } else {
82 - $view->key = 'can_view_course';
83 - $view->flag = true;
84 - $view->message = '';
85 - }
86 - }
87 - }
88 -
89 - // Todo: set cache - tungnx
90 -
91 - return apply_filters( 'learnpress/course/can-view-content', $view, $this->get_id(), $course );
92 - }
93 -
94 - /**
95 - * Check the user can access to an item inside course.
96 - *
97 - * @param int $item_id Course's item Id.
98 - * @param LP_Model_User_Can_View_Course_Item $view Course Id.
99 - *
100 - * @author tungnx
101 - * @return LP_Model_User_Can_View_Course_Item
102 - * @since 4.0.0
103 - */
104 - public function can_view_item( int $item_id = 0, $view = null ): LP_Model_User_Can_View_Course_Item {
105 - $view_new = null;
106 -
107 - if ( ! $view instanceof LP_Model_User_Can_View_Course_Item ) {
108 - return new LP_Model_User_Can_View_Course_Item();
109 - }
110 -
111 - $item = LP_Course_Item::get_item( $item_id );
112 -
113 - if ( ! $item ) {
114 - return $view;
115 - }
116 -
117 - if ( $item instanceof LP_Course_Item && $item->is_preview() ) {
118 - $view_new = clone $view; // or create new LP_Model_User_Can_View_Course_Item()
119 - $view_new->flag = true;
120 - $view_new->key = 'lesson_preview';
121 - $view_new->message = '';
122 - }
123 -
124 - if ( $view_new ) {
125 - $view = $view_new;
126 - }
127 -
128 - return apply_filters( 'learnpress/course/item/can-view', $view, $item, $this );
129 - }
130 -
131 - /**
132 - * Check if user can retry course.
133 - *
134 - * @param int $course_id .
135 - *
136 - * @return int
137 - * @since 4.0.0
138 - * @author tungnx
139 - */
140 - public function can_retry_course( int $course_id = 0 ): int {
141 - $flag = 0;
142 -
143 - try {
144 - $course = learn_press_get_course( $course_id );
145 -
146 - if ( ! $course ) {
147 - throw new Exception( 'Course is not available' );
148 - }
149 -
150 - $retake_option = (int) $course->get_data( 'retake_count' );
151 -
152 - if ( $retake_option > 0 ) {
153 - /**
154 - * Check course is finished
155 - * Check duration is blocked
156 - */
157 - if ( ! $this->has_finished_course( $course->get_id() ) ) {
158 - if ( 0 !== $course->timestamp_remaining_duration() ) {
159 - throw new Exception();
160 - }
161 - }
162 -
163 - $user_course_data = $this->get_course_data( $course_id );
164 - if ( $user_course_data instanceof LP_User_Item_Course ) {
165 - $retaken = $user_course_data->get_retaken_count();
166 - $can_retake_times = $retake_option - $retaken;
167 -
168 - if ( $can_retake_times > 0 ) {
169 - $flag = $can_retake_times;
170 - }
171 - }
172 - }
173 - } catch ( Exception $e ) {
174 -
175 - }
176 -
177 - return apply_filters( 'learn-press/user/course/can-retry', $flag, $this->get_id(), $course_id );
178 - }
179 -
180 - /**
181 - * Return true if user has already purchased course
182 - *
183 - * @param int $course_id
184 - *
185 - * @return bool
186 - * @editor tungnx
187 - * @modify 4.1.3
188 - * @throws Exception
189 - */
190 - public function has_purchased_course( int $course_id ): bool {
191 - $user_course = $this->get_course_data( $course_id );
192 -
193 - return apply_filters( 'learn-press/user-purchased-course', $user_course && $user_course->is_purchased(), $course_id, $this->get_id() );
194 - }
195 -
196 - /**
197 - * Check course is enrolled
198 - *
199 - * @param integer $course_id Course ID
200 - * @param boolean $return_bool
201 - *
202 - * @return bool|object
203 - * @editor tungnx
204 - * @since 4.1.2
205 - * @version 1.0.2
206 - *
207 - * @author Nhamdv
208 - */
209 - public function has_enrolled_course( int $course_id, bool $return_bool = true ) {
210 - $result_check = new stdClass();
211 - $result_check->check = true;
212 - $result_check->message = '';
213 -
214 - try {
215 - /*$order = $this->get_course_order( $course_id );
216 -
217 - if ( ! $order || ! $order->is_completed() ) {
218 - throw new Exception( esc_html__( 'Order is not completed', 'learnpress' ) );
219 - }*/
220 -
221 - $user_course = $this->get_course_data( $course_id );
222 - if ( ! $user_course || ! $user_course->is_enrolled() ) {
223 - throw new Exception( esc_html__( 'The course is not enrolled.', 'learnpress' ) );
224 - }
225 - } catch ( Throwable $th ) {
226 - $result_check->check = false;
227 - $result_check->message = $th->getMessage();
228 - }
229 -
230 - return apply_filters( 'learn-press/user/is-course-enrolled', $return_bool ? $result_check->check : $result_check, $course_id, $return_bool );
231 - }
232 -
233 - /**
234 - * Return true if user has finished a course
235 - *
236 - * @param int $course_id .
237 - *
238 - * @return bool
239 - * @throws Exception
240 - */
241 - public function has_finished_course( int $course_id ): bool {
242 - $user_course = $this->get_course_data( $course_id );
243 -
244 - return apply_filters( 'learn-press/user-has-finished-course', $user_course && $user_course->is_finished(), $this->get_id(), $course_id );
245 - }
246 -
247 - /**
248 - * Check course of user is enrolled or finished
249 - *
250 - * @param int $course_id
251 - *
252 - * @return bool
253 - */
254 - public function has_enrolled_or_finished( int $course_id ): bool {
255 - $status = true;
256 -
257 - try {
258 - $user_course = $this->get_course_data( $course_id );
259 -
260 - if ( ! $user_course ) {
261 - $status = false;
262 - } elseif ( ! $user_course->is_enrolled() && ! $user_course->is_finished() ) {
263 - $status = false;
264 - }
265 - } catch ( Throwable $e ) {
266 - $status = false;
267 - }
268 -
269 - return apply_filters( 'learn-press/user-has-finished-course', $status, $this->get_id(), $course_id );
270 - }
271 -
272 - /**
273 - * Check user can enroll course
274 - *
275 - * @param int $course_id
276 - * @param bool $return_bool
277 - *
278 - * @return mixed|object|bool
279 - */
280 - public function can_enroll_course( int $course_id, bool $return_bool = true ) {
281 - $course = learn_press_get_course( $course_id );
282 - $output = new stdClass();
283 - $output->check = true;
284 - $output->message = '';
285 -
286 - try {
287 - $is_no_required_enroll = $course->get_data( 'no_required_enroll', 'no' ) === 'yes';
288 - if ( ! $course ) {
289 - $output->code = 'course_unavailable';
290 - throw new Exception( esc_html__( 'No Course or User available', 'learnpress' ) );
291 - }
292 -
293 - if ( ! $course->is_publish() ) {
294 - $output->code = 'course_not_publish';
295 - throw new Exception( esc_html__( 'The course is not public', 'learnpress' ) );
296 - }
297 -
298 - if ( $this->has_enrolled_course( $course_id ) ) {
299 - $output->code = 'course_is_enrolled';
300 - throw new Exception( esc_html__( 'This course is already enrolled.', 'learnpress' ) );
301 - }
302 -
303 - if ( ! $course->is_in_stock_enroll() && ! $this->has_purchased_course( $course_id )
304 - && ! $this->has_enrolled_or_finished( $course_id ) && ! $is_no_required_enroll ) {
305 - $output->code = 'course_out_of_stock';
306 - throw new Exception( esc_html__( 'The course is full of students.', 'learnpress' ) );
307 - }
308 -
309 - if ( $course->get_external_link()
310 - && ! $this->has_purchased_course( $course_id )
311 - && ! $course->is_offline() ) {
312 - $output->code = 'course_is_external';
313 - throw new Exception( esc_html__( 'The course is external', 'learnpress' ) );
314 - }
315 -
316 - if ( $this->can_retry_course( $course_id ) ) {
317 - $output->code = 'course_can_retry';
318 - throw new Exception( esc_html__( 'Course can retake.', 'learnpress' ) );
319 - }
320 -
321 - if ( $is_no_required_enroll && ! is_user_logged_in() ) {
322 - $output->code = 'course_is_no_required_enroll_not_login';
323 - throw new Exception(
324 - esc_html__( 'Enrollment in the course is not mandatory. You can access course for learning now.', 'learnpress' )
325 - );
326 - }
327 -
328 - if ( ! $course->is_free() && ! $is_no_required_enroll && ! $this->has_purchased_course( $course_id ) ) {
329 - $output->code = 'course_is_not_purchased';
330 - throw new Exception( esc_html__( 'The course is not purchased.', 'learnpress' ) );
331 - }
332 - } catch ( \Throwable $th ) {
333 - $output->check = false;
334 - $output->message = $th->getMessage();
335 - }
336 -
337 - if ( $return_bool ) {
338 - $output = $output->check;
339 - }
340 -
341 - return apply_filters( 'learn-press/user/can-enroll-course', $output, $course, $return_bool, $this );
342 - }
343 -
344 - /**
345 - * Check can show purchase course button
346 - *
347 - * @param int $course_id
348 - *
349 - * @return bool|WP_Error
350 - * @author nhamdv
351 - * @editor tungnx
352 - * @since 4.0.8
353 - * @version 1.0.6
354 - */
355 - public function can_purchase_course( int $course_id = 0 ) {
356 - $can_purchase = true;
357 - $course = learn_press_get_course( $course_id );
358 - $code_err = '';
359 -
360 - try {
361 - $can_enroll_course = $this->can_enroll_course( $course_id, false );
362 - if ( ! $can_enroll_course->check &&
363 - ! in_array( $can_enroll_course->code, [ 'course_is_not_purchased', 'course_is_enrolled' ] ) ) {
364 - $code_err = $can_enroll_course->code;
365 - throw new Exception( $can_enroll_course->message );
366 - }
367 -
368 - if ( $course->is_free() ) {
369 - $code_err = 'course_is_free';
370 - throw new Exception( __( 'Course is free, so you can not purchase', 'learnpress' ) );
371 - }
372 -
373 - // Course is not require enrolling.
374 - // Not use $course->is_no_required_enroll() because it is not correct, it check with user logged.
375 - if ( $course->get_data( 'no_required_enroll', 'no' ) === 'yes' ) {
376 - $code_err = 'no_required_enroll';
377 - throw new Exception(
378 - __(
379 - 'Enrollment in the course is not mandatory. You can access materials for learning or to take quizzes now.',
380 - 'learnpress'
381 - )
382 - );
383 - }
384 -
385 - // If the order contains course is processing
386 - $order = $this->get_course_order( $course_id );
387 - if ( $order && $order->get_status() === LP_ORDER_PROCESSING ) {
388 - $code_err = 'order_processing';
389 - throw new Exception( __( 'Your order is waiting for processing', 'learnpress' ) );
390 - }
391 -
392 - if ( $this->has_purchased_course( $course_id ) ) {
393 - $code_err = 'course_purchased';
394 - throw new Exception( __( 'Course is purchased', 'learnpress' ) );
395 - }
396 -
397 - $is_blocked_course = 0 === $course->timestamp_remaining_duration();
398 - $is_enrolled_course = $this->has_enrolled_course( $course_id );
399 - if ( $course->allow_repurchase() ) {
400 - if ( $is_enrolled_course && ! $is_blocked_course ) {
401 - $code_err = 'course_is_enrolled';
402 - throw new Exception( 'Course is enrolled' );
403 - }
404 - } else {
405 - if ( $this->has_enrolled_or_finished( $course_id ) ) {
406 - $code_err = 'course_is_enrolled_or_finished';
407 - throw new Exception( __( 'Course is enrolled or finished', 'learnpress' ) );
408 - }
409 - }
410 - } catch ( Throwable $e ) {
411 - $can_purchase = new WP_Error( $code_err, $e->getMessage() );
412 - }
413 -
414 - if ( $can_purchase instanceof WP_Error ) {
415 - $can_purchase = false;
416 - }
417 -
418 - return apply_filters( 'learn-press/user/can-purchase-course', $can_purchase, $this->get_id(), $course_id );
419 - }
420 -
421 - /**
422 - * Check condition show finish course button
423 - *
424 - * @param $course
425 - *
426 - * @return array
427 - * @author nhamdv
428 - * @editor tungnx
429 - * @version 1.0.2
430 - */
431 - public function can_show_finish_course_btn( $course ): array {
432 - $return = array(
433 - 'status' => 'success',
434 - 'message' => '',
435 - );
436 -
437 - try {
438 - if ( ! $course ) {
439 - throw new Exception( esc_html__( 'Error: No Course or User available.', 'learnpress' ) );
440 - }
441 -
442 - $course_id = $course->get_id();
443 -
444 - if ( $this->has_finished_course( $course_id ) ) {
445 - throw new Exception( esc_html__( 'The course has finished.', 'learnpress' ) );
446 - }
447 -
448 - if ( ! $this->has_enrolled_course( $course_id ) ) {
449 - throw new Exception( esc_html__( 'The course is not enrolled.', 'learnpress' ) );
450 - }
451 -
452 - if ( ! $this->is_course_in_progress( $course_id ) ) {
453 - throw new Exception( esc_html__( 'Error: The course is not in progress.', 'learnpress' ) );
454 - }
455 -
456 - $course_data = $this->get_course_data( $course_id );
457 -
458 - if ( $course_data ) {
459 - $course_result = $course_data->get_result();
460 -
461 - if ( ! $course_result['pass'] ) {
462 - // Get option Can finish course if completed all item without pass
463 - $can_finish = get_post_meta( $course_id, '_lp_has_finish', true ) ?? 'yes';
464 -
465 - if ( $can_finish == 'yes' ) {
466 - // Check completed all items
467 - $is_all_completed = $this->is_completed_all_items( $course_id );
468 - if ( ! $is_all_completed ) {
469 - throw new Exception( 'All items not completed and Course not pass' );
470 - }
471 - } else {
472 - throw new Exception( 'Course not pass' );
473 - }
474 - }
475 - }
476 -
477 - if ( ! apply_filters( 'lp_can_finish_course', true ) ) {
478 - throw new Exception( esc_html__( 'Error: Filter the disabled finished courses.', 'learnpress' ) );
479 - }
480 - } catch ( Exception $e ) {
481 - $return['status'] = 'false';
482 - $return['message'] = $e->getMessage();
483 - }
484 -
485 - return $return;
486 - }
487 -
488 - /**
489 - * Start quiz for the user.
490 - *
491 - * @param int $quiz_id
492 - * @param int $course_id
493 - * @param bool $wp_error Optional. Whether to return a WP_Error on failure. Default false.
494 - *
495 - * @return LP_User_Item_Quiz|bool|WP_Error
496 - * @throws Exception
497 - * @deprecated 4.2.9.1
498 - */
499 - public function start_quiz( int $quiz_id, int $course_id = 0, bool $wp_error = false ) {
500 - _deprecated_function( __METHOD__, '4.2.9.1' );
501 - return false;
502 -
503 - try {
504 - $item_is_preview = learn_press_get_request( 'lp-preview' );
505 - if ( $item_is_preview ) {
506 - throw new Exception( __( 'You cannot start a quiz in preview mode.', 'learnpress' ) );
507 - }
508 -
509 - // Validate course and quiz
510 - $course_id = $this->_verify_course_item( $quiz_id, $course_id );
511 - if ( ! $course_id ) {
512 - throw new Exception(
513 - __( 'The course does not exist or does not contain a quiz.', 'learnpress' ),
514 - LP_INVALID_QUIZ_OR_COURSE
515 - );
516 - }
517 -
518 - $course = learn_press_get_course( $course_id );
519 - // If user has already finished the course
520 - if ( $this->has_finished_course( $course_id ) ) {
521 - throw new Exception(
522 - __( 'You have already finished the course of this quiz', 'learnpress' ),
523 - LP_COURSE_IS_FINISHED
524 - );
525 - }
526 -
527 - if ( ! $this->has_enrolled_course( $course_id ) || ! $this->is_course_in_progress( $course_id ) ) {
528 - if ( ! $course->is_no_required_enroll() ) {
529 - throw new Exception(
530 - __( 'Please enroll in the course before starting the quiz.', 'learnpress' ),
531 - LP_COURSE_IS_FINISHED
532 - );
533 - }
534 - }
535 -
536 - // Check if user has already started or completed quiz
537 - if ( $this->has_item_status( array( 'started', 'completed' ), $quiz_id, $course_id ) ) {
538 - throw new Exception(
539 - __( 'The user has started or completed the quiz.', 'learnpress' ),
540 - LP_QUIZ_HAS_STARTED_OR_COMPLETED
541 - );
542 - }
543 -
544 - $user_current = learn_press_get_current_user();
545 - if ( $user_current->is_guest() ) {
546 - // if course required enroll => print message "You have to login for starting quiz"
547 - if ( ! $course->is_no_required_enroll() ) {
548 - throw new Exception( __( 'You have to log in to start the quiz.', 'learnpress' ), LP_REQUIRE_LOGIN );
549 - }
550 - }
551 -
552 - /**
553 - * Hook can start quiz
554 - *
555 - * @see learn_press_hk_before_start_quiz
556 - */
557 - $can_start_quiz = apply_filters(
558 - 'learn-press/can-start-quiz',
559 - true,
560 - $quiz_id,
561 - $course_id,
562 - $this->get_id()
563 - );
564 -
565 - if ( ! $can_start_quiz ) {
566 - return false;
567 - }
568 -
569 - $user_quiz = false;
570 -
571 - /**
572 - * Hook quiz started
573 - *
574 - * @since 3.0.0
575 - */
576 - do_action( 'learn-press/user/quiz-started', $quiz_id, $course_id, $this->get_id() );
577 -
578 - // $return = $user_quiz->get_mysql_data();
579 - $return = $user_quiz;
580 - } catch ( Throwable $ex ) {
581 - $return = $wp_error ? new WP_Error( $ex->getCode(), $ex->getMessage() ) : false;
582 - }
583 -
584 - return $return;
585 - }
586 -
587 - /**
588 - * Finish a quiz for the user and save all data needed
589 - *
590 - * @param int $quiz_id
591 - * @param int $course_id
592 - * @param bool $wp_error
593 - *
594 - * @return LP_User_Item_Quiz|bool|WP_Error
595 - */
596 - public function finish_quiz( int $quiz_id, int $course_id, bool $wp_error = false ) {
597 - $return = false;
598 -
599 - try {
600 - // If user has already finished the course
601 - if ( $this->has_finished_course( $course_id ) ) {
602 - throw new Exception(
603 - __( 'The user has already finished the course of this quiz.', 'learnpress' ),
604 - LP_COURSE_IS_FINISHED
605 - );
606 -
607 - }
608 -
609 - // Check if user has already started or completed quiz
610 - if ( $this->has_item_status( array( 'completed' ), $quiz_id, $course_id ) ) {
611 - throw new Exception(
612 - __( 'The user has completed the quiz', 'learnpress' ),
613 - LP_QUIZ_HAS_STARTED_OR_COMPLETED
614 - );
615 - }
616 -
617 - /**
618 - * @var LP_User_Item_Quiz $user_quiz
619 - */
620 - $user_quiz = $this->get_item_data( $quiz_id, $course_id );
621 - $user_quiz->complete();
622 -
623 - do_action( 'learn-press/user/quiz-finished', $quiz_id, $course_id, $this->get_id() );
624 - } catch ( Exception $ex ) {
625 - $return = $wp_error ? new WP_Error( $ex->getCode(), $ex->getMessage() ) : true;
626 - }
627 -
628 - return $return;
629 - }
630 -
631 - /**
632 - * Retake a quiz for the user
633 - *
634 - * @param int $quiz_id
635 - * @param int $course_id
636 - * @param bool $wp_error
637 - *
638 - * @return bool|WP_Error|LP_User_Item_Quiz
639 - *
640 - * @throws Exception
641 - * @deprecated 4.2.7.2
642 - */
643 - /*public function retake_quiz( int $quiz_id, int $course_id, bool $wp_error = false ) {
644 - $return = false;
645 -
646 - try {
647 - $course_id = $this->_verify_course_item( $quiz_id, $course_id );
648 -
649 - if ( false === $course_id ) {
650 - throw new Exception(
651 - sprintf(
652 - __(
653 - 'The course does not exist or does not contain a quiz.',
654 - 'learnpress'
655 - ),
656 - __CLASS__,
657 - __FUNCTION__
658 - ),
659 - LP_INVALID_QUIZ_OR_COURSE
660 - );
661 - }
662 -
663 - // If user has already finished the course.
664 - if ( $this->has_finished_course( $course_id ) ) {
665 - throw new Exception(
666 - sprintf(
667 - __( 'You can not redo a quiz in a finished course.', 'learnpress' ),
668 - __CLASS__,
669 - __FUNCTION__
670 - ),
671 - LP_COURSE_IS_FINISHED
672 - );
673 -
674 - }
675 -
676 - // Check if user has already started or completed quiz
677 - if ( ! $this->has_item_status( array( 'completed' ), $quiz_id, $course_id ) ) {
678 - throw new Exception(
679 - sprintf(
680 - __( '%1$s::%2$s - The user has not completed the quiz.', 'learnpress' ),
681 - __CLASS__,
682 - __FUNCTION__
683 - ),
684 - LP_QUIZ_HAS_STARTED_OR_COMPLETED
685 - );
686 - }
687 -
688 - $allow_attempts = learn_press_get_quiz_max_retrying( $quiz_id, $course_id );
689 -
690 - if ( ! $this->has_retake_quiz( $quiz_id, $course_id ) ) {
691 - throw new Exception(
692 - sprintf(
693 - __( '%1$s::%2$s - Your quiz can\'t be retaken.', 'learnpress' ),
694 - __CLASS__,
695 - __FUNCTION__
696 - ),
697 - LP_QUIZ_HAS_STARTED_OR_COMPLETED
698 - );
699 - }
700 -
701 - $return = learn_press_user_retake_quiz( $quiz_id, false, $course_id, $wp_error );
702 -
703 - do_action( 'learn-press/user/quiz-retried', $quiz_id, $course_id, $this->get_id() );
704 - } catch ( Exception $ex ) {
705 - $return = $wp_error ? new WP_Error( $ex->getCode(), $ex->getMessage() ) : false;
706 - do_action( 'learn-press/user/retake-quiz-failure', $quiz_id, $course_id, $this->get_id() );
707 - }
708 -
709 - return $return;
710 - }*/
711 -
712 - /**
713 - * Get quiz's user learning or completed
714 - *
715 - * @param LP_User_Items_Filter|UserItemsFilter $filter
716 - *
717 - * @return LP_Query_List_Table
718 - */
719 - public function get_user_quizzes( $filter ): LP_Query_List_Table {
720 - $quizzes = [
721 - 'total' => 0,
722 - 'items' => [],
723 - 'pages' => 0,
724 - ];
725 -
726 - try {
727 - $user_quizzes = LP_User_Items_DB::getInstance()->get_user_quizzes( $filter );
728 -
729 - if ( $user_quizzes ) {
730 - $count = LP_User_Items_DB::getInstance()->wpdb->get_var( 'SELECT FOUND_ROWS()' );
731 -
732 - $quizzes['total'] = $count;
733 - $quizzes['pages'] = ceil( $count / $filter->limit );
734 -
735 - foreach ( $user_quizzes as $item ) {
736 - $quizzes['items'][] = new LP_User_Item_Quiz( $item );
737 - }
738 - }
739 -
740 - $quizzes['single'] = __( 'quiz', 'learnpress' );
741 - $quizzes['plural'] = __( 'quizzes', 'learnpress' );
742 - } catch ( Throwable $e ) {
743 - error_log( __FUNCTION__ . ': ' . $e->getMessage() );
744 - }
745 -
746 - return new LP_Query_List_Table( $quizzes );
747 - }
748 -
749 - /**
750 - * Set item is viewing in single course.
751 - *
752 - * @param LP_Course_Item $item
753 - *
754 - * @return int|LP_Course_Item
755 - * @since 4.1.6.9 - move from LP_Course
756 - * @editor tungnx
757 - * @version 1.0.0
758 - */
759 - public function set_viewing_item( LP_Course_Item $item ) {
760 - $flag = false;
761 -
762 - try {
763 - $user = learn_press_get_current_user();
764 - if ( $user instanceof LP_User_Guest ) {
765 - return $flag;
766 - }
767 -
768 - $course_id = $item->get_course_id();
769 - $item_id = $item->get_id();
770 - $course_data = $this->get_course_data( $course_id );
771 -
772 - if ( $course_data && $course_data->is_enrolled() ) {
773 - $item = $course_data->get_item( $item_id );
774 -
775 - if ( ! $item ) {
776 - $item = LP_User_Item::get_item_object( $item_id );
777 -
778 - if ( ! $item ) {
779 - return $flag;
780 - }
781 -
782 - if ( $item instanceof LP_User_Item_Quiz ) {
783 - return $flag;
784 - }
785 -
786 - $item->set_ref_id( $course_id );
787 - $item->set_parent_id( $course_data->get_user_item_id() );
788 -
789 - $flag = $item->update();
790 - }
791 - }
792 - } catch ( Throwable $e ) {
793 - error_log( $e->getMessage() );
794 - }
795 -
796 - return $flag;
797 - }
798 -
799 - /**
800 - * Get statistic info of student user
801 - *
802 - * @return array
803 - * @since 4.1.6
804 - * @version 1.0.0
805 - */
806 - public function get_student_statistic(): array {
807 - $user = $this;
808 - $statistic = array(
809 - 'enrolled_courses' => 0,
810 - 'in_progress_course' => 0,
811 - 'finished_courses' => 0,
812 - 'passed_courses' => 0,
813 - 'failed_courses' => 0,
814 - );
815 -
816 - try {
817 - if ( ! $user ) {
818 - throw new Exception( 'The user is invalid' );
819 - }
820 -
821 - $user_id = $user->get_id();
822 - $lp_user_items_db = LP_User_Items_DB::getInstance();
823 -
824 - // Count status
825 - $filter = new LP_User_Items_Filter();
826 - $filter->user_id = $user_id;
827 - $count_status = $lp_user_items_db->count_status_by_items( $filter );
828 - $total_courses_enrolled = intval( $count_status->{LP_COURSE_PURCHASED} ?? 0 )
829 - + intval( $count_status->{LP_COURSE_ENROLLED} ?? 0 )
830 - + intval( $count_status->{LP_COURSE_FINISHED} ?? 0 );
831 -
832 - $statistic['enrolled_courses'] = $total_courses_enrolled;
833 - $statistic['in_progress_course'] = $count_status->{LP_COURSE_GRADUATION_IN_PROGRESS} ?? 0;
834 - $statistic['finished_courses'] = $count_status->{LP_COURSE_FINISHED} ?? 0;
835 - $statistic['passed_courses'] = $count_status->{LP_COURSE_GRADUATION_PASSED} ?? 0;
836 - $statistic['failed_courses'] = $count_status->{LP_COURSE_GRADUATION_FAILED} ?? 0;
837 - } catch ( Throwable $e ) {
838 - error_log( __FUNCTION__ . ': ' . $e->getMessage() );
839 - }
840 -
841 - return apply_filters( 'lp/profile/student/statistic', $statistic, $this );
842 - }
843 -
844 - /**
845 - * Get statistic info of instructor user
846 - *
847 - * @param array $params
848 - *
849 - * @return array
850 - * @since 4.1.6
851 - * @version 1.0.1
852 - */
853 - public function get_instructor_statistic( array $params = [] ): array {
854 - $statistic = array(
855 - 'total_course' => 0,
856 - 'published_course' => 0,
857 - 'pending_course' => 0,
858 - 'total_student' => 0,
859 - 'student_completed' => 0,
860 - 'student_in_progress' => 0,
861 - );
862 -
863 - try {
864 - $user_id = $this->get_id();
865 - $lp_user_items_db = LP_User_Items_DB::getInstance();
866 - $lp_course_db = LP_Course_DB::getInstance();
867 -
868 - if ( ! $this->can_create_course() ) {
869 - throw new Exception( 'The user is not Instructor' );
870 - }
871 -
872 - // Count total user completed course of author
873 - $filter_course = new LP_Course_Filter();
874 - $filter_course->only_fields = array( 'ID' );
875 - $filter_course->post_author = $user_id;
876 - $filter_course->post_status = 'publish';
877 - $filter_course->return_string_query = true;
878 - $query_courses_str = LP_Course_DB::getInstance()->get_courses( $filter_course );
879 -
880 - $filter_count_users = new LP_User_Items_Filter();
881 - $filter_count_users->item_type = LP_COURSE_CPT;
882 - $filter_count_users->where[] = "AND item_id IN ({$query_courses_str})";
883 - $count_student_has_status = $lp_user_items_db->count_status_by_items( $filter_count_users );
884 - // Count total user in progress course of author
885 -
886 - // Get total users attend course of author
887 - $filter_count_users = $lp_user_items_db->count_user_attend_courses_of_author( $user_id );
888 - $count_users_attend_courses_of_author = $lp_user_items_db->get_user_courses( $filter_count_users );
889 -
890 - // Get total courses publish of author
891 - $filter_count_courses = $lp_course_db->count_courses_of_author( $user_id, [ 'publish' ] );
892 - $total_courses_publish_of_author = $lp_course_db->get_courses( $filter_count_courses );
893 -
894 - // Get total courses of author
895 - $filter_count_courses = $lp_course_db->count_courses_of_author( $user_id );
896 - $total_courses_of_author = $lp_course_db->get_courses( $filter_count_courses );
897 -
898 - // Get total courses pending of author
899 - $filter_count_courses = $lp_course_db->count_courses_of_author( $user_id, [ 'pending' ] );
900 - $total_courses_pending_of_author = $lp_course_db->get_courses( $filter_count_courses );
901 -
902 - $statistic['total_course'] = $total_courses_of_author;
903 - $statistic['published_course'] = $total_courses_publish_of_author;
904 - $statistic['pending_course'] = $total_courses_pending_of_author;
905 - $statistic['total_student'] = $count_users_attend_courses_of_author;
906 - $statistic['student_completed'] = $count_student_has_status->{LP_COURSE_FINISHED} ?? 0;
907 - $statistic['student_in_progress'] = $count_student_has_status->{LP_COURSE_GRADUATION_IN_PROGRESS} ?? 0;
908 - } catch ( Throwable $e ) {
909 - error_log( __FUNCTION__ . ': ' . $e->getMessage() );
910 - }
911 -
912 - return apply_filters( 'lp/profile/instructor/statistic', $statistic, $this );
913 - }
914 -
915 - /**
916 - * Get url instructor.
917 - *
918 - * @return string
919 - * @version 1.0.1
920 - * @since 4.2.3
921 - */
922 - public function get_url_instructor(): string {
923 - $single_instructor_link = '';
924 -
925 - try {
926 - $author_id = $this->get_id();
927 - $userModel = UserModel::find( $author_id, true );
928 - if ( ! $userModel ) {
929 - throw new Exception( 'User not found' );
930 - }
931 -
932 - $single_instructor_page_id = learn_press_get_page_id( 'single_instructor' );
933 - $user_slug = $userModel->get_slug_link();
934 - $single_instructor_link = trailingslashit( trailingslashit( get_page_link( $single_instructor_page_id ) ) . $user_slug );
935 - } catch ( Throwable $e ) {
936 - LP_Debug::error_log( $e );
937 - }
938 -
939 - return $single_instructor_link;
940 - }
941 -}
1 +<?php
2 +
3 +use LearnPress\Filters\FilterBase;
4 +use LearnPress\Filters\UserItemsFilter;
5 +use LearnPress\Models\UserModel;
6 +
7 +/**
8 + * Class LP_User
9 + *
10 + * @author ThimPress
11 + * @package LearnPress/Classes
12 + * @version 1.0.1
13 + */
14 +class LP_User extends LP_Abstract_User {
15 + /**
16 + * Check the user can view content items' course
17 + *
18 + * @param int $course_id
19 + *
20 + * @return LP_Model_User_Can_View_Course_Item
21 + * @throws Exception
22 + */
23 + public function can_view_content_course( int $course_id = 0 ): LP_Model_User_Can_View_Course_Item {
24 + $view = new LP_Model_User_Can_View_Course_Item();
25 + $view->message = esc_html__(
26 + 'This content is protected. Please enroll in the course to view this content!',
27 + 'learnpress'
28 + );
29 +
30 + $course = learn_press_get_course( $course_id );
31 + if ( ! $course ) {
32 + return $view;
33 + }
34 +
35 + if ( $this->is_admin() || $this->is_author_of( $course_id ) ) {
36 + $view->flag = true;
37 + $view->message = 'User can view because is Admin or author of course';
38 +
39 + return $view;
40 + }
41 +
42 + // Set view->flag is true if course is no required enroll
43 + if ( $course->is_no_required_enroll() ) {
44 + $view->flag = true;
45 +
46 + return $view;
47 + } elseif ( ! is_user_logged_in() ) {
48 + $view->message = __(
49 + 'This content is protected. Please log in or enroll in the course to view this content!',
50 + 'learnpress'
51 + );
52 +
53 + return $view;
54 + }
55 +
56 + if ( $course->is_publish() ) {
57 + $is_enrolled_or_finished = $this->has_enrolled_or_finished( $course_id );
58 +
59 + if ( $is_enrolled_or_finished ) {
60 + $is_finished_course = $this->has_finished_course( $course_id );
61 + $enable_block_item_when_finish = $course->enable_block_item_when_finish();
62 +
63 + if ( $is_finished_course && $enable_block_item_when_finish ) {
64 + $view->key = LP_BLOCK_COURSE_FINISHED;
65 + $view->message = __(
66 + 'You finished this course. This content is protected. Please enroll in the course to view this content!',
67 + 'learnpress'
68 + );
69 + } elseif ( 0 === $course->timestamp_remaining_duration() ) {
70 + $view->key = LP_BLOCK_COURSE_DURATION_EXPIRE;
71 + $view->message = __(
72 + 'The content of this item has been blocked because the course has exceeded its duration.',
73 + 'learnpress'
74 + );
75 + } elseif ( $this->get_course_status( $course_id ) === LP_COURSE_PURCHASED ) {
76 + $view->key = LP_BLOCK_COURSE_PURCHASE;
77 + $view->message = __(
78 + 'This content is protected. Please enroll in the course to view this content!',
79 + 'learnpress'
80 + );
81 + } else {
82 + $view->key = 'can_view_course';
83 + $view->flag = true;
84 + $view->message = '';
85 + }
86 + }
87 + }
88 +
89 + // Todo: set cache - tungnx
90 +
91 + return apply_filters( 'learnpress/course/can-view-content', $view, $this->get_id(), $course );
92 + }
93 +
94 + /**
95 + * Check the user can access to an item inside course.
96 + *
97 + * @param int $item_id Course's item Id.
98 + * @param LP_Model_User_Can_View_Course_Item $view Course Id.
99 + *
100 + * @author tungnx
101 + * @return LP_Model_User_Can_View_Course_Item
102 + * @since 4.0.0
103 + */
104 + public function can_view_item( int $item_id = 0, $view = null ): LP_Model_User_Can_View_Course_Item {
105 + $view_new = null;
106 +
107 + if ( ! $view instanceof LP_Model_User_Can_View_Course_Item ) {
108 + return new LP_Model_User_Can_View_Course_Item();
109 + }
110 +
111 + $item = LP_Course_Item::get_item( $item_id );
112 +
113 + if ( ! $item ) {
114 + return $view;
115 + }
116 +
117 + if ( $item instanceof LP_Course_Item && $item->is_preview() ) {
118 + $view_new = clone $view; // or create new LP_Model_User_Can_View_Course_Item()
119 + $view_new->flag = true;
120 + $view_new->key = 'lesson_preview';
121 + $view_new->message = '';
122 + }
123 +
124 + if ( $view_new ) {
125 + $view = $view_new;
126 + }
127 +
128 + return apply_filters( 'learnpress/course/item/can-view', $view, $item, $this );
129 + }
130 +
131 + /**
132 + * Check if user can retry course.
133 + *
134 + * @param int $course_id .
135 + *
136 + * @return int
137 + * @since 4.0.0
138 + * @author tungnx
139 + */
140 + public function can_retry_course( int $course_id = 0 ): int {
141 + $flag = 0;
142 +
143 + try {
144 + $course = learn_press_get_course( $course_id );
145 +
146 + if ( ! $course ) {
147 + throw new Exception( 'Course is not available' );
148 + }
149 +
150 + $retake_option = (int) $course->get_data( 'retake_count' );
151 +
152 + if ( $retake_option > 0 ) {
153 + /**
154 + * Check course is finished
155 + * Check duration is blocked
156 + */
157 + if ( ! $this->has_finished_course( $course->get_id() ) ) {
158 + if ( 0 !== $course->timestamp_remaining_duration() ) {
159 + throw new Exception();
160 + }
161 + }
162 +
163 + $user_course_data = $this->get_course_data( $course_id );
164 + if ( $user_course_data instanceof LP_User_Item_Course ) {
165 + $retaken = $user_course_data->get_retaken_count();
166 + $can_retake_times = $retake_option - $retaken;
167 +
168 + if ( $can_retake_times > 0 ) {
169 + $flag = $can_retake_times;
170 + }
171 + }
172 + }
173 + } catch ( Exception $e ) {
174 +
175 + }
176 +
177 + return apply_filters( 'learn-press/user/course/can-retry', $flag, $this->get_id(), $course_id );
178 + }
179 +
180 + /**
181 + * Return true if user has already purchased course
182 + *
183 + * @param int $course_id
184 + *
185 + * @return bool
186 + * @editor tungnx
187 + * @modify 4.1.3
188 + * @throws Exception
189 + */
190 + public function has_purchased_course( int $course_id ): bool {
191 + $user_course = $this->get_course_data( $course_id );
192 +
193 + return apply_filters( 'learn-press/user-purchased-course', $user_course && $user_course->is_purchased(), $course_id, $this->get_id() );
194 + }
195 +
196 + /**
197 + * Check course is enrolled
198 + *
199 + * @param integer $course_id Course ID
200 + * @param boolean $return_bool
201 + *
202 + * @return bool|object
203 + * @editor tungnx
204 + * @since 4.1.2
205 + * @version 1.0.2
206 + *
207 + * @author Nhamdv
208 + */
209 + public function has_enrolled_course( int $course_id, bool $return_bool = true ) {
210 + $result_check = new stdClass();
211 + $result_check->check = true;
212 + $result_check->message = '';
213 +
214 + try {
215 + /*$order = $this->get_course_order( $course_id );
216 +
217 + if ( ! $order || ! $order->is_completed() ) {
218 + throw new Exception( esc_html__( 'Order is not completed', 'learnpress' ) );
219 + }*/
220 +
221 + $user_course = $this->get_course_data( $course_id );
222 + if ( ! $user_course || ! $user_course->is_enrolled() ) {
223 + throw new Exception( esc_html__( 'The course is not enrolled.', 'learnpress' ) );
224 + }
225 + } catch ( Throwable $th ) {
226 + $result_check->check = false;
227 + $result_check->message = $th->getMessage();
228 + }
229 +
230 + return apply_filters( 'learn-press/user/is-course-enrolled', $return_bool ? $result_check->check : $result_check, $course_id, $return_bool );
231 + }
232 +
233 + /**
234 + * Return true if user has finished a course
235 + *
236 + * @param int $course_id .
237 + *
238 + * @return bool
239 + * @throws Exception
240 + */
241 + public function has_finished_course( int $course_id ): bool {
242 + $user_course = $this->get_course_data( $course_id );
243 +
244 + return apply_filters( 'learn-press/user-has-finished-course', $user_course && $user_course->is_finished(), $this->get_id(), $course_id );
245 + }
246 +
247 + /**
248 + * Check course of user is enrolled or finished
249 + *
250 + * @param int $course_id
251 + *
252 + * @return bool
253 + */
254 + public function has_enrolled_or_finished( int $course_id ): bool {
255 + $status = true;
256 +
257 + try {
258 + $user_course = $this->get_course_data( $course_id );
259 +
260 + if ( ! $user_course ) {
261 + $status = false;
262 + } elseif ( ! $user_course->is_enrolled() && ! $user_course->is_finished() ) {
263 + $status = false;
264 + }
265 + } catch ( Throwable $e ) {
266 + $status = false;
267 + }
268 +
269 + return apply_filters( 'learn-press/user-has-finished-course', $status, $this->get_id(), $course_id );
270 + }
271 +
272 + /**
273 + * Check user can enroll course
274 + *
275 + * @param int $course_id
276 + * @param bool $return_bool
277 + *
278 + * @return mixed|object|bool
279 + */
280 + public function can_enroll_course( int $course_id, bool $return_bool = true ) {
281 + $course = learn_press_get_course( $course_id );
282 + $output = new stdClass();
283 + $output->check = true;
284 + $output->message = '';
285 +
286 + try {
287 + $is_no_required_enroll = $course->get_data( 'no_required_enroll', 'no' ) === 'yes';
288 + if ( ! $course ) {
289 + $output->code = 'course_unavailable';
290 + throw new Exception( esc_html__( 'No Course or User available', 'learnpress' ) );
291 + }
292 +
293 + if ( ! $course->is_publish() ) {
294 + $output->code = 'course_not_publish';
295 + throw new Exception( esc_html__( 'The course is not public', 'learnpress' ) );
296 + }
297 +
298 + if ( $this->has_enrolled_course( $course_id ) ) {
299 + $output->code = 'course_is_enrolled';
300 + throw new Exception( esc_html__( 'This course is already enrolled.', 'learnpress' ) );
301 + }
302 +
303 + if ( ! $course->is_in_stock_enroll() && ! $this->has_purchased_course( $course_id )
304 + && ! $this->has_enrolled_or_finished( $course_id ) && ! $is_no_required_enroll ) {
305 + $output->code = 'course_out_of_stock';
306 + throw new Exception( esc_html__( 'The course is full of students.', 'learnpress' ) );
307 + }
308 +
309 + if ( $course->get_external_link()
310 + && ! $this->has_purchased_course( $course_id )
311 + && ! $course->is_offline() ) {
312 + $output->code = 'course_is_external';
313 + throw new Exception( esc_html__( 'The course is external', 'learnpress' ) );
314 + }
315 +
316 + if ( $this->can_retry_course( $course_id ) ) {
317 + $output->code = 'course_can_retry';
318 + throw new Exception( esc_html__( 'Course can retake.', 'learnpress' ) );
319 + }
320 +
321 + if ( $is_no_required_enroll && ! is_user_logged_in() ) {
322 + $output->code = 'course_is_no_required_enroll_not_login';
323 + throw new Exception(
324 + esc_html__( 'Enrollment in the course is not mandatory. You can access course for learning now.', 'learnpress' )
325 + );
326 + }
327 +
328 + if ( ! $course->is_free() && ! $is_no_required_enroll && ! $this->has_purchased_course( $course_id ) ) {
329 + $output->code = 'course_is_not_purchased';
330 + throw new Exception( esc_html__( 'The course is not purchased.', 'learnpress' ) );
331 + }
332 + } catch ( \Throwable $th ) {
333 + $output->check = false;
334 + $output->message = $th->getMessage();
335 + }
336 +
337 + if ( $return_bool ) {
338 + $output = $output->check;
339 + }
340 +
341 + return apply_filters( 'learn-press/user/can-enroll-course', $output, $course, $return_bool, $this );
342 + }
343 +
344 + /**
345 + * Check can show purchase course button
346 + *
347 + * @param int $course_id
348 + *
349 + * @return bool|WP_Error
350 + * @author nhamdv
351 + * @editor tungnx
352 + * @since 4.0.8
353 + * @version 1.0.6
354 + */
355 + public function can_purchase_course( int $course_id = 0 ) {
356 + $can_purchase = true;
357 + $course = learn_press_get_course( $course_id );
358 + $code_err = '';
359 +
360 + try {
361 + $can_enroll_course = $this->can_enroll_course( $course_id, false );
362 + if ( ! $can_enroll_course->check &&
363 + ! in_array( $can_enroll_course->code, [ 'course_is_not_purchased', 'course_is_enrolled' ] ) ) {
364 + $code_err = $can_enroll_course->code;
365 + throw new Exception( $can_enroll_course->message );
366 + }
367 +
368 + if ( $course->is_free() ) {
369 + $code_err = 'course_is_free';
370 + throw new Exception( __( 'Course is free, so you can not purchase', 'learnpress' ) );
371 + }
372 +
373 + // Course is not require enrolling.
374 + // Not use $course->is_no_required_enroll() because it is not correct, it check with user logged.
375 + if ( $course->get_data( 'no_required_enroll', 'no' ) === 'yes' ) {
376 + $code_err = 'no_required_enroll';
377 + throw new Exception(
378 + __(
379 + 'Enrollment in the course is not mandatory. You can access materials for learning or to take quizzes now.',
380 + 'learnpress'
381 + )
382 + );
383 + }
384 +
385 + // If the order contains course is processing
386 + $order = $this->get_course_order( $course_id );
387 + if ( $order && $order->get_status() === LP_ORDER_PROCESSING ) {
388 + $code_err = 'order_processing';
389 + throw new Exception( __( 'Your order is waiting for processing', 'learnpress' ) );
390 + }
391 +
392 + if ( $this->has_purchased_course( $course_id ) ) {
393 + $code_err = 'course_purchased';
394 + throw new Exception( __( 'Course is purchased', 'learnpress' ) );
395 + }
396 +
397 + $is_blocked_course = 0 === $course->timestamp_remaining_duration();
398 + $is_enrolled_course = $this->has_enrolled_course( $course_id );
399 + if ( $course->allow_repurchase() ) {
400 + if ( $is_enrolled_course && ! $is_blocked_course ) {
401 + $code_err = 'course_is_enrolled';
402 + throw new Exception( 'Course is enrolled' );
403 + }
404 + } else {
405 + if ( $this->has_enrolled_or_finished( $course_id ) ) {
406 + $code_err = 'course_is_enrolled_or_finished';
407 + throw new Exception( __( 'Course is enrolled or finished', 'learnpress' ) );
408 + }
409 + }
410 + } catch ( Throwable $e ) {
411 + $can_purchase = new WP_Error( $code_err, $e->getMessage() );
412 + }
413 +
414 + if ( $can_purchase instanceof WP_Error ) {
415 + $can_purchase = false;
416 + }
417 +
418 + return apply_filters( 'learn-press/user/can-purchase-course', $can_purchase, $this->get_id(), $course_id );
419 + }
420 +
421 + /**
422 + * Check condition show finish course button
423 + *
424 + * @param $course
425 + *
426 + * @return array
427 + * @author nhamdv
428 + * @editor tungnx
429 + * @version 1.0.2
430 + */
431 + public function can_show_finish_course_btn( $course ): array {
432 + $return = array(
433 + 'status' => 'success',
434 + 'message' => '',
435 + );
436 +
437 + try {
438 + if ( ! $course ) {
439 + throw new Exception( esc_html__( 'Error: No Course or User available.', 'learnpress' ) );
440 + }
441 +
442 + $course_id = $course->get_id();
443 +
444 + if ( $this->has_finished_course( $course_id ) ) {
445 + throw new Exception( esc_html__( 'The course has finished.', 'learnpress' ) );
446 + }
447 +
448 + if ( ! $this->has_enrolled_course( $course_id ) ) {
449 + throw new Exception( esc_html__( 'The course is not enrolled.', 'learnpress' ) );
450 + }
451 +
452 + if ( ! $this->is_course_in_progress( $course_id ) ) {
453 + throw new Exception( esc_html__( 'Error: The course is not in progress.', 'learnpress' ) );
454 + }
455 +
456 + $course_data = $this->get_course_data( $course_id );
457 +
458 + if ( $course_data ) {
459 + $course_result = $course_data->get_result();
460 +
461 + if ( ! $course_result['pass'] ) {
462 + // Get option Can finish course if completed all item without pass
463 + $can_finish = get_post_meta( $course_id, '_lp_has_finish', true ) ?? 'yes';
464 +
465 + if ( $can_finish == 'yes' ) {
466 + // Check completed all items
467 + $is_all_completed = $this->is_completed_all_items( $course_id );
468 + if ( ! $is_all_completed ) {
469 + throw new Exception( 'All items not completed and Course not pass' );
470 + }
471 + } else {
472 + throw new Exception( 'Course not pass' );
473 + }
474 + }
475 + }
476 +
477 + if ( ! apply_filters( 'lp_can_finish_course', true ) ) {
478 + throw new Exception( esc_html__( 'Error: Filter the disabled finished courses.', 'learnpress' ) );
479 + }
480 + } catch ( Exception $e ) {
481 + $return['status'] = 'false';
482 + $return['message'] = $e->getMessage();
483 + }
484 +
485 + return $return;
486 + }
487 +
488 + /**
489 + * Start quiz for the user.
490 + *
491 + * @param int $quiz_id
492 + * @param int $course_id
493 + * @param bool $wp_error Optional. Whether to return a WP_Error on failure. Default false.
494 + *
495 + * @return LP_User_Item_Quiz|bool|WP_Error
496 + * @throws Exception
497 + * @deprecated 4.2.9.1
498 + */
499 + public function start_quiz( int $quiz_id, int $course_id = 0, bool $wp_error = false ) {
500 + _deprecated_function( __METHOD__, '4.2.9.1' );
501 + return false;
502 +
503 + try {
504 + $item_is_preview = learn_press_get_request( 'lp-preview' );
505 + if ( $item_is_preview ) {
506 + throw new Exception( __( 'You cannot start a quiz in preview mode.', 'learnpress' ) );
507 + }
508 +
509 + // Validate course and quiz
510 + $course_id = $this->_verify_course_item( $quiz_id, $course_id );
511 + if ( ! $course_id ) {
512 + throw new Exception(
513 + __( 'The course does not exist or does not contain a quiz.', 'learnpress' ),
514 + LP_INVALID_QUIZ_OR_COURSE
515 + );
516 + }
517 +
518 + $course = learn_press_get_course( $course_id );
519 + // If user has already finished the course
520 + if ( $this->has_finished_course( $course_id ) ) {
521 + throw new Exception(
522 + __( 'You have already finished the course of this quiz', 'learnpress' ),
523 + LP_COURSE_IS_FINISHED
524 + );
525 + }
526 +
527 + if ( ! $this->has_enrolled_course( $course_id ) || ! $this->is_course_in_progress( $course_id ) ) {
528 + if ( ! $course->is_no_required_enroll() ) {
529 + throw new Exception(
530 + __( 'Please enroll in the course before starting the quiz.', 'learnpress' ),
531 + LP_COURSE_IS_FINISHED
532 + );
533 + }
534 + }
535 +
536 + // Check if user has already started or completed quiz
537 + if ( $this->has_item_status( array( 'started', 'completed' ), $quiz_id, $course_id ) ) {
538 + throw new Exception(
539 + __( 'The user has started or completed the quiz.', 'learnpress' ),
540 + LP_QUIZ_HAS_STARTED_OR_COMPLETED
541 + );
542 + }
543 +
544 + $user_current = learn_press_get_current_user();
545 + if ( $user_current->is_guest() ) {
546 + // if course required enroll => print message "You have to login for starting quiz"
547 + if ( ! $course->is_no_required_enroll() ) {
548 + throw new Exception( __( 'You have to log in to start the quiz.', 'learnpress' ), LP_REQUIRE_LOGIN );
549 + }
550 + }
551 +
552 + /**
553 + * Hook can start quiz
554 + *
555 + * @see learn_press_hk_before_start_quiz
556 + */
557 + $can_start_quiz = apply_filters(
558 + 'learn-press/can-start-quiz',
559 + true,
560 + $quiz_id,
561 + $course_id,
562 + $this->get_id()
563 + );
564 +
565 + if ( ! $can_start_quiz ) {
566 + return false;
567 + }
568 +
569 + $user_quiz = false;
570 +
571 + /**
572 + * Hook quiz started
573 + *
574 + * @since 3.0.0
575 + */
576 + do_action( 'learn-press/user/quiz-started', $quiz_id, $course_id, $this->get_id() );
577 +
578 + // $return = $user_quiz->get_mysql_data();
579 + $return = $user_quiz;
580 + } catch ( Throwable $ex ) {
581 + $return = $wp_error ? new WP_Error( $ex->getCode(), $ex->getMessage() ) : false;
582 + }
583 +
584 + return $return;
585 + }
586 +
587 + /**
588 + * Finish a quiz for the user and save all data needed
589 + *
590 + * @param int $quiz_id
591 + * @param int $course_id
592 + * @param bool $wp_error
593 + *
594 + * @return LP_User_Item_Quiz|bool|WP_Error
595 + */
596 + public function finish_quiz( int $quiz_id, int $course_id, bool $wp_error = false ) {
597 + $return = false;
598 +
599 + try {
600 + // If user has already finished the course
601 + if ( $this->has_finished_course( $course_id ) ) {
602 + throw new Exception(
603 + __( 'The user has already finished the course of this quiz.', 'learnpress' ),
604 + LP_COURSE_IS_FINISHED
605 + );
606 +
607 + }
608 +
609 + // Check if user has already started or completed quiz
610 + if ( $this->has_item_status( array( 'completed' ), $quiz_id, $course_id ) ) {
611 + throw new Exception(
612 + __( 'The user has completed the quiz', 'learnpress' ),
613 + LP_QUIZ_HAS_STARTED_OR_COMPLETED
614 + );
615 + }
616 +
617 + /**
618 + * @var LP_User_Item_Quiz $user_quiz
619 + */
620 + $user_quiz = $this->get_item_data( $quiz_id, $course_id );
621 + $user_quiz->complete();
622 +
623 + do_action( 'learn-press/user/quiz-finished', $quiz_id, $course_id, $this->get_id() );
624 + } catch ( Exception $ex ) {
625 + $return = $wp_error ? new WP_Error( $ex->getCode(), $ex->getMessage() ) : true;
626 + }
627 +
628 + return $return;
629 + }
630 +
631 + /**
632 + * Retake a quiz for the user
633 + *
634 + * @param int $quiz_id
635 + * @param int $course_id
636 + * @param bool $wp_error
637 + *
638 + * @return bool|WP_Error|LP_User_Item_Quiz
639 + *
640 + * @throws Exception
641 + * @deprecated 4.2.7.2
642 + */
643 + /*public function retake_quiz( int $quiz_id, int $course_id, bool $wp_error = false ) {
644 + $return = false;
645 +
646 + try {
647 + $course_id = $this->_verify_course_item( $quiz_id, $course_id );
648 +
649 + if ( false === $course_id ) {
650 + throw new Exception(
651 + sprintf(
652 + __(
653 + 'The course does not exist or does not contain a quiz.',
654 + 'learnpress'
655 + ),
656 + __CLASS__,
657 + __FUNCTION__
658 + ),
659 + LP_INVALID_QUIZ_OR_COURSE
660 + );
661 + }
662 +
663 + // If user has already finished the course.
664 + if ( $this->has_finished_course( $course_id ) ) {
665 + throw new Exception(
666 + sprintf(
667 + __( 'You can not redo a quiz in a finished course.', 'learnpress' ),
668 + __CLASS__,
669 + __FUNCTION__
670 + ),
671 + LP_COURSE_IS_FINISHED
672 + );
673 +
674 + }
675 +
676 + // Check if user has already started or completed quiz
677 + if ( ! $this->has_item_status( array( 'completed' ), $quiz_id, $course_id ) ) {
678 + throw new Exception(
679 + sprintf(
680 + __( '%1$s::%2$s - The user has not completed the quiz.', 'learnpress' ),
681 + __CLASS__,
682 + __FUNCTION__
683 + ),
684 + LP_QUIZ_HAS_STARTED_OR_COMPLETED
685 + );
686 + }
687 +
688 + $allow_attempts = learn_press_get_quiz_max_retrying( $quiz_id, $course_id );
689 +
690 + if ( ! $this->has_retake_quiz( $quiz_id, $course_id ) ) {
691 + throw new Exception(
692 + sprintf(
693 + __( '%1$s::%2$s - Your quiz can\'t be retaken.', 'learnpress' ),
694 + __CLASS__,
695 + __FUNCTION__
696 + ),
697 + LP_QUIZ_HAS_STARTED_OR_COMPLETED
698 + );
699 + }
700 +
701 + $return = learn_press_user_retake_quiz( $quiz_id, false, $course_id, $wp_error );
702 +
703 + do_action( 'learn-press/user/quiz-retried', $quiz_id, $course_id, $this->get_id() );
704 + } catch ( Exception $ex ) {
705 + $return = $wp_error ? new WP_Error( $ex->getCode(), $ex->getMessage() ) : false;
706 + do_action( 'learn-press/user/retake-quiz-failure', $quiz_id, $course_id, $this->get_id() );
707 + }
708 +
709 + return $return;
710 + }*/
711 +
712 + /**
713 + * Get quiz's user learning or completed
714 + *
715 + * @param LP_User_Items_Filter|UserItemsFilter $filter
716 + *
717 + * @return LP_Query_List_Table
718 + */
719 + public function get_user_quizzes( $filter ): LP_Query_List_Table {
720 + $quizzes = [
721 + 'total' => 0,
722 + 'items' => [],
723 + 'pages' => 0,
724 + ];
725 +
726 + try {
727 + $user_quizzes = LP_User_Items_DB::getInstance()->get_user_quizzes( $filter );
728 +
729 + if ( $user_quizzes ) {
730 + $count = LP_User_Items_DB::getInstance()->wpdb->get_var( 'SELECT FOUND_ROWS()' );
731 +
732 + $quizzes['total'] = $count;
733 + $quizzes['pages'] = ceil( $count / $filter->limit );
734 +
735 + foreach ( $user_quizzes as $item ) {
736 + $quizzes['items'][] = new LP_User_Item_Quiz( $item );
737 + }
738 + }
739 +
740 + $quizzes['single'] = __( 'quiz', 'learnpress' );
741 + $quizzes['plural'] = __( 'quizzes', 'learnpress' );
742 + } catch ( Throwable $e ) {
743 + error_log( __FUNCTION__ . ': ' . $e->getMessage() );
744 + }
745 +
746 + return new LP_Query_List_Table( $quizzes );
747 + }
748 +
749 + /**
750 + * Set item is viewing in single course.
751 + *
752 + * @param LP_Course_Item $item
753 + *
754 + * @return int|LP_Course_Item
755 + * @since 4.1.6.9 - move from LP_Course
756 + * @editor tungnx
757 + * @version 1.0.0
758 + */
759 + public function set_viewing_item( LP_Course_Item $item ) {
760 + $flag = false;
761 +
762 + try {
763 + $user = learn_press_get_current_user();
764 + if ( $user instanceof LP_User_Guest ) {
765 + return $flag;
766 + }
767 +
768 + $course_id = $item->get_course_id();
769 + $item_id = $item->get_id();
770 + $course_data = $this->get_course_data( $course_id );
771 +
772 + if ( $course_data && $course_data->is_enrolled() ) {
773 + $item = $course_data->get_item( $item_id );
774 +
775 + if ( ! $item ) {
776 + $item = LP_User_Item::get_item_object( $item_id );
777 +
778 + if ( ! $item ) {
779 + return $flag;
780 + }
781 +
782 + if ( $item instanceof LP_User_Item_Quiz ) {
783 + return $flag;
784 + }
785 +
786 + $item->set_ref_id( $course_id );
787 + $item->set_parent_id( $course_data->get_user_item_id() );
788 +
789 + $flag = $item->update();
790 + }
791 + }
792 + } catch ( Throwable $e ) {
793 + error_log( $e->getMessage() );
794 + }
795 +
796 + return $flag;
797 + }
798 +
799 + /**
800 + * Get statistic info of student user
801 + *
802 + * @return array
803 + * @since 4.1.6
804 + * @version 1.0.0
805 + */
806 + public function get_student_statistic(): array {
807 + $user = $this;
808 + $statistic = array(
809 + 'enrolled_courses' => 0,
810 + 'in_progress_course' => 0,
811 + 'finished_courses' => 0,
812 + 'passed_courses' => 0,
813 + 'failed_courses' => 0,
814 + );
815 +
816 + try {
817 + if ( ! $user ) {
818 + throw new Exception( 'The user is invalid' );
819 + }
820 +
821 + $user_id = $user->get_id();
822 + $lp_user_items_db = LP_User_Items_DB::getInstance();
823 +
824 + // Count status
825 + $filter = new LP_User_Items_Filter();
826 + $filter->user_id = $user_id;
827 + $count_status = $lp_user_items_db->count_status_by_items( $filter );
828 + $total_courses_enrolled = intval( $count_status->{LP_COURSE_PURCHASED} ?? 0 )
829 + + intval( $count_status->{LP_COURSE_ENROLLED} ?? 0 )
830 + + intval( $count_status->{LP_COURSE_FINISHED} ?? 0 );
831 +
832 + $statistic['enrolled_courses'] = $total_courses_enrolled;
833 + $statistic['in_progress_course'] = $count_status->{LP_COURSE_GRADUATION_IN_PROGRESS} ?? 0;
834 + $statistic['finished_courses'] = $count_status->{LP_COURSE_FINISHED} ?? 0;
835 + $statistic['passed_courses'] = $count_status->{LP_COURSE_GRADUATION_PASSED} ?? 0;
836 + $statistic['failed_courses'] = $count_status->{LP_COURSE_GRADUATION_FAILED} ?? 0;
837 + } catch ( Throwable $e ) {
838 + error_log( __FUNCTION__ . ': ' . $e->getMessage() );
839 + }
840 +
841 + return apply_filters( 'lp/profile/student/statistic', $statistic, $this );
842 + }
843 +
844 + /**
845 + * Get statistic info of instructor user
846 + *
847 + * @param array $params
848 + *
849 + * @return array
850 + * @since 4.1.6
851 + * @version 1.0.1
852 + */
853 + public function get_instructor_statistic( array $params = [] ): array {
854 + $statistic = array(
855 + 'total_course' => 0,
856 + 'published_course' => 0,
857 + 'pending_course' => 0,
858 + 'total_student' => 0,
859 + 'student_completed' => 0,
860 + 'student_in_progress' => 0,
861 + );
862 +
863 + try {
864 + $user_id = $this->get_id();
865 + $lp_user_items_db = LP_User_Items_DB::getInstance();
866 + $lp_course_db = LP_Course_DB::getInstance();
867 +
868 + if ( ! $this->can_create_course() ) {
869 + throw new Exception( 'The user is not Instructor' );
870 + }
871 +
872 + // Count total user completed course of author
873 + $filter_course = new LP_Course_Filter();
874 + $filter_course->only_fields = array( 'ID' );
875 + $filter_course->post_author = $user_id;
876 + $filter_course->post_status = 'publish';
877 + $filter_course->return_string_query = true;
878 + $query_courses_str = LP_Course_DB::getInstance()->get_courses( $filter_course );
879 +
880 + $filter_count_users = new LP_User_Items_Filter();
881 + $filter_count_users->item_type = LP_COURSE_CPT;
882 + $filter_count_users->where[] = "AND item_id IN ({$query_courses_str})";
883 + $count_student_has_status = $lp_user_items_db->count_status_by_items( $filter_count_users );
884 + // Count total user in progress course of author
885 +
886 + // Get total users attend course of author
887 + $filter_count_users = $lp_user_items_db->count_user_attend_courses_of_author( $user_id );
888 + $count_users_attend_courses_of_author = $lp_user_items_db->get_user_courses( $filter_count_users );
889 +
890 + // Get total courses publish of author
891 + $filter_count_courses = $lp_course_db->count_courses_of_author( $user_id, [ 'publish' ] );
892 + $total_courses_publish_of_author = $lp_course_db->get_courses( $filter_count_courses );
893 +
894 + // Get total courses of author
895 + $filter_count_courses = $lp_course_db->count_courses_of_author( $user_id );
896 + $total_courses_of_author = $lp_course_db->get_courses( $filter_count_courses );
897 +
898 + // Get total courses pending of author
899 + $filter_count_courses = $lp_course_db->count_courses_of_author( $user_id, [ 'pending' ] );
900 + $total_courses_pending_of_author = $lp_course_db->get_courses( $filter_count_courses );
901 +
902 + $statistic['total_course'] = $total_courses_of_author;
903 + $statistic['published_course'] = $total_courses_publish_of_author;
904 + $statistic['pending_course'] = $total_courses_pending_of_author;
905 + $statistic['total_student'] = $count_users_attend_courses_of_author;
906 + $statistic['student_completed'] = $count_student_has_status->{LP_COURSE_FINISHED} ?? 0;
907 + $statistic['student_in_progress'] = $count_student_has_status->{LP_COURSE_GRADUATION_IN_PROGRESS} ?? 0;
908 + } catch ( Throwable $e ) {
909 + error_log( __FUNCTION__ . ': ' . $e->getMessage() );
910 + }
911 +
912 + return apply_filters( 'lp/profile/instructor/statistic', $statistic, $this );
913 + }
914 +
915 + /**
916 + * Get url instructor.
917 + *
918 + * @return string
919 + * @version 1.0.1
920 + * @since 4.2.3
921 + */
922 + public function get_url_instructor(): string {
923 + $single_instructor_link = '';
924 +
925 + try {
926 + $author_id = $this->get_id();
927 + $userModel = UserModel::find( $author_id, true );
928 + if ( ! $userModel ) {
929 + throw new Exception( 'User not found' );
930 + }
931 +
932 + $single_instructor_page_id = learn_press_get_page_id( 'single_instructor' );
933 + $user_slug = $userModel->get_slug_link();
934 + $single_instructor_link = trailingslashit( trailingslashit( get_page_link( $single_instructor_page_id ) ) . $user_slug );
935 + } catch ( Throwable $e ) {
936 + LP_Debug::error_log( $e );
937 + }
938 +
939 + return $single_instructor_link;
940 + }
941 +}