LLMS_Generator_Courses
LLMS_Generator_Courses class
Source Source
File: includes/class-llms-generator-courses.php
class LLMS_Generator_Courses extends LLMS_Abstract_Generator_Posts { /** * Exception code: Raw data missing required data. * * @var int */ const ERROR_GEN_MISSING_REQUIRED = 2000; /** * Exception code: Raw data in an invalid format. * * @var int */ const ERROR_GEN_INVALID_FORMAT = 2001; /** * Add taxonomy terms to a course * * @since 3.3.0 * @since 3.7.5 Unknown. * @since 4.7.0 Moved from `LLMS_Generator` and made `protected` instead of `private`. * * @param obj $course_id WP_Post ID of a Course. * @param array $raw_terms Array of raw term arrays. * @return void */ protected function add_course_terms( $course_id, $raw_terms ) { $taxes = array( 'course_cat' => 'categories', 'course_difficulty' => 'difficulty', 'course_tag' => 'tags', 'course_track' => 'tracks', ); foreach ( $taxes as $tax => $key ) { if ( ! empty( $raw_terms[ $key ] ) && is_array( $raw_terms[ $key ] ) ) { // We can only have one difficulty at a time. $append = ( 'difficulty' === $key ) ? false : true; $terms = array(); // Find term id or create it. foreach ( $raw_terms[ $key ] as $term_name ) { if ( empty( $term_name ) ) { continue; } $term_id = $this->get_term_id( $term_name, $tax ); if ( $term_id ) { $terms[] = $term_id; } } wp_set_post_terms( $course_id, $terms, $tax, $append ); } } } /** * Generator called when cloning a course * * @since 4.13.0 * * @param array $raw Raw course data array. * @return int|null WP_Post ID of the generated course or `null` on failure. */ public function clone_course( $raw ) { return $this->generate_course( $this->setup_raw_for_clone( $raw ) ); } /** * Generator called when cloning a lesson * * @since 3.14.8 * @since 4.7.0 Moved from `LLMS_Generator` and made `public` instead of `private`. * @since 4.13.0 Use `setup_raw_for_clone()` to normalize the * * @param array $raw Raw data array. * @return int|WP_Error WP_Post ID of the created lesson on success and an error object on failure. */ public function clone_lesson( $raw ) { return $this->create_lesson( $this->setup_raw_for_clone( $raw ), 0, '', '' ); } /** * Generator called for single course imports * * Converts the single course into a format that can be handled by the bulk courses generator * and invokes that generator. * * @since 3.3.0 * @since 4.7.0 Moved from `LLMS_Generator` and made `public` instead of `private`. * Returns an int on success. * @param array $raw Raw data array. * @return int|null WP_Post ID of the generated course or `null` on failure. */ public function generate_course( $raw ) { $new_raw = array(); foreach ( array( '_generator', '_version', '_source' ) as $meta ) { if ( isset( $raw[ $meta ] ) ) { $new_raw[ $meta ] = $raw[ $meta ]; unset( $raw[ $meta ] ); } } $new_raw['courses'] = array( $raw ); $courses = $this->generate_courses( $new_raw ); return is_array( $courses ) ? $courses[0] : null; } /** * Generator called for bulk course imports * * @since 3.3.0 * @since 4.7.0 Moved from `LLMS_Generator` to `LLMS_Abstract_Generator_Courses`. * Updated method access from `private` to `public`. * Throws an exception in favor of returning `null` when an error is encountered. * Returns an array of generated course IDs on success. * * @param array $raw Raw data array. * @return void * * @throws Exception When invalid `$raw` data is submitted. */ public function generate_courses( $raw ) { if ( empty( $raw['courses'] ) ) { throw new Exception( __( 'Raw data is missing the required "courses" array.', 'lifterlms' ), self::ERROR_GEN_MISSING_REQUIRED ); } elseif ( ! is_array( $raw['courses'] ) ) { throw new Exception( __( 'The raw "courses" item must be an array.', 'lifterlms' ), self::ERROR_GEN_INVALID_FORMAT ); } $courses = array(); foreach ( $raw['courses'] as $raw_course ) { unset( $raw_course['_generator'], $raw_course['_version'] ); $courses[] = $this->create_course( $raw_course ); } $this->handle_prerequisites(); return $courses; } /** * Create a new access plan * * @since 3.3.0 * @since 3.7.3 Unknown. * @since 4.3.3 Use an empty string in favor of `null` for an empty `post_content` field. * @since 4.7.0 Sideload images attached to the post, use `create_post()` from abstract, add hooks. * * @param array $raw Raw Access Plan Data. * @param int $course_id WP Post ID of a LLMS Course to assign the access plan to. * @param int $fallback_author_id Optional. WP User ID to use for the access plan author if no author is supplied in the raw data. Default is `null`. * When not supplied the fall back will be on the current user ID. * @return int */ protected function create_access_plan( $raw, $course_id, $fallback_author_id = null ) { /** * Filter raw course import data prior to generation * * @since 4.7.0 * * @param array $raw Raw course data array. * @param LLMS_Generator $generator Generator instance. */ $raw = apply_filters( 'llms_generator_before_new_access_plan', $raw, $this ); // Force course relationship. $raw['product_id'] = $course_id; $plan = $this->create_post( 'access_plan', $raw, $fallback_author_id ); if ( ! $plan ) { return null; } /** * Action triggered immediately following generation of a new acess plan * * @since 4.7.0 * * @param LLMS_Access_Plan $plan Generated access plan object. * @param array $raw Original raw course data array. * @param LLMS_Generator $generator Generator instance. */ do_action( 'llms_generator_new_access_plan', $plan, $raw, $this ); return $plan->get( 'id' ); } /** * Create a new course * * @since 3.3.0 * @since 3.30.2 Added hooks. * @since 4.3.3 Use an empty string in favor of `null` for empty `post_content` and `post_excerpt` fields. * @since 4.7.0 Import images and reusable blocks found in the post's content and use `create_post()` from abstract. * * @param array $raw Raw course data. * @return int * * @throws Exception When an error is encountered during course creation. */ protected function create_course( $raw ) { /** * Filter raw course import data prior to generation * * @since 3.30.2 * * @param array $raw Raw course data array. * @param LLMS_Generator $generator Generator instance. */ $raw = apply_filters( 'llms_generator_before_new_course', $raw, $this ); // Create the course. $course = $this->create_post( 'course', $raw, get_current_user_id() ); // Add terms to our course. $terms = array(); if ( isset( $raw['difficulty'] ) ) { $terms['difficulty'] = array( $raw['difficulty'] ); } foreach ( array( 'categories', 'tags', 'tracks' ) as $tax ) { if ( isset( $raw[ $tax ] ) ) { $terms[ $tax ] = $raw[ $tax ]; } } $this->add_course_terms( $course->get( 'id' ), $terms ); // Create all access plans. if ( isset( $raw['access_plans'] ) ) { foreach ( $raw['access_plans'] as $plan ) { $this->create_access_plan( $plan, $course->get( 'id' ), $course->get( 'author' ) ); } } // Create all sections. if ( isset( $raw['sections'] ) ) { foreach ( $raw['sections'] as $order => $section ) { $this->create_section( $section, ++$order, $course->get( 'id' ), $course->get( 'author' ) ); } } /** * Action triggered immediately following generation of a new course * * @since 3.30.2 * * @param LLMS_Course $course Generated course object. * @param array $raw Original raw course data array. * @param LLMS_Generator $generator Generator instance. */ do_action( 'llms_generator_new_course', $course, $raw, $this ); return $course->get( 'id' ); } /** * Create a new lesson * * @since 3.3.0 * @since 3.30.2 Added hooks. * @since 4.3.3 Use an empty string in favor of `null` for empty `post_content` and `post_excerpt` fields. * @since 4.7.0 Import images and reusable blocks found in the post's content and use `create_post()` from abstract. * * @param array $raw Raw lesson data. * @param int $order Lesson order within the section (starts at 1). * @param int $section_id WP Post ID of the lesson's parent section. * @param int $course_id WP Post ID of the lesson's parent course. * @param int $fallback_author_id Optional. Author ID to use as a fallback if no raw author data supplied for the lesson. Default is `null`. * When not supplied the fall back will be on the current user ID. * @return int * * @throws Exception When an error is encountered during post creation. */ protected function create_lesson( $raw, $order, $section_id, $course_id, $fallback_author_id = null ) { /** * Filter raw lesson import data prior to generation * * @since 3.30.2 * * @param array $raw Raw lesson data array. * @param int $order Lesson order within the section (starts at 1). * @param int $section_id WP Post ID of the lesson's parent section. * @param int $course_id WP Post ID of the lesson's parent course. * @param int $fallback_author_id Optional author ID to use as a fallback if no raw author data supplied for the lesson. * @param LLMS_Generator $generator Generator instance. */ $raw = apply_filters( 'llms_generator_before_new_lesson', $raw, $order, $section_id, $course_id, $fallback_author_id, $this ); // Force some data. $raw['parent_course'] = $course_id; $raw['parent_section'] = $section_id; $raw['order'] = $order; $raw_quiz = ! empty( $raw['quiz'] ) ? $raw['quiz'] : false; unset( $raw['quiz'] ); $lesson = $this->create_post( 'lesson', $raw, $fallback_author_id ); if ( $raw_quiz ) { $raw_quiz['lesson_id'] = $lesson->get( 'id' ); $lesson->set( 'quiz', $this->create_quiz( $raw_quiz, $lesson->get( 'author' ) ) ); } /** * Action triggered immediately following generation of a new lesson * * @since 3.30.2 * * @param LLMS_Lesson $lesson Generated lesson object. * @param array $raw Original raw lesson data array. * @param LLMS_Generator $generator Generator instance. */ do_action( 'llms_generator_new_lesson', $lesson, $raw, $this ); return $lesson->get( 'id' ); } /** * Creates a new quiz * Creates all questions within the quiz as well * * @since 3.3.0 * @since 3.30.2 Added hooks. * @since 4.3.3 Use an empty string in favor of `null` for an empty `post_content` field. * @since 4.7.0 Sideload images attached to the post and use `create_post()` from abstract. * * @param array $raw Raw quiz data. * @param int $fallback_author_id Optional. Author ID to use as a fallback if no raw author data supplied for the quiz. Default is `null`. * When not supplied the fall back will be on the current user ID. * @return int * * @throws Exception When an error is encountered during post creation. */ protected function create_quiz( $raw, $fallback_author_id = null ) { /** * Filter raw quiz import data prior to generation * * @since 3.30.2 * * @param array $raw Raw quiz data array. * @param int $fallback_author_id Optional author ID to use as a fallback if no raw author data supplied for the quiz. * @param LLMS_Generator $generator Generator instance. */ $raw = apply_filters( 'llms_generator_before_new_quiz', $raw, $fallback_author_id, $this ); $quiz = $this->create_post( 'quiz', $raw, $fallback_author_id ); if ( isset( $raw['questions'] ) ) { $manager = $quiz->questions(); foreach ( $raw['questions'] as $question ) { $this->create_question( $question, $manager, $quiz->get( 'author' ) ); } } /** * Action triggered immediately following generation of a new quiz * * @since 3.30.2 * * @param LLMS_Quiz $quiz Generated quiz object. * @param array $raw Original raw quiz data array. * @param LLMS_Generator $generator Generator instance. */ do_action( 'llms_generator_new_quiz', $quiz, $raw, $this ); return $quiz->get( 'id' ); } /** * Creates a new question * * @since 3.3.0 * @since 3.30.2 Added hooks. * @since 4.7.0 Attempt to sideload images found in the imported post's content and image choices. * * @param array $raw Raw question data. * @param obj $manager Question manager instance. * @param int $author_id Optional. Author ID to use as a fallback if no raw author data supplied for the question. Default is `null`. * When not supplied the fall back will be on the current user ID. * @return int * * @throws Exception When an error is encountered during course creation. */ protected function create_question( $raw, $manager, $author_id ) { /** * Filter raw question import data prior to generation * * @since 3.30.2 * * @param array $raw Raw quiz data array. * @param obj $manager Question manager instance. * @param int $author_id Optional author ID to use as a fallback if no raw author data supplied for the question. * @param LLMS_Generator $generator Generator instance. */ $raw = apply_filters( 'llms_generator_before_new_question', $raw, $manager, $author_id, $this ); unset( $raw['parent_id'] ); $question_id = $manager->create_question( array_merge( array( 'post_status' => 'publish', 'post_author' => $author_id, ), $raw ) ); if ( ! $question_id ) { throw new Exception( __( 'Error creating the question post object.', 'lifterlms' ), self::ERROR_CREATE_POST ); } $question = llms_get_post( $question_id ); $this->store_temp_id( $raw, $question ); if ( isset( $raw['choices'] ) ) { foreach ( $raw['choices'] as $choice ) { unset( $choice['question_id'] ); $question->create_choice( $this->maybe_sideload_choice_image( $choice, $question_id ) ); } } // Set all metadata. foreach ( array_keys( $question->get_properties() ) as $key ) { if ( isset( $raw[ $key ] ) ) { $question->set( $key, $raw[ $key ] ); } } $this->sideload_images( $question, $raw ); /** * Action triggered immediately following generation of a new question * * @since 3.30.2 * * @param LLMS_Question $question Generated question object. * @param array $raw Original raw question data array. * @param obj $manager Question manager instance. * @param LLMS_Generator $generator Generator instance. */ do_action( 'llms_generator_new_question', $question, $raw, $manager, $this ); return $question->get( 'id' ); } /** * Creates a new section * * Creates all lessons within the section data. * * @since 3.3.0 * @since 3.30.2 Added hooks. * @since 4.7.0 Use `create_post()` from abstract. * * @param array $raw Raw section data. * @param int $order Order within the course (starts at 1). * @param int $course_id WP Post ID of the parent course. * @param int $fallback_author_id Optional. Author ID to use as a fallback if no raw author data supplied for the section. * When not supplied the fall back will be on the current user ID. * @return int * * @throws Exception When an error is encountered during course creation. */ protected function create_section( $raw, $order, $course_id, $fallback_author_id = null ) { /** * Filter raw section import data prior to generation * * @since 3.30.2 * * @param array $raw Raw quiz data array. * @param int $order Order within the course (starts at 1). * @param int $course_id WP Post ID of the parent course. * @param int $fallback_author_id Optional author ID to use as a fallback if no raw author data supplied for the section. * @param LLMS_Generator $generator Generator instance. */ $raw = apply_filters( 'llms_generator_before_new_section', $raw, $order, $course_id, $fallback_author_id, $this ); $raw['parent_course'] = $course_id; $raw['order'] = $order; $section = $this->create_post( 'section', $raw, $fallback_author_id ); if ( isset( $raw['lessons'] ) ) { foreach ( $raw['lessons'] as $lesson_order => $lesson ) { $this->create_lesson( $lesson, ++$lesson_order, $section->get( 'id' ), $course_id, $section->get( 'author' ) ); } } /** * Action triggered immediately following generation of a new section * * @since 3.30.2 * * @param LLMS_Section $section Generated section object. * @param array $raw Original raw section data array. * @param LLMS_Generator $generator Generator instance. */ do_action( 'llms_generator_new_section', $section, $raw, $this ); return $section->get( 'id' ); } /** * Updates course and lesson prerequisites * * If the prerequisite was included in the import, updates to the new imported version. * * If the prereq is not included but the source matches, leaves the prereq intact as long as the prereq exists. * * Otherwise removes prerequisite data from the new course / lesson. * * Removes prereq track associations if there's no source or source doesn't match * or if the track doesn't exist. * * @since 3.3.0 * @since 3.24.0 Unknown. * * @return void */ protected function handle_prerequisites() { foreach ( array( 'course', 'lesson' ) as $obj_type ) { $ids = ! empty( $this->tempids[ $obj_type ] ) ? $this->tempids[ $obj_type ] : array(); // Courses have two kinds of prereqs. $has_prereq_param = ( 'course' === $obj_type ) ? 'course' : null; // Loop through all then created lessons. foreach ( $ids as $old_id => $new_id ) { // Instantiate the new instance of the object. $obj = llms_get_post( $new_id ); // If this is a course and there isn't a source or the source doesn't match the current site. // We should remove the track prerequisites. if ( 'course' === $obj_type && ( ! isset( $raw['_source'] ) || get_site_url() !== $raw['_source'] ) ) { // Remove prereq track settings. if ( $obj->has_prerequisite( 'course_track' ) ) { $obj->set( 'prerequisite_track', 0 ); if ( ! $obj->has_prerequisite( 'course' ) ) { $obj->set( 'has_prerequisite', 'no' ); } } } // If the object has a prereq. if ( $obj->has_prerequisite( $has_prereq_param ) ) { // Get the old preqeq's id. $old_prereq = $obj->get( 'prerequisite' ); // If the old prereq is a key in the array of created objects. // We can replace it with the new id. if ( in_array( $old_prereq, array_keys( $ids ) ) ) { $obj->set( 'prerequisite', $ids[ $old_prereq ] ); } elseif ( ! isset( $raw['_source'] ) || get_site_url() !== $raw['_source'] ) { $obj->set( 'has_prerequisite', 'no' ); $obj->set( 'prerequisite', 0 ); } else { $post = get_post( $old_prereq ); // Post doesn't exist or the post type doesn't match, get rid of it. if ( ! $post || $obj_type !== $post->post_type ) { $obj->set( 'has_prerequisite', 'no' ); $obj->set( 'prerequisite', 0 ); } } } } } } /** * Determines if a raw question choice object contains image data that should be sideloaded * * @since 4.7.0 * * @param array $choice Raw choice data array. * @param int $question_id WP_Post ID of the parent question. * @return array Choice data array. */ protected function maybe_sideload_choice_image( $choice, $question_id ) { if ( empty( $choice['choice_type'] ) || 'image' !== $choice['choice_type'] || ! $this->is_image_sideloading_enabled() ) { return $choice; } $id = $this->sideload_image( $question_id, $choice['choice']['src'], 'id' ); if ( is_wp_error( $id ) ) { return $choice; } $choice['choice']['id'] = $id; $choice['choice']['src'] = wp_get_attachment_url( $id ); return $choice; } /** * Modifies incoming raw data when creating a clone of a course or lesson * * When a clone is created, it will automatically have "(Clone)" appended to the existing title * and will be created with the "Draft" status. * * @since 4.13.0 * * @param array $raw Raw data array for the course or lesson. * @return array */ protected function setup_raw_for_clone( $raw ) { /** * Filters the suffix appended to the WP_Post title of a duplicated post when cloning a course or lesson * * @since 4.13.0 * * @param string $status The WP_Post status to use for the duplicate of the post. Default: "draft". * @param array $raw Raw data array passed into the generator. * @param LLMS_Generator $generator Generator instance. */ $raw['title'] .= apply_filters( 'llms_generator_cloned_post_title_suffix', sprintf( ' (%s)', __( 'Clone', 'lifterlms' ) ), $raw, $this ); /** * Filters the WP_Post status used for the duplicated post when cloning a course or lesson * * @since 4.13.0 * * @param string $status The WP_Post status to use for the duplicate of the post. Default: "draft". * @param array $raw Raw data array passed into the generator. * @param LLMS_Generator $generator Generator instance. */ $raw['status'] = apply_filters( 'llms_generator_cloned_post_status', 'draft', $raw, $this ); return $raw; } /**
Expand full source code Collapse full source code View on GitHub
Methods Methods
- add_course_terms — Add taxonomy terms to a course
- clone_course — Generator called when cloning a course
- clone_lesson — Generator called when cloning a lesson
- create_access_plan — Create a new access plan
- create_course — Create a new course
- create_lesson — Create a new lesson
- create_question — Creates a new question
- create_quiz — Creates a new quiz Creates all questions within the quiz as well
- create_section — Creates a new section
- generate_course — Generator called for single course imports
- generate_courses — Generator called for bulk course imports
- handle_prerequisites — Updates course and lesson prerequisites
- maybe_sideload_choice_image — Determines if a raw question choice object contains image data that should be sideloaded
- setup_raw_for_clone — Modifies incoming raw data when creating a clone of a course or lesson
Changelog Changelog
Version | Description |
---|---|
4.7.0 | Introduced. |