PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.1.7
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.1.7
4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.9.1 4.3.9 4.3.8 4.3.7 4.1.6.9 4.1.6.9.1 4.1.6.9.2 4.1.6.9.3 4.1.6.9.4 4.1.7 4.1.7.1 4.1.7.2 4.1.7.3 4.1.7.3.1 4.1.7.3.2 4.2.0 4.2.1 All 138 releases
learnpress / inc / question / class-lp-question.php

class-lp-question.php in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses 4.1.7, at inc/question/class-lp-question.php

1,107 lines 27.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Base class for types of question
4 *
5 * @author ThimPress
6 * @package LearnPress/Classes
7 * @version 3.0.0
8 */
9
10 /**
11 * Prevent loading this file directly
12 */
13 defined( 'ABSPATH' ) || exit();
14
15 if ( ! class_exists( 'LP_Question' ) ) {
16
17 /**
18 * Class LP_Question
19 *
20 * @extend LP_Course_Item
21 */
22 class LP_Question extends LP_Course_Item {
23
24 /**
25 * @var null
26 */
27 protected $_options = null;
28
29 /**
30 * @var string
31 */
32 protected $_content = '';
33
34 /**
35 * Type of this question.
36 *
37 * @var string
38 */
39 protected $_question_type = 'true_or_false';
40
41 /**
42 * @var string
43 */
44 protected $_item_type = LP_QUESTION_CPT;
45
46 /**
47 * @var array
48 */
49 protected static $_types = array();
50
51 /**
52 * support answer options
53 *
54 * @var bool
55 */
56 protected $_answer_options = true;
57
58 /**
59 * @var int
60 */
61 // protected static $_loaded = 0;
62
63 /**
64 * @var string
65 */
66 public $object_type = 'question';
67
68 /**
69 * @var array
70 */
71 protected $_data = array(
72 'mark' => 0,
73 'answer_options' => array(),
74 'show_correct_answers' => '',
75 'disable_answers' => '',
76 'answered' => '',
77 'explanation' => '',
78 'hint' => '',
79 );
80
81 /**
82 * Construct
83 *
84 * @param mixed
85 * @param array
86 *
87 * @throws Exception
88 */
89 public function __construct( $the_question = null, $args = null ) {
90
91 parent::__construct( $the_question, $args );
92
93 $this->_curd = new LP_Question_CURD();
94 if ( is_numeric( $the_question ) && $the_question > 0 ) {
95 $this->set_id( $the_question );
96 } elseif ( $the_question instanceof self ) {
97 $this->set_id( absint( $the_question->get_id() ) );
98 } elseif ( ! empty( $the_question->ID ) ) {
99 $this->set_id( absint( $the_question->ID ) );
100 }
101
102 if ( $this->_answer_options ) {
103 $this->add_support( 'answer_options' );
104 $this->add_support( 'auto_calculate_point' );
105
106 if ( $this->get_type() !== 'true_or_false' ) {
107 $this->add_support( 'add_answer_option' );
108 }
109 }
110
111 $this->set_data( 'answered', false );
112
113 if ( $this->get_id() > 0 ) {
114 $this->load();
115 }
116
117 $this->_options = $args;
118 $this->_init();
119 // self::$_loaded ++;
120 }
121
122 public function add_support( $feature, $type = 'yes' ) {
123 $feature = $this->_sanitize_feature_key( $feature );
124 LP_Global::add_object_feature( $this->object_type . '.' . $this->get_type(), $feature, $type );
125 }
126
127 /*public function is_support( $feature, $type = '' ) {
128 $feature = $this->_sanitize_feature_key( $feature );
129
130 return LP_Global::object_is_support_feature( $this->object_type . '.' . $this->get_type(), $feature, $type );
131 }*/
132
133 public function get_supports() {
134 if ( empty( LP_Global::$object_support_features ) ) {
135 return false;
136 }
137
138 return LP_Global::get_object_supports( $this->object_type . '.' . $this->get_type() );
139 }
140
141 public static function get_type_support_answer_options() {
142
143 }
144
145 /**
146 * Load data for question
147 *
148 * @throws Exception
149 */
150 public function load() {
151 $this->_curd->load( $this );
152 }
153
154 /**
155 * Get default question meta.
156 *
157 * @return mixed
158 */
159 public static function get_default_meta() {
160 $meta = array(
161 'mark' => 1,
162 'explanation' => null,
163 'hint' => null,
164 );
165
166 return apply_filters( 'learn-press/question/default-meta', $meta );
167 }
168
169 /**
170 * Save question data.
171 *
172 * @since 3.0.0
173 *
174 * @return int|object|WP_Error
175 */
176 public function save() {
177
178 if ( $this->get_id() ) {
179 $return = $this->_curd->update( $this );
180 } else {
181 $return = $this->_curd->create( $this );
182 }
183
184 return $return;
185 }
186
187 /**
188 * @return array|mixed
189 */
190 public function get_mark() {
191 return $this->get_data( 'mark', 1 );
192 }
193
194 /**
195 * @param $mark
196 */
197 public function set_mark( $mark ) {
198 $this->_set_data( 'mark', abs( $mark ) );
199 }
200
201 /**
202 * Do something before get data.
203 * Some data now is not auto loading when object is created
204 * therefore, we will load it here.
205 *
206 * @param string $name
207 * @param string $default
208 *
209 * @return array|mixed
210 */
211 public function get_data( $name = '', $default = '' ) {
212 switch ( $name ) {
213 case 'answer_options':
214 $answer_options = parent::get_data( $name, $default );
215
216 if ( ! $answer_options ) {
217 $answer_options = $this->_curd->load_answer_options( $this->get_id() );
218 $this->set_data( $name, $answer_options );
219 }
220
221 break;
222 }
223
224 return parent::get_data( $name, $default );
225 }
226
227 /**
228 * @param string $yes_or_no
229 *
230 * @return array|mixed
231 */
232 public function show_correct_answers( $yes_or_no = '' ) {
233 if ( in_array( $yes_or_no, array( 'yes', 'no' ) ) ) {
234 $this->_set_data( 'show_correct_answers', $yes_or_no );
235 }
236
237 return $this->get_data( 'show_correct_answers' );
238 }
239
240 /**
241 * @param string $yes_or_no
242 *
243 * @return array|mixed
244 */
245 public function disable_answers( $yes_or_no = '' ) {
246 if ( in_array( $yes_or_no, array( 'yes', 'no' ) ) ) {
247 $this->_set_data( 'disable_answers', $yes_or_no );
248 }
249
250 return $this->get_data( 'disable_answers' );
251 }
252
253 /**
254 * Set answer for this question.
255 *
256 * @param mixed $answered
257 */
258 public function set_answered( $answered ) {
259 $this->set_data( 'answered', $answered );
260 }
261
262 /**
263 * Get answer for this question if set.
264 *
265 * @return array|mixed
266 */
267 public function get_answered() {
268 return $this->get_data( 'answered' );
269 }
270
271 /**
272 * Get answer options of the question
273 *
274 * @param array $args - Optional.
275 *
276 * @return mixed
277 */
278 public function get_answer_options( $args = array() ) {
279
280 $args = wp_parse_args(
281 $args,
282 array(
283 'exclude' => '',
284 'map' => '',
285 'answer' => '',
286 )
287 );
288
289 if ( $args['exclude'] && is_string( $args['exclude'] ) ) {
290 $exclude = array_map( 'trim', explode( ',', $args['exclude'] ) );
291 } else {
292 $exclude = $args['exclude'];
293 }
294
295 $map = $args['map'];
296
297 $options = $this->get_data( 'answer_options' );
298
299 // Remove key if it in $exclude.
300 if ( $options && ( $exclude || $map ) ) {
301 $exclude = array_flip( $exclude );
302
303 foreach ( $options as $k => $option ) {
304 $option['title'] = do_shortcode( $option['title'] );
305
306 foreach ( $map as $k_map => $v_map ) {
307 if ( array_key_exists( $k_map, $option ) ) {
308 $option[ $v_map ] = $option[ $k_map ];
309 $exclude[ $k_map ] = 1;
310 }
311 }
312 $options[ $k ] = array_diff_key( $option, $exclude );
313 }
314 }
315
316 return apply_filters( 'learn-press/question/answer-options', $options, $this->get_id() );
317 }
318
319 /**
320 * @param string $explanation
321 */
322 public function set_explanation( $explanation = '' ) {
323 $this->_set_data( 'explanation', $explanation );
324 }
325
326 /**
327 * @return mixed
328 */
329 public function get_explanation() {
330 return apply_filters( 'learn-press/question/explanation', do_shortcode( $this->get_data( 'explanation' ) ), $this->get_id() );
331 }
332
333 /**
334 * @param string $hint
335 */
336 public function set_hint( $hint = '' ) {
337 $this->_set_data( 'hint', $hint );
338 }
339
340 /**
341 * @return mixed
342 */
343 public function get_hint() {
344 return apply_filters( 'learn-press/question/hint', do_shortcode( $this->get_data( 'hint' ) ), $this->get_id() );
345 }
346
347 /**
348 * Get all type of questions
349 *
350 * @return mixed
351 */
352 public static function get_types() {
353 $types = apply_filters(
354 'learn-press/question-types',
355 array(
356 'true_or_false' => esc_html__( 'True Or False', 'learnpress' ),
357 'multi_choice' => esc_html__( 'Multi Choice', 'learnpress' ),
358 'single_choice' => esc_html__( 'Single Choice', 'learnpress' ),
359 'fill_in_blanks' => esc_html__( 'Fill In Blanks', 'learnpress' ),
360 )
361 );
362
363 return apply_filters( 'learn_press_question_types', $types );
364 }
365
366 /**
367 * Store question and it's related data into database.
368 *
369 * @return mixed
370 */
371 public function store() {
372 global $wpdb;
373 $id = absint( $this->get_id() );
374 $is_update = $id > 0;
375 $post_data = array(
376 'post_title' => $this->get_data( 'title' ),
377 'post_type' => LP_QUESTION_CPT,
378 'ID' => $id,
379 );
380 if ( $is_update ) {
381 $updated = wp_update_post( $post_data, true );
382 } else {
383 $updated = wp_insert_post( $post_data, true );
384 }
385
386 if ( ! is_numeric( $updated ) ) {
387 return false;
388 }
389
390 // Does this question support answer options?
391 /*if ( ! $this->is_support( 'answer_options' ) ) {
392 return $updated;
393 }*/
394
395 $this->empty_answers();
396 if ( $answer_options = $this->get_data( 'answer_options' ) ) {
397 $question_order = 1;
398 $query = "INSERT INTO {$wpdb->prefix}learnpress_question_answers(`question_id`, `order`) VALUES";
399 foreach ( $answer_options as $answer_option ) {
400 if ( empty( $answer_option['title'] ) ) {
401 if ( apply_filters( 'learn-press/question/ignore-insert-empty-answer-option', true, $answer_option, $id ) ) {
402 continue;
403 }
404 }
405 $qry = $query . $wpdb->prepare( '(%d, %d)', $id, $question_order ++ );
406 do_action( 'learn-press/question/insert-answer-option', $id, $answer_option );
407 if ( $wpdb->query( $qry ) ) {
408 $inserted_id = $wpdb->insert_id;
409 learn_press_update_question_answer_meta( $inserted_id, 'text', $answer_option['text'] );
410 learn_press_update_question_answer_meta( $inserted_id, 'value', $answer_option['value'] );
411 if ( ! empty( $answer_option['is_true'] ) && ! learn_press_is_negative_value( $answer_option['is_true'] ) ) {
412 learn_press_update_question_answer_meta( $inserted_id, 'checked', 'yes' );
413 }
414 do_action( 'learn-press/question/inserted-answer-option', $inserted_id, $id, $answer_option );
415 }
416 }
417 }
418
419 return $updated;
420 }
421
422 /**
423 * Remove all answers to prepare for inserting new
424 */
425 public function empty_answers() {
426 global $wpdb;
427 $id = absint( $this->get_id() );
428 $table_meta = $wpdb->learnpress_question_answermeta;
429 $table_main = $wpdb->learnpress_question_answers;
430 $query = $wpdb->prepare(
431 "
432 DELETE FROM t1, t2
433 USING {$table_main} AS t1 INNER JOIN {$table_meta} AS t2 ON t1.question_answer_id = t2.learnpress_question_answer_id
434 WHERE t1.question_id = %d
435 ",
436 $id
437 );
438
439 // deprecated
440 do_action( 'learn_press_before_delete_question_answers', $id );
441
442 do_action( 'learn-press/question/delete-answers', $id );
443 if ( $wpdb->query( $query ) ) {
444 do_action( 'learn-press/question/deleted-answers', $id );
445 }
446 // deprecated
447 do_action( 'learn_press_delete_question_answers', $id );
448 }
449
450 /**
451 * @param $meta
452 * @param $field
453 * @param $is_saved
454 *
455 * @return string
456 */
457 public function _filter_meta_box_meta( $meta, $field, $is_saved ) {
458 if ( preg_match( '~\[question-content\]~', $field['id'] ) && $field['context'] == 'quiz-list-questions' ) {
459 $post = get_post( $this->get_id() );
460 $meta = $post->post_content;
461 }
462
463 return $meta;
464 }
465
466 /**
467 * Set new type of question.
468 * Update _lp_type meta to new type.
469 *
470 * @param string $type
471 *
472 * @return bool
473 */
474 public function set_type( $type = '' ) {
475
476 if ( ! $type ) {
477 return false;
478 }
479
480 if ( ! learn_press_is_support_question_type( $type ) ) {
481 return false;
482 }
483
484 // Change to new type and update meta value
485 $this->_question_type = $type;
486 update_post_meta( $this->get_id(), '_lp_type', $type );
487
488 return true;
489 }
490
491 /**
492 * Update ordering of question answers
493 *
494 * @param array $orders List of answers
495 */
496 public function update_answer_orders( $orders ) {
497 global $wpdb;
498 $query = $wpdb->prepare(
499 "
500 SELECT qa.question_answer_id, qam2.meta_value as `name`, qam.meta_value as `value`
501 FROM {$wpdb->learnpress_question_answers} qa
502 INNER JOIN {$wpdb->learnpress_question_answermeta} qam ON qa.question_answer_id = qam.learnpress_question_answer_id AND qam.meta_key = %s
503 INNER JOIN {$wpdb->learnpress_question_answermeta} qam2 ON qa.question_answer_id = qam2.learnpress_question_answer_id AND qam2.meta_key = %s
504 WHERE qa.question_id = %d
505 ORDER BY `order`
506 ",
507 'value',
508 'text',
509 $this->get_id()
510 );
511 if ( $answers = $wpdb->get_results( $query ) ) {
512 $query = "
513 UPDATE {$wpdb->learnpress_question_answers}
514 SET `order` = CASE
515 ";
516 for ( $order = 0, $n = sizeof( $orders ); $order < $n; $order ++ ) {
517 $found_answer = false;
518 foreach ( $answers as $answer ) {
519 if ( $answer->value == $orders[ $order ]['value'] && $answer->name == $orders[ $order ]['text'] ) {
520 $found_answer = $answer;
521 break;
522 }
523 }
524 if ( $found_answer === false ) {
525 continue;
526 }
527 $query .= $wpdb->prepare( 'WHEN question_answer_id = %d THEN %d', $found_answer->question_answer_id, $order + 1 ) . "\n";
528 }
529 $query .= sprintf( 'ELSE `order` END WHERE question_id = %d', $this->get_id() );
530 $wpdb->query( $query );
531 }
532 }
533
534 /**
535 * Return type of question.
536 *
537 * @return string
538 */
539 public function get_type() {
540 return $this->_question_type;
541 }
542
543 /**
544 * Return type of question in 'readable text'.
545 *
546 * @return string
547 */
548 public function get_type_label() {
549 return learn_press_question_types()[ $this->get_type() ];
550 }
551
552 protected function _init() {
553 add_filter( 'learn_press_question_answers', array( $this, '_get_default_answers' ), 10, 2 );
554 }
555
556
557 /**
558 *
559 * @param mixed $answers
560 * @param LP_Question $q
561 *
562 * @return array|bool
563 */
564 public function _get_default_answers( $answers = false, $q = null ) {
565 if ( ! $answers && ( $q && $q->get_id() == $this->get_id() ) ) {
566 $answers = $this->get_default_answers();
567 }
568
569 return $answers;
570 }
571
572 /**
573 * Get default question answer.
574 *
575 * @return array
576 */
577 public static function get_default_answer() {
578 $answer = array(
579 'question_answer_id' => - 1,
580 'title' => '',
581 'is_true' => '',
582 'value' => learn_press_random_value(),
583 );
584
585 return $answer;
586 }
587
588 /**
589 * Get default question list answers.
590 *
591 * @return array|bool
592 */
593 public function get_default_answers() {
594 $answers = array(
595 array(
596 'question_answer_id' => - 1,
597 'is_true' => 'yes',
598 'value' => learn_press_random_value(),
599 'title' => esc_html__( 'First option', 'learnpress' ),
600 ),
601 array(
602 'question_answer_id' => - 2,
603 'is_true' => 'no',
604 'value' => learn_press_random_value(),
605 'title' => esc_html__( 'Second option', 'learnpress' ),
606 ),
607 array(
608 'question_answer_id' => - 3,
609 'is_true' => 'no',
610 'value' => learn_press_random_value(),
611 'title' => esc_html__( 'Third option', 'learnpress' ),
612 ),
613 );
614
615 return $answers;
616 }
617
618
619 /**
620 * @param string $field
621 * @param string $exclude
622 *
623 * @return LP_Question_Answers
624 */
625 public function get_answers( $field = null, $exclude = null ) {
626 $answers = LP_Object_Cache::get( 'answer-options-' . $this->get_id(), 'learn-press/questions' );
627
628 if ( false === $answers ) {
629 $answers = $this->_curd->load_answer_options( $this->get_id() );
630
631 if ( ! $answers ) {
632 $answers = $this->get_default_answers();
633 }
634
635 LP_Object_Cache::set( 'answer-options-' . $this->get_id(), $answers, 'learn-press/questions' );
636 };
637
638 if ( $answers ) {
639 $answers = new LP_Question_Answers( $this, $answers );
640 }
641
642 // @deprecated
643 $answers = apply_filters( 'learn_press_question_answers', $answers, $this );
644
645 return apply_filters( 'learn-press/questions/answers', $answers, $this->get_id() );
646 }
647
648 /**
649 * Create default answers.
650 *
651 * @since 3.3.0
652 */
653 public function create_default_answers() {
654 global $wpdb;
655
656 $answers = $this->get_default_answers();
657
658 foreach ( $answers as $index => $answer ) {
659 $answer = array(
660 'question_id' => $this->get_id(),
661 'title' => $answer['title'],
662 'value' => isset( $answer['value'] ) ? $answer['value'] : '',
663 'is_true' => ( $answer['is_true'] == 'yes' ) ? $answer['is_true'] : '',
664 'order' => $index + 1,
665 );
666
667 $wpdb->insert(
668 $wpdb->learnpress_question_answers,
669 $answer,
670 array( '%d', '%s', '%s', '%s', '%d' )
671 );
672
673 $question_answer_id = $wpdb->insert_id;
674
675 if ( $question_answer_id ) {
676 $answer['question_answer_id'] = $question_answer_id;
677 }
678
679 $answers[ $index ] = $answer;
680 }
681
682 $this->set_data( 'answer_options', $answers );
683 }
684
685 public function setup_data( $quiz_id, $course_id = 0, $user_id = 0 ) {
686
687 $quiz = learn_press_get_quiz( $quiz_id );
688 $course = learn_press_get_course( $course_id );
689
690 if ( $user_id ) {
691 $user = learn_press_get_user( $user_id );
692 } else {
693 $user = learn_press_get_current_user();
694 }
695
696 $show_correct = false;
697
698 if ( $user && $quiz && $course ) {
699 $user_quiz = $user->get_quiz_data( $quiz->get_id(), $course->get_id() );
700 if ( $user_quiz ) {
701 $has_checked = $user->has_checked_answer( $this->get_id(), $quiz->get_id(), $course->get_id() );
702 $show_correct = $user_quiz->is_completed() && ( $has_checked || $quiz->get_show_result() ) ? 'yes' : false;
703 $answered = $user_quiz->get_question_answer( $this->get_id() );
704 $this->set_answered( $answered );
705 }
706 }
707
708 $this->show_correct_answers( $show_correct );
709 }
710
711 /**
712 * Get question name.
713 *
714 * @return string
715 */
716 public function get_name() {
717 return isset( $this->_options['name'] ) ? $this->_options['name'] : ucfirst(
718 preg_replace_callback(
719 '!_([a-z])!',
720 array(
721 $this,
722 'sanitize_name_callback',
723 ),
724 $this->get_type()
725 )
726 );
727 }
728
729 /**
730 * @param $matches
731 *
732 * @return string
733 */
734 public function sanitize_name_callback( $matches ) {
735 return strtoupper( $matches[1] );
736 }
737
738 /**
739 * Sets the value for a variable of this class
740 *
741 * @param $key string The name of a variable of this class
742 * @param $value mixed The value to set
743 *
744 * @return void
745 */
746 public function set( $key, $value ) {
747 $this->$key = $value;
748 }
749
750 /**
751 * Gets the value of a variable of this class with multiple level of an object or array
752 * example: $obj->get('a.b') -> like this :
753 * - $obj->a->b
754 * - or $obj->a['b']
755 *
756 * @param null $key string Single or multiple level such as a.b.c
757 * @param null $default mixed Return a default value if the key does not exists or is empty
758 * @param null $func string The function to apply the result before return
759 *
760 * @return mixed|null
761 */
762 public function get( $key = null, $default = null, $func = null ) {
763 $val = $this->_get( $this, $key, $default );
764
765 return is_callable( $func ) ? call_user_func_array( $func, array( $val ) ) : $val;
766 }
767
768
769 /**
770 * Magic function to get question data.
771 *
772 * @param $prop
773 * @param $key
774 * @param null $default
775 * @param null $type
776 *
777 * @return mixed|null
778 */
779 protected function _get( $prop, $key, $default = null, $type = null ) {
780 $return = $default;
781
782 if ( $key === false || $key == null ) {
783 return $return;
784 }
785 $deep = explode( '.', $key );
786
787 if ( is_array( $prop ) ) {
788 if ( isset( $prop[ $deep[0] ] ) ) {
789 $return = $prop[ $deep[0] ];
790 if ( count( $deep ) > 1 ) {
791 unset( $deep[0] );
792 $return = $this->_get( $return, implode( '.', $deep ), $default, $type );
793 }
794 }
795 } elseif ( is_object( $prop ) ) {
796 if ( isset( $prop->{$deep[0]} ) ) {
797 $return = $prop->{$deep[0]};
798 if ( count( $deep ) > 1 ) {
799 unset( $deep[0] );
800 $return = $this->_get( $return, implode( '.', $deep ), $default, $type );
801 }
802 }
803 }
804
805 if ( $type == 'object' ) {
806 settype( $return, 'object' );
807 } elseif ( $type == 'array' ) {
808 settype( $return, 'array' );
809 }
810
811 return $return;
812 }
813
814 /**
815 * Find value in answer's option and compare with value answered by user.
816 *
817 * @param LP_Question_Answer_Option $answer
818 * @param mixed $answered
819 *
820 * @return bool
821 */
822 public function is_selected_option( $answer, $answered = false ) {
823
824 if ( is_array( $answered ) ) {
825 $is_selected = in_array( $answer['value'], $answered );
826 } else {
827 $is_selected = ( $answer['value'] . '' === $answered . '' );
828 }
829
830 return apply_filters( 'learn-press/question/is-selected-option', $is_selected, $answer, $answered, $this->get_id() );
831 }
832
833 /**
834 * Save user question answer.
835 *
836 * @param $answer
837 * @param $quiz_id
838 * @param null $user_id
839 */
840 public function save_user_answer( $answer, $quiz_id, $user_id = null ) {
841 if ( $user_id ) {
842 $user = LP_User_Factory::get_user( $user_id );
843 } else {
844 $user = learn_press_get_current_user();
845 }
846
847 $progress = $user->get_quiz_progress( $quiz_id );
848
849 if ( $progress ) {
850 if ( ! isset( $progress->question_answers ) ) {
851 $question_answers = array();
852 } else {
853 $question_answers = $progress->question_answers;
854 }
855 $question_answers[ $this->get_id() ] = $answer;
856
857 $question_answers = apply_filters( 'learn_press_update_user_question_answers', $question_answers, $progress->history_id, $user_id, $this, $quiz_id );
858
859 // learn_press_update_user_quiz_meta( $progress->history_id, 'question_answers', $question_answers );
860 }
861 }
862
863 /**
864 * Allow check question answer, default disable for True or False and Single choice, override by Multiple choice question.
865 *
866 * @return bool
867 */
868 public function can_check_answer() {
869 return false;
870 }
871
872 /**
873 * Check user answer, override by question type class.
874 *
875 * @param null $args | question answered
876 *
877 * @return array
878 */
879 public function check( $args = null ) {
880 $return = array(
881 'correct' => false,
882 'mark' => 0,
883 );
884
885 return $return;
886 }
887
888 /**
889 * Get answer at position
890 *
891 * @since 3.0.0
892 *
893 * @param int $at
894 *
895 * @return LP_Question_Answer_Option|mixed
896 */
897 public function get_answer_at( $at ) {
898 return $this->get_answers()->get_answer_at( $at );
899 }
900
901 /**
902 * Get user answered data.
903 *
904 * @param $args
905 *
906 * @return null
907 */
908 public function get_user_answered( $args ) {
909 $args = wp_parse_args(
910 $args,
911 array(
912 'history_id' => 0,
913 'quiz_id' => 0,
914 'course_id' => 0,
915 'force' => false,
916 )
917 );
918 $answered = null;
919 if ( $args['history_id'] ) {
920 $user_meta = learn_press_get_user_item_meta( $args['history_id'], 'question_answers', true );
921 if ( $user_meta && array_key_exists( $this->get_id(), $user_meta ) ) {
922 $answered = $user_meta[ $this->get_id() ];
923 }
924 } elseif ( $args['quiz_id'] && $args['course_id'] ) {
925 $user = learn_press_get_current_user();
926 $history = $user->get_quiz_results( $args['quiz_id'], $args['course_id'] );
927
928 if ( $history ) {
929 $user_meta = learn_press_get_user_item_meta( $history->history_id, 'question_answers', true );
930
931 if ( $user_meta && array_key_exists( $this->get_id(), $user_meta ) ) {
932 $answered = $user_meta[ $this->get_id() ];
933 }
934 }
935 }
936
937 return $answered;
938 }
939
940 /**
941 * Get question.
942 *
943 * @param bool $the_question
944 * @param array $args
945 *
946 * @return LP_Question|bool
947 */
948 public static function get_question( $the_question = false, $args = array() ) {
949 // question object
950 $the_question = self::get_question_object( $the_question );
951 if ( ! $the_question ) {
952 return false;
953 }
954
955 if ( ! empty( $args['force'] ) ) {
956 $force = ! ! $args['force'];
957 unset( $args['force'] );
958 } else {
959 $force = false;
960 }
961
962 $key_args = wp_parse_args(
963 $args,
964 array(
965 'id' => $the_question->ID,
966 'type' => $the_question->post_type,
967 )
968 );
969
970 $key = LP_Helper::array_to_md5( $key_args );
971
972 if ( $force ) {
973 LP_Global::$questions[ $key ] = false;
974 }
975
976 if ( empty( LP_Global::$questions[ $key ] ) ) {
977 $class_name = self::get_question_class( $the_question, $args );
978 if ( is_string( $class_name ) && class_exists( $class_name ) ) {
979 $lesson = new $class_name( $the_question->ID, $args );
980 } elseif ( $class_name instanceof LP_Question ) {
981 $lesson = $class_name;
982 } else {
983 $lesson = new self( $the_question->ID, $args );
984 }
985 LP_Global::$questions[ $key ] = $lesson;
986 }
987
988 return LP_Global::$questions[ $key ];
989 }
990
991 /**
992 * Get the question class name.
993 *
994 * @param WP_Post $the_question
995 * @param array $args (default: array())
996 *
997 * @return string
998 */
999 private static function get_question_class( $the_question, $args = array() ) {
1000 $question_id = absint( $the_question->ID );
1001 if ( ! empty( $args['type'] ) ) {
1002 $question_type = $args['type'];
1003 } else {
1004 $question_type = get_post_meta( $question_id, '_lp_type', true );
1005 }
1006
1007 $class_name = self::get_class_name_from_question_type( array( $question_type ) );
1008
1009 // Filter class name so that the class can be overridden if extended.
1010 return apply_filters( 'learn-press/question/object-class', $class_name, $question_type, $question_id );
1011 }
1012
1013 /**
1014 * Get question class from question type.
1015 *
1016 * @param string $question_type
1017 *
1018 * @return string|false
1019 */
1020 public static function get_class_name_from_question_type( $question_type ) {
1021
1022 if ( is_array( $question_type ) ) {
1023 $question_type = reset( $question_type );
1024 }
1025
1026 return ! $question_type ? __CLASS__ : 'LP_Question_' . implode( '_', array_map( 'ucfirst', explode( '-', $question_type ) ) );
1027 }
1028
1029 /**
1030 * Get the question object.
1031 *
1032 * @since 3.0.0
1033 *
1034 * @param mixed $the_question
1035 *
1036 * @uses WP_Post
1037 * @return WP_Post|bool false on failure
1038 */
1039 private static function get_question_object( $the_question ) {
1040 if ( false === $the_question ) {
1041 $the_question = get_post_type() === LP_QUESTION_CPT ? $GLOBALS['post'] : false;
1042 } elseif ( is_numeric( $the_question ) ) {
1043 $the_question = get_post( $the_question );
1044 } elseif ( $the_question instanceof LP_Course_Item ) {
1045 $the_question = get_post( $the_question->get_id() );
1046 } elseif ( ! ( $the_question instanceof WP_Post ) ) {
1047 $the_question = false;
1048 }
1049
1050 return apply_filters( 'learn-press/question/post-object', $the_question );
1051 }
1052
1053 protected function _get_checked( $user_answer = null ) {
1054 $key = $user_answer ? md5( serialize( $user_answer ) ) : - 1;
1055
1056 return LP_Object_Cache::get( 'question-' . $this->get_id() . '/' . $key, 'learn-press/answer-checked' );
1057 }
1058
1059 protected function _set_checked( $checked, $user_answer ) {
1060 $key = $user_answer ? md5( serialize( $user_answer ) ) : - 1;
1061
1062 return LP_Object_Cache::set( 'question-' . $this->get_id() . '/' . $key, $checked, 'learn-press/answer-checked' );
1063 }
1064
1065 /**
1066 * Get default json data for editor settings
1067 *
1068 * @since 3.3.0
1069 *
1070 * @return array
1071 */
1072 public function get_editor_default_settings() {
1073 return apply_filters(
1074 'learn-press/question-editor-default-settings',
1075 array(
1076 'id' => $this->get_id(),
1077 'type' => $this->get_type(),
1078 'duration' => $this->get_duration(),
1079 )
1080 );
1081 }
1082
1083 /**
1084 * Get json data for editor settings.
1085 *
1086 * @since 3.3.0
1087 *
1088 * @return array
1089 */
1090 public function get_editor_settings() {
1091 $defaults = $this->get_editor_default_settings();
1092
1093 return apply_filters(
1094 'learn-press/question-editor-settings',
1095 array_merge(
1096 $defaults,
1097 array()
1098 ),
1099 $this->get_type(),
1100 $this->get_id(),
1101 $this
1102 );
1103 }
1104 }
1105
1106 }
1107