PluginProbe
Tutor LMS – eLearning and online course solution / 1.3.5
Tutor LMS – eLearning and online course solution v1.3.5
4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 4.0.2 4.0.1 4.0.0 3.9.15 3.9.14 3.9.13 3.9.12 3.9.11 trunk 1.0.0 1.0.0-alpha 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 All 191 releases
tutor / classes / Quiz.php

Quiz.php in Tutor LMS – eLearning and online course solution 1.3.5, at classes/Quiz.php

1,095 lines 39.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Quize 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 class Quiz {
18
19 public function __construct() {
20 add_filter( "manage_tutor_quiz_posts_columns", array($this, 'add_column'), 10,1 );
21 add_action( "manage_tutor_quiz_posts_custom_column" , array($this, 'custom_question_column'), 10, 2 );
22
23 add_action( 'add_meta_boxes', array($this, 'register_meta_box') );
24 add_action('save_post_tutor_quiz', array($this, 'save_quiz_meta'));
25
26 //Depricated at alpha version
27 add_action('wp_ajax_tutor_load_quiz_modal', array($this, 'tutor_load_quiz_modal'));
28
29 add_action('wp_ajax_tutor_load_quiz_builder_modal', array($this, 'tutor_load_quiz_builder_modal'));
30 add_action('wp_ajax_tutor_add_quiz_to_post', array($this, 'tutor_add_quiz_to_post'));
31 add_action('wp_ajax_remove_quiz_from_post', array($this, 'remove_quiz_from_post'));
32
33 add_action('wp_ajax_tutor_quiz_timeout', array($this, 'tutor_quiz_timeout'));
34
35 //User take the quiz
36 add_action('template_redirect', array($this, 'start_the_quiz'));
37 add_action('template_redirect', array($this, 'answering_quiz'));
38 add_action('template_redirect', array($this, 'finishing_quiz_attempt'));
39
40 add_action('admin_action_review_quiz_answer', array($this, 'review_quiz_answer'));
41
42 /**
43 * New Design Quiz
44 */
45
46 add_action('wp_ajax_tutor_create_quiz_and_load_modal', array($this, 'tutor_create_quiz_and_load_modal'));
47 add_action('wp_ajax_tutor_delete_quiz_by_id', array($this, 'tutor_delete_quiz_by_id'));
48 add_action('wp_ajax_tutor_quiz_builder_quiz_update', array($this, 'tutor_quiz_builder_quiz_update'));
49 add_action('wp_ajax_tutor_load_edit_quiz_modal', array($this, 'tutor_load_edit_quiz_modal'));
50 add_action('wp_ajax_tutor_quiz_builder_get_question_form', array($this, 'tutor_quiz_builder_get_question_form'));
51 add_action('wp_ajax_tutor_quiz_modal_update_question', array($this, 'tutor_quiz_modal_update_question'));
52 add_action('wp_ajax_tutor_quiz_builder_question_delete', array($this, 'tutor_quiz_builder_question_delete'));
53 add_action('wp_ajax_tutor_quiz_add_question_answers', array($this, 'tutor_quiz_add_question_answers'));
54 add_action('wp_ajax_tutor_quiz_edit_question_answer', array($this, 'tutor_quiz_edit_question_answer'));
55 add_action('wp_ajax_tutor_save_quiz_answer_options', array($this, 'tutor_save_quiz_answer_options'));
56 add_action('wp_ajax_tutor_update_quiz_answer_options', array($this, 'tutor_update_quiz_answer_options'));
57 add_action('wp_ajax_tutor_quiz_builder_get_answers_by_question', array($this, 'tutor_quiz_builder_get_answers_by_question'));
58 add_action('wp_ajax_tutor_quiz_builder_delete_answer', array($this, 'tutor_quiz_builder_delete_answer'));
59 add_action('wp_ajax_tutor_quiz_question_sorting', array($this, 'tutor_quiz_question_sorting'));
60 add_action('wp_ajax_tutor_quiz_answer_sorting', array($this, 'tutor_quiz_answer_sorting'));
61 add_action('wp_ajax_tutor_mark_answer_as_correct', array($this, 'tutor_mark_answer_as_correct'));
62 add_action('wp_ajax_tutor_quiz_modal_update_settings', array($this, 'tutor_quiz_modal_update_settings'));
63
64
65
66 /**
67 * Frontend Stuff
68 */
69
70 add_action('wp_ajax_tutor_render_quiz_content', array($this, 'tutor_render_quiz_content'));
71
72 }
73
74 public function add_column($columns){
75 $date_col = $columns['date'];
76 unset($columns['date']);
77 $columns['quiz'] = __('Course', 'tutor');
78 $columns['questions'] = __('Questions', 'tutor');
79 $columns['date'] = $date_col;
80
81 return $columns;
82 }
83
84 public function custom_question_column($column, $post_id ){
85 if ($column === 'quiz'){
86 $quiz = tutor_utils()->get_course_by_quiz($post_id);
87
88 if ($quiz){
89 echo '<a href="'.admin_url('post.php?post='.$quiz->ID.'&action=edit').'">'.get_the_title($quiz->ID).'</a>';
90 }
91 }
92
93 if ($column === 'questions'){
94 echo tutor_utils()->total_questions_for_student_by_quiz($post_id);
95 }
96 }
97
98 public function register_meta_box(){
99 add_meta_box( 'tutor-quiz-questions', __( 'Questions', 'tutor' ), array($this, 'quiz_questions'), 'tutor_quiz' );
100 add_meta_box( 'tutor-quiz-settings', __( 'Settings', 'tutor' ), array($this, 'quiz_settings'), 'tutor_quiz' );
101 }
102
103 public function quiz_questions(){
104 include tutor()->path.'views/metabox/quiz_questions.php';
105 }
106
107 public function quiz_settings(){
108 include tutor()->path.'views/metabox/quizzes.php';
109 }
110
111 public function save_quiz_meta($post_ID){
112 if (isset($_POST['quiz_option'])){
113 $quiz_option = tutor_utils()->sanitize_array($_POST['quiz_option']);
114 update_post_meta($post_ID, 'tutor_quiz_option', $quiz_option);
115 }
116 }
117
118 /**
119 * @depricated at alpha version
120 * Check tutor_load_quiz_builder_modal instead of this method
121 */
122 public function tutor_load_quiz_modal(){
123 $quiz_for_post_id = (int) sanitize_text_field($_POST['quiz_for_post_id']);
124
125 $search_terms = sanitize_text_field(tutor_utils()->avalue_dot('search_terms', $_POST));
126 $quizzes = tutor_utils()->get_unattached_quiz(array('search_term' => $search_terms));
127
128 $output = '';
129 if ($quizzes){
130 foreach ($quizzes as $quiz){
131 $output .= "<p><label><input type='checkbox' name='quiz_for[{$quiz_for_post_id}][quiz_id][]' value='{$quiz->ID}' > {$quiz->post_title} </label></p>";
132 }
133 $output .= '<p class="quiz-search-suggest-text">Search the quiz to get specific quiz</p>';
134 }else{
135 $add_question_url = admin_url('post-new.php?post_type=tutor_quiz');
136 $output .= sprintf('No quiz available right now, please %s add some quiz %s', '<a href="'.$add_question_url.'" target="_blank">', '</a>' );
137 }
138
139 ob_start();
140 ?>
141 <div class="tutor-option-field-row">
142 <div class="tutor-option-field-label">
143 <label for="">
144 <?php _e('New quiz title', 'tutor'); ?>
145 </label>
146 </div>
147 <div class="tutor-option-field">
148 <input type="text" name="quiz_title" placeholder="<?php _e('Place quiz title to create new quiz', 'tutor'); ?>" >
149 <p class="desc"><?php _e('Provide a quiz title to create a quiz from here.'); ?></p>
150 </div>
151 </div>
152
153 <?php
154 $output .= ob_get_clean();
155
156 wp_send_json_success(array('output' => $output));
157 }
158
159 /**
160 * Tutor Quiz Builder Modal
161 */
162 public function tutor_load_quiz_builder_modal(){
163 ob_start();
164 include tutor()->path.'views/modal/add_quiz.php';
165 $output = ob_get_clean();
166
167 wp_send_json_success(array('output' => $output));
168
169 }
170
171 public function tutor_add_quiz_to_post(){
172 global $wpdb;
173
174 $quiz_data = tutor_utils()->avalue_dot('quiz_for', $_POST);
175
176 $output = '';
177 $post_id = (int) sanitize_text_field(tutor_utils()->avalue_dot('parent_post_id', $_POST)) ;
178 if ($quiz_data){
179 foreach ($quiz_data as $post_id => $quiz_ids_a);
180
181 $quiz_ids = tutor_utils()->avalue_dot('quiz_id', $quiz_ids_a);
182 foreach ($quiz_ids as $quiz_id){
183 $wpdb->update($wpdb->posts, array('post_parent' => $post_id), array('ID' => $quiz_id) );
184 }
185 }
186
187 $quiz_title = sanitize_text_field(tutor_utils()->avalue_dot('quiz_title', $_POST));
188 if ($quiz_title){
189 wp_insert_post(array(
190 'post_parent' => $post_id,
191 'post_title' => $quiz_title,
192 'post_type' => 'tutor_quiz',
193 'post_status' => 'publish',
194 ));
195 }
196
197 if ($post_id) {
198 ob_start();
199 $attached_quizzes = tutor_utils()->get_attached_quiz( $post_id );
200 if ( $attached_quizzes ) {
201 foreach ( $attached_quizzes as $attached_quiz ) {
202 ?>
203 <div id="added-quiz-id-<?php echo $attached_quiz->ID; ?>" class="added-quiz-item added-quiz-item-<?php echo $attached_quiz->ID; ?>" data-quiz-id="<?php echo $attached_quiz->ID; ?>">
204 <span class="quiz-icon"><i class="dashicons dashicons-clock"></i></span>
205 <span class="quiz-name">
206 <?php edit_post_link( $attached_quiz->post_title, null, null, $attached_quiz->ID ); ?>
207 </span>
208 <span class="quiz-control">
209 <a href="javascript:;" class="tutor-quiz-delete-btn"><i class="dashicons dashicons-trash"></i></a>
210 </span>
211 </div>
212 <?php
213 }
214 }
215 $output .= ob_get_clean();
216 }
217
218 wp_send_json_success(array('output' => $output));
219 }
220
221 public function remove_quiz_from_post(){
222 global $wpdb;
223 $quiz_id = (int) tutor_utils()->avalue_dot('quiz_id', $_POST);
224 $wpdb->update($wpdb->posts, array('post_parent' => 0), array('ID' => $quiz_id) );
225 wp_send_json_success();
226 }
227
228 /**
229 *
230 * Start Quiz from here...
231 *
232 * @since v.1.0.0
233 */
234
235 public function start_the_quiz(){
236 if ( ! isset($_POST['tutor_action']) || $_POST['tutor_action'] !== 'tutor_start_quiz' ){
237 return;
238 }
239 //Checking nonce
240 tutor_utils()->checking_nonce();
241
242 if ( ! is_user_logged_in()){
243 //TODO: need to set a view in the next version
244 die('Please sign in to do this operation');
245 }
246
247 global $wpdb;
248
249 $user_id = get_current_user_id();
250 $user = get_userdata($user_id);
251
252 $quiz_id = (int) sanitize_text_field($_POST['quiz_id']);
253
254 $quiz = get_post($quiz_id);
255 $course = tutor_utils()->get_course_by_quiz($quiz_id);
256 if ( empty($course->ID)){
257 die('There is something went wrong with course, please check if quiz attached with a course');
258 }
259
260 $date = date("Y-m-d H:i:s");
261
262 $tutor_quiz_option = maybe_unserialize(get_post_meta($quiz_id, 'tutor_quiz_option', true));
263 $attempts_allowed = tutor_utils()->get_quiz_option($quiz_id, 'attempts_allowed', 0);
264
265 $time_limit = tutor_utils()->get_quiz_option($quiz_id, 'time_limit.time_value');
266 $time_limit_seconds = 0;
267 $time_type = 'seconds';
268 if ($time_limit){
269 $time_type = tutor_utils()->get_quiz_option($quiz_id, 'time_limit.time_type');
270
271 switch ($time_type){
272 case 'seconds':
273 $time_limit_seconds = $time_limit;
274 break;
275 case 'minutes':
276 $time_limit_seconds = $time_limit * 60;
277 break;
278 case 'hours':
279 $time_limit_seconds = $time_limit * 60 * 60;
280 break;
281 case 'days':
282 $time_limit_seconds = $time_limit * 60 * 60 * 24;
283 break;
284 case 'weeks':
285 $time_limit_seconds = $time_limit * 60 * 60 * 24 * 7;
286 break;
287 }
288 }
289
290 $max_question_allowed = tutor_utils()->max_questions_for_take_quiz($quiz_id);
291 $tutor_quiz_option['time_limit']['time_limit_seconds'] = $time_limit_seconds;
292
293 $attempt_data = array(
294 'course_id' => $course->ID,
295 'quiz_id' => $quiz_id,
296 'user_id' => $user_id,
297 'total_questions' => $max_question_allowed,
298 'total_answered_questions' => 0,
299 'attempt_info' => maybe_serialize($tutor_quiz_option),
300 'attempt_status' => 'attempt_started',
301 'attempt_ip' => tutor_utils()->get_ip(),
302 'attempt_started_at' => $date,
303 );
304
305 $wpdb->insert($wpdb->prefix.'tutor_quiz_attempts', $attempt_data);
306 $attempt_id = (int) $wpdb->insert_id;
307
308 wp_redirect(get_permalink($quiz_id));
309 die();
310 }
311
312 public function answering_quiz(){
313 if ( ! isset($_POST['tutor_action']) || $_POST['tutor_action'] !== 'tutor_answering_quiz_question' ){
314 return;
315 }
316 //Checking nonce
317 tutor_utils()->checking_nonce();
318
319 $attempt_id = (int) sanitize_text_field(tutor_utils()->avalue_dot('attempt_id', $_POST));
320 $attempt = tutor_utils()->get_attempt($attempt_id);
321
322 $attempt_answers = isset($_POST['attempt']) ? $_POST['attempt'] : false;
323 if ( ! is_user_logged_in()){
324 die('Please sign in to do this operation');
325 }
326
327 global $wpdb;
328 $user_id = get_current_user_id();
329
330 if ($attempt_answers && is_array($attempt_answers) && count($attempt_answers)){
331 foreach ($attempt_answers as $attempt_id => $attempt_answers){
332
333 /**
334 * Get total marks of all question comes
335 */
336 $question_ids = tutor_utils()->avalue_dot('quiz_question_ids', $attempt_answers);
337 if (is_array($question_ids) && count($question_ids)){
338 $question_ids_string = "'".implode("','", $question_ids)."'";
339 $total_question_marks = $wpdb->get_var("SELECT SUM(question_mark) FROM {$wpdb->prefix}tutor_quiz_questions WHERE question_id IN({$question_ids_string}) ;");
340 $wpdb->update($wpdb->prefix.'tutor_quiz_attempts', array('total_marks' =>$total_question_marks ), array('attempt_id' => $attempt_id ));
341 }
342
343 if ( ! $attempt || $user_id != $attempt->user_id){
344 die('Operation not allowed, attempt not found or permission denied');
345 }
346
347 $quiz_answers = tutor_utils()->avalue_dot('quiz_question', $attempt_answers);
348 $total_marks = 0;
349 foreach ($quiz_answers as $question_id => $answers){
350 $question = tutor_utils()->get_quiz_question_by_id($question_id);
351 $question_type = $question->question_type;
352
353 $is_answer_was_correct = false;
354 $given_answer = '';
355
356 if ($question_type === 'true_false' || $question_type === 'single_choice'){
357
358 $given_answer = $answers;
359 $is_answer_was_correct = (bool) $wpdb->get_var("SELECT is_correct FROM {$wpdb->prefix}tutor_quiz_question_answers WHERE answer_id = {$answers} ");
360
361 }elseif ($question_type === 'multiple_choice'){
362
363 $given_answer = maybe_serialize($answers);
364 $get_original_answers = (array) $wpdb->get_col("SELECT answer_id FROM {$wpdb->prefix}tutor_quiz_question_answers WHERE belongs_question_id = {$question->question_id} AND belongs_question_type = '{$question_type}' AND is_correct = 1 ;");
365 if (maybe_serialize($get_original_answers) == $given_answer){
366 $is_answer_was_correct = true;
367 }
368
369 }elseif ($question_type === 'fill_in_the_blank'){
370
371 $given_answer = (array) array_map('sanitize_text_field', $answers);
372 $given_answer = maybe_serialize($given_answer);
373
374 $get_original_answer = $wpdb->get_row("SELECT * FROM {$wpdb->prefix}tutor_quiz_question_answers WHERE belongs_question_id = {$question->question_id} AND belongs_question_type = '{$question_type}' ;");
375 $gap_answer = (array) explode('|', $get_original_answer->answer_two_gap_match);
376
377 $gap_answer = array_map('sanitize_text_field', $gap_answer);
378 if ($given_answer == maybe_serialize($gap_answer)){
379 $is_answer_was_correct = true;
380 }
381
382 }elseif ($question_type === 'open_ended' || $question_type === 'short_answer'){
383
384 $given_answer = wp_kses_post($answers);
385
386 }elseif ($question_type === 'ordering' || $question_type === 'matching'|| $question_type === 'image_matching' ){
387
388 $given_answer = (array) array_map('sanitize_text_field', tutor_utils()->avalue_dot('answers', $answers));
389 $given_answer = maybe_serialize($given_answer);
390
391 $get_original_answers = (array) $wpdb->get_col("SELECT answer_id FROM {$wpdb->prefix}tutor_quiz_question_answers WHERE belongs_question_id = {$question->question_id} AND belongs_question_type = '{$question_type}' ORDER BY answer_order ASC ;");
392 $get_original_answers = array_map('sanitize_text_field', $get_original_answers);
393
394 if ($given_answer == maybe_serialize($get_original_answers)){
395 $is_answer_was_correct = true;
396 }
397
398 }elseif ($question_type === 'image_answering'){
399 echo '<pre>';
400
401 $image_inputs = tutor_utils()->avalue_dot('answer_id', $answers);
402 $image_inputs = (array) array_map('sanitize_text_field', $image_inputs);
403 $given_answer = maybe_serialize($image_inputs);
404 $is_answer_was_correct = false;
405
406 $db_answer = $wpdb->get_col("SELECT answer_title FROM {$wpdb->prefix}tutor_quiz_question_answers WHERE belongs_question_id = {$question_id} AND belongs_question_type =
407 'image_answering' ORDER BY answer_order asc ;");
408
409 if (is_array($db_answer) && count($db_answer)){
410 $is_answer_was_correct = (strtolower(maybe_serialize(array_values($image_inputs))) == strtolower(maybe_serialize($db_answer)) );
411 }
412 }
413
414 $question_mark = $is_answer_was_correct ? $question->question_mark : 0;
415 $total_marks += $question_mark;
416
417 $answers_data = array(
418 'user_id' => $user_id,
419 'quiz_id' => $attempt->quiz_id,
420 'question_id' => $question_id,
421 'quiz_attempt_id' => $attempt_id,
422 'given_answer' => $given_answer,
423 'question_mark' => $question->question_mark,
424 'achieved_mark' => $question_mark,
425 'minus_mark' => 0,
426 'is_correct' => $is_answer_was_correct ? 1 : 0,
427 );
428 $wpdb->insert($wpdb->prefix.'tutor_quiz_attempt_answers', $answers_data);
429 }
430
431 $attempt_info = array(
432 'total_answered_questions' => count($quiz_answers),
433 'earned_marks' => $total_marks,
434 'attempt_status' => 'attempt_ended',
435 'attempt_ended_at' => date("Y-m-d H:i:s"),
436 );
437 $wpdb->update($wpdb->prefix.'tutor_quiz_attempts', $attempt_info, array('attempt_id' => $attempt_id));
438 }
439 }
440
441 wp_redirect(get_the_permalink($attempt->quiz_id));
442 die();
443 }
444
445
446 /**
447 * Quiz attempt will be finish here
448 *
449 */
450
451 public function finishing_quiz_attempt(){
452 if ( ! isset($_POST['tutor_action']) || $_POST['tutor_action'] !== 'tutor_finish_quiz_attempt' ){
453 return;
454 }
455 //Checking nonce
456 tutor_utils()->checking_nonce();
457
458 if ( ! is_user_logged_in()){
459 die('Please sign in to do this operation');
460 }
461
462 global $wpdb;
463
464 $quiz_id = (int) sanitize_text_field($_POST['quiz_id']);
465 $attempt = tutor_utils()->is_started_quiz($quiz_id);
466 $attempt_id = $attempt->attempt_id;
467
468 $attempt_info = array(
469 'total_answered_questions' => 0,
470 'earned_marks' => 0,
471 'attempt_status' => 'attempt_ended',
472 'attempt_ended_at' => date("Y-m-d H:i:s"),
473 );
474
475 do_action('tutor_quiz_before_finish', $attempt_id, $quiz_id, $attempt->user_id);
476 $wpdb->update($wpdb->prefix.'tutor_quiz_attempts', $attempt_info, array('attempt_id' => $attempt_id));
477 do_action('tutor_quiz_finished', $attempt_id, $quiz_id, $attempt->user_id);
478
479 wp_redirect(tutor_utils()->input_old('_wp_http_referer'));
480 }
481
482 /**
483 * Quiz timeout by ajax
484 */
485 public function tutor_quiz_timeout(){
486 global $wpdb;
487
488 $quiz_id = (int) sanitize_text_field($_POST['quiz_id']);
489 $attempt = tutor_utils()->is_started_quiz($quiz_id);
490
491 if ($attempt) {
492 $attempt_id = $attempt->attempt_id;
493
494 $data = array(
495 'attempt_status' => 'attempt_timeout',
496 'attempt_ended_at' => date("Y-m-d H:i:s"),
497 );
498 $wpdb->update($wpdb->prefix.'tutor_quiz_attempts', $data, array('attempt_id' => $attempt->attempt_id));
499
500 do_action('tutor_quiz_timeout', $attempt_id, $quiz_id, $attempt->user_id);
501
502 wp_send_json_success();
503 }
504
505 wp_send_json_error(__('Quiz has been timeout already', 'tutor'));
506 }
507
508 /**
509 * Review the answer and change individual answer result
510 */
511
512 public function review_quiz_answer(){
513 global $wpdb;
514
515 $attempt_id = (int) sanitize_text_field($_GET['attempt_id']);
516 $attempt_answer_id = (int) sanitize_text_field($_GET['attempt_answer_id']);
517 $mark_as = sanitize_text_field($_GET['mark_as']);
518
519 $attempt_answer = $wpdb->get_row("SELECT * FROM {$wpdb->prefix}tutor_quiz_attempt_answers WHERE attempt_answer_id = {$attempt_answer_id} ");
520 $attempt = tutor_utils()->get_attempt($attempt_id);
521
522 $is_correct = (int) $attempt_answer->is_correct;
523
524 do_action('tutor_quiz_review_answer_before', $attempt_answer_id, $attempt_id, $mark_as);
525
526 if ($mark_as === 'correct' && ! $is_correct){
527
528 $answer_update_data = array(
529 'achieved_mark' => $attempt_answer->question_mark,
530 'is_correct' => 1,
531 );
532 $wpdb->update($wpdb->prefix.'tutor_quiz_attempt_answers', $answer_update_data, array('attempt_answer_id' => $attempt_answer_id ));
533
534 $attempt_update_data = array(
535 'earned_marks' => $attempt->earned_marks + $attempt_answer->question_mark,
536 'is_manually_reviewed' => 1,
537 'manually_reviewed_at' => date("Y-m-d H:i:s"),
538 );
539
540 $wpdb->update($wpdb->prefix.'tutor_quiz_attempts', $attempt_update_data, array('attempt_id' => $attempt_id ));
541
542 }elseif($mark_as === 'incorrect' && $is_correct){
543
544 $answer_update_data = array(
545 'achieved_mark' => '0.00',
546 'is_correct' => 0,
547 );
548 $wpdb->update($wpdb->prefix.'tutor_quiz_attempt_answers', $answer_update_data, array('attempt_answer_id' => $attempt_answer_id ));
549
550 $attempt_update_data = array(
551 'earned_marks' => $attempt->earned_marks - $attempt_answer->question_mark,
552 'is_manually_reviewed' => 1,
553 'manually_reviewed_at' => date("Y-m-d H:i:s"),
554 );
555
556 $wpdb->update($wpdb->prefix.'tutor_quiz_attempts', $attempt_update_data, array('attempt_id' => $attempt_id ));
557 }
558 do_action('tutor_quiz_review_answer_after', $attempt_answer_id, $attempt_id, $mark_as);
559
560 wp_redirect(admin_url("admin.php?page=tutor_quiz_attempts&sub_page=view_attempt&attempt_id=".$attempt_id));
561 die();
562 }
563
564
565 /**
566 * New Design Quiz
567 */
568 public function tutor_create_quiz_and_load_modal(){
569 $topic_id = sanitize_text_field($_POST['topic_id']);
570 $quiz_title = sanitize_text_field($_POST['quiz_title']);
571 $quiz_description = sanitize_text_field($_POST['quiz_description']);
572 $next_order_id = tutor_utils()->get_next_course_content_order_id($topic_id);
573
574 $post_arr = array(
575 'post_type' => 'tutor_quiz',
576 'post_title' => $quiz_title,
577 'post_content' => $quiz_description,
578 'post_status' => 'publish',
579 'post_author' => get_current_user_id(),
580 'post_parent' => $topic_id,
581 'menu_order' => $next_order_id,
582 );
583 $quiz_id = wp_insert_post( $post_arr );
584 do_action('tutor_initial_quiz_created', $quiz_id);
585
586 ob_start();
587 include tutor()->path.'views/modal/edit_quiz.php';
588 $output = ob_get_clean();
589
590 ob_start();
591 ?>
592 <div id="tutor-quiz-<?php echo $quiz_id; ?>" class="course-content-item tutor-quiz tutor-quiz-<?php echo $quiz_id; ?>">
593 <div class="tutor-lesson-top">
594 <i class="tutor-icon-move"></i>
595 <a href="javascript:;" class="open-tutor-quiz-modal" data-quiz-id="<?php echo $quiz_id; ?>" data-topic-id="<?php echo $topic_id;
596 ?>"> <i class=" tutor-icon-doubt"></i>[QUIZ] <?php echo $quiz_title; ?> </a>
597 <a href="javascript:;" class="tutor-delete-quiz-btn" data-quiz-id="<?php echo $quiz_id; ?>"><i class="tutor-icon-garbage"></i></a>
598 </div>
599 </div>
600 <?php
601 $output_quiz_row = ob_get_clean();
602
603 wp_send_json_success(array('output' => $output, 'output_quiz_row' => $output_quiz_row));
604 }
605
606 public function tutor_delete_quiz_by_id(){
607 global $wpdb;
608
609 $quiz_id = (int) sanitize_text_field($_POST['quiz_id']);
610 $post = get_post($quiz_id);
611
612 if ( $post->post_type === 'tutor_quiz'){
613 do_action('tutor_delete_quiz_before', $quiz_id);
614
615 $wpdb->delete($wpdb->prefix.'tutor_quiz_attempts', array('quiz_id' => $quiz_id));
616 $wpdb->delete($wpdb->prefix.'tutor_quiz_attempt_answers', array('quiz_id' => $quiz_id));
617
618 $questions_ids = $wpdb->get_col("SELECT question_id FROM {$wpdb->prefix}tutor_quiz_questions WHERE quiz_id = {$quiz_id} ");
619 if (is_array($questions_ids) && count($questions_ids)){
620 $in_question_ids = "'".implode("','", $questions_ids)."'";
621 $wpdb->query("DELETE FROM {$wpdb->prefix}tutor_quiz_question_answers WHERE belongs_question_id IN({$in_question_ids}) ");
622 }
623 $wpdb->delete($wpdb->prefix.'tutor_quiz_questions', array('quiz_id' => $quiz_id));
624
625 wp_delete_post($quiz_id, true);
626 delete_post_meta($quiz_id, '_tutor_course_id_for_lesson');
627
628 do_action('tutor_delete_quiz_after', $quiz_id);
629
630
631 wp_send_json_success();
632 }
633
634 wp_send_json_error();
635 }
636
637 /**
638 * Update Quiz from quiz builder modal
639 *
640 * @since v.1.0.0
641 */
642 public function tutor_quiz_builder_quiz_update(){
643 $quiz_id = sanitize_text_field($_POST['quiz_id']);
644 $topic_id = sanitize_text_field($_POST['topic_id']);
645 $quiz_title = sanitize_text_field($_POST['quiz_title']);
646 $quiz_description = sanitize_text_field($_POST['quiz_description']);
647
648 $post_arr = array(
649 'ID' => $quiz_id,
650 'post_title' => $quiz_title,
651 'post_content' => $quiz_description,
652
653 );
654 $quiz_id = wp_update_post( $post_arr );
655
656 do_action('tutor_quiz_updated', $quiz_id);
657
658 ob_start();
659 ?>
660 <div class="tutor-lesson-top">
661 <i class="tutor-icon-move"></i>
662 <a href="javascript:;" class="open-tutor-quiz-modal" data-quiz-id="<?php echo $quiz_id; ?>" data-topic-id="<?php echo $topic_id;
663 ?>"> <i class=" tutor-icon-doubt"></i>[QUIZ] <?php echo $quiz_title; ?> </a>
664 <a href="javascript:;" class="tutor-delete-quiz-btn" data-quiz-id="<?php echo $quiz_id; ?>"><i class="tutor-icon-garbage"></i></a>
665 </div>
666 <?php
667 $output_quiz_row = ob_get_clean();
668
669 wp_send_json_success(array('output_quiz_row' => $output_quiz_row));
670 }
671
672 /**
673 * Load quiz Modal for edit quiz
674 *
675 * @since v.1.0.0
676 */
677 public function tutor_load_edit_quiz_modal(){
678 $quiz_id = sanitize_text_field($_POST['quiz_id']);
679
680 ob_start();
681 include tutor()->path.'views/modal/edit_quiz.php';
682 $output = ob_get_clean();
683
684 wp_send_json_success(array('output' => $output));
685 }
686
687 /**
688 * Load quiz question form for quiz
689 *
690 * @since v.1.0.0
691 */
692 public function tutor_quiz_builder_get_question_form(){
693 global $wpdb;
694 $quiz_id = sanitize_text_field($_POST['quiz_id']);
695 $question_id = sanitize_text_field(tutor_utils()->avalue_dot('question_id', $_POST));
696
697 if ( ! $question_id){
698 $next_question_id = tutor_utils()->quiz_next_question_id();
699 $next_question_order = tutor_utils()->quiz_next_question_order_id($quiz_id);
700
701 $new_question_data = array(
702 'quiz_id' => $quiz_id,
703 'question_title' => __('Question ').$next_question_id,
704 'question_description' => '',
705 'question_type' => 'true_false',
706 'question_mark' => 1,
707 'question_settings' => maybe_serialize(array()),
708 'question_order' => $next_question_order,
709 );
710
711 $wpdb->insert($wpdb->prefix.'tutor_quiz_questions', $new_question_data);
712 $question_id = $wpdb->insert_id;
713 }
714
715 $question = $wpdb->get_row("SELECT * FROM {$wpdb->prefix}tutor_quiz_questions where question_id = {$question_id} ");
716
717 ob_start();
718 include tutor()->path.'views/modal/question_form.php';
719 $output = ob_get_clean();
720
721 wp_send_json_success(array('output' => $output));
722 }
723
724 public function tutor_quiz_modal_update_question(){
725 global $wpdb;
726
727 $question_data = $_POST['tutor_quiz_question'];
728
729 foreach ($question_data as $question_id => $question){
730 $question_title = $question['question_title'];
731 $question_description = $question['question_description'];
732 $question_type = $question['question_type'];
733 $question_mark = $question['question_mark'];
734
735 unset($question['question_title']);
736 unset($question['question_description']);
737
738 $data = array(
739 'question_title' => $question_title,
740 'question_description' => $question_description,
741 'question_type' => $question_type,
742 'question_mark' => $question_mark,
743 'question_settings' => maybe_serialize($question),
744 );
745
746 $wpdb->update($wpdb->prefix.'tutor_quiz_questions', $data, array('question_id' => $question_id) );
747 }
748
749 wp_send_json_success();
750 }
751
752 public function tutor_quiz_builder_question_delete(){
753 global $wpdb;
754
755 $question_id = sanitize_text_field(tutor_utils()->avalue_dot('question_id', $_POST));
756 if ($question_id){
757 $wpdb->delete($wpdb->prefix.'tutor_quiz_questions', array('question_id' => $question_id));
758 }
759
760 wp_send_json_success();
761 }
762
763 /**
764 * Get answers options form for quiz question
765 *
766 * @since v.1.0.0
767 */
768 public function tutor_quiz_add_question_answers(){
769 $question_id = sanitize_text_field($_POST['question_id']);
770 $question = tutor_utils()->avalue_dot($question_id, $_POST['tutor_quiz_question']);
771 $question_type = $question['question_type'];
772
773 ob_start();
774 include tutor()->path.'views/modal/question_answer_form.php';
775 $output = ob_get_clean();
776
777 wp_send_json_success(array('output' => $output));
778 }
779
780 /**
781 * Edit Answer Form
782 *
783 * @since v.1.0.0
784 */
785 public function tutor_quiz_edit_question_answer(){
786 $answer_id = (int) sanitize_text_field($_POST['answer_id']);
787 $old_answer = tutor_utils()->get_answer_by_id($answer_id);
788 foreach ($old_answer as $old_answer);
789 $question_id = $old_answer->belongs_question_id;
790 $question_type = $old_answer->belongs_question_type;
791
792 ob_start();
793 include tutor()->path.'views/modal/question_answer_edit_form.php';
794 $output = ob_get_clean();
795
796 wp_send_json_success(array('output' => $output));
797 }
798
799 public function tutor_save_quiz_answer_options(){
800 global $wpdb;
801
802 $questions = $_POST['tutor_quiz_question'];
803 $answers = $_POST['quiz_answer'];
804
805 foreach ($answers as $question_id => $answer){
806 $question = tutor_utils()->avalue_dot($question_id, $questions);
807 $question_type = $question['question_type'];
808
809 //Getting next sorting order
810 $next_order_id = (int) $wpdb->get_var("SELECT MAX(answer_order) FROM {$wpdb->prefix}tutor_quiz_question_answers where belongs_question_id = {$question_id} AND belongs_question_type = '{$question_type}' ");
811 $next_order_id = $next_order_id + 1;
812
813 if ($question){
814 if ($question_type === 'true_false'){
815 $wpdb->delete($wpdb->prefix.'tutor_quiz_question_answers', array('belongs_question_id' => $question_id, 'belongs_question_type' => $question_type));
816 $data_true_false = array(
817 array(
818 'belongs_question_id' => $question_id,
819 'belongs_question_type' => $question_type,
820 'answer_title' => __('True', 'tutor'),
821 'is_correct' => $answer['true_false'] == 'true' ? 1 : 0,
822 'answer_two_gap_match' => 'true',
823 ),
824 array(
825 'belongs_question_id' => $question_id,
826 'belongs_question_type' => $question_type,
827 'answer_title' => __('False', 'tutor'),
828 'is_correct' => $answer['true_false'] == 'false' ? 1 : 0,
829 'answer_two_gap_match' => 'false',
830 ),
831 );
832
833 foreach ($data_true_false as $true_false_data){
834 $wpdb->insert($wpdb->prefix.'tutor_quiz_question_answers', $true_false_data);
835 }
836
837 }elseif($question_type === 'multiple_choice' || $question_type === 'single_choice' || $question_type === 'ordering' ||
838 $question_type === 'matching' || $question_type === 'image_matching' || $question_type === 'image_answering' ){
839
840 $answer_data = array(
841 'belongs_question_id' => $question_id,
842 'belongs_question_type' => $question_type,
843 'answer_title' => $answer['answer_title'],
844 'image_id' => isset($answer['image_id']) ? $answer['image_id'] : 0,
845 'answer_view_format' => isset($answer['answer_view_format']) ? $answer['answer_view_format'] : 0,
846 'answer_order' => $next_order_id,
847 );
848 if (isset($answer['matched_answer_title'])){
849 $answer_data['answer_two_gap_match'] = $answer['matched_answer_title'];
850 }
851
852 $wpdb->insert($wpdb->prefix.'tutor_quiz_question_answers', $answer_data);
853
854 }elseif($question_type === 'fill_in_the_blank'){
855 $wpdb->delete($wpdb->prefix.'tutor_quiz_question_answers', array('belongs_question_id' => $question_id, 'belongs_question_type' => $question_type));
856 $answer_data = array(
857 'belongs_question_id' => $question_id,
858 'belongs_question_type' => $question_type,
859 'answer_title' => $answer['answer_title'],
860 'answer_two_gap_match' => isset($answer['answer_two_gap_match']) ? strtolower(trim($answer['answer_two_gap_match'])) : null,
861 );
862 $wpdb->insert($wpdb->prefix.'tutor_quiz_question_answers', $answer_data);
863 }
864 }
865 }
866
867 wp_send_json_success();
868 }
869
870 /**
871 * Tutor Update Answer
872 *
873 * @since v.1.0.0
874 */
875 public function tutor_update_quiz_answer_options(){
876 global $wpdb;
877
878 $answer_id = (int) sanitize_text_field($_POST['tutor_quiz_answer_id']);
879 $questions = $_POST['tutor_quiz_question'];
880 $answers = $_POST['quiz_answer'];
881
882 foreach ($answers as $question_id => $answer){
883 $question = tutor_utils()->avalue_dot($question_id, $questions);
884 $question_type = $question['question_type'];
885
886 if ($question){
887 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' ){
888
889 $answer_data = array(
890 'belongs_question_id' => $question_id,
891 'belongs_question_type' => $question_type,
892 'answer_title' => $answer['answer_title'],
893 'image_id' => isset($answer['image_id']) ? $answer['image_id'] : 0,
894 'answer_view_format' => isset($answer['answer_view_format']) ? $answer['answer_view_format'] : '',
895 );
896 if (isset($answer['matched_answer_title'])){
897 $answer_data['answer_two_gap_match'] = $answer['matched_answer_title'];
898 }
899
900 if ($question_type === 'fill_in_the_blank'){
901 $answer_data['answer_two_gap_match'] = isset($answer['answer_two_gap_match']) ? strtolower(trim($answer['answer_two_gap_match'])) : null;
902 }
903
904 $wpdb->update($wpdb->prefix.'tutor_quiz_question_answers', $answer_data, array('answer_id' => $answer_id));
905 }
906 }
907 }
908
909 //die(print_r($_POST));
910 wp_send_json_success();
911 }
912
913 public function tutor_quiz_builder_get_answers_by_question(){
914 global $wpdb;
915 $question_id = sanitize_text_field($_POST['question_id']);
916 $question_type = sanitize_text_field($_POST['question_type']);
917
918 $question = $wpdb->get_row("SELECT * FROM {$wpdb->prefix}tutor_quiz_questions WHERE question_id = {$question_id} ");
919 $answers = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}tutor_quiz_question_answers where belongs_question_id = {$question_id} AND belongs_question_type = '{$question_type}' order by answer_order asc ;");
920
921 ob_start();
922
923 /*
924 * @TODO: Should We remove this text?
925 * Screenshot: http://prntscr.com/ngl2mb
926 * Screenshot: http://prntscr.com/ngl2ak
927 */
928
929 switch ($question_type){
930 case 'true_false':
931 echo '<label>'.__('Answer options &amp; mark correct', 'tutor').'</label>';
932 break;
933 case 'ordering':
934 echo '<label>'.__('Student should order below items exact this order, make sure your answer is in right order, you can re-order them', 'tutor').'</label>';
935 break;
936 }
937
938 if (is_array($answers) && count($answers)){
939 foreach ($answers as $answer){
940 ?>
941 <div class="tutor-quiz-answer-wrap" data-answer-id="<?php echo $answer->answer_id; ?>">
942 <div class="tutor-quiz-answer">
943 <span class="tutor-quiz-answer-title">
944 <?php
945 echo $answer->answer_title;
946 if ($answer->belongs_question_type === 'fill_in_the_blank'){
947 echo ' ('.__('Answer', 'tutor').' : ';
948 echo "<strong>{$answer->answer_two_gap_match} </strong>)";
949 }
950 if ($answer->belongs_question_type === 'matching'){
951 echo " - {$answer->answer_two_gap_match}";
952 }
953 ?>
954 </span>
955
956 <?php
957 if ($answer->image_id){
958 echo '<span class="tutor-question-answer-image"><img src="'.wp_get_attachment_image_url($answer->image_id).'" /> </span>';
959 }
960 if ($question_type === 'true_false' || $question_type === 'single_choice'){
961 ?>
962 <span class="tutor-quiz-answers-mark-correct-wrap">
963 <input type="radio" name="mark_as_correct[<?php echo $answer->belongs_question_id; ?>]" value="<?php echo $answer->answer_id; ?>" title="<?php _e('Mark as correct', 'tutor'); ?>" <?php checked(1, $answer->is_correct); ?> >
964 </span>
965 <?php
966 }elseif ($question_type === 'multiple_choice'){
967 ?>
968 <span class="tutor-quiz-answers-mark-correct-wrap">
969 <input type="checkbox" name="mark_as_correct[<?php echo $answer->belongs_question_id; ?>]" value="<?php echo $answer->answer_id; ?>" title="<?php _e('Mark as correct', 'tutor'); ?>" <?php checked(1, $answer->is_correct); ?> >
970 </span>
971 <?php
972 }
973 ?>
974 <span class="tutor-quiz-answer-edit">
975 <a href="javascript:;"><i class="tutor-icon-pencil"></i> </a>
976 </span>
977 <span class="tutor-quiz-answer-sort-icon"><i class="tutor-icon-menu-2"></i> </span>
978 </div>
979
980 <div class="tutor-quiz-answer-trash-wrap">
981 <a href="javascript:;" class="answer-trash-btn" data-answer-id="<?php echo $answer->answer_id; ?>"><i class="tutor-icon-garbage"></i> </a>
982 </div>
983 </div>
984 <?php
985 }
986 }
987 $output = ob_get_clean();
988
989 wp_send_json_success(array('output' => $output));
990 }
991
992 public function tutor_quiz_builder_delete_answer(){
993 global $wpdb;
994 $answer_id = sanitize_text_field($_POST['answer_id']);
995
996 $wpdb->delete($wpdb->prefix.'tutor_quiz_question_answers', array('answer_id' => $answer_id));
997 wp_send_json_success();
998 }
999
1000 /**
1001 * Save quiz questions sorting
1002 */
1003 public function tutor_quiz_question_sorting(){
1004 global $wpdb;
1005
1006 $question_ids = tutor_utils()->avalue_dot('sorted_question_ids', $_POST);
1007 if (is_array($question_ids) && count($question_ids) ){
1008 $i = 0;
1009 foreach ($question_ids as $key => $question_id){
1010 $i++;
1011 $wpdb->update($wpdb->prefix.'tutor_quiz_questions', array('question_order' => $i), array('question_id' => $question_id));
1012 }
1013 }
1014 }
1015
1016 /**
1017 * Save sorting data for quiz answers
1018 */
1019 public function tutor_quiz_answer_sorting(){
1020 global $wpdb;
1021
1022 if ( ! empty($_POST['sorted_answer_ids']) && is_array($_POST['sorted_answer_ids']) && count($_POST['sorted_answer_ids']) ){
1023 $answer_ids = $_POST['sorted_answer_ids'];
1024 $i = 0;
1025 foreach ($answer_ids as $key => $answer_id){
1026 $i++;
1027 $wpdb->update($wpdb->prefix.'tutor_quiz_question_answers', array('answer_order' => $i), array('answer_id' => $answer_id));
1028 }
1029 }
1030
1031 }
1032
1033 /**
1034 * Mark answer as correct
1035 */
1036
1037 public function tutor_mark_answer_as_correct(){
1038 global $wpdb;
1039
1040 $answer_id = sanitize_text_field($_POST['answer_id']);
1041 $inputValue = sanitize_text_field($_POST['inputValue']);
1042
1043 $answer = $wpdb->get_row("SELECT * FROM {$wpdb->prefix}tutor_quiz_question_answers WHERE answer_id = {$answer_id} LIMIT 0,1 ;");
1044 if ($answer->belongs_question_type === 'single_choice'){
1045 $wpdb->update($wpdb->prefix.'tutor_quiz_question_answers', array('is_correct' => 0), array('belongs_question_id' => $answer->belongs_question_id));
1046 }
1047 $wpdb->update($wpdb->prefix.'tutor_quiz_question_answers', array('is_correct' => $inputValue), array('answer_id' => $answer_id));
1048 }
1049
1050 /**
1051 * Update quiz settings from modal
1052 *
1053 * @since : v.1.0.0
1054 */
1055 public function tutor_quiz_modal_update_settings(){
1056 $quiz_id = sanitize_text_field($_POST['quiz_id']);
1057
1058 $quiz_option = tutor_utils()->sanitize_array($_POST['quiz_option']);
1059 update_post_meta($quiz_id, 'tutor_quiz_option', $quiz_option);
1060
1061 wp_send_json_success();
1062 }
1063
1064
1065 //=========================//
1066 // Front end stuffs
1067 //=========================//
1068
1069 /**
1070 * Rendering quiz for frontend
1071 *
1072 * @since v.1.0.0
1073 */
1074
1075 public function tutor_render_quiz_content(){
1076 $quiz_id = (int) sanitize_text_field(tutor_utils()->avalue_dot('quiz_id', $_POST));
1077
1078 ob_start();
1079 global $post;
1080
1081 $post = get_post($quiz_id);
1082 setup_postdata($post);
1083 //tutor_lesson_content();
1084
1085 single_quiz_contents();
1086
1087 wp_reset_postdata();
1088
1089
1090 $html = ob_get_clean();
1091 wp_send_json_success(array('html' => $html));
1092 }
1093
1094
1095 }