From 66d67576b5e1990d216d27ddab517b987825247b Mon Sep 17 00:00:00 2001 From: Tungnx Date: Mon, 23 Dec 2024 17:37:45 +0700 Subject: [PATCH] = 4.2.7.6 = ~ Refactor code handle Quiz. --- .../frontend/quiz/components/buttons/index.js | 1 + assets/src/scss/learnpress.scss | 10 +- inc/Databases/class-lp-post-db.php | 3 +- inc/Filters/class-lp-quiz-filter.php | 25 ++ inc/Models/CourseModel.php | 16 +- inc/Models/PostModel.php | 6 +- inc/Models/QuizPostModel.php | 219 +++++++++++++++ inc/Models/UserItems/UserItemModel.php | 34 +-- inc/Models/UserItems/UserQuizModel.php | 251 +++++++++++------- inc/question/class-lp-question-answers.php | 105 ++++---- .../class-lp-rest-users-controller.php | 2 +- inc/templates/class-lp-template-course.php | 4 +- inc/user/lp-user-functions.php | 11 + learnpress.php | 3 +- templates/content-quiz/js.php | 90 ++++--- 15 files changed, 544 insertions(+), 236 deletions(-) create mode 100644 inc/Filters/class-lp-quiz-filter.php create mode 100644 inc/Models/QuizPostModel.php diff --git a/assets/src/apps/js/frontend/quiz/components/buttons/index.js b/assets/src/apps/js/frontend/quiz/components/buttons/index.js index c9ebe4315..508556a14 100644 --- a/assets/src/apps/js/frontend/quiz/components/buttons/index.js +++ b/assets/src/apps/js/frontend/quiz/components/buttons/index.js @@ -16,6 +16,7 @@ class Buttons extends Component { if ( 'no' === confirm( __( 'Are you sure you want to retake the quiz?', 'learnpress' ), this.startQuiz ) ) { ! isOpen() && btn && btn.removeAttribute( 'disabled' ); + btn.classList.remove( 'loading' ); return; } } diff --git a/assets/src/scss/learnpress.scss b/assets/src/scss/learnpress.scss index a2bfff3f2..f5edcc391 100644 --- a/assets/src/scss/learnpress.scss +++ b/assets/src/scss/learnpress.scss @@ -174,10 +174,12 @@ ul.learn-press-breadcrumb { /** * Forms */ -.retake-course, -.enroll-course, -.purchase-course { - display: inline-block; +form.retake-course, +form.enroll-course, +form.purchase-course { + display: inline-flex; + flex-direction: column; + gap: 10px; } /* */ diff --git a/inc/Databases/class-lp-post-db.php b/inc/Databases/class-lp-post-db.php index 1bebb89c0..715986a66 100644 --- a/inc/Databases/class-lp-post-db.php +++ b/inc/Databases/class-lp-post-db.php @@ -47,7 +47,7 @@ public function get_posts( LP_Post_Type_Filter $filter, int &$total_rows = 0 ) { // Find ID if ( isset( $filter->ID ) ) { - $filter->where[] = $this->wpdb->prepare( "AND $ca.ID = %d", $filter->ID ); + $filter->where[] = $this->wpdb->prepare( "AND $ca.ID = %d", $filter->ID ); } // Status @@ -101,4 +101,3 @@ public function get_posts( LP_Post_Type_Filter $filter, int &$total_rows = 0 ) { return $this->execute( $filter, $total_rows ); } } - diff --git a/inc/Filters/class-lp-quiz-filter.php b/inc/Filters/class-lp-quiz-filter.php new file mode 100644 index 000000000..55ca45bd9 --- /dev/null +++ b/inc/Filters/class-lp-quiz-filter.php @@ -0,0 +1,25 @@ +only_fields = [ 'json', 'post_content' ]; - // Load cache - if ( $check_cache ) { - - $key_cache = "course-model/{$filter->ID}/" . md5( json_encode( $filter ) ); - $lp_course_cache = new LP_Course_Cache(); - $course_model = $lp_course_cache->get_cache( $key_cache ); - - if ( $course_model instanceof CourseModel ) { - return $course_model; - } - } $course_rs = self::get_course_from_db( $filter ); if ( $course_rs instanceof stdClass && isset( $course_rs->json ) ) { diff --git a/inc/Models/PostModel.php b/inc/Models/PostModel.php index dc57e4137..2474604dd 100644 --- a/inc/Models/PostModel.php +++ b/inc/Models/PostModel.php @@ -110,6 +110,8 @@ public function __construct( $data = null ) { * Get user model * * @return false|UserModel + * @since 4.2.6.9 + * @version 1.0.1 */ public function get_author_model() { if ( ! empty( $this->post_author ) ) { @@ -145,11 +147,11 @@ public function map_to_object( $data ): PostModel { * If exists, return PostModel. * * @param LP_Course_Filter $filter - * @param bool $check_cache * * @return PostModel|false|static + * @version 1.0.1 */ - public static function get_item_model_from_db( LP_Post_Type_Filter $filter, bool $check_cache = false ) { + public static function get_item_model_from_db( LP_Post_Type_Filter $filter ) { $lp_post_db = LP_Post_DB::getInstance(); $post_model = false; diff --git a/inc/Models/QuizPostModel.php b/inc/Models/QuizPostModel.php new file mode 100644 index 000000000..7415e48d8 --- /dev/null +++ b/inc/Models/QuizPostModel.php @@ -0,0 +1,219 @@ +ID = $post_id; + + $key_cache = "quizPostModel/find/{$post_id}"; + $lpQuizCache = new LP_Cache(); + + // Check cache + if ( $check_cache ) { + $quizPostModel = $lpQuizCache->get_cache( $key_cache ); + if ( $quizPostModel instanceof QuizPostModel ) { + return $quizPostModel; + } + } + + $quizPostModel = self::get_item_model_from_db( $filter_post ); + // Set cache + if ( $quizPostModel instanceof QuizPostModel ) { + $lpQuizCache->set_cache( $key_cache, $quizPostModel ); + } + + return $quizPostModel; + } + + /** + * Get max mark of assignment + * + * @return string + */ + public function get_duration(): string { + return $this->get_meta_value_by_key( self::META_KEY_DURATION, '0 minute' ); + } + + /** + * Get max mark of assignment + * + * @return float + */ + public function get_passing_grade(): float { + return (float) $this->get_meta_value_by_key( self::META_KEY_PASSING_GRADE, 80 ); + } + + /** + * Get retake count option. + * + * @return int + */ + public function get_retake_count(): int { + return (int) $this->get_meta_value_by_key( self::META_KEY_RETAKE_COUNT, 0 ); + } + + /** + * Get all question's ids of the quiz. + * + * @param array $statuses + * + * @return int[] + * @version 1.0.0 + * @since 4.2.7.6 + */ + public function get_question_ids( array $statuses = [ 'publish' ] ): array { + $lp_question_db = LP_Question_DB::getInstance(); + $question_ids = []; + + try { + $filter = new LP_Question_Filter(); + $filter->ID = $this->get_id(); + $filter->post_status = $statuses; + $question_ids = $lp_question_db->get_list_question_ids_of_quiz( $filter ); + + // Hook old + if ( has_filter( 'learn-press/quiz/get-question-ids' ) ) { + $quiz_old = new LP_Quiz( $this->get_id() ); + $course_id_of_quiz_old = $quiz_old->get_course_id(); + + $question_ids = apply_filters( + 'learn-press/quiz/get-question-ids', + $question_ids, + $this->get_id(), + $course_id_of_quiz_old + ); + } + + $question_ids = apply_filters( + 'learn-press/quiz/question-ids', + $question_ids, + $this, + $statuses + ); + + if ( ! is_array( $question_ids ) ) { + $question_ids = array(); + } + } catch ( Throwable $e ) { + error_log( $e->getMessage() ); + } + + return $question_ids; + } + + /** + * Get number questions in quiz. + * + * @return int + */ + public function count_questions(): int { + $size = 0; + $questions = $this->get_question_ids(); + + if ( $questions ) { + $size = sizeof( $questions ); + } + + return (int) apply_filters( 'learn-press/quiz/count-questions', $size, $this->get_id() ); + } + + /** + * Get Mark of the quiz (total mark questions). + * + * @return mixed|null + */ + public function get_mark() { + $questions = $this->get_question_ids(); + $mark = 0; + + foreach ( $questions as $question_id ) { + $question = LP_Question::get_question( $question_id ); + if ( $question ) { + $mark += $question->get_mark(); + } + } + + return apply_filters( 'learn-press/quiz-mark', $mark, $this->get_id() ); + } + + /** + * Check option instant check has enabled. + * + * @return bool + */ + public function has_instant_check(): bool { + return $this->get_meta_value_by_key( self::META_KEY_INSTANT_CHECK, 'no' ) === 'yes'; + } + + /** + * Check option negative marking has enabled. + * + * @return bool + */ + public function has_negative_marking(): bool { + return $this->get_meta_value_by_key( self::META_KEY_NEGATIVE_MARKING, 'no' ) === 'yes'; + } + + /** + * Check option minus skip questions has enabled. + * + * @return bool + */ + public function has_minus_skip_questions(): bool { + return $this->get_meta_value_by_key( self::META_KEY_MINUS_SKIP_QUESTIONS, 'no' ) === 'yes'; + } + + /** + * Check option minus skip questions has enabled. + * + * @return bool + */ + public function has_show_correct_review(): bool { + return $this->get_meta_value_by_key( self::META_KEY_SHOW_CORRECT_REVIEW, 'yes' ) === 'yes'; + } +} diff --git a/inc/Models/UserItems/UserItemModel.php b/inc/Models/UserItems/UserItemModel.php index fc317f5f6..040922929 100644 --- a/inc/Models/UserItems/UserItemModel.php +++ b/inc/Models/UserItems/UserItemModel.php @@ -5,7 +5,7 @@ * To replace class LP_User_Item * * @package LearnPress/Classes - * @version 1.0.1 + * @version 1.0.2 * @since 4.2.5 */ @@ -15,11 +15,8 @@ use LearnPress\Models\CoursePostModel; use LearnPress\Models\PostModel; use LearnPress\Models\UserItemMeta\UserItemMetaModel; -use LearnPressAssignment\Models\UserAssignmentModel; -use LP_Cache; +use LearnPress\Models\UserModel; use LP_Datetime; -use LP_User; -use LP_User_Guest; use LP_User_Item_Meta_DB; use LP_User_Item_Meta_Filter; use LP_User_Items_Cache; @@ -93,10 +90,6 @@ class UserItemModel { * @var null|PostModel|CoursePostModel */ public $item; - /** - * @var LP_User|null - */ - public $user; /** * List UserItemMetaModel * object {meta_key: {meta_id, learnpress_user_item_id, meta_key, meta_value, extra_value}} @@ -180,14 +173,12 @@ private function set_user_item_id( int $user_item_id ) { /** * Get user model * - * @return false|LP_User|LP_User_Guest + * @return false|UserModel + * @since 4.2.6 + * @version 1.0.1 */ public function get_user_model() { - if ( empty( $this->user ) ) { - $this->user = learn_press_get_user( $this->user_id ); - } - - return $this->user; + return UserModel::find( $this->user_id, true ); } /** @@ -231,8 +222,7 @@ public static function get_user_item_model_from_db( LP_User_Items_Filter $filter $query_single_row = $lp_user_item_db->get_user_items( $filter ); $user_item_rs = $lp_user_item_db->wpdb->get_row( $query_single_row ); if ( $user_item_rs instanceof stdClass ) { - $user_item_model = new static( $user_item_rs ); - $user_item_model->user = $user_item_model->get_user_model(); + $user_item_model = new static( $user_item_rs ); } } catch ( Throwable $e ) { error_log( __METHOD__ . ': ' . $e->getMessage() ); @@ -270,11 +260,11 @@ public static function find_user_item( $filter->item_type = $item_type; if ( ! empty( $ref_id ) ) { $filter->ref_id = $ref_id; - $key_cache .= "/{$ref_id}"; + $key_cache .= "/{$ref_id}"; } if ( ! empty( $ref_type ) ) { $filter->ref_type = $ref_type; - $key_cache .= "/{$ref_type}"; + $key_cache .= "/{$ref_type}"; } $lpUserItemCache = new LP_User_Items_Cache(); @@ -325,11 +315,11 @@ public function get_meta_model_from_key( string $key ) { * * @return false|string * @since 4.2.5 - * @version 1.0.2 + * @version 1.0.3 */ public function get_meta_value_from_key( string $key, $default_value = false, bool $get_extra = false ) { if ( $this->meta_data instanceof stdClass && isset( $this->meta_data->{$key} ) ) { - return $this->meta_data->{$key}; + return maybe_unserialize( $this->meta_data->{$key} ); } $user_item_metadata = $this->get_meta_model_from_key( $key ); @@ -344,7 +334,7 @@ public function get_meta_value_from_key( string $key, $default_value = false, bo $data = $user_item_metadata->meta_value; } - $this->meta_data->{$key} = $data; + $this->meta_data->{$key} = maybe_unserialize( $data ); } else { $data = $default_value; } diff --git a/inc/Models/UserItems/UserQuizModel.php b/inc/Models/UserItems/UserQuizModel.php index fa29dcaab..f93cc6b33 100644 --- a/inc/Models/UserItems/UserQuizModel.php +++ b/inc/Models/UserItems/UserQuizModel.php @@ -4,17 +4,22 @@ * Class UserItemModel * * @package LearnPress/Classes - * @version 1.0.0 + * @version 1.0.1 * @since 4.2.5 */ namespace LearnPress\Models\UserItems; use Exception; +use LearnPress\Models\CourseModel; +use LearnPress\Models\QuizPostModel; use LearnPress\Models\UserItemMeta\UserItemMetaModel; use LearnPress\Models\UserItemMeta\UserQuizMetaModel; +use LearnPress\Models\UserModel; +use LearnPressAssignment\Models\AssignmentPostModel; use LP_Course; use LP_Datetime; +use LP_Helper; use LP_Question; use LP_Quiz; use LP_Quiz_CURD; @@ -36,42 +41,46 @@ class UserQuizModel extends UserItemModel { * @var string */ public $ref_type = LP_COURSE_CPT; - /** - * @var LP_User $user not column in DB - */ - public $user; - /** - * @var LP_Course $course not column in DB - */ - public $course; - /** - * @var LP_Quiz $quiz not column in DB - */ - public $quiz; - /** - * @var UserCourseModel $user_course not column in DB - */ - public $user_course; public function __construct( $data = null ) { parent::__construct( $data ); if ( $data ) { - $this->get_quiz_model(); + $this->get_quiz_post_model(); } } /** * Get quiz model * - * @return bool|LP_Quiz + * @return bool|QuizPostModel + * @since 4.2.5 + * @version 1.0.1 */ - public function get_quiz_model() { - if ( empty( $this->quiz ) ) { - $this->quiz = learn_press_get_quiz( $this->item_id ); - } + public function get_quiz_post_model() { + return QuizPostModel::find( $this->item_id, true ); + } - return $this->quiz; + /** + * Get course model + * + * @return false|CourseModel + * @since 4.2.7.6 + * @version 1.0.0 + */ + public function get_course_model() { + return CourseModel::find( $this->ref_id, true ); + } + + /** + * Get user course model + * + * @return false|UserCourseModel + * @since 4.2.7.6 + * @version 1.0.0 + */ + public function get_user_course_model() { + return UserCourseModel::find( $this->user_id, $this->ref_id, true ); } /** @@ -106,35 +115,29 @@ public function get_question_ids( string $context = 'display' ): array { } /** - * Get Timestamp remaining when user doing quiz + * Get time remaining when user doing assignment. * - * @return int - * @throws Exception - * @version 1.0.1 - * @sicne 4.1.4.1 + * @return int seconds + * @since 4.2.7.6 + * @version 1.0.0 */ - public function get_timestamp_remaining(): int { - $timestamp_remaining = 0; - if ( empty( $this->get_user_item_id() ) || $this->status !== LP_ITEM_STARTED ) { - return $timestamp_remaining; - } - - $quiz = $this->get_quiz_model(); - if ( empty( $quiz ) ) { - return $timestamp_remaining; + public function get_time_remaining(): int { + $time_remaining = 0; + $quizPostModel = $this->get_quiz_post_model(); + if ( ! $quizPostModel instanceof QuizPostModel ) { + return $time_remaining; } - $duration = $quiz->get_duration()->get() . ' second'; - $course_start_time = $this->start_time; - $timestamp_expire = strtotime( $course_start_time . ' +' . $duration ); - $timestamp_current = time(); - $timestamp_remaining = $timestamp_expire - $timestamp_current; + $duration = $quizPostModel->get_duration(); + $start_time_stamp = strtotime( $this->get_start_time() ); + $expire_time_stamp = strtotime( '+' . $duration, $start_time_stamp ); + $time_remaining = $expire_time_stamp - time(); - if ( $timestamp_remaining < 0 ) { - $timestamp_remaining = 0; + if ( $time_remaining < 0 ) { + $time_remaining = 0; } - return apply_filters( 'learn-press/user-course-quiz/timestamp_remaining', $timestamp_remaining, $this, $quiz ); + return $time_remaining; } /** @@ -207,7 +210,7 @@ public function retake() { // Hook old - random quiz using. do_action( 'learn-press/user/quiz-retried', $this->item_id, $this->ref_id, $this->user_id ); // Hook new - do_action( 'learn-press/user/quiz/retried', $this ); + do_action( 'learn-press/user/quiz/retake', $this ); } /** @@ -219,23 +222,23 @@ public function retake() { * return bool|WP_Error * * @since 4.2.5 - * @version 1.0.1 + * @version 1.0.2 */ public function check_can_start() { $can_start = true; - $this->user = $this->get_user_model(); - if ( ! $this->user instanceof LP_User ) { + $userModel = $this->get_user_model(); + if ( ! $userModel instanceof UserModel ) { $can_start = new WP_Error( 'user_invalid', __( 'User is invalid.', 'learnpress' ) ); } - $this->quiz = $this->get_quiz_model(); - if ( empty( $this->quiz ) ) { + $quizPostModel = $this->get_quiz_post_model(); + if ( ! $quizPostModel instanceof QuizPostModel ) { $can_start = new WP_Error( 'quiz_invalid', __( 'Quiz is invalid.', 'learnpress' ) ); } - $this->course = learn_press_get_course( $this->ref_id ); - if ( empty( $this->course ) ) { + $courseModel = $this->get_course_model(); + if ( ! $courseModel instanceof CourseModel ) { $can_start = new WP_Error( 'course_invalid', __( 'Course is invalid.', 'learnpress' ) ); } @@ -252,7 +255,7 @@ public function check_can_start() { } // Check user, course of quiz is enrolled. - $userCourseModel = UserCourseModel::find( $this->user_id, $this->course->get_id(), true ); + $userCourseModel = $this->get_user_course_model(); if ( ! $userCourseModel instanceof UserCourseModel || $userCourseModel->graduation !== LP_GRADUATION_IN_PROGRESS ) { $can_start = new WP_Error( 'not_errol_course', __( 'Please enroll in the course before starting the quiz.', 'learnpress' ) ); @@ -268,7 +271,8 @@ public function check_can_start() { try { $can_start = apply_filters( 'learn-press/can-start-quiz', - $can_start, $userQuizModel->item_id, + $can_start, + $userQuizModel->item_id, $userQuizModel->ref_id, $userQuizModel->user_id ); @@ -289,61 +293,80 @@ public function check_can_start() { * Check can retake quiz. * * @throws Exception + * @since 4.2.5 + * @version 1.0.1 */ public function check_can_retake() { $can_retake = true; - $this->user = $this->get_user_model(); - if ( ! $this->user instanceof LP_User ) { + $userModel = $this->get_user_model(); + if ( ! $userModel instanceof UserModel ) { $can_retake = new WP_Error( 'user_invalid', __( 'User is invalid.', 'learnpress' ) ); } - $this->quiz = $this->get_quiz_model(); - if ( empty( $this->quiz ) ) { + $quizPostModel = $this->get_quiz_post_model(); + if ( ! $quizPostModel instanceof QuizPostModel ) { $can_retake = new WP_Error( 'quiz_invalid', __( 'Quiz is invalid.', 'learnpress' ) ); } - $this->course = learn_press_get_course( $this->ref_id ); - if ( empty( $this->course ) ) { + $course = CourseModel::find( $this->ref_id, true ); + if ( ! $course instanceof CourseModel ) { $can_retake = new WP_Error( 'course_invalid', __( 'Course is invalid.', 'learnpress' ) ); } // Check user, course of quiz is enrolled. - $user_course = $this->user->get_course_attend( $this->ref_id ); - $this->user_course = $user_course; - if ( ! $user_course instanceof UserCourseModel - || $user_course->graduation !== LP_COURSE_GRADUATION_IN_PROGRESS ) { - $can_retake = new WP_Error( 'not_errol_course', __( 'Please enroll in the course before starting the quiz.', 'learnpress' ) ); - } elseif ( $user_course->status === LP_COURSE_FINISHED ) { - $can_retake = new WP_Error( 'finished_course', __( 'You have already finished the course of this quiz.', 'learnpress' ) ); + $userCourseModel = $this->get_user_course_model(); + if ( ! $userCourseModel instanceof UserCourseModel + || $userCourseModel->get_graduation() !== LP_COURSE_GRADUATION_IN_PROGRESS ) { + $can_retake = new WP_Error( + 'not_errol_course', + __( 'Please enroll in the course before starting the quiz.', 'clearness' ) + ); + } elseif ( $userCourseModel->get_status() === LP_COURSE_FINISHED ) { + $can_retake = new WP_Error( + 'finished_course', + __( 'You have already finished the course of this quiz.', 'learnpress' ) + ); } // Check user quiz start and completed?. - $user_quiz_exists = $this->user_course->get_item_attend( $this->item_id, $this->item_type ); - if ( ! $user_quiz_exists instanceof UserQuizModel ) { - $can_retake = new WP_Error( 'not_started_quiz', __( 'You have not start the quiz.', 'learnpress' ) ); - } elseif ( $user_quiz_exists->status !== LP_ITEM_COMPLETED ) { + if ( $this->get_status() !== LP_ITEM_COMPLETED ) { $can_retake = new WP_Error( 'not_completed_quiz', __( 'You have not completed the quiz.', 'learnpress' ) ); } // Check retaken count. - $retake_config = get_post_meta( $this->item_id, '_lp_retake_count', true ); - if ( $retake_config !== '-1' ) { - $number_retaken = absint( $this->get_meta_value_from_key( UserQuizMetaModel::KEY_RETAKEN_COUNT, 0 ) ); - if ( $number_retaken >= $retake_config ) { - $can_retake = new WP_Error( 'exceed_retaken_count', __( 'You have exceeded the number of retakes.', 'learnpress' ) ); - } + $retake_max = $quizPostModel->get_retake_count(); + if ( $retake_max != -1 && $this->get_remaining_retake() === 0 ) { + $can_retake = new WP_Error( 'exceed_retaken_count', __( 'You have exceeded the number of retakes.', 'learnpress' ) ); } // Hook can retake quiz return apply_filters( - 'learn-press/can-retake-quiz', + 'learn-press/user/can-retake-quiz', $can_retake, - $user_course, $this ); } + /** + * Get number retake remaining. + * + * @return int + * @since 4.2.7.6 + * @version 1.0.0 + */ + public function get_remaining_retake(): int { + $quizPostModel = $this->get_quiz_post_model(); + $retake_max = $quizPostModel->get_retake_count(); + $retaken_count = $this->get_retaken_count(); + $remaining_retake = $retake_max - $retaken_count; + if ( $remaining_retake <= 0 ) { + $remaining_retake = 0; + } + + return $remaining_retake; + } + /** * Get all attempts of a quiz. * @@ -381,7 +404,7 @@ public function get_attempts( $limit = 3 ) { * @return integer */ public function get_retaken_count(): int { - return absint( $this->get_meta_value_from_key( UserQuizMetaModel::KEY_RETAKEN_COUNT ) ); + return absint( $this->get_meta_value_from_key( UserQuizMetaModel::KEY_RETAKEN_COUNT, 0 ) ); } /** @@ -391,7 +414,7 @@ public function get_retaken_count(): int { * @return array */ public function get_checked_questions(): array { - $value_str = $this->get_meta_value_from_key( UserQuizMetaModel::KEY_QUESTION_CHECKED ); + $value_str = $this->get_meta_value_from_key( UserQuizMetaModel::KEY_QUESTION_CHECKED, [] ); $value = maybe_unserialize( $value_str ); if ( $value ) { @@ -451,9 +474,8 @@ public function get_result(): array { * @param array $answered [question_id => answered, 'instant_check' => 0] * * @return array - * @author tungnx - * @since 4.1.4.1 - * @version 1.0.1 + * @since 4.2.5 + * @version 1.0.0 */ private function calculate_quiz_result( array $answered ): array { $result = array( @@ -473,16 +495,16 @@ private function calculate_quiz_result( array $answered ): array { 'pass' => 0, ); - $quiz = $this->quiz; - if ( empty( $quiz ) ) { + $quizPostModel = $this->get_quiz_post_model(); + if ( ! $quizPostModel instanceof QuizPostModel ) { return $result; } - $question_ids = $quiz->get_question_ids(); - $result['mark'] = $quiz->get_mark(); - $result['question_count'] = $quiz->count_questions(); + $question_ids = $quizPostModel->get_question_ids(); + $result['mark'] = $quizPostModel->get_mark(); + $result['question_count'] = $quizPostModel->count_questions(); $result['time_spend'] = $this->get_time_spend(); - $result['passing_grade'] = $quiz->get_passing_grade(); + $result['passing_grade'] = $quizPostModel->get_passing_grade(); $checked_questions = $this->get_checked_questions(); foreach ( $question_ids as $question_id ) { @@ -504,7 +526,7 @@ private function calculate_quiz_result( array $answered ): array { $result['questions'][ $question_id ]['correct'] = true; $result['questions'][ $question_id ]['mark'] = $point; } else { - if ( $quiz->get_negative_marking() ) { + if ( $quizPostModel->has_negative_marking() ) { $result['user_mark'] -= $point; $result['minus_point'] += $point; } @@ -514,7 +536,7 @@ private function calculate_quiz_result( array $answered ): array { $result['questions'][ $question_id ]['mark'] = 0; } } elseif ( ! array_key_exists( 'instant_check', $answered ) ) { // User skip question - if ( $quiz->get_minus_skip_questions() ) { + if ( $quizPostModel->has_minus_skip_questions() ) { $result['user_mark'] -= $point; $result['minus_point'] += $point; } @@ -524,14 +546,12 @@ private function calculate_quiz_result( array $answered ): array { $result['questions'][ $question_id ]['mark'] = 0; } - $can_review_quiz = get_post_meta( $quiz->get_id(), '_lp_review', true ) === 'yes'; - if ( $can_review_quiz && ! array_key_exists( 'instant_check', $answered ) ) { - + if ( $quizPostModel->has_instant_check() && ! array_key_exists( 'instant_check', $answered ) ) { $result['questions'][ $question_id ]['explanation'] = $question->get_explanation(); $result['questions'][ $question_id ]['options'] = learn_press_get_question_options_for_js( $question, array( - 'include_is_true' => in_array( $question_id, $checked_questions ) || get_post_meta( $quiz->get_id(), '_lp_show_correct_review', true ) === 'yes', + 'include_is_true' => in_array( $question_id, $checked_questions ) || $quizPostModel->has_show_correct_review(), 'answer' => $answered[ $question_id ] ?? '', ) ); @@ -546,7 +566,7 @@ private function calculate_quiz_result( array $answered ): array { $result['result'] = round( $result['user_mark'] * 100 / $result['mark'], 2, PHP_ROUND_HALF_DOWN ); } - $passing_grade = $quiz->get_data( 'passing_grade', 0 ); + $passing_grade = $quizPostModel->get_passing_grade(); if ( $result['result'] >= $passing_grade ) { $result['pass'] = 1; } else { @@ -570,4 +590,37 @@ public function get_time_spend(): string { $duration = new LP_Datetime( $interval ); return $duration->format( 'H:i:s' ); } + + /** + * Get history user did quiz. + * + * @param int $limit + * + * @return array + * @version 1.0.0 + * @since 4.2.7.6 + */ + public function get_history( int $limit = 3 ): array { + $history = array(); + + try { + $results = LP_User_Items_Result_DB::instance()->get_results( $this->get_user_item_id(), $limit, true ); + + if ( ! empty( $results ) ) { + foreach ( $results as $result ) { + if ( $result && is_string( $result ) ) { + $result = LP_Helper::json_decode( $result ); + + unset( $result->questions ); + + $history[] = $result; + } + } + } + } catch ( Throwable $e ) { + error_log( __METHOD__ . ': ' . $e->getMessage() ); + } + + return $history; + } } diff --git a/inc/question/class-lp-question-answers.php b/inc/question/class-lp-question-answers.php index 91b880a82..836f1a7db 100644 --- a/inc/question/class-lp-question-answers.php +++ b/inc/question/class-lp-question-answers.php @@ -259,7 +259,10 @@ protected function _shuffle() { LP_Helper::shuffle_assoc( $this->_answers ); } - public function get_class( $more = '' ) { + /** + * @deprecated 4.2.7.6 + */ + /*public function get_class( $more = '' ) { $classes = array( 'answer-options' ); if ( $more && is_string( $more ) ) { $more = explode( ' ', $more ); @@ -276,7 +279,7 @@ public function get_class( $more = '' ) { $classes = LP_Helper::sanitize_array( $classes ); return apply_filters( 'learn-press/question/answer-options/classes', $classes, $this ); - } + }*/ /** * Return an answer from a position. @@ -301,10 +304,10 @@ public function get_answer_at( $at ) { * @param string $more * @deprecated 4.1.7.3 */ - public function answers_class( $more = '' ) { + /*public function answers_class( $more = '' ) { $classes = $this->get_class( $more ); echo 'class="' . join( ' ', $classes ) . '"'; - } + }*/ } } @@ -384,50 +387,51 @@ public function get_id() { * @param string $more * * @return array - */ - public function get_class( $more = '' ) { - $classes = array( 'answer-option' ); - if ( $more && is_string( $more ) ) { - $more = explode( ' ', $more ); - } - - if ( $more && is_array( $more ) ) { - $classes = array_merge( $classes, $more ); - } - $is_checked = $this->is_checked(); - $is_true = $this->is_true(); - - if ( $this->get_question()->show_correct_answers() === 'yes' ) { - - if ( $is_true ) { - $classes[] = 'answer-correct'; - } - - if ( $is_checked ) { - $classes[] = 'answer-selected'; - } - - if ( $is_checked && $is_true ) { - $classes[] = 'answered-correct'; - } elseif ( $is_checked && ! $is_true ) { - $classes[] = 'answered-wrong'; - } elseif ( ! $is_checked && $is_true ) { - $classes[] = 'answered-wrong'; - } - } - /*elseif ( learn_press_is_review_questions() ) { - if ( $is_checked && $is_true ) { - $classes[] = 'answered-correct'; - } elseif ( $is_checked && ! $is_true ) { - $classes[] = 'answered-wrong'; - } - }*/ - - // sanitize unwanted classes - $classes = LP_Helper::sanitize_array( $classes ); - - return apply_filters( 'learn-press/question/answer-option/classes', $classes, $this ); - } + * @deprecated 4.2.7.6 + */ +// public function get_class( $more = '' ) { +// $classes = array( 'answer-option' ); +// if ( $more && is_string( $more ) ) { +// $more = explode( ' ', $more ); +// } +// +// if ( $more && is_array( $more ) ) { +// $classes = array_merge( $classes, $more ); +// } +// $is_checked = $this->is_checked(); +// $is_true = $this->is_true(); +// +// if ( $this->get_question()->show_correct_answers() === 'yes' ) { +// +// if ( $is_true ) { +// $classes[] = 'answer-correct'; +// } +// +// if ( $is_checked ) { +// $classes[] = 'answer-selected'; +// } +// +// if ( $is_checked && $is_true ) { +// $classes[] = 'answered-correct'; +// } elseif ( $is_checked && ! $is_true ) { +// $classes[] = 'answered-wrong'; +// } elseif ( ! $is_checked && $is_true ) { +// $classes[] = 'answered-wrong'; +// } +// } +// /*elseif ( learn_press_is_review_questions() ) { +// if ( $is_checked && $is_true ) { +// $classes[] = 'answered-correct'; +// } elseif ( $is_checked && ! $is_true ) { +// $classes[] = 'answered-wrong'; +// } +// }*/ +// +// // sanitize unwanted classes +// $classes = LP_Helper::sanitize_array( $classes ); +// +// return apply_filters( 'learn-press/question/answer-option/classes', $classes, $this ); +// } /** * @param bool $echo @@ -492,10 +496,11 @@ public function get_question_id() { * Print class attribute * * @param string $more + * @deprecated 4.2.7.6 */ - public function option_class( $more = '' ) { + /*public function option_class( $more = '' ) { echo 'class="' . join( ' ', $this->get_class( $more ) ) . '"'; - } + }*/ public function get_data() { return $this->_data; diff --git a/inc/rest-api/v1/frontend/class-lp-rest-users-controller.php b/inc/rest-api/v1/frontend/class-lp-rest-users-controller.php index 2b8bf2f30..0119e2997 100644 --- a/inc/rest-api/v1/frontend/class-lp-rest-users-controller.php +++ b/inc/rest-api/v1/frontend/class-lp-rest-users-controller.php @@ -227,7 +227,7 @@ public function start_quiz( WP_REST_Request $request ): WP_REST_Response { $show_correct_review = $quiz->get_show_correct_review(); $question_ids = $quiz->get_question_ids(); $status = $user_quiz->get_status(); - $time_remaining = $user_quiz->get_timestamp_remaining(); + $time_remaining = $user_quiz->get_time_remaining(); $questions = learn_press_rest_prepare_user_questions( $question_ids, diff --git a/inc/templates/class-lp-template-course.php b/inc/templates/class-lp-template-course.php index 5794e6a8d..7004077c2 100644 --- a/inc/templates/class-lp-template-course.php +++ b/inc/templates/class-lp-template-course.php @@ -714,11 +714,11 @@ public function item_lesson_content() { /** * @deprecated 4.1.7.2 */ - public function item_quiz_content() { + /*public function item_quiz_content() { $item = LP_Global::course_item(); learn_press_get_template( 'content-quiz/js.php' ); - } + }*/ /** * @deprecated 4.1.7.2 diff --git a/inc/user/lp-user-functions.php b/inc/user/lp-user-functions.php index 76ebbe4df..e8bb52e6b 100644 --- a/inc/user/lp-user-functions.php +++ b/inc/user/lp-user-functions.php @@ -1,5 +1,6 @@ item_type, ] ); + // Clear cache userItemModel + $userItemModel = UserItemModel::find_user_item( + $updated_item->user_id, + $updated_item->item_id, + $updated_item->item_type, + $updated_item->ref_id, + $updated_item->ref_type, + true + ); + $userItemModel->clean_caches(); } do_action( 'learn-press/updated-user-item-field', $updated_item ); diff --git a/learnpress.php b/learnpress.php index dc5ea67ac..2242fb491 100644 --- a/learnpress.php +++ b/learnpress.php @@ -4,7 +4,7 @@ * Plugin URI: http://thimpress.com/learnpress * Description: LearnPress is a WordPress complete solution for creating a Learning Management System (LMS). It can help you to create courses, lessons and quizzes. * Author: ThimPress - * Version: 4.2.7.5 + * Version: 4.2.7.6-beta.1 * Author URI: http://thimpress.com * Requires at least: 6.0 * Requires PHP: 7.0 @@ -266,6 +266,7 @@ private function include_files_global() { include_once 'inc/Filters/class-lp-question-filter.php'; include_once 'inc/Filters/class-lp-user-items-filter.php'; include_once 'inc/Filters/class-lp-user-item-meta-filter.php'; + include_once 'inc/Filters/class-lp-quiz-filter.php'; include_once 'inc/Filters/class-lp-quiz-questions-filter.php'; include_once 'inc/Filters/class-lp-question-answers-filter.php'; include_once 'inc/Filters/class-lp-question-answermeta-filter.php'; diff --git a/templates/content-quiz/js.php b/templates/content-quiz/js.php index b74316dd7..bed64d15e 100644 --- a/templates/content-quiz/js.php +++ b/templates/content-quiz/js.php @@ -1,67 +1,79 @@ count_questions(); +$quizPostModel = QuizPostModel::find( $quiz->get_id(), true ); +if ( ! $quizPostModel instanceof QuizPostModel ) { + return; +} + +$total_question = $quizPostModel->count_questions(); $questions = array(); $show_check = $quiz->get_instant_check(); $show_correct_review = $quiz->get_show_correct_review(); $question_ids = $quiz->get_question_ids(); $user_js = array(); - -$user_course = $user->get_course_attend( $course->get_id() ); -$user_quiz = $user_course ? $user_course->get_item_attend( $quiz->get_id(), LP_QUIZ_CPT ) : false; $answered = array(); $status = ''; $checked_questions = array(); $crypto_js_aes = false; -$editable = $user->is_admin() || get_post_field( $user->is_author_of( $course->get_id() ) ); -$max_retrying = learn_press_get_quiz_max_retrying( $quiz->get_id(), $course->get_id() ); +$user = learn_press_get_current_user(); +$editable = $user->is_admin() || get_post_field( $user->is_author_of( $courseModel->get_id() ) ); +$max_retrying = learn_press_get_quiz_max_retrying( $quiz->get_id(), $courseModel->get_id() ); $quiz_results = null; -if ( $user_quiz ) { - $status = $user_quiz->get_status(); - $quiz_results = $user_quiz->get_result(); - $checked_questions = $user_quiz->get_checked_questions(); - - $user_js = array( - 'status' => $status, - 'attempts' => $user_quiz->get_attempts(), - 'checked_questions' => $checked_questions, - 'start_time' => $user_quiz->start_time, - 'retaken' => $user_quiz->get_retaken_count(), +if ( $userModel ) { + $userQuizModel = UserQuizModel::find_user_item( + $userModel->get_id(), + $quiz->get_id(), + LP_QUIZ_CPT, + $courseModel->get_id(), + LP_COURSE_CPT, + true ); - try { - $time_remaining = $user_quiz->get_timestamp_remaining(); - } catch ( Exception $e ) { - $time_remaining = 0; - } - $user_js['total_time'] = $time_remaining; - - if ( $quiz_results ) { - $user_js['results'] = $quiz_results; - $answered = $quiz_results['questions']; + if ( $userQuizModel instanceof UserQuizModel ) { + $status = $userQuizModel->get_status(); + $quiz_results = $userQuizModel->get_result(); + $user_js = array( + 'status' => $status, + 'attempts' => $userQuizModel->get_history(), + 'checked_questions' => $userQuizModel->get_checked_questions(), + 'start_time' => $userQuizModel->get_start_time(), + 'retaken' => $userQuizModel->get_retaken_count(), + 'total_time' => $userQuizModel->get_time_remaining(), + 'results' => $quiz_results, + ); + + $answered = $quiz_results['questions']; } } else { // Display quiz content. @@ -85,7 +97,7 @@ $duration = $quiz->get_duration(); $js = array( - 'course_id' => $course->get_id(), + 'course_id' => $courseModel->get_id(), 'nonce' => wp_create_nonce( sprintf( 'user-quiz-%d', get_current_user_id() ) ), 'id' => $quiz->get_id(), 'title' => $quiz->get_title(), @@ -118,11 +130,12 @@ 'quiz_description' => $quiz->get_content(), ); -LP_Helper::print_inline_script_tag( 'lp_quiz_js_data', [ 'data' => $js ] ); - $js = array_merge( $js, $user_js ); -if ( $total_question || $user_quiz ) : +// To show data debug. +LP_Helper::print_inline_script_tag( 'lp_quiz_js_data', [ 'data' => $js ] ); + +if ( $total_question ) { ?>
@@ -138,10 +151,7 @@ } } ); - - - +}