From ee5fd8b39fdce764b6ea86074d566b2e27af120d Mon Sep 17 00:00:00 2001 From: b-l-i-n-d Date: Thu, 23 Apr 2026 15:06:43 +0600 Subject: [PATCH 1/7] fix(quiz): persist attempt question ids --- classes/Utils.php | 185 +++++++++++++++++- .../dashboard/quiz-attempts/quiz-reviews.php | 2 +- .../components/quiz/attempt-details.php | 2 +- .../attempt-details/questions-sidebar.php | 4 +- 4 files changed, 188 insertions(+), 5 deletions(-) diff --git a/classes/Utils.php b/classes/Utils.php index 6b6a719aed..fb060ebea7 100644 --- a/classes/Utils.php +++ b/classes/Utils.php @@ -5469,6 +5469,172 @@ public function max_questions_for_take_quiz( $quiz_id ) { return $max_questions; } + /** + * Normalize question ids. + * + * @since 4.2.0 + * + * @param array $question_ids Question ids. + * + * @return array + */ + private function normalize_question_ids( array $question_ids ): array { + $question_ids = array_map( 'intval', $question_ids ); + $question_ids = array_filter( + $question_ids, + function ( int $question_id ) { + return $question_id > 0; + } + ); + + return array_values( array_unique( $question_ids ) ); + } + + /** + * Get questions by question ids while preserving order. + * + * @since 4.2.0 + * + * @param array $question_ids Question ids. + * + * @return array + */ + public function get_questions_by_ids( array $question_ids ): array { + $question_ids = $this->normalize_question_ids( $question_ids ); + + if ( empty( $question_ids ) ) { + return array(); + } + + $questions = QueryHelper::get_all( + 'tutor_quiz_questions', + array( + 'question_id' => array( 'IN', $question_ids ), + ), + 'question_order', + -1, + 'ASC' + ); + + $questions_by_id = array(); + foreach ( $questions as $question ) { + $question->question_title = stripslashes( $question->question_title ); + $question->question_description = stripslashes( $question->question_description ); + $question->answer_explanation = stripslashes( $question->answer_explanation ); + $questions_by_id[ (int) $question->question_id ] = $question; + } + + $ordered_questions = array(); + foreach ( $question_ids as $question_id ) { + if ( isset( $questions_by_id[ $question_id ] ) ) { + $ordered_questions[] = $questions_by_id[ $question_id ]; + } + } + + return $ordered_questions; + } + + /** + * Get stored question ids for an attempt. + * + * @since 4.2.0 + * + * @param int $attempt_id Attempt id. + * + * @return array + */ + public function get_attempt_question_ids( int $attempt_id ): array { + $attempt = $this->get_attempt( $attempt_id ); + + if ( ! $attempt ) { + return array(); + } + + $attempt_info = maybe_unserialize( $attempt->attempt_info ?? '' ); + if ( is_array( $attempt_info ) && ! empty( $attempt_info['attempt_question_ids'] ) && is_array( $attempt_info['attempt_question_ids'] ) ) { + return $this->normalize_question_ids( $attempt_info['attempt_question_ids'] ); + } + + $attempt_answers = QuizModel::get_quiz_answers_by_attempt_id( $attempt_id ); + if ( ! is_array( $attempt_answers ) || empty( $attempt_answers ) ) { + return array(); + } + + $question_ids = array_map( + function ( $attempt_answer ) { + return (int) ( $attempt_answer->question_id ?? 0 ); + }, + $attempt_answers + ); + + return $this->normalize_question_ids( $question_ids ); + } + + /** + * Persist question ids for an attempt. + * + * @since 4.2.0 + * + * @param int $attempt_id Attempt id. + * @param array $question_ids Question ids. + * + * @return bool + */ + public function persist_attempt_question_ids( int $attempt_id, array $question_ids ): bool { + $question_ids = $this->normalize_question_ids( $question_ids ); + + if ( $attempt_id <= 0 || empty( $question_ids ) ) { + return false; + } + + $attempt = $this->get_attempt( $attempt_id ); + if ( ! $attempt ) { + return false; + } + + $attempt_info = maybe_unserialize( $attempt->attempt_info ?? '' ); + $attempt_info = is_array( $attempt_info ) ? $attempt_info : array(); + + $stored_question_ids = isset( $attempt_info['attempt_question_ids'] ) && is_array( $attempt_info['attempt_question_ids'] ) + ? $this->normalize_question_ids( $attempt_info['attempt_question_ids'] ) + : array(); + + if ( $stored_question_ids === $question_ids ) { + return true; + } + + $attempt_info['attempt_question_ids'] = $question_ids; + + return (bool) Quiz::update_attempt_info( $attempt_id, maybe_serialize( $attempt_info ) ); + } + + /** + * Get questions used in an attempt. + * + * @since 4.2.0 + * + * @param int $attempt_id Attempt id. + * + * @return array|bool + */ + public function get_questions_by_attempt( int $attempt_id ) { + $attempt = $this->get_attempt( $attempt_id ); + + if ( ! $attempt ) { + return false; + } + + $question_ids = $this->get_attempt_question_ids( $attempt_id ); + if ( ! empty( $question_ids ) ) { + $questions = $this->get_questions_by_ids( $question_ids ); + if ( ! empty( $questions ) ) { + return $questions; + } + } + + return $this->get_questions_by_quiz( (int) $attempt->quiz_id ); + } + /** * Get single quiz attempt * @@ -5576,12 +5742,17 @@ public function get_random_questions_by_quiz( $quiz_id = 0 ) { $quiz_id = $this->get_post_id( $quiz_id ); $attempt = $this->is_started_quiz( $quiz_id ); - $total_questions = (int) $attempt->total_questions; if ( ! $attempt ) { return false; } - $questions_order = $this->get_quiz_option( get_the_ID(), 'questions_order', 'rand' ); + $total_questions = (int) $attempt->total_questions; + $stored_question_ids = $this->get_attempt_question_ids( (int) $attempt->attempt_id ); + if ( ! empty( $stored_question_ids ) ) { + return $this->get_questions_by_ids( $stored_question_ids ); + } + + $questions_order = $this->get_quiz_option( $quiz_id, 'questions_order', 'rand' ); $order_by = ''; if ( 'rand' === $questions_order ) { @@ -5611,6 +5782,16 @@ public function get_random_questions_by_quiz( $quiz_id = 0 ) { ) ); + $this->persist_attempt_question_ids( + (int) $attempt->attempt_id, + array_map( + function ( $question ) { + return (int) ( $question->question_id ?? 0 ); + }, + is_array( $questions ) ? $questions : array() + ) + ); + return $questions; } diff --git a/templates/dashboard/quiz-attempts/quiz-reviews.php b/templates/dashboard/quiz-attempts/quiz-reviews.php index 93b7a4c7cc..1c25044c72 100644 --- a/templates/dashboard/quiz-attempts/quiz-reviews.php +++ b/templates/dashboard/quiz-attempts/quiz-reviews.php @@ -36,7 +36,7 @@ ); $attempt_answers_map = array(); -$questions = tutor_utils()->get_questions_by_quiz( $quiz_id ); +$questions = tutor_utils()->get_questions_by_attempt( $attempt_id ); $attempt_answers = QuizModel::get_quiz_answers_by_attempt_id( $attempt_id ); if ( is_array( $attempt_answers ) ) { diff --git a/templates/shared/components/quiz/attempt-details.php b/templates/shared/components/quiz/attempt-details.php index 5e28110089..14a349717e 100644 --- a/templates/shared/components/quiz/attempt-details.php +++ b/templates/shared/components/quiz/attempt-details.php @@ -69,7 +69,7 @@ $quiz_id = (int) ( $attempt_data->quiz_id ?? 0 ); } -$questions = tutor_utils()->get_questions_by_quiz( $quiz_id ); +$questions = tutor_utils()->get_questions_by_attempt( (int) $attempt_data->attempt_id ); $course_contents = tutor_utils()->get_course_prev_next_contents_by_id( $quiz_id ); ?>
diff --git a/templates/shared/components/quiz/attempt-details/questions-sidebar.php b/templates/shared/components/quiz/attempt-details/questions-sidebar.php index 66d50b6e0f..db037d594c 100644 --- a/templates/shared/components/quiz/attempt-details/questions-sidebar.php +++ b/templates/shared/components/quiz/attempt-details/questions-sidebar.php @@ -22,7 +22,9 @@ return; } -$questions = tutor_utils()->get_questions_by_quiz( $quiz_id ); +$questions = isset( $attempt_data ) && is_object( $attempt_data ) && ! empty( $attempt_data->attempt_id ) + ? tutor_utils()->get_questions_by_attempt( (int) $attempt_data->attempt_id ) + : tutor_utils()->get_questions_by_quiz( $quiz_id ); $question_status_map = array(); $default_item_status = ( isset( $attempt_data ) && is_object( $attempt_data ) ) ? 'incorrect' : ''; $status_priority = array( From 50e3d63cc3821c6655008cde58b8e073f58e9e2e Mon Sep 17 00:00:00 2001 From: b-l-i-n-d Date: Thu, 23 Apr 2026 15:09:42 +0600 Subject: [PATCH 2/7] chore: Resolve version typo --- classes/Utils.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/classes/Utils.php b/classes/Utils.php index fb060ebea7..4587256927 100644 --- a/classes/Utils.php +++ b/classes/Utils.php @@ -5472,7 +5472,7 @@ public function max_questions_for_take_quiz( $quiz_id ) { /** * Normalize question ids. * - * @since 4.2.0 + * @since 4.0.0 * * @param array $question_ids Question ids. * @@ -5493,7 +5493,7 @@ function ( int $question_id ) { /** * Get questions by question ids while preserving order. * - * @since 4.2.0 + * @since 4.0.0 * * @param array $question_ids Question ids. * @@ -5537,7 +5537,7 @@ public function get_questions_by_ids( array $question_ids ): array { /** * Get stored question ids for an attempt. * - * @since 4.2.0 + * @since 4.0.0 * * @param int $attempt_id Attempt id. * @@ -5573,7 +5573,7 @@ function ( $attempt_answer ) { /** * Persist question ids for an attempt. * - * @since 4.2.0 + * @since 4.0.0 * * @param int $attempt_id Attempt id. * @param array $question_ids Question ids. @@ -5611,7 +5611,7 @@ public function persist_attempt_question_ids( int $attempt_id, array $question_i /** * Get questions used in an attempt. * - * @since 4.2.0 + * @since 4.0.0 * * @param int $attempt_id Attempt id. * From 98c17a35473862e1a360e66d9174835d4d93fa5d Mon Sep 17 00:00:00 2001 From: b-l-i-n-d Date: Sun, 3 May 2026 15:52:26 +0600 Subject: [PATCH 3/7] fix(quiz): update attempt details templates --- classes/Utils.php | 182 +----------------- .../dashboard/quiz-attempts/quiz-reviews.php | 13 +- .../components/quiz/attempt-details.php | 4 +- .../attempt-details/questions-sidebar.php | 33 ++-- .../quiz/attempt-details/review-answers.php | 28 +-- 5 files changed, 26 insertions(+), 234 deletions(-) diff --git a/classes/Utils.php b/classes/Utils.php index 6fe936dd16..5eec4a7edf 100644 --- a/classes/Utils.php +++ b/classes/Utils.php @@ -5247,171 +5247,6 @@ public function max_questions_for_take_quiz( $quiz_id ) { return $max_questions; } - /** - * Normalize question ids. - * - * @since 4.0.0 - * - * @param array $question_ids Question ids. - * - * @return array - */ - private function normalize_question_ids( array $question_ids ): array { - $question_ids = array_map( 'intval', $question_ids ); - $question_ids = array_filter( - $question_ids, - function ( int $question_id ) { - return $question_id > 0; - } - ); - - return array_values( array_unique( $question_ids ) ); - } - - /** - * Get questions by question ids while preserving order. - * - * @since 4.0.0 - * - * @param array $question_ids Question ids. - * - * @return array - */ - public function get_questions_by_ids( array $question_ids ): array { - $question_ids = $this->normalize_question_ids( $question_ids ); - - if ( empty( $question_ids ) ) { - return array(); - } - - $questions = QueryHelper::get_all( - 'tutor_quiz_questions', - array( - 'question_id' => array( 'IN', $question_ids ), - ), - 'question_order', - -1, - 'ASC' - ); - - $questions_by_id = array(); - foreach ( $questions as $question ) { - $question->question_title = stripslashes( $question->question_title ); - $question->question_description = stripslashes( $question->question_description ); - $question->answer_explanation = stripslashes( $question->answer_explanation ); - $questions_by_id[ (int) $question->question_id ] = $question; - } - - $ordered_questions = array(); - foreach ( $question_ids as $question_id ) { - if ( isset( $questions_by_id[ $question_id ] ) ) { - $ordered_questions[] = $questions_by_id[ $question_id ]; - } - } - - return $ordered_questions; - } - - /** - * Get stored question ids for an attempt. - * - * @since 4.0.0 - * - * @param int $attempt_id Attempt id. - * - * @return array - */ - public function get_attempt_question_ids( int $attempt_id ): array { - $attempt = $this->get_attempt( $attempt_id ); - - if ( ! $attempt ) { - return array(); - } - - $attempt_info = maybe_unserialize( $attempt->attempt_info ?? '' ); - if ( is_array( $attempt_info ) && ! empty( $attempt_info['attempt_question_ids'] ) && is_array( $attempt_info['attempt_question_ids'] ) ) { - return $this->normalize_question_ids( $attempt_info['attempt_question_ids'] ); - } - - $attempt_answers = QuizModel::get_quiz_answers_by_attempt_id( $attempt_id ); - if ( ! is_array( $attempt_answers ) || empty( $attempt_answers ) ) { - return array(); - } - - $question_ids = array_map( - function ( $attempt_answer ) { - return (int) ( $attempt_answer->question_id ?? 0 ); - }, - $attempt_answers - ); - - return $this->normalize_question_ids( $question_ids ); - } - - /** - * Persist question ids for an attempt. - * - * @since 4.0.0 - * - * @param int $attempt_id Attempt id. - * @param array $question_ids Question ids. - * - * @return bool - */ - public function persist_attempt_question_ids( int $attempt_id, array $question_ids ): bool { - $question_ids = $this->normalize_question_ids( $question_ids ); - - if ( $attempt_id <= 0 || empty( $question_ids ) ) { - return false; - } - - $attempt = $this->get_attempt( $attempt_id ); - if ( ! $attempt ) { - return false; - } - - $attempt_info = maybe_unserialize( $attempt->attempt_info ?? '' ); - $attempt_info = is_array( $attempt_info ) ? $attempt_info : array(); - - $stored_question_ids = isset( $attempt_info['attempt_question_ids'] ) && is_array( $attempt_info['attempt_question_ids'] ) - ? $this->normalize_question_ids( $attempt_info['attempt_question_ids'] ) - : array(); - - if ( $stored_question_ids === $question_ids ) { - return true; - } - - $attempt_info['attempt_question_ids'] = $question_ids; - - return (bool) Quiz::update_attempt_info( $attempt_id, maybe_serialize( $attempt_info ) ); - } - - /** - * Get questions used in an attempt. - * - * @since 4.0.0 - * - * @param int $attempt_id Attempt id. - * - * @return array|bool - */ - public function get_questions_by_attempt( int $attempt_id ) { - $attempt = $this->get_attempt( $attempt_id ); - - if ( ! $attempt ) { - return false; - } - - $question_ids = $this->get_attempt_question_ids( $attempt_id ); - if ( ! empty( $question_ids ) ) { - $questions = $this->get_questions_by_ids( $question_ids ); - if ( ! empty( $questions ) ) { - return $questions; - } - } - - return $this->get_questions_by_quiz( (int) $attempt->quiz_id ); - } /** * Get single quiz attempt @@ -5524,12 +5359,7 @@ public function get_random_questions_by_quiz( $quiz_id = 0 ) { return false; } - $total_questions = (int) $attempt->total_questions; - $stored_question_ids = $this->get_attempt_question_ids( (int) $attempt->attempt_id ); - if ( ! empty( $stored_question_ids ) ) { - return $this->get_questions_by_ids( $stored_question_ids ); - } - + $total_questions = (int) $attempt->total_questions; $questions_order = $this->get_quiz_option( $quiz_id, 'questions_order', 'rand' ); $order_by = ''; @@ -5560,16 +5390,6 @@ public function get_random_questions_by_quiz( $quiz_id = 0 ) { ) ); - $this->persist_attempt_question_ids( - (int) $attempt->attempt_id, - array_map( - function ( $question ) { - return (int) ( $question->question_id ?? 0 ); - }, - is_array( $questions ) ? $questions : array() - ) - ); - return $questions; } diff --git a/templates/dashboard/quiz-attempts/quiz-reviews.php b/templates/dashboard/quiz-attempts/quiz-reviews.php index 1c25044c72..1762f97016 100644 --- a/templates/dashboard/quiz-attempts/quiz-reviews.php +++ b/templates/dashboard/quiz-attempts/quiz-reviews.php @@ -36,7 +36,6 @@ ); $attempt_answers_map = array(); -$questions = tutor_utils()->get_questions_by_attempt( $attempt_id ); $attempt_answers = QuizModel::get_quiz_answers_by_attempt_id( $attempt_id ); if ( is_array( $attempt_answers ) ) { @@ -45,17 +44,7 @@ if ( $question_id > 0 ) { $attempt_answers_map[ $question_id ] = $attempt_answer; - } - } -} - -if ( is_array( $questions ) ) { - foreach ( $questions as $question ) { - $question_id = (int) ( $question->question_id ?? 0 ); - $attempt_answer = $attempt_answers_map[ $question_id ] ?? null; - $answer_status = $attempt_answer ? QuizModel::get_attempt_answer_status( $attempt_answer ) : 'skipped'; - - if ( $question_id > 0 ) { + $answer_status = QuizModel::get_attempt_answer_status( $attempt_answer ); $form_default_values[ "review_statuses[{$question_id}]" ] = $answer_status; } } diff --git a/templates/shared/components/quiz/attempt-details.php b/templates/shared/components/quiz/attempt-details.php index 14a349717e..41b622645f 100644 --- a/templates/shared/components/quiz/attempt-details.php +++ b/templates/shared/components/quiz/attempt-details.php @@ -69,7 +69,7 @@ $quiz_id = (int) ( $attempt_data->quiz_id ?? 0 ); } -$questions = tutor_utils()->get_questions_by_attempt( (int) $attempt_data->attempt_id ); +$attempt_answers = QuizModel::get_quiz_answers_by_attempt_id( (int) $attempt_data->attempt_id ); $course_contents = tutor_utils()->get_course_prev_next_contents_by_id( $quiz_id ); ?>
@@ -133,7 +133,7 @@ tutor_load_template( 'shared.components.quiz.attempt-details.review-answers', array( - 'questions' => is_array( $questions ) ? $questions : array(), + 'attempt_answers' => is_array( $attempt_answers ) ? $attempt_answers : array(), 'attempt_data' => $attempt_data, 'back_url' => $back_url, 'context' => $context, diff --git a/templates/shared/components/quiz/attempt-details/questions-sidebar.php b/templates/shared/components/quiz/attempt-details/questions-sidebar.php index db037d594c..4ac58ed91f 100644 --- a/templates/shared/components/quiz/attempt-details/questions-sidebar.php +++ b/templates/shared/components/quiz/attempt-details/questions-sidebar.php @@ -22,9 +22,6 @@ return; } -$questions = isset( $attempt_data ) && is_object( $attempt_data ) && ! empty( $attempt_data->attempt_id ) - ? tutor_utils()->get_questions_by_attempt( (int) $attempt_data->attempt_id ) - : tutor_utils()->get_questions_by_quiz( $quiz_id ); $question_status_map = array(); $default_item_status = ( isset( $attempt_data ) && is_object( $attempt_data ) ) ? 'incorrect' : ''; $status_priority = array( @@ -32,29 +29,31 @@ 'incorrect' => 2, 'pending' => 3, ); -$first_question_id = is_array( $questions ) && ! empty( $questions ) ? (int) ( $questions[0]->question_id ?? 0 ) : 0; -$first_question_id = $first_question_id > 0 ? $first_question_id : ''; if ( isset( $attempt_data ) && is_object( $attempt_data ) && ! empty( $attempt_data->attempt_id ) ) { $attempt_answers = QuizModel::get_quiz_answers_by_attempt_id( (int) $attempt_data->attempt_id ); + $questions = is_array( $attempt_answers ) ? $attempt_answers : array(); - if ( is_array( $attempt_answers ) ) { - foreach ( $attempt_answers as $answer_row ) { - $question_id = (int) ( $answer_row->question_id ?? 0 ); - if ( ! $question_id ) { - continue; - } + foreach ( $questions as $answer_row ) { + $question_id = (int) ( $answer_row->question_id ?? 0 ); + if ( ! $question_id ) { + continue; + } - $answer_status = QuizModel::get_attempt_answer_status( $answer_row ); - $item_status = 'correct' === $answer_status ? 'correct' : ( 'pending' === $answer_status ? 'pending' : 'incorrect' ); - $current = $question_status_map[ $question_id ] ?? ''; + $answer_status = QuizModel::get_attempt_answer_status( $answer_row ); + $item_status = 'correct' === $answer_status ? 'correct' : ( 'pending' === $answer_status ? 'pending' : 'incorrect' ); + $current = $question_status_map[ $question_id ] ?? ''; - if ( ! $current || $status_priority[ $item_status ] > $status_priority[ $current ] ) { - $question_status_map[ $question_id ] = $item_status; - } + if ( ! $current || $status_priority[ $item_status ] > $status_priority[ $current ] ) { + $question_status_map[ $question_id ] = $item_status; } } +} else { + $questions = tutor_utils()->get_questions_by_quiz( $quiz_id ); } + +$first_question_id = is_array( $questions ) && ! empty( $questions ) ? (int) ( $questions[0]->question_id ?? 0 ) : 0; +$first_question_id = $first_question_id > 0 ? $first_question_id : ''; ?>
attempt_id ) ) { - $attempt_answers = QuizModel::get_quiz_answers_by_attempt_id( (int) $attempt_data->attempt_id ); - if ( is_array( $attempt_answers ) ) { - foreach ( $attempt_answers as $attempt_answer ) { - $qid = (int) ( $attempt_answer->question_id ?? 0 ); - if ( $qid > 0 ) { - $attempt_answers_map[ $qid ] = $attempt_answer; - } - } - } -} +$attempt_answers = isset( $attempt_answers ) && is_array( $attempt_answers ) ? $attempt_answers : array(); +$attempt_data = isset( $attempt_data ) && is_object( $attempt_data ) ? $attempt_data : null; +$back_url = isset( $back_url ) ? (string) $back_url : ''; +$context = isset( $context ) ? (string) $context : ''; ?>
- $question ) : ?> + $question ) : ?> question_type ?? ''; @@ -49,8 +35,6 @@ $is_coordinates = 'coordinates' === $question_type; $is_puzzle = 'puzzle' === $question_type; - $attempt_answer = $attempt_answers_map[ $question_id ] ?? null; - $question_template = ''; if ( $is_dnd ) { @@ -83,7 +67,7 @@ 'shared.components.quiz.attempt-details.question', array( 'question' => $question, - 'attempt_answer' => $attempt_answer, + 'attempt_answer' => $question, 'index' => (int) $index + 1, 'question_template' => $question_template, 'attempt_id' => (int) ( $attempt_data->attempt_id ?? 0 ), From 6c3d9f3ebef53a41e41d59de7dd984d65688b5dd Mon Sep 17 00:00:00 2001 From: b-l-i-n-d Date: Sun, 3 May 2026 16:26:06 +0600 Subject: [PATCH 4/7] refactor: simplify attempt answer assignment in quiz detail templates --- templates/shared/components/quiz/attempt-details/question.php | 2 +- .../shared/components/quiz/attempt-details/review-answers.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/templates/shared/components/quiz/attempt-details/question.php b/templates/shared/components/quiz/attempt-details/question.php index 0a77bdd9ca..1fed041e27 100644 --- a/templates/shared/components/quiz/attempt-details/question.php +++ b/templates/shared/components/quiz/attempt-details/question.php @@ -16,8 +16,8 @@ return; } +$attempt_answer = $question; $index = (int) ( $index ?? 1 ); -$attempt_answer = isset( $attempt_answer ) && is_object( $attempt_answer ) ? $attempt_answer : null; $attempt_id = (int) ( $attempt_id ?? 0 ); $back_url = (string) ( $back_url ?? '' ); $context = (string) ( $context ?? '' ); diff --git a/templates/shared/components/quiz/attempt-details/review-answers.php b/templates/shared/components/quiz/attempt-details/review-answers.php index 33a58903aa..fbc64de6c0 100644 --- a/templates/shared/components/quiz/attempt-details/review-answers.php +++ b/templates/shared/components/quiz/attempt-details/review-answers.php @@ -67,7 +67,6 @@ 'shared.components.quiz.attempt-details.question', array( 'question' => $question, - 'attempt_answer' => $question, 'index' => (int) $index + 1, 'question_template' => $question_template, 'attempt_id' => (int) ( $attempt_data->attempt_id ?? 0 ), From 7fbe3fb652c6594b2bc874c949f177932254d8df Mon Sep 17 00:00:00 2001 From: b-l-i-n-d Date: Sun, 3 May 2026 16:35:21 +0600 Subject: [PATCH 5/7] refactor: remove redundant whitespace in Utils class --- classes/Utils.php | 1 - 1 file changed, 1 deletion(-) diff --git a/classes/Utils.php b/classes/Utils.php index 5eec4a7edf..dd0da1ff1b 100644 --- a/classes/Utils.php +++ b/classes/Utils.php @@ -5247,7 +5247,6 @@ public function max_questions_for_take_quiz( $quiz_id ) { return $max_questions; } - /** * Get single quiz attempt * From ddf3d92ef966a01872c47b6dec33311bebae2002 Mon Sep 17 00:00:00 2001 From: b-l-i-n-d Date: Sun, 3 May 2026 17:06:41 +0600 Subject: [PATCH 6/7] refactor: remove redundant attempt_answer variable in favor of direct question object usage across quiz attempt templates --- .../quiz/attempt-details/question.php | 18 ++++++++---------- .../questions/fill-in-the-blank.php | 5 ++--- .../questions/multiple-choice.php | 5 ++--- .../attempt-details/questions/open-ended.php | 7 +++---- .../questions/review-answer-dnd.php | 5 ++--- .../attempt-details/questions/true-false.php | 5 ++--- 6 files changed, 19 insertions(+), 26 deletions(-) diff --git a/templates/shared/components/quiz/attempt-details/question.php b/templates/shared/components/quiz/attempt-details/question.php index 1fed041e27..7c7f186641 100644 --- a/templates/shared/components/quiz/attempt-details/question.php +++ b/templates/shared/components/quiz/attempt-details/question.php @@ -16,7 +16,6 @@ return; } -$attempt_answer = $question; $index = (int) ( $index ?? 1 ); $attempt_id = (int) ( $attempt_id ?? 0 ); $back_url = (string) ( $back_url ?? '' ); @@ -35,8 +34,8 @@ $question_type = 'multiple_choice'; } -$is_skipped = QuizModel::is_attempt_answer_skipped( $attempt_answer ); -$review_status = $attempt_answer ? QuizModel::get_attempt_answer_status( $attempt_answer ) : 'skipped'; +$is_skipped = QuizModel::is_attempt_answer_skipped( $question ); +$review_status = $question ? QuizModel::get_attempt_answer_status( $question ) : 'skipped'; $answer_status = $review_status; $status_badges = array(); @@ -86,7 +85,7 @@ 'status_badges' => $status_badges, 'answer_status' => $answer_status, 'attempt_id' => $attempt_id, - 'attempt_answer_id' => (int) ( $attempt_answer->attempt_answer_id ?? 0 ), + 'attempt_answer_id' => (int) ( $question->attempt_answer_id ?? 0 ), 'back_url' => $back_url, 'context' => $context, 'is_instructor_review' => $is_instructor_review, @@ -97,16 +96,15 @@ tutor_load_template( 'shared.components.quiz.attempt-details.questions.' . $question_template, array( - 'question' => $question, - 'attempt_answer' => $attempt_answer, - 'index' => $index, + 'question' => $question, + 'index' => $index, ) ); - do_action( 'tutor_quiz_attempt_details_after_question_template', $question, $question_template, $attempt_answer, $index ); + do_action( 'tutor_quiz_attempt_details_after_question_template', $question, $question_template, $index ); - if ( is_object( $attempt_answer ) ) { - do_action( 'tutor_quiz_attempt_details_loop_after_row', $attempt_answer, $answer_status, array() ); + if ( is_object( $question ) ) { + do_action( 'tutor_quiz_attempt_details_loop_after_row', $question, $answer_status, array() ); } ?>
diff --git a/templates/shared/components/quiz/attempt-details/questions/fill-in-the-blank.php b/templates/shared/components/quiz/attempt-details/questions/fill-in-the-blank.php index bec7bee7d3..8c693f2fd4 100644 --- a/templates/shared/components/quiz/attempt-details/questions/fill-in-the-blank.php +++ b/templates/shared/components/quiz/attempt-details/questions/fill-in-the-blank.php @@ -15,13 +15,12 @@ return; } -$attempt_answer = isset( $attempt_answer ) && is_object( $attempt_answer ) ? $attempt_answer : null; $question_answers = QuizModel::get_answers_by_quiz_question( (int) $question->question_id ); $question_answers = is_array( $question_answers ) ? $question_answers : array(); $given_values = array(); -if ( $attempt_answer ) { - $given_raw = maybe_unserialize( $attempt_answer->given_answer ); +if ( isset( $question->given_answer ) ) { + $given_raw = maybe_unserialize( $question->given_answer ); if ( is_array( $given_raw ) ) { $given_values = array_values( array_map( 'strval', $given_raw ) ); } elseif ( is_string( $given_raw ) && '' !== trim( $given_raw ) ) { diff --git a/templates/shared/components/quiz/attempt-details/questions/multiple-choice.php b/templates/shared/components/quiz/attempt-details/questions/multiple-choice.php index 9d9c578be2..803be27fa1 100644 --- a/templates/shared/components/quiz/attempt-details/questions/multiple-choice.php +++ b/templates/shared/components/quiz/attempt-details/questions/multiple-choice.php @@ -15,7 +15,6 @@ return; } -$attempt_answer = isset( $attempt_answer ) && is_object( $attempt_answer ) ? $attempt_answer : null; $question_settings = maybe_unserialize( $question->question_settings ); $question_settings = is_array( $question_settings ) ? $question_settings : array(); $question_answers = QuizModel::get_answers_by_quiz_question( (int) $question->question_id ); @@ -23,8 +22,8 @@ $has_multiple_correct_answer = isset( $question_settings['has_multiple_correct_answer'] ) && '1' === (string) $question_settings['has_multiple_correct_answer']; $given_ids = array(); -if ( $attempt_answer ) { - $given_value = maybe_unserialize( $attempt_answer->given_answer ); +if ( isset( $question->given_answer ) ) { + $given_value = maybe_unserialize( $question->given_answer ); $given_ids = is_array( $given_value ) ? array_values( $given_value ) : array( $given_value ); } $given_ids = array_map( 'intval', array_filter( $given_ids ) ); diff --git a/templates/shared/components/quiz/attempt-details/questions/open-ended.php b/templates/shared/components/quiz/attempt-details/questions/open-ended.php index 56c8f4478d..a43beaa654 100644 --- a/templates/shared/components/quiz/attempt-details/questions/open-ended.php +++ b/templates/shared/components/quiz/attempt-details/questions/open-ended.php @@ -13,11 +13,10 @@ return; } -$attempt_answer = isset( $attempt_answer ) && is_object( $attempt_answer ) ? $attempt_answer : null; -$given_answer = ''; +$given_answer = ''; -if ( $attempt_answer ) { - $given_raw = maybe_unserialize( $attempt_answer->given_answer ); +if ( isset( $question->given_answer ) ) { + $given_raw = maybe_unserialize( $question->given_answer ); if ( is_array( $given_raw ) ) { $given_answer = implode( ', ', array_map( 'strval', $given_raw ) ); } else { diff --git a/templates/shared/components/quiz/attempt-details/questions/review-answer-dnd.php b/templates/shared/components/quiz/attempt-details/questions/review-answer-dnd.php index b57ecfe2b1..976cef8ed3 100644 --- a/templates/shared/components/quiz/attempt-details/questions/review-answer-dnd.php +++ b/templates/shared/components/quiz/attempt-details/questions/review-answer-dnd.php @@ -30,8 +30,7 @@ $question_settings['is_image_matching'] = $is_image_matching ? '1' : ( $question_settings['is_image_matching'] ?? '0' ); $question_answers = QuizModel::get_answers_by_quiz_question( (int) $question->question_id ); $question_answers = is_array( $question_answers ) ? array_values( $question_answers ) : array(); -$attempt_answer = isset( $attempt_answer ) && is_object( $attempt_answer ) ? $attempt_answer : null; -$given_raw = $attempt_answer->given_answer ?? null; +$given_raw = $question->given_answer ?? null; $given_answer = maybe_unserialize( $given_raw ); $normalize = static function ( $value ) { @@ -84,7 +83,7 @@ } } elseif ( 'image_answering' === $question_type ) { $given_map = is_array( $given_answer ) ? $given_answer : array(); - $answer_status = $attempt_answer ? QuizModel::get_attempt_answer_status( $attempt_answer ) : 'incorrect'; + $answer_status = QuizModel::get_attempt_answer_status( $question ); $row_item_state = 'correct' === $answer_status ? 'correct' : ( 'pending' === $answer_status ? 'pending' : 'incorrect' ); foreach ( $question_answers as $correct_item ) { diff --git a/templates/shared/components/quiz/attempt-details/questions/true-false.php b/templates/shared/components/quiz/attempt-details/questions/true-false.php index 4f2a043ae2..b0e87130b0 100644 --- a/templates/shared/components/quiz/attempt-details/questions/true-false.php +++ b/templates/shared/components/quiz/attempt-details/questions/true-false.php @@ -17,13 +17,12 @@ return; } -$attempt_answer = isset( $attempt_answer ) && is_object( $attempt_answer ) ? $attempt_answer : null; $question_answers = QuizModel::get_answers_by_quiz_question( (int) $question->question_id ); $question_answers = is_array( $question_answers ) ? $question_answers : array(); $given_ids = array(); -if ( $attempt_answer ) { - $given_value = maybe_unserialize( $attempt_answer->given_answer ); +if ( isset( $question->given_answer ) ) { + $given_value = maybe_unserialize( $question->given_answer ); $given_ids = is_array( $given_value ) ? array_values( $given_value ) : array( $given_value ); } $given_ids = array_map( 'intval', array_filter( $given_ids ) ); From 05189dc90a2ec1ca27037dd51c9f5e856d0e0809 Mon Sep 17 00:00:00 2001 From: b-l-i-n-d Date: Sun, 3 May 2026 17:11:44 +0600 Subject: [PATCH 7/7] refactor: rename attempt_answers variables to questions for consistent naming across quiz attempt templates --- templates/dashboard/quiz-attempts/quiz-reviews.php | 12 ++++++------ templates/shared/components/quiz/attempt-details.php | 4 ++-- .../quiz/attempt-details/questions-sidebar.php | 4 ++-- .../quiz/attempt-details/review-answers.php | 10 +++++----- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/templates/dashboard/quiz-attempts/quiz-reviews.php b/templates/dashboard/quiz-attempts/quiz-reviews.php index 1762f97016..bd6ec96a13 100644 --- a/templates/dashboard/quiz-attempts/quiz-reviews.php +++ b/templates/dashboard/quiz-attempts/quiz-reviews.php @@ -36,15 +36,15 @@ ); $attempt_answers_map = array(); -$attempt_answers = QuizModel::get_quiz_answers_by_attempt_id( $attempt_id ); +$questions = QuizModel::get_quiz_answers_by_attempt_id( $attempt_id ); -if ( is_array( $attempt_answers ) ) { - foreach ( $attempt_answers as $attempt_answer ) { - $question_id = (int) ( $attempt_answer->question_id ?? 0 ); +if ( is_array( $questions ) ) { + foreach ( $questions as $question ) { + $question_id = (int) ( $question->question_id ?? 0 ); if ( $question_id > 0 ) { - $attempt_answers_map[ $question_id ] = $attempt_answer; - $answer_status = QuizModel::get_attempt_answer_status( $attempt_answer ); + $attempt_answers_map[ $question_id ] = $question; + $answer_status = QuizModel::get_attempt_answer_status( $question ); $form_default_values[ "review_statuses[{$question_id}]" ] = $answer_status; } } diff --git a/templates/shared/components/quiz/attempt-details.php b/templates/shared/components/quiz/attempt-details.php index 41b622645f..62ab01d2a1 100644 --- a/templates/shared/components/quiz/attempt-details.php +++ b/templates/shared/components/quiz/attempt-details.php @@ -69,7 +69,7 @@ $quiz_id = (int) ( $attempt_data->quiz_id ?? 0 ); } -$attempt_answers = QuizModel::get_quiz_answers_by_attempt_id( (int) $attempt_data->attempt_id ); +$questions = QuizModel::get_quiz_answers_by_attempt_id( (int) $attempt_data->attempt_id ); $course_contents = tutor_utils()->get_course_prev_next_contents_by_id( $quiz_id ); ?>
@@ -133,7 +133,7 @@ tutor_load_template( 'shared.components.quiz.attempt-details.review-answers', array( - 'attempt_answers' => is_array( $attempt_answers ) ? $attempt_answers : array(), + 'questions' => is_array( $questions ) ? $questions : array(), 'attempt_data' => $attempt_data, 'back_url' => $back_url, 'context' => $context, diff --git a/templates/shared/components/quiz/attempt-details/questions-sidebar.php b/templates/shared/components/quiz/attempt-details/questions-sidebar.php index 4ac58ed91f..204c6e6811 100644 --- a/templates/shared/components/quiz/attempt-details/questions-sidebar.php +++ b/templates/shared/components/quiz/attempt-details/questions-sidebar.php @@ -31,8 +31,8 @@ ); if ( isset( $attempt_data ) && is_object( $attempt_data ) && ! empty( $attempt_data->attempt_id ) ) { - $attempt_answers = QuizModel::get_quiz_answers_by_attempt_id( (int) $attempt_data->attempt_id ); - $questions = is_array( $attempt_answers ) ? $attempt_answers : array(); + $questions = QuizModel::get_quiz_answers_by_attempt_id( (int) $attempt_data->attempt_id ); + $questions = is_array( $questions ) ? $questions : array(); foreach ( $questions as $answer_row ) { $question_id = (int) ( $answer_row->question_id ?? 0 ); diff --git a/templates/shared/components/quiz/attempt-details/review-answers.php b/templates/shared/components/quiz/attempt-details/review-answers.php index fbc64de6c0..0730934725 100644 --- a/templates/shared/components/quiz/attempt-details/review-answers.php +++ b/templates/shared/components/quiz/attempt-details/review-answers.php @@ -11,14 +11,14 @@ use Tutor\Quiz; -$attempt_answers = isset( $attempt_answers ) && is_array( $attempt_answers ) ? $attempt_answers : array(); -$attempt_data = isset( $attempt_data ) && is_object( $attempt_data ) ? $attempt_data : null; -$back_url = isset( $back_url ) ? (string) $back_url : ''; -$context = isset( $context ) ? (string) $context : ''; +$questions = isset( $questions ) && is_array( $questions ) ? $questions : array(); +$attempt_data = isset( $attempt_data ) && is_object( $attempt_data ) ? $attempt_data : null; +$back_url = isset( $back_url ) ? (string) $back_url : ''; +$context = isset( $context ) ? (string) $context : ''; ?>
- $question ) : ?> + $question ) : ?> question_type ?? '';