PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.9
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.9
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 4.1.7.3.1 4.1.7.3.2 All 140 releases
← All changes | inc/curds/class-lp-question-curd.php +991 -999 4.3.9 → 4.4.9 View file →
@@ -1,999 +1,991 @@
1 -<?php
2 -
3 -/**
4 - * Class LP_Question_CURD
5 - *
6 - * @author ThimPress
7 - * @package LearnPress/Classes/CURD
8 - * @since 3.0.0
9 - */
10 -
11 -/**
12 - * Prevent loading this file directly
13 - */
14 -defined( 'ABSPATH' ) || exit();
15 -
16 -if ( ! class_exists( 'LP_Question_CURD' ) ) {
17 -
18 - /**
19 - * Class LP_Question_CURD
20 - */
21 - class LP_Question_CURD extends LP_Object_Data_CURD implements LP_Interface_CURD {
22 -
23 - /**
24 - * LP_Question_CURD constructor.
25 - */
26 - public function __construct() {
27 - $this->_error_messages = array(
28 - 'QUESTION_NOT_EXISTS' => __( 'The question does not exist.', 'learnpress' ),
29 - );
30 - }
31 -
32 - /**
33 - * Create question and can add to quiz.
34 - *
35 - * @param $args
36 - *
37 - * @return bool|int|LP_Question|WP_Error
38 - */
39 - public function create( &$args ) {
40 - $args = wp_parse_args(
41 - $args,
42 - array(
43 - 'quiz_id' => 0,
44 - 'order' => - 1,
45 - 'id' => '',
46 - 'status' => 'publish',
47 - 'type' => 'true_or_false',
48 - 'title' => __( 'New Question', 'learnpress' ),
49 - 'content' => '',
50 - 'create_answers' => true,
51 - )
52 - );
53 -
54 - // set question author for author of quiz
55 - if ( ! empty( $args['quiz_id'] ) ) {
56 - $user_id = get_post_field( 'post_author', $args['quiz_id'] );
57 - } else {
58 - $user_id = learn_press_get_current_user_id();
59 - }
60 -
61 - $question_id = wp_insert_post(
62 - array(
63 - 'ID' => $args['id'],
64 - 'post_author' => $user_id,
65 - 'post_type' => LP_QUESTION_CPT,
66 - 'post_status' => $args['status'],
67 - 'post_title' => $args['title'],
68 - 'post_content' => $args['content'],
69 - )
70 - );
71 -
72 - if ( $question_id ) {
73 - // add default meta for new lesson
74 - $default_meta = LP_Question::get_default_meta();
75 -
76 - if ( is_array( $default_meta ) ) {
77 - foreach ( $default_meta as $key => $value ) {
78 - update_post_meta( $question_id, '_lp_' . $key, $value );
79 - }
80 - }
81 - update_post_meta( $question_id, '_lp_type', $args['type'] );
82 - // update user memory question types
83 - get_user_meta( $user_id, '_learn_press_memorize_question_types', $args['type'] );
84 -
85 - $question = LP_Question::get_question( $question_id, array( 'type' => $args['type'] ) );
86 - $question->set_type( $args['type'] );
87 -
88 - // add question to quiz
89 - if ( ! empty( $args['quiz_id'] ) ) {
90 - $quiz_curd = new LP_Quiz_CURD();
91 - $quiz_curd->add_question( $args['quiz_id'], $question_id, $args['order'] );
92 - }
93 -
94 - if ( $args['create_answers'] ) {
95 - $question->create_default_answers();
96 - }
97 -
98 - do_action( 'learn-press/after-create-question', $question );
99 -
100 - return $question;
101 - }
102 -
103 - return $question_id;
104 - }
105 -
106 -
107 - public function update( &$question ) {
108 - return $question;
109 - // TODO: Implement update() method.
110 - }
111 -
112 - /**
113 - * Delete all question's related data before run wp_delete_post(), hook to before delete question hook.
114 - *
115 - * @since 3.0.0
116 - *
117 - * @param object $question_id
118 - */
119 - public function delete( &$question_id ) {
120 - // remove all answer of question from {$wpdb->prefix}learnpress_question_answers table
121 - $this->clear( $question_id );
122 -
123 - // quiz curd
124 - $curd = new LP_Quiz_CURD();
125 -
126 - // allow hook
127 - do_action( 'learn-press/before-delete-question', $question_id );
128 -
129 - // get the quizzes that a question is assigned to, return WP Post
130 - $quiz = $this->get_quiz( $question_id );
131 -
132 - // remove question from quiz
133 - if ( $quiz ) {
134 - $curd->remove_questions( $quiz->ID, $question_id );
135 - }
136 - }
137 -
138 - /**
139 - * Duplicate question.
140 - *
141 - * @since 3.0.0
142 - *
143 - * @param $question_id
144 - * @param array $args
145 - *
146 - * @return mixed|WP_Error
147 - */
148 - public function duplicate( &$question_id, $args = array() ) {
149 - if ( ! $question_id ) {
150 - return new WP_Error( 'lp/question/curd/duplicate/err', 'Oops! ID not found' );
151 - }
152 -
153 - if ( learn_press_get_post_type( $question_id ) != LP_QUESTION_CPT ) {
154 - return new WP_Error( 'lp/question/curd/duplicate/err', 'Op! The question does not exist' );
155 - }
156 -
157 - // ensure that user can create question
158 - if ( ! current_user_can( 'edit_posts' ) ) {
159 - return new WP_Error( 'lp/question/curd/duplicate/err', 'Sorry! You do not have permission to duplicate this question' );
160 - }
161 -
162 - // origin question
163 - $question = LP_Question::get_question( $question_id );
164 -
165 - // duplicate question
166 - $new_question_id = learn_press_duplicate_post( $question_id, array( 'post_status' => 'publish' ) );
167 -
168 - if ( ! $new_question_id || is_wp_error( $new_question_id ) ) {
169 - return new WP_Error( 'lp/question/curd/duplicate/err', 'Sorry! Failed to duplicate the question!' );
170 - } else {
171 -
172 - // init new question
173 - $new_question = LP_Question::get_question( $new_question_id );
174 -
175 - // set data
176 - $new_question->set_type( $question->get_type() );
177 - $new_question->set_data( 'answer_options', $question->get_data( 'answer_options' ) );
178 -
179 - // trigger change user memorize question types
180 - $user_id = get_current_user_id();
181 - update_user_meta( $user_id, '_learn_press_memorize_question_types', $new_question->get_type() );
182 -
183 - // duplicate answer
184 - $this->duplicate_answer( $question_id, $new_question_id );
185 -
186 - do_action( 'learn-press/item/after-duplicate', $question_id, $new_question_id, $args );
187 -
188 - return $new_question_id;
189 - }
190 - }
191 -
192 - /**
193 - * Duplicate answer question.
194 - *
195 - * @param $question_id | origin question
196 - * @param $question_id_clone | new question
197 - * @version 3.0.1
198 - * @since 1.0.1
199 - */
200 - public function duplicate_answer( $question_id, $question_id_clone ) {
201 - $lp_db = LP_Database::getInstance();
202 - $lp_question_answers_db = LP_Question_Answers_DB::getInstance();
203 -
204 - try {
205 - // Get all answer of question
206 - $filter_get_answer_options = new LP_Question_Answers_Filter();
207 - $filter_get_answer_options->question_ids = [ $question_id ];
208 - $answer_options = $lp_question_answers_db->get_question_answers( $filter_get_answer_options );
209 -
210 - if ( $answer_options ) {
211 - foreach ( $answer_options as $answer_option ) {
212 - $question_answer_id = $answer_option->question_answer_id;
213 -
214 - // Insert new question_answer
215 - $insert_question_answer_rs = $lp_db->wpdb->insert(
216 - $lp_db->tb_lp_question_answers,
217 - array(
218 - 'question_id' => $question_id_clone,
219 - 'title' => ! empty( $answer_option->title ) ? $answer_option->title : '',
220 - 'value' => ! empty( $answer_option->value ) ? $answer_option->value : '',
221 - 'is_true' => ! empty( $answer_option->is_true ) ? $answer_option->is_true : '',
222 - 'order' => $answer_option->order,
223 - ),
224 - array( '%d', '%s', '%s', '%s', '%s' )
225 - );
226 -
227 - if ( ! $insert_question_answer_rs ) {
228 - throw new Exception( __( 'Failed to duplicate answer', 'learnpress' ) );
229 - }
230 -
231 - // Get question_answer_id have just inserted
232 - $filter_question_answer_id = new LP_Question_Answers_Filter();
233 - $filter_question_answer_id->only_fields = [ 'MAX(question_answer_id)' ];
234 - $filter_question_answer_id->question_ids = [ $question_id_clone ];
235 - $filter_question_answer_id->return_string_query = true;
236 - $question_answer_id_query = $lp_question_answers_db->get_question_answers( $filter_question_answer_id );
237 - $question_answer_id_new = (int) $lp_question_answers_db->wpdb->get_var( $question_answer_id_query );
238 -
239 - if ( ! $question_answer_id_new ) {
240 - throw new Exception( __( 'Failed to duplicate answer', 'learnpress' ) );
241 - }
242 -
243 - // Duplicate answer meta
244 - // Get answer meta by question_answer_id
245 - $filter_get = new LP_Question_Answermeta_Filter();
246 - $filter_get->collection = $lp_db->tb_lp_question_answermeta;
247 - $filter_get->collection_alias = 'qam';
248 - $filter_get->field_count = 'meta_id';
249 - $filter_get->limit = -1;
250 - $filter_get->where[] = $lp_db->wpdb->prepare( 'AND qam.learnpress_question_answer_id = %d', $question_answer_id );
251 - $filter_get->fields = $lp_db->get_cols_of_table( $lp_db->tb_lp_question_answermeta );
252 - $question_answermeta_rs = $lp_db->execute( $filter_get );
253 -
254 - foreach ( $question_answermeta_rs as $question_answermeta ) {
255 - $lp_db->wpdb->insert(
256 - $lp_db->tb_lp_question_answermeta,
257 - array(
258 - 'learnpress_question_answer_id' => $question_answer_id_new,
259 - 'meta_key' => $question_answermeta->meta_key,
260 - 'meta_value' => $question_answermeta->meta_value,
261 - ),
262 - array( '%d', '%s', '%s' )
263 - );
264 - }
265 - }
266 - }
267 - } catch ( Throwable $e ) {
268 - error_log( $e->getMessage() );
269 - }
270 - }
271 -
272 - /**
273 - * @param LP_Question $question
274 - *
275 - * @return bool
276 - * @throws Exception
277 - */
278 - public function load( &$question ) {
279 - // question id
280 - $id = $question->get_id();
281 -
282 - if ( ! $id || ! in_array( learn_press_get_post_type( $id ), array( 'revision', LP_QUESTION_CPT ) ) ) {
283 - throw new Exception( sprintf( __( 'Invalid question with ID "%d".', 'learnpress' ), $id ) );
284 - }
285 -
286 - $question->set_data_via_methods(
287 - array(
288 - 'explanation' => get_post_meta( $id, '_lp_explanation', true ),
289 - 'hint' => get_post_meta( $id, '_lp_hint', true ),
290 - )
291 - );
292 - // $this->_load_answer_options( $question );
293 - $this->_load_meta( $question );
294 -
295 - return true;
296 - }
297 -
298 - /**
299 - * @param $question | LP_Question
300 - */
301 - protected function _load_meta( &$question ) {
302 - $type = get_post_meta( $question->get_id(), '_lp_type', true );
303 - if ( ! learn_press_is_support_question_type( $type ) ) {
304 - $type = 'true_or_false';
305 - }
306 - // $question->set_type( $type );
307 -
308 - $mark = $this->_get_question_mark( $question->get_id() );
309 - $question->set_data_via_methods(
310 - array(
311 - 'mark' => $mark,
312 - )
313 - );
314 - }
315 -
316 - public function _get_question_mark( $question_id ) {
317 -
318 - // Recheck _lp_mark @tungnx
319 - $mark = get_post_meta( $question_id, '_lp_mark', true ) ? get_post_meta( $question_id, '_lp_mark', true ) : 0;
320 -
321 - $mark = abs( $mark );
322 - if ( ! $mark ) {
323 - $mark = apply_filters( 'learn-press/question/default-mark', 1, $question_id );
324 - update_post_meta( $question_id, '_lp_mark', $mark );
325 - }
326 -
327 - return $mark;
328 - }
329 -
330 - /**
331 - * Get the quizzes that a question is assigned to.
332 - *
333 - * @since 3.0.0
334 - *
335 - * @param $question_id
336 - *
337 - * @return null|object WP_Post
338 - */
339 - public function get_quiz( $question_id ) {
340 - global $wpdb;
341 -
342 - $query = $wpdb->prepare(
343 - "
344 - SELECT post.* FROM {$wpdb->posts} post
345 - INNER JOIN {$wpdb->prefix}learnpress_quiz_questions quiz ON post.ID = quiz.quiz_id
346 - WHERE quiz.question_id = %d
347 - ",
348 - $question_id
349 - );
350 -
351 - // get single row
352 - return $wpdb->get_row( $query );
353 - }
354 -
355 - /**
356 - * Change question type.
357 - *
358 - * @param $question LP_Question
359 - * @param $new_type
360 - *
361 - * @return false|void
362 - * @since 3.0.0
363 - * @version 1.0.1
364 - */
365 - public function change_question_type( &$question, $new_type ) {
366 - if ( learn_press_get_post_type( $question->get_id() ) != LP_QUESTION_CPT ) {
367 - return false;
368 - }
369 -
370 - $question_id = $question->get_id();
371 -
372 - // If not new Question or not change type return
373 - $old_type = get_post_meta( $question_id, '_lp_type', true );
374 - if ( ! empty( $old_type ) && $old_type === $new_type ) {
375 - return;
376 - }
377 -
378 - $answer_options = $question->get_data( 'answer_options' );
379 - update_post_meta( $question_id, '_lp_type', $new_type );
380 - $question->set_type( $new_type );
381 -
382 - $new_question = LP_Question::get_question( $question_id, array( 'force' => true ) );
383 -
384 - if ( $new_question ) {
385 - $user_id = get_current_user_id();
386 - update_user_meta( $user_id, '_learn_press_memorize_question_types', $new_type );
387 -
388 - if ( $old_type == 'multi_choice' && $new_type == 'single_choice' ) {
389 - $func = '_convert_answers_multi_choice_to_single_choice';
390 - } elseif ( ( $old_type == 'multi_choice' || $old_type == 'single_choice' ) && 'true_or_false' == $new_type ) {
391 - $func = '_convert_answers_to_true_or_false';
392 - } else {
393 - // for rest, clear answer data and create default
394 - $func = '_convert_default_answers';
395 - }
396 -
397 - if ( is_callable( array( $this, $func ) ) ) {
398 - $answer_options = call_user_func_array(
399 - array( $this, $func ),
400 - array(
401 - $question,
402 - $new_question,
403 - $answer_options,
404 - )
405 - );
406 - }
407 -
408 - LP_Object_Cache::set( 'answer-options-' . $question_id, $answer_options, 'learn-press/questions' );
409 - $new_question->set_data( 'answer_options', $answer_options );
410 -
411 - $question = $new_question;
412 - }
413 - }
414 -
415 - /**
416 - * Update answer title
417 - *
418 - * @param $question_id
419 - * @param $answer
420 - *
421 - * @return bool|false|int
422 - */
423 - public function update_answer_title( $question_id, $answer ) {
424 - if ( get_post_type( $question_id ) !== LP_QUESTION_CPT ) {
425 - return false;
426 - }
427 -
428 - global $wpdb;
429 -
430 - // question data
431 - $data = array(
432 - 'data' => apply_filters(
433 - 'learn-press/question/update-answer-data',
434 - array(
435 - 'title' => $answer['title'],
436 - 'value' => $answer['value'] ?? '',
437 - 'is_true' => $answer['is_true'] ?? '',
438 - )
439 - ),
440 - 'where' => array(
441 - 'question_answer_id' => $answer['question_answer_id'],
442 - 'question_id' => $question_id,
443 - ),
444 - );
445 -
446 - $update = $wpdb->update(
447 - $wpdb->learnpress_question_answers,
448 - $data['data'],
449 - $data['where'],
450 - array( '%s', '%s', '%s' ),
451 - array( '%d', '%d' )
452 - );
453 -
454 - // Update for Fill in Blanks.
455 - if ( ! empty( $answer['blanks'] ) ) {
456 - $blanks = $answer['blanks'];
457 -
458 - /*if ( is_array( $blanks ) ) {
459 - $question = LP_Question::get_question( $question_id );
460 -
461 - foreach ( $blanks as $id => $blank ) {
462 - $question->_blanks[ $blank['id'] ] = $blank;
463 - }
464 - }*/
465 -
466 - learn_press_update_question_answer_meta( $answer['question_answer_id'], '_blanks', $blanks );
467 - }
468 -
469 - return $update;
470 - }
471 -
472 - /**
473 - * Update correct answer.
474 - *
475 - * @param $question LP_Question
476 - * @param $correct
477 - *
478 - * @return bool | LP_Question
479 - */
480 - public function change_correct_answer( $question, $correct ) {
481 - if ( learn_press_get_post_type( $question->get_id() ) != LP_QUESTION_CPT ) {
482 - return false;
483 - }
484 -
485 - global $wpdb;
486 -
487 - $question_id = $question->get_id();
488 - $question_type = $question->get_type();
489 - $question_answers = $question->get_data( 'answer_options' );
490 -
491 - $db_args = $answers = array();
492 -
493 - foreach ( $question_answers as $index => $answer ) {
494 -
495 - $answer_data = array(
496 - 'title' => $answer['title'],
497 - 'value' => isset( $answer['value'] ) ? stripslashes( $answer['value'] ) : '',
498 - 'is_true' => isset( $answer['is_true'] ) ? $answer['is_true'] : '',
499 - );
500 -
501 - // update correct for select answer
502 - if ( $answer['question_answer_id'] == $correct['question_answer_id'] ) {
503 - $answer_data['is_true'] = $correct['is_true'];
504 - } else {
505 - // untrue all rest answer with True or false and Single choice question
506 - if ( in_array( $question_type, array( 'true_or_false', 'single_choice' ) ) ) {
507 - $answer_data['is_true'] = '';
508 - }
509 - }
510 -
511 - // question answers data to set cache
512 - $answers[ $index ] = array(
513 - 'question_answer_id' => $answer['question_answer_id'],
514 - 'question_id' => $question_id,
515 - 'order' => $answer['order'],
516 - 'title' => $answer_data['title'],
517 - 'value' => $answer_data['value'],
518 - 'is_true' => $answer_data['is_true'],
519 - );
520 -
521 - // new answers data
522 - $db_args[ $index ] = array(
523 - 'data' => array(
524 - 'title' => $answer_data['title'],
525 - 'value' => $answer_data['value'],
526 - 'is_true' => $answer_data['is_true'],
527 - ),
528 - 'where' => array(
529 - 'question_answer_id' => $answer['question_answer_id'],
530 - 'question_id' => $question_id,
531 - 'order' => $answer['order'],
532 - ),
533 - );
534 - }
535 -
536 - // update db
537 - foreach ( $db_args as $id => $arg ) {
538 - $wpdb->update(
539 - $wpdb->learnpress_question_answers,
540 - $arg['data'],
541 - $arg['where'],
542 - array( '%s', '%s', '%s' ),
543 - array( '%d', '%d', '%d' )
544 - );
545 - }
546 -
547 - // set question answer data
548 - $question->set_data( 'answer_options', $answers );
549 -
550 - return $question;
551 - }
552 -
553 - /**
554 - * Sort answers.
555 - *
556 - * @since 3.0.0
557 - *
558 - * @param $question_id
559 - * @param array $order
560 - *
561 - * @return bool|LP_Question
562 - */
563 - public function sort_answers( $question_id, $order = array() ) {
564 -
565 - if ( learn_press_get_post_type( $question_id ) !== LP_QUESTION_CPT ) {
566 - return false;
567 - }
568 -
569 - if ( $order ) {
570 - $question = LP_Question::get_question( $question_id );
571 - $answers = $question->get_data( 'answer_options' );
572 -
573 - global $wpdb;
574 - $new_answers = array();
575 - foreach ( $order as $index => $answer_id ) {
576 - $wpdb->update(
577 - $wpdb->learnpress_question_answers,
578 - array( 'order' => $index + 1 ),
579 - array( 'question_answer_id' => $answer_id )
580 - );
581 -
582 - $new_answers[ $answer_id ] = $answers[ $answer_id ];
583 - }
584 -
585 - $question->set_data( 'answer_options', $new_answers );
586 -
587 - return $question;
588 -
589 - }
590 -
591 - return false;
592 - }
593 -
594 - /**
595 - * Delete question answer.
596 - *
597 - * @param $question_id
598 - * @param $answer_id
599 - * @param $force
600 - *
601 - * @return bool|false|int
602 - */
603 - public function delete_answer( $question_id, $answer_id, $force = false ) {
604 -
605 - if ( learn_press_get_post_type( $question_id ) !== LP_QUESTION_CPT ) {
606 - return false;
607 - }
608 -
609 - $question = LP_Question::get_question( $question_id );
610 -
611 - // exist answer options
612 - $answers = $question->get_data( 'answer_options' );
613 -
614 - global $wpdb;
615 -
616 - // Delete answer meta
617 - $wpdb->delete(
618 - $wpdb->learnpress_question_answermeta,
619 - array( 'learnpress_question_answer_id' => $answer_id )
620 - );
621 -
622 - $delete = $wpdb->delete(
623 - $wpdb->learnpress_question_answers,
624 - array( 'question_answer_id' => $answer_id )
625 - );
626 - if ( $delete ) {
627 - unset( $answers[ $answer_id ] );
628 - $question->set_data( 'answer_options', $answers );
629 -
630 - $this->sort_answers( $question_id, array_keys( $answers ) );
631 - }
632 -
633 - return $delete;
634 - }
635 -
636 - /**
637 - * Add new answer.
638 - *
639 - * @param $question_id
640 - * @param $new_answer
641 - *
642 - * @return bool|false|int
643 - */
644 - public function new_answer( $question_id, $new_answer ) {
645 - if ( learn_press_get_post_type( $question_id ) !== LP_QUESTION_CPT ) {
646 - return false;
647 - }
648 -
649 - $question = LP_Question::get_question( $question_id );
650 -
651 - // exist answer options
652 - $answers = $question->get_data( 'answer_options' );
653 - // number answer options
654 - $number = count( $answers );
655 -
656 - if ( $question->get_type() === 'sorting_choice' ) {
657 - $new_answer['is_true'] = 'yes';
658 - }
659 -
660 - global $wpdb;
661 -
662 - $new_answer = wp_parse_args(
663 - $new_answer,
664 - array(
665 - 'question_id' => $question_id,
666 - 'title' => '',
667 - 'value' => learn_press_random_value(),
668 - 'is_true' => '',
669 - 'order' => $number + 1,
670 - )
671 - );
672 -
673 - $insert = $wpdb->insert(
674 - $wpdb->learnpress_question_answers,
675 - $new_answer,
676 - learn_press_map_columns_format(
677 - $new_answer,
678 - array(
679 - 'question_id' => '%d',
680 - 'title' => '%s',
681 - 'value' => '%s',
682 - 'is_true' => '%s',
683 - 'order' => '%d',
684 - )
685 - )
686 - );
687 -
688 - if ( $insert ) {
689 - $new_answer['question_answer_id'] = $wpdb->insert_id;
690 -
691 - if ( is_array( $answers ) ) {
692 - $answers = array_merge( $answers, array( $new_answer ) );
693 - } else {
694 - $answers = array( $new_answer );
695 - }
696 - $question->set_data( 'answer_options', $answers );
697 - }
698 -
699 - return $wpdb->insert_id;
700 - }
701 -
702 -
703 - /**
704 - * Convert answers to true or false question.
705 - *
706 - * @since 3.0.0
707 - *
708 - * @param $question
709 - * @param $new_question
710 - * @param $answers
711 - *
712 - * @return mixed
713 - */
714 - protected function _convert_answers_to_true_or_false( $question, $new_question, $answers ) {
715 - if ( is_array( $answers ) ) {
716 - // array answer ids
717 - $answer_ids = array_keys( $answers );
718 -
719 - if ( sizeof( $answers ) > 2 ) {
720 - global $wpdb;
721 -
722 - foreach ( $answers as $key => $answer ) {
723 - if ( array_search( $key, $answer_ids ) > 1 ) {
724 - $wpdb->delete(
725 - $wpdb->learnpress_question_answers,
726 - array( 'question_answer_id' => $answer['question_answer_id'] )
727 - );
728 - }
729 - }
730 - $answers = array_slice( $answers, 0, 2, true );
731 - }
732 -
733 - $correct = 0;
734 - foreach ( $answers as $key => $answer ) {
735 - if ( $answer['is_true'] == 'yes' ) {
736 - $correct += 1;
737 - }
738 - }
739 -
740 - if ( ! $correct ) {
741 - // for single choice deletes all correct, set first option is correct
742 - $answers[ $answer_ids[0] ]['is_true'] = 'yes';
743 - } elseif ( $correct == 2 ) {
744 - // for multiple choice keeps all correct, remove all correct and keep first option
745 - $answers[ $answer_ids[1] ]['is_true'] = 'no';
746 - }
747 - }
748 -
749 - return $answers;
750 - }
751 -
752 - /**
753 - *
754 - * Convert answers for multi choice to single choice question.
755 - *
756 - * @since 3.0.0
757 - *
758 - * @param $question
759 - * @param $new_question
760 - * @param $answers
761 - *
762 - * @return array
763 - */
764 - protected function _convert_answers_multi_choice_to_single_choice( $question, $new_question, $answers ) {
765 - if ( is_array( $answers ) ) {
766 - // array answer ids
767 - $answer_ids = array_keys( $answers );
768 -
769 - $correct = 0;
770 - foreach ( $answers as $key => $answer ) {
771 - if ( $answer['is_true'] == 'yes' ) {
772 - $correct += 1;
773 - }
774 - }
775 -
776 - if ( ! $correct ) {
777 - $answers[ $answer_ids[0] ]['is_true'] = 'yes';
778 - } elseif ( $correct > 1 ) {
779 - // remove all correct and keep first option
780 - $answers[ $answer_ids[0] ]['is_true'] = 'no';
781 - }
782 - }
783 -
784 - return $answers;
785 - }
786 -
787 - /**
788 - * Convert default answers.
789 - *
790 - * @param $question LP_Question
791 - * @param $new_question LP_Question
792 - * @param $answers
793 - *
794 - * @return array
795 - */
796 - protected function _convert_default_answers( $question, $new_question, $answers ) {
797 - $question_id = $question->get_id();
798 - // clear all exists answer
799 - $this->clear( $question_id );
800 - // set default answer
801 - $answer_options = $new_question->get_default_answers();
802 -
803 - if ( is_array( $answer_options ) ) {
804 - foreach ( $answer_options as $index => $answer ) {
805 - $insert = array(
806 - 'question_id' => $question_id,
807 - 'title' => $answer['title'],
808 - 'value' => isset( $answer['value'] ) ? stripslashes( $answer['value'] ) : '',
809 - 'is_true' => ( $answer['is_true'] == 'yes' ) ? $answer['is_true'] : '',
810 - 'order' => $index + 1,
811 - );
812 - $new_answers[] = $this->add_answer( $new_question->get_type(), $insert );
813 - };
814 -
815 - return $new_answers;
816 - }
817 -
818 - return $answers;
819 - }
820 -
821 - /**
822 - * Add question answer.
823 - *
824 - * @since 3.0.0
825 - *
826 - * @param string $question_type
827 - * @param array $args
828 - *
829 - * @return array|bool
830 - */
831 - public function add_answer( $question_type = '', $args = array() ) {
832 - global $wpdb;
833 -
834 - $question = LP_Question::get_question( $args['question_id'], array( 'type' => $question_type ) );
835 -
836 - $wpdb->insert(
837 - $wpdb->learnpress_question_answers,
838 - array(
839 - 'question_id' => $args['question_id'],
840 - 'title' => $args['title'],
841 - 'value' => $args['value'],
842 - 'is_true' => $args['is_true'],
843 - 'order' => $args['order'],
844 - ),
845 - array( '%d', '%s', '%s', '%s', '%d' )
846 - );
847 -
848 - $question_answer_id = $wpdb->insert_id;
849 - if ( $question_answer_id ) {
850 - // update question answer option data
851 - $answer_options = $question->get_data( 'answer_options' ) ? $question->get_data( 'answer_options' ) : array();
852 -
853 - $new_answer_option_data = array(
854 - 'question_answer_id' => $question_answer_id,
855 - 'question_id' => $args['question_id'],
856 - 'order' => $args['order'],
857 - 'title' => $args['title'],
858 - 'value' => $args['value'],
859 - 'is_true' => $args['is_true'],
860 - );
861 -
862 - if ( ! $answer_options ) {
863 - $question->set_data( 'answer_options', array( $new_answer_option_data ) );
864 - } else {
865 - $answer_options[] = $new_answer_option_data;
866 - $question->set_data( 'answer_options', $answer_options );
867 - }
868 -
869 - return $new_answer_option_data;
870 - } else {
871 - return false;
872 - }
873 - }
874 -
875 - /**
876 - * Delete question answer.
877 - *
878 - * @since 3.0.0
879 - *
880 - * @param $question_id
881 - * @param $answer_id
882 - *
883 - * @return bool|false|int
884 - */
885 - public function delete_question_answer( $question_id, $answer_id ) {
886 - if ( learn_press_get_post_type( $question_id ) !== LP_QUESTION_CPT || ! $answer_id ) {
887 - return false;
888 - }
889 -
890 - global $wpdb;
891 -
892 - $result = $wpdb->delete(
893 - $wpdb->learnpress_question_answers,
894 - array( 'question_answer_id' => $answer_id )
895 - );
896 -
897 - return $result;
898 - }
899 -
900 - /**
901 - * Delete all question answers.
902 - *
903 - * @param $question_id
904 - *
905 - * @return bool|WP_Error
906 - */
907 - public function clear( $question_id ) {
908 -
909 - if ( ! learn_press_get_question( $question_id ) ) {
910 - return $this->get_error( 'QUESTION_NOT_EXISTS' );
911 - }
912 -
913 - do_action( 'learn-press/before-clear-question', $question_id );
914 -
915 - global $wpdb;
916 - $wpdb->delete( $wpdb->learnpress_question_answers, array( 'question_id' => $question_id ) );
917 -
918 - return true;
919 - }
920 -
921 - /**
922 - * Load answer options for the question from database.
923 - * Load from cache if data is already loaded into cache.
924 - * Otherwise, load from database and put to cache.
925 - *
926 - * @param int $question_id
927 - *
928 - * @return array
929 - */
930 - protected function _read_answers( $question_id ) {
931 - global $wpdb;
932 -
933 - $query = $wpdb->prepare(
934 - "
935 - SELECT question_answer_id, title, value, is_true
936 - FROM {$wpdb->prefix}learnpress_question_answers
937 - WHERE question_id = %d
938 - ORDER BY `order` ASC
939 - ",
940 - $question_id
941 - );
942 -
943 - $answer_options = array();
944 -
945 - $results = $wpdb->get_results( $query );
946 -
947 - if ( $results ) {
948 - foreach ( $results as $k => $v ) {
949 - $answer_option = array(
950 - 'question_answer_id' => absint( $v->question_answer_id ),
951 - 'title' => $v->title,
952 - 'value' => $v->value,
953 - 'is_true' => $v->is_true,
954 - 'order' => $k + 1, // Need???
955 - );
956 -
957 - $answer_options[ $v->question_answer_id ] = $answer_option;
958 - }
959 - }
960 -
961 - return $answer_options;
962 - }
963 -
964 - /**
965 - * Load question answers
966 - *
967 - * @updated 3.1.0
968 - *
969 - * @param array|int $question_id
970 - *
971 - * @return array|bool
972 - */
973 - public function load_answer_options( $question_id ) {
974 - global $wpdb;
975 -
976 - $return_id = 0;
977 -
978 - if ( is_array( $question_id ) ) {
979 -
980 - foreach ( $question_id as $q_id ) {
981 - $this->load_answer_options( $q_id );
982 - if ( ! $return_id ) {
983 - $return_id = $q_id;
984 - }
985 - }
986 - $question_id = $return_id;
987 - }
988 -
989 - $answer_options = LP_Object_Cache::get( 'question-' . $question_id, 'question-answers' );
990 -
991 - if ( false === $answer_options ) {
992 - $answer_options = $this->_read_answers( $question_id );
993 - LP_Object_Cache::set( 'question-' . $question_id, $answer_options, 'question-answers' );
994 - }
995 -
996 - return $answer_options;
997 - }
998 - }
999 -}
1 +<?php
2 +
3 +/**
4 + * Class LP_Question_CURD
5 + *
6 + * @author ThimPress
7 + * @package LearnPress/Classes/CURD
8 + * @since 3.0.0
9 + */
10 +
11 +/**
12 + * Prevent loading this file directly
13 + */
14 +defined( 'ABSPATH' ) || exit();
15 +
16 +if ( ! class_exists( 'LP_Question_CURD' ) ) {
17 +
18 + /**
19 + * Class LP_Question_CURD
20 + */
21 + class LP_Question_CURD extends LP_Object_Data_CURD implements LP_Interface_CURD {
22 +
23 + /**
24 + * LP_Question_CURD constructor.
25 + */
26 + public function __construct() {
27 + $this->_error_messages = array(
28 + 'QUESTION_NOT_EXISTS' => __( 'The question does not exist.', 'learnpress' ),
29 + );
30 + }
31 +
32 + /**
33 + * Create question and can add to quiz.
34 + *
35 + * @param $args
36 + *
37 + * @return bool|int|LP_Question|WP_Error
38 + */
39 + public function create( &$args ) {
40 + $args = wp_parse_args(
41 + $args,
42 + array(
43 + 'quiz_id' => 0,
44 + 'order' => - 1,
45 + 'id' => '',
46 + 'status' => 'publish',
47 + 'type' => 'true_or_false',
48 + 'title' => __( 'New Question', 'learnpress' ),
49 + 'content' => '',
50 + 'create_answers' => true,
51 + )
52 + );
53 +
54 + // set question author for author of quiz
55 + if ( ! empty( $args['quiz_id'] ) ) {
56 + $user_id = get_post_field( 'post_author', $args['quiz_id'] );
57 + } else {
58 + $user_id = learn_press_get_current_user_id();
59 + }
60 +
61 + $question_id = wp_insert_post(
62 + array(
63 + 'ID' => $args['id'],
64 + 'post_author' => $user_id,
65 + 'post_type' => LP_QUESTION_CPT,
66 + 'post_status' => $args['status'],
67 + 'post_title' => $args['title'],
68 + 'post_content' => $args['content'],
69 + )
70 + );
71 +
72 + if ( $question_id ) {
73 + // add default meta for new lesson
74 + $default_meta = LP_Question::get_default_meta();
75 +
76 + if ( is_array( $default_meta ) ) {
77 + foreach ( $default_meta as $key => $value ) {
78 + update_post_meta( $question_id, '_lp_' . $key, $value );
79 + }
80 + }
81 + update_post_meta( $question_id, '_lp_type', $args['type'] );
82 + // update user memory question types
83 + get_user_meta( $user_id, '_learn_press_memorize_question_types', $args['type'] );
84 +
85 + $question = LP_Question::get_question( $question_id, array( 'type' => $args['type'] ) );
86 + $question->set_type( $args['type'] );
87 +
88 + // add question to quiz
89 + if ( ! empty( $args['quiz_id'] ) ) {
90 + $quiz_curd = new LP_Quiz_CURD();
91 + $quiz_curd->add_question( $args['quiz_id'], $question_id, $args['order'] );
92 + }
93 +
94 + if ( $args['create_answers'] ) {
95 + $question->create_default_answers();
96 + }
97 +
98 + do_action( 'learn-press/after-create-question', $question );
99 +
100 + return $question;
101 + }
102 +
103 + return $question_id;
104 + }
105 +
106 +
107 + public function update( &$question ) {
108 + return $question;
109 + // TODO: Implement update() method.
110 + }
111 +
112 + /**
113 + * Delete all question's related data before run wp_delete_post(), hook to before delete question hook.
114 + *
115 + * @since 3.0.0
116 + *
117 + * @param object $question_id
118 + */
119 + public function delete( &$question_id ) {
120 + // remove all answer of question from {$wpdb->prefix}learnpress_question_answers table
121 + $this->clear( $question_id );
122 +
123 + // quiz curd
124 + $curd = new LP_Quiz_CURD();
125 +
126 + // allow hook
127 + do_action( 'learn-press/before-delete-question', $question_id );
128 +
129 + // get the quizzes that a question is assigned to, return WP Post
130 + $quiz = $this->get_quiz( $question_id );
131 +
132 + // remove question from quiz
133 + if ( $quiz ) {
134 + $curd->remove_questions( $quiz->ID, $question_id );
135 + }
136 + }
137 +
138 + /**
139 + * Duplicate question.
140 + *
141 + * @since 3.0.0
142 + *
143 + * @param $question_id
144 + * @param array $args
145 + *
146 + * @return mixed|WP_Error
147 + */
148 + public function duplicate( &$question_id, $args = array() ) {
149 + if ( ! $question_id ) {
150 + return new WP_Error( 'lp/question/curd/duplicate/err', 'Oops! ID not found' );
151 + }
152 +
153 + if ( learn_press_get_post_type( $question_id ) != LP_QUESTION_CPT ) {
154 + return new WP_Error( 'lp/question/curd/duplicate/err', 'Op! The question does not exist' );
155 + }
156 +
157 + // ensure that user can create question
158 + if ( ! current_user_can( 'edit_posts' ) ) {
159 + return new WP_Error( 'lp/question/curd/duplicate/err', 'Sorry! You do not have permission to duplicate this question' );
160 + }
161 +
162 + // origin question
163 + $question = LP_Question::get_question( $question_id );
164 +
165 + // duplicate question
166 + $new_question_id = learn_press_duplicate_post( $question_id, array( 'post_status' => 'publish' ) );
167 +
168 + if ( ! $new_question_id || is_wp_error( $new_question_id ) ) {
169 + return new WP_Error( 'lp/question/curd/duplicate/err', 'Sorry! Failed to duplicate the question!' );
170 + } else {
171 +
172 + // init new question
173 + $new_question = LP_Question::get_question( $new_question_id );
174 +
175 + // set data
176 + $new_question->set_type( $question->get_type() );
177 + $new_question->set_data( 'answer_options', $question->get_data( 'answer_options' ) );
178 +
179 + // trigger change user memorize question types
180 + $user_id = get_current_user_id();
181 + update_user_meta( $user_id, '_learn_press_memorize_question_types', $new_question->get_type() );
182 +
183 + // duplicate answer
184 + $this->duplicate_answer( $question_id, $new_question_id );
185 +
186 + do_action( 'learn-press/item/after-duplicate', $question_id, $new_question_id, $args );
187 +
188 + return $new_question_id;
189 + }
190 + }
191 +
192 + /**
193 + * Duplicate answer question.
194 + *
195 + * @param $question_id | origin question
196 + * @param $question_id_clone | new question
197 + * @version 3.0.1
198 + * @since 1.0.1
199 + */
200 + public function duplicate_answer( $question_id, $question_id_clone ) {
201 + $lp_db = LP_Database::getInstance();
202 + $lp_question_answers_db = LP_Question_Answers_DB::getInstance();
203 +
204 + try {
205 + // Get all answer of question
206 + $filter_get_answer_options = new LP_Question_Answers_Filter();
207 + $filter_get_answer_options->question_ids = [ $question_id ];
208 + $answer_options = $lp_question_answers_db->get_question_answers( $filter_get_answer_options );
209 +
210 + if ( $answer_options ) {
211 + foreach ( $answer_options as $answer_option ) {
212 + $question_answer_id = $answer_option->question_answer_id;
213 +
214 + // Insert new question_answer
215 + $insert_question_answer_rs = $lp_db->wpdb->insert(
216 + $lp_db->tb_lp_question_answers,
217 + array(
218 + 'question_id' => $question_id_clone,
219 + 'title' => ! empty( $answer_option->title ) ? $answer_option->title : '',
220 + 'value' => ! empty( $answer_option->value ) ? $answer_option->value : '',
221 + 'is_true' => ! empty( $answer_option->is_true ) ? $answer_option->is_true : '',
222 + 'order' => $answer_option->order,
223 + ),
224 + array( '%d', '%s', '%s', '%s', '%s' )
225 + );
226 +
227 + if ( ! $insert_question_answer_rs ) {
228 + throw new Exception( __( 'Failed to duplicate answer', 'learnpress' ) );
229 + }
230 +
231 + // Get question_answer_id have just inserted
232 + $filter_question_answer_id = new LP_Question_Answers_Filter();
233 + $filter_question_answer_id->only_fields = [ 'MAX(question_answer_id)' ];
234 + $filter_question_answer_id->question_ids = [ $question_id_clone ];
235 + $filter_question_answer_id->return_string_query = true;
236 + $question_answer_id_query = $lp_question_answers_db->get_question_answers( $filter_question_answer_id );
237 + $question_answer_id_new = (int) $lp_question_answers_db->wpdb->get_var( $question_answer_id_query );
238 +
239 + if ( ! $question_answer_id_new ) {
240 + throw new Exception( __( 'Failed to duplicate answer', 'learnpress' ) );
241 + }
242 +
243 + // Duplicate answer meta
244 + // Get answer meta by question_answer_id
245 + $filter_get = new LP_Question_Answermeta_Filter();
246 + $filter_get->collection = $lp_db->tb_lp_question_answermeta;
247 + $filter_get->collection_alias = 'qam';
248 + $filter_get->field_count = 'meta_id';
249 + $filter_get->limit = -1;
250 + $filter_get->where[] = $lp_db->wpdb->prepare( 'AND qam.learnpress_question_answer_id = %d', $question_answer_id );
251 + $filter_get->fields = $lp_db->get_cols_of_table( $lp_db->tb_lp_question_answermeta );
252 + $question_answermeta_rs = $lp_db->execute( $filter_get );
253 +
254 + foreach ( $question_answermeta_rs as $question_answermeta ) {
255 + $lp_db->wpdb->insert(
256 + $lp_db->tb_lp_question_answermeta,
257 + array(
258 + 'learnpress_question_answer_id' => $question_answer_id_new,
259 + 'meta_key' => $question_answermeta->meta_key,
260 + 'meta_value' => $question_answermeta->meta_value,
261 + ),
262 + array( '%d', '%s', '%s' )
263 + );
264 + }
265 + }
266 + }
267 + } catch ( Throwable $e ) {
268 + error_log( $e->getMessage() );
269 + }
270 + }
271 +
272 + /**
273 + * @param LP_Question $question
274 + *
275 + * @return bool
276 + * @throws Exception
277 + */
278 + public function load( &$question ) {
279 + // question id
280 + $id = $question->get_id();
281 +
282 + if ( ! $id || ! in_array( learn_press_get_post_type( $id ), array( 'revision', LP_QUESTION_CPT ) ) ) {
283 + throw new Exception( sprintf( __( 'Invalid question with ID "%d".', 'learnpress' ), $id ) );
284 + }
285 +
286 + $question->set_explanation( get_post_meta( $id, '_lp_explanation', true ) );
287 + $question->set_hint( get_post_meta( $id, '_lp_hint', true ) );
288 + // $this->_load_answer_options( $question );
289 + $this->_load_meta( $question );
290 +
291 + return true;
292 + }
293 +
294 + /**
295 + * @param $question | LP_Question
296 + */
297 + protected function _load_meta( &$question ) {
298 + $type = get_post_meta( $question->get_id(), '_lp_type', true );
299 + if ( ! learn_press_is_support_question_type( $type ) ) {
300 + $type = 'true_or_false';
301 + }
302 + // $question->set_type( $type );
303 +
304 + $mark = $this->_get_question_mark( $question->get_id() );
305 + $question->set_mark( $mark );
306 + }
307 +
308 + public function _get_question_mark( $question_id ) {
309 +
310 + // Recheck _lp_mark @tungnx
311 + $mark = get_post_meta( $question_id, '_lp_mark', true ) ? get_post_meta( $question_id, '_lp_mark', true ) : 0;
312 +
313 + $mark = abs( $mark );
314 + if ( ! $mark ) {
315 + $mark = apply_filters( 'learn-press/question/default-mark', 1, $question_id );
316 + update_post_meta( $question_id, '_lp_mark', $mark );
317 + }
318 +
319 + return $mark;
320 + }
321 +
322 + /**
323 + * Get the quizzes that a question is assigned to.
324 + *
325 + * @since 3.0.0
326 + *
327 + * @param $question_id
328 + *
329 + * @return null|object WP_Post
330 + */
331 + public function get_quiz( $question_id ) {
332 + global $wpdb;
333 +
334 + $query = $wpdb->prepare(
335 + "
336 + SELECT post.* FROM {$wpdb->posts} post
337 + INNER JOIN {$wpdb->prefix}learnpress_quiz_questions quiz ON post.ID = quiz.quiz_id
338 + WHERE quiz.question_id = %d
339 + ",
340 + $question_id
341 + );
342 +
343 + // get single row
344 + return $wpdb->get_row( $query );
345 + }
346 +
347 + /**
348 + * Change question type.
349 + *
350 + * @param $question LP_Question
351 + * @param $new_type
352 + *
353 + * @return false|void
354 + * @since 3.0.0
355 + * @version 1.0.1
356 + */
357 + public function change_question_type( &$question, $new_type ) {
358 + if ( learn_press_get_post_type( $question->get_id() ) != LP_QUESTION_CPT ) {
359 + return false;
360 + }
361 +
362 + $question_id = $question->get_id();
363 +
364 + // If not new Question or not change type return
365 + $old_type = get_post_meta( $question_id, '_lp_type', true );
366 + if ( ! empty( $old_type ) && $old_type === $new_type ) {
367 + return;
368 + }
369 +
370 + $answer_options = $question->get_data( 'answer_options' );
371 + update_post_meta( $question_id, '_lp_type', $new_type );
372 + $question->set_type( $new_type );
373 +
374 + $new_question = LP_Question::get_question( $question_id, array( 'force' => true ) );
375 +
376 + if ( $new_question ) {
377 + $user_id = get_current_user_id();
378 + update_user_meta( $user_id, '_learn_press_memorize_question_types', $new_type );
379 +
380 + if ( $old_type == 'multi_choice' && $new_type == 'single_choice' ) {
381 + $func = '_convert_answers_multi_choice_to_single_choice';
382 + } elseif ( ( $old_type == 'multi_choice' || $old_type == 'single_choice' ) && 'true_or_false' == $new_type ) {
383 + $func = '_convert_answers_to_true_or_false';
384 + } else {
385 + // for rest, clear answer data and create default
386 + $func = '_convert_default_answers';
387 + }
388 +
389 + if ( is_callable( array( $this, $func ) ) ) {
390 + $answer_options = call_user_func_array(
391 + array( $this, $func ),
392 + array(
393 + $question,
394 + $new_question,
395 + $answer_options,
396 + )
397 + );
398 + }
399 +
400 + LP_Object_Cache::set( 'answer-options-' . $question_id, $answer_options, 'learn-press/questions' );
401 + $new_question->set_data( 'answer_options', $answer_options );
402 +
403 + $question = $new_question;
404 + }
405 + }
406 +
407 + /**
408 + * Update answer title
409 + *
410 + * @param $question_id
411 + * @param $answer
412 + *
413 + * @return bool|false|int
414 + */
415 + public function update_answer_title( $question_id, $answer ) {
416 + if ( get_post_type( $question_id ) !== LP_QUESTION_CPT ) {
417 + return false;
418 + }
419 +
420 + global $wpdb;
421 +
422 + // question data
423 + $data = array(
424 + 'data' => apply_filters(
425 + 'learn-press/question/update-answer-data',
426 + array(
427 + 'title' => $answer['title'],
428 + 'value' => $answer['value'] ?? '',
429 + 'is_true' => $answer['is_true'] ?? '',
430 + )
431 + ),
432 + 'where' => array(
433 + 'question_answer_id' => $answer['question_answer_id'],
434 + 'question_id' => $question_id,
435 + ),
436 + );
437 +
438 + $update = $wpdb->update(
439 + $wpdb->learnpress_question_answers,
440 + $data['data'],
441 + $data['where'],
442 + array( '%s', '%s', '%s' ),
443 + array( '%d', '%d' )
444 + );
445 +
446 + // Update for Fill in Blanks.
447 + if ( ! empty( $answer['blanks'] ) ) {
448 + $blanks = $answer['blanks'];
449 +
450 + /*if ( is_array( $blanks ) ) {
451 + $question = LP_Question::get_question( $question_id );
452 +
453 + foreach ( $blanks as $id => $blank ) {
454 + $question->_blanks[ $blank['id'] ] = $blank;
455 + }
456 + }*/
457 +
458 + learn_press_update_question_answer_meta( $answer['question_answer_id'], '_blanks', $blanks );
459 + }
460 +
461 + return $update;
462 + }
463 +
464 + /**
465 + * Update correct answer.
466 + *
467 + * @param $question LP_Question
468 + * @param $correct
469 + *
470 + * @return bool | LP_Question
471 + */
472 + public function change_correct_answer( $question, $correct ) {
473 + if ( learn_press_get_post_type( $question->get_id() ) != LP_QUESTION_CPT ) {
474 + return false;
475 + }
476 +
477 + global $wpdb;
478 +
479 + $question_id = $question->get_id();
480 + $question_type = $question->get_type();
481 + $question_answers = $question->get_data( 'answer_options' );
482 +
483 + $db_args = $answers = array();
484 +
485 + foreach ( $question_answers as $index => $answer ) {
486 +
487 + $answer_data = array(
488 + 'title' => $answer['title'],
489 + 'value' => isset( $answer['value'] ) ? stripslashes( $answer['value'] ) : '',
490 + 'is_true' => isset( $answer['is_true'] ) ? $answer['is_true'] : '',
491 + );
492 +
493 + // update correct for select answer
494 + if ( $answer['question_answer_id'] == $correct['question_answer_id'] ) {
495 + $answer_data['is_true'] = $correct['is_true'];
496 + } else {
497 + // untrue all rest answer with True or false and Single choice question
498 + if ( in_array( $question_type, array( 'true_or_false', 'single_choice' ) ) ) {
499 + $answer_data['is_true'] = '';
500 + }
501 + }
502 +
503 + // question answers data to set cache
504 + $answers[ $index ] = array(
505 + 'question_answer_id' => $answer['question_answer_id'],
506 + 'question_id' => $question_id,
507 + 'order' => $answer['order'],
508 + 'title' => $answer_data['title'],
509 + 'value' => $answer_data['value'],
510 + 'is_true' => $answer_data['is_true'],
511 + );
512 +
513 + // new answers data
514 + $db_args[ $index ] = array(
515 + 'data' => array(
516 + 'title' => $answer_data['title'],
517 + 'value' => $answer_data['value'],
518 + 'is_true' => $answer_data['is_true'],
519 + ),
520 + 'where' => array(
521 + 'question_answer_id' => $answer['question_answer_id'],
522 + 'question_id' => $question_id,
523 + 'order' => $answer['order'],
524 + ),
525 + );
526 + }
527 +
528 + // update db
529 + foreach ( $db_args as $id => $arg ) {
530 + $wpdb->update(
531 + $wpdb->learnpress_question_answers,
532 + $arg['data'],
533 + $arg['where'],
534 + array( '%s', '%s', '%s' ),
535 + array( '%d', '%d', '%d' )
536 + );
537 + }
538 +
539 + // set question answer data
540 + $question->set_data( 'answer_options', $answers );
541 +
542 + return $question;
543 + }
544 +
545 + /**
546 + * Sort answers.
547 + *
548 + * @since 3.0.0
549 + *
550 + * @param $question_id
551 + * @param array $order
552 + *
553 + * @return bool|LP_Question
554 + */
555 + public function sort_answers( $question_id, $order = array() ) {
556 +
557 + if ( learn_press_get_post_type( $question_id ) !== LP_QUESTION_CPT ) {
558 + return false;
559 + }
560 +
561 + if ( $order ) {
562 + $question = LP_Question::get_question( $question_id );
563 + $answers = $question->get_data( 'answer_options' );
564 +
565 + global $wpdb;
566 + $new_answers = array();
567 + foreach ( $order as $index => $answer_id ) {
568 + $wpdb->update(
569 + $wpdb->learnpress_question_answers,
570 + array( 'order' => $index + 1 ),
571 + array( 'question_answer_id' => $answer_id )
572 + );
573 +
574 + $new_answers[ $answer_id ] = $answers[ $answer_id ];
575 + }
576 +
577 + $question->set_data( 'answer_options', $new_answers );
578 +
579 + return $question;
580 +
581 + }
582 +
583 + return false;
584 + }
585 +
586 + /**
587 + * Delete question answer.
588 + *
589 + * @param $question_id
590 + * @param $answer_id
591 + * @param $force
592 + *
593 + * @return bool|false|int
594 + */
595 + public function delete_answer( $question_id, $answer_id, $force = false ) {
596 +
597 + if ( learn_press_get_post_type( $question_id ) !== LP_QUESTION_CPT ) {
598 + return false;
599 + }
600 +
601 + $question = LP_Question::get_question( $question_id );
602 +
603 + // exist answer options
604 + $answers = $question->get_data( 'answer_options' );
605 +
606 + global $wpdb;
607 +
608 + // Delete answer meta
609 + $wpdb->delete(
610 + $wpdb->learnpress_question_answermeta,
611 + array( 'learnpress_question_answer_id' => $answer_id )
612 + );
613 +
614 + $delete = $wpdb->delete(
615 + $wpdb->learnpress_question_answers,
616 + array( 'question_answer_id' => $answer_id )
617 + );
618 + if ( $delete ) {
619 + unset( $answers[ $answer_id ] );
620 + $question->set_data( 'answer_options', $answers );
621 +
622 + $this->sort_answers( $question_id, array_keys( $answers ) );
623 + }
624 +
625 + return $delete;
626 + }
627 +
628 + /**
629 + * Add new answer.
630 + *
631 + * @param $question_id
632 + * @param $new_answer
633 + *
634 + * @return bool|false|int
635 + */
636 + public function new_answer( $question_id, $new_answer ) {
637 + if ( learn_press_get_post_type( $question_id ) !== LP_QUESTION_CPT ) {
638 + return false;
639 + }
640 +
641 + $question = LP_Question::get_question( $question_id );
642 +
643 + // exist answer options
644 + $answers = $question->get_data( 'answer_options' );
645 + // number answer options
646 + $number = count( $answers );
647 +
648 + if ( $question->get_type() === 'sorting_choice' ) {
649 + $new_answer['is_true'] = 'yes';
650 + }
651 +
652 + global $wpdb;
653 +
654 + $new_answer = wp_parse_args(
655 + $new_answer,
656 + array(
657 + 'question_id' => $question_id,
658 + 'title' => '',
659 + 'value' => learn_press_random_value(),
660 + 'is_true' => '',
661 + 'order' => $number + 1,
662 + )
663 + );
664 +
665 + $insert = $wpdb->insert(
666 + $wpdb->learnpress_question_answers,
667 + $new_answer,
668 + learn_press_map_columns_format(
669 + $new_answer,
670 + array(
671 + 'question_id' => '%d',
672 + 'title' => '%s',
673 + 'value' => '%s',
674 + 'is_true' => '%s',
675 + 'order' => '%d',
676 + )
677 + )
678 + );
679 +
680 + if ( $insert ) {
681 + $new_answer['question_answer_id'] = $wpdb->insert_id;
682 +
683 + if ( is_array( $answers ) ) {
684 + $answers = array_merge( $answers, array( $new_answer ) );
685 + } else {
686 + $answers = array( $new_answer );
687 + }
688 + $question->set_data( 'answer_options', $answers );
689 + }
690 +
691 + return $wpdb->insert_id;
692 + }
693 +
694 +
695 + /**
696 + * Convert answers to true or false question.
697 + *
698 + * @since 3.0.0
699 + *
700 + * @param $question
701 + * @param $new_question
702 + * @param $answers
703 + *
704 + * @return mixed
705 + */
706 + protected function _convert_answers_to_true_or_false( $question, $new_question, $answers ) {
707 + if ( is_array( $answers ) ) {
708 + // array answer ids
709 + $answer_ids = array_keys( $answers );
710 +
711 + if ( sizeof( $answers ) > 2 ) {
712 + global $wpdb;
713 +
714 + foreach ( $answers as $key => $answer ) {
715 + if ( array_search( $key, $answer_ids ) > 1 ) {
716 + $wpdb->delete(
717 + $wpdb->learnpress_question_answers,
718 + array( 'question_answer_id' => $answer['question_answer_id'] )
719 + );
720 + }
721 + }
722 + $answers = array_slice( $answers, 0, 2, true );
723 + }
724 +
725 + $correct = 0;
726 + foreach ( $answers as $key => $answer ) {
727 + if ( $answer['is_true'] == 'yes' ) {
728 + $correct += 1;
729 + }
730 + }
731 +
732 + if ( ! $correct ) {
733 + // for single choice deletes all correct, set first option is correct
734 + $answers[ $answer_ids[0] ]['is_true'] = 'yes';
735 + } elseif ( $correct == 2 ) {
736 + // for multiple choice keeps all correct, remove all correct and keep first option
737 + $answers[ $answer_ids[1] ]['is_true'] = 'no';
738 + }
739 + }
740 +
741 + return $answers;
742 + }
743 +
744 + /**
745 + *
746 + * Convert answers for multi choice to single choice question.
747 + *
748 + * @since 3.0.0
749 + *
750 + * @param $question
751 + * @param $new_question
752 + * @param $answers
753 + *
754 + * @return array
755 + */
756 + protected function _convert_answers_multi_choice_to_single_choice( $question, $new_question, $answers ) {
757 + if ( is_array( $answers ) ) {
758 + // array answer ids
759 + $answer_ids = array_keys( $answers );
760 +
761 + $correct = 0;
762 + foreach ( $answers as $key => $answer ) {
763 + if ( $answer['is_true'] == 'yes' ) {
764 + $correct += 1;
765 + }
766 + }
767 +
768 + if ( ! $correct ) {
769 + $answers[ $answer_ids[0] ]['is_true'] = 'yes';
770 + } elseif ( $correct > 1 ) {
771 + // remove all correct and keep first option
772 + $answers[ $answer_ids[0] ]['is_true'] = 'no';
773 + }
774 + }
775 +
776 + return $answers;
777 + }
778 +
779 + /**
780 + * Convert default answers.
781 + *
782 + * @param $question LP_Question
783 + * @param $new_question LP_Question
784 + * @param $answers
785 + *
786 + * @return array
787 + */
788 + protected function _convert_default_answers( $question, $new_question, $answers ) {
789 + $question_id = $question->get_id();
790 + // clear all exists answer
791 + $this->clear( $question_id );
792 + // set default answer
793 + $answer_options = $new_question->get_default_answers();
794 +
795 + if ( is_array( $answer_options ) ) {
796 + foreach ( $answer_options as $index => $answer ) {
797 + $insert = array(
798 + 'question_id' => $question_id,
799 + 'title' => $answer['title'],
800 + 'value' => isset( $answer['value'] ) ? stripslashes( $answer['value'] ) : '',
801 + 'is_true' => ( $answer['is_true'] == 'yes' ) ? $answer['is_true'] : '',
802 + 'order' => $index + 1,
803 + );
804 + $new_answers[] = $this->add_answer( $new_question->get_type(), $insert );
805 + };
806 +
807 + return $new_answers;
808 + }
809 +
810 + return $answers;
811 + }
812 +
813 + /**
814 + * Add question answer.
815 + *
816 + * @since 3.0.0
817 + *
818 + * @param string $question_type
819 + * @param array $args
820 + *
821 + * @return array|bool
822 + */
823 + public function add_answer( $question_type = '', $args = array() ) {
824 + global $wpdb;
825 +
826 + $question = LP_Question::get_question( $args['question_id'], array( 'type' => $question_type ) );
827 +
828 + $wpdb->insert(
829 + $wpdb->learnpress_question_answers,
830 + array(
831 + 'question_id' => $args['question_id'],
832 + 'title' => $args['title'],
833 + 'value' => $args['value'],
834 + 'is_true' => $args['is_true'],
835 + 'order' => $args['order'],
836 + ),
837 + array( '%d', '%s', '%s', '%s', '%d' )
838 + );
839 +
840 + $question_answer_id = $wpdb->insert_id;
841 + if ( $question_answer_id ) {
842 + // update question answer option data
843 + $answer_options = $question->get_data( 'answer_options' ) ? $question->get_data( 'answer_options' ) : array();
844 +
845 + $new_answer_option_data = array(
846 + 'question_answer_id' => $question_answer_id,
847 + 'question_id' => $args['question_id'],
848 + 'order' => $args['order'],
849 + 'title' => $args['title'],
850 + 'value' => $args['value'],
851 + 'is_true' => $args['is_true'],
852 + );
853 +
854 + if ( ! $answer_options ) {
855 + $question->set_data( 'answer_options', array( $new_answer_option_data ) );
856 + } else {
857 + $answer_options[] = $new_answer_option_data;
858 + $question->set_data( 'answer_options', $answer_options );
859 + }
860 +
861 + return $new_answer_option_data;
862 + } else {
863 + return false;
864 + }
865 + }
866 +
867 + /**
868 + * Delete question answer.
869 + *
870 + * @since 3.0.0
871 + *
872 + * @param $question_id
873 + * @param $answer_id
874 + *
875 + * @return bool|false|int
876 + */
877 + public function delete_question_answer( $question_id, $answer_id ) {
878 + if ( learn_press_get_post_type( $question_id ) !== LP_QUESTION_CPT || ! $answer_id ) {
879 + return false;
880 + }
881 +
882 + global $wpdb;
883 +
884 + $result = $wpdb->delete(
885 + $wpdb->learnpress_question_answers,
886 + array( 'question_answer_id' => $answer_id )
887 + );
888 +
889 + return $result;
890 + }
891 +
892 + /**
893 + * Delete all question answers.
894 + *
895 + * @param $question_id
896 + *
897 + * @return bool|WP_Error
898 + */
899 + public function clear( $question_id ) {
900 +
901 + if ( ! learn_press_get_question( $question_id ) ) {
902 + return $this->get_error( 'QUESTION_NOT_EXISTS' );
903 + }
904 +
905 + do_action( 'learn-press/before-clear-question', $question_id );
906 +
907 + global $wpdb;
908 + $wpdb->delete( $wpdb->learnpress_question_answers, array( 'question_id' => $question_id ) );
909 +
910 + return true;
911 + }
912 +
913 + /**
914 + * Load answer options for the question from database.
915 + * Load from cache if data is already loaded into cache.
916 + * Otherwise, load from database and put to cache.
917 + *
918 + * @param int $question_id
919 + *
920 + * @return array
921 + */
922 + protected function _read_answers( $question_id ) {
923 + global $wpdb;
924 +
925 + $query = $wpdb->prepare(
926 + "
927 + SELECT question_answer_id, title, value, is_true
928 + FROM {$wpdb->prefix}learnpress_question_answers
929 + WHERE question_id = %d
930 + ORDER BY `order` ASC
931 + ",
932 + $question_id
933 + );
934 +
935 + $answer_options = array();
936 +
937 + $results = $wpdb->get_results( $query );
938 +
939 + if ( $results ) {
940 + foreach ( $results as $k => $v ) {
941 + $answer_option = array(
942 + 'question_answer_id' => absint( $v->question_answer_id ),
943 + 'title' => $v->title,
944 + 'value' => $v->value,
945 + 'is_true' => $v->is_true,
946 + 'order' => $k + 1, // Need???
947 + );
948 +
949 + $answer_options[ $v->question_answer_id ] = $answer_option;
950 + }
951 + }
952 +
953 + return $answer_options;
954 + }
955 +
956 + /**
957 + * Load question answers
958 + *
959 + * @updated 3.1.0
960 + *
961 + * @param array|int $question_id
962 + *
963 + * @return array|bool
964 + */
965 + public function load_answer_options( $question_id ) {
966 + global $wpdb;
967 +
968 + $return_id = 0;
969 +
970 + if ( is_array( $question_id ) ) {
971 +
972 + foreach ( $question_id as $q_id ) {
973 + $this->load_answer_options( $q_id );
974 + if ( ! $return_id ) {
975 + $return_id = $q_id;
976 + }
977 + }
978 + $question_id = $return_id;
979 + }
980 +
981 + $answer_options = LP_Object_Cache::get( 'question-' . $question_id, 'question-answers' );
982 +
983 + if ( false === $answer_options ) {
984 + $answer_options = $this->_read_answers( $question_id );
985 + LP_Object_Cache::set( 'question-' . $question_id, $answer_options, 'question-answers' );
986 + }
987 +
988 + return $answer_options;
989 + }
990 + }
991 +}