This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
0 ignored issues
–
show
|
|||
2 | if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly |
||
3 | |||
4 | /** |
||
5 | * Sensei Quiz Class |
||
6 | * |
||
7 | * All functionality pertaining to the quiz post type in Sensei. |
||
8 | * |
||
9 | * @package Assessment |
||
10 | * @author Automattic |
||
11 | * |
||
12 | * @since 1.0.0 |
||
13 | */ |
||
14 | class Sensei_Quiz { |
||
15 | public $token; |
||
16 | public $meta_fields; |
||
17 | public $file; |
||
18 | |||
19 | /** |
||
20 | * Constructor. |
||
21 | * @since 1.0.0 |
||
22 | * |
||
23 | * @param $file |
||
24 | */ |
||
25 | public function __construct ( $file = __FILE__ ) { |
||
26 | $this->file = $file; |
||
27 | $this->token = 'quiz'; |
||
28 | $this->meta_fields = array( 'quiz_passmark', 'quiz_lesson', 'quiz_type', 'quiz_grade_type', 'pass_required','enable_quiz_reset' ); |
||
29 | add_action( 'save_post', array( $this, 'update_author' )); |
||
30 | |||
31 | // listen to the reset button click |
||
32 | add_action( 'template_redirect', array( $this, 'reset_button_click_listener' ) ); |
||
33 | |||
34 | // fire the complete quiz button submit for grading action |
||
35 | add_action( 'sensei_single_quiz_content_inside_before', array( $this, 'user_quiz_submit_listener' ) ); |
||
36 | |||
37 | // fire the save user answers quiz button click responder |
||
38 | add_action( 'sensei_single_quiz_content_inside_before', array( $this, 'user_save_quiz_answers_listener' ) ); |
||
39 | |||
40 | // fire the load global data function |
||
41 | add_action( 'sensei_single_quiz_content_inside_before', array( $this, 'load_global_quiz_data' ), 80 ); |
||
42 | |||
43 | add_action( 'template_redirect', array ( $this, 'quiz_has_no_questions') ); |
||
44 | |||
45 | |||
46 | } // End __construct() |
||
47 | |||
48 | /** |
||
49 | * Update the quiz author when the lesson post type is save |
||
50 | * |
||
51 | * @param int $post_id |
||
52 | * @return void |
||
53 | */ |
||
54 | public function update_author( $post_id ){ |
||
55 | |||
56 | // If this isn't a 'lesson' post, don't update it. |
||
57 | // if this is a revision don't save it |
||
58 | if ( isset( $_POST['post_type'] ) && 'lesson' != $_POST['post_type'] |
||
59 | || wp_is_post_revision( $post_id ) ) { |
||
60 | |||
61 | return; |
||
62 | |||
63 | } |
||
64 | // get the lesson author id to be use late |
||
65 | $saved_post = get_post( $post_id ); |
||
66 | $new_lesson_author_id = $saved_post->post_author; |
||
67 | |||
68 | //get the lessons quiz |
||
69 | $lesson_quizzes = Sensei()->lesson->lesson_quizzes( $post_id ); |
||
70 | foreach ( (array) $lesson_quizzes as $quiz_item ) { |
||
71 | |||
72 | if( ! $quiz_item ) { |
||
73 | continue; |
||
74 | } |
||
75 | |||
76 | // setup the quiz items new author value |
||
77 | $my_post = array( |
||
78 | 'ID' => $quiz_item, |
||
79 | 'post_author' => $new_lesson_author_id |
||
80 | ); |
||
81 | |||
82 | // remove the action so that it doesn't fire again |
||
83 | remove_action( 'save_post', array( $this, 'update_author' )); |
||
84 | |||
85 | // Update the post into the database |
||
86 | wp_update_post( $my_post ); |
||
87 | } |
||
88 | |||
89 | return; |
||
90 | }// end update_author |
||
91 | |||
92 | |||
93 | /** |
||
94 | * Get the lesson this quiz belongs to |
||
95 | * |
||
96 | * @since 1.7.2 |
||
97 | * @param int $quiz_id |
||
98 | * @return int @lesson_id |
||
99 | */ |
||
100 | public function get_lesson_id( $quiz_id ){ |
||
101 | |||
102 | if( empty( $quiz_id ) || ! intval( $quiz_id ) > 0 ){ |
||
103 | global $post; |
||
104 | if( 'quiz' == get_post_type( $post ) ){ |
||
105 | $quiz_id = $post->ID; |
||
106 | }else{ |
||
107 | return false; |
||
108 | } |
||
109 | |||
110 | } |
||
111 | |||
112 | $quiz = get_post( $quiz_id ); |
||
113 | $lesson_id = $quiz->post_parent; |
||
114 | |||
115 | return $lesson_id; |
||
116 | |||
117 | } // end lesson |
||
118 | |||
119 | |||
120 | /** |
||
121 | * user_save_quiz_answers_listener |
||
122 | * |
||
123 | * This function hooks into the quiz page and accepts the answer form save post. |
||
124 | * @since 1.7.3 |
||
125 | * @return bool $saved; |
||
126 | */ |
||
127 | public function user_save_quiz_answers_listener(){ |
||
128 | |||
129 | View Code Duplication | if( ! isset( $_POST[ 'quiz_save' ]) |
|
130 | || !isset( $_POST[ 'sensei_question' ] ) |
||
131 | || empty( $_POST[ 'sensei_question' ] ) |
||
132 | || ! wp_verify_nonce( $_POST['woothemes_sensei_save_quiz_nonce'], 'woothemes_sensei_save_quiz_nonce' ) > 1 ) { |
||
133 | return; |
||
134 | } |
||
135 | |||
136 | global $post; |
||
137 | $lesson_id = $this->get_lesson_id( $post->ID ); |
||
138 | $quiz_answers = $_POST[ 'sensei_question' ]; |
||
139 | // call the save function |
||
140 | self::save_user_answers( $quiz_answers, $_FILES , $lesson_id , get_current_user_id() ); |
||
141 | |||
142 | // remove the hook as it should only fire once per click |
||
143 | remove_action( 'sensei_single_quiz_content_inside_before', 'user_save_quiz_answers_listener' ); |
||
144 | |||
145 | } // end user_save_quiz_answers_listener |
||
146 | |||
147 | /** |
||
148 | * Save the user answers for the given lesson's quiz |
||
149 | * |
||
150 | * For this function you must supply all three parameters. If will return false one is left out. |
||
151 | * |
||
152 | * @since 1.7.4 |
||
153 | * @access public |
||
154 | * |
||
155 | * @param array $quiz_answers |
||
156 | * @param array $files from global $_FILES |
||
157 | * @param int $lesson_id |
||
158 | * @param int $user_id |
||
159 | * |
||
160 | * @return false or int $answers_saved |
||
161 | */ |
||
162 | public static function save_user_answers( $quiz_answers, $files = array(), $lesson_id , $user_id = 0 ){ |
||
163 | |||
164 | if( ! ( $user_id > 0 ) ){ |
||
165 | $user_id = get_current_user_id(); |
||
166 | } |
||
167 | |||
168 | // make sure the parameters are valid before continuing |
||
169 | View Code Duplication | if( empty( $lesson_id ) || empty( $user_id ) |
|
170 | || 'lesson' != get_post_type( $lesson_id ) |
||
171 | ||!get_userdata( $user_id ) |
||
172 | || !is_array( $quiz_answers ) ){ |
||
173 | |||
174 | return false; |
||
175 | |||
176 | } |
||
177 | |||
178 | |||
179 | // start the lesson before saving the data in case the user has not started the lesson |
||
180 | $activity_logged = Sensei_Utils::sensei_start_lesson( $lesson_id, $user_id ); |
||
181 | |||
182 | //prepare the answers |
||
183 | $prepared_answers = self::prepare_form_submitted_answers( $quiz_answers , $files ); |
||
184 | |||
185 | // save the user data |
||
186 | $answers_saved = Sensei_Utils::add_user_data( 'quiz_answers', $lesson_id, $prepared_answers, $user_id ) ; |
||
187 | |||
188 | // were the answers saved correctly? |
||
189 | if( intval( $answers_saved ) > 0){ |
||
190 | |||
191 | // save transient to make retrieval faster |
||
192 | $transient_key = 'sensei_answers_'.$user_id.'_'.$lesson_id; |
||
193 | set_transient( $transient_key, $prepared_answers, 10 * DAY_IN_SECONDS ); |
||
194 | |||
195 | // update the message showed to user |
||
196 | Sensei()->frontend->messages = '<div class="sensei-message note">' . __( 'Quiz Saved Successfully.', 'woothemes-sensei' ) . '</div>'; |
||
197 | } |
||
198 | |||
199 | return $answers_saved; |
||
200 | |||
201 | }// end save_user_answers() |
||
202 | |||
203 | /** |
||
204 | * Get the user answers for the given lesson's quiz. |
||
205 | * |
||
206 | * This function returns the data that is stored on the lesson as meta and is not compatible with |
||
207 | * retrieving data for quiz answer before sensei 1.7.4 |
||
208 | * |
||
209 | * |
||
210 | * @since 1.7.4 |
||
211 | * @access public |
||
212 | * |
||
213 | * @param int $lesson_id |
||
214 | * @param int $user_id |
||
215 | * |
||
216 | * @return array $answers or false |
||
217 | */ |
||
218 | public function get_user_answers( $lesson_id, $user_id ){ |
||
219 | |||
220 | $answers = false; |
||
221 | |||
222 | View Code Duplication | if ( ! intval( $lesson_id ) > 0 || 'lesson' != get_post_type( $lesson_id ) |
|
223 | || ! intval( $user_id ) > 0 || !get_userdata( $user_id ) ) { |
||
224 | return false; |
||
225 | } |
||
226 | |||
227 | // save some time and get the transient cached data |
||
228 | $transient_key = 'sensei_answers_'.$user_id.'_'.$lesson_id; |
||
229 | $transient_cached_answers = get_transient( $transient_key ); |
||
230 | |||
231 | // return the transient or get the values get the values from the comment meta |
||
232 | if( !empty( $transient_cached_answers ) && false != $transient_cached_answers ){ |
||
233 | |||
234 | $encoded_user_answers = $transient_cached_answers; |
||
235 | |||
236 | }else{ |
||
237 | |||
238 | $encoded_user_answers = Sensei_Utils::get_user_data( 'quiz_answers', $lesson_id , $user_id ); |
||
239 | |||
240 | } // end if transient check |
||
241 | |||
242 | if( ! is_array( $encoded_user_answers ) ){ |
||
243 | return false; |
||
244 | } |
||
245 | |||
246 | //set the transient with the new valid data for faster retrieval in future |
||
247 | set_transient( $transient_key, $encoded_user_answers, 10 * DAY_IN_SECONDS); |
||
248 | |||
249 | // decode an unserialize all answers |
||
250 | foreach( $encoded_user_answers as $question_id => $encoded_answer ) { |
||
251 | $decoded_answer = base64_decode( $encoded_answer ); |
||
252 | $answers[$question_id] = maybe_unserialize( $decoded_answer ); |
||
253 | } |
||
254 | |||
255 | return $answers; |
||
256 | |||
257 | }// end get_user_answers() |
||
258 | |||
259 | |||
260 | /** |
||
261 | * |
||
262 | * This function runs on the init hook and checks if the reset quiz button was clicked. |
||
263 | * |
||
264 | * @since 1.7.2 |
||
265 | * @hooked init |
||
266 | * |
||
267 | * @return void; |
||
268 | */ |
||
269 | public function reset_button_click_listener( ){ |
||
270 | |||
271 | if( ! isset( $_POST[ 'quiz_reset' ]) |
||
272 | || ! wp_verify_nonce( $_POST['woothemes_sensei_reset_quiz_nonce'], 'woothemes_sensei_reset_quiz_nonce' ) > 1 ) { |
||
273 | |||
274 | return; // exit |
||
275 | } |
||
276 | |||
277 | global $post; |
||
278 | $current_quiz_id = $post->ID; |
||
279 | $lesson_id = $this->get_lesson_id( $current_quiz_id ); |
||
280 | |||
281 | // reset all user data |
||
282 | $this->reset_user_lesson_data( $lesson_id, get_current_user_id() ); |
||
283 | |||
284 | //this function should only run once |
||
285 | remove_action( 'template_redirect', array( $this, 'reset_button_click_listener' ) ); |
||
286 | |||
287 | } // end reset_button_click_listener |
||
288 | |||
289 | /** |
||
290 | * Complete/ submit quiz hooked function |
||
291 | * |
||
292 | * This function listens to the complete button submit action and processes the users submitted answers |
||
293 | * not that this function submits the given users quiz answers for grading. |
||
294 | * |
||
295 | * @since 1.7.4 |
||
296 | * @access public |
||
297 | * |
||
298 | * @since |
||
299 | * @return void |
||
300 | */ |
||
301 | public function user_quiz_submit_listener() { |
||
302 | |||
303 | // only respond to valid quiz completion submissions |
||
304 | View Code Duplication | if( ! isset( $_POST[ 'quiz_complete' ]) |
|
305 | || !isset( $_POST[ 'sensei_question' ] ) |
||
306 | || empty( $_POST[ 'sensei_question' ] ) |
||
307 | || ! wp_verify_nonce( $_POST['woothemes_sensei_complete_quiz_nonce'], 'woothemes_sensei_complete_quiz_nonce' ) > 1 ) { |
||
308 | return; |
||
309 | } |
||
310 | |||
311 | global $post, $current_user; |
||
312 | $lesson_id = $this->get_lesson_id( $post->ID ); |
||
313 | $quiz_answers = $_POST[ 'sensei_question' ]; |
||
314 | |||
315 | self::submit_answers_for_grading( $quiz_answers, $_FILES , $lesson_id , $current_user->ID ); |
||
316 | |||
317 | } // End sensei_complete_quiz() |
||
318 | |||
319 | /** |
||
320 | * This function set's up the data need for the quiz page |
||
321 | * |
||
322 | * This function hooks into sensei_complete_quiz and load the global data for the |
||
323 | * current quiz. |
||
324 | * |
||
325 | * @since 1.7.4 |
||
326 | * @access public |
||
327 | * |
||
328 | */ |
||
329 | public function load_global_quiz_data(){ |
||
330 | |||
331 | global $post, $current_user; |
||
332 | $this->data = new stdClass(); |
||
333 | |||
334 | // Default grade |
||
335 | $grade = 0; |
||
336 | |||
337 | // Get Quiz Questions |
||
338 | $lesson_quiz_questions = Sensei()->lesson->lesson_quiz_questions( $post->ID ); |
||
339 | |||
340 | $quiz_lesson_id = absint( get_post_meta( $post->ID, '_quiz_lesson', true ) ); |
||
341 | |||
342 | // Get quiz grade type |
||
343 | $quiz_grade_type = get_post_meta( $post->ID, '_quiz_grade_type', true ); |
||
344 | |||
345 | // Get quiz pass setting |
||
346 | $pass_required = get_post_meta( $post->ID, '_pass_required', true ); |
||
347 | |||
348 | // Get quiz pass mark |
||
349 | $quiz_passmark = abs( round( doubleval( get_post_meta( $post->ID, '_quiz_passmark', true ) ), 2 ) ); |
||
350 | |||
351 | // Get latest quiz answers and grades |
||
352 | $lesson_id = Sensei()->quiz->get_lesson_id( $post->ID ); |
||
353 | $user_quizzes = Sensei()->quiz->get_user_answers( $lesson_id, get_current_user_id() ); |
||
354 | $user_lesson_status = Sensei_Utils::user_lesson_status( $quiz_lesson_id, $current_user->ID ); |
||
355 | $user_quiz_grade = 0; |
||
356 | if( isset( $user_lesson_status->comment_ID ) ) { |
||
357 | $user_quiz_grade = get_comment_meta( $user_lesson_status->comment_ID, 'grade', true ); |
||
358 | } |
||
359 | |||
360 | if ( ! is_array($user_quizzes) ) { $user_quizzes = array(); } |
||
361 | |||
362 | // Check again that the lesson is complete |
||
363 | $user_lesson_end = Sensei_Utils::user_completed_lesson( $user_lesson_status ); |
||
364 | $user_lesson_complete = false; |
||
365 | if ( $user_lesson_end ) { |
||
366 | $user_lesson_complete = true; |
||
367 | } // End If Statement |
||
368 | |||
369 | $reset_allowed = get_post_meta( $post->ID, '_enable_quiz_reset', true ); |
||
370 | //backwards compatibility |
||
371 | if( 'on' == $reset_allowed ) { |
||
372 | $reset_allowed = 1; |
||
373 | } |
||
374 | |||
375 | // Build frontend data object for backwards compatibility |
||
376 | // using this is no longer recommended |
||
377 | $this->data->user_quiz_grade = $user_quiz_grade;// Sensei_Quiz::get_user_quiz_grade( $lesson_id, get_current_user_id() ); |
||
378 | $this->data->quiz_passmark = $quiz_passmark; |
||
379 | $this->data->quiz_lesson = $quiz_lesson_id; |
||
380 | $this->data->quiz_grade_type = $quiz_grade_type; // get_post_meta( $quiz_id, '_quiz_grade_type', true ); |
||
381 | $this->data->user_lesson_end = $user_lesson_end; |
||
382 | $this->data->user_lesson_complete = $user_lesson_complete; //Sensei_Utils::user_completed_lesson( $lesson_id, get_current_user_id() ); |
||
383 | $this->data->lesson_quiz_questions = $lesson_quiz_questions; |
||
384 | $this->data->reset_quiz_allowed = $reset_allowed; // Sensei_Quiz::is_reset_allowed( $lesson_id ); |
||
385 | |||
386 | } // end load_global_quiz_data |
||
387 | |||
388 | |||
389 | /** |
||
390 | * This function converts the submitted array and makes it ready it for storage |
||
391 | * |
||
392 | * Creating a single array of all question types including file id's to be stored |
||
393 | * as comment meta by the calling function. |
||
394 | * |
||
395 | * @since 1.7.4 |
||
396 | * @access public |
||
397 | * |
||
398 | * @param array $unprepared_answers |
||
399 | * @param $files |
||
400 | * @return array |
||
401 | */ |
||
402 | public static function prepare_form_submitted_answers( $unprepared_answers, $files ){ |
||
403 | |||
404 | |||
405 | $prepared_answers = array(); |
||
406 | |||
407 | // validate incoming answers |
||
408 | if( empty( $unprepared_answers ) || ! is_array( $unprepared_answers ) ){ |
||
409 | return false; |
||
410 | } |
||
411 | |||
412 | // Loop through submitted quiz answers and save them appropriately |
||
413 | foreach( $unprepared_answers as $question_id => $answer ) { |
||
414 | |||
415 | //get the current questions question type |
||
416 | $question_type = Sensei()->question->get_question_type( $question_id ); |
||
417 | |||
418 | // Sanitise answer |
||
419 | if( 0 == get_magic_quotes_gpc() ) { |
||
420 | $answer = wp_unslash( $answer ); |
||
421 | } |
||
422 | |||
423 | // compress the answer for saving |
||
424 | if( 'multi-line' == $question_type ) { |
||
425 | $answer = esc_html( $answer ); |
||
426 | }elseif( 'file-upload' == $question_type ){ |
||
427 | $file_key = 'file_upload_' . $question_id; |
||
428 | if( isset( $files[ $file_key ] ) ) { |
||
429 | $attachment_id = Sensei_Utils::upload_file( $files[ $file_key ] ); |
||
430 | if( $attachment_id ) { |
||
431 | $answer = $attachment_id; |
||
432 | } |
||
433 | } |
||
434 | } // end if |
||
435 | |||
436 | $prepared_answers[ $question_id ] = base64_encode( maybe_serialize( $answer ) ); |
||
437 | |||
438 | }// end for each $quiz_answers |
||
439 | |||
440 | return $prepared_answers; |
||
441 | } // prepare_form_submitted_answers |
||
442 | |||
443 | /** |
||
444 | * Reset user submitted questions |
||
445 | * |
||
446 | * This function resets the quiz data for a user that has been submitted fro grading already. It is different to |
||
447 | * the save_user_answers as currently the saved and submitted answers are stored differently. |
||
448 | * |
||
449 | * @since 1.7.4 |
||
450 | * @access public |
||
451 | * |
||
452 | * @return bool $reset_success |
||
453 | * @param int $user_id |
||
454 | * @param int $lesson_id |
||
455 | */ |
||
456 | public function reset_user_lesson_data( $lesson_id , $user_id = 0 ){ |
||
457 | |||
458 | //make sure the parameters are valid |
||
459 | if( empty( $lesson_id ) || empty( $user_id ) |
||
460 | || 'lesson' != get_post_type( $lesson_id ) |
||
461 | || ! get_userdata( $user_id ) ){ |
||
462 | return false; |
||
463 | } |
||
464 | |||
465 | |||
466 | |||
467 | //get the users lesson status to make |
||
468 | $user_lesson_status = Sensei_Utils::user_lesson_status( $lesson_id, $user_id ); |
||
469 | if( ! isset( $user_lesson_status->comment_ID ) ) { |
||
470 | // this user is not taking this lesson so this process is not needed |
||
471 | return false; |
||
472 | } |
||
473 | |||
474 | //get the lesson quiz and course |
||
475 | $quiz_id = Sensei()->lesson->lesson_quizzes( $lesson_id ); |
||
476 | $course_id = Sensei()->lesson->get_course_id( $lesson_id ); |
||
477 | |||
478 | // reset the transients |
||
479 | $answers_transient_key = 'sensei_answers_'.$user_id.'_'.$lesson_id; |
||
480 | $grades_transient_key = 'quiz_grades_'.$user_id.'_'.$lesson_id; |
||
481 | $answers_feedback_transient_key = 'sensei_answers_feedback_'.$user_id.'_'.$lesson_id; |
||
482 | delete_transient( $answers_transient_key ); |
||
483 | delete_transient( $grades_transient_key ); |
||
484 | delete_transient( $answers_feedback_transient_key ); |
||
485 | |||
486 | // reset the quiz answers and feedback notes |
||
487 | $deleted_answers = Sensei_Utils::delete_user_data( 'quiz_answers', $lesson_id, $user_id ); |
||
488 | $deleted_grades = Sensei_Utils::delete_user_data( 'quiz_grades', $lesson_id, $user_id ); |
||
489 | $deleted_user_feedback = Sensei_Utils::delete_user_data( 'quiz_answers_feedback', $lesson_id, $user_id ); |
||
490 | |||
491 | // Delete quiz answers, this auto deletes the corresponding meta data, such as the question/answer grade |
||
492 | Sensei_Utils::sensei_delete_quiz_answers( $quiz_id, $user_id ); |
||
493 | |||
494 | Sensei_Utils::update_lesson_status( $user_id , $lesson_id, 'in-progress', array( 'questions_asked' => '', 'grade' => '' ) ); |
||
495 | |||
496 | // Update course completion |
||
497 | Sensei_Utils::update_course_status( $user_id, $course_id ); |
||
498 | |||
499 | // Run any action on quiz/lesson reset (previously this didn't occur on resetting a quiz, see resetting a lesson in sensei_complete_lesson() |
||
500 | do_action( 'sensei_user_lesson_reset', $user_id, $lesson_id ); |
||
501 | Sensei()->frontend->messages = '<div class="sensei-message note">' . __( 'Quiz Reset Successfully.', 'woothemes-sensei' ) . '</div>'; |
||
502 | |||
503 | return ( $deleted_answers && $deleted_grades ) ; |
||
504 | |||
505 | } // end reset_user_lesson_data |
||
506 | |||
507 | /** |
||
508 | * Submit the users quiz answers for grading |
||
509 | * |
||
510 | * This function accepts users answers and stores it but also initiates the grading |
||
511 | * if a quiz can be graded automatically it will, if not the answers can be graded by the teacher. |
||
512 | * |
||
513 | * @since 1.7.4 |
||
514 | * @access public |
||
515 | * |
||
516 | * @param array $quiz_answers |
||
517 | * @param array $files from $_FILES |
||
518 | * @param int $user_id |
||
519 | * @param int $lesson_id |
||
520 | * |
||
521 | * @return bool $answers_submitted |
||
522 | */ |
||
523 | public static function submit_answers_for_grading( $quiz_answers , $files = array() , $lesson_id , $user_id = 0 ){ |
||
524 | |||
525 | $answers_submitted = false; |
||
526 | |||
527 | // get the user_id if none was passed in use the current logged in user |
||
528 | if( ! intval( $user_id ) > 0 ) { |
||
529 | $user_id = get_current_user_id(); |
||
530 | } |
||
531 | |||
532 | // make sure the parameters are valid before continuing |
||
533 | View Code Duplication | if( empty( $lesson_id ) || empty( $user_id ) |
|
534 | || 'lesson' != get_post_type( $lesson_id ) |
||
535 | ||!get_userdata( $user_id ) |
||
536 | || !is_array( $quiz_answers ) ){ |
||
537 | |||
538 | return false; |
||
539 | |||
540 | } |
||
541 | |||
542 | // Default grade |
||
543 | $grade = 0; |
||
544 | |||
545 | // Get Quiz ID |
||
546 | $quiz_id = Sensei()->lesson->lesson_quizzes( $lesson_id ); |
||
547 | |||
548 | // Get quiz grade type |
||
549 | $quiz_grade_type = get_post_meta( $quiz_id, '_quiz_grade_type', true ); |
||
550 | |||
551 | // Get quiz pass setting |
||
552 | $pass_required = get_post_meta( $quiz_id, '_pass_required', true ); |
||
553 | |||
554 | // Get the minimum percentage need to pass this quiz |
||
555 | $quiz_pass_percentage = abs( round( doubleval( get_post_meta( $quiz_id, '_quiz_passmark', true ) ), 2 ) ); |
||
556 | |||
557 | // Handle Quiz Questions asked |
||
558 | // This is to ensure we save the questions that we've asked this user and that this can't be change unless |
||
559 | // the quiz is reset by admin or user( user: only if the setting is enabled ). |
||
560 | // get the questions asked when when the quiz questions were generated for the user : Sensei_Lesson::lesson_quiz_questions |
||
561 | $user_lesson_status = Sensei_Utils::user_lesson_status( $lesson_id, $user_id ); |
||
562 | $questions_asked = get_comment_meta( $user_lesson_status->comment_ID, 'questions_asked', true ); |
||
563 | if( empty( $questions_asked ) ){ |
||
564 | |||
565 | $questions_asked = array_keys( $quiz_answers ); |
||
566 | $questions_asked_string = implode( ',', $questions_asked ); |
||
567 | |||
568 | // Save questions that were asked in this quiz |
||
569 | update_comment_meta( $user_lesson_status->comment_ID, 'questions_asked', $questions_asked_string ); |
||
570 | |||
571 | } |
||
572 | |||
573 | // Save Quiz Answers for grading, the save function also calls the sensei_start_lesson |
||
574 | self::save_user_answers( $quiz_answers , $files , $lesson_id , $user_id ); |
||
575 | |||
576 | // Grade quiz |
||
577 | $grade = Sensei_Grading::grade_quiz_auto( $quiz_id, $quiz_answers, 0 , $quiz_grade_type ); |
||
578 | |||
579 | // Get Lesson Grading Setting |
||
580 | $lesson_metadata = array(); |
||
581 | $lesson_status = 'ungraded'; // Default when completing a quiz |
||
582 | |||
583 | // At this point the answers have been submitted |
||
584 | $answers_submitted = true; |
||
585 | |||
586 | // if this condition is false the quiz should manually be graded by admin |
||
587 | if ('auto' == $quiz_grade_type && ! is_wp_error( $grade ) ) { |
||
588 | |||
589 | // Quiz has been automatically Graded |
||
590 | View Code Duplication | if ( 'on' == $pass_required ) { |
|
591 | |||
592 | // Student has reached the pass mark and lesson is complete |
||
593 | if ( $quiz_pass_percentage <= $grade ) { |
||
594 | |||
595 | $lesson_status = 'passed'; |
||
596 | |||
597 | } else { |
||
598 | |||
599 | $lesson_status = 'failed'; |
||
600 | |||
601 | } // End If Statement |
||
602 | |||
603 | } else { |
||
604 | |||
605 | // Student only has to partake the quiz |
||
606 | $lesson_status = 'graded'; |
||
607 | |||
608 | } |
||
609 | |||
610 | $lesson_metadata['grade'] = $grade; // Technically already set as part of "WooThemes_Sensei_Utils::sensei_grade_quiz_auto()" above |
||
611 | |||
612 | } // end if ! is_wp_error( $grade ... |
||
613 | |||
614 | Sensei_Utils::update_lesson_status( $user_id, $lesson_id, $lesson_status, $lesson_metadata ); |
||
615 | |||
616 | if( 'passed' == $lesson_status || 'graded' == $lesson_status ){ |
||
617 | |||
618 | /** |
||
619 | * Lesson end action hook |
||
620 | * |
||
621 | * This hook is fired after a lesson quiz has been graded and the lesson status is 'passed' OR 'graded' |
||
622 | * |
||
623 | * @param int $user_id |
||
624 | * @param int $lesson_id |
||
625 | */ |
||
626 | do_action( 'sensei_user_lesson_end', $user_id, $lesson_id ); |
||
627 | |||
628 | } |
||
629 | |||
630 | /** |
||
631 | * User quiz has been submitted |
||
632 | * |
||
633 | * Fires the end of the submit_answers_for_grading function. It will fire irrespective of the submission |
||
634 | * results. |
||
635 | * |
||
636 | * @param int $user_id |
||
637 | * @param int $quiz_id |
||
638 | * @param string $grade |
||
639 | * @param string $quiz_pass_percentage |
||
640 | * @param string $quiz_grade_type |
||
641 | */ |
||
642 | do_action( 'sensei_user_quiz_submitted', $user_id, $quiz_id, $grade, $quiz_pass_percentage, $quiz_grade_type ); |
||
643 | |||
644 | return $answers_submitted; |
||
645 | |||
646 | }// end submit_answers_for_grading |
||
647 | |||
648 | /** |
||
649 | * Get the user question answer |
||
650 | * |
||
651 | * This function gets the the users saved answer on given quiz for the given question parameter |
||
652 | * this function allows for a fallback to users still using the question saved data from before 1.7.4 |
||
653 | * |
||
654 | * @since 1.7.4 |
||
655 | * |
||
656 | * @param int $lesson_id |
||
657 | * @param int $question_id |
||
658 | * @param int $user_id ( optional ) |
||
659 | * |
||
660 | * @return bool|null $answers_submitted |
||
661 | */ |
||
662 | public function get_user_question_answer( $lesson_id, $question_id, $user_id = 0 ){ |
||
663 | |||
664 | // parameter validation |
||
665 | View Code Duplication | if( empty( $lesson_id ) || empty( $question_id ) |
|
666 | || ! ( intval( $lesson_id ) > 0 ) |
||
667 | || ! ( intval( $question_id ) > 0 ) |
||
668 | || 'lesson' != get_post_type( $lesson_id ) |
||
669 | || 'question' != get_post_type( $question_id )) { |
||
670 | |||
671 | return false; |
||
672 | } |
||
673 | |||
674 | if( ! ( intval( $user_id ) > 0 ) ){ |
||
675 | $user_id = get_current_user_id(); |
||
676 | } |
||
677 | |||
678 | $users_answers = $this->get_user_answers( $lesson_id, $user_id ); |
||
679 | |||
680 | if( !$users_answers || empty( $users_answers ) |
||
681 | || ! is_array( $users_answers ) || ! isset( $users_answers[ $question_id ] ) ){ |
||
682 | |||
683 | //Fallback for pre 1.7.4 data |
||
684 | $comment = Sensei_Utils::sensei_check_for_activity( array( 'post_id' => $question_id, 'user_id' => $user_id, 'type' => 'sensei_user_answer' ), true ); |
||
685 | |||
686 | if( ! isset( $comment->comment_content ) ){ |
||
687 | return NULL; |
||
688 | } |
||
689 | |||
690 | return maybe_unserialize( base64_decode( $comment->comment_content ) ); |
||
691 | } |
||
692 | |||
693 | return $users_answers[ $question_id ]; |
||
694 | |||
695 | }// end get_user_question_answer |
||
696 | |||
697 | /** |
||
698 | * Saving the users quiz question grades |
||
699 | * |
||
700 | * This function save all the grades for all the question in a given quiz on the lesson |
||
701 | * comment meta. It makes use of transients to save the grades for easier access at a later stage |
||
702 | * |
||
703 | * @since 1.7.4 |
||
704 | * |
||
705 | * @param array $quiz_grades{ |
||
706 | * @type int $question_id |
||
707 | * @type int $question_grade |
||
708 | * } |
||
709 | * @param $lesson_id |
||
710 | * @param $user_id (Optional) will use the current user if not supplied |
||
711 | * |
||
712 | * @return bool |
||
713 | */ |
||
714 | public function set_user_grades( $quiz_grades, $lesson_id, $user_id = 0 ){ |
||
715 | |||
716 | // get the user_id if none was passed in use the current logged in user |
||
717 | if( ! intval( $user_id ) > 0 ) { |
||
718 | $user_id = get_current_user_id(); |
||
719 | } |
||
720 | |||
721 | // make sure the parameters are valid before continuing |
||
722 | View Code Duplication | if( empty( $lesson_id ) || empty( $user_id ) |
|
723 | || 'lesson' != get_post_type( $lesson_id ) |
||
724 | ||!get_userdata( $user_id ) |
||
725 | || !is_array( $quiz_grades ) ){ |
||
726 | |||
727 | return false; |
||
728 | |||
729 | } |
||
730 | |||
731 | $success = false; |
||
732 | |||
733 | // save that data for the user on the lesson comment meta |
||
734 | $comment_meta_id = Sensei_Utils::add_user_data( 'quiz_grades', $lesson_id, $quiz_grades, $user_id ); |
||
735 | |||
736 | // were the grades save successfully ? |
||
737 | View Code Duplication | if( intval( $comment_meta_id ) > 0 ) { |
|
738 | |||
739 | $success = true; |
||
740 | // save transient |
||
741 | $transient_key = 'quiz_grades_'. $user_id . '_' . $lesson_id; |
||
742 | set_transient( $transient_key, $quiz_grades, 10 * DAY_IN_SECONDS ); |
||
743 | } |
||
744 | |||
745 | return $success; |
||
746 | |||
747 | }// end set_user_grades |
||
748 | |||
749 | /** |
||
750 | * Retrieve the users quiz question grades |
||
751 | * |
||
752 | * This function gets all the grades for all the questions in the given lesson quiz for a specific user. |
||
753 | * |
||
754 | * @since 1.7.4 |
||
755 | * |
||
756 | * @param $lesson_id |
||
757 | * @param $user_id (Optional) will use the current user if not supplied |
||
758 | * |
||
759 | * @return array $user_quiz_grades or false if none exists for this users |
||
760 | */ |
||
761 | public function get_user_grades( $lesson_id, $user_id = 0 ){ |
||
762 | |||
763 | $user_grades = array(); |
||
764 | |||
765 | // get the user_id if none was passed in use the current logged in user |
||
766 | if( ! intval( $user_id ) > 0 ) { |
||
767 | $user_id = get_current_user_id(); |
||
768 | } |
||
769 | |||
770 | View Code Duplication | if ( ! intval( $lesson_id ) > 0 || 'lesson' != get_post_type( $lesson_id ) |
|
771 | || ! intval( $user_id ) > 0 || !get_userdata( $user_id ) ) { |
||
772 | return false; |
||
773 | } |
||
774 | |||
775 | // save some time and get the transient cached data |
||
776 | $transient_key = 'quiz_grades_'. $user_id . '_' . $lesson_id; |
||
777 | $user_grades = get_transient( $transient_key ); |
||
778 | |||
779 | // get the data if nothing was stored in the transient |
||
780 | View Code Duplication | if( empty( $user_grades ) || false != $user_grades ){ |
|
781 | |||
782 | $user_grades = Sensei_Utils::get_user_data( 'quiz_grades', $lesson_id, $user_id ); |
||
783 | |||
784 | //set the transient with the new valid data for faster retrieval in future |
||
785 | set_transient( $transient_key, $user_grades, 10 * DAY_IN_SECONDS ); |
||
786 | |||
787 | } // end if transient check |
||
788 | |||
789 | // if there is no data for this user |
||
790 | if( ! is_array( $user_grades ) ){ |
||
791 | return false; |
||
792 | } |
||
793 | |||
794 | return $user_grades; |
||
795 | |||
796 | }// end get_user_grades |
||
797 | |||
798 | /** |
||
799 | * Get the user question grade |
||
800 | * |
||
801 | * This function gets the grade on a quiz for the given question parameter |
||
802 | * It does NOT do any grading. It simply retrieves the data that was stored during grading. |
||
803 | * this function allows for a fallback to users still using the question saved data from before 1.7.4 |
||
804 | * |
||
805 | * @since 1.7.4 |
||
806 | * |
||
807 | * @param int $lesson_id |
||
808 | * @param int $question_id |
||
809 | * @param int $user_id ( optional ) |
||
810 | * |
||
811 | * @return bool $question_grade |
||
812 | */ |
||
813 | public function get_user_question_grade( $lesson_id, $question_id, $user_id = 0 ){ |
||
814 | |||
815 | // parameter validation |
||
816 | View Code Duplication | if( empty( $lesson_id ) || empty( $question_id ) |
|
817 | || ! ( intval( $lesson_id ) > 0 ) |
||
818 | || ! ( intval( $question_id ) > 0 ) |
||
819 | || 'lesson' != get_post_type( $lesson_id ) |
||
820 | || 'question' != get_post_type( $question_id )) { |
||
821 | |||
822 | return false; |
||
823 | } |
||
824 | |||
825 | $all_user_grades = self::get_user_grades( $lesson_id,$user_id ); |
||
826 | |||
827 | if( ! $all_user_grades || ! isset( $all_user_grades[ $question_id ] ) ){ |
||
828 | |||
829 | //fallback to data pre 1.7.4 |
||
830 | $args = array( |
||
831 | 'post_id' => $question_id, |
||
832 | 'user_id' => $user_id, |
||
833 | 'type' => 'sensei_user_answer' |
||
834 | ); |
||
835 | |||
836 | $question_activity = Sensei_Utils::sensei_check_for_activity( $args , true ); |
||
837 | $fall_back_grade = false; |
||
838 | if( isset( $question_activity->comment_ID ) ){ |
||
839 | $fall_back_grade = get_comment_meta( $question_activity->comment_ID , 'user_grade', true ); |
||
840 | } |
||
841 | |||
842 | return $fall_back_grade; |
||
843 | |||
844 | } // end if $all_user_grades... |
||
845 | |||
846 | return $all_user_grades[ $question_id ]; |
||
847 | |||
848 | }// end get_user_question_grade |
||
849 | |||
850 | /** |
||
851 | * Save the user's answers feedback |
||
852 | * |
||
853 | * For this function you must supply all three parameters. If will return false one is left out. |
||
854 | * The data will be saved on the lesson ID supplied. |
||
855 | * |
||
856 | * @since 1.7.5 |
||
857 | * @access public |
||
858 | * |
||
859 | * @param array $answers_feedback{ |
||
860 | * $type int $question_id |
||
861 | * $type string $question_feedback |
||
862 | * } |
||
863 | * @param int $lesson_id |
||
864 | * @param int $user_id |
||
865 | * |
||
866 | * @return false or int $feedback_saved |
||
867 | */ |
||
868 | public function save_user_answers_feedback( $answers_feedback, $lesson_id , $user_id = 0 ){ |
||
869 | |||
870 | // make sure the parameters are valid before continuing |
||
871 | View Code Duplication | if( empty( $lesson_id ) || empty( $user_id ) |
|
872 | || 'lesson' != get_post_type( $lesson_id ) |
||
873 | ||!get_userdata( $user_id ) |
||
874 | || !is_array( $answers_feedback ) ){ |
||
875 | |||
876 | return false; |
||
877 | |||
878 | } |
||
879 | |||
880 | |||
881 | // check if the lesson is started before saving, if not start the lesson for the user |
||
882 | if ( !( 0 < intval( Sensei_Utils::user_started_lesson( $lesson_id, $user_id) ) ) ) { |
||
883 | Sensei_Utils::sensei_start_lesson( $lesson_id, $user_id ); |
||
884 | } |
||
885 | |||
886 | // encode the feedback |
||
887 | $encoded_answers_feedback = array(); |
||
888 | foreach( $answers_feedback as $question_id => $feedback ){ |
||
889 | $encoded_answers_feedback[ $question_id ] = base64_encode( $feedback ); |
||
890 | } |
||
891 | |||
892 | // save the user data |
||
893 | $feedback_saved = Sensei_Utils::add_user_data( 'quiz_answers_feedback', $lesson_id , $encoded_answers_feedback, $user_id ) ; |
||
894 | |||
895 | //Were the the question feedback save correctly? |
||
896 | View Code Duplication | if( intval( $feedback_saved ) > 0){ |
|
897 | |||
898 | // save transient to make retrieval faster in future |
||
899 | $transient_key = 'sensei_answers_feedback_'.$user_id.'_'.$lesson_id; |
||
900 | set_transient( $transient_key, $encoded_answers_feedback, 10 * DAY_IN_SECONDS ); |
||
901 | |||
902 | } |
||
903 | |||
904 | return $feedback_saved; |
||
905 | |||
906 | } // end save_user_answers_feedback |
||
907 | |||
908 | /** |
||
909 | * Get the user's answers feedback. |
||
910 | * |
||
911 | * This function returns the feedback submitted by the teacher/admin |
||
912 | * during grading. Grading occurs manually or automatically. |
||
913 | * |
||
914 | * @since 1.7.5 |
||
915 | * @access public |
||
916 | * |
||
917 | * @param int $lesson_id |
||
918 | * @param int $user_id |
||
919 | * |
||
920 | * @return false | array $answers_feedback{ |
||
921 | * $type int $question_id |
||
922 | * $type string $question_feedback |
||
923 | * } |
||
924 | */ |
||
925 | public function get_user_answers_feedback( $lesson_id , $user_id = 0 ){ |
||
926 | |||
927 | $answers_feedback = array(); |
||
928 | |||
929 | // get the user_id if none was passed in use the current logged in user |
||
930 | if( ! intval( $user_id ) > 0 ) { |
||
931 | $user_id = get_current_user_id(); |
||
932 | } |
||
933 | |||
934 | View Code Duplication | if ( ! intval( $lesson_id ) > 0 || 'lesson' != get_post_type( $lesson_id ) |
|
935 | || ! intval( $user_id ) > 0 || !get_userdata( $user_id ) ) { |
||
936 | return false; |
||
937 | } |
||
938 | |||
939 | // first check the transient to save a few split seconds |
||
940 | $transient_key = 'sensei_answers_feedback_'.$user_id.'_'.$lesson_id; |
||
941 | $encoded_feedback = get_transient( $transient_key ); |
||
942 | |||
943 | // get the data if nothing was stored in the transient |
||
944 | View Code Duplication | if( empty( $encoded_feedback ) || !$encoded_feedback ){ |
|
945 | |||
946 | $encoded_feedback = Sensei_Utils::get_user_data( 'quiz_answers_feedback', $lesson_id, $user_id ); |
||
947 | |||
948 | //set the transient with the new valid data for faster retrieval in future |
||
949 | set_transient( $transient_key, $encoded_feedback, 10 * DAY_IN_SECONDS); |
||
950 | |||
951 | } // end if transient check |
||
952 | |||
953 | // if there is no data for this user |
||
954 | if( ! is_array( $encoded_feedback ) ){ |
||
955 | return false; |
||
956 | } |
||
957 | |||
958 | foreach( $encoded_feedback as $question_id => $feedback ){ |
||
959 | |||
960 | $answers_feedback[ $question_id ] = base64_decode( $feedback ); |
||
961 | |||
962 | } |
||
963 | |||
964 | return $answers_feedback; |
||
965 | |||
966 | } // end get_user_answers_feedback |
||
967 | |||
968 | /** |
||
969 | * Get the user's answer feedback for a specific question. |
||
970 | * |
||
971 | * This function gives you a single answer note/feedback string |
||
972 | * for the user on the given question. |
||
973 | * |
||
974 | * @since 1.7.5 |
||
975 | * @access public |
||
976 | * |
||
977 | * @param int $lesson_id |
||
978 | * @param int $question_id |
||
979 | * @param int $user_id |
||
980 | * |
||
981 | * @return string $feedback or bool if false |
||
982 | */ |
||
983 | public function get_user_question_feedback( $lesson_id, $question_id, $user_id = 0 ){ |
||
984 | |||
985 | $feedback = false; |
||
986 | |||
987 | // parameter validation |
||
988 | View Code Duplication | if( empty( $lesson_id ) || empty( $question_id ) |
|
989 | || ! ( intval( $lesson_id ) > 0 ) |
||
990 | || ! ( intval( $question_id ) > 0 ) |
||
991 | || 'lesson' != get_post_type( $lesson_id ) |
||
992 | || 'question' != get_post_type( $question_id )) { |
||
993 | |||
994 | return false; |
||
995 | } |
||
996 | |||
997 | // get all the feedback for the user on the given lesson |
||
998 | $all_feedback = $this->get_user_answers_feedback( $lesson_id, $user_id ); |
||
999 | |||
1000 | if( !$all_feedback || empty( $all_feedback ) |
||
1001 | || ! is_array( $all_feedback ) || ! isset( $all_feedback[ $question_id ] ) ){ |
||
1002 | |||
1003 | //fallback to data pre 1.7.4 |
||
1004 | |||
1005 | // setup the sensei data query |
||
1006 | $args = array( |
||
1007 | 'post_id' => $question_id, |
||
1008 | 'user_id' => $user_id, |
||
1009 | 'type' => 'sensei_user_answer' |
||
1010 | ); |
||
1011 | $question_activity = Sensei_Utils::sensei_check_for_activity( $args , true ); |
||
1012 | |||
1013 | // set the default to false and return that if no old data is available. |
||
1014 | if( isset( $question_activity->comment_ID ) ){ |
||
1015 | $feedback = base64_decode( get_comment_meta( $question_activity->comment_ID , 'answer_note', true ) ); |
||
1016 | } |
||
1017 | |||
1018 | // finally use the default question feedback |
||
1019 | if( empty( $feedback ) ){ |
||
1020 | $feedback = get_post_meta( $question_id, '_answer_feedback', true ); |
||
1021 | } |
||
1022 | |||
1023 | return $feedback; |
||
1024 | |||
1025 | } |
||
1026 | |||
1027 | return $all_feedback[ $question_id ]; |
||
1028 | |||
1029 | } // end get_user_question_feedback |
||
1030 | |||
1031 | /** |
||
1032 | * Check if a quiz has no questions, and redirect back to lesson. |
||
1033 | * |
||
1034 | * Though a quiz is created for each lesson, it should not be visible |
||
1035 | * unless it has questions. |
||
1036 | * |
||
1037 | * @since 1.9.0 |
||
1038 | * @access public |
||
1039 | * @param none |
||
1040 | * @return void |
||
1041 | */ |
||
1042 | |||
1043 | public function quiz_has_no_questions() { |
||
1044 | |||
1045 | if( ! is_singular( 'quiz' ) ) { |
||
1046 | return; |
||
1047 | } |
||
1048 | |||
1049 | global $post; |
||
1050 | |||
1051 | $lesson_id = $this->get_lesson_id($post->ID); |
||
1052 | |||
1053 | $has_questions = get_post_meta( $lesson_id, '_quiz_has_questions', true ); |
||
1054 | |||
1055 | $lesson = get_post($lesson_id); |
||
1056 | |||
1057 | if ( is_singular('quiz') && ! $has_questions && $_SERVER['REQUEST_URI'] != "/lesson/$lesson->post_name" ) { |
||
1058 | |||
1059 | wp_redirect(get_permalink($lesson->ID), 301); |
||
1060 | exit; |
||
1061 | |||
1062 | } |
||
1063 | |||
1064 | } // end quiz_has_no_questions |
||
1065 | |||
1066 | /** |
||
1067 | * Deprecate the sensei_single_main_content on the single-quiz template. |
||
1068 | * |
||
1069 | * @deprecated since 1.9.0 |
||
1070 | */ |
||
1071 | public static function deprecate_quiz_sensei_single_main_content_hook(){ |
||
1072 | |||
1073 | sensei_do_deprecated_action('sensei_single_main_content', '1.9.0', 'sensei_single_quiz_content_inside_before or sensei_single_quiz_content_inside_after'); |
||
1074 | |||
1075 | } |
||
1076 | /* |
||
1077 | * Deprecate the sensei_quiz_single_title on the single-quiz template. |
||
1078 | * |
||
1079 | * @deprecated since 1.9.0 |
||
1080 | */ |
||
1081 | public static function deprecate_quiz_sensei_quiz_single_title_hook(){ |
||
1082 | |||
1083 | sensei_do_deprecated_action('sensei_quiz_single_title', '1.9.0', 'sensei_single_quiz_content_inside_before '); |
||
1084 | |||
1085 | } |
||
1086 | |||
1087 | /** |
||
1088 | * Filter the single title and add the Quiz to it. |
||
1089 | * |
||
1090 | * @param string $title |
||
1091 | * @param int $id title post id |
||
1092 | * @return string $quiz_title |
||
1093 | */ |
||
1094 | public static function single_quiz_title( $title, $post_id ){ |
||
1095 | |||
1096 | if( 'quiz' == get_post_type( $post_id ) ){ |
||
1097 | |||
1098 | $title_with_no_quizzes = $title; |
||
1099 | |||
1100 | // if the title has quiz, remove it: legacy titles have the word quiz stored. |
||
1101 | if( 1 < substr_count( strtoupper( $title_with_no_quizzes ), 'QUIZ' ) ){ |
||
1102 | |||
1103 | // remove all possible appearances of quiz |
||
1104 | $title_with_no_quizzes = str_replace( 'quiz', '', $title ); |
||
1105 | $title_with_no_quizzes = str_replace( 'Quiz', '', $title_with_no_quizzes ); |
||
1106 | $title_with_no_quizzes = str_replace( 'QUIZ', '', $title_with_no_quizzes ); |
||
1107 | |||
1108 | } |
||
1109 | |||
1110 | $title = $title_with_no_quizzes . ' ' . __( 'Quiz', 'woothemes-sensei' ); |
||
1111 | } |
||
1112 | |||
1113 | /** |
||
1114 | * hook document in class-woothemes-sensei-message.php |
||
1115 | */ |
||
1116 | return apply_filters( 'sensei_single_title', $title, get_post_type( ) ); |
||
1117 | |||
1118 | } |
||
1119 | |||
1120 | /** |
||
1121 | * Initialize the quiz question loop on the single quiz template |
||
1122 | * |
||
1123 | * The function will create a global quiz loop varialbe. |
||
1124 | * |
||
1125 | * @since 1.9.0 |
||
1126 | * |
||
1127 | */ |
||
1128 | public static function start_quiz_questions_loop(){ |
||
1129 | |||
1130 | global $sensei_question_loop; |
||
1131 | |||
1132 | //intialize the questions loop object |
||
1133 | $sensei_question_loop['current'] = -1; |
||
1134 | $sensei_question_loop['total'] = 0; |
||
1135 | $sensei_question_loop['questions'] = array(); |
||
1136 | |||
1137 | |||
1138 | $questions = Sensei()->lesson->lesson_quiz_questions( get_the_ID() ); |
||
1139 | |||
1140 | if( count( $questions ) > 0 ){ |
||
1141 | |||
1142 | $sensei_question_loop['total'] = count( $questions ); |
||
1143 | $sensei_question_loop['questions'] = $questions; |
||
1144 | $sensei_question_loop['quiz_id'] = get_the_ID(); |
||
1145 | |||
1146 | } |
||
1147 | |||
1148 | }// static function |
||
1149 | |||
1150 | /** |
||
1151 | * Initialize the quiz question loop on the single quiz template |
||
1152 | * |
||
1153 | * The function will create a global quiz loop varialbe. |
||
1154 | * |
||
1155 | * @since 1.9.0 |
||
1156 | * |
||
1157 | */ |
||
1158 | public static function stop_quiz_questions_loop(){ |
||
1159 | |||
1160 | $sensei_question_loop['total'] = 0; |
||
1161 | $sensei_question_loop['questions'] = array(); |
||
1162 | $sensei_question_loop['quiz_id'] = ''; |
||
1163 | |||
1164 | } |
||
1165 | |||
1166 | /** |
||
1167 | * Output the title for the single quiz page |
||
1168 | * |
||
1169 | * @since 1.9.0 |
||
1170 | */ |
||
1171 | public static function the_title(){ |
||
1172 | ?> |
||
1173 | <header> |
||
1174 | |||
1175 | <h1> |
||
1176 | |||
1177 | <?php |
||
1178 | /** |
||
1179 | * Filter documented in class-sensei-messages.php the_title |
||
1180 | */ |
||
1181 | echo apply_filters( 'sensei_single_title', get_the_title( get_post() ), get_post_type( get_the_ID() ) ); |
||
1182 | ?> |
||
1183 | |||
1184 | </h1> |
||
1185 | |||
1186 | </header> |
||
1187 | |||
1188 | <?php |
||
1189 | }//the_title |
||
1190 | |||
1191 | /** |
||
1192 | * Output the sensei quiz status message. |
||
1193 | * |
||
1194 | * @param $quiz_id |
||
1195 | */ |
||
1196 | public static function the_user_status_message( $quiz_id ){ |
||
1197 | |||
1198 | $lesson_id = Sensei()->quiz->get_lesson_id( $quiz_id ); |
||
1199 | $status = Sensei_Utils::sensei_user_quiz_status_message( $lesson_id , get_current_user_id() ); |
||
1200 | echo '<div class="sensei-message ' . $status['box_class'] . '">' . $status['message'] . '</div>'; |
||
1201 | |||
1202 | } |
||
1203 | |||
1204 | /** |
||
1205 | * This functions runs the old sensei_quiz_action_buttons action |
||
1206 | * for backwards compatiblity sake. |
||
1207 | * |
||
1208 | * @since 1.9.0 |
||
1209 | * @deprecated |
||
1210 | */ |
||
1211 | public static function deprecate_sensei_quiz_action_buttons_hook(){ |
||
1212 | |||
1213 | sensei_do_deprecated_action( 'sensei_quiz_action_buttons', '1.9.0', 'sensei_single_quiz_questions_after'); |
||
1214 | |||
1215 | } |
||
1216 | |||
1217 | /** |
||
1218 | * The quiz action buttons needed to ouput quiz |
||
1219 | * action such as reset complete and save. |
||
1220 | * |
||
1221 | * @since 1.3.0 |
||
1222 | */ |
||
1223 | public static function action_buttons() { |
||
1224 | |||
1225 | global $post, $current_user; |
||
1226 | |||
1227 | $lesson_id = (int) get_post_meta( $post->ID, '_quiz_lesson', true ); |
||
1228 | $lesson_course_id = (int) get_post_meta( $lesson_id, '_lesson_course', true ); |
||
1229 | $lesson_prerequisite = (int) get_post_meta( $lesson_id, '_lesson_prerequisite', true ); |
||
1230 | $show_actions = true; |
||
1231 | $user_lesson_status = Sensei_Utils::user_lesson_status( $lesson_id, $current_user->ID ); |
||
1232 | |||
1233 | //setup quiz grade |
||
1234 | $user_quiz_grade = ''; |
||
1235 | if( ! empty( $user_lesson_status ) ){ |
||
1236 | $user_quiz_grade = get_comment_meta( $user_lesson_status->comment_ID, 'grade', true ); |
||
1237 | } |
||
1238 | |||
1239 | |||
1240 | if( intval( $lesson_prerequisite ) > 0 ) { |
||
1241 | |||
1242 | // If the user hasn't completed the prereq then hide the current actions |
||
1243 | $show_actions = Sensei_Utils::user_completed_lesson( $lesson_prerequisite, $current_user->ID ); |
||
1244 | |||
1245 | } |
||
1246 | if ( $show_actions && is_user_logged_in() && Sensei_Utils::user_started_course( $lesson_course_id, $current_user->ID ) ) { |
||
1247 | |||
1248 | // Get Reset Settings |
||
1249 | $reset_quiz_allowed = get_post_meta( $post->ID, '_enable_quiz_reset', true ); ?> |
||
1250 | |||
1251 | <!-- Action Nonce's --> |
||
1252 | <input type="hidden" name="woothemes_sensei_complete_quiz_nonce" id="woothemes_sensei_complete_quiz_nonce" |
||
1253 | value="<?php echo esc_attr( wp_create_nonce( 'woothemes_sensei_complete_quiz_nonce' ) ); ?>" /> |
||
1254 | <input type="hidden" name="woothemes_sensei_reset_quiz_nonce" id="woothemes_sensei_reset_quiz_nonce" |
||
1255 | value="<?php echo esc_attr( wp_create_nonce( 'woothemes_sensei_reset_quiz_nonce' ) ); ?>" /> |
||
1256 | <input type="hidden" name="woothemes_sensei_save_quiz_nonce" id="woothemes_sensei_save_quiz_nonce" |
||
1257 | value="<?php echo esc_attr( wp_create_nonce( 'woothemes_sensei_save_quiz_nonce' ) ); ?>" /> |
||
1258 | <!--#end Action Nonce's --> |
||
1259 | |||
1260 | <?php if ( '' == $user_quiz_grade) { ?> |
||
1261 | |||
1262 | <span><input type="submit" name="quiz_complete" class="quiz-submit complete" value="<?php _e( 'Complete Quiz', 'woothemes-sensei' ); ?>"/></span> |
||
1263 | |||
1264 | <span><input type="submit" name="quiz_save" class="quiz-submit save" value="<?php _e( 'Save Quiz', 'woothemes-sensei' ); ?>"/></span> |
||
1265 | |||
1266 | <?php } // End If Statement ?> |
||
1267 | |||
1268 | <?php if ( isset( $reset_quiz_allowed ) && $reset_quiz_allowed ) { ?> |
||
1269 | |||
1270 | <span><input type="submit" name="quiz_reset" class="quiz-submit reset" value="<?php _e( 'Reset Quiz', 'woothemes-sensei' ); ?>"/></span> |
||
1271 | |||
1272 | <?php } ?> |
||
1273 | |||
1274 | <?php } |
||
1275 | |||
1276 | } // End sensei_quiz_action_buttons() |
||
1277 | |||
1278 | /** |
||
1279 | * Fetch the quiz grade |
||
1280 | * |
||
1281 | * @since 1.9.0 |
||
1282 | * |
||
1283 | * @param int $lesson_id |
||
1284 | * @param int $user_id |
||
1285 | * |
||
1286 | * @return double $user_quiz_grade |
||
1287 | */ |
||
1288 | public static function get_user_quiz_grade( $lesson_id, $user_id ){ |
||
1289 | |||
1290 | // get the quiz grade |
||
1291 | $user_lesson_status = Sensei_Utils::user_lesson_status( $lesson_id, $user_id ); |
||
1292 | $user_quiz_grade = 0; |
||
1293 | if( isset( $user_lesson_status->comment_ID ) ) { |
||
1294 | $user_quiz_grade = get_comment_meta( $user_lesson_status->comment_ID, 'grade', true ); |
||
1295 | } |
||
1296 | |||
1297 | return (double) $user_quiz_grade; |
||
1298 | |||
1299 | } |
||
1300 | |||
1301 | /** |
||
1302 | * Check the quiz reset property for a given lesson's quiz. |
||
1303 | * |
||
1304 | * The data is stored on the quiz but going forward the quiz post |
||
1305 | * type will be retired, hence the lesson_id is a require parameter. |
||
1306 | * |
||
1307 | * @since 1.9.0 |
||
1308 | * |
||
1309 | * @param int $lesson_id |
||
1310 | * @return bool |
||
1311 | */ |
||
1312 | public static function is_reset_allowed( $lesson_id ){ |
||
1313 | |||
1314 | $quiz_id = Sensei()->lesson->lesson_quizzes( $lesson_id ); |
||
1315 | |||
1316 | $reset_allowed = get_post_meta( $quiz_id, '_enable_quiz_reset', true ); |
||
1317 | //backwards compatibility |
||
1318 | if( 'on' == $reset_allowed ) { |
||
1319 | $reset_allowed = 1; |
||
1320 | } |
||
1321 | |||
1322 | return (bool) $reset_allowed; |
||
1323 | |||
1324 | } |
||
1325 | |||
1326 | } // End Class WooThemes_Sensei_Quiz |
||
1327 | |||
1328 | |||
1329 | |||
1330 | /** |
||
1331 | * Class WooThemes_Sensei_Quiz |
||
1332 | * @ignore only for backward compatibility |
||
1333 | * @since 1.9.0 |
||
1334 | */ |
||
1335 | class WooThemes_Sensei_Quiz extends Sensei_Quiz{} |
||
1336 |
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.