Diff: STRATO-apps/wordpress_03/app/wp-content/plugins/tutor-pro/tools/importers/QuizImporter.php

Keine Baseline-Datei – Diff nur gegen leer.
Zur Liste
1 -
1 + <?php
2 + /**
3 + * Quiz Importer
4 + *
5 + * @package TutorPro\Tools
6 + * @author Themeum<support@themeum.com>
7 + * @link https://themeum.com
8 + * @since 3.6.0
9 + */
10 +
11 + namespace TutorPro\Tools;
12 +
13 + use Tutor\Helpers\QueryHelper;
14 + use Tutor\Helpers\ValidationHelper;
15 + use TutorPro\ContentBank\Models\ContentModel;
16 + use TUTOR\Quiz;
17 +
18 + if ( ! defined( 'ABSPATH' ) ) {
19 + exit;
20 + }
21 +
22 + /**
23 + * Quiz Importer Class.
24 + */
25 + class QuizImporter {
26 +
27 + /**
28 + * Quiz Class Instance.
29 + *
30 + * @since 3.6.0
31 + *
32 + * @var Quiz
33 + */
34 + private $quiz;
35 +
36 + /**
37 + * Quiz Importer Class Constructor.
38 + */
39 + public function __construct() {
40 + $this->quiz = new Quiz();
41 + }
42 +
43 + /**
44 + * Set meta data of quiz post type.
45 + *
46 + * @since 3.6.0
47 + *
48 + * @param array $quiz_meta the quiz meta data to set.
49 + * @param int $quiz_id the quiz post id.
50 + *
51 + * @return void
52 + */
53 + public function set_quiz_meta( $quiz_meta, $quiz_id ) {
54 + $quiz_meta = array_map( fn( $val ) => $val[0], $quiz_meta );
55 + if ( isset( $quiz_meta['_content_drip_settings'] ) ) {
56 + update_post_meta( $quiz_id, '_content_drip_settings', $quiz_meta['_content_drip_settings'] );
57 + }
58 +
59 + if ( isset( $quiz_meta[ QUIZ::META_QUIZ_OPTION ] ) ) {
60 + update_post_meta( $quiz_id, QUIZ::META_QUIZ_OPTION, $quiz_meta[ QUIZ::META_QUIZ_OPTION ] );
61 + }
62 + }
63 +
64 + /**
65 + * Flatten all nested quiz question and answer data to a single array.
66 + *
67 + * @since 3.6.0
68 + *
69 + * @since 3.7.0 param $question and $answers added.
70 + *
71 + * @param array $data the quiz question answer data to flatten.
72 + * @param int $content_id the content id for the question.
73 + * @param array $question array of question data.
74 + * @param array $answers array of answer data.
75 + *
76 + * @return array
77 + */
78 + public function flatten_quiz_question_answer( $data, $content_id = 0, $question = array(), $answers = array() ) {
79 + $flatten_content = array();
80 +
81 + if ( $data ) {
82 + foreach ( $data as $post_id => $question_answer ) {
83 + $questions = array_column( $question_answer, 'question' );
84 + $answers = array_column( $question_answer, 'answers' );
85 + $answers = array_merge( ...$answers );
86 + foreach ( $questions as $questions_data ) {
87 + $questions_data['quiz_id'] = $post_id;
88 + $flatten_content['question'][] = $questions_data;
89 + }
90 +
91 + foreach ( $answers as $answers_data ) {
92 + $flatten_content['answers'][] = $answers_data;
93 + }
94 + }
95 + }
96 +
97 + if ( $content_id ) {
98 +
99 + $question['content_id'] = $content_id;
100 + $flatten_content['question'][] = $question;
101 +
102 + foreach ( $answers as $answers_data ) {
103 + $flatten_content['answers'][] = $answers_data;
104 + }
105 + }
106 +
107 + return $flatten_content;
108 + }
109 +
110 + /**
111 + * Prepare quiz question answer data for bulk insertion.
112 + *
113 + * @since 3.6.0
114 + * @since 3.8.1 param $from_course added.
115 + *
116 + * @param array $quiz_questions_answers the quiz question answer data to prepare.
117 + * @param bool $from_course whether import from course.
118 + *
119 + * @return array
120 + */
121 + private function prepare_quiz_questions_answers( $quiz_questions_answers, $from_course = false ) {
122 + $questions = $quiz_questions_answers['question'];
123 + $answers = $quiz_questions_answers['answers'];
124 + $final_questions = array();
125 + $final_answers = array();
126 +
127 + $question_types = array_keys( tutor_utils()->get_question_types() );
128 +
129 + foreach ( $questions as $question ) {
130 + if ( ! $from_course ) {
131 + $rules = array(
132 + 'question_mark' => 'required|numeric',
133 + 'question_title' => 'required',
134 + 'question_type' => 'required|match_string:' . implode( ',', $question_types ),
135 + 'question_settings' => 'required',
136 + );
137 +
138 + $validate = ValidationHelper::validate( $rules, $question );
139 +
140 + if ( ! $validate->success ) {
141 + continue;
142 + }
143 + }
144 +
145 + if ( isset( $question['question_type'] ) ) {
146 + if ( 'image_matching' === $question['question_type'] ) {
147 + $question['question_type'] = 'matching';
148 + }
149 +
150 + if ( 'single_choice' === $question['question_type'] ) {
151 + $question['question_type'] = 'multiple_choice';
152 + }
153 + }
154 +
155 + $question['question_title'] = addslashes( $question['question_title'] );
156 +
157 + if ( isset( $question['question_description'] ) ) {
158 + $question['question_description'] = addslashes( $question['question_description'] );
159 + }
160 +
161 + if ( isset( $question['answer_explanation'] ) ) {
162 + $question['answer_explanation'] = addslashes( $question['answer_explanation'] );
163 + }
164 +
165 + $question_settings = $question['question_settings'];
166 +
167 + if ( isset( $question_settings['question_type'] ) && ! in_array( $question_settings['question_type'], $question_types ) ) {
168 + continue;
169 + }
170 +
171 + if ( isset( $question_settings['question_type'] ) ) {
172 + if ( 'single_choice' === $question_settings['question_type'] ) {
173 + $question_settings['question_type'] = 'multiple_choice';
174 + }
175 +
176 + if ( 'image_matching' === $question_settings['question_type'] ) {
177 + $question_settings['is_image_matching'] = true;
178 + }
179 + }
180 +
181 + $question['question_settings'] = maybe_serialize( $question_settings );
182 +
183 + if ( isset( $question['question_id'] ) ) {
184 + unset( $question['question_id'] );
185 + }
186 +
187 + array_push( $final_questions, $question );
188 + }
189 +
190 + foreach ( $answers as $answer ) {
191 + if ( ! $from_course ) {
192 + $rules = array(
193 + 'belongs_question_id' => 'required|numeric',
194 + 'answer_title' => 'required',
195 + 'question_type' => '',
196 + 'belongs_question_type' => 'required|match_string:' . implode( ',', $question_types ),
197 + );
198 +
199 + $validate = ValidationHelper::validate( $rules, $answer );
200 +
201 + if ( ! $validate->success ) {
202 + continue;
203 + }
204 + }
205 +
206 + if ( isset( $answer['belongs_question_type'] ) ) {
207 + if ( 'single_choice' === $answer['belongs_question_type'] ) {
208 + $answer['belongs_question_type'] = 'multiple_choice';
209 + }
210 +
211 + if ( 'image_matching' === $answer['belongs_question_type'] ) {
212 + $answer['belongs_question_type'] = 'matching';
213 + }
214 + }
215 +
216 + if ( isset( $answer['answer_title'] ) ) {
217 + $answer['answer_title'] = addslashes( $answer['answer_title'] );
218 + }
219 +
220 + if ( $answer['image_url'] ) {
221 + $upload_data = Helper::upload_file_by_url( $answer['image_url'] );
222 +
223 + if ( ! is_wp_error( $upload_data ) ) {
224 + $answer['image_id'] = $upload_data['id'];
225 + }
226 + }
227 +
228 + unset( $answer['image_url'] );
229 +
230 + if ( isset( $answer['answer_id'] ) ) {
231 + unset( $answer['answer_id'] );
232 + }
233 +
234 + array_push( $final_answers, $answer );
235 + }
236 +
237 + return array(
238 + 'question' => $final_questions,
239 + 'answers' => $final_answers,
240 + );
241 + }
242 +
243 +
244 + /**
245 + * Bulk save quiz question and answer into database.
246 + *
247 + * @since 3.6.0
248 + *
249 + * @param array $quiz_questions_answers the quiz question answers to save.
250 + * @param bool $from_course is the import for course.
251 + * @param int $parent_id the parent course id.
252 + * @param int $quiz_id the quiz id.
253 + *
254 + * @return bool|\WP_Error
255 + */
256 + public function save_quiz_questions_answers( $quiz_questions_answers, $from_course = false, $parent_id = 0, $quiz_id = 0 ) {
257 + global $wpdb;
258 +
259 + $result = true;
260 +
261 + $table_question = "{$wpdb->prefix}tutor_quiz_questions";
262 + $table_answer = "{$wpdb->prefix}tutor_quiz_question_answers";
263 +
264 + $questions = $quiz_questions_answers['question'];
265 + $answers = $quiz_questions_answers['answers'];
266 + $previous_question_ids = array_column( $questions, 'question_id' );
267 + $previous_answer_ids = $answers ? array_filter( array_column( $answers, 'answer_id' ) ) : null; // filter to remove null values.
268 + $question_ids = array();
269 + $answer_ids = array();
270 +
271 + $quiz_questions_answers = $this->prepare_quiz_questions_answers( $quiz_questions_answers, $from_course );
272 +
273 + $answers = $quiz_questions_answers['answers'];
274 + $questions = $quiz_questions_answers['question'];
275 +
276 + $courses_map = ContentMapHandler::get_content_map()['courses'] ?? null;
277 +
278 + try {
279 + $result = QueryHelper::insert_multiple_rows( $table_question, $questions, false, false );
280 + if ( $result ) {
281 + $question_ids = $wpdb->get_results(//phpcs:ignore
282 + "SELECT question_id FROM {$table_question} WHERE question_id >= LAST_INSERT_ID()", //phpcs:ignore
283 + 'ARRAY_N'
284 + );
285 + // Flatten ids.
286 + $question_ids = array_merge( ...$question_ids );
287 + }
288 + } catch ( \Exception $e ) {
289 + return new \WP_Error( 'db_insert_fail', __( 'Error inserting quiz questions', 'tutor-pro' ), $e->getMessage() );
290 + }
291 +
292 + if ( count( $question_ids ) !== count( $previous_question_ids ) ) {
293 + return new \WP_Error( 'question_mismatch_error', __( 'Invalid quiz question data', 'tutor-pro' ) );
294 + }
295 +
296 + if ( $question_ids && $previous_question_ids ) {
297 + $question_ids = array_combine( $previous_question_ids, $question_ids );
298 + }
299 +
300 + if ( $answers && $question_ids ) {
301 + $answers = array_map(
302 + function ( $val ) use ( $question_ids ) {
303 + $val['belongs_question_id'] = $question_ids[ $val['belongs_question_id'] ];
304 + return $val;
305 + },
306 + $answers
307 + );
308 + }
309 +
310 + if ( $answers ) {
311 + try {
312 + $result = QueryHelper::insert_multiple_rows( $table_answer, $answers, false, false );
313 + if ( $result ) {
314 + $answer_ids = $wpdb->get_results(//phpcs:ignore
315 + "SELECT answer_id FROM {$table_answer} WHERE answer_id >= LAST_INSERT_ID()", //phpcs:ignore
316 + 'ARRAY_N'
317 + );
318 + // Flatten ids.
319 + $answer_ids = array_merge( ...$answer_ids );
320 + }
321 + } catch ( \Exception $e ) {
322 + return new \WP_Error( 'db_insert_fail', __( 'Error inserting quiz questions', 'tutor-pro' ), $e->getMessage() );
323 + }
324 +
325 + if ( count( $answer_ids ) !== count( $previous_answer_ids ) ) {
326 + return new \WP_Error( 'answer_mismatch_error', __( 'Invalid quiz answer data', 'tutor-pro' ) );
327 + }
328 +
329 + if ( $answer_ids && $previous_answer_ids ) {
330 + $answer_ids = array_combine( $previous_answer_ids, $answer_ids );
331 + }
332 + }
333 +
334 + if ( $parent_id && $quiz_id && $courses_map ) {
335 + if ( isset( $courses_map[ $parent_id ] ) ) {
336 + $map = $courses_map[ $parent_id ];
337 + if ( isset( $map[ tutor()->quiz_post_type ] ) && isset( $map[ tutor()->quiz_post_type ][ $quiz_id ] ) ) {
338 + $quiz_map = $map[ tutor()->quiz_post_type ][ $quiz_id ];
339 + $quiz_map['question'] = $question_ids;
340 + $quiz_map['answers'] = $answer_ids;
341 +
342 + $map[ tutor()->quiz_post_type ][ $quiz_id ] = $quiz_map;
343 + }
344 +
345 + $courses_map[ $parent_id ] = $map;
346 + }
347 + }
348 +
349 + ContentMapHandler::update_content_map( 'courses', $courses_map );
350 +
351 + unset( $questions );
352 + unset( $answers );
353 + unset( $previous_question_ids );
354 + unset( $previous_answer_ids );
355 + unset( $question_ids );
356 + unset( $answer_ids );
357 + unset( $courses_map );
358 +
359 + return $result;
360 + }
361 +
362 + /**
363 + * Insert quiz contents.
364 + *
365 + * @since 3.8.1
366 + *
367 + * @param array $content the list of quiz contents.
368 + * @param boolean $keep_media_files whether to keep media files.
369 + * @param int $parent_id the parent course id.
370 + *
371 + * @return int|\WP_Error
372 + */
373 + public function insert_quiz( $content, $keep_media_files = false, $parent_id = 0 ) {
374 + global $wpdb;
375 +
376 + $previous_quiz_id = $content['ID'] ?? 0;
377 + $meta = $content['meta'] ?? null;
378 + $question_answer = $content['question_answer'] ?? null;
379 + $question = $content['question'] ?? null;
380 + $answers = $content['answers'] ?? null;
381 +
382 + $courses_map = ContentMapHandler::get_content_map()['courses'] ?? null;
383 +
384 + $content = Helper::unset_post_data( $content );
385 +
386 + $content_id = wp_insert_post( $content, true, true );
387 +
388 + if ( is_wp_error( $content_id ) ) {
389 + return $content_id;
390 + }
391 +
392 + if ( get_post_type( $content_id ) === ContentModel::QUESTION_POST_TYPE ) {
393 + $quiz_question_answer = $this->flatten_quiz_question_answer( null, $content_id, $question, $answers );
394 +
395 + $result = $this->save_quiz_questions_answers( $quiz_question_answer );
396 + if ( is_wp_error( $result ) ) {
397 + ErrorHandler::set_error( $content['post_type'], 'Error adding question answer for quiz : ' . $content['post_title'] );
398 + }
399 + }
400 +
401 + if ( $meta && tutor()->quiz_post_type === get_post_type( $content_id ) ) {
402 + $meta = Helper::prepare_meta( $content_id, $meta, $keep_media_files );
403 + try {
404 + QueryHelper::insert_multiple_rows( $wpdb->postmeta, $meta, false, false );
405 + } catch ( \Throwable $th ) {
406 + ErrorHandler::set_error( $content['post_type'], 'Error saving meta value for quiz : ' . $content['post_title'] );
407 + }
408 + }
409 +
410 + if ( get_tutor_post_types( 'quiz' ) === get_post_type( $content_id ) ) {
411 + if ( $courses_map && $parent_id ) {
412 + if ( isset( $courses_map[ $parent_id ] ) ) {
413 + $map = $courses_map[ $parent_id ];
414 + $map[ tutor()->quiz_post_type ][ $previous_quiz_id ] = array( 'quiz_id' => $content_id );
415 + $courses_map[ $parent_id ] = $map;
416 + }
417 + }
418 +
419 + ContentMapHandler::update_content_map( 'courses', $courses_map );
420 + unset( $courses_map );
421 +
422 + if ( $question_answer ) {
423 + $quiz_question_answer = $this->flatten_quiz_question_answer( array( $content_id => $question_answer ) );
424 + $this->save_quiz_questions_answers( $quiz_question_answer, false, $parent_id, $previous_quiz_id );
425 + }
426 + }
427 +
428 + unset( $meta );
429 + unset( $quiz_question_answer );
430 + unset( $question );
431 + unset( $answers );
432 +
433 + return $content_id;
434 + }
435 + }
436 +