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/Models/QuizPostModel.php +411 -393 4.3.84.4.8 View file →
@@ -1,393 +1,411 @@
1 -<?php
2 -
3 -/**
4 - * Class Quiz Post Model
5 - *
6 - * @package LearnPress/Classes
7 - * @version 1.0.1
8 - * @since 4.2.7.6
9 - */
10 -
11 -namespace LearnPress\Models;
12 -
13 -use Exception;
14 -use LearnPress\Databases\QuizQuestionsDB;
15 -use LearnPress\Models\Question\QuestionAnswerModel;
16 -use LearnPress\Models\Question\QuestionPostModel;
17 -use LearnPress\Models\Quiz\QuizQuestionModel;
18 -use LP_Cache;
19 -use LP_Question;
20 -use LP_Question_DB;
21 -use LP_Question_Filter;
22 -use LP_Quiz;
23 -use LP_Quiz_Filter;
24 -use Throwable;
25 -
26 -
27 -class QuizPostModel extends PostModel {
28 - /**
29 - * @var string Post Type
30 - */
31 - public $post_type = LP_QUIZ_CPT;
32 -
33 - /**
34 - * Const meta key
35 - */
36 - const META_KEY_PASSING_GRADE = '_lp_passing_grade';
37 - const META_KEY_RETAKE_COUNT = '_lp_retake_count';
38 - const META_KEY_DURATION = '_lp_duration';
39 - const META_KEY_PAGINATION = '_lp_pagination';
40 - const META_KEY_REVIEW = '_lp_review';
41 - const META_KEY_SHOW_CORRECT_REVIEW = '_lp_show_correct_review';
42 - const META_KEY_INSTANT_CHECK = '_lp_instant_check';
43 - const META_KEY_NEGATIVE_MARKING = '_lp_negative_marking';
44 - const META_KEY_MINUS_SKIP_QUESTIONS = '_lp_minus_skip_questions';
45 -
46 - /**
47 - * Get post assignment by ID
48 - *
49 - * @param int $post_id
50 - * @param bool $check_cache
51 - *
52 - * @return false|static
53 - */
54 - public static function find( int $post_id, bool $check_cache = false ) {
55 - $filter_post = new LP_Quiz_Filter();
56 - $filter_post->ID = $post_id;
57 -
58 - $key_cache = "quizPostModel/find/{$post_id}";
59 - $lpQuizCache = new LP_Cache();
60 -
61 - // Check cache
62 - if ( $check_cache ) {
63 - $quizPostModel = $lpQuizCache->get_cache( $key_cache );
64 - if ( $quizPostModel instanceof QuizPostModel ) {
65 - return $quizPostModel;
66 - }
67 - }
68 -
69 - $quizPostModel = self::get_item_model_from_db( $filter_post );
70 - // Set cache
71 - if ( $quizPostModel instanceof QuizPostModel ) {
72 - $lpQuizCache->set_cache( $key_cache, $quizPostModel );
73 - }
74 -
75 - return $quizPostModel;
76 - }
77 -
78 - /**
79 - * Get max mark of assignment
80 - *
81 - * @return string
82 - */
83 - public function get_duration(): string {
84 - return $this->get_meta_value_by_key( self::META_KEY_DURATION, '0 minute' );
85 - }
86 -
87 - /**
88 - * Get max mark of assignment
89 - *
90 - * @return float
91 - */
92 - public function get_passing_grade(): float {
93 - return (float) $this->get_meta_value_by_key( self::META_KEY_PASSING_GRADE, 80 );
94 - }
95 -
96 - /**
97 - * Get retake count option.
98 - *
99 - * @return int
100 - */
101 - public function get_retake_count(): int {
102 - return (int) $this->get_meta_value_by_key( self::META_KEY_RETAKE_COUNT, 0 );
103 - }
104 -
105 - /**
106 - * Get all question's ids of the quiz.
107 - *
108 - * @param array $statuses
109 - *
110 - * @return int[]
111 - * @version 1.0.0
112 - * @since 4.2.7.6
113 - */
114 - public function get_question_ids( array $statuses = [ 'publish' ] ): array {
115 - $lp_question_db = LP_Question_DB::getInstance();
116 - $question_ids = [];
117 -
118 - try {
119 - $filter = new LP_Question_Filter();
120 - $filter->ID = $this->get_id();
121 - $filter->post_status = $statuses;
122 - $question_ids = $lp_question_db->get_list_question_ids_of_quiz( $filter );
123 -
124 - // Hook old
125 - if ( has_filter( 'learn-press/quiz/get-question-ids' ) ) {
126 - $quiz_old = new LP_Quiz( $this->get_id() );
127 - $course_id_of_quiz_old = $quiz_old->get_course_id();
128 -
129 - $question_ids = apply_filters(
130 - 'learn-press/quiz/get-question-ids',
131 - $question_ids,
132 - $this->get_id(),
133 - $course_id_of_quiz_old
134 - );
135 - }
136 -
137 - $question_ids = apply_filters(
138 - 'learn-press/quiz/question-ids',
139 - $question_ids,
140 - $this,
141 - $statuses
142 - );
143 -
144 - if ( ! is_array( $question_ids ) ) {
145 - $question_ids = array();
146 - }
147 - } catch ( Throwable $e ) {
148 - error_log( $e->getMessage() );
149 - }
150 -
151 - return $question_ids;
152 - }
153 -
154 - /**
155 - * Get number questions in quiz.
156 - *
157 - * @return int
158 - */
159 - public function count_questions(): int {
160 - $size = 0;
161 - $questions = $this->get_question_ids();
162 -
163 - if ( $questions ) {
164 - $size = sizeof( $questions );
165 - }
166 -
167 - return (int) apply_filters( 'learn-press/quiz/count-questions', $size, $this->get_id() );
168 - }
169 -
170 - /**
171 - * Get Mark of the quiz (total mark questions).
172 - *
173 - * @return mixed|null
174 - */
175 - public function get_mark() {
176 - $questions = $this->get_question_ids();
177 - $mark = 0;
178 -
179 - foreach ( $questions as $question_id ) {
180 - $question = LP_Question::get_question( $question_id );
181 - if ( $question ) {
182 - $mark += $question->get_mark();
183 - }
184 - }
185 -
186 - return apply_filters( 'learn-press/quiz-mark', $mark, $this->get_id() );
187 - }
188 -
189 - /**
190 - * Check option instant check has enabled.
191 - *
192 - * @return bool
193 - */
194 - public function has_instant_check(): bool {
195 - return $this->get_meta_value_by_key( self::META_KEY_INSTANT_CHECK, 'no' ) === 'yes';
196 - }
197 -
198 - /**
199 - * Check option negative marking has enabled.
200 - *
201 - * @return bool
202 - */
203 - public function has_negative_marking(): bool {
204 - return $this->get_meta_value_by_key( self::META_KEY_NEGATIVE_MARKING, 'no' ) === 'yes';
205 - }
206 -
207 - /**
208 - * Check option minus skip questions has enabled.
209 - *
210 - * @return bool
211 - */
212 - public function has_minus_skip_questions(): bool {
213 - return $this->get_meta_value_by_key( self::META_KEY_MINUS_SKIP_QUESTIONS, 'no' ) === 'yes';
214 - }
215 -
216 - /**
217 - * Check option minus skip questions has enabled.
218 - *
219 - * @return bool
220 - */
221 - public function has_show_correct_review(): bool {
222 - return $this->get_meta_value_by_key( self::META_KEY_SHOW_CORRECT_REVIEW, 'yes' ) === 'yes';
223 - }
224 -
225 - /**
226 - * Create question and add to quiz.
227 - *
228 - * @param array $data [ question_title, question_type, question_content ]
229 - *
230 - * @return QuizQuestionModel
231 - * @throws Exception
232 - * @since 4.2.9
233 - * @version 1.0.1
234 - */
235 - public function create_question_and_add( array $data ): QuizQuestionModel {
236 - $question_title = $data['question_title'] ?? '';
237 - $question_type = $data['question_type'] ?? '';
238 - $question_content = $data['question_content'] ?? '';
239 - $question_options = $data['question_options'] ?? [];
240 -
241 - if ( empty( $question_title ) ) {
242 - throw new Exception( __( 'Question title is required', 'learnpress' ) );
243 - }
244 -
245 - if ( ! QuestionPostModel::check_type_valid( $question_type ) ) {
246 - throw new Exception( __( 'Invalid question type', 'learnpress' ) );
247 - }
248 -
249 - // Create question
250 - $questionPostModelNew = new QuestionPostModel();
251 - $questionPostModelNew->post_title = $question_title;
252 - $questionPostModelNew->post_content = $question_content;
253 - $questionPostModelNew->post_status = 'publish';
254 - $questionPostModelNew->post_author = get_current_user_id();
255 - $questionPostModelNew->save();
256 - $questionPostModelNew->save_meta_value_by_key(
257 - QuestionPostModel::META_KEY_TYPE,
258 - $question_type
259 - );
260 -
261 - // Get question object by type
262 - $questionClassName = $questionPostModelNew::get_question_obj_by_type( $question_type );
263 - if ( class_exists( $questionClassName ) ) {
264 - /**
265 - * @var QuestionPostModel $questionPostTyeModel
266 - */
267 - $questionPostTyeModel = new $questionClassName( $questionPostModelNew );
268 - if ( ! empty( $question_options ) ) {
269 - foreach ( $question_options as $index => $answer ) {
270 - $answer = array(
271 - 'question_id' => $questionPostTyeModel->get_id(),
272 - 'title' => $answer['title'],
273 - 'value' => $questionPostTyeModel->random_value(),
274 - 'is_true' => $answer['is_true'] ?? '',
275 - 'order' => $index + 1,
276 - );
277 -
278 - $questionAnswerModel = new QuestionAnswerModel( $answer );
279 - $questionAnswerModel->save();
280 - $answers[ $index ]['question_answer_id'] = $questionAnswerModel->question_answer_id;
281 - }
282 - } elseif ( method_exists( $questionPostTyeModel, 'create_default_answers' ) ) {
283 - $questionPostTyeModel->create_default_answers();
284 - }
285 - } else {
286 - throw new Exception( __( 'Question type not found', 'learnpress' ) );
287 - }
288 -
289 - $quizQuestionsDB = QuizQuestionsDB::getInstance();
290 - $max_order = $quizQuestionsDB->get_last_number_order( $this->get_id() );
291 -
292 - // Add question to quiz
293 - $quizQuestionModel = new QuizQuestionModel();
294 - $quizQuestionModel->quiz_id = $this->get_id();
295 - $quizQuestionModel->question_id = $questionPostModelNew->get_id();
296 - $quizQuestionModel->question_order = $max_order + 1;
297 - $quizQuestionModel->save();
298 -
299 - return $quizQuestionModel;
300 - }
301 -
302 - /**
303 - * Add questions exists (from Question Bank) to quiz.
304 - *
305 - * @param array $data [ 'question_ids' => [] ]
306 - *
307 - * @throws Exception
308 - * @since 4.3.2
309 - * @version 1.0.0
310 - */
311 - public function add_questions_to_quiz( array $data ): array {
312 - $this->check_capabilities_update_item_course();
313 -
314 - $question_ids = $data['question_ids'] ?? [];
315 - if ( empty( $question_ids ) ) {
316 - throw new Exception( __( 'Question IDs are required', 'learnpress' ) );
317 - }
318 -
319 - $quiz_questions_added = [];
320 - foreach ( $question_ids as $question_id ) {
321 - $questionPostModel = QuestionPostModel::find( $question_id, true );
322 - if ( ! $questionPostModel ) {
323 - throw new Exception( __( 'Question not found', 'learnpress' ) );
324 - }
325 -
326 - // Check if question already exists in quiz
327 - $quizQuestionModel = QuizQuestionModel::find( $this->get_id(), $question_id, true );
328 - if ( $quizQuestionModel ) {
329 - continue; // Skip if question already exists in quiz
330 - }
331 -
332 - $quizQuestionsDB = QuizQuestionsDB::getInstance();
333 - $max_order = $quizQuestionsDB->get_last_number_order( $this->get_id() );
334 -
335 - // Add question to quiz
336 - $quizQuestionModel = new QuizQuestionModel();
337 - $quizQuestionModel->quiz_id = $this->get_id();
338 - $quizQuestionModel->question_id = $question_id;
339 - $quizQuestionModel->question_order = $max_order + 1;
340 - $quizQuestionModel->save();
341 -
342 - $quiz_questions_added[] = $quizQuestionModel;
343 - }
344 -
345 - return $quiz_questions_added;
346 - }
347 -
348 - /**
349 - * Update question positions in quiz.
350 - *
351 - * @param int[] $data [ 'question_ids' => [] ]
352 - *
353 - * @throws Exception
354 - * @since 4.3.2
355 - * @version 1.0.0
356 - */
357 - public function update_question_position( array $data ) {
358 - $this->check_capabilities_update_item_course();
359 -
360 - $question_ids = $data['question_ids'] ?? [];
361 - if ( empty( $question_ids ) ) {
362 - throw new Exception( __( 'Question IDs are required', 'learnpress' ) );
363 - }
364 -
365 - $question_ids = $data['question_ids'] ?? [];
366 - QuizQuestionsDB::getInstance()->update_question_position( $question_ids, $this->get_id() );
367 - }
368 -
369 - /**
370 - * Remove question from quiz.
371 - *
372 - * @param int $question_id
373 - *
374 - * @throws Exception
375 - * @since 4.3.2
376 - * @version 1.0.0
377 - */
378 - public function remove_question_from_quiz( int $question_id ) {
379 - $this->check_capabilities_update_item_course();
380 -
381 - $questionPostModel = QuestionPostModel::find( $question_id, true );
382 - if ( ! $questionPostModel ) {
383 - throw new Exception( __( 'Question not found', 'learnpress' ) );
384 - }
385 -
386 - $quizQuestionModel = QuizQuestionModel::find( $this->get_id(), $question_id );
387 - if ( ! $quizQuestionModel ) {
388 - throw new Exception( __( 'Question not found in quiz', 'learnpress' ) );
389 - }
390 -
391 - $quizQuestionModel->delete();
392 - }
393 -}
1 +<?php
2 +
3 +/**
4 + * Class Quiz Post Model
5 + *
6 + * @package LearnPress/Classes
7 + * @version 1.0.1
8 + * @since 4.2.7.6
9 + */
10 +
11 +namespace LearnPress\Models;
12 +
13 +use Exception;
14 +use LearnPress\Databases\QuizQuestionsDB;
15 +use LearnPress\Models\Question\QuestionAnswerModel;
16 +use LearnPress\Models\Question\QuestionPostModel;
17 +use LearnPress\Models\Quiz\QuizQuestionModel;
18 +use LP_Cache;
19 +use LP_Question;
20 +use LP_Question_DB;
21 +use LP_Question_Filter;
22 +use LP_Quiz;
23 +use LP_Quiz_Filter;
24 +use Throwable;
25 +
26 +
27 +class QuizPostModel extends PostModel {
28 + /**
29 + * @var string Post Type
30 + */
31 + public $post_type = LP_QUIZ_CPT;
32 +
33 + /**
34 + * Const meta key
35 + */
36 + const META_KEY_PASSING_GRADE = '_lp_passing_grade';
37 + const META_KEY_RETAKE_COUNT = '_lp_retake_count';
38 + const META_KEY_DURATION = '_lp_duration';
39 + const META_KEY_PAGINATION = '_lp_pagination';
40 + const META_KEY_REVIEW = '_lp_review';
41 + const META_KEY_SHOW_CORRECT_REVIEW = '_lp_show_correct_review';
42 + const META_KEY_INSTANT_CHECK = '_lp_instant_check';
43 + const META_KEY_NEGATIVE_MARKING = '_lp_negative_marking';
44 + const META_KEY_MINUS_SKIP_QUESTIONS = '_lp_minus_skip_questions';
45 +
46 + /**
47 + * Get post assignment by ID
48 + *
49 + * @param int $post_id
50 + * @param bool $check_cache
51 + *
52 + * @return false|static
53 + */
54 + public static function find( int $post_id, bool $check_cache = false ) {
55 + $filter_post = new LP_Quiz_Filter();
56 + $filter_post->ID = $post_id;
57 +
58 + $key_cache = "quizPostModel/find/{$post_id}";
59 + $lpQuizCache = new LP_Cache();
60 +
61 + // Check cache
62 + if ( $check_cache ) {
63 + $quizPostModel = $lpQuizCache->get_cache( $key_cache );
64 + if ( $quizPostModel instanceof QuizPostModel ) {
65 + return $quizPostModel;
66 + }
67 + }
68 +
69 + $quizPostModel = self::get_item_model_from_db( $filter_post );
70 + // Set cache
71 + if ( $quizPostModel instanceof QuizPostModel ) {
72 + $lpQuizCache->set_cache( $key_cache, $quizPostModel );
73 + }
74 +
75 + return $quizPostModel;
76 + }
77 +
78 + /**
79 + * Get max mark of assignment
80 + *
81 + * @return string
82 + */
83 + public function get_duration(): string {
84 + return $this->get_meta_value_by_key( self::META_KEY_DURATION, '0 minute' );
85 + }
86 +
87 + /**
88 + * Get max mark of assignment
89 + *
90 + * @return float
91 + */
92 + public function get_passing_grade(): float {
93 + return (float) $this->get_meta_value_by_key( self::META_KEY_PASSING_GRADE, 80 );
94 + }
95 +
96 + /**
97 + * Get retake count option.
98 + *
99 + * @return int
100 + */
101 + public function get_retake_count(): int {
102 + return (int) $this->get_meta_value_by_key( self::META_KEY_RETAKE_COUNT, 0 );
103 + }
104 +
105 + /**
106 + * Get all question's ids of the quiz.
107 + *
108 + * @param array $statuses
109 + *
110 + * @return int[]
111 + * @version 1.0.0
112 + * @since 4.2.7.6
113 + */
114 + public function get_question_ids( array $statuses = [ 'publish' ] ): array {
115 + $lp_question_db = LP_Question_DB::getInstance();
116 + $question_ids = [];
117 +
118 + try {
119 + $filter = new LP_Question_Filter();
120 + $filter->ID = $this->get_id();
121 + $filter->post_status = $statuses;
122 + $question_ids = $lp_question_db->get_list_question_ids_of_quiz( $filter );
123 +
124 + // Hook old
125 + if ( has_filter( 'learn-press/quiz/get-question-ids' ) ) {
126 + $quiz_old = new LP_Quiz( $this->get_id() );
127 + $course_id_of_quiz_old = $quiz_old->get_course_id();
128 +
129 + $question_ids = apply_filters(
130 + 'learn-press/quiz/get-question-ids',
131 + $question_ids,
132 + $this->get_id(),
133 + $course_id_of_quiz_old
134 + );
135 + }
136 +
137 + $question_ids = apply_filters(
138 + 'learn-press/quiz/question-ids',
139 + $question_ids,
140 + $this,
141 + $statuses
142 + );
143 +
144 + if ( ! is_array( $question_ids ) ) {
145 + $question_ids = array();
146 + }
147 + } catch ( Throwable $e ) {
148 + error_log( $e->getMessage() );
149 + }
150 +
151 + return $question_ids;
152 + }
153 +
154 + /**
155 + * Get number questions in quiz.
156 + *
157 + * @return int
158 + */
159 + public function count_questions(): int {
160 + $size = 0;
161 + $questions = $this->get_question_ids();
162 +
163 + if ( $questions ) {
164 + $size = sizeof( $questions );
165 + }
166 +
167 + return (int) apply_filters( 'learn-press/quiz/count-questions', $size, $this->get_id() );
168 + }
169 +
170 + /**
171 + * Get Mark of the quiz (total mark questions).
172 + *
173 + * @return mixed|null
174 + */
175 + public function get_mark() {
176 + $questions = $this->get_question_ids();
177 + $mark = 0;
178 +
179 + foreach ( $questions as $question_id ) {
180 + $question = LP_Question::get_question( $question_id );
181 + if ( $question ) {
182 + $mark += $question->get_mark();
183 + }
184 + }
185 +
186 + return apply_filters( 'learn-press/quiz-mark', $mark, $this->get_id() );
187 + }
188 +
189 + /**
190 + * Check option instant check has enabled.
191 + *
192 + * @return bool
193 + */
194 + public function has_instant_check(): bool {
195 + return $this->get_meta_value_by_key( self::META_KEY_INSTANT_CHECK, 'no' ) === 'yes';
196 + }
197 +
198 + /**
199 + * Check option negative marking has enabled.
200 + *
201 + * @return bool
202 + */
203 + public function has_negative_marking(): bool {
204 + return $this->get_meta_value_by_key( self::META_KEY_NEGATIVE_MARKING, 'no' ) === 'yes';
205 + }
206 +
207 + /**
208 + * Check option minus skip questions has enabled.
209 + *
210 + * @return bool
211 + */
212 + public function has_minus_skip_questions(): bool {
213 + return $this->get_meta_value_by_key( self::META_KEY_MINUS_SKIP_QUESTIONS, 'no' ) === 'yes';
214 + }
215 +
216 + /**
217 + * Check option minus skip questions has enabled.
218 + *
219 + * @return bool
220 + */
221 + public function has_show_correct_review(): bool {
222 + return $this->get_meta_value_by_key( self::META_KEY_SHOW_CORRECT_REVIEW, 'yes' ) === 'yes';
223 + }
224 +
225 + /**
226 + * Create question and add to quiz.
227 + *
228 + * @param array $data [ question_title, question_type, question_content ]
229 + *
230 + * @return QuizQuestionModel
231 + * @throws Exception
232 + * @since 4.2.9
233 + * @version 1.0.1
234 + */
235 + public function create_question_and_add( array $data ): QuizQuestionModel {
236 + $question_title = $data['question_title'] ?? '';
237 + $question_type = $data['question_type'] ?? '';
238 + $question_content = $data['question_content'] ?? '';
239 + $question_options = $data['question_options'] ?? [];
240 +
241 + if ( empty( $question_title ) ) {
242 + throw new Exception( __( 'Question title is required', 'learnpress' ) );
243 + }
244 +
245 + if ( ! QuestionPostModel::check_type_valid( $question_type ) ) {
246 + throw new Exception( __( 'Invalid question type', 'learnpress' ) );
247 + }
248 +
249 + // Create question
250 + $questionPostModelNew = new QuestionPostModel();
251 + $questionPostModelNew->post_title = $question_title;
252 + $questionPostModelNew->post_content = $question_content;
253 + $questionPostModelNew->post_status = 'publish';
254 + $questionPostModelNew->post_author = get_current_user_id();
255 + $questionPostModelNew->save();
256 + $questionPostModelNew->save_meta_value_by_key(
257 + QuestionPostModel::META_KEY_TYPE,
258 + $question_type
259 + );
260 +
261 + // Get question object by type
262 + $questionClassName = $questionPostModelNew::get_question_obj_by_type( $question_type );
263 + if ( class_exists( $questionClassName ) ) {
264 + /**
265 + * @var QuestionPostModel $questionPostTyeModel
266 + */
267 + $questionPostTyeModel = new $questionClassName( $questionPostModelNew );
268 + if ( ! empty( $question_options ) ) {
269 + foreach ( $question_options as $index => $answer ) {
270 + $answer = array(
271 + 'question_id' => $questionPostTyeModel->get_id(),
272 + 'title' => $answer['title'],
273 + 'value' => $questionPostTyeModel->random_value(),
274 + 'is_true' => $answer['is_true'] ?? '',
275 + 'order' => $index + 1,
276 + );
277 +
278 + $questionAnswerModel = new QuestionAnswerModel( $answer );
279 + $questionAnswerModel->save();
280 + $answers[ $index ]['question_answer_id'] = $questionAnswerModel->question_answer_id;
281 + }
282 + } elseif ( method_exists( $questionPostTyeModel, 'create_default_answers' ) ) {
283 + $questionPostTyeModel->create_default_answers();
284 + }
285 + } else {
286 + throw new Exception( __( 'Question type not found', 'learnpress' ) );
287 + }
288 +
289 + $quizQuestionsDB = QuizQuestionsDB::getInstance();
290 + $max_order = $quizQuestionsDB->get_last_number_order( $this->get_id() );
291 +
292 + // Add question to quiz
293 + $quizQuestionModel = new QuizQuestionModel();
294 + $quizQuestionModel->quiz_id = $this->get_id();
295 + $quizQuestionModel->question_id = $questionPostModelNew->get_id();
296 + $quizQuestionModel->question_order = $max_order + 1;
297 + $quizQuestionModel->save();
298 +
299 + return $quizQuestionModel;
300 + }
301 +
302 + /**
303 + * Add questions exists (from Question Bank) to quiz.
304 + *
305 + * @param array $data [ 'question_ids' => [] ]
306 + *
307 + * @throws Exception
308 + * @since 4.3.2
309 + * @version 1.0.0
310 + */
311 + public function add_questions_to_quiz( array $data ): array {
312 + $this->check_capabilities_update_item_course();
313 +
314 + $question_ids = $data['question_ids'] ?? [];
315 + if ( empty( $question_ids ) ) {
316 + throw new Exception( __( 'Question IDs are required', 'learnpress' ) );
317 + }
318 +
319 + $quiz_questions_added = [];
320 + foreach ( $question_ids as $question_id ) {
321 + $questionPostModel = QuestionPostModel::find( $question_id, true );
322 + if ( ! $questionPostModel ) {
323 + throw new Exception( __( 'Question not found', 'learnpress' ) );
324 + }
325 +
326 + // Check if question already exists in quiz
327 + $quizQuestionModel = QuizQuestionModel::find( $this->get_id(), $question_id, true );
328 + if ( $quizQuestionModel ) {
329 + continue; // Skip if question already exists in quiz
330 + }
331 +
332 + $quizQuestionsDB = QuizQuestionsDB::getInstance();
333 + $max_order = $quizQuestionsDB->get_last_number_order( $this->get_id() );
334 +
335 + // Add question to quiz
336 + $quizQuestionModel = new QuizQuestionModel();
337 + $quizQuestionModel->quiz_id = $this->get_id();
338 + $quizQuestionModel->question_id = $question_id;
339 + $quizQuestionModel->question_order = $max_order + 1;
340 + $quizQuestionModel->save();
341 +
342 + $quiz_questions_added[] = $quizQuestionModel;
343 + }
344 +
345 + return $quiz_questions_added;
346 + }
347 +
348 + /**
349 + * Update question positions in quiz.
350 + *
351 + * @param int[] $data [ 'question_ids' => [] ]
352 + *
353 + * @throws Exception
354 + * @since 4.3.2
355 + * @version 1.0.0
356 + */
357 + public function update_question_position( array $data ) {
358 + $this->check_capabilities_update_item_course();
359 +
360 + $question_ids = $data['question_ids'] ?? [];
361 + if ( empty( $question_ids ) ) {
362 + throw new Exception( __( 'Question IDs are required', 'learnpress' ) );
363 + }
364 +
365 + $question_ids = $data['question_ids'] ?? [];
366 + QuizQuestionsDB::getInstance()->update_question_position( $question_ids, $this->get_id() );
367 + }
368 +
369 + /**
370 + * Remove question from quiz.
371 + *
372 + * @param int $question_id
373 + *
374 + * @throws Exception
375 + * @since 4.3.2
376 + * @version 1.0.0
377 + */
378 + public function remove_question_from_quiz( int $question_id ) {
379 + $this->check_capabilities_update_item_course();
380 +
381 + $questionPostModel = QuestionPostModel::find( $question_id, true );
382 + if ( ! $questionPostModel ) {
383 + throw new Exception( __( 'Question not found', 'learnpress' ) );
384 + }
385 +
386 + $quizQuestionModel = QuizQuestionModel::find( $this->get_id(), $question_id );
387 + if ( ! $quizQuestionModel ) {
388 + throw new Exception( __( 'Question not found in quiz', 'learnpress' ) );
389 + }
390 +
391 + $quizQuestionModel->delete();
392 + }
393 +
394 + /**
395 + * Check create
396 + *
397 + * @return bool
398 + */
399 + public function check_capabilities_create(): bool {
400 + return current_user_can( 'edit_' . LP_LESSON_CPT . 's' );
401 + }
402 +
403 + /**
404 + * Check update
405 + *
406 + * @return bool
407 + */
408 + public function check_capabilities_update(): bool {
409 + return current_user_can( 'edit_' . LP_LESSON_CPT, $this->ID );
410 + }
411 +}