PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 2.0.10
Tutor LMS – eLearning and online course solution v2.0.10
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 / classes / Quiz.php
tutor / classes Last commit date
Addons.php 4 years ago Admin.php 4 years ago Ajax.php 3 years ago Announcements.php 4 years ago Assets.php 3 years ago Backend_Page_Trait.php 4 years ago Course.php 3 years ago Course_Embed.php 3 years ago Course_Filter.php 4 years ago Course_List.php 3 years ago Course_Settings_Tabs.php 4 years ago Course_Widget.php 4 years ago Custom_Validation.php 4 years ago Dashboard.php 3 years ago FormHandler.php 4 years ago Frontend.php 3 years ago Gutenberg.php 4 years ago Input.php 3 years ago Instructor.php 4 years ago Instructors_List.php 3 years ago Lesson.php 3 years ago Options_V2.php 3 years ago Post_types.php 4 years ago Private_Course_Access.php 4 years ago Q_and_A.php 3 years ago Question_Answers_List.php 4 years ago Quiz.php 3 years ago Quiz_Attempts_List.php 3 years ago RestAPI.php 4 years ago Reviews.php 4 years ago Rewrite_Rules.php 4 years ago Shortcode.php 4 years ago Student.php 4 years ago Students_List.php 4 years ago Taxonomies.php 4 years ago Template.php 3 years ago Theme_Compatibility.php 5 years ago Tools.php 3 years ago Tools_V2.php 4 years ago Tutor.php 3 years ago TutorEDD.php 4 years ago Tutor_Base.php 5 years ago Tutor_List_Table.php 4 years ago Tutor_Setup.php 3 years ago Upgrader.php 4 years ago User.php 4 years ago Utils.php 3 years ago Video_Stream.php 4 years ago Withdraw.php 3 years ago Withdraw_Requests_List.php 4 years ago WooCommerce.php 3 years ago
Quiz.php
1352 lines
1 <?php
2
3 /**
4 * Quiz class
5 *
6 * @author: themeum
7 * @author_uri: https://themeum.com
8 * @package Tutor
9 * @since v.1.0.0
10 */
11
12 namespace TUTOR;
13
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 class Quiz {
19
20 private $allowed_attributes = array(
21 'src' => array(),
22 'style' => array(),
23 'class' => array(),
24 'id' => array(),
25 'href' => array(),
26 'alt' => array(),
27 'title' => array(),
28 'type' => array(),
29 'controls' => array(),
30 'muted' => array(),
31 'loop' => array(),
32 'poster' => array(),
33 'preload' => array(),
34 'autoplay' => array(),
35 'width' => array(),
36 'height' => array(),
37 );
38
39 private $allowed_html = array( 'img', 'b', 'i', 'br', 'a', 'audio', 'video', 'source' );
40
41 public function __construct() {
42
43 add_action('save_post_tutor_quiz', array($this, 'save_quiz_meta'));
44 add_action('wp_ajax_remove_quiz_from_post', array($this, 'remove_quiz_from_post'));
45
46 add_action( 'wp_ajax_tutor_quiz_timeout', array( $this, 'tutor_quiz_timeout' ) );
47
48 // User take the quiz
49 add_action( 'template_redirect', array( $this, 'start_the_quiz' ) );
50 add_action( 'template_redirect', array( $this, 'answering_quiz' ) );
51 add_action( 'template_redirect', array( $this, 'finishing_quiz_attempt' ) );
52
53 add_action('wp_ajax_review_quiz_answer', array($this, 'review_quiz_answer'));
54 add_action('wp_ajax_tutor_instructor_feedback', array($this, 'tutor_instructor_feedback')); // Instructor Feedback Action
55
56 /**
57 * New Design Quiz
58 */
59
60 add_action('wp_ajax_tutor_quiz_save', array($this, 'tutor_quiz_save'));
61 add_action('wp_ajax_tutor_delete_quiz_by_id', array($this, 'tutor_delete_quiz_by_id'));
62 add_action('wp_ajax_tutor_load_quiz_builder_modal', array($this, 'tutor_load_quiz_builder_modal'), 10, 0);
63 add_action('wp_ajax_tutor_quiz_builder_get_question_form', array($this, 'tutor_quiz_builder_get_question_form'));
64 add_action('wp_ajax_tutor_quiz_modal_update_question', array($this, 'tutor_quiz_modal_update_question'));
65 add_action('wp_ajax_tutor_quiz_builder_question_delete', array($this, 'tutor_quiz_builder_question_delete'));
66 add_action('wp_ajax_tutor_quiz_question_answer_editor', array($this, 'tutor_quiz_question_answer_editor'));
67 add_action('wp_ajax_tutor_save_quiz_answer_options', array($this, 'tutor_save_quiz_answer_options'), 10, 0);
68 add_action('wp_ajax_tutor_update_quiz_answer_options', array($this, 'tutor_update_quiz_answer_options'));
69 add_action('wp_ajax_tutor_quiz_builder_change_type', array($this, 'tutor_quiz_builder_change_type'));
70 add_action('wp_ajax_tutor_quiz_builder_delete_answer', array($this, 'tutor_quiz_builder_delete_answer'));
71 add_action('wp_ajax_tutor_quiz_question_sorting', array($this, 'tutor_quiz_question_sorting'));
72 add_action('wp_ajax_tutor_quiz_answer_sorting', array($this, 'tutor_quiz_answer_sorting'));
73 add_action('wp_ajax_tutor_mark_answer_as_correct', array($this, 'tutor_mark_answer_as_correct'));
74
75 /**
76 * Frontend Stuff
77 */
78 add_action( 'wp_ajax_tutor_render_quiz_content', array( $this, 'tutor_render_quiz_content' ) );
79
80 /**
81 * Quiz abandon action
82 *
83 * @since 1.9.6
84 */
85 add_action( 'wp_ajax_tutor_quiz_abandon', array( $this, 'tutor_quiz_abandon' ) );
86
87 $this->prepare_allowed_html();
88 }
89
90 private function prepare_allowed_html() {
91
92 $allowed = array();
93
94 foreach ( $this->allowed_html as $tag ) {
95 $allowed[ $tag ] = $this->allowed_attributes;
96 }
97
98 $this->allowed_html = $allowed;
99 }
100
101 /**
102 * Instructor feedback ajax request handler
103 *
104 * @return void | send json response
105 */
106 public function tutor_instructor_feedback() {
107 tutor_utils()->checking_nonce();
108 $attempt_details = self::attempt_details( $_POST['attempt_id'] );
109 $feedback = wp_kses_post( $_POST['feedback'] );
110 $attempt_info = isset( $attempt_details->attempt_info ) ? $attempt_details->attempt_info : false;
111 if ( $attempt_info ) {
112 $unserialized = unserialize( $attempt_details->attempt_info );
113 if ( is_array( $unserialized ) ) {
114 $unserialized['instructor_feedback'] = $feedback;
115
116 do_action( 'tutor_quiz/attempt/submitted/feedback', $attempt_details->attempt_id );
117
118 $update = self::update_attempt_info( $attempt_details->attempt_id, serialize( $unserialized ) );
119 if ( $update ) {
120 wp_send_json_success();
121 } else {
122 wp_send_json_error();
123 }
124 } else {
125 wp_send_json_error( __( 'Invalid quiz info' ) );
126 }
127 }
128 wp_send_json_error();
129 }
130
131 public function save_quiz_meta( $post_ID ) {
132 if ( isset( $_POST['quiz_option'] ) ) {
133 $quiz_option = tutor_utils()->sanitize_array( $_POST['quiz_option'] );
134 update_post_meta( $post_ID, 'tutor_quiz_option', $quiz_option );
135 }
136 }
137
138 public function remove_quiz_from_post(){
139 tutor_utils()->checking_nonce();
140
141 global $wpdb;
142 $quiz_id = (int) tutor_utils()->avalue_dot( 'quiz_id', tutor_sanitize_data($_POST) );
143
144 if(!tutor_utils()->can_user_manage('quiz', $quiz_id)) {
145 wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
146 }
147
148 $wpdb->update( $wpdb->posts, array( 'post_parent' => 0 ), array( 'ID' => $quiz_id ) );
149 wp_send_json_success();
150 }
151
152 /**
153 *
154 * Start Quiz from here...
155 *
156 * @since v.1.0.0
157 */
158
159 public function start_the_quiz() {
160 if ( ! isset( $_POST['tutor_action'] ) || $_POST['tutor_action'] !== 'tutor_start_quiz' ) {
161 return;
162 }
163 // Checking nonce
164 tutor_utils()->checking_nonce();
165
166 if ( ! is_user_logged_in() ) {
167 // TODO: need to set a view in the next version
168 die( 'Please sign in to do this operation' );
169 }
170
171 global $wpdb;
172
173 $user_id = get_current_user_id();
174 $user = get_userdata( $user_id );
175
176 $quiz_id = (int) $_POST['quiz_id'];
177
178 $quiz = get_post( $quiz_id );
179 $course = tutor_utils()->get_course_by_quiz( $quiz_id );
180 if ( empty( $course->ID ) ) {
181 die( 'There is something went wrong with course, please check if quiz attached with a course' );
182 }
183
184 do_action( 'tutor_quiz/start/before', $quiz_id, $user_id );
185
186 $date = date( 'Y-m-d H:i:s', tutor_time() );
187
188 $tutor_quiz_option = (array) maybe_unserialize( get_post_meta( $quiz_id, 'tutor_quiz_option', true ) );
189 $attempts_allowed = tutor_utils()->get_quiz_option( $quiz_id, 'attempts_allowed', 0 );
190
191 $time_limit = tutor_utils()->get_quiz_option( $quiz_id, 'time_limit.time_value' );
192 $time_limit_seconds = 0;
193 $time_type = 'seconds';
194 if ( $time_limit ) {
195 $time_type = tutor_utils()->get_quiz_option( $quiz_id, 'time_limit.time_type' );
196
197 switch ( $time_type ) {
198 case 'seconds':
199 $time_limit_seconds = $time_limit;
200 break;
201 case 'minutes':
202 $time_limit_seconds = $time_limit * 60;
203 break;
204 case 'hours':
205 $time_limit_seconds = $time_limit * 60 * 60;
206 break;
207 case 'days':
208 $time_limit_seconds = $time_limit * 60 * 60 * 24;
209 break;
210 case 'weeks':
211 $time_limit_seconds = $time_limit * 60 * 60 * 24 * 7;
212 break;
213 }
214 }
215
216 $max_question_allowed = tutor_utils()->max_questions_for_take_quiz( $quiz_id );
217 $tutor_quiz_option['time_limit']['time_limit_seconds'] = $time_limit_seconds;
218
219 $attempt_data = array(
220 'course_id' => $course->ID,
221 'quiz_id' => $quiz_id,
222 'user_id' => $user_id,
223 'total_questions' => $max_question_allowed,
224 'total_answered_questions' => 0,
225 'attempt_info' => maybe_serialize( $tutor_quiz_option ),
226 'attempt_status' => 'attempt_started',
227 'attempt_ip' => tutor_utils()->get_ip(),
228 'attempt_started_at' => $date,
229 );
230
231 $wpdb->insert( $wpdb->prefix . 'tutor_quiz_attempts', $attempt_data );
232 $attempt_id = (int) $wpdb->insert_id;
233
234 do_action( 'tutor_quiz/start/after', $quiz_id, $user_id, $attempt_id );
235
236 wp_redirect( get_permalink( $quiz_id ) );
237 die();
238 }
239
240 public function answering_quiz() {
241
242 if ( tutor_utils()->array_get('tutor_action', $_POST) !== 'tutor_answering_quiz_question' ){
243 return;
244 }
245 // submit quiz attempts
246 self::tutor_quiz_attemp_submit();
247
248 wp_redirect( get_the_permalink() );
249 die();
250 }
251
252 /**
253 * Quiz abandon submission handler
254 *
255 * @return JSON response
256 *
257 * @since 1.9.6
258 */
259 public function tutor_quiz_abandon(){
260 if ( tutor_utils()->array_get('tutor_action', $_POST) !== 'tutor_answering_quiz_question' ){
261 return;
262 }
263 // submit quiz attempts
264 if ( self::tutor_quiz_attemp_submit() ) {
265 wp_send_json_success();
266 } else {
267 wp_send_json_error();
268 }
269 }
270
271 /**
272 * This is a unified method for handling normal quiz submit or abandon submit
273 *
274 * It will handle ajax or normal form submit and can be used with different hooks
275 *
276 * @return true | false
277 *
278 * @since 1.9.6
279 */
280 public static function tutor_quiz_attemp_submit() {
281
282 // Check logged in
283 if ( ! is_user_logged_in() ) {
284 die( 'Please sign in to do this operation' );
285 }
286
287 // Check nonce
288 tutor_utils()->checking_nonce();
289
290 // Prepare attempt info
291 global $wpdb;
292 $user_id = get_current_user_id();
293 $attempt_id = (int) tutor_utils()->avalue_dot( 'attempt_id', $_POST );
294 $attempt = tutor_utils()->get_attempt( $attempt_id );
295 $course_id = tutor_utils()->get_course_by_quiz( $attempt->quiz_id )->ID;
296 $attempt_answers = isset( $_POST['attempt'] ) ? tutor_sanitize_data( $_POST['attempt'] ) : false;
297 $attempt_answers = is_array($attempt_answers) ? $attempt_answers : array();
298
299 // Check if has access to the attempt
300 if ( ! $attempt || $user_id != $attempt->user_id ) {
301 die( 'Operation not allowed, attempt not found or permission denied' );
302 }
303
304 // Before ook
305 do_action( 'tutor_quiz/attempt_analysing/before', $attempt_id );
306
307 // Loop through every single attempt answer
308 // Single quiz can have multiple question. So multiple answer should be saved
309 foreach ( $attempt_answers as $attempt_id => $attempt_answer ) {
310
311 /**
312 * Get total marks of all question comes
313 */
314 $question_ids = tutor_utils()->avalue_dot( 'quiz_question_ids', $attempt_answer );
315 $question_ids = array_filter($question_ids, function($id){
316 return (int)$id;
317 });
318
319 // Calculate and set the total marks in attempt table for this question
320 if ( is_array( $question_ids ) && count( $question_ids ) ) {
321 $question_ids_string = implode(',', $question_ids );
322
323 // Get total marks of the questions from question table
324 $total_question_marks = $wpdb->get_var(
325 "SELECT SUM(question_mark)
326 FROM {$wpdb->prefix}tutor_quiz_questions
327 WHERE question_id IN({$question_ids_string});"
328 );
329
330 // Set the the total mark in the attempt table for the question
331 $wpdb->update(
332 $wpdb->prefix . 'tutor_quiz_attempts',
333 array( 'total_marks' => $total_question_marks ),
334 array( 'attempt_id' => $attempt_id )
335 );
336 }
337
338 $total_marks = 0;
339 $review_required = false;
340 $quiz_answers = tutor_utils()->avalue_dot( 'quiz_question', $attempt_answer );
341
342 if ( tutor_utils()->count($quiz_answers)) {
343
344 foreach ( $quiz_answers as $question_id => $answers ) {
345 $question = tutor_utils()->get_quiz_question_by_id( $question_id );
346 $question_type = $question->question_type;
347
348 $is_answer_was_correct = false;
349 $given_answer = '';
350
351 if ( $question_type === 'true_false' || $question_type === 'single_choice' ) {
352
353 if ( ! is_numeric( $answers ) || ! $answers ) {
354 wp_send_json_error();
355 exit;
356 }
357
358 $given_answer = $answers;
359 $is_answer_was_correct = (bool) $wpdb->get_var( $wpdb->prepare(
360 "SELECT is_correct
361 FROM {$wpdb->prefix}tutor_quiz_question_answers
362 WHERE answer_id = %d ",
363 $answers
364 ) );
365
366 } elseif ( $question_type === 'multiple_choice' ) {
367
368 $given_answer = (array) ( $answers );
369
370 $given_answer = array_filter( $given_answer, function($id) {
371 return is_numeric($id) && $id>0;
372 } );
373 $get_original_answers = (array) $wpdb->get_col($wpdb->prepare(
374 "SELECT
375 answer_id
376 FROM
377 {$wpdb->prefix}tutor_quiz_question_answers
378 WHERE belongs_question_id = %d
379 AND belongs_question_type = %s
380 AND is_correct = 1 ;
381 ",
382 $question->question_id,
383 $question_type
384 )
385 );
386
387 if ( count( array_diff( $get_original_answers, $given_answer ) ) === 0 && count( $get_original_answers ) === count( $given_answer ) ) {
388 $is_answer_was_correct = true;
389 }
390 $given_answer = maybe_serialize( $answers );
391
392 } elseif ( $question_type === 'fill_in_the_blank' ) {
393
394 $given_answer = (array) array_map( 'sanitize_text_field', $answers );
395 $given_answer = maybe_serialize( $given_answer );
396
397 $get_original_answer = $wpdb->get_row( $wpdb->prepare(
398 "SELECT * FROM {$wpdb->prefix}tutor_quiz_question_answers
399 WHERE belongs_question_id = %d
400 AND belongs_question_type = %s ;",
401 $question->question_id,
402 $question_type
403 ) );
404
405 $gap_answer = (array) explode( '|', $get_original_answer->answer_two_gap_match );
406
407 $gap_answer = array_map( 'sanitize_text_field', $gap_answer );
408 if ( strtolower( $given_answer ) == strtolower( maybe_serialize( $gap_answer ) ) ) {
409 $is_answer_was_correct = true;
410 }
411 } elseif ( $question_type === 'open_ended' || $question_type === 'short_answer' ) {
412 $review_required = true;
413 $given_answer = wp_kses_post( $answers );
414
415 } elseif ( $question_type === 'ordering' || $question_type === 'matching' || $question_type === 'image_matching' ) {
416
417 $given_answer = (array) array_map( 'sanitize_text_field', tutor_utils()->avalue_dot( 'answers', $answers ) );
418 $given_answer = maybe_serialize( $given_answer );
419
420 $get_original_answers = (array) $wpdb->get_col(
421 $wpdb->prepare(
422 "SELECT answer_id
423 FROM {$wpdb->prefix}tutor_quiz_question_answers
424 WHERE belongs_question_id = %d AND belongs_question_type = %s ORDER BY answer_order ASC ;",
425 $question->question_id,
426 $question_type
427 )
428 );
429
430 $get_original_answers = array_map( 'sanitize_text_field', $get_original_answers );
431
432 if ( $given_answer == maybe_serialize( $get_original_answers ) ) {
433 $is_answer_was_correct = true;
434 }
435 } elseif ( $question_type === 'image_answering' ) {
436 $image_inputs = tutor_utils()->avalue_dot( 'answer_id', $answers );
437 $image_inputs = (array) array_map( 'sanitize_text_field', $image_inputs );
438 $given_answer = maybe_serialize( $image_inputs );
439 $is_answer_was_correct = false;
440
441 $db_answer = $wpdb->get_col(
442 $wpdb->prepare(
443 "SELECT answer_title
444 FROM {$wpdb->prefix}tutor_quiz_question_answers
445 WHERE belongs_question_id = %d
446 AND belongs_question_type = 'image_answering'
447 ORDER BY answer_order asc ;",
448 $question_id
449 )
450 );
451
452 if ( is_array( $db_answer ) && count( $db_answer ) ) {
453 $is_answer_was_correct = ( strtolower( maybe_serialize( array_values( $image_inputs ) ) ) == strtolower( maybe_serialize( $db_answer ) ) );
454 }
455 }
456
457 $question_mark = $is_answer_was_correct ? $question->question_mark : 0;
458 $total_marks += $question_mark;
459
460 $answers_data = array(
461 'user_id' => $user_id,
462 'quiz_id' => $attempt->quiz_id,
463 'question_id' => $question_id,
464 'quiz_attempt_id' => $attempt_id,
465 'given_answer' => $given_answer,
466 'question_mark' => $question->question_mark,
467 'achieved_mark' => $question_mark,
468 'minus_mark' => 0,
469 'is_correct' => $is_answer_was_correct ? 1 : 0,
470 );
471
472 /*
473 check if question_type open ended or short ans the set is_correct default value null before saving
474 */
475 if ( in_array($question_type, array('open_ended', 'short_answer', 'image_answering'))) {
476 $answers_data['is_correct'] = null;
477 $review_required = true;
478 }
479
480 $wpdb->insert( $wpdb->prefix . 'tutor_quiz_attempt_answers', $answers_data );
481 }
482 }
483
484 $attempt_info = array(
485 'total_answered_questions' => tutor_utils()->count($quiz_answers),
486 'earned_marks' => $total_marks,
487 'attempt_status' => 'attempt_ended',
488 'attempt_ended_at' => date("Y-m-d H:i:s", tutor_time()),
489 );
490
491 if ($review_required){
492 $attempt_info['attempt_status'] = 'review_required';
493 }
494
495 $wpdb->update($wpdb->prefix.'tutor_quiz_attempts', $attempt_info, array('attempt_id' => $attempt_id));
496 }
497
498 // After hook
499 do_action('tutor_quiz/attempt_ended', $attempt_id, $course_id, $user_id);
500
501 return true;
502 }
503
504
505 /**
506 * Quiz attempt will be finish here
507 */
508
509 public function finishing_quiz_attempt() {
510
511 if ( ! isset( $_POST['tutor_action'] ) || $_POST['tutor_action'] !== 'tutor_finish_quiz_attempt' ) {
512 return;
513 }
514 // Checking nonce
515 tutor_utils()->checking_nonce();
516
517 if ( ! is_user_logged_in() ) {
518 die( 'Please sign in to do this operation' );
519 }
520
521 global $wpdb;
522
523 $quiz_id = (int) $_POST['quiz_id'];
524 $attempt = tutor_utils()->is_started_quiz( $quiz_id );
525 $attempt_id = $attempt->attempt_id;
526
527 $attempt_info = array(
528 'total_answered_questions' => 0,
529 'earned_marks' => 0,
530 'attempt_status' => 'attempt_ended',
531 'attempt_ended_at' => date( 'Y-m-d H:i:s', tutor_time() ),
532 );
533
534 do_action( 'tutor_quiz_before_finish', $attempt_id, $quiz_id, $attempt->user_id );
535 $wpdb->update( $wpdb->prefix . 'tutor_quiz_attempts', $attempt_info, array( 'attempt_id' => $attempt_id ) );
536 do_action( 'tutor_quiz_finished', $attempt_id, $quiz_id, $attempt->user_id );
537
538 wp_redirect( tutor_utils()->input_old( '_wp_http_referer' ) );
539 }
540
541 /**
542 * Quiz timeout by ajax
543 */
544 public function tutor_quiz_timeout() {
545 tutils()->checking_nonce();
546
547 global $wpdb;
548
549 $quiz_id = (int) $_POST['quiz_id'];
550
551 // if(!tutor_utils()->can_user_manage('quiz', $quiz_id)) {
552 // wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
553 // }
554
555 $attempt = tutor_utils()->is_started_quiz( $quiz_id );
556
557 if ( $attempt ) {
558 $attempt_id = $attempt->attempt_id;
559
560 $data = array(
561 'attempt_status' => 'attempt_timeout',
562 'attempt_ended_at' => date( 'Y-m-d H:i:s', tutor_time() ),
563 );
564 $wpdb->update( $wpdb->prefix . 'tutor_quiz_attempts', $data, array( 'attempt_id' => $attempt->attempt_id ) );
565
566 do_action( 'tutor_quiz_timeout', $attempt_id, $quiz_id, $attempt->user_id );
567
568 wp_send_json_success();
569 }
570
571 wp_send_json_error( __( 'Quiz has been timeout already', 'tutor' ) );
572 }
573
574 /**
575 * Review the answer and change individual answer result
576 */
577
578 public function review_quiz_answer() {
579
580 tutor_utils()->checking_nonce();
581
582 global $wpdb;
583
584 $attempt_id = (int) sanitize_text_field($_POST['attempt_id']);
585 $context = sanitize_text_field($_POST['context']);
586 $attempt_answer_id = (int) sanitize_text_field($_POST['attempt_answer_id']);
587 $mark_as = sanitize_text_field($_POST['mark_as']);
588
589 if(!tutor_utils()->can_user_manage('attempt', $attempt_id) || !tutor_utils()->can_user_manage('attempt_answer', $attempt_answer_id)) {
590 wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
591 }
592
593 $attempt_answer = $wpdb->get_row(
594 $wpdb->prepare(
595 "SELECT * FROM {$wpdb->prefix}tutor_quiz_attempt_answers
596 WHERE attempt_answer_id = %d ",
597 $attempt_answer_id
598 )
599 );
600
601 $attempt = tutor_utils()->get_attempt($attempt_id);
602 $question = tutor_utils()->get_quiz_question_by_id($attempt_answer->question_id);
603 $course_id = $attempt->course_id;
604 $student_id = $attempt->user_id;
605 $previous_ans = $attempt_answer->is_correct;
606
607 do_action( 'tutor_quiz_review_answer_before', $attempt_answer_id, $attempt_id, $mark_as );
608
609 if ( $mark_as === 'correct' ) {
610
611 $answer_update_data = array(
612 'achieved_mark' => $attempt_answer->question_mark,
613 'is_correct' => 1,
614 );
615 $wpdb->update( $wpdb->prefix . 'tutor_quiz_attempt_answers', $answer_update_data, array( 'attempt_answer_id' => $attempt_answer_id ) );
616 if ( $previous_ans == 0 or $previous_ans == null ) {
617
618 // if previous answer was wrong or in review then add point as correct
619 $attempt_update_data = array(
620 'earned_marks' => $attempt->earned_marks + $attempt_answer->question_mark,
621 'is_manually_reviewed' => 1,
622 'manually_reviewed_at' => date( 'Y-m-d H:i:s', tutor_time() ),
623 );
624 }
625
626 if ($question->question_type === 'open_ended' || $question->question_type === 'short_answer' ){
627 $attempt_update_data['attempt_status'] = 'attempt_ended';
628 }
629 $wpdb->update($wpdb->prefix.'tutor_quiz_attempts', $attempt_update_data, array('attempt_id' => $attempt_id ));
630
631 } elseif($mark_as === 'incorrect') {
632
633 $answer_update_data = array(
634 'achieved_mark' => '0.00',
635 'is_correct' => 0,
636 );
637 $wpdb->update( $wpdb->prefix . 'tutor_quiz_attempt_answers', $answer_update_data, array( 'attempt_answer_id' => $attempt_answer_id ) );
638
639 if ( $previous_ans == 1 ) {
640
641 // if previous ans was right then mynus
642 $attempt_update_data = array(
643 'earned_marks' => $attempt->earned_marks - $attempt_answer->question_mark,
644 'is_manually_reviewed' => 1,
645 'manually_reviewed_at' => date( 'Y-m-d H:i:s', tutor_time() ),
646 );
647
648 }
649 if ( $question->question_type === 'open_ended' || $question->question_type === 'short_answer' ) {
650 $attempt_update_data['attempt_status'] = 'attempt_ended';
651 }
652
653 $wpdb->update( $wpdb->prefix . 'tutor_quiz_attempts', $attempt_update_data, array( 'attempt_id' => $attempt_id ) );
654 }
655 do_action('tutor_quiz_review_answer_after', $attempt_answer_id, $attempt_id, $mark_as);
656 do_action('tutor_quiz/answer/review/after', $attempt_answer_id, $course_id, $student_id);
657
658 ob_start();
659 tutor_load_template_from_custom_path(tutor()->path . '/views/quiz/attempt-details.php', array(
660 'attempt_id' => $attempt_id,
661 'user_id' => $student_id,
662 'context' => $context,
663 'back_url' => isset($_POST['back_url']) ? esc_url( $_POST['back_url'] ) : null
664 ));
665 wp_send_json_success( array('html' => ob_get_clean()) );
666 }
667
668 /**
669 * Save single quiz into database and send html response
670 */
671 public function tutor_quiz_save(){
672
673 tutor_utils()->checking_nonce();
674
675 // Prepare args
676 $topic_id = Input::post( 'topic_id', 0, Input::TYPE_INT );
677 $ex_quiz_id = Input::post( 'quiz_id', 0, Input::TYPE_INT );
678 $quiz_title = Input::post( 'quiz_title' );
679 $quiz_description = wp_kses( $_POST['quiz_description'], $this->allowed_html );
680 $next_order_id = tutor_utils()->get_next_course_content_order_id($topic_id, $ex_quiz_id);
681
682 // Check edit privilege
683 if( ! tutor_utils()->can_user_manage( 'topic', $topic_id ) ) {
684 wp_send_json_error( array(
685 'message' => __( 'Access Denied', 'tutor' ),
686 'data' => $_POST
687 ));
688 }
689
690 // Prepare quiz data to save in database
691 $post_arr = array(
692 'ID' => $ex_quiz_id,
693 'post_type' => 'tutor_quiz',
694 'post_title' => $quiz_title,
695 'post_content' => $quiz_description,
696 'post_status' => 'publish',
697 'post_author' => get_current_user_id(),
698 'post_parent' => $topic_id,
699 'menu_order' => $next_order_id,
700 );
701
702 // Insert quiz and run hook
703 $quiz_id = wp_insert_post( $post_arr );
704 do_action(( $ex_quiz_id ? 'tutor_quiz_updated' : 'tutor_initial_quiz_created' ), $quiz_id );
705
706 // Now save quiz settings
707 $quiz_option = tutor_utils()->sanitize_array( $_POST['quiz_option'] );
708 update_post_meta($quiz_id, 'tutor_quiz_option', $quiz_option);
709 do_action('tutor_quiz_settings_updated', $quiz_id);
710
711 // Generate quiz modal to show in modal
712 $output = $this->tutor_load_quiz_builder_modal(array(
713 'topic_id'=> $topic_id,
714 'quiz_id'=>$quiz_id
715 ), true);
716
717 // Generate quiz list to show under topic as sub list
718 ob_start();
719 tutor_load_template_from_custom_path(tutor()->path.'/views/fragments/quiz-list-single.php', array(
720 'quiz_id' => $quiz_id,
721 'topic_id' => $topic_id,
722 'quiz_title' => $quiz_title,
723 ), false);
724 $output_quiz_row = ob_get_clean();
725
726 wp_send_json_success(array(
727 'output' => $output,
728 'output_quiz_row' => $output_quiz_row
729 ));
730 }
731
732 public function tutor_delete_quiz_by_id(){
733 tutor_utils()->checking_nonce();
734
735 global $wpdb;
736
737 $quiz_id = (int) $_POST['quiz_id'];
738 $post = get_post( $quiz_id );
739
740 if ( ! tutils()->can_user_manage( 'quiz', $quiz_id ) ) {
741 wp_send_json_error( array( 'message' => __( 'Access Denied', 'tutor' ) ) );
742 }
743
744 if ( $post->post_type === 'tutor_quiz' ) {
745 do_action( 'tutor_delete_quiz_before', $quiz_id );
746
747 $wpdb->delete( $wpdb->prefix . 'tutor_quiz_attempts', array( 'quiz_id' => $quiz_id ) );
748 $wpdb->delete( $wpdb->prefix . 'tutor_quiz_attempt_answers', array( 'quiz_id' => $quiz_id ) );
749
750 $questions_ids = $wpdb->get_col( $wpdb->prepare( "SELECT question_id FROM {$wpdb->prefix}tutor_quiz_questions WHERE quiz_id = %d ", $quiz_id ) );
751
752 if ( is_array( $questions_ids ) && count( $questions_ids ) ) {
753 $in_question_ids = "'" . implode( "','", $questions_ids ) . "'";
754 $wpdb->query( "DELETE FROM {$wpdb->prefix}tutor_quiz_question_answers WHERE belongs_question_id IN({$in_question_ids}) " );
755 }
756
757 $wpdb->delete( $wpdb->prefix . 'tutor_quiz_questions', array( 'quiz_id' => $quiz_id ) );
758
759 wp_delete_post( $quiz_id, true );
760
761 do_action( 'tutor_delete_quiz_after', $quiz_id );
762
763 wp_send_json_success();
764 }
765
766 wp_send_json_error();
767 }
768
769 /**
770 * Load quiz Modal on add/edit click
771 *
772 * @since v.1.0.0
773 */
774 public function tutor_load_quiz_builder_modal($params=array(), $ret=false){
775 tutor_utils()->checking_nonce();
776
777 $data = array_merge($_POST, $params);
778 $quiz_id = isset($data['quiz_id']) ? sanitize_text_field ($data['quiz_id']) : 0;
779 $topic_id = isset($data['topic_id'])?sanitize_text_field( $data['topic_id'] ):0;
780 $quiz = $quiz_id ? get_post($quiz_id) : null;
781
782 if($quiz_id && !tutor_utils()->can_user_manage('quiz', $quiz_id)) {
783 wp_send_json_error( array('message'=>__('Quiz Permission Denied', 'tutor')) );
784 }
785
786 ob_start();
787 include tutor()->path . 'views/modal/edit_quiz.php';
788 $output = ob_get_clean();
789
790 if($ret) {
791 return $output;
792 }
793
794 wp_send_json_success(array('output' => $output));
795 }
796
797 /**
798 * Load quiz question form for quiz
799 *
800 * @since v.1.0.0
801 */
802 public function tutor_quiz_builder_get_question_form(){
803 tutor_utils()->checking_nonce();
804
805 global $wpdb;
806 $quiz_id = sanitize_text_field($_POST['quiz_id']);
807 $topic_id = isset( $_POST['topic_id'] ) ? (int)$_POST['topic_id'] : 0;
808 $question_id = sanitize_text_field(tutor_utils()->avalue_dot('question_id', $_POST));
809
810 // check if the user can manage the quiz
811 if(!tutor_utils()->can_user_manage('quiz', $quiz_id)) {
812 wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
813 }
814
815 // If question ID not provided, then create new before rendering the form
816 if ( ! $question_id){
817 $next_question_id = tutor_utils()->quiz_next_question_id();
818 $next_question_order = tutor_utils()->quiz_next_question_order_id($quiz_id);
819 $question_title = __('Question', 'tutor').' '.$next_question_id;
820
821 $new_question_data = array(
822 'quiz_id' => $quiz_id,
823 'question_title' => $question_title,
824 'question_description' => '',
825 'question_type' => 'true_false',
826 'question_mark' => 1,
827 'question_settings' => maybe_serialize(array()),
828 'question_order' => esc_sql( $next_question_order ) ,
829 );
830
831 $wpdb->insert( $wpdb->prefix . 'tutor_quiz_questions', $new_question_data );
832 $question_id = $wpdb->insert_id;
833
834
835
836 // Add default true/false options for this question since it is by default true/false type.
837 $question_array = array(
838 $question_id => array(
839 'Question' => $question_title,
840 'question_type' => 'true_false',
841 'question_mark' => '1.00',
842 'question_description' => '',
843 )
844 );
845
846 $answer_array = array(
847 $question_id=>array(
848 'true_false' => true
849 )
850 );
851
852 $this->tutor_save_quiz_answer_options($question_array, $answer_array, false);
853 }
854
855 // Now get all data by this question id
856 $question = $wpdb->get_row($wpdb->prepare(
857 "SELECT * FROM {$wpdb->prefix}tutor_quiz_questions
858 WHERE question_id = %d ",
859 $question_id
860 ));
861
862 // Render the question form finally
863 ob_start();
864 require tutor()->path.'views/modal/question_form.php';
865 $output = ob_get_clean();
866
867 wp_send_json_success( array( 'output' => $output ) );
868 }
869
870 public function tutor_quiz_modal_update_question(){
871 tutor_utils()->checking_nonce();
872
873 global $wpdb;
874 $question_data = $_POST['tutor_quiz_question'];
875 $requires_answeres = array(
876 'multiple_choice',
877 'single_choice',
878 'true_false',
879 'fill_in_the_blank',
880 'matching',
881 'image_matching',
882 'image_answering',
883 'ordering'
884 );
885
886 $need_correct = array(
887 'multiple_choice',
888 'single_choice',
889 'true_false'
890 );
891
892 foreach ( $question_data as $question_id => $question ) {
893
894 // Make sure the quiz has answers
895 if( isset( $question['question_type'] ) && in_array($question['question_type'], $requires_answeres ) ) {
896 $require_correct = in_array($question['question_type'], $need_correct);
897 $all_answers = $this->get_answers_by_q_id($question_id, $question['question_type']);
898 $correct_answers = $this->get_answers_by_q_id($question_id, $question['question_type'], $require_correct);
899
900 if(!empty($all_answers) && empty($correct_answers)) {
901 wp_send_json_error( array('message' => __('Please make sure the question has answer')) );
902 exit;
903 }
904 }
905
906 if(!tutor_utils()->can_user_manage('question', $question_id)) {
907 continue;
908 }
909
910 $question_title = sanitize_text_field( $question['question_title'] ?? '' );
911 //$question_description = wp_kses( $question['question_description'] ?? '', $this->allowed_html ); // sanitize_text_field($question['question_description']);
912 $question_description = sanitize_post( $question['question_description'] );
913 $question_type = sanitize_text_field( $question['question_type'] ?? '' );
914 $question_mark = sanitize_text_field( $question['question_mark'] ?? '' );
915
916 unset( $question['question_title'] );
917 unset( $question['question_description'] );
918
919 $data = array(
920 'question_title' => $question_title,
921 'question_description' => $question_description,
922 'question_type' => $question_type,
923 'question_mark' => $question_mark,
924 'question_settings' => maybe_serialize( $question ),
925 );
926
927 $wpdb->update( $wpdb->prefix . 'tutor_quiz_questions', $data, array( 'question_id' => $question_id ) );
928 }
929
930 wp_send_json_success();
931 }
932
933 public function tutor_quiz_builder_question_delete(){
934 tutor_utils()->checking_nonce();
935
936 global $wpdb;
937
938 $question_id = sanitize_text_field(tutor_utils()->avalue_dot('question_id', $_POST));
939
940 if(!tutor_utils()->can_user_manage('question', $question_id)) {
941 wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
942 }
943
944 if ( $question_id ) {
945 $wpdb->delete( $wpdb->prefix . 'tutor_quiz_questions', array( 'question_id' => esc_sql( $question_id ) ) );
946 }
947
948 wp_send_json_success();
949 }
950
951 /**
952 * Get answers options form for quiz question
953 *
954 * @since v.1.0.0
955 */
956 public function tutor_quiz_question_answer_editor(){
957 tutor_utils()->checking_nonce();
958
959 $question_id = sanitize_text_field($_POST['question_id']);
960 $answer_id = (int) sanitize_text_field(tutor_utils()->array_get('answer_id', $_POST));
961 $question = tutor_utils()->avalue_dot($question_id, $_POST['tutor_quiz_question']);
962 $question_type = $question['question_type'];
963
964 if(!tutor_utils()->can_user_manage('question', $question_id)) {
965 wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
966 }
967
968 if($answer_id) {
969 $old_answer = tutor_utils()->get_answer_by_id($answer_id);
970 foreach ($old_answer as $old_answer);
971 }
972
973 ob_start();
974 include tutor()->path.'views/modal/question_answer_form.php';
975 $output = ob_get_clean();
976
977 wp_send_json_success(array('output' => $output));
978 }
979
980 public function tutor_save_quiz_answer_options($questions=null, $answers=null, $response=true){
981 tutor_utils()->checking_nonce();
982
983 global $wpdb;
984
985 $questions = $questions ? $questions : $_POST['tutor_quiz_question'];
986 $answers = $answers ? $answers : $_POST['quiz_answer'];
987
988 foreach ( $answers as $question_id => $answer ) {
989
990 if(!tutor_utils()->can_user_manage('question', $question_id)) {
991 continue;
992 }
993
994 $question = tutor_utils()->avalue_dot( $question_id, $questions );
995 $question_type = $question['question_type'];
996
997 // Getting next sorting order
998 $next_order_id = (int) $wpdb->get_var(
999 $wpdb->prepare(
1000 "SELECT MAX(answer_order)
1001 FROM {$wpdb->prefix}tutor_quiz_question_answers
1002 where belongs_question_id = %d
1003 AND belongs_question_type = %s ",
1004 $question_id,
1005 esc_sql( $question_type )
1006 )
1007 );
1008
1009 $next_order_id = $next_order_id + 1;
1010
1011 if ( $question ) {
1012 if ( $question_type === 'true_false' ) {
1013 $wpdb->delete(
1014 $wpdb->prefix . 'tutor_quiz_question_answers',
1015 array(
1016 'belongs_question_id' => $question_id,
1017 'belongs_question_type' => $question_type,
1018 )
1019 );
1020 $data_true_false = array(
1021 array(
1022 'belongs_question_id' => esc_sql( $question_id ),
1023 'belongs_question_type' => $question_type,
1024 'answer_title' => __( 'True', 'tutor' ),
1025 'is_correct' => $answer['true_false'] == 'true' ? 1 : 0,
1026 'answer_two_gap_match' => 'true',
1027 ),
1028 array(
1029 'belongs_question_id' => esc_sql( $question_id ),
1030 'belongs_question_type' => $question_type,
1031 'answer_title' => __('False', 'tutor'),
1032 'is_correct' => $answer['true_false'] == 'false' ? 0 : 1,
1033 'answer_two_gap_match' => 'false',
1034 ),
1035 );
1036
1037 foreach ( $data_true_false as $true_false_data ) {
1038 $wpdb->insert( $wpdb->prefix . 'tutor_quiz_question_answers', $true_false_data );
1039 }
1040 } elseif ( $question_type === 'multiple_choice' || $question_type === 'single_choice' || $question_type === 'ordering' ||
1041 $question_type === 'matching' || $question_type === 'image_matching' || $question_type === 'image_answering' ) {
1042
1043 $answer_data = array(
1044 'belongs_question_id' => sanitize_text_field( $question_id ),
1045 'belongs_question_type' => $question_type,
1046 'answer_title' => sanitize_text_field( $answer['answer_title'] ),
1047 'image_id' => isset( $answer['image_id'] ) ? $answer['image_id'] : 0,
1048 'answer_view_format' => isset( $answer['answer_view_format'] ) ? $answer['answer_view_format'] : 0,
1049 'answer_order' => $next_order_id,
1050 );
1051 if ( isset( $answer['matched_answer_title'] ) ) {
1052 $answer_data['answer_two_gap_match'] = sanitize_text_field( $answer['matched_answer_title'] );
1053 }
1054
1055 $wpdb->insert( $wpdb->prefix . 'tutor_quiz_question_answers', $answer_data );
1056
1057 } elseif ( $question_type === 'fill_in_the_blank' ) {
1058 $wpdb->delete(
1059 $wpdb->prefix . 'tutor_quiz_question_answers',
1060 array(
1061 'belongs_question_id' => $question_id,
1062 'belongs_question_type' => $question_type,
1063 )
1064 );
1065 $answer_data = array(
1066 'belongs_question_id' => sanitize_text_field( $question_id ),
1067 'belongs_question_type' => $question_type,
1068 'answer_title' => sanitize_text_field( $answer['answer_title'] ),
1069 'answer_two_gap_match' => isset( $answer['answer_two_gap_match'] ) ? sanitize_text_field( trim( $answer['answer_two_gap_match'] ) ) : null,
1070 );
1071 $wpdb->insert( $wpdb->prefix . 'tutor_quiz_question_answers', $answer_data );
1072 }
1073 }
1074 }
1075
1076 // Send response to browser if not internal call
1077 if($response) {
1078 wp_send_json_success();
1079 exit;
1080 }
1081 }
1082
1083 /**
1084 * Tutor Update Answer
1085 *
1086 * @since v.1.0.0
1087 */
1088 public function tutor_update_quiz_answer_options(){
1089 tutor_utils()->checking_nonce();
1090
1091 global $wpdb;
1092
1093 $answer_id = (int) $_POST['tutor_quiz_answer_id'];
1094
1095 if(!tutor_utils()->can_user_manage('quiz_answer', $answer_id)) {
1096 wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
1097 }
1098
1099 $questions = tutor_sanitize_data( $_POST['tutor_quiz_question'] );
1100 $answers = tutor_sanitize_data( $_POST['quiz_answer'] );
1101
1102 foreach ( $answers as $question_id => $answer ) {
1103 $question = tutor_utils()->avalue_dot( $question_id, $questions );
1104 $question_type = $question['question_type'];
1105
1106 if ( $question ) {
1107 if ( $question_type === 'multiple_choice' || $question_type === 'single_choice' || $question_type === 'ordering' || $question_type === 'matching' || $question_type === 'image_matching' || $question_type === 'fill_in_the_blank' || $question_type === 'image_answering' ) {
1108
1109 $answer_data = array(
1110 'belongs_question_id' => $question_id,
1111 'belongs_question_type' => $question_type,
1112 'answer_title' => sanitize_text_field( $answer['answer_title'] ),
1113 'image_id' => isset( $answer['image_id'] ) ? $answer['image_id'] : 0,
1114 'answer_view_format' => isset( $answer['answer_view_format'] ) ? sanitize_text_field( $answer['answer_view_format'] ) : '',
1115 );
1116 if ( isset( $answer['matched_answer_title'] ) ) {
1117 $answer_data['answer_two_gap_match'] = sanitize_text_field( $answer['matched_answer_title'] );
1118 }
1119
1120 if ( $question_type === 'fill_in_the_blank' ) {
1121 $answer_data['answer_two_gap_match'] = isset( $answer['answer_two_gap_match'] ) ? sanitize_text_field( trim( $answer['answer_two_gap_match'] ) ) : null;
1122 }
1123
1124 $wpdb->update( $wpdb->prefix . 'tutor_quiz_question_answers', $answer_data, array( 'answer_id' => $answer_id ) );
1125 }
1126 }
1127 }
1128
1129 // die(print_r($_POST));
1130 wp_send_json_success();
1131 }
1132
1133 private function get_answers_by_q_id($question_id, $question_type, $is_correct=false) {
1134 global $wpdb;
1135
1136 $correct_clause = $is_correct ? ' AND is_correct=1 ' : '';
1137
1138 return $wpdb->get_results($wpdb->prepare(
1139 "SELECT * FROM {$wpdb->prefix}tutor_quiz_question_answers
1140 WHERE belongs_question_id = %d AND
1141 belongs_question_type = %s
1142 {$correct_clause}
1143 ORDER BY answer_order ASC;",
1144 $question_id,
1145 esc_sql( $question_type )
1146 ));
1147 }
1148
1149 public function tutor_quiz_builder_change_type(){
1150 tutor_utils()->checking_nonce();
1151
1152 global $wpdb;
1153 $question_id = sanitize_text_field( $_POST['question_id'] );
1154 $question_type = sanitize_text_field( $_POST['question_type'] );
1155
1156 if(!tutor_utils()->can_user_manage('question', $question_id)) {
1157 wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
1158 }
1159
1160 // Get question data by question ID
1161 $question = $wpdb->get_row($wpdb->prepare(
1162 "SELECT * FROM {$wpdb->prefix}tutor_quiz_questions
1163 WHERE question_id = %d ",
1164 $question_id
1165 ));
1166
1167 // Get answers by question ID
1168 $answers = $this->get_answers_by_q_id($question_id, $question_type);
1169
1170 ob_start();
1171 require tutor()->path . '/views/modal/question_answer_list.php';
1172 $output = ob_get_clean();
1173
1174 wp_send_json_success( array( 'output' => $output ) );
1175 }
1176
1177 public function tutor_quiz_builder_delete_answer(){
1178 tutor_utils()->checking_nonce();
1179
1180 global $wpdb;
1181 $answer_id = sanitize_text_field($_POST['answer_id']);
1182
1183 if(!tutor_utils()->can_user_manage('quiz_answer', $answer_id)) {
1184 wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
1185 }
1186
1187 $wpdb->delete( $wpdb->prefix . 'tutor_quiz_question_answers', array( 'answer_id' => esc_sql( $answer_id ) ) );
1188 wp_send_json_success();
1189 }
1190
1191 /**
1192 * Save quiz questions sorting
1193 */
1194 public function tutor_quiz_question_sorting(){
1195 tutor_utils()->checking_nonce();
1196
1197 global $wpdb;
1198
1199 $question_ids = tutor_utils()->avalue_dot( 'sorted_question_ids', tutor_sanitize_data($_POST) );
1200 if ( is_array( $question_ids ) && count( $question_ids ) ) {
1201 $i = 0;
1202 foreach ($question_ids as $key => $question_id){
1203 if(tutor_utils()->can_user_manage('question', $question_id)) {
1204 $i++;
1205 $wpdb->update( $wpdb->prefix . 'tutor_quiz_questions', array( 'question_order' => $i ), array( 'question_id' => $question_id ) );
1206 }
1207 }
1208 }
1209 }
1210
1211 /**
1212 * Save sorting data for quiz answers
1213 */
1214 public function tutor_quiz_answer_sorting(){
1215 tutor_utils()->checking_nonce();
1216
1217 global $wpdb;
1218
1219 if ( ! empty($_POST['sorted_answer_ids']) && is_array($_POST['sorted_answer_ids']) && count($_POST['sorted_answer_ids']) ){
1220 $answer_ids = $_POST['sorted_answer_ids'];
1221 $i = 0;
1222 foreach ($answer_ids as $key => $answer_id){
1223 if(tutor_utils()->can_user_manage('quiz_answer', $answer_id)) {
1224 $i++;
1225 $wpdb->update( $wpdb->prefix . 'tutor_quiz_question_answers', array( 'answer_order' => $i ), array( 'answer_id' => $answer_id ) );
1226 }
1227 }
1228 }
1229 }
1230
1231 /**
1232 * Mark answer as correct
1233 */
1234
1235 public function tutor_mark_answer_as_correct(){
1236 tutor_utils()->checking_nonce();
1237
1238 global $wpdb;
1239
1240 $answer_id = sanitize_text_field($_POST['answer_id']);
1241 // get question info.
1242 $belong_question = $wpdb->get_row( $wpdb->prepare(
1243 " SELECT belongs_question_id, belongs_question_type
1244 FROM {$wpdb->tutor_quiz_question_answers}
1245 WHERE answer_id = %d
1246 LIMIT 1
1247 ",
1248 $answer_id
1249 ) );
1250 if ( $belong_question ) {
1251 // if question found update all answer is_correct to 0 except post answer.
1252 $question_type = $belong_question->belongs_question_type;
1253 $question_id = $belong_question->belongs_question_id;
1254 if ( 'true_false' === $question_type || 'single_choice' === $question_type ) {
1255 $update = $wpdb->query( $wpdb->prepare(
1256 "UPDATE {$wpdb->tutor_quiz_question_answers}
1257 SET is_correct = 0
1258 WHERE belongs_question_id = %d
1259 AND answer_id != %d
1260 ",
1261 $question_id,
1262 $answer_id
1263 ) );
1264 }
1265 }
1266
1267 $inputValue = sanitize_text_field($_POST['inputValue']);
1268
1269 if(!tutor_utils()->can_user_manage('quiz_answer', $answer_id)) {
1270 wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
1271 }
1272
1273 $answer = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}tutor_quiz_question_answers WHERE answer_id = %d LIMIT 0,1 ;", $answer_id ) );
1274 if ( $answer->belongs_question_type === 'single_choice' ) {
1275 $wpdb->update( $wpdb->prefix . 'tutor_quiz_question_answers', array( 'is_correct' => 0 ), array( 'belongs_question_id' => esc_sql( $answer->belongs_question_id ) ) );
1276 }
1277 $wpdb->update( $wpdb->prefix . 'tutor_quiz_question_answers', array( 'is_correct' => esc_sql( $inputValue ) ), array( 'answer_id' => esc_sql( $answer_id ) ) );
1278 }
1279
1280 //=========================//
1281 // Front end stuffs
1282 //=========================//
1283
1284 /**
1285 * Rendering quiz for frontend
1286 *
1287 * @since v.1.0.0
1288 */
1289
1290 public function tutor_render_quiz_content() {
1291
1292 tutor_utils()->checking_nonce();
1293
1294 $quiz_id = (int) tutor_utils()->avalue_dot( 'quiz_id', $_POST );
1295
1296 if(!tutor_utils()->has_enrolled_content_access('quiz', $quiz_id)) {
1297 wp_send_json_error( array('message'=>__('Access Denied.', 'tutor')) );
1298 }
1299
1300 ob_start();
1301 global $post;
1302
1303 $post = get_post( $quiz_id );
1304 setup_postdata( $post );
1305 // tutor_lesson_content();
1306
1307 single_quiz_contents();
1308 wp_reset_postdata();
1309
1310 $html = ob_get_clean();
1311 wp_send_json_success( array( 'html' => $html ) );
1312 }
1313
1314 /**
1315 * Get attempt details
1316 *
1317 * @param int $attempt_id, required attempt id to get details
1318 *
1319 * @return mixed, object on success, null on failure
1320 */
1321 public static function attempt_details( int $attempt_id ) {
1322 global $wpdb;
1323 $attempt_details = $wpdb->get_row(
1324 $wpdb->prepare(
1325 "SELECT *FROM {$wpdb->prefix}tutor_quiz_attempts
1326 WHERE attempt_id = %d",
1327 $attempt_id
1328 )
1329 );
1330 return $attempt_details;
1331 }
1332
1333 /**
1334 * Update attempt info
1335 *
1336 * @param $attempt_info, serialize data
1337 *
1338 * @return bool, true on success, false on failure
1339 */
1340 public static function update_attempt_info( int $attempt_id, $attempt_info ) {
1341 global $wpdb;
1342 $table = $wpdb->prefix . 'tutor_quiz_attempts';
1343 $update_info = $wpdb->update(
1344 $table,
1345 array( 'attempt_info' => $attempt_info ),
1346 array( 'attempt_id' => $attempt_id )
1347 );
1348 return $update_info ? true : false;
1349 }
1350
1351 }
1352