PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.7
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.7
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 4.2.1 All 138 releases
learnpress / inc / Services / SampleDataService.php

SampleDataService.php in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses 4.4.7, at inc/Services/SampleDataService.php

678 lines 17.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class SampleDataService
4 *
5 * Create and delete sample course data.
6 *
7 * @since 4.4.5
8 * @version 1.0.0
9 */
10
11 namespace LearnPress\Services;
12
13 use Exception;
14 use LearnPress\Databases\PostDB;
15 use LearnPress\Databases\QuestionAnswersDB;
16 use LearnPress\Databases\QuizQuestionsDB;
17 use LearnPress\Filters\PostFilter;
18 use LearnPress\Filters\QuestionAnswersFilter;
19 use LearnPress\Filters\QuizQuestionsFilter;
20 use LearnPress\Helpers\Singleton;
21 use LearnPress\Models\CourseModel;
22 use LearnPress\Models\CoursePostModel;
23 use LearnPress\Models\CourseSectionItemModel;
24 use LearnPress\Models\CourseSectionModel;
25 use LearnPress\Models\LessonPostModel;
26 use LearnPress\Models\PostModel;
27 use LearnPress\Models\Question\QuestionPostModel;
28 use LearnPress\Models\Quiz\QuizQuestionModel;
29 use LearnPress\Models\QuizPostModel;
30 use LP_WP_Filesystem;
31 use Throwable;
32 use WP_Error;
33
34 defined( 'ABSPATH' ) || exit;
35
36 /**
37 * Class SampleDataService
38 */
39 class SampleDataService {
40 use Singleton;
41
42 /**
43 * Singleton init.
44 */
45 public function init(): void {}
46
47 /**
48 * Meta key to mark sample data.
49 */
50 public const KEY_META_SAMPLE_DATA = '_lp_sample_data';
51
52 /**
53 * @var array
54 */
55 protected $section_range = array( 3, 3 );
56
57 /**
58 * @var array
59 */
60 protected $item_range = array( 5, 10 );
61
62 /**
63 * @var array
64 */
65 protected $question_range = array( 2, 5 );
66
67 /**
68 * @var array
69 */
70 protected $answer_range = array( 2, 5 );
71
72 /**
73 * @var int
74 */
75 protected $max_content_paragraph = 5;
76
77 /**
78 * @var string
79 */
80 protected $dummy_text = '';
81
82 /**
83 * Install sample course data.
84 *
85 * @param array $params
86 *
87 * @return array
88 * @throws Exception
89 */
90 public function install( array $params ): CoursePostModel {
91 $dummy_text = LP_WP_Filesystem::instance()->file_get_contents( LP_PLUGIN_PATH . '/dummy-data/dummy-text.txt' );
92 $this->dummy_text = preg_split( '!\s!', $dummy_text );
93
94 $section_range = $params['section_range'] ?? array();
95 if ( is_array( $section_range ) && 2 === count( $section_range ) ) {
96 $this->section_range = array_map( 'intval', $section_range );
97 }
98
99 $item_range = $params['item_range'] ?? array();
100 if ( is_array( $item_range ) && 2 === count( $item_range ) ) {
101 $this->item_range = array_map( 'intval', $item_range );
102 }
103
104 $question_range = $params['question_range'] ?? array();
105 if ( is_array( $question_range ) && 2 === count( $question_range ) ) {
106 $this->question_range = array_map( 'intval', $question_range );
107 }
108
109 $answer_range = $params['answer_range'] ?? array();
110 if ( is_array( $answer_range ) && 2 === count( $answer_range ) ) {
111 $this->answer_range = array_map( 'intval', $answer_range );
112 }
113
114 $data = array(
115 'price' => floatval( $params['price'] ?? 0 ),
116 'name' => sanitize_text_field( $params['name'] ?? '' ),
117 );
118
119 $coursePostModel = $this->create_course( $data );
120 $course_id = $coursePostModel->get_id();
121
122 $this->create_sections( $coursePostModel );
123
124 $courseModel = CourseModel::find( $course_id, true );
125 // Unset value of keys for calculate again.
126 unset( $courseModel->first_item_id );
127 unset( $courseModel->total_items );
128 unset( $courseModel->sections_items );
129 unset( $courseModel->meta_data->_lp_final_quiz );
130 $courseModel->get_first_item_id();
131 $courseModel->get_total_items();
132 $courseModel->get_section_items();
133 $courseModel->get_final_quiz();
134 $courseModel->save();
135
136 return $coursePostModel;
137 }
138
139 /**
140 * Uninstall sample data.
141 *
142 * @return void
143 * @throws Exception
144 */
145 public function uninstall() {
146 $posts = $this->get_sample_posts();
147 if ( ! $posts ) {
148 throw new Exception( esc_html__( 'No data sample.', 'learnpress' ) );
149 }
150
151 foreach ( $posts as $post ) {
152 $post_id = (int) $post->ID;
153
154 switch ( $post->post_type ) {
155 case LP_COURSE_CPT:
156 $this->delete_sample_course( $post_id );
157 break;
158 case LP_QUIZ_CPT:
159 $this->delete_sample_quiz( $post_id );
160
161 $quizPostModel = QuizPostModel::find( $post_id );
162 if ( $quizPostModel instanceof QuizPostModel ) {
163 $quizPostModel->delete();
164 }
165 break;
166 case LP_QUESTION_CPT:
167 $this->delete_sample_question_answers( $post_id );
168
169 $questionPostModel = QuestionPostModel::find( $post_id );
170 if ( $questionPostModel instanceof QuestionPostModel ) {
171 $questionPostModel->delete();
172 }
173 break;
174 case LP_LESSON_CPT:
175 $lessonPostModel = LessonPostModel::find( $post_id );
176 if ( $lessonPostModel instanceof LessonPostModel ) {
177 $lessonPostModel->delete();
178 }
179 break;
180 default:
181 $postModel = PostModel::find_by_id( $post_id );
182 if ( $postModel instanceof PostModel ) {
183 $postModel->delete();
184 }
185 break;
186 }
187 }
188 }
189
190 /**
191 * Get all posts marked as "sample data".
192 *
193 * Use PostDB/PostFilter instead of raw $wpdb query.
194 *
195 * @return array
196 * @throws Exception
197 */
198 protected function get_sample_posts(): array {
199 $db = PostDB::getInstance();
200 $post_types = array( LP_COURSE_CPT, LP_LESSON_CPT, LP_QUIZ_CPT, LP_QUESTION_CPT );
201 $posts = array();
202
203 foreach ( $post_types as $post_type ) {
204 $filter = new PostFilter();
205 $filter->post_type = $post_type;
206 $filter->only_fields = array( 'p.ID', 'p.post_type' );
207 $filter->join[] = sprintf(
208 "INNER JOIN %s AS pm ON p.ID = pm.post_id AND pm.meta_key = '%s' AND pm.meta_value = 'yes'",
209 $db->tb_postmeta,
210 self::KEY_META_SAMPLE_DATA
211 );
212 $filter->run_query_count = false;
213
214 $rows = $db->get_posts( $filter );
215 if ( is_array( $rows ) ) {
216 $posts = array_merge( $posts, $rows );
217 }
218 }
219
220 return $posts;
221 }
222
223 /**
224 * Delete a sample course: its section items (lessons/quizzes with their
225 * questions/answers), sections, the course row and the course post.
226 *
227 * @param int $course_id
228 *
229 * @return void
230 * @throws Exception
231 */
232 protected function delete_sample_course( int $course_id ) {
233 // Not load from cache, to get from Posts table.
234 $courseModel = CourseModel::find( $course_id );
235
236 if ( $courseModel instanceof CourseModel ) {
237 $sections_items = $courseModel->get_section_items();
238
239 foreach ( $sections_items as $section ) {
240 $section_id = $section->section_id ?? 0;
241
242 // Only delete section, not delete items inside section.
243 $courseSectionModel = CourseSectionModel::find( $section_id, $course_id, false );
244 if ( $courseSectionModel instanceof CourseSectionModel ) {
245 $courseSectionModel->delete();
246 }
247 }
248
249 // No need delete here, delete via hook when trigger $coursePostModel->delete().
250 }
251
252 // Delete course post and via hook will delete course on the learnpress_courses table.
253 $coursePostModel = CoursePostModel::find_by_id( $course_id );
254 if ( $coursePostModel instanceof CoursePostModel ) {
255 $coursePostModel->delete();
256 }
257 }
258
259 /**
260 * Delete quiz questions and their answers of a sample quiz.
261 * Does not delete the quiz post itself.
262 *
263 * @param int $quiz_id
264 *
265 * @return void
266 * @throws Exception
267 */
268 protected function delete_sample_quiz( int $quiz_id ) {
269 $db = QuizQuestionsDB::getInstance();
270 $filter = new QuizQuestionsFilter();
271 $filter->quiz_id = $quiz_id;
272 $filter->query_count = false;
273 $filter->run_query_count = false;
274 $filter->field_count = QuizQuestionsFilter::COL_QUIZ_QUESTION_ID;
275 $quiz_questions = $db->get_quiz_questions( $filter );
276
277 if ( is_array( $quiz_questions ) ) {
278 foreach ( $quiz_questions as $quiz_question ) {
279 // Only delete map quiz question, not delete question inside quiz.
280 $quizQuestionModel = new QuizQuestionModel( $quiz_question );
281 $quizQuestionModel->delete();
282 }
283 }
284 }
285
286 /**
287 * Delete all answers of a sample question.
288 *
289 * @param int $question_id
290 *
291 * @return void
292 * @throws Exception
293 */
294 protected function delete_sample_question_answers( int $question_id ) {
295 $db = QuestionAnswersDB::getInstance();
296 $filter = new QuestionAnswersFilter();
297 $filter->where[] = $db->wpdb->prepare( 'AND question_id = %d', $question_id );
298 $filter->collection = $db->tb_lp_question_answers;
299 $db->delete_execute( $filter );
300 }
301
302 /**
303 * Generate content with 'lorem' text.
304 *
305 * @param int $min
306 * @param int $max
307 * @param int $paragraphs
308 *
309 * @return string
310 */
311 protected function generate_content( $min = 100, $max = 500, $paragraphs = 10 ) {
312 $length = rand( $min, $max );
313 $max = sizeof( $this->dummy_text ) - 1;
314 $words = array();
315
316 for ( $i = 0; $i < $length; $i++ ) {
317 $words[] = $this->dummy_text[ rand( 0, $max ) ];
318 }
319
320 $p = array();
321
322 if ( ! $paragraphs ) {
323 $paragraphs = $this->max_content_paragraph;
324 }
325
326 while ( $words && sizeof( $p ) < $paragraphs ) {
327 $len = rand( 10, 20 );
328 $cut = array_splice( $words, 0, $len );
329 $p[] = '<p>' . ucfirst( join( ' ', $cut ) ) . '</p>';
330 }
331
332 return join( '', $p );
333 }
334
335 /**
336 * Generate title with 'lorem' text.
337 *
338 * @param int $min
339 * @param int $max
340 *
341 * @return string
342 */
343 protected function generate_title( $min = 10, $max = 15 ) {
344 $length = rand( $min, $max );
345 $max = sizeof( $this->dummy_text ) - 1;
346 $words = array();
347 for ( $i = 0; $i < $length; $i++ ) {
348 $words[] = $this->dummy_text[ rand( 0, $max ) ];
349 }
350
351 return ucfirst( join( ' ', $words ) );
352 }
353
354 /**
355 * Create course.
356 *
357 * @param array $data
358 *
359 * @return CoursePostModel
360 * @throws Exception
361 */
362 protected function create_course( array $data ): CoursePostModel {
363 $title = $data['name'] ?? '';
364
365 $meta_input = array(
366 CoursePostModel::META_KEY_DURATION => '10 week',
367 CoursePostModel::META_KEY_SAMPLE_DATA => 'yes',
368 CoursePostModel::META_KEY_LEVEL => 'all',
369 );
370
371 // Set price.
372 if ( $data['price'] > 0 ) {
373 $meta_input[ CoursePostModel::META_KEY_PRICE ] = $data['price'];
374 $meta_input[ CoursePostModel::META_KEY_REGULAR_PRICE ] = $data['price'];
375 }
376
377 // Requirements.
378 $requirements = array();
379 for ( $i = 0, $n = rand( 5, 10 ); $i <= $n; $i++ ) {
380 $requirements[] = $this->generate_title();
381 }
382 $meta_input[ CoursePostModel::META_KEY_REQUIREMENTS ] = $requirements;
383
384 // Target audiences.
385 $target_audiences = array();
386 for ( $i = 0, $n = rand( 5, 10 ); $i <= $n; $i++ ) {
387 $target_audiences[] = $this->generate_title();
388 }
389 $meta_input[ CoursePostModel::META_KEY_TARGET ] = $target_audiences;
390
391 // Key features.
392 $key_features = array();
393 for ( $i = 0, $n = rand( 5, 10 ); $i <= $n; $i++ ) {
394 $key_features[] = $this->generate_title();
395 }
396 $meta_input[ CoursePostModel::META_KEY_FEATURES ] = $key_features;
397
398 // FAQs.
399 $faqs = array();
400 for ( $i = 0, $n = rand( 5, 10 ); $i <= $n; $i++ ) {
401 $faqs[] = array( $this->generate_title() . '?', $this->generate_content( 20, 30, 3 ) );
402 }
403 $meta_input[ CoursePostModel::META_KEY_FAQS ] = $faqs;
404
405 // Featured review.
406 $meta_input[ CoursePostModel::META_KEY_FEATURED_REVIEW ] = $this->generate_title( 30, 40 );
407
408 $data_insert = array(
409 'post_title' => ! empty( $title ) ? $title : __( 'Sample course', 'learnpress' ),
410 'post_type' => LP_COURSE_CPT,
411 'post_status' => 'publish',
412 'post_content' => $this->generate_content( 25, 40, 5 ),
413 'post_author' => get_current_user_id(),
414 'meta_input' => $meta_input,
415 );
416
417 $courseService = CourseService::instance();
418 return $courseService->create_info_main( $data_insert );
419 }
420
421 /**
422 * Create sections.
423 *
424 * @param CoursePostModel $coursePostModel
425 * @throws Exception
426 */
427 protected function create_sections( CoursePostModel $coursePostModel ) {
428 $section_length = call_user_func_array( 'rand', $this->section_range );
429
430 for ( $i = 1; $i <= $section_length; $i++ ) {
431 $courseSectionModel = $coursePostModel->add_section(
432 [ 'section_name' => __( 'Section ', 'learnpress' ) . $i ]
433 );
434 $section_id = $courseSectionModel->get_section_id();
435
436 if ( $section_id ) {
437 $this->create_section_items( $courseSectionModel );
438 }
439 }
440 }
441
442 /**
443 * Create section items.
444 *
445 * @param CourseSectionModel $courseSectionModel
446 * @throws Exception
447 */
448 protected function create_section_items( CourseSectionModel $courseSectionModel ) {
449 static $lesson_count = 1;
450 static $quiz_count = 1;
451
452 $order = 0;
453
454 $item_length = call_user_func_array( 'rand', $this->item_range );
455
456 for ( $i = 1; $i < $item_length; $i++ ) {
457 $this->create_lesson_and_add_to_section(
458 $courseSectionModel,
459 __( 'Lesson ', 'learnpress' ) . $lesson_count++
460 );
461 }
462
463 $quiz_id = $this->create_quiz_and_add_to_section(
464 $courseSectionModel,
465 __( 'Quiz ', 'learnpress' ) . $quiz_count++
466 );
467
468 if ( $quiz_id ) {
469 ++$order;
470 }
471 }
472
473 /**
474 * Create lesson.
475 *
476 * @param string $name
477 * @param CourseSectionModel $courseSectionModel
478 * @return CourseSectionItemModel|false
479 * @throws Exception
480 */
481 protected function create_lesson_and_add_to_section( CourseSectionModel $courseSectionModel, string $name ) {
482 $lessonPostModel = new LessonPostModel();
483 $lessonPostModel->post_title = $name;
484 $lessonPostModel->post_status = 'publish';
485 $lessonPostModel->post_author = get_current_user_id();
486 $lessonPostModel->post_type = LP_LESSON_CPT;
487 $lessonPostModel->post_content = $this->generate_content();
488 $lessonPostModel->meta_data->{self::KEY_META_SAMPLE_DATA} = 'yes';
489
490 $lessonPostModel->save();
491
492 $items = [
493 'items' => [
494 [
495 'id' => $lessonPostModel->get_id(),
496 'type' => LP_LESSON_CPT,
497 ],
498 ],
499 ];
500 $courseSectionItems = $courseSectionModel->add_items( $items );
501
502 return $courseSectionItems[0] ?? false;
503 }
504
505 /**
506 * Create quiz.
507 *
508 * @param string $name
509 * @param CourseSectionModel $courseSectionModel
510 *
511 * @return int
512 * @throws Exception
513 */
514 protected function create_quiz_and_add_to_section( CourseSectionModel $courseSectionModel, string $name ): int {
515 $quizPostModel = new QuizPostModel();
516 $quizPostModel->post_title = $name;
517 $quizPostModel->post_status = 'publish';
518 $quizPostModel->post_author = get_current_user_id();
519 $quizPostModel->post_type = LP_QUIZ_CPT;
520 $quizPostModel->post_content = $this->generate_content( 25, 40, 2 );
521 $quizPostModel->meta_data->{self::KEY_META_SAMPLE_DATA} = 'yes';
522
523 $quizPostModel->save();
524
525 $quiz_id = $quizPostModel->get_id();
526 if ( $quiz_id ) {
527 $items = [
528 'items' => [
529 [
530 'id' => $quizPostModel->get_id(),
531 'type' => LP_QUIZ_CPT,
532 ],
533 ],
534 ];
535
536 $courseSectionItems = $courseSectionModel->add_items(
537 $items
538 );
539
540 $this->create_quiz_questions( $quiz_id );
541 }
542
543 return $quiz_id;
544 }
545
546 /**
547 * Create questions of a quiz.
548 *
549 * @param int $quiz_id
550 */
551 protected function create_quiz_questions( $quiz_id ) {
552 static $question_index = 1;
553 global $wpdb;
554
555 $question_count = call_user_func_array( 'rand', $this->question_range );
556 for ( $i = 1; $i <= $question_count; $i++ ) {
557 $data = array(
558 'post_title' => 'Question ' . $question_index++,
559 'post_type' => LP_QUESTION_CPT,
560 'post_status' => 'publish',
561 'post_content' => $this->generate_content( 25, 40, 2 ),
562 );
563
564 $question_id = wp_insert_post( $data );
565
566 if ( ! $question_id ) {
567 continue;
568 }
569
570 $type = $this->get_question_type();
571
572 update_post_meta( $question_id, '_lp_type', $type );
573 update_post_meta( $question_id, self::KEY_META_SAMPLE_DATA, 'yes' );
574
575 $quiz_question_data = array(
576 'quiz_id' => $quiz_id,
577 'question_id' => $question_id,
578 );
579
580 $wpdb->insert(
581 $wpdb->learnpress_quiz_questions,
582 $quiz_question_data,
583 array( '%d', '%d' )
584 );
585
586 if ( $wpdb->insert_id ) {
587 $this->create_question_answers( $question_id, $type );
588 } else {
589 error_log( 'create_quiz_questions => ' . $wpdb->last_error );
590 }
591 }
592 }
593
594 /**
595 * Create answers for a question.
596 *
597 * @param int $question_id
598 * @param string $type
599 */
600 protected function create_question_answers( $question_id, $type ) {
601 global $wpdb;
602
603 $answers = $this->get_answers( $type );
604 foreach ( $answers as $order => $answer ) {
605 $data = array(
606 'question_id' => $question_id,
607 'title' => $answer['title'],
608 'value' => $answer['value'],
609 'is_true' => $answer['is_true'],
610 'order' => $order + 1,
611 );
612
613 $wpdb->insert(
614 $wpdb->learnpress_question_answers,
615 $data,
616 array( '%d', '%s', '%s', '%s', '%d' )
617 );
618 }
619 }
620
621 /**
622 * Get random answers by type of question.
623 *
624 * @param string $type
625 *
626 * @return array
627 */
628 protected function get_answers( $type ) {
629 $answers = array();
630
631 $option_count = 'true_or_false' === $type ? 2 : call_user_func_array( 'rand', $this->answer_range );
632
633 for ( $i = 1; $i <= $option_count; $i++ ) {
634 $answers[] = array(
635 'title' => $this->generate_title(),
636 'value' => learn_press_random_value(),
637 'is_true' => 'no',
638 );
639 }
640
641 // Set option is TRUE randomize.
642 if ( 'multi_choice' !== $type ) {
643 $at = rand( 0, sizeof( $answers ) - 1 );
644 $answers[ $at ]['is_true'] = 'yes';
645 $answers[ $at ]['title'] = _x( '[TRUE] - ', 'install-sample-course', 'learnpress' ) . $answers[ $at ]['title'];
646 } else {
647 $has_true_option = false;
648 while ( ! $has_true_option ) {
649 foreach ( $answers as $k => $v ) {
650 $answers[ $k ]['is_true'] = rand( 0, 100 ) % 2 ? 'yes' : 'no';
651
652 if ( 'yes' === $answers[ $k ]['is_true'] ) {
653 $answers[ $k ]['title'] = _x( ' [TRUE] - ', 'install-sample-course', 'learnpress' ) . $answers[ $k ]['title'];
654 $has_true_option = true;
655 }
656 }
657 }
658 }
659
660 return $answers;
661 }
662
663 /**
664 * Get random type for a question.
665 *
666 * @return string
667 */
668 protected function get_question_type() {
669 $types = array(
670 'true_or_false',
671 'single_choice',
672 'multi_choice',
673 );
674
675 return $types[ rand( 0, sizeof( $types ) - 1 ) ];
676 }
677 }
678