PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 2.0.8
Tutor LMS – eLearning and online course solution v2.0.8
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 4 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_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 4 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 4 years ago Question_Answers_List.php 4 years ago Quiz.php 4 years ago Quiz_Attempts_List.php 4 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 4 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
1351 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 }
478
479 $wpdb->insert( $wpdb->prefix . 'tutor_quiz_attempt_answers', $answers_data );
480 $review_required = true;
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 = sanitize_text_field($_POST['topic_id']);
677 $ex_quiz_id = isset($_POST['quiz_id']) ? sanitize_text_field($_POST['quiz_id']) : 0;
678 $quiz_title = sanitize_text_field($_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(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_type = sanitize_text_field( $question['question_type'] );
913 $question_mark = sanitize_text_field( $question['question_mark'] );
914
915 unset( $question['question_title'] );
916 unset( $question['question_description'] );
917
918 $data = array(
919 'question_title' => $question_title,
920 'question_description' => $question_description,
921 'question_type' => $question_type,
922 'question_mark' => $question_mark,
923 'question_settings' => maybe_serialize( $question ),
924 );
925
926 $wpdb->update( $wpdb->prefix . 'tutor_quiz_questions', $data, array( 'question_id' => $question_id ) );
927 }
928
929 wp_send_json_success();
930 }
931
932 public function tutor_quiz_builder_question_delete(){
933 tutor_utils()->checking_nonce();
934
935 global $wpdb;
936
937 $question_id = sanitize_text_field(tutor_utils()->avalue_dot('question_id', $_POST));
938
939 if(!tutor_utils()->can_user_manage('question', $question_id)) {
940 wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
941 }
942
943 if ( $question_id ) {
944 $wpdb->delete( $wpdb->prefix . 'tutor_quiz_questions', array( 'question_id' => esc_sql( $question_id ) ) );
945 }
946
947 wp_send_json_success();
948 }
949
950 /**
951 * Get answers options form for quiz question
952 *
953 * @since v.1.0.0
954 */
955 public function tutor_quiz_question_answer_editor(){
956 tutor_utils()->checking_nonce();
957
958 $question_id = sanitize_text_field($_POST['question_id']);
959 $answer_id = (int) sanitize_text_field(tutor_utils()->array_get('answer_id', $_POST));
960 $question = tutor_utils()->avalue_dot($question_id, $_POST['tutor_quiz_question']);
961 $question_type = $question['question_type'];
962
963 if(!tutor_utils()->can_user_manage('question', $question_id)) {
964 wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
965 }
966
967 if($answer_id) {
968 $old_answer = tutor_utils()->get_answer_by_id($answer_id);
969 foreach ($old_answer as $old_answer);
970 }
971
972 ob_start();
973 include tutor()->path.'views/modal/question_answer_form.php';
974 $output = ob_get_clean();
975
976 wp_send_json_success(array('output' => $output));
977 }
978
979 public function tutor_save_quiz_answer_options($questions=null, $answers=null, $response=true){
980 tutor_utils()->checking_nonce();
981
982 global $wpdb;
983
984 $questions = $questions ? $questions : $_POST['tutor_quiz_question'];
985 $answers = $answers ? $answers : $_POST['quiz_answer'];
986
987 foreach ( $answers as $question_id => $answer ) {
988
989 if(!tutor_utils()->can_user_manage('question', $question_id)) {
990 continue;
991 }
992
993 $question = tutor_utils()->avalue_dot( $question_id, $questions );
994 $question_type = $question['question_type'];
995
996 // Getting next sorting order
997 $next_order_id = (int) $wpdb->get_var(
998 $wpdb->prepare(
999 "SELECT MAX(answer_order)
1000 FROM {$wpdb->prefix}tutor_quiz_question_answers
1001 where belongs_question_id = %d
1002 AND belongs_question_type = %s ",
1003 $question_id,
1004 esc_sql( $question_type )
1005 )
1006 );
1007
1008 $next_order_id = $next_order_id + 1;
1009
1010 if ( $question ) {
1011 if ( $question_type === 'true_false' ) {
1012 $wpdb->delete(
1013 $wpdb->prefix . 'tutor_quiz_question_answers',
1014 array(
1015 'belongs_question_id' => $question_id,
1016 'belongs_question_type' => $question_type,
1017 )
1018 );
1019 $data_true_false = array(
1020 array(
1021 'belongs_question_id' => esc_sql( $question_id ),
1022 'belongs_question_type' => $question_type,
1023 'answer_title' => __( 'True', 'tutor' ),
1024 'is_correct' => $answer['true_false'] == 'true' ? 1 : 0,
1025 'answer_two_gap_match' => 'true',
1026 ),
1027 array(
1028 'belongs_question_id' => esc_sql( $question_id ),
1029 'belongs_question_type' => $question_type,
1030 'answer_title' => __('False', 'tutor'),
1031 'is_correct' => $answer['true_false'] == 'false' ? 0 : 1,
1032 'answer_two_gap_match' => 'false',
1033 ),
1034 );
1035
1036 foreach ( $data_true_false as $true_false_data ) {
1037 $wpdb->insert( $wpdb->prefix . 'tutor_quiz_question_answers', $true_false_data );
1038 }
1039 } elseif ( $question_type === 'multiple_choice' || $question_type === 'single_choice' || $question_type === 'ordering' ||
1040 $question_type === 'matching' || $question_type === 'image_matching' || $question_type === 'image_answering' ) {
1041
1042 $answer_data = array(
1043 'belongs_question_id' => sanitize_text_field( $question_id ),
1044 'belongs_question_type' => $question_type,
1045 'answer_title' => sanitize_text_field( $answer['answer_title'] ),
1046 'image_id' => isset( $answer['image_id'] ) ? $answer['image_id'] : 0,
1047 'answer_view_format' => isset( $answer['answer_view_format'] ) ? $answer['answer_view_format'] : 0,
1048 'answer_order' => $next_order_id,
1049 );
1050 if ( isset( $answer['matched_answer_title'] ) ) {
1051 $answer_data['answer_two_gap_match'] = sanitize_text_field( $answer['matched_answer_title'] );
1052 }
1053
1054 $wpdb->insert( $wpdb->prefix . 'tutor_quiz_question_answers', $answer_data );
1055
1056 } elseif ( $question_type === 'fill_in_the_blank' ) {
1057 $wpdb->delete(
1058 $wpdb->prefix . 'tutor_quiz_question_answers',
1059 array(
1060 'belongs_question_id' => $question_id,
1061 'belongs_question_type' => $question_type,
1062 )
1063 );
1064 $answer_data = array(
1065 'belongs_question_id' => sanitize_text_field( $question_id ),
1066 'belongs_question_type' => $question_type,
1067 'answer_title' => sanitize_text_field( $answer['answer_title'] ),
1068 'answer_two_gap_match' => isset( $answer['answer_two_gap_match'] ) ? sanitize_text_field( trim( $answer['answer_two_gap_match'] ) ) : null,
1069 );
1070 $wpdb->insert( $wpdb->prefix . 'tutor_quiz_question_answers', $answer_data );
1071 }
1072 }
1073 }
1074
1075 // Send response to browser if not internal call
1076 if($response) {
1077 wp_send_json_success();
1078 exit;
1079 }
1080 }
1081
1082 /**
1083 * Tutor Update Answer
1084 *
1085 * @since v.1.0.0
1086 */
1087 public function tutor_update_quiz_answer_options(){
1088 tutor_utils()->checking_nonce();
1089
1090 global $wpdb;
1091
1092 $answer_id = (int) $_POST['tutor_quiz_answer_id'];
1093
1094 if(!tutor_utils()->can_user_manage('quiz_answer', $answer_id)) {
1095 wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
1096 }
1097
1098 $questions = tutor_sanitize_data( $_POST['tutor_quiz_question'] );
1099 $answers = tutor_sanitize_data( $_POST['quiz_answer'] );
1100
1101 foreach ( $answers as $question_id => $answer ) {
1102 $question = tutor_utils()->avalue_dot( $question_id, $questions );
1103 $question_type = $question['question_type'];
1104
1105 if ( $question ) {
1106 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' ) {
1107
1108 $answer_data = array(
1109 'belongs_question_id' => $question_id,
1110 'belongs_question_type' => $question_type,
1111 'answer_title' => sanitize_text_field( $answer['answer_title'] ),
1112 'image_id' => isset( $answer['image_id'] ) ? $answer['image_id'] : 0,
1113 'answer_view_format' => isset( $answer['answer_view_format'] ) ? sanitize_text_field( $answer['answer_view_format'] ) : '',
1114 );
1115 if ( isset( $answer['matched_answer_title'] ) ) {
1116 $answer_data['answer_two_gap_match'] = sanitize_text_field( $answer['matched_answer_title'] );
1117 }
1118
1119 if ( $question_type === 'fill_in_the_blank' ) {
1120 $answer_data['answer_two_gap_match'] = isset( $answer['answer_two_gap_match'] ) ? sanitize_text_field( trim( $answer['answer_two_gap_match'] ) ) : null;
1121 }
1122
1123 $wpdb->update( $wpdb->prefix . 'tutor_quiz_question_answers', $answer_data, array( 'answer_id' => $answer_id ) );
1124 }
1125 }
1126 }
1127
1128 // die(print_r($_POST));
1129 wp_send_json_success();
1130 }
1131
1132 private function get_answers_by_q_id($question_id, $question_type, $is_correct=false) {
1133 global $wpdb;
1134
1135 $correct_clause = $is_correct ? ' AND is_correct=1 ' : '';
1136
1137 return $wpdb->get_results($wpdb->prepare(
1138 "SELECT * FROM {$wpdb->prefix}tutor_quiz_question_answers
1139 WHERE belongs_question_id = %d AND
1140 belongs_question_type = %s
1141 {$correct_clause}
1142 ORDER BY answer_order ASC;",
1143 $question_id,
1144 esc_sql( $question_type )
1145 ));
1146 }
1147
1148 public function tutor_quiz_builder_change_type(){
1149 tutor_utils()->checking_nonce();
1150
1151 global $wpdb;
1152 $question_id = sanitize_text_field( $_POST['question_id'] );
1153 $question_type = sanitize_text_field( $_POST['question_type'] );
1154
1155 if(!tutor_utils()->can_user_manage('question', $question_id)) {
1156 wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
1157 }
1158
1159 // Get question data by question ID
1160 $question = $wpdb->get_row($wpdb->prepare(
1161 "SELECT * FROM {$wpdb->prefix}tutor_quiz_questions
1162 WHERE question_id = %d ",
1163 $question_id
1164 ));
1165
1166 // Get answers by question ID
1167 $answers = $this->get_answers_by_q_id($question_id, $question_type);
1168
1169 ob_start();
1170 require tutor()->path . '/views/modal/question_answer_list.php';
1171 $output = ob_get_clean();
1172
1173 wp_send_json_success( array( 'output' => $output ) );
1174 }
1175
1176 public function tutor_quiz_builder_delete_answer(){
1177 tutor_utils()->checking_nonce();
1178
1179 global $wpdb;
1180 $answer_id = sanitize_text_field($_POST['answer_id']);
1181
1182 if(!tutor_utils()->can_user_manage('quiz_answer', $answer_id)) {
1183 wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
1184 }
1185
1186 $wpdb->delete( $wpdb->prefix . 'tutor_quiz_question_answers', array( 'answer_id' => esc_sql( $answer_id ) ) );
1187 wp_send_json_success();
1188 }
1189
1190 /**
1191 * Save quiz questions sorting
1192 */
1193 public function tutor_quiz_question_sorting(){
1194 tutor_utils()->checking_nonce();
1195
1196 global $wpdb;
1197
1198 $question_ids = tutor_utils()->avalue_dot( 'sorted_question_ids', tutor_sanitize_data($_POST) );
1199 if ( is_array( $question_ids ) && count( $question_ids ) ) {
1200 $i = 0;
1201 foreach ($question_ids as $key => $question_id){
1202 if(tutor_utils()->can_user_manage('question', $question_id)) {
1203 $i++;
1204 $wpdb->update( $wpdb->prefix . 'tutor_quiz_questions', array( 'question_order' => $i ), array( 'question_id' => $question_id ) );
1205 }
1206 }
1207 }
1208 }
1209
1210 /**
1211 * Save sorting data for quiz answers
1212 */
1213 public function tutor_quiz_answer_sorting(){
1214 tutor_utils()->checking_nonce();
1215
1216 global $wpdb;
1217
1218 if ( ! empty($_POST['sorted_answer_ids']) && is_array($_POST['sorted_answer_ids']) && count($_POST['sorted_answer_ids']) ){
1219 $answer_ids = $_POST['sorted_answer_ids'];
1220 $i = 0;
1221 foreach ($answer_ids as $key => $answer_id){
1222 if(tutor_utils()->can_user_manage('quiz_answer', $answer_id)) {
1223 $i++;
1224 $wpdb->update( $wpdb->prefix . 'tutor_quiz_question_answers', array( 'answer_order' => $i ), array( 'answer_id' => $answer_id ) );
1225 }
1226 }
1227 }
1228 }
1229
1230 /**
1231 * Mark answer as correct
1232 */
1233
1234 public function tutor_mark_answer_as_correct(){
1235 tutor_utils()->checking_nonce();
1236
1237 global $wpdb;
1238
1239 $answer_id = sanitize_text_field($_POST['answer_id']);
1240 // get question info.
1241 $belong_question = $wpdb->get_row( $wpdb->prepare(
1242 " SELECT belongs_question_id, belongs_question_type
1243 FROM {$wpdb->tutor_quiz_question_answers}
1244 WHERE answer_id = %d
1245 LIMIT 1
1246 ",
1247 $answer_id
1248 ) );
1249 if ( $belong_question ) {
1250 // if question found update all answer is_correct to 0 except post answer.
1251 $question_type = $belong_question->belongs_question_type;
1252 $question_id = $belong_question->belongs_question_id;
1253 if ( 'true_false' === $question_type || 'single_choice' === $question_type ) {
1254 $update = $wpdb->query( $wpdb->prepare(
1255 "UPDATE {$wpdb->tutor_quiz_question_answers}
1256 SET is_correct = 0
1257 WHERE belongs_question_id = %d
1258 AND answer_id != %d
1259 ",
1260 $question_id,
1261 $answer_id
1262 ) );
1263 }
1264 }
1265
1266 $inputValue = sanitize_text_field($_POST['inputValue']);
1267
1268 if(!tutor_utils()->can_user_manage('quiz_answer', $answer_id)) {
1269 wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
1270 }
1271
1272 $answer = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}tutor_quiz_question_answers WHERE answer_id = %d LIMIT 0,1 ;", $answer_id ) );
1273 if ( $answer->belongs_question_type === 'single_choice' ) {
1274 $wpdb->update( $wpdb->prefix . 'tutor_quiz_question_answers', array( 'is_correct' => 0 ), array( 'belongs_question_id' => esc_sql( $answer->belongs_question_id ) ) );
1275 }
1276 $wpdb->update( $wpdb->prefix . 'tutor_quiz_question_answers', array( 'is_correct' => esc_sql( $inputValue ) ), array( 'answer_id' => esc_sql( $answer_id ) ) );
1277 }
1278
1279 //=========================//
1280 // Front end stuffs
1281 //=========================//
1282
1283 /**
1284 * Rendering quiz for frontend
1285 *
1286 * @since v.1.0.0
1287 */
1288
1289 public function tutor_render_quiz_content() {
1290
1291 tutor_utils()->checking_nonce();
1292
1293 $quiz_id = (int) tutor_utils()->avalue_dot( 'quiz_id', $_POST );
1294
1295 if(!tutor_utils()->has_enrolled_content_access('quiz', $quiz_id)) {
1296 wp_send_json_error( array('message'=>__('Access Denied.', 'tutor')) );
1297 }
1298
1299 ob_start();
1300 global $post;
1301
1302 $post = get_post( $quiz_id );
1303 setup_postdata( $post );
1304 // tutor_lesson_content();
1305
1306 single_quiz_contents();
1307 wp_reset_postdata();
1308
1309 $html = ob_get_clean();
1310 wp_send_json_success( array( 'html' => $html ) );
1311 }
1312
1313 /**
1314 * Get attempt details
1315 *
1316 * @param int $attempt_id, required attempt id to get details
1317 *
1318 * @return mixed, object on success, null on failure
1319 */
1320 public static function attempt_details( int $attempt_id ) {
1321 global $wpdb;
1322 $attempt_details = $wpdb->get_row(
1323 $wpdb->prepare(
1324 "SELECT *FROM {$wpdb->prefix}tutor_quiz_attempts
1325 WHERE attempt_id = %d",
1326 $attempt_id
1327 )
1328 );
1329 return $attempt_details;
1330 }
1331
1332 /**
1333 * Update attempt info
1334 *
1335 * @param $attempt_info, serialize data
1336 *
1337 * @return bool, true on success, false on failure
1338 */
1339 public static function update_attempt_info( int $attempt_id, $attempt_info ) {
1340 global $wpdb;
1341 $table = $wpdb->prefix . 'tutor_quiz_attempts';
1342 $update_info = $wpdb->update(
1343 $table,
1344 array( 'attempt_info' => $attempt_info ),
1345 array( 'attempt_id' => $attempt_id )
1346 );
1347 return $update_info ? true : false;
1348 }
1349
1350 }
1351