PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 1.0.0-alpha
Tutor LMS – eLearning and online course solution v1.0.0-alpha
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 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 7 years ago Admin.php 7 years ago Ajax.php 7 years ago Assets.php 7 years ago Course.php 7 years ago Gutenberg.php 7 years ago Instructor.php 7 years ago Instructors_List.php 7 years ago Lesson.php 7 years ago Options.php 7 years ago Post_types.php 7 years ago Q_and_A.php 7 years ago Question.php 7 years ago Question_Answers_List.php 7 years ago Quiz.php 7 years ago Quiz_Attempts_List.php 7 years ago Rewrite_Rules.php 7 years ago Shortcode.php 7 years ago Student.php 7 years ago Students_List.php 7 years ago Template.php 7 years ago Theme_Compatibility.php 7 years ago Tools.php 7 years ago Tutor_Base.php 7 years ago Tutor_List_Table.php 7 years ago User.php 7 years ago Utils.php 7 years ago Video_Stream.php 7 years ago init.php 7 years ago
Quiz.php
520 lines
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 add_action('wp_ajax_tutor_load_quiz_modal', array($this, 'tutor_load_quiz_modal'));
27 add_action('wp_ajax_tutor_add_quiz_to_post', array($this, 'tutor_add_quiz_to_post'));
28 add_action('wp_ajax_remove_quiz_from_post', array($this, 'remove_quiz_from_post'));
29
30 add_action('wp_ajax_tutor_quiz_timeout', array($this, 'tutor_quiz_timeout'));
31
32 //User take the quiz
33 add_action('template_redirect', array($this, 'start_the_quiz'));
34 add_action('template_redirect', array($this, 'answering_quiz'));
35 add_action('template_redirect', array($this, 'finishing_quiz_attempt'));
36
37 add_action('admin_action_review_quiz_answer', array($this, 'review_quiz_answer'));
38 }
39
40
41
42 public function add_column($columns){
43 $date_col = $columns['date'];
44 unset($columns['date']);
45 $columns['quiz'] = __('Course', 'tutor');
46 $columns['questions'] = __('Questions', 'tutor');
47 $columns['date'] = $date_col;
48
49 return $columns;
50 }
51
52 public function custom_question_column($column, $post_id ){
53 if ($column === 'quiz'){
54 $quiz = tutor_utils()->get_course_by_quiz($post_id);
55
56 if ($quiz){
57 echo '<a href="'.admin_url('post.php?post='.$quiz->ID.'&action=edit').'">'.get_the_title($quiz->ID).'</a>';
58 }
59 }
60
61 if ($column === 'questions'){
62 echo tutor_utils()->total_questions_for_student_by_quiz($post_id);
63 }
64 }
65
66
67 public function register_meta_box(){
68 add_meta_box( 'tutor-quiz-questions', __( 'Questions', 'tutor' ), array($this, 'quiz_questions'), 'tutor_quiz' );
69 add_meta_box( 'tutor-quiz-settings', __( 'Settings', 'tutor' ), array($this, 'quiz_settings'), 'tutor_quiz' );
70 }
71
72 public function quiz_questions(){
73 include tutor()->path.'views/metabox/quiz_questions.php';
74 }
75
76 public function quiz_settings(){
77 include tutor()->path.'views/metabox/quizzes.php';
78 }
79
80 public function save_quiz_meta($post_ID){
81 if (isset($_POST['quiz_option'])){
82 $quiz_option = tutor_utils()->sanitize_array($_POST['quiz_option']);
83 update_post_meta($post_ID, 'tutor_quiz_option', $quiz_option);
84 }
85 }
86
87 public function tutor_load_quiz_modal(){
88 $quiz_for_post_id = (int) sanitize_text_field($_POST['quiz_for_post_id']);
89
90 $search_terms = sanitize_text_field(tutor_utils()->avalue_dot('search_terms', $_POST));
91 $quizzes = tutor_utils()->get_unattached_quiz(array('search_term' => $search_terms));
92
93 $output = '';
94 if ($quizzes){
95 foreach ($quizzes as $quiz){
96 $output .= "<p><label><input type='checkbox' name='quiz_for[{$quiz_for_post_id}][quiz_id][]' value='{$quiz->ID}' > {$quiz->post_title} </label></p>";
97 }
98 $output .= '<p class="quiz-search-suggest-text">Search the quiz to get specific quiz</p>';
99 }else{
100 $add_question_url = admin_url('post-new.php?post_type=tutor_quiz');
101 $output .= sprintf('No quiz available right now, please %s add some quiz %s', '<a href="'.$add_question_url.'" target="_blank">', '</a>' );
102 }
103
104 ob_start();
105 ?>
106 <div class="tutor-option-field-row">
107 <div class="tutor-option-field-label">
108 <label for="">
109 <?php _e('New quiz title', 'tutor'); ?>
110 </label>
111 </div>
112 <div class="tutor-option-field">
113 <input type="text" name="quiz_title" placeholder="<?php _e('Place quiz title to create new quiz', 'tutor'); ?>" >
114 <p class="desc"><?php _e('Provide a quiz title to create a quiz from here.'); ?></p>
115 </div>
116 </div>
117
118 <?php
119 $output .= ob_get_clean();
120
121 wp_send_json_success(array('output' => $output));
122 }
123
124 public function tutor_add_quiz_to_post(){
125 global $wpdb;
126
127 $quiz_data = tutor_utils()->avalue_dot('quiz_for', $_POST);
128
129 $output = '';
130 $post_id = (int) sanitize_text_field(tutor_utils()->avalue_dot('parent_post_id', $_POST)) ;
131 if ($quiz_data){
132 foreach ($quiz_data as $post_id => $quiz_ids_a);
133
134 $quiz_ids = tutor_utils()->avalue_dot('quiz_id', $quiz_ids_a);
135 foreach ($quiz_ids as $quiz_id){
136 $wpdb->update($wpdb->posts, array('post_parent' => $post_id), array('ID' => $quiz_id) );
137 }
138 }
139
140 $quiz_title = sanitize_text_field(tutor_utils()->avalue_dot('quiz_title', $_POST));
141 if ($quiz_title){
142 wp_insert_post(array(
143 'post_parent' => $post_id,
144 'post_title' => $quiz_title,
145 'post_type' => 'tutor_quiz',
146 'post_status' => 'publish',
147 ));
148 }
149
150 if ($post_id) {
151 ob_start();
152 $attached_quizzes = tutor_utils()->get_attached_quiz( $post_id );
153 if ( $attached_quizzes ) {
154 foreach ( $attached_quizzes as $attached_quiz ) {
155 ?>
156 <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; ?>">
157 <span class="quiz-icon"><i class="dashicons dashicons-clock"></i></span>
158 <span class="quiz-name">
159 <?php edit_post_link( $attached_quiz->post_title, null, null, $attached_quiz->ID ); ?>
160 </span>
161 <span class="quiz-control">
162 <a href="javascript:;" class="tutor-quiz-delete-btn"><i class="dashicons dashicons-trash"></i></a>
163 </span>
164 </div>
165 <?php
166 }
167 }
168 $output .= ob_get_clean();
169 }
170
171 wp_send_json_success(array('output' => $output));
172 }
173
174 public function remove_quiz_from_post(){
175 global $wpdb;
176 $quiz_id = (int) tutor_utils()->avalue_dot('quiz_id', $_POST);
177 $wpdb->update($wpdb->posts, array('post_parent' => 0), array('ID' => $quiz_id) );
178 wp_send_json_success();
179 }
180
181 public function start_the_quiz(){
182 if ( ! isset($_POST['tutor_action']) || $_POST['tutor_action'] !== 'tutor_start_quiz' ){
183 return;
184 }
185 //Checking nonce
186 tutor_utils()->checking_nonce();
187
188 if ( ! is_user_logged_in()){
189 //TODO: need to set a view in the next version
190 die('Please sign in to do this operation');
191 }
192
193 global $wpdb;
194
195 $user_id = get_current_user_id();
196 $user = get_userdata($user_id);
197
198 $quiz_id = (int) sanitize_text_field($_POST['quiz_id']);
199 $quiz = get_post($quiz_id);
200 $date = date("Y-m-d H:i:s");
201
202 $attempts_allowed = tutor_utils()->get_quiz_option($quiz_id, 'attempts_allowed', 0);
203
204 do_action('tutor_before_start_quiz', $quiz_id);
205 $data = array(
206 'comment_post_ID' => $quiz_id, //QuizID
207 'comment_author' => $user->user_login,
208 'comment_date' => $date,
209 'comment_date_gmt' => get_gmt_from_date($date),
210 'comment_approved' => 'quiz_started', //quiz_timeup, quiz_complete
211 'comment_agent' => 'TutorLMSPlugin',
212 'comment_type' => 'tutor_quiz_attempt',
213 'comment_parent' => $quiz->post_parent, //Quiz Parent Attached Course || Lesson || Topic
214 'user_id' => $user_id,
215 );
216
217 $wpdb->insert($wpdb->comments, $data);
218 $attempt_id = (int) $wpdb->insert_id;
219
220 $time_limit = tutor_utils()->get_quiz_option($quiz_id, 'time_limit.time_value');
221 $time_limit_seconds = 0;
222 $time_type = 'seconds';
223 if ($time_limit){
224 $time_type = tutor_utils()->get_quiz_option($quiz_id, 'time_limit.time_type');
225
226 switch ($time_type){
227 case 'seconds':
228 $time_limit_seconds = $time_limit;
229 break;
230 case 'minutes':
231 $time_limit_seconds = $time_limit * 60;
232 break;
233 case 'hours':
234 $time_limit_seconds = $time_limit * 60 * 60;
235 break;
236 case 'days':
237 $time_limit_seconds = $time_limit * 60 * 60 * 24;
238 break;
239 case 'weeks':
240 $time_limit_seconds = $time_limit * 60 * 60 * 24 * 7;
241 break;
242 }
243 }
244
245 $max_question_allowed = tutor_utils()->max_questions_for_take_quiz($quiz_id);
246 $quiz_attempt_info = array(
247 'time_limit' => $time_limit,
248 'time_type' => $time_type,
249 'time_limit_seconds' => $time_limit_seconds,
250 'total_question' => $max_question_allowed,
251 'answered_question' => 0,
252 'current_question' => 0,
253 'marks_earned' => 0,
254 'answers' => array(),
255 );
256
257 //answers format
258 /*
259 array(
260 '0' => array( 'questionID' => 344, 'has_correct' => 1, //or 0 for false, 'questionSiNo' => 1
261 'answers_list' => array(
262 'answers_id' => array('selected_answerId_1', 'selected_answerId_2', 'or_line_answer_text')
263 )
264 ),
265
266 '1' => array( 'questionID' => 654, 'has_correct' => 0, //or 0 for false, 'questionSiNo' => 2
267 'answers_list' => array(
268 'answers_id' => array('selected_answerId_1', 'selected_answerId_2', 'or_line_answer_text')
269 )
270 ),
271 );
272 */
273
274 update_comment_meta($attempt_id, 'quiz_attempt_info', $quiz_attempt_info);
275 update_comment_meta($attempt_id, 'earned_mark_percent', '0');
276
277 do_action('tutor_after_start_quiz', $quiz_id, $attempt_id);
278
279 wp_redirect(tutor_utils()->input_old('_wp_http_referer'));
280 die();
281 }
282
283
284 public function answering_quiz(){
285 if ( ! isset($_POST['tutor_action']) || $_POST['tutor_action'] !== 'tutor_answering_quiz_question' ){
286 return;
287 }
288 //Checking nonce
289 tutor_utils()->checking_nonce();
290
291 if ( ! is_user_logged_in()){
292 die('Please sign in to do this operation');
293 }
294
295 global $wpdb;
296
297 $user_id = get_current_user_id();
298 $attempt_id = (int) sanitize_text_field(tutor_utils()->avalue_dot('attempt_id', $_POST));
299 $post_question_id = (int) sanitize_text_field(tutor_utils()->avalue_dot('quiz_question_id', $_POST));
300 $attempt = tutor_utils()->get_attempt($attempt_id);
301
302 if ( ! $attempt || $user_id != $attempt->user_id){
303 die('Operation not allowed, attempt not found or permission denied');
304 }
305
306 $attempt_info = tutor_utils()->quiz_attempt_info($attempt_id);
307 $given_answers = tutor_utils()->avalue_dot("attempt.{$attempt_id}.quiz_question.{$post_question_id}", $_POST);
308
309 $plus_mark = 0;
310 $minus_mark = 0;
311 $is_answer_corrected = false;
312
313 $answers = array(
314 'questionID' => $post_question_id,
315 );
316
317 $question_type = get_post_meta($post_question_id, '_question_type', true);
318 $question_mark = get_post_meta($post_question_id, '_question_mark', true);
319
320 if ($given_answers){
321 $answers['status'] = 'answered'; //or 0 for false, 'questionSiNo' => 2
322 $answers['has_correct'] = 0;
323
324 $saved_answers = tutor_utils()->get_quiz_answer_options_by_question($post_question_id);
325 $corrects_answer_ids = array();
326 if (is_array($saved_answers) && count($saved_answers)){
327 foreach ($saved_answers as $saved_answer){
328 $saved_answer_info = json_decode($saved_answer->comment_content);
329
330 if ( ! empty($saved_answer_info->is_correct) && $saved_answer_info->is_correct){
331 $corrects_answer_ids[] = $saved_answer->comment_ID;
332 }
333 }
334 }
335
336 if ($question_type === 'multiple_choice'){
337 $given_answers = (array) $given_answers;
338 }
339
340 //TODO: need to provide support for question type more if we add
341 //Checking if all answer corrects
342 if ($question_type === 'true_false' || $question_type === 'multiple_choice' || $question_type === 'single_choice'){
343 if ($question_type === 'multiple_choice') {
344 $is_answer_corrected = count(array_intersect($given_answers, $corrects_answer_ids)) == count($given_answers);
345 }else{
346 $is_answer_corrected = in_array($given_answers, $corrects_answer_ids);
347 }
348 }
349
350 if ($is_answer_corrected){
351 $plus_mark = $question_mark;
352 $answers['has_correct'] = 1;
353 }else{
354 //TODO: Do operation for incorrect answer
355 }
356
357 $answers['plus_mark'] = $plus_mark;
358 $answers['minus_mark'] = $minus_mark;
359
360 $answers['answers_list'] = array(
361 'answer_type' => $question_type,
362 'answer_ids' => $given_answers
363 );
364 }else{
365 //If not answered, that means users skipped the questions
366 $answers = array(
367 'questionID' => $post_question_id, 'status' => 'skipped', 'has_correct' => 0, //or 0 for false, 'questionSiNo' => 2
368 'plus_mark' => 0,
369 'minus_mark' => 0,
370 'answers_list' => array()
371 );
372 }
373
374 $answers['question_mark'] = $question_mark;
375
376 if ($is_answer_corrected){
377 if (isset($attempt_info['marks_earned'])){
378 //If not found
379 $attempt_info['marks_earned'] = $attempt_info['marks_earned'] + $plus_mark;
380 }else{
381 $attempt_info['marks_earned'] = $plus_mark;
382 }
383 }else{
384 if ( ! isset($attempt_info['marks_earned'])){
385 $attempt_info['marks_earned'] = 0;
386 }
387
388 //Todo: mark minus if necessary
389 }
390
391 $attempt_info['answers'][] = $answers;
392 tutor_utils()->quiz_update_attempt_info($attempt_id, $attempt_info);
393
394 wp_redirect(tutor_utils()->input_old('_wp_http_referer'));
395 die();
396 }
397
398 /**
399 * Quiz attempt will be finish here
400 *
401 */
402
403 public function finishing_quiz_attempt(){
404 if ( ! isset($_POST['tutor_action']) || $_POST['tutor_action'] !== 'tutor_finish_quiz_attempt' ){
405 return;
406 }
407 //Checking nonce
408 tutor_utils()->checking_nonce();
409
410 if ( ! is_user_logged_in()){
411 die('Please sign in to do this operation');
412 }
413
414
415 global $wpdb;
416
417 $quiz_id = (int) sanitize_text_field($_POST['quiz_id']);
418
419 $is_started_quiz = tutor_utils()->is_started_quiz($quiz_id);
420 $attempt_id = $is_started_quiz->comment_ID;
421
422 if ($is_started_quiz) {
423 do_action('tutor_quiz_finished_before', $attempt_id);
424
425 $quiz_attempt_info = tutor_utils()->quiz_attempt_info( $attempt_id );
426 $answers = tutor_utils()->avalue_dot('answers', $quiz_attempt_info);
427
428 $total_marks = 0;
429 if (is_array($answers)){
430 $total_marks = array_sum(wp_list_pluck($answers, 'question_mark'));
431 }
432
433 $quiz_attempt_info['total_marks'] = $total_marks;
434 $pass_mark_percent = tutor_utils()->get_quiz_option($quiz_id,'passing_grade');
435 $quiz_attempt_info['pass_mark_percent'] = $pass_mark_percent;
436 $quiz_attempt_info['submission_time'] = time();
437
438 //Updating Attempt Info
439 tutor_utils()->quiz_update_attempt_info($attempt_id, $quiz_attempt_info);
440
441 $wpdb->update($wpdb->comments, array('comment_approved' => 'quiz_finished'), array('comment_ID' => $attempt_id));
442
443 do_action('tutor_quiz_finished_after', $attempt_id);
444 }
445
446 wp_redirect(tutor_utils()->input_old('_wp_http_referer'));
447 die();
448 }
449
450 /**
451 * Quiz timeout by ajax
452 */
453 public function tutor_quiz_timeout(){
454 global $wpdb;
455
456 $quiz_id = (int) sanitize_text_field($_POST['quiz_id']);
457
458 $is_started_quiz = tutor_utils()->is_started_quiz($quiz_id);
459 $attempt_id = $is_started_quiz->comment_ID;
460
461 if ($is_started_quiz) {
462 $quiz_attempt_info = tutor_utils()->quiz_attempt_info( $attempt_id );
463 $answers = tutor_utils()->avalue_dot('answers', $quiz_attempt_info);
464
465 $total_marks = 0;
466 if (is_array($answers)){
467 $total_marks = array_sum(wp_list_pluck($answers, 'question_mark'));
468 }
469
470 $quiz_attempt_info['total_marks'] = $total_marks;
471 $pass_mark_percent = tutor_utils()->get_quiz_option($quiz_id,'passing_grade');
472 $quiz_attempt_info['pass_mark_percent'] = $pass_mark_percent;
473
474 //Updating Attempt Info
475 tutor_utils()->quiz_update_attempt_info($attempt_id, $quiz_attempt_info);
476
477 $wpdb->update($wpdb->comments, array('comment_approved' => 'quiz_timeout'), array('comment_ID' => $attempt_id));
478 wp_send_json_success();
479 }
480
481 wp_send_json_error(__('Quiz has been timeout already', 'tutor'));
482 }
483
484 /**
485 * Review the answer and change individual answer result
486 */
487
488 public function review_quiz_answer(){
489 $attempt_id = (int) sanitize_text_field($_GET['attempt_id']);
490 $answer_index = (int) sanitize_text_field($_GET['answer_index']);
491 $mark_as = sanitize_text_field($_GET['mark_as']);
492
493 $attempt_info = tutor_utils()->quiz_attempt_info($attempt_id);
494
495 $previous_answer = $attempt_info['answers'][$answer_index];
496 $previous_correct = tutor_utils()->avalue_dot('has_correct', $previous_answer);
497
498 if ($mark_as === 'correct' && ! $previous_correct ){
499 $previous_answer['has_correct'] = 1;
500 $previous_answer['plus_mark'] = $previous_answer['question_mark'];
501 $previous_answer['minus_mark'] = 0;
502 $attempt_info['marks_earned'] = $attempt_info['marks_earned'] + $previous_answer['question_mark'];
503
504 }elseif($mark_as === 'incorrect' && $previous_correct){
505 $previous_answer['has_correct'] = 0;
506 $previous_answer['plus_mark'] = 0;
507 $previous_answer['minus_mark'] = 0;
508 $attempt_info['marks_earned'] = $attempt_info['marks_earned'] - $previous_answer['question_mark'];
509 }
510
511 $attempt_info['answers'][$answer_index] = $previous_answer;
512 $attempt_info['manual_reviewed'] = time();
513
514 tutor_utils()->quiz_update_attempt_info($attempt_id, $attempt_info);
515
516 wp_redirect(admin_url("admin.php?page=tutor_quiz_attempts&sub_page=view_attempt&attempt_id=".$attempt_id));
517 die();
518 }
519
520 }