PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.8
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.8
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 4.1.7.3.1 4.1.7.3.2 4.2.0 All 139 releases
← All changes | inc/question/class-lp-question.php +1120 -1120 4.3.84.4.8 View file →
@@ -1,1120 +1,1120 @@
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 - /**
134 - * @deprecated 4.1.7.3
135 - */
136 - public function get_supports() {
137 - _deprecated_function( __FUNCTION__, '4.1.7.3' );
138 - if ( empty( LP_Global::$object_support_features ) ) {
139 - return false;
140 - }
141 -
142 - return LP_Global::get_object_supports( $this->object_type . '.' . $this->get_type() );
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 - $answer_options = $this->get_data( 'answer_options' );
397 - if ( $answer_options ) {
398 - $question_order = 1;
399 - $query = "INSERT INTO {$wpdb->prefix}learnpress_question_answers(`question_id`, `order`) VALUES";
400 - foreach ( $answer_options as $answer_option ) {
401 - if ( empty( $answer_option['title'] ) ) {
402 - if ( apply_filters( 'learn-press/question/ignore-insert-empty-answer-option', true, $answer_option, $id ) ) {
403 - continue;
404 - }
405 - }
406 - $qry = $query . $wpdb->prepare( '(%d, %d)', $id, $question_order ++ );
407 - do_action( 'learn-press/question/insert-answer-option', $id, $answer_option );
408 - if ( $wpdb->query( $qry ) ) {
409 - $inserted_id = $wpdb->insert_id;
410 - learn_press_update_question_answer_meta( $inserted_id, 'text', $answer_option['text'] );
411 - learn_press_update_question_answer_meta( $inserted_id, 'value', $answer_option['value'] );
412 - if ( ! empty( $answer_option['is_true'] ) && ! learn_press_is_negative_value( $answer_option['is_true'] ) ) {
413 - learn_press_update_question_answer_meta( $inserted_id, 'checked', 'yes' );
414 - }
415 - do_action( 'learn-press/question/inserted-answer-option', $inserted_id, $id, $answer_option );
416 - }
417 - }
418 - }
419 -
420 - return $updated;
421 - }
422 -
423 - /**
424 - * Remove all answers to prepare for inserting new
425 - */
426 - public function empty_answers() {
427 - global $wpdb;
428 - $id = absint( $this->get_id() );
429 - $table_meta = $wpdb->learnpress_question_answermeta;
430 - $table_main = $wpdb->learnpress_question_answers;
431 - $query = $wpdb->prepare(
432 - "
433 - DELETE FROM t1, t2
434 - USING {$table_main} AS t1 INNER JOIN {$table_meta} AS t2 ON t1.question_answer_id = t2.learnpress_question_answer_id
435 - WHERE t1.question_id = %d
436 - ",
437 - $id
438 - );
439 -
440 - // deprecated
441 - do_action( 'learn_press_before_delete_question_answers', $id );
442 -
443 - do_action( 'learn-press/question/delete-answers', $id );
444 - if ( $wpdb->query( $query ) ) {
445 - do_action( 'learn-press/question/deleted-answers', $id );
446 - }
447 - // deprecated
448 - do_action( 'learn_press_delete_question_answers', $id );
449 - }
450 -
451 - /**
452 - * @param $meta
453 - * @param $field
454 - * @param $is_saved
455 - *
456 - * @return string
457 - * @deprecated 4.1.7.3
458 - */
459 - public function _filter_meta_box_meta( $meta, $field, $is_saved ) {
460 - _deprecated_function( __METHOD__, '4.1.7.3' );
461 - /*if ( preg_match( '~\[question-content\]~', $field['id'] ) && $field['context'] == 'quiz-list-questions' ) {
462 - $post = get_post( $this->get_id() );
463 - $meta = $post->post_content;
464 - }
465 -
466 - return $meta;*/
467 - }
468 -
469 - /**
470 - * Set new type of question.
471 - * Update _lp_type meta to new type.
472 - *
473 - * @param string $type
474 - *
475 - * @return bool
476 - */
477 - public function set_type( $type = '' ) {
478 -
479 - if ( ! $type ) {
480 - return false;
481 - }
482 -
483 - if ( ! learn_press_is_support_question_type( $type ) ) {
484 - return false;
485 - }
486 -
487 - // Change to new type and update meta value
488 - $this->_question_type = $type;
489 - update_post_meta( $this->get_id(), '_lp_type', $type );
490 -
491 - return true;
492 - }
493 -
494 - /**
495 - * Update ordering of question answers
496 - *
497 - * @param array $orders List of answers
498 - * @deprecated 4.1.7.3
499 - */
500 - public function update_answer_orders( $orders ) {
501 - _deprecated_function( __METHOD__, '4.1.7.3' );
502 - /*global $wpdb;
503 - $query = $wpdb->prepare(
504 - "
505 - SELECT qa.question_answer_id, qam2.meta_value as `name`, qam.meta_value as `value`
506 - FROM {$wpdb->learnpress_question_answers} qa
507 - INNER JOIN {$wpdb->learnpress_question_answermeta} qam ON qa.question_answer_id = qam.learnpress_question_answer_id AND qam.meta_key = %s
508 - INNER JOIN {$wpdb->learnpress_question_answermeta} qam2 ON qa.question_answer_id = qam2.learnpress_question_answer_id AND qam2.meta_key = %s
509 - WHERE qa.question_id = %d
510 - ORDER BY `order`
511 - ",
512 - 'value',
513 - 'text',
514 - $this->get_id()
515 - );
516 - if ( $answers = $wpdb->get_results( $query ) ) {
517 - $query = "
518 - UPDATE {$wpdb->learnpress_question_answers}
519 - SET `order` = CASE
520 - ";
521 - for ( $order = 0, $n = sizeof( $orders ); $order < $n; $order ++ ) {
522 - $found_answer = false;
523 - foreach ( $answers as $answer ) {
524 - if ( $answer->value == $orders[ $order ]['value'] && $answer->name == $orders[ $order ]['text'] ) {
525 - $found_answer = $answer;
526 - break;
527 - }
528 - }
529 - if ( $found_answer === false ) {
530 - continue;
531 - }
532 - $query .= $wpdb->prepare( 'WHEN question_answer_id = %d THEN %d', $found_answer->question_answer_id, $order + 1 ) . "\n";
533 - }
534 - $query .= sprintf( 'ELSE `order` END WHERE question_id = %d', $this->get_id() );
535 - $wpdb->query( $query );
536 - }*/
537 - }
538 -
539 - /**
540 - * Return type of question.
541 - *
542 - * @return string
543 - */
544 - public function get_type() {
545 - return $this->_question_type;
546 - }
547 -
548 - /**
549 - * Return type of question in 'readable text'.
550 - *
551 - * @return string
552 - */
553 - public function get_type_label() {
554 - return learn_press_question_types()[ $this->get_type() ];
555 - }
556 -
557 - protected function _init() {
558 - //add_filter( 'learn_press_question_answers', array( $this, '_get_default_answers' ), 10, 2 );
559 - }
560 -
561 -
562 - /**
563 - *
564 - * @param mixed $answers
565 - * @param LP_Question $q
566 - *
567 - * @return array|bool
568 - * @deprecated 4.1.7.3
569 - */
570 - /*public function _get_default_answers( $answers = false, $q = null ) {
571 - if ( ! $answers && ( $q && $q->get_id() == $this->get_id() ) ) {
572 - $answers = $this->get_default_answers();
573 - }
574 -
575 - return $answers;
576 - }*/
577 -
578 - /**
579 - * Get default question answer.
580 - *
581 - * @return array
582 - */
583 - public static function get_default_answer() {
584 - $answer = array(
585 - 'question_answer_id' => - 1,
586 - 'title' => '',
587 - 'is_true' => '',
588 - 'value' => learn_press_random_value(),
589 - );
590 -
591 - return $answer;
592 - }
593 -
594 - /**
595 - * Get default question list answers.
596 - *
597 - * @return array|bool
598 - */
599 - public function get_default_answers() {
600 - $answers = array(
601 - array(
602 - 'question_answer_id' => - 1,
603 - 'is_true' => 'yes',
604 - 'value' => learn_press_random_value(),
605 - 'title' => esc_html__( 'First option', 'learnpress' ),
606 - ),
607 - array(
608 - 'question_answer_id' => - 2,
609 - 'is_true' => 'no',
610 - 'value' => learn_press_random_value(),
611 - 'title' => esc_html__( 'Second option', 'learnpress' ),
612 - ),
613 - array(
614 - 'question_answer_id' => - 3,
615 - 'is_true' => 'no',
616 - 'value' => learn_press_random_value(),
617 - 'title' => esc_html__( 'Third option', 'learnpress' ),
618 - ),
619 - );
620 -
621 - return $answers;
622 - }
623 -
624 -
625 - /**
626 - * Get question answers option.
627 - *
628 - * @since 3.0.0
629 - * @version 4.0.1
630 - * @modify 4.1.7.3 by tungnx
631 - * @return LP_Question_Answer_Option[]
632 - */
633 - public function get_answers(): array {
634 - $lp_question_cache = LP_Question_Cache::instance();
635 - $key_cache = "{$this->get_id()}/option_answers";
636 - //$answers = LP_Object_Cache::get( 'answer-options-' . $this->get_id(), 'learn-press/questions' );
637 - $answers_option = $lp_question_cache->get_cache( $key_cache );
638 -
639 - if ( false === $answers_option ) {
640 - $answers_option = $this->_curd->load_answer_options( $this->get_id() );
641 -
642 - if ( ! $answers_option ) {
643 - $answers_option = $this->get_default_answers();
644 - }
645 -
646 - $lp_question_cache->set_cache( $key_cache, $answers_option );
647 - }
648 -
649 - require_once 'class-lp-question-answers.php';
650 -
651 - $answers_option_tmp = [];
652 - foreach ( $answers_option as $k => $answer ) {
653 - $answer_option = new LP_Question_Answer_Option( $this, $answer );
654 - $answers_option_tmp[] = $answer_option;
655 - }
656 -
657 - return $answers_option_tmp;
658 - }
659 -
660 - /**
661 - * Create default answers.
662 - *
663 - * @since 3.3.0
664 - */
665 - public function create_default_answers() {
666 - global $wpdb;
667 -
668 - $answers = $this->get_default_answers();
669 -
670 - foreach ( $answers as $index => $answer ) {
671 - $answer = array(
672 - 'question_id' => $this->get_id(),
673 - 'title' => $answer['title'],
674 - 'value' => $answer['value'] ?? '',
675 - 'is_true' => ( $answer['is_true'] == 'yes' ) ? $answer['is_true'] : '',
676 - 'order' => $index + 1,
677 - );
678 -
679 - $wpdb->insert(
680 - $wpdb->learnpress_question_answers,
681 - $answer,
682 - array( '%d', '%s', '%s', '%s', '%d' )
683 - );
684 -
685 - $question_answer_id = $wpdb->insert_id;
686 -
687 - if ( $question_answer_id ) {
688 - $answer['question_answer_id'] = $question_answer_id;
689 - }
690 -
691 - $answers[ $index ] = $answer;
692 - }
693 -
694 - $this->set_data( 'answer_options', $answers );
695 - }
696 -
697 - public function setup_data( $quiz_id, $course_id = 0, $user_id = 0 ) {
698 -
699 - $quiz = learn_press_get_quiz( $quiz_id );
700 - $course = learn_press_get_course( $course_id );
701 -
702 - if ( $user_id ) {
703 - $user = learn_press_get_user( $user_id );
704 - } else {
705 - $user = learn_press_get_current_user();
706 - }
707 -
708 - $show_correct = false;
709 -
710 - if ( $user && $quiz && $course ) {
711 - $user_quiz = $user->get_quiz_data( $quiz->get_id(), $course->get_id() );
712 - if ( $user_quiz ) {
713 - $has_checked = $user->has_checked_answer( $this->get_id(), $quiz->get_id(), $course->get_id() );
714 - $show_correct = $user_quiz->is_completed() && ( $has_checked || $quiz->get_show_result() ) ? 'yes' : false;
715 - $answered = $user_quiz->get_question_answer( $this->get_id() );
716 - $this->set_answered( $answered );
717 - }
718 - }
719 -
720 - $this->show_correct_answers( $show_correct );
721 - }
722 -
723 - /**
724 - * Get question name.
725 - *
726 - * @return string
727 - * @deprecated 4.1.7.3
728 - */
729 - public function get_name() {
730 - _deprecated_function( __METHOD__, '4.1.7.3' );
731 - /*return $this->_options['name'] ?? ucfirst(
732 - preg_replace_callback(
733 - '!_([a-z])!',
734 - array(
735 - $this,
736 - 'sanitize_name_callback',
737 - ),
738 - $this->get_type()
739 - )
740 - );*/
741 - }
742 -
743 - /**
744 - * @param $matches
745 - *
746 - * @return string
747 - */
748 - public function sanitize_name_callback( $matches ) {
749 - return strtoupper( $matches[1] );
750 - }
751 -
752 - /**
753 - * Sets the value for a variable of this class
754 - *
755 - * @param $key string The name of a variable of this class
756 - * @param $value mixed The value to set
757 - *
758 - * @return void
759 - */
760 - public function set( $key, $value ) {
761 - $this->$key = $value;
762 - }
763 -
764 - /**
765 - * Gets the value of a variable of this class with multiple level of an object or array
766 - * example: $obj->get('a.b') -> like this :
767 - * - $obj->a->b
768 - * - or $obj->a['b']
769 - *
770 - * @param null $key string Single or multiple level such as a.b.c
771 - * @param null $default mixed Return a default value if the key does not exists or is empty
772 - * @param null $func string The function to apply the result before return
773 - *
774 - * @return mixed|null
775 - */
776 - public function get( $key = null, $default = null, $func = null ) {
777 - $val = $this->_get( $this, $key, $default );
778 -
779 - return is_callable( $func ) ? call_user_func_array( $func, array( $val ) ) : $val;
780 - }
781 -
782 -
783 - /**
784 - * Magic function to get question data.
785 - *
786 - * @param $prop
787 - * @param $key
788 - * @param null $default
789 - * @param null $type
790 - *
791 - * @return mixed|null
792 - */
793 - protected function _get( $prop, $key, $default = null, $type = null ) {
794 - $return = $default;
795 -
796 - if ( $key === false || $key == null ) {
797 - return $return;
798 - }
799 - $deep = explode( '.', $key );
800 -
801 - if ( is_array( $prop ) ) {
802 - if ( isset( $prop[ $deep[0] ] ) ) {
803 - $return = $prop[ $deep[0] ];
804 - if ( count( $deep ) > 1 ) {
805 - unset( $deep[0] );
806 - $return = $this->_get( $return, implode( '.', $deep ), $default, $type );
807 - }
808 - }
809 - } elseif ( is_object( $prop ) ) {
810 - if ( isset( $prop->{$deep[0]} ) ) {
811 - $return = $prop->{$deep[0]};
812 - if ( count( $deep ) > 1 ) {
813 - unset( $deep[0] );
814 - $return = $this->_get( $return, implode( '.', $deep ), $default, $type );
815 - }
816 - }
817 - }
818 -
819 - if ( $type == 'object' ) {
820 - settype( $return, 'object' );
821 - } elseif ( $type == 'array' ) {
822 - settype( $return, 'array' );
823 - }
824 -
825 - return $return;
826 - }
827 -
828 - /**
829 - * Find value in answer's option and compare with value answered by user.
830 - *
831 - * @param LP_Question_Answer_Option $answer
832 - * @param mixed $answered
833 - *
834 - * @return bool
835 - */
836 - public function is_selected_option( $answer, $answered = false ): bool {
837 - if ( is_array( $answered ) ) {
838 - $is_selected = in_array( $answer['value'], $answered );
839 - } else {
840 - $is_selected = $answer['value'] === $answered;
841 - }
842 -
843 - return apply_filters( 'learn-press/question/is-selected-option', $is_selected, $answer, $answered, $this->get_id() );
844 - }
845 -
846 - /**
847 - * Save user question answer.
848 - *
849 - * @param $answer
850 - * @param $quiz_id
851 - * @param null $user_id
852 - * @deprecated 4.2.7.3
853 - */
854 - /*public function save_user_answer( $answer, $quiz_id, $user_id = null ) {
855 - if ( $user_id ) {
856 - $user = LP_User_Factory::get_user( $user_id );
857 - } else {
858 - $user = learn_press_get_current_user();
859 - }
860 -
861 - $progress = $user->get_quiz_progress( $quiz_id );
862 -
863 - if ( $progress ) {
864 - if ( ! isset( $progress->question_answers ) ) {
865 - $question_answers = array();
866 - } else {
867 - $question_answers = $progress->question_answers;
868 - }
869 - $question_answers[ $this->get_id() ] = $answer;
870 -
871 - $question_answers = apply_filters( 'learn_press_update_user_question_answers', $question_answers, $progress->history_id, $user_id, $this, $quiz_id );
872 -
873 - // learn_press_update_user_quiz_meta( $progress->history_id, 'question_answers', $question_answers );
874 - }
875 - }*/
876 -
877 - /**
878 - * Allow check question answer, default disable for True or False and Single choice, override by Multiple choice question.
879 - *
880 - * @return bool
881 - */
882 - public function can_check_answer() {
883 - return false;
884 - }
885 -
886 - /**
887 - * Check user answer, override by question type class.
888 - *
889 - * @param null $args | question answered
890 - *
891 - * @return array
892 - */
893 - public function check( $args = null ) {
894 - $return = array(
895 - 'correct' => false,
896 - 'mark' => 0,
897 - );
898 -
899 - return $return;
900 - }
901 -
902 - /**
903 - * Get answer at position
904 - *
905 - * @since 3.0.0
906 - *
907 - * @param int $at
908 - *
909 - * @return LP_Question_Answer_Option|mixed
910 - */
911 - public function get_answer_at( $at ) {
912 - return $this->get_answers()->get_answer_at( $at );
913 - }
914 -
915 - /**
916 - * Get user answered data.
917 - *
918 - * @param $args
919 - *
920 - * @return null
921 - */
922 - public function get_user_answered( $args ) {
923 - $args = wp_parse_args(
924 - $args,
925 - array(
926 - 'history_id' => 0,
927 - 'quiz_id' => 0,
928 - 'course_id' => 0,
929 - 'force' => false,
930 - )
931 - );
932 - $answered = null;
933 - if ( $args['history_id'] ) {
934 - $user_meta = learn_press_get_user_item_meta( $args['history_id'], 'question_answers', true );
935 - if ( $user_meta && array_key_exists( $this->get_id(), $user_meta ) ) {
936 - $answered = $user_meta[ $this->get_id() ];
937 - }
938 - } elseif ( $args['quiz_id'] && $args['course_id'] ) {
939 - $user = learn_press_get_current_user();
940 - $history = $user->get_quiz_results( $args['quiz_id'], $args['course_id'] );
941 -
942 - if ( $history ) {
943 - $user_meta = learn_press_get_user_item_meta( $history->history_id, 'question_answers', true );
944 -
945 - if ( $user_meta && array_key_exists( $this->get_id(), $user_meta ) ) {
946 - $answered = $user_meta[ $this->get_id() ];
947 - }
948 - }
949 - }
950 -
951 - return $answered;
952 - }
953 -
954 - /**
955 - * Get question.
956 - *
957 - * @param bool $the_question
958 - * @param array $args
959 - *
960 - * @return LP_Question|bool
961 - */
962 - public static function get_question( $the_question = false, $args = array() ) {
963 - // question object
964 - $the_question = self::get_question_object( $the_question );
965 - if ( ! $the_question ) {
966 - return false;
967 - }
968 -
969 - if ( ! empty( $args['force'] ) ) {
970 - $force = ! ! $args['force'];
971 - unset( $args['force'] );
972 - } else {
973 - $force = false;
974 - }
975 -
976 - $key_args = wp_parse_args(
977 - $args,
978 - array(
979 - 'id' => $the_question->ID,
980 - 'type' => $the_question->post_type,
981 - )
982 - );
983 -
984 - $key = LP_Helper::array_to_md5( $key_args );
985 -
986 - if ( $force ) {
987 - LP_Global::$questions[ $key ] = false;
988 - }
989 -
990 - if ( empty( LP_Global::$questions[ $key ] ) ) {
991 - $class_name = self::get_question_class( $the_question, $args );
992 - if ( is_string( $class_name ) && class_exists( $class_name ) ) {
993 - $lesson = new $class_name( $the_question->ID, $args );
994 - } elseif ( $class_name instanceof LP_Question ) {
995 - $lesson = $class_name;
996 - } else {
997 - $lesson = new self( $the_question->ID, $args );
998 - }
999 - LP_Global::$questions[ $key ] = $lesson;
1000 - }
1001 -
1002 - return LP_Global::$questions[ $key ];
1003 - }
1004 -
1005 - /**
1006 - * Get the question class name.
1007 - *
1008 - * @param WP_Post $the_question
1009 - * @param array $args (default: array())
1010 - *
1011 - * @return string
1012 - */
1013 - private static function get_question_class( $the_question, $args = array() ) {
1014 - $question_id = absint( $the_question->ID );
1015 - if ( ! empty( $args['type'] ) ) {
1016 - $question_type = $args['type'];
1017 - } else {
1018 - $question_type = get_post_meta( $question_id, '_lp_type', true );
1019 - }
1020 -
1021 - $class_name = self::get_class_name_from_question_type( array( $question_type ) );
1022 -
1023 - // Filter class name so that the class can be overridden if extended.
1024 - return apply_filters( 'learn-press/question/object-class', $class_name, $question_type, $question_id );
1025 - }
1026 -
1027 - /**
1028 - * Get question class from question type.
1029 - *
1030 - * @param string $question_type
1031 - *
1032 - * @return string|false
1033 - */
1034 - public static function get_class_name_from_question_type( $question_type ) {
1035 -
1036 - if ( is_array( $question_type ) ) {
1037 - $question_type = reset( $question_type );
1038 - }
1039 -
1040 - return ! $question_type ? __CLASS__ : 'LP_Question_' . implode( '_', array_map( 'ucfirst', explode( '-', $question_type ) ) );
1041 - }
1042 -
1043 - /**
1044 - * Get the question object.
1045 - *
1046 - * @since 3.0.0
1047 - *
1048 - * @param mixed $the_question
1049 - *
1050 - * @uses WP_Post
1051 - * @return WP_Post|bool false on failure
1052 - */
1053 - private static function get_question_object( $the_question ) {
1054 - if ( false === $the_question ) {
1055 - $the_question = get_post_type() === LP_QUESTION_CPT ? $GLOBALS['post'] : false;
1056 - } elseif ( is_numeric( $the_question ) ) {
1057 - $the_question = get_post( $the_question );
1058 - } elseif ( $the_question instanceof LP_Course_Item ) {
1059 - $the_question = get_post( $the_question->get_id() );
1060 - } elseif ( ! ( $the_question instanceof WP_Post ) ) {
1061 - $the_question = false;
1062 - }
1063 -
1064 - return apply_filters( 'learn-press/question/post-object', $the_question );
1065 - }
1066 -
1067 - protected function _get_checked( $user_answer = null ) {
1068 - $key = $user_answer ? md5( serialize( $user_answer ) ) : - 1;
1069 -
1070 - return LP_Object_Cache::get( 'question-' . $this->get_id() . '/' . $key, 'learn-press/answer-checked' );
1071 - }
1072 -
1073 - protected function _set_checked( $checked, $user_answer ) {
1074 - $key = $user_answer ? md5( serialize( $user_answer ) ) : - 1;
1075 -
1076 - return LP_Object_Cache::set( 'question-' . $this->get_id() . '/' . $key, $checked, 'learn-press/answer-checked' );
1077 - }
1078 -
1079 - /**
1080 - * Get default json data for editor settings
1081 - *
1082 - * @since 3.3.0
1083 - *
1084 - * @return array
1085 - */
1086 - public function get_editor_default_settings() {
1087 - return apply_filters(
1088 - 'learn-press/question-editor-default-settings',
1089 - array(
1090 - 'id' => $this->get_id(),
1091 - 'type' => $this->get_type(),
1092 - 'duration' => $this->get_duration(),
1093 - )
1094 - );
1095 - }
1096 -
1097 - /**
1098 - * Get json data for editor settings.
1099 - *
1100 - * @since 3.3.0
1101 - *
1102 - * @return array
1103 - */
1104 - public function get_editor_settings() {
1105 - $defaults = $this->get_editor_default_settings();
1106 -
1107 - return apply_filters(
1108 - 'learn-press/question-editor-settings',
1109 - array_merge(
1110 - $defaults,
1111 - array()
1112 - ),
1113 - $this->get_type(),
1114 - $this->get_id(),
1115 - $this
1116 - );
1117 - }
1118 - }
1119 -
1120 -}
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 + /**
134 + * @deprecated 4.1.7.3
135 + */
136 + public function get_supports() {
137 + _deprecated_function( __FUNCTION__, '4.1.7.3' );
138 + if ( empty( LP_Global::$object_support_features ) ) {
139 + return false;
140 + }
141 +
142 + return LP_Global::get_object_supports( $this->object_type . '.' . $this->get_type() );
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 + $answer_options = $this->get_data( 'answer_options' );
397 + if ( $answer_options ) {
398 + $question_order = 1;
399 + $query = "INSERT INTO {$wpdb->prefix}learnpress_question_answers(`question_id`, `order`) VALUES";
400 + foreach ( $answer_options as $answer_option ) {
401 + if ( empty( $answer_option['title'] ) ) {
402 + if ( apply_filters( 'learn-press/question/ignore-insert-empty-answer-option', true, $answer_option, $id ) ) {
403 + continue;
404 + }
405 + }
406 + $qry = $query . $wpdb->prepare( '(%d, %d)', $id, $question_order ++ );
407 + do_action( 'learn-press/question/insert-answer-option', $id, $answer_option );
408 + if ( $wpdb->query( $qry ) ) {
409 + $inserted_id = $wpdb->insert_id;
410 + learn_press_update_question_answer_meta( $inserted_id, 'text', $answer_option['text'] );
411 + learn_press_update_question_answer_meta( $inserted_id, 'value', $answer_option['value'] );
412 + if ( ! empty( $answer_option['is_true'] ) && ! learn_press_is_negative_value( $answer_option['is_true'] ) ) {
413 + learn_press_update_question_answer_meta( $inserted_id, 'checked', 'yes' );
414 + }
415 + do_action( 'learn-press/question/inserted-answer-option', $inserted_id, $id, $answer_option );
416 + }
417 + }
418 + }
419 +
420 + return $updated;
421 + }
422 +
423 + /**
424 + * Remove all answers to prepare for inserting new
425 + */
426 + public function empty_answers() {
427 + global $wpdb;
428 + $id = absint( $this->get_id() );
429 + $table_meta = $wpdb->learnpress_question_answermeta;
430 + $table_main = $wpdb->learnpress_question_answers;
431 + $query = $wpdb->prepare(
432 + "
433 + DELETE FROM t1, t2
434 + USING {$table_main} AS t1 INNER JOIN {$table_meta} AS t2 ON t1.question_answer_id = t2.learnpress_question_answer_id
435 + WHERE t1.question_id = %d
436 + ",
437 + $id
438 + );
439 +
440 + // deprecated
441 + do_action( 'learn_press_before_delete_question_answers', $id );
442 +
443 + do_action( 'learn-press/question/delete-answers', $id );
444 + if ( $wpdb->query( $query ) ) {
445 + do_action( 'learn-press/question/deleted-answers', $id );
446 + }
447 + // deprecated
448 + do_action( 'learn_press_delete_question_answers', $id );
449 + }
450 +
451 + /**
452 + * @param $meta
453 + * @param $field
454 + * @param $is_saved
455 + *
456 + * @return string
457 + * @deprecated 4.1.7.3
458 + */
459 + public function _filter_meta_box_meta( $meta, $field, $is_saved ) {
460 + _deprecated_function( __METHOD__, '4.1.7.3' );
461 + /*if ( preg_match( '~\[question-content\]~', $field['id'] ) && $field['context'] == 'quiz-list-questions' ) {
462 + $post = get_post( $this->get_id() );
463 + $meta = $post->post_content;
464 + }
465 +
466 + return $meta;*/
467 + }
468 +
469 + /**
470 + * Set new type of question.
471 + * Update _lp_type meta to new type.
472 + *
473 + * @param string $type
474 + *
475 + * @return bool
476 + */
477 + public function set_type( $type = '' ) {
478 +
479 + if ( ! $type ) {
480 + return false;
481 + }
482 +
483 + if ( ! learn_press_is_support_question_type( $type ) ) {
484 + return false;
485 + }
486 +
487 + // Change to new type and update meta value
488 + $this->_question_type = $type;
489 + update_post_meta( $this->get_id(), '_lp_type', $type );
490 +
491 + return true;
492 + }
493 +
494 + /**
495 + * Update ordering of question answers
496 + *
497 + * @param array $orders List of answers
498 + * @deprecated 4.1.7.3
499 + */
500 + public function update_answer_orders( $orders ) {
501 + _deprecated_function( __METHOD__, '4.1.7.3' );
502 + /*global $wpdb;
503 + $query = $wpdb->prepare(
504 + "
505 + SELECT qa.question_answer_id, qam2.meta_value as `name`, qam.meta_value as `value`
506 + FROM {$wpdb->learnpress_question_answers} qa
507 + INNER JOIN {$wpdb->learnpress_question_answermeta} qam ON qa.question_answer_id = qam.learnpress_question_answer_id AND qam.meta_key = %s
508 + INNER JOIN {$wpdb->learnpress_question_answermeta} qam2 ON qa.question_answer_id = qam2.learnpress_question_answer_id AND qam2.meta_key = %s
509 + WHERE qa.question_id = %d
510 + ORDER BY `order`
511 + ",
512 + 'value',
513 + 'text',
514 + $this->get_id()
515 + );
516 + if ( $answers = $wpdb->get_results( $query ) ) {
517 + $query = "
518 + UPDATE {$wpdb->learnpress_question_answers}
519 + SET `order` = CASE
520 + ";
521 + for ( $order = 0, $n = sizeof( $orders ); $order < $n; $order ++ ) {
522 + $found_answer = false;
523 + foreach ( $answers as $answer ) {
524 + if ( $answer->value == $orders[ $order ]['value'] && $answer->name == $orders[ $order ]['text'] ) {
525 + $found_answer = $answer;
526 + break;
527 + }
528 + }
529 + if ( $found_answer === false ) {
530 + continue;
531 + }
532 + $query .= $wpdb->prepare( 'WHEN question_answer_id = %d THEN %d', $found_answer->question_answer_id, $order + 1 ) . "\n";
533 + }
534 + $query .= sprintf( 'ELSE `order` END WHERE question_id = %d', $this->get_id() );
535 + $wpdb->query( $query );
536 + }*/
537 + }
538 +
539 + /**
540 + * Return type of question.
541 + *
542 + * @return string
543 + */
544 + public function get_type() {
545 + return $this->_question_type;
546 + }
547 +
548 + /**
549 + * Return type of question in 'readable text'.
550 + *
551 + * @return string
552 + */
553 + public function get_type_label() {
554 + return learn_press_question_types()[ $this->get_type() ];
555 + }
556 +
557 + protected function _init() {
558 + //add_filter( 'learn_press_question_answers', array( $this, '_get_default_answers' ), 10, 2 );
559 + }
560 +
561 +
562 + /**
563 + *
564 + * @param mixed $answers
565 + * @param LP_Question $q
566 + *
567 + * @return array|bool
568 + * @deprecated 4.1.7.3
569 + */
570 + /*public function _get_default_answers( $answers = false, $q = null ) {
571 + if ( ! $answers && ( $q && $q->get_id() == $this->get_id() ) ) {
572 + $answers = $this->get_default_answers();
573 + }
574 +
575 + return $answers;
576 + }*/
577 +
578 + /**
579 + * Get default question answer.
580 + *
581 + * @return array
582 + */
583 + public static function get_default_answer() {
584 + $answer = array(
585 + 'question_answer_id' => - 1,
586 + 'title' => '',
587 + 'is_true' => '',
588 + 'value' => learn_press_random_value(),
589 + );
590 +
591 + return $answer;
592 + }
593 +
594 + /**
595 + * Get default question list answers.
596 + *
597 + * @return array|bool
598 + */
599 + public function get_default_answers() {
600 + $answers = array(
601 + array(
602 + 'question_answer_id' => - 1,
603 + 'is_true' => 'yes',
604 + 'value' => learn_press_random_value(),
605 + 'title' => esc_html__( 'First option', 'learnpress' ),
606 + ),
607 + array(
608 + 'question_answer_id' => - 2,
609 + 'is_true' => 'no',
610 + 'value' => learn_press_random_value(),
611 + 'title' => esc_html__( 'Second option', 'learnpress' ),
612 + ),
613 + array(
614 + 'question_answer_id' => - 3,
615 + 'is_true' => 'no',
616 + 'value' => learn_press_random_value(),
617 + 'title' => esc_html__( 'Third option', 'learnpress' ),
618 + ),
619 + );
620 +
621 + return $answers;
622 + }
623 +
624 +
625 + /**
626 + * Get question answers option.
627 + *
628 + * @since 3.0.0
629 + * @version 4.0.1
630 + * @modify 4.1.7.3 by tungnx
631 + * @return LP_Question_Answer_Option[]
632 + */
633 + public function get_answers(): array {
634 + $lp_question_cache = LP_Question_Cache::instance();
635 + $key_cache = "{$this->get_id()}/option_answers";
636 + //$answers = LP_Object_Cache::get( 'answer-options-' . $this->get_id(), 'learn-press/questions' );
637 + $answers_option = $lp_question_cache->get_cache( $key_cache );
638 +
639 + if ( false === $answers_option ) {
640 + $answers_option = $this->_curd->load_answer_options( $this->get_id() );
641 +
642 + if ( ! $answers_option ) {
643 + $answers_option = $this->get_default_answers();
644 + }
645 +
646 + $lp_question_cache->set_cache( $key_cache, $answers_option );
647 + }
648 +
649 + require_once 'class-lp-question-answers.php';
650 +
651 + $answers_option_tmp = [];
652 + foreach ( $answers_option as $k => $answer ) {
653 + $answer_option = new LP_Question_Answer_Option( $this, $answer );
654 + $answers_option_tmp[] = $answer_option;
655 + }
656 +
657 + return $answers_option_tmp;
658 + }
659 +
660 + /**
661 + * Create default answers.
662 + *
663 + * @since 3.3.0
664 + */
665 + public function create_default_answers() {
666 + global $wpdb;
667 +
668 + $answers = $this->get_default_answers();
669 +
670 + foreach ( $answers as $index => $answer ) {
671 + $answer = array(
672 + 'question_id' => $this->get_id(),
673 + 'title' => $answer['title'],
674 + 'value' => $answer['value'] ?? '',
675 + 'is_true' => ( $answer['is_true'] == 'yes' ) ? $answer['is_true'] : '',
676 + 'order' => $index + 1,
677 + );
678 +
679 + $wpdb->insert(
680 + $wpdb->learnpress_question_answers,
681 + $answer,
682 + array( '%d', '%s', '%s', '%s', '%d' )
683 + );
684 +
685 + $question_answer_id = $wpdb->insert_id;
686 +
687 + if ( $question_answer_id ) {
688 + $answer['question_answer_id'] = $question_answer_id;
689 + }
690 +
691 + $answers[ $index ] = $answer;
692 + }
693 +
694 + $this->set_data( 'answer_options', $answers );
695 + }
696 +
697 + public function setup_data( $quiz_id, $course_id = 0, $user_id = 0 ) {
698 +
699 + $quiz = learn_press_get_quiz( $quiz_id );
700 + $course = learn_press_get_course( $course_id );
701 +
702 + if ( $user_id ) {
703 + $user = learn_press_get_user( $user_id );
704 + } else {
705 + $user = learn_press_get_current_user();
706 + }
707 +
708 + $show_correct = false;
709 +
710 + if ( $user && $quiz && $course ) {
711 + $user_quiz = $user->get_quiz_data( $quiz->get_id(), $course->get_id() );
712 + if ( $user_quiz ) {
713 + $has_checked = $user->has_checked_answer( $this->get_id(), $quiz->get_id(), $course->get_id() );
714 + $show_correct = $user_quiz->is_completed() && $has_checked ? 'yes' : false;
715 + $answered = $user_quiz->get_question_answer( $this->get_id() );
716 + $this->set_answered( $answered );
717 + }
718 + }
719 +
720 + $this->show_correct_answers( $show_correct );
721 + }
722 +
723 + /**
724 + * Get question name.
725 + *
726 + * @return string
727 + * @deprecated 4.1.7.3
728 + */
729 + public function get_name() {
730 + _deprecated_function( __METHOD__, '4.1.7.3' );
731 + /*return $this->_options['name'] ?? ucfirst(
732 + preg_replace_callback(
733 + '!_([a-z])!',
734 + array(
735 + $this,
736 + 'sanitize_name_callback',
737 + ),
738 + $this->get_type()
739 + )
740 + );*/
741 + }
742 +
743 + /**
744 + * @param $matches
745 + *
746 + * @return string
747 + */
748 + public function sanitize_name_callback( $matches ) {
749 + return strtoupper( $matches[1] );
750 + }
751 +
752 + /**
753 + * Sets the value for a variable of this class
754 + *
755 + * @param $key string The name of a variable of this class
756 + * @param $value mixed The value to set
757 + *
758 + * @return void
759 + */
760 + public function set( $key, $value ) {
761 + $this->$key = $value;
762 + }
763 +
764 + /**
765 + * Gets the value of a variable of this class with multiple level of an object or array
766 + * example: $obj->get('a.b') -> like this :
767 + * - $obj->a->b
768 + * - or $obj->a['b']
769 + *
770 + * @param null $key string Single or multiple level such as a.b.c
771 + * @param null $default mixed Return a default value if the key does not exists or is empty
772 + * @param null $func string The function to apply the result before return
773 + *
774 + * @return mixed|null
775 + */
776 + public function get( $key = null, $default = null, $func = null ) {
777 + $val = $this->_get( $this, $key, $default );
778 +
779 + return is_callable( $func ) ? call_user_func_array( $func, array( $val ) ) : $val;
780 + }
781 +
782 +
783 + /**
784 + * Magic function to get question data.
785 + *
786 + * @param $prop
787 + * @param $key
788 + * @param null $default
789 + * @param null $type
790 + *
791 + * @return mixed|null
792 + */
793 + protected function _get( $prop, $key, $default = null, $type = null ) {
794 + $return = $default;
795 +
796 + if ( $key === false || $key == null ) {
797 + return $return;
798 + }
799 + $deep = explode( '.', $key );
800 +
801 + if ( is_array( $prop ) ) {
802 + if ( isset( $prop[ $deep[0] ] ) ) {
803 + $return = $prop[ $deep[0] ];
804 + if ( count( $deep ) > 1 ) {
805 + unset( $deep[0] );
806 + $return = $this->_get( $return, implode( '.', $deep ), $default, $type );
807 + }
808 + }
809 + } elseif ( is_object( $prop ) ) {
810 + if ( isset( $prop->{$deep[0]} ) ) {
811 + $return = $prop->{$deep[0]};
812 + if ( count( $deep ) > 1 ) {
813 + unset( $deep[0] );
814 + $return = $this->_get( $return, implode( '.', $deep ), $default, $type );
815 + }
816 + }
817 + }
818 +
819 + if ( $type == 'object' ) {
820 + settype( $return, 'object' );
821 + } elseif ( $type == 'array' ) {
822 + settype( $return, 'array' );
823 + }
824 +
825 + return $return;
826 + }
827 +
828 + /**
829 + * Find value in answer's option and compare with value answered by user.
830 + *
831 + * @param LP_Question_Answer_Option $answer
832 + * @param mixed $answered
833 + *
834 + * @return bool
835 + */
836 + public function is_selected_option( $answer, $answered = false ): bool {
837 + if ( is_array( $answered ) ) {
838 + $is_selected = in_array( $answer['value'], $answered );
839 + } else {
840 + $is_selected = $answer['value'] === $answered;
841 + }
842 +
843 + return apply_filters( 'learn-press/question/is-selected-option', $is_selected, $answer, $answered, $this->get_id() );
844 + }
845 +
846 + /**
847 + * Save user question answer.
848 + *
849 + * @param $answer
850 + * @param $quiz_id
851 + * @param null $user_id
852 + * @deprecated 4.2.7.3
853 + */
854 + /*public function save_user_answer( $answer, $quiz_id, $user_id = null ) {
855 + if ( $user_id ) {
856 + $user = LP_User_Factory::get_user( $user_id );
857 + } else {
858 + $user = learn_press_get_current_user();
859 + }
860 +
861 + $progress = $user->get_quiz_progress( $quiz_id );
862 +
863 + if ( $progress ) {
864 + if ( ! isset( $progress->question_answers ) ) {
865 + $question_answers = array();
866 + } else {
867 + $question_answers = $progress->question_answers;
868 + }
869 + $question_answers[ $this->get_id() ] = $answer;
870 +
871 + $question_answers = apply_filters( 'learn_press_update_user_question_answers', $question_answers, $progress->history_id, $user_id, $this, $quiz_id );
872 +
873 + // learn_press_update_user_quiz_meta( $progress->history_id, 'question_answers', $question_answers );
874 + }
875 + }*/
876 +
877 + /**
878 + * Allow check question answer, default disable for True or False and Single choice, override by Multiple choice question.
879 + *
880 + * @return bool
881 + */
882 + public function can_check_answer() {
883 + return false;
884 + }
885 +
886 + /**
887 + * Check user answer, override by question type class.
888 + *
889 + * @param null $args | question answered
890 + *
891 + * @return array
892 + */
893 + public function check( $args = null ) {
894 + $return = array(
895 + 'correct' => false,
896 + 'mark' => 0,
897 + );
898 +
899 + return $return;
900 + }
901 +
902 + /**
903 + * Get answer at position
904 + *
905 + * @since 3.0.0
906 + *
907 + * @param int $at
908 + *
909 + * @return LP_Question_Answer_Option|mixed
910 + */
911 + public function get_answer_at( $at ) {
912 + return $this->get_answers()->get_answer_at( $at );
913 + }
914 +
915 + /**
916 + * Get user answered data.
917 + *
918 + * @param $args
919 + *
920 + * @return null
921 + */
922 + public function get_user_answered( $args ) {
923 + $args = wp_parse_args(
924 + $args,
925 + array(
926 + 'history_id' => 0,
927 + 'quiz_id' => 0,
928 + 'course_id' => 0,
929 + 'force' => false,
930 + )
931 + );
932 + $answered = null;
933 + if ( $args['history_id'] ) {
934 + $user_meta = learn_press_get_user_item_meta( $args['history_id'], 'question_answers', true );
935 + if ( $user_meta && array_key_exists( $this->get_id(), $user_meta ) ) {
936 + $answered = $user_meta[ $this->get_id() ];
937 + }
938 + } elseif ( $args['quiz_id'] && $args['course_id'] ) {
939 + $user = learn_press_get_current_user();
940 + $history = $user->get_quiz_results( $args['quiz_id'], $args['course_id'] );
941 +
942 + if ( $history ) {
943 + $user_meta = learn_press_get_user_item_meta( $history->history_id, 'question_answers', true );
944 +
945 + if ( $user_meta && array_key_exists( $this->get_id(), $user_meta ) ) {
946 + $answered = $user_meta[ $this->get_id() ];
947 + }
948 + }
949 + }
950 +
951 + return $answered;
952 + }
953 +
954 + /**
955 + * Get question.
956 + *
957 + * @param bool $the_question
958 + * @param array $args
959 + *
960 + * @return LP_Question|bool
961 + */
962 + public static function get_question( $the_question = false, $args = array() ) {
963 + // question object
964 + $the_question = self::get_question_object( $the_question );
965 + if ( ! $the_question ) {
966 + return false;
967 + }
968 +
969 + if ( ! empty( $args['force'] ) ) {
970 + $force = ! ! $args['force'];
971 + unset( $args['force'] );
972 + } else {
973 + $force = false;
974 + }
975 +
976 + $key_args = wp_parse_args(
977 + $args,
978 + array(
979 + 'id' => $the_question->ID,
980 + 'type' => $the_question->post_type,
981 + )
982 + );
983 +
984 + $key = LP_Helper::array_to_md5( $key_args );
985 +
986 + if ( $force ) {
987 + LP_Global::$questions[ $key ] = false;
988 + }
989 +
990 + if ( empty( LP_Global::$questions[ $key ] ) ) {
991 + $class_name = self::get_question_class( $the_question, $args );
992 + if ( is_string( $class_name ) && class_exists( $class_name ) ) {
993 + $lesson = new $class_name( $the_question->ID, $args );
994 + } elseif ( $class_name instanceof LP_Question ) {
995 + $lesson = $class_name;
996 + } else {
997 + $lesson = new self( $the_question->ID, $args );
998 + }
999 + LP_Global::$questions[ $key ] = $lesson;
1000 + }
1001 +
1002 + return LP_Global::$questions[ $key ];
1003 + }
1004 +
1005 + /**
1006 + * Get the question class name.
1007 + *
1008 + * @param WP_Post $the_question
1009 + * @param array $args (default: array())
1010 + *
1011 + * @return string
1012 + */
1013 + private static function get_question_class( $the_question, $args = array() ) {
1014 + $question_id = absint( $the_question->ID );
1015 + if ( ! empty( $args['type'] ) ) {
1016 + $question_type = $args['type'];
1017 + } else {
1018 + $question_type = get_post_meta( $question_id, '_lp_type', true );
1019 + }
1020 +
1021 + $class_name = self::get_class_name_from_question_type( array( $question_type ) );
1022 +
1023 + // Filter class name so that the class can be overridden if extended.
1024 + return apply_filters( 'learn-press/question/object-class', $class_name, $question_type, $question_id );
1025 + }
1026 +
1027 + /**
1028 + * Get question class from question type.
1029 + *
1030 + * @param string $question_type
1031 + *
1032 + * @return string|false
1033 + */
1034 + public static function get_class_name_from_question_type( $question_type ) {
1035 +
1036 + if ( is_array( $question_type ) ) {
1037 + $question_type = reset( $question_type );
1038 + }
1039 +
1040 + return ! $question_type ? __CLASS__ : 'LP_Question_' . implode( '_', array_map( 'ucfirst', explode( '-', $question_type ) ) );
1041 + }
1042 +
1043 + /**
1044 + * Get the question object.
1045 + *
1046 + * @since 3.0.0
1047 + *
1048 + * @param mixed $the_question
1049 + *
1050 + * @uses WP_Post
1051 + * @return WP_Post|bool false on failure
1052 + */
1053 + private static function get_question_object( $the_question ) {
1054 + if ( false === $the_question ) {
1055 + $the_question = get_post_type() === LP_QUESTION_CPT ? $GLOBALS['post'] : false;
1056 + } elseif ( is_numeric( $the_question ) ) {
1057 + $the_question = get_post( $the_question );
1058 + } elseif ( $the_question instanceof LP_Course_Item ) {
1059 + $the_question = get_post( $the_question->get_id() );
1060 + } elseif ( ! ( $the_question instanceof WP_Post ) ) {
1061 + $the_question = false;
1062 + }
1063 +
1064 + return apply_filters( 'learn-press/question/post-object', $the_question );
1065 + }
1066 +
1067 + protected function _get_checked( $user_answer = null ) {
1068 + $key = $user_answer ? md5( serialize( $user_answer ) ) : - 1;
1069 +
1070 + return LP_Object_Cache::get( 'question-' . $this->get_id() . '/' . $key, 'learn-press/answer-checked' );
1071 + }
1072 +
1073 + protected function _set_checked( $checked, $user_answer ) {
1074 + $key = $user_answer ? md5( serialize( $user_answer ) ) : - 1;
1075 +
1076 + return LP_Object_Cache::set( 'question-' . $this->get_id() . '/' . $key, $checked, 'learn-press/answer-checked' );
1077 + }
1078 +
1079 + /**
1080 + * Get default json data for editor settings
1081 + *
1082 + * @since 3.3.0
1083 + *
1084 + * @return array
1085 + */
1086 + public function get_editor_default_settings() {
1087 + return apply_filters(
1088 + 'learn-press/question-editor-default-settings',
1089 + array(
1090 + 'id' => $this->get_id(),
1091 + 'type' => $this->get_type(),
1092 + 'duration' => $this->get_duration(),
1093 + )
1094 + );
1095 + }
1096 +
1097 + /**
1098 + * Get json data for editor settings.
1099 + *
1100 + * @since 3.3.0
1101 + *
1102 + * @return array
1103 + */
1104 + public function get_editor_settings() {
1105 + $defaults = $this->get_editor_default_settings();
1106 +
1107 + return apply_filters(
1108 + 'learn-press/question-editor-settings',
1109 + array_merge(
1110 + $defaults,
1111 + array()
1112 + ),
1113 + $this->get_type(),
1114 + $this->get_id(),
1115 + $this
1116 + );
1117 + }
1118 + }
1119 +
1120 +}