PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 4.0.1
Tutor LMS – eLearning and online course solution v4.0.1
4.0.1 4.0.0 3.9.15 3.9.14 3.9.13 3.9.12 3.9.11 trunk 1.0.0 1.0.0-alpha 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.2.0 1.2.1 1.2.11 1.2.12 1.2.13 1.2.20 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 1.7.7 1.7.8 1.7.9 1.8.0 1.8.1 1.8.10 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.14 1.9.15 1.9.16 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.10 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.1.1 2.1.10 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.3.0 2.4.0 2.5.0 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 3.0.0 3.0.1 3.0.2 3.1.0 3.2.0 3.2.1 3.2.2 3.2.3 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.7.0 3.7.1 3.7.2 3.7.3 3.7.4 3.8.0 3.8.1 3.8.2 3.8.3 3.9.0 3.9.1 3.9.10 3.9.2 3.9.3 3.9.4 3.9.5 3.9.6 3.9.7 3.9.8 3.9.9
tutor / templates / learning-area / quiz / attempt.php
tutor / templates / learning-area / quiz Last commit date
modals 4 days ago questions 4 days ago attempt-details.php 4 days ago attempt.php 4 days ago content.php 4 days ago nav-item.php 4 days ago progress-bar.php 4 days ago question-header.php 4 days ago question.php 4 days ago
attempt.php
414 lines
1 <?php
2 /**
3 * Tutor learning area quiz active template
4 *
5 * This template get loaded once user submit the quiz or
6 * if there is any active quiz
7 *
8 * @package Tutor\Templates
9 * @subpackage LearningArea
10 * @author Themeum <support@themeum.com>
11 * @link https://themeum.com
12 * @since 4.0.0
13 */
14
15 defined( 'ABSPATH' ) || exit;
16
17 use Tutor\Quiz;
18 use Tutor\Models\QuizModel;
19 use TUTOR\Icon;
20 use TUTOR\Quiz_Attempts_List;
21 use Tutor\Components\SvgIcon;
22 use Tutor\Components\Button;
23 use Tutor\Components\ConfirmationModal;
24 use Tutor\Components\Modal;
25 use Tutor\Components\Constants\Size;
26 use Tutor\Components\Constants\Variant;
27 use Tutor\Helpers\UrlHelper;
28
29 global $tutor_is_started_quiz;
30
31 // Quiz attempt data.
32 $quiz_attempt_info = tutor_utils()->quiz_attempt_info( $tutor_is_started_quiz->attempt_info );
33 $quiz_attempt_info['date_time_now'] = date( 'Y-m-d H:i:s', tutor_time() ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
34
35 // Time limit calculations.
36 $time_limit_seconds = (int) tutor_utils()->avalue_dot( 'time_limit.time_limit_seconds', $quiz_attempt_info );
37 $has_time_limit = $time_limit_seconds > 0;
38 $remaining_time_secs = $has_time_limit
39 ? ( strtotime( $tutor_is_started_quiz->attempt_started_at ) + $time_limit_seconds ) - strtotime( $quiz_attempt_info['date_time_now'] )
40 : 0;
41 $remaining_time_context = tutor_utils()->seconds_to_time_context( $remaining_time_secs );
42
43 // Quiz settings.
44 $quiz_when_time_expires = tutor_utils()->get_option( 'quiz_when_time_expires', 'auto_abandon' );
45 $quiz_settings = tutor_utils()->get_quiz_option( (int) $tutor_is_started_quiz->quiz_id );
46 $show_timeout_attempts = 'auto_abandon' !== $quiz_when_time_expires;
47 $timeout_modal_message = 'auto_abandon' === $quiz_when_time_expires
48 ? __( 'Your quiz was abandoned automatically because time expired before you submitted it.', 'tutor' )
49 : __( 'Your quiz has been submitted automatically.', 'tutor' );
50 $reveal_wait_ms = 1000 * $quiz_settings['answers_reveal_duration'];
51 $show_previous_button = (bool) tutor_utils()->get_option( 'quiz_previous_button_enabled', true );
52 $hide_previous_button = '1' === (string) ( $quiz_settings['hide_previous_button'] ?? '0' );
53 $hide_quiz_time_display = '1' === (string) ( $quiz_settings['hide_quiz_time_display'] ?? '0' );
54 $show_previous_button = $show_previous_button && ! $hide_previous_button;
55
56 // Quiz layout.
57 $question_layout_view = $quiz_settings['question_layout_view'] ?? 'single_question';
58 $enable_pagination = '1' === (string) ( $quiz_settings['enable_pagination'] ?? '0' );
59 $enable_answer_reveal = '1' === (string) ( $quiz_settings['enable_answer_reveal'] ?? '0' );
60 $is_linear_layout = 'single_question' === $question_layout_view;
61 $is_pagination_layout = 'single_question' === $question_layout_view && $enable_pagination;
62
63 // Pagination style — only applies to single question layout with pagination enabled.
64 $supported_pagination_styles = array( 'shape', 'radio', 'number' );
65 $pagination_style = $quiz_settings['pagination_type'] ?? 'shape';
66 $pagination_style = in_array( $pagination_style, $supported_pagination_styles, true ) ? $pagination_style : 'shape';
67
68 // Questions and attempts.
69 $questions = tutor_utils()->get_random_questions_by_quiz();
70 $hide_question_number_overview = (bool) ( $quiz_settings['hide_question_number_overview'] ?? false );
71
72 $reveal_question_types = array( 'true_false', 'single_choice', 'multiple_choice' );
73 $quiz_answers = array();
74 foreach ( $questions as $question ) {
75 if ( ! in_array( $question->question_type, $reveal_question_types, true ) ) {
76 continue;
77 }
78
79 $answers = QuizModel::get_answers_by_quiz_question( $question->question_id );
80 foreach ( $answers as $answer ) {
81 if ( ! empty( $answer->is_correct ) ) {
82 $quiz_answers[] = $answer->answer_id;
83 }
84 }
85 }
86
87 $form_id = 'quiz-attempt-form-' . $tutor_is_started_quiz->attempt_id . '-' . $tutor_is_started_quiz->quiz_id;
88 $modal_id = 'tutor-quiz-abandon-modal';
89 $submitted_modal_id = 'tutor-quiz-submitted-modal';
90 $timeout_modal_id = 'tutor-quiz-timeout-modal';
91 $attempt_details_url = Quiz_Attempts_List::is_attempt_details_hidden()
92 ? ''
93 : UrlHelper::add_query_params(
94 get_pagenum_link(),
95 array(
96 'action' => Quiz::ACTION_VIEW_DETAILS,
97 'attempt_id' => (int) $tutor_is_started_quiz->attempt_id,
98 )
99 );
100 $modal_cancel_button = Button::make()
101 ->label( __( 'Stay Here', 'tutor' ) )
102 ->variant( Variant::SECONDARY )
103 ->size( Size::SMALL )
104 ->attr( '@click', "handleAbandonCancel(); TutorCore.modal.closeModal('$modal_id')" )
105 ->get();
106
107 $default_values = array(
108 'attempt[' . $tutor_is_started_quiz->attempt_id . '][quiz_question_ids][]' => array_map(
109 function ( $question ) {
110 return $question->question_id;
111 },
112 $questions
113 ),
114 );
115
116 ?>
117 <form
118 id="<?php echo esc_attr( $form_id ); ?>"
119 class="tutor-quiz tutor-quiz-submission"
120 data-question-layout-view="<?php echo esc_attr( $question_layout_view ); ?>"
121 x-data='(() => {
122 const form = tutorForm({
123 id: "<?php echo esc_attr( $form_id ); ?>",
124 mode: "onSubmit",
125 defaultValues: <?php echo wp_json_encode( $default_values ); ?>,
126 });
127 const submission = tutorQuizSubmission({
128 formId: "<?php echo esc_attr( $form_id ); ?>",
129 attemptId: "<?php echo esc_attr( $tutor_is_started_quiz->attempt_id ); ?>",
130 quizId: <?php echo esc_attr( $tutor_is_started_quiz->quiz_id ); ?>,
131 abandonModalId: "<?php echo esc_attr( $modal_id ); ?>",
132 submittedModalId: "<?php echo esc_attr( $submitted_modal_id ); ?>",
133 timeoutModalId: "<?php echo esc_attr( $timeout_modal_id ); ?>",
134 totalQuestions: <?php echo esc_attr( count( $questions ) ); ?>,
135 enableAnswerReveal: <?php echo $enable_answer_reveal ? 'true' : 'false'; ?>,
136 revealWaitMs: <?php echo esc_attr( (int) $reveal_wait_ms ); ?>,
137 });
138
139 const layout = tutorQuizLayout({
140 layout: "<?php echo esc_attr( $question_layout_view ); ?>",
141 formId: "<?php echo esc_attr( $form_id ); ?>",
142 totalQuestions: <?php echo esc_attr( count( $questions ) ); ?>,
143 enableAnswerReveal: <?php echo $enable_answer_reveal ? 'true' : 'false'; ?>,
144 revealWaitMs: <?php echo esc_attr( (int) $reveal_wait_ms ); ?>,
145 });
146
147 return {
148 ...form,
149 ...submission,
150 ...layout,
151 init() {
152 form.init?.call(this);
153 submission.init?.call(this);
154 layout.init?.call(this);
155 },
156 };
157 })()'
158 x-bind="getFormBindings()"
159 @submit.prevent="handleSubmit(
160 (data) => handleQuizSubmit(data),
161 (errors) => handleQuizError(errors)
162 )($event)"
163 >
164 <?php
165 tutor_load_template(
166 'learning-area.quiz.progress-bar',
167 array(
168 'remaining_time_secs' => max( 0, (int) $remaining_time_secs ),
169 'quiz_when_time_expires' => $quiz_when_time_expires,
170 'has_time_limit' => $has_time_limit,
171 'hide_quiz_time_display' => $hide_quiz_time_display,
172 'form_id' => $form_id,
173 'modal_id' => $modal_id,
174 'total_questions' => count( $questions ),
175 )
176 );
177 ?>
178
179 <div
180 class="tutor-quiz-questions"
181 data-question-layout-view="<?php echo esc_attr( $question_layout_view ); ?>"
182 x-cloak
183 >
184 <?php if ( $is_linear_layout && ! $hide_question_number_overview ) : ?>
185 <div class="tutor-quiz-question-meta">
186 <div class="tutor-quiz-question-indicator">
187 <?php
188 echo wp_kses(
189 sprintf(
190 /* translators: %s: question number indicator (e.g. 01/15) */
191 __( 'Question No: %s', 'tutor' ),
192 '<strong x-text="String(currentIndex).padStart(2, \'0\') + \'/\' + String(totalQuestions).padStart(2, \'0\')"></strong>'
193 ),
194 array(
195 'strong' => array(
196 'x-text' => true,
197 ),
198 )
199 );
200 ?>
201 </div>
202 </div>
203 <?php endif; ?>
204
205 <?php
206 do_action( 'tutor_quiz/body/before', $tutor_is_started_quiz->quiz_id, $quiz_attempt_info );
207 foreach ( $questions as $index => $question ) {
208 $question_settings = maybe_unserialize( $question->question_settings );
209 $answer_required = isset( $question_settings['answer_required'] ) && '1' === $question_settings['answer_required'];
210 $question_index = $index + 1;
211 ?>
212 <div
213 class="tutor-quiz-question-wrapper"
214 data-quiz-question-index="<?php echo esc_attr( $question_index ); ?>"
215 data-answer-required="<?php echo esc_attr( $answer_required ? '1' : '0' ); ?>"
216 x-show="isQuestionActive(<?php echo esc_attr( $question_index ); ?>)"
217 :class="{ 'tutor-quiz-question-wrapper-active': isQuestionActive(<?php echo esc_attr( $question_index ); ?>) }"
218 x-cloak
219 >
220 <?php Quiz::render_question( $question, $question_index ); ?>
221 </div>
222 <?php
223 }
224 ?>
225 </div>
226
227 <?php if ( $is_pagination_layout && count( $questions ) > 1 ) : ?>
228 <div
229 class="tutor-quiz-questions-pagination"
230 data-pagination-style="<?php echo esc_attr( $pagination_style ); ?>"
231 >
232 <ul>
233 <?php foreach ( $questions as $index => $question ) : ?>
234 <li>
235 <button
236 type="button"
237 class="tutor-quiz-question-paginate-item"
238 :class="getPaginationItemClass(<?php echo esc_attr( $index + 1 ); ?>)"
239 :data-state="getPaginationState(<?php echo esc_attr( $index + 1 ); ?>)"
240 @click="goTo(<?php echo esc_attr( $index + 1 ); ?>)"
241 >
242 <span class="tutor-quiz-question-paginate-label">
243 <?php echo esc_html( $index + 1 ); ?>
244 </span>
245 <span class="tutor-quiz-question-paginate-icon tutor-quiz-question-paginate-icon-correct">
246 <?php SvgIcon::make()->name( Icon::CHECK_2 )->size( 12 )->render(); ?>
247 </span>
248 <span class="tutor-quiz-question-paginate-icon tutor-quiz-question-paginate-icon-incorrect">
249 <?php SvgIcon::make()->name( Icon::CROSS )->size( 12 )->render(); ?>
250 </span>
251 </button>
252 </li>
253 <?php endforeach; ?>
254 </ul>
255 </div>
256 <?php endif; ?>
257
258 <?php if ( $is_linear_layout ) : ?>
259 <div
260 class="tutor-quiz-footer"
261 :data-reveal-state="revealFooterState"
262 x-cloak
263 >
264 <div class="tutor-quiz-footer-inner">
265 <div
266 class="tutor-quiz-footer-feedback"
267 x-show="revealFooterState !== ''"
268 >
269 <span
270 class="tutor-quiz-footer-feedback-icon"
271 x-show="revealFooterState === 'correct'"
272 >
273 <?php SvgIcon::make()->name( Icon::CHECK_2 )->size( 26 )->render(); ?>
274 </span>
275 <span
276 class="tutor-quiz-footer-feedback-icon"
277 x-show="revealFooterState === 'incorrect'"
278 >
279 <?php SvgIcon::make()->name( Icon::CROSS )->size( 26 )->render(); ?>
280 </span>
281 <span
282 class="tutor-quiz-footer-feedback-text"
283 x-show="revealFooterState === 'correct'"
284 >
285 <?php esc_html_e( 'Nicely Done!', 'tutor' ); ?>
286 </span>
287 <span
288 class="tutor-quiz-footer-feedback-text"
289 x-show="revealFooterState === 'incorrect'"
290 >
291 <?php esc_html_e( 'Wrong Answer', 'tutor' ); ?>
292 </span>
293 </div>
294
295 <?php
296 Button::make()
297 ->label( __( 'Skip Question', 'tutor' ) )
298 ->size( Size::LARGE )
299 ->variant( Variant::LINK_GRAY )
300 ->attr( 'type', 'button' )
301 ->attr( ':disabled', 'isRevealSubmitting || isRevealing' )
302 ->attr( 'x-show', 'canSkip(currentIndex) && revealFooterState === ""' )
303 ->attr( '@click', 'goNext({ skipValidation: true })' )
304 ->attr( 'class', 'tutor-quiz-skip-btn' )
305 ->render();
306 ?>
307
308 <div class="tutor-quiz-footer-actions">
309 <?php
310 Button::make()
311 ->label( __( 'Back', 'tutor' ) )
312 ->size( Size::LARGE )
313 ->variant( Variant::OUTLINE )
314 ->icon( Icon::ARROW_LEFT_2, 'left', 20 )
315 ->flip_rtl()
316 ->attr( 'type', 'button' )
317 ->attr( ':disabled', 'isRevealSubmitting' )
318 ->attr( '@click', 'goPrev()' )
319 ->attr( 'x-show', $show_previous_button ? 'currentIndex > 1' : 'false' )
320 ->attr( 'class', 'tutor-quiz-answer-previous-btn' )
321 ->render();
322
323 Button::make()
324 ->label( __( 'Next', 'tutor' ) )
325 ->size( Size::LARGE )
326 ->attr( 'type', 'button' )
327 ->attr( ':disabled', 'isRevealSubmitting || shouldDisableNextButton()' )
328 ->attr( '@click', 'goNext()' )
329 ->attr( 'x-show', 'currentIndex < totalQuestions' )
330 ->attr( 'class', 'tutor-quiz-answer-next-btn' )
331 ->render();
332
333 Button::make()
334 ->label( __( 'Submit Quiz', 'tutor' ) )
335 ->size( Size::LARGE )
336 ->attr( 'type', 'submit' )
337 ->attr( 'x-show', 'currentIndex === totalQuestions' )
338 ->attr( ':disabled', 'isRevealSubmitting' )
339 ->attr( ':class', '{ \'tutor-btn-loading\': submitQuizMutation?.isPending }' )
340 ->attr( 'class', 'tutor-quiz-submit-btn' )
341 ->render();
342 ?>
343 </div>
344 </div>
345 </div>
346 <?php else : ?>
347 <div class="tutor-quiz-footer">
348 <?php
349 Button::make()
350 ->label( __( 'Submit Quiz', 'tutor' ) )
351 ->size( Size::LARGE )
352 ->attr( 'form', $form_id )
353 ->attr( 'type', 'submit' )
354 ->attr( ':disabled', 'isRevealSubmitting' )
355 ->attr( ':class', '{ \'tutor-btn-loading\': submitQuizMutation?.isPending }' )
356 ->attr( 'style', 'display: block; margin: 0 auto; min-width: 290px;' )
357 ->render();
358 ?>
359 </div>
360 <?php endif; ?>
361 <?php
362 ConfirmationModal::make()
363 ->id( $modal_id )
364 ->icon( tutor_utils()->get_themed_svg( 'images/illustrations/warning.svg' ), 80, 80, ConfirmationModal::ICON_TYPE_HTML )
365 ->title( __( 'Leave this Quiz?', 'tutor' ) )
366 ->message( __( 'If you leave now, your quiz will be submitted with the answers completed so far.', 'tutor' ) )
367 ->confirm_text( __( 'Yes, Leave Quiz', 'tutor' ) )
368 ->confirm_handler( "handleAbandonConfirm(); TutorCore.modal.closeModal('$modal_id')" )
369 ->mutation_state( 'abandonQuizMutation' )
370 ->cancel_button( $modal_cancel_button )
371 ->render();
372 ?>
373
374 <?php
375 Modal::make()
376 ->id( $submitted_modal_id )
377 ->width( '426px' )
378 ->template(
379 tutor()->path . 'templates/learning-area/quiz/modals/result.php',
380 array(
381 'modal_id' => $submitted_modal_id,
382 'title' => __( 'Quiz Submitted', 'tutor' ),
383 'message' => __( 'Your answers are locked in. Ready to check your score?', 'tutor' ),
384 'icon_html' => tutor_utils()->get_themed_svg( 'images/illustrations/quiz-submitted.svg' ),
385 'show_attempts' => false,
386 'action_url' => $attempt_details_url,
387 'action_label' => __( 'View Results', 'tutor' ),
388 )
389 )
390 ->render();
391
392 Modal::make()
393 ->id( $timeout_modal_id )
394 ->width( '426px' )
395 ->template(
396 tutor()->path . 'templates/learning-area/quiz/modals/result.php',
397 array(
398 'modal_id' => $timeout_modal_id,
399 'title' => __( 'Times up!', 'tutor' ),
400 'message' => $timeout_modal_message,
401 'icon_html' => tutor_utils()->get_themed_svg( 'images/illustrations/quiz-timeout.svg' ),
402 'show_attempts' => $show_timeout_attempts,
403 'action_url' => $attempt_details_url,
404 'action_label' => __( 'View Results', 'tutor' ),
405 )
406 )
407 ->render();
408 ?>
409 </form>
410
411 <script type="application/octet-stream" id="tutor-quiz-context">
412 <?php echo esc_html( bin2hex( wp_json_encode( $quiz_answers ) ) ); ?>
413 </script>
414