PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.10
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.10
4.4.10 4.4.9.1 4.4.9 4.4.8 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 All 142 releases
learnpress / inc / question / class-lp-question.php

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

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