PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.10
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.10
4.4.10 4.4.9.1 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 All 142 releases
← All changes | inc/Ajax/CourseBuilder/CourseBuilderAjax.php +319 -329 4.3.7 → 4.4.10 View file →
@@ -13,8 +13,10 @@
13 13 use Exception;
14 14 use LearnPress\Ajax\AbstractAjax;
15 15 use LearnPress\CourseBuilder\CourseBuilder;
16 16 use LearnPress\CourseBuilder\CourseBuilderAccessPolicy;
17 +use LearnPress\Helpers\Response;
18 +use LearnPress\Helpers\Template;
17 19 use LearnPress\Models\CourseModel;
18 20 use LearnPress\Models\CoursePostModel;
19 21 use LearnPress\Models\CourseSectionItemModel;
20 22 use LearnPress\Models\LessonPostModel;
@@ -53,22 +55,25 @@
53 55 if ( empty( $params ) ) {
54 56 throw new Exception( 'Error: params invalid!' );
55 57 }
56 58
57 - $params = LP_Helper::json_decode( $params, true );
58 - $course_id = ! empty( $params['course_id'] ) ? (int) $params['course_id'] : 0;
59 - $course_model = CourseModel::find( $course_id, true );
60 - if ( empty( $course_model ) ) {
59 + $params = LP_Helper::json_decode( $params, true );
60 + $course_id = ! empty( $params['course_id'] ) ? (int) $params['course_id'] : 0;
61 + $courseModel = CourseModel::find( $course_id, true );
62 + if ( empty( $courseModel ) ) {
61 63 $params['insert'] = true;
62 64 $params['course_model'] = '';
63 65 } else {
64 66 $params['insert'] = false;
65 - $params['course_model'] = $course_model;
67 + $params['course_model'] = $courseModel;
66 68 }
67 69
68 70 return $params;
69 71 }
70 72
73 + /**
74 + * @throws Exception
75 + */
71 76 public static function check_valid_lesson() {
72 77 $params = wp_unslash( $_REQUEST['data'] ?? '' );
73 78 if ( empty( $params ) ) {
74 79 throw new Exception( 'Error: params invalid!' );
@@ -74,9 +79,9 @@
74 79 throw new Exception( 'Error: params invalid!' );
75 80 }
76 81
77 82 $params = LP_Helper::json_decode( $params, true );
78 - $lesson_id = ! empty( $params['lesson_id'] ) ? (int) $params['lesson_id'] : 0;
83 + $lesson_id = absint( $params['lesson_id'] ?? 0 );
79 84 $lesson_model = LessonPostModel::find( $lesson_id, true );
80 85 if ( empty( $lesson_model ) ) {
81 86 $params['insert'] = true;
82 87 $params['lesson_model'] = '';
@@ -213,13 +218,13 @@
213 218 * Mirrors wp-admin behavior for instructors without publish capability.
214 219 *
215 220 * @param string $requested_status
216 221 * @param bool $insert
217 - * @param CourseModel|null $course_model
222 + * @param CourseModel|null $courseModel
218 223 *
219 224 * @return string
220 225 */
221 - protected function normalize_course_status_for_save( string $requested_status, bool $insert, ?CourseModel $course_model ): string {
226 + protected function normalize_course_status_for_save( string $requested_status, bool $insert, ?CourseModel $courseModel ): string {
222 227 $allowed_statuses = [ 'publish', 'future', 'draft', 'pending', 'private' ];
223 228 if ( ! in_array( $requested_status, $allowed_statuses, true ) ) {
224 229 $requested_status = 'draft';
225 230 }
@@ -232,9 +237,9 @@
232 237 $is_instructor_user = current_user_can( LP_TEACHER_ROLE );
233 238 $required_review = LP_Settings::get_option( 'required_review', 'yes' ) === 'yes';
234 239 $can_publish_course = current_user_can( 'publish_' . LP_COURSE_CPT . 's' );
235 240
236 - $current_status = $insert ? 'auto-draft' : (string) ( $course_model->post_status ?? 'draft' );
241 + $current_status = $insert ? 'auto-draft' : (string) ( $courseModel->post_status ?? 'draft' );
237 242 $is_public_state = $this->is_public_post_status( $current_status );
238 243
239 244 // Require moderation for instructors when review is enabled.
240 245 if ( $is_instructor_user && $required_review && ! $is_public_state && in_array( $requested_status, [ 'publish', 'private', 'future' ], true ) ) {
@@ -334,9 +339,9 @@
334 339
335 340 $insert_post_data = array(
336 341 'post_type' => LP_COURSE_CPT,
337 342 'post_title' => $course_title,
338 - 'post_content' => wp_unslash( $data['course_description'] ?? '' ),
343 + 'post_content' => Template::sanitize_html_content( $data['course_description'] ?? '' ),
339 344 'post_status' => $course_status,
340 345 'tax_input' => array(
341 346 'course_category' => $categories,
342 347 'course_tag' => $tags,
@@ -404,9 +409,9 @@
404 409 $categories = ! empty( $data['course_categories'] ) ? array_map( 'absint', explode( ',', $data['course_categories'] ) ) : array();
405 410 $tags = ! empty( $data['course_tags'] ) ? array_map( 'absint', explode( ',', $data['course_tags'] ) ) : array();
406 411
407 412 $courseModel->post_title = $course_title;
408 - $courseModel->post_content = wp_unslash( $data['course_description'] ?? '' );
413 + $courseModel->post_content = Template::sanitize_html_content( $data['course_description'] ?? '' );
409 414
410 415 wp_set_post_terms( $courseModel->ID, $categories, 'course_category' );
411 416 wp_set_post_terms( $courseModel->ID, $tags, 'course_tag' );
412 417 }
@@ -577,8 +582,19 @@
577 582 $this->save_assessment_settings_to_model( $courseModel, $data );
578 583
579 584 // Author settings
580 585 $this->save_author_settings_to_model( $courseModel, $data );
586 +
587 + /**
588 + * Allow addons to persist their own course settings fields added to the
589 + * settings metabox tabs (e.g. Co-Instructor) when saving from Course Builder.
590 + *
591 + * @param CoursePostModel $courseModel Course model being saved.
592 + * @param array $data Submitted settings data.
593 + *
594 + * @since 4.3.8
595 + */
596 + do_action( 'learn-press/course-builder/save-course-settings', $courseModel, $data );
581 597 }
582 598
583 599 /**
584 600 * Save general settings to CourseModel
@@ -712,8 +728,27 @@
712 728 }
713 729 }
714 730
715 731 /**
732 + * Normalize an extra-info field value to an array.
733 + *
734 + * The Course Builder sends these fields as arrays (one entry per input);
735 + * older paths may send a comma-separated string. Splitting on comma must
736 + * never be applied to array input, or values that legitimately contain a
737 + * comma get broken into multiple entries.
738 + *
739 + * @param mixed $value
740 + * @return array
741 + */
742 + protected function extra_field_to_array( $value ): array {
743 + if ( is_array( $value ) ) {
744 + return $value;
745 + }
746 +
747 + return $value !== '' ? explode( ',', (string) $value ) : [];
748 + }
749 +
750 + /**
716 751 * Save extra info settings to CourseModel
717 752 *
718 753 * @param CoursePostModel $courseModel
719 754 * @param array $data
@@ -720,11 +755,10 @@
720 755 */
721 756 protected function save_extra_settings_to_model( CoursePostModel &$courseModel, array $data ) {
722 757 // Requirements
723 758 if ( isset( $data['_lp_requirements'] ) ) {
724 - $requirements = ! empty( $data['_lp_requirements'] ) ? explode( ',', $data['_lp_requirements'] ) : [];
725 759 $requirements = array_filter(
726 - $requirements,
760 + $this->extra_field_to_array( $data['_lp_requirements'] ),
727 761 function ( $item ) {
728 762 return ! is_null( $item ) && $item !== '';
729 763 }
730 764 );
@@ -731,11 +765,10 @@
731 765 $courseModel->meta_data->{CoursePostModel::META_KEY_REQUIREMENTS} = array_map( 'sanitize_text_field', array_values( $requirements ) );
732 766 }
733 767
734 768 if ( isset( $data['_lp_target_audiences'] ) ) {
735 - $target_audiences = ! empty( $data['_lp_target_audiences'] ) ? explode( ',', $data['_lp_target_audiences'] ) : [];
736 769 $target_audiences = array_filter(
737 - $target_audiences,
770 + $this->extra_field_to_array( $data['_lp_target_audiences'] ),
738 771 function ( $item ) {
739 772 return ! is_null( $item ) && $item !== '';
740 773 }
741 774 );
@@ -742,11 +775,10 @@
742 775 $courseModel->meta_data->{CoursePostModel::META_KEY_TARGET} = array_map( 'sanitize_text_field', array_values( $target_audiences ) );
743 776 }
744 777
745 778 if ( isset( $data['_lp_key_features'] ) ) {
746 - $key_features = ! empty( $data['_lp_key_features'] ) ? explode( ',', $data['_lp_key_features'] ) : [];
747 779 $key_features = array_filter(
748 - $key_features,
780 + $this->extra_field_to_array( $data['_lp_key_features'] ),
749 781 function ( $item ) {
750 782 return ! is_null( $item ) && $item !== '';
751 783 }
752 784 );
@@ -754,10 +786,10 @@
754 786 }
755 787
756 788 // FAQs
757 789 if ( isset( $data['_lp_faqs_question'] ) ) {
758 - $questions = ! empty( $data['_lp_faqs_question'] ) ? explode( ',', $data['_lp_faqs_question'] ) : [];
759 - $answers = ! empty( $data['_lp_faqs_answer'] ) ? explode( ',', $data['_lp_faqs_answer'] ) : [];
790 + $questions = $this->extra_field_to_array( $data['_lp_faqs_question'] );
791 + $answers = ! empty( $data['_lp_faqs_answer'] ) ? $this->extra_field_to_array( $data['_lp_faqs_answer'] ) : [];
760 792 $faqs = [];
761 793
762 794 if ( ! empty( $questions ) ) {
763 795 foreach ( $questions as $index => $question ) {
@@ -823,13 +855,13 @@
823 855 $response = new LP_REST_Response();
824 856 $response->data = new stdClass();
825 857
826 858 try {
827 - $data = self::check_valid_course();
828 - $course_id = $data['course_id'] ?? 0;
829 - $course_model = $data['course_model'];
859 + $data = self::check_valid_course();
860 + $course_id = $data['course_id'] ?? 0;
861 + $courseModel = $data['course_model'];
830 862
831 - if ( absint( $course_model->post_author ) !== get_current_user_id() && ! current_user_can( 'manage_options' ) ) {
863 + if ( absint( $courseModel->post_author ) !== get_current_user_id() && ! current_user_can( 'manage_options' ) ) {
832 864 throw new Exception( __( 'You are not allowed to duplicate this course', 'learnpress' ) );
833 865 }
834 866
835 867 if ( ! function_exists( 'learn_press_duplicate_post' ) ) {
@@ -856,10 +888,10 @@
856 888 if ( is_wp_error( $new_item_id ) ) {
857 889 throw new Exception( $new_item_id->get_error_message() );
858 890 }
859 891
860 - $course_model = CourseModel::find( $new_item_id, true );
861 - $html = BuilderListCoursesTemplate::render_course( $course_model );
892 + $courseModel = CourseModel::find( $new_item_id, true );
893 + $html = BuilderListCoursesTemplate::render_course( $courseModel );
862 894 $response->status = 'success';
863 895 $response->data->html = $html;
864 896 $response->data->course_id_new = $new_item_id;
865 897 $response->data->redirect_url = CourseBuilder::get_link_course_builder( "courses/{$new_item_id}" );
@@ -876,17 +908,17 @@
876 908 $response = new LP_REST_Response();
877 909 $response->data = new stdClass();
878 910
879 911 try {
880 - $data = self::check_valid_course();
881 - $course_id = $data['course_id'] ?? 0;
882 - $course_model = $data['course_model'];
883 - $status = $data['status'] ?? 'trash';
912 + $data = self::check_valid_course();
913 + $course_id = $data['course_id'] ?? 0;
914 + $courseModel = $data['course_model'];
915 + $status = $data['status'] ?? 'trash';
884 916
885 917 $co_instructor_ids = get_post_meta( $course_id, '_lp_co_teacher', false );
886 918 $co_instructor_ids = ! empty( $co_instructor_ids ) ? $co_instructor_ids : array();
887 919
888 - if ( absint( $course_model->post_author ) !== get_current_user_id() && ! current_user_can( 'manage_options' ) && ! in_array( get_current_user_id(), $co_instructor_ids ) ) {
920 + if ( absint( $courseModel->post_author ) !== get_current_user_id() && ! current_user_can( 'manage_options' ) && ! in_array( get_current_user_id(), $co_instructor_ids ) ) {
889 921 throw new Exception( __( 'You are not allowed to delete this course', 'learnpress' ) );
890 922 }
891 923
892 924 if ( $status === 'delete' ) {
@@ -912,9 +944,9 @@
912 944
913 945 $message = __( 'Course has been moved to draft', 'learnpress' );
914 946 } else {
915 947 // Store original slug before trashing (wp_trash_post adds __trashed suffix)
916 - $original_slug = $course_model->post_name;
948 + $original_slug = $courseModel->post_name;
917 949
918 950 $delete = wp_trash_post( $course_id );
919 951
920 952 if ( ! $delete ) {
@@ -950,18 +982,9 @@
950 982 $response = new LP_REST_Response();
951 983 $response->data = new stdClass();
952 984
953 985 try {
954 - $data = self::check_valid_course();
955 - $course_id = absint( $data['course_id'] ?? 0 );
956 - if ( $course_id > 0 ) {
957 - if ( ! CourseBuilderAccessPolicy::can_edit_course_by_id( $course_id ) ) {
958 - throw new Exception( __( 'You are not allowed to update this course', 'learnpress' ) );
959 - }
960 - } elseif ( ! CourseBuilderAccessPolicy::can_create_item_type( LP_COURSE_CPT ) ) {
961 - throw new Exception( __( 'You are not allowed to create courses', 'learnpress' ) );
962 - }
963 -
986 + $data = self::check_valid_course();
964 987 if ( ! current_user_can( 'edit_lp_courses' ) ) {
965 988 throw new Exception( __( 'You are not allowed to create categories', 'learnpress' ) );
966 989 }
967 990
@@ -1008,18 +1031,9 @@
1008 1031 $response = new LP_REST_Response();
1009 1032 $response->data = new stdClass();
1010 1033
1011 1034 try {
1012 - $data = self::check_valid_course();
1013 - $course_id = absint( $data['course_id'] ?? 0 );
1014 - if ( $course_id > 0 ) {
1015 - if ( ! CourseBuilderAccessPolicy::can_edit_course_by_id( $course_id ) ) {
1016 - throw new Exception( __( 'You are not allowed to update this course', 'learnpress' ) );
1017 - }
1018 - } elseif ( ! CourseBuilderAccessPolicy::can_create_item_type( LP_COURSE_CPT ) ) {
1019 - throw new Exception( __( 'You are not allowed to create courses', 'learnpress' ) );
1020 - }
1021 -
1035 + $data = self::check_valid_course();
1022 1036 if ( ! current_user_can( 'edit_lp_courses' ) ) {
1023 1037 throw new Exception( __( 'You are not allowed to create tags', 'learnpress' ) );
1024 1038 }
1025 1039
@@ -1087,152 +1101,159 @@
1087 1101 * Update Lesson from Course Builder.
1088 1102 * Supports partial updates (title only, description only, or settings only).
1089 1103 *
1090 1104 * @since 4.3
1091 - * @version 1.0.2
1105 + * @version 1.0.3
1092 1106 */
1093 1107 public function builder_update_lesson() {
1094 - $response = new LP_REST_Response();
1108 + $response = new Response();
1095 1109 $response->data = new stdClass();
1096 1110
1097 1111 try {
1098 1112 $data = self::check_valid_lesson();
1099 1113 $lesson_id = $data['lesson_id'] ?? 0;
1100 - $title = $data['lesson_title'] ?? null;
1101 - $description = $data['lesson_description'] ?? null;
1114 + $has_title = array_key_exists( 'lesson_title', $data );
1115 + $has_content = array_key_exists( 'lesson_description', $data );
1116 + $title = LP_Helper::sanitize_params_submitted( $data['lesson_title'] ?? '' );
1117 + $description = LP_Helper::sanitize_params_submitted(
1118 + $data['lesson_description'] ?? '',
1119 + 'html'
1120 + );
1102 1121 $settings = $data['lesson_settings'] ?? false;
1103 1122 $is_elementor = $data['is_elementor'] ?? false;
1104 1123 $return_html = ( $data['return_html'] ?? 'no' ) === 'yes';
1105 1124 $insert = $data['insert'];
1106 - $lesson_slug = ! empty( $data['lesson_permalink'] )
1107 - ? sanitize_title( wp_unslash( (string) $data['lesson_permalink'] ) )
1108 - : '';
1125 + $lesson_slug = LP_Helper::sanitize_params_submitted(
1126 + $data['lesson_permalink'] ?? '',
1127 + 'key'
1128 + );
1109 1129 $course_id = absint( $data['course_id'] ?? 0 );
1110 1130
1111 1131 // Determine target status (draft or publish)
1112 - $target_status = ! empty( $data['lesson_status'] ) && in_array( $data['lesson_status'], [ 'draft', 'publish' ], true )
1113 - ? sanitize_text_field( $data['lesson_status'] )
1114 - : 'publish';
1132 + $target_status = LP_Helper::sanitize_params_submitted(
1133 + $data['lesson_status'] ?? '',
1134 + 'key'
1135 + );
1115 1136 $restore_with_custom_slug = false;
1116 1137
1138 + if ( ( $insert || $has_title ) && empty( $title ) ) {
1139 + throw new Exception( __( 'Lesson title is required', 'learnpress' ) );
1140 + }
1141 +
1117 1142 if ( $insert ) {
1118 - if ( ! CourseBuilderAccessPolicy::can_create_item_type( LP_LESSON_CPT ) ) {
1119 - throw new Exception( __( 'You are not allowed to create lessons', 'learnpress' ) );
1120 - }
1121 -
1122 1143 $insert_arg = array(
1123 - 'post_type' => LP_LESSON_CPT,
1124 - 'post_title' => sanitize_text_field( $title ?? '' ),
1125 - 'post_content' => wp_kses_post( $description ?? '' ),
1144 + 'post_title' => $title,
1145 + 'post_content' => $description,
1126 1146 'post_status' => $target_status,
1147 + 'post_author' => get_current_user_id(),
1127 1148 );
1128 1149
1129 - if ( ! empty( $lesson_slug ) ) {
1130 - $insert_arg['post_name'] = $lesson_slug;
1131 - }
1150 + $lessonPostModelNew = new LessonPostModel( $insert_arg );
1151 + $lessonPostModelNew->check_capabilities_create_item_course();
1132 1152
1133 - $lesson_id = wp_insert_post( $insert_arg, true );
1153 + $lessonPostModelNew->save();
1154 + $lesson_id = $lessonPostModelNew->ID;
1134 1155
1135 - if ( is_wp_error( $lesson_id ) ) {
1136 - throw new Exception( $lesson_id->get_error_message() );
1137 - }
1138 -
1139 - $lesson_model = LessonPostModel::find( $lesson_id, true );
1156 + $lesson_model = $lessonPostModelNew;
1157 + $response->message = __( 'Create lesson successfully', 'learnpress' );
1140 1158 } else {
1141 - $lesson_model = $data['lesson_model'];
1142 -
1143 - if ( ! $lesson_model ) {
1159 + $lesson_model = $data['lesson_model'] ?? null;
1160 + if ( ! $lesson_model instanceof LessonPostModel ) {
1144 1161 throw new Exception( __( 'Lesson not found', 'learnpress' ) );
1145 1162 }
1146 1163
1164 + $lesson_model->check_capabilities_update_item_course();
1165 +
1147 1166 if ( ! $course_id ) {
1148 1167 $course_id = absint( $this->get_course_by_item_id( $lesson_id ) );
1149 1168 }
1150 1169
1151 1170 // Support for co-instructor.
1152 - $co_instructor_ids = get_post_meta( $course_id, '_lp_co_teacher', false );
1171 + /*$co_instructor_ids = get_post_meta( $course_id, '_lp_co_teacher', false );
1153 1172 $co_instructor_ids = ! empty( $co_instructor_ids ) ? $co_instructor_ids : array();
1154 1173
1155 1174 if ( absint( $lesson_model->post_author ) !== get_current_user_id() && ! current_user_can( 'manage_options' ) && ! in_array( get_current_user_id(), $co_instructor_ids ) ) {
1156 1175 throw new Exception( __( 'You are not allowed to update this lesson', 'learnpress' ) );
1157 - }
1176 + }*/
1158 1177
1159 - $update_arg = array(
1178 + /*$update_arg = array(
1160 1179 'ID' => $lesson_id,
1161 1180 'post_type' => LP_LESSON_CPT,
1162 1181 'post_status' => $target_status,
1163 - );
1182 + );*/
1164 1183
1165 - if ( defined( 'ELEMENTOR_VERSION' ) ) {
1184 + if ( defined( 'ELEMENTOR_VERSION' ) && array_key_exists( 'is_elementor', $data ) ) {
1166 1185 \Elementor\Plugin::$instance->documents->get( $lesson_id )->set_is_built_with_elementor( ! empty( $is_elementor ) );
1167 1186 }
1168 1187
1169 - if ( isset( $data['lesson_title'] ) ) {
1170 - $update_arg['post_title'] = sanitize_text_field( $title );
1188 + $must_save_lesson = false;
1189 + if ( $has_title ) {
1190 + $lesson_model->post_title = $title;
1191 + $must_save_lesson = true;
1171 1192 }
1172 1193
1173 - if ( isset( $data['lesson_description'] ) ) {
1174 - $update_arg['post_content'] = wp_kses_post( $description );
1194 + if ( $has_content ) {
1195 + $lesson_model->post_content = $description;
1196 + $must_save_lesson = true;
1175 1197 }
1176 1198
1199 + if ( ! empty( $target_status ) ) {
1200 + $lesson_model->post_status = $target_status;
1201 + $must_save_lesson = true;
1202 + }
1203 +
1177 1204 if ( ! empty( $lesson_slug ) ) {
1178 - $update_arg['post_name'] = $lesson_slug;
1205 + $lesson_model->post_name = $lesson_slug;
1206 + $must_save_lesson = true;
1179 1207 }
1180 1208
1181 - $restore_with_custom_slug = $this->prepare_desired_slug_for_restore( $lesson_id, $target_status, $lesson_slug );
1209 + //$restore_with_custom_slug = $this->prepare_desired_slug_for_restore( $lesson_id, $target_status, $lesson_slug );
1182 1210
1183 - $update = wp_update_post( $update_arg );
1184 -
1185 - if ( is_wp_error( $update ) ) {
1186 - throw new Exception( $update->get_error_message() );
1211 + //$update = wp_update_post( $update_arg );
1212 + if ( $must_save_lesson ) {
1213 + $lesson_model->save();
1187 1214 }
1188 1215
1189 - if ( $restore_with_custom_slug ) {
1216 + /*if ( $restore_with_custom_slug ) {
1190 1217 $this->sync_slug_after_restore( $lesson_id, $lesson_slug );
1191 - }
1218 + }*/
1192 1219
1193 1220 if ( $course_id ) {
1194 - $course_model_cache = CourseModel::find( $course_id, true );
1195 - if ( $course_model_cache ) {
1196 - $course_model_cache->sections_items = null;
1197 - $course_model_cache->save();
1221 + $courseModelCache = CourseModel::find( $course_id, true );
1222 + if ( $courseModelCache ) {
1223 + $courseModelCache->sections_items = null;
1224 + $courseModelCache->save();
1198 1225 }
1199 1226 }
1227 +
1228 + $response->message = __( 'Update lesson successfully', 'learnpress' );
1200 1229 }
1201 1230
1202 - if ( $settings && $lesson_model ) {
1231 + if ( $settings ) {
1203 1232 $this->save_lesson_settings_to_model( $lesson_model, $data );
1204 1233 }
1205 1234
1206 1235 // Remove lesson from curriculum if status is not public
1207 - if ( $target_status !== 'publish' ) {
1236 + if ( ! empty( $target_status ) && $target_status !== 'publish' ) {
1208 1237 $this->remove_course_item_from_curriculum( $lesson_id, $course_id );
1209 1238 }
1210 1239
1211 - do_action( 'learn-press/course-builder/update-lesson', $data, $lesson_model );
1212 -
1213 - $saved_lesson_status = get_post_status( $lesson_id );
1214 - if ( ! is_string( $saved_lesson_status ) || '' === $saved_lesson_status ) {
1215 - $saved_lesson_status = $target_status;
1216 - }
1217 -
1218 1240 $response->status = 'success';
1219 - $response->data->status = $saved_lesson_status;
1220 - $response->data->button_title = $saved_lesson_status === 'publish' ? __( 'Update', 'learnpress' ) : __( 'Publish', 'learnpress' );
1241 + $response->data->status = $lesson_model->post_status;
1242 + $response->data->button_title = $lesson_model->post_status === 'publish' ?
1243 + __( 'Update', 'learnpress' ) :
1244 + __( 'Publish', 'learnpress' );
1221 1245 $response->data->lesson_id_new = $insert ? $lesson_id : '';
1222 1246 $response->message = $target_status === 'draft'
1223 1247 ? esc_html__( 'Lesson saved as draft', 'learnpress' )
1224 1248 : ( $insert ? esc_html__( 'Insert lesson successfully', 'learnpress' ) : esc_html__( 'Update lesson successfully', 'learnpress' ) );
1225 1249
1226 - $saved_lesson_post = get_post( $lesson_id );
1227 - if ( $saved_lesson_post ) {
1228 - $response->data->lesson_slug = $saved_lesson_post->post_name;
1229 - }
1250 + $response->data->lesson_slug = $lesson_model->post_name;
1230 1251
1231 1252 $course_id_of_item = $this->get_course_by_item_id( $lesson_id );
1232 1253 $response->data->permalink_available = false;
1233 1254 $response->data->permalink_notice = $this->get_item_permalink_unavailable_message();
1234 - if ( 'publish' === $saved_lesson_status && $course_id_of_item ) {
1255 + if ( 'publish' === $lesson_model->post_status && $course_id_of_item ) {
1235 1256 $course = learn_press_get_course( $course_id_of_item );
1236 1257 if ( $course ) {
1237 1258 $response->data->permalink_available = true;
1238 1259 $response->data->lesson_permalink = urldecode( $course->get_item_link( $lesson_id ) );
@@ -1238,9 +1259,9 @@
1238 1259 $response->data->lesson_permalink = urldecode( $course->get_item_link( $lesson_id ) );
1239 1260 }
1240 1261 }
1241 1262
1242 - $lesson_model_for_html = LessonPostModel::find( $lesson_id, true );
1263 + $lesson_model_for_html = LessonPostModel::find( $lesson_id, false );
1243 1264 if ( $return_html ) {
1244 1265 $response->data->list_item_html = $lesson_model_for_html
1245 1266 ? BuilderListLessonsTemplate::render_lesson( $lesson_model_for_html )
1246 1267 : '';
@@ -1245,26 +1266,26 @@
1245 1266 ? BuilderListLessonsTemplate::render_lesson( $lesson_model_for_html )
1246 1267 : '';
1247 1268
1248 1269 if ( $course_id ) {
1249 - $course_model_for_html = CourseModel::find( $course_id, true );
1250 - if ( $course_model_for_html && $lesson_model_for_html ) {
1270 + $courseModelForHtml = CourseModel::find( $course_id, true );
1271 + if ( $courseModelForHtml && $lesson_model_for_html ) {
1251 1272 $item = new stdClass();
1252 1273 $item->item_id = $lesson_id;
1253 1274 $item->title = $lesson_model_for_html->post_title;
1254 1275 $item->item_type = LP_LESSON_CPT;
1255 1276 AdminEditCurriculumTemplate::instance()->context_data = [ 'is_course_builder' => true ];
1256 - $response->data->section_item_html = AdminEditCurriculumTemplate::instance()->html_section_item( $course_model_for_html, $item );
1277 + $response->data->section_item_html = AdminEditCurriculumTemplate::instance()->html_section_item( $courseModelForHtml, $item );
1257 1278 }
1258 1279 }
1259 1280 }
1260 1281
1261 - wp_send_json( $response );
1262 - } catch ( Throwable $th ) {
1263 - $response->status = 'error';
1264 - $response->message = $th->getMessage();
1265 - wp_send_json( $response );
1282 + $response->status = Response::STATUS_SUCCESS;
1283 + } catch ( Throwable $e ) {
1284 + $response->message = $e->getMessage();
1266 1285 }
1286 +
1287 + wp_send_json( $response );
1267 1288 }
1268 1289
1269 1290 public function move_trash_lesson() {
1270 1291 $response = new LP_REST_Response();
@@ -1273,9 +1294,9 @@
1273 1294 try {
1274 1295 $data = self::check_valid_lesson();
1275 1296 $lesson_id = $data['lesson_id'] ?? 0;
1276 1297 $status = $data['status'] ?? 'trash';
1277 - $lesson_model = $data['lesson_model'] ?? [];
1298 + $lesson_model = LessonPostModel::find( $lesson_id, false );
1278 1299 $lesson_slug = ! empty( $data['lesson_permalink'] )
1279 1300 ? sanitize_title( wp_unslash( (string) $data['lesson_permalink'] ) )
1280 1301 : '';
1281 1302
@@ -1377,9 +1398,9 @@
1377 1398 }
1378 1399 }
1379 1400
1380 1401 if ( 'delete' !== $status ) {
1381 - $lesson_model_new = LessonPostModel::find( $lesson_id, true );
1402 + $lesson_model_new = LessonPostModel::find( $lesson_id, false );
1382 1403 $response->data->html = $lesson_model_new ? BuilderListLessonsTemplate::render_lesson( $lesson_model_new ) : '';
1383 1404 }
1384 1405
1385 1406 $response->status = 'success';
@@ -1393,10 +1414,13 @@
1393 1414 }
1394 1415
1395 1416 /**
1396 1417 * Save Lesson Settings
1418 + * @throws Exception
1397 1419 */
1398 1420 protected function save_lesson_settings_to_model( LessonPostModel $lessonModel, array $data ) {
1421 + $must_save = 0;
1422 +
1399 1423 if ( isset( $data['_lp_duration'] ) ) {
1400 1424 $duration = ! empty( $data['_lp_duration'] ) ? str_replace( ',', ' ', $data['_lp_duration'] ) : '0 minute';
1401 1425 $explode = explode( ' ', $duration );
1402 1426 $number = (float) $explode[0] < 0 ? 0 : absint( $explode[0] );
@@ -1401,15 +1425,32 @@
1401 1425 $explode = explode( ' ', $duration );
1402 1426 $number = (float) $explode[0] < 0 ? 0 : absint( $explode[0] );
1403 1427 $unit = $explode[1] ?? 'minute';
1404 1428
1405 - $lessonModel->save_meta_value_by_key( '_lp_duration', $number . ' ' . $unit );
1429 + $lessonModel->set_meta_value_by_key( $lessonModel::META_KEY_DURATION, $number . ' ' . $unit );
1430 + $must_save = 1;
1406 1431 }
1407 1432
1408 1433 if ( isset( $data['_lp_preview'] ) ) {
1409 1434 $enable = $data['_lp_preview'] === 'yes';
1410 1435 $lessonModel->set_preview( $enable );
1436 + $must_save = 1;
1411 1437 }
1438 +
1439 + if ( $must_save ) {
1440 + $lessonModel->save();
1441 + }
1442 +
1443 + /**
1444 + * Allow addons to persist their own lesson settings fields added to the
1445 + * settings metabox tabs when saving from Course Builder.
1446 + *
1447 + * @param LessonPostModel $lessonModel Lesson model being saved.
1448 + * @param array $data Submitted settings data.
1449 + *
1450 + * @since 4.3.8
1451 + */
1452 + do_action( 'learn-press/course-builder/save-lesson-settings', $lessonModel, $data );
1412 1453 }
1413 1454
1414 1455 /**
1415 1456 * Save Quiz Settings to QuizPostModel
@@ -1461,8 +1502,19 @@
1461 1502 }
1462 1503 $quizModel->save_meta_value_by_key( $key, $value );
1463 1504 }
1464 1505 }
1506 +
1507 + /**
1508 + * Allow addons to persist their own quiz settings fields added to the
1509 + * settings metabox tabs when saving from Course Builder.
1510 + *
1511 + * @param QuizPostModel $quizModel Quiz model being saved.
1512 + * @param array $data Submitted settings data.
1513 + *
1514 + * @since 4.3.8
1515 + */
1516 + do_action( 'learn-press/course-builder/save-quiz-settings', $quizModel, $data );
1465 1517 }
1466 1518
1467 1519 /**
1468 1520 * Update Quiz from Course Builder.
@@ -1471,117 +1523,92 @@
1471 1523 * @since 4.3
1472 1524 * @version 1.0.2
1473 1525 */
1474 1526 public function builder_update_quiz() {
1475 - $response = new LP_REST_Response();
1527 + $response = new Response();
1476 1528 $response->data = new stdClass();
1477 1529
1478 1530 try {
1479 1531 $data = self::check_valid_quiz();
1480 1532 $quiz_id = $data['quiz_id'] ?? 0;
1481 - $title = $data['quiz_title'] ?? null;
1482 - $description = $data['quiz_description'] ?? null;
1533 + $title = LP_Helper::sanitize_params_submitted( $data['quiz_title'] ?? '' );
1534 + $description = LP_Helper::sanitize_params_submitted(
1535 + $data['quiz_description'] ?? '',
1536 + 'html'
1537 + );
1483 1538 $settings = $data['quiz_settings'] ?? false;
1484 1539 $is_elementor = $data['is_elementor'] ?? false;
1485 1540 $return_html = ( $data['return_html'] ?? 'no' ) === 'yes';
1486 1541 $insert = $data['insert'];
1487 - $quiz_slug = ! empty( $data['quiz_permalink'] )
1488 - ? sanitize_title( wp_unslash( (string) $data['quiz_permalink'] ) )
1489 - : '';
1542 + $quiz_slug = LP_Helper::sanitize_params_submitted(
1543 + $data['quiz_permalink'] ?? '',
1544 + 'key'
1545 + );
1490 1546 $course_id = absint( $data['course_id'] ?? 0 );
1491 1547
1492 1548 // Determine target status (draft or publish)
1493 - $target_status = ! empty( $data['quiz_status'] ) && in_array( $data['quiz_status'], [ 'draft', 'publish' ], true )
1494 - ? sanitize_text_field( $data['quiz_status'] )
1495 - : 'publish';
1496 - $restore_with_custom_slug = false;
1549 + $target_status = LP_Helper::sanitize_params_submitted(
1550 + $data['quiz_status'] ?? '',
1551 + 'key'
1552 + );
1497 1553
1554 + if ( empty( $title ) ) {
1555 + throw new Exception( __( 'Quiz title is required', 'learnpress' ) );
1556 + }
1557 +
1498 1558 if ( $insert ) {
1499 - if ( ! CourseBuilderAccessPolicy::can_create_item_type( LP_QUIZ_CPT ) ) {
1500 - throw new Exception( __( 'You are not allowed to create quizzes', 'learnpress' ) );
1501 - }
1502 -
1503 1559 $insert_arg = array(
1504 - 'post_type' => LP_QUIZ_CPT,
1505 - 'post_title' => sanitize_text_field( $title ?? '' ),
1506 - 'post_content' => wp_kses_post( $description ?? '' ),
1560 + 'post_title' => $title,
1561 + 'post_content' => $description,
1507 1562 'post_status' => $target_status,
1563 + 'post_author' => get_current_user_id(),
1508 1564 );
1509 1565
1510 - if ( ! empty( $quiz_slug ) ) {
1511 - $insert_arg['post_name'] = $quiz_slug;
1512 - }
1566 + $quizPostModelNew = new QuizPostModel( $insert_arg );
1567 + $quizPostModelNew->check_capabilities_create_item_course();
1513 1568
1514 - $quiz_id = wp_insert_post( $insert_arg, true );
1569 + $quizPostModelNew->save();
1570 + $quiz_id = $quizPostModelNew->ID;
1515 1571
1516 - if ( is_wp_error( $quiz_id ) ) {
1517 - throw new Exception( $quiz_id->get_error_message() );
1518 - }
1519 -
1520 - $quiz_model = QuizPostModel::find( $quiz_id, true );
1572 + $quiz_model = $quizPostModelNew;
1573 + $response->message = __( 'Create quiz successfully', 'learnpress' );
1521 1574 } else {
1522 - $quiz_model = $data['quiz_model'];
1523 -
1524 - if ( ! $quiz_model ) {
1575 + $quiz_model = $data['quiz_model'] ?? null;
1576 + if ( ! $quiz_model instanceof QuizPostModel ) {
1525 1577 throw new Exception( __( 'Quiz not found', 'learnpress' ) );
1526 1578 }
1527 1579
1580 + $quiz_model->check_capabilities_update_item_course();
1581 +
1528 1582 if ( ! $course_id ) {
1529 1583 $course_id = absint( $this->get_course_by_item_id( $quiz_id ) );
1530 1584 }
1531 1585
1532 - // Support for co-instructor.
1533 - $co_instructor_ids = get_post_meta( $course_id, '_lp_co_teacher', false );
1534 - $co_instructor_ids = ! empty( $co_instructor_ids ) ? $co_instructor_ids : array();
1535 -
1536 - if ( absint( $quiz_model->post_author ) !== get_current_user_id() && ! current_user_can( 'manage_options' ) && ! in_array( get_current_user_id(), $co_instructor_ids ) ) {
1537 - throw new Exception( __( 'You are not allowed to update this quiz', 'learnpress' ) );
1538 - }
1539 -
1540 - $update_arg = array(
1541 - 'ID' => $quiz_id,
1542 - 'post_type' => LP_QUIZ_CPT,
1543 - 'post_status' => $target_status,
1544 - );
1545 -
1546 - if ( isset( $data['quiz_title'] ) ) {
1547 - $update_arg['post_title'] = sanitize_text_field( $title );
1548 - }
1549 -
1550 - if ( isset( $data['quiz_description'] ) ) {
1551 - $update_arg['post_content'] = wp_kses_post( $description );
1552 - }
1553 -
1554 - if ( ! empty( $quiz_slug ) ) {
1555 - $update_arg['post_name'] = $quiz_slug;
1556 - }
1557 -
1558 1586 if ( defined( 'ELEMENTOR_VERSION' ) ) {
1559 1587 \Elementor\Plugin::$instance->documents->get( $quiz_id )->set_is_built_with_elementor( ! empty( $is_elementor ) );
1560 1588 }
1561 1589
1562 - $restore_with_custom_slug = $this->prepare_desired_slug_for_restore( $quiz_id, $target_status, $quiz_slug );
1563 -
1564 - $update = wp_update_post( $update_arg );
1565 -
1566 - if ( is_wp_error( $update ) ) {
1567 - throw new Exception( $update->get_error_message() );
1590 + $quiz_model->post_title = $title;
1591 + $quiz_model->post_content = $description;
1592 + $quiz_model->post_status = $target_status;
1593 + if ( ! empty( $quiz_slug ) ) {
1594 + $quiz_model->post_name = $quiz_slug;
1568 1595 }
1569 1596
1570 - if ( $restore_with_custom_slug ) {
1571 - $this->sync_slug_after_restore( $quiz_id, $quiz_slug );
1572 - }
1597 + $quiz_model->save();
1573 1598
1574 1599 if ( $course_id ) {
1575 - $course_model_cache = CourseModel::find( $course_id, true );
1576 - if ( $course_model_cache ) {
1577 - $course_model_cache->sections_items = null;
1578 - $course_model_cache->save();
1600 + $courseModelCache = CourseModel::find( $course_id, true );
1601 + if ( $courseModelCache ) {
1602 + $courseModelCache->sections_items = null;
1603 + $courseModelCache->save();
1579 1604 }
1580 1605 }
1606 +
1607 + $response->message = __( 'Update quiz successfully', 'learnpress' );
1581 1608 }
1582 1609
1583 - if ( $settings && $quiz_model ) {
1610 + if ( $settings ) {
1584 1611 $this->save_quiz_settings_to_model( $quiz_model, $data );
1585 1612 }
1586 1613
1587 1614 // Remove quiz from curriculum if status is not public
@@ -1588,37 +1615,27 @@
1588 1615 if ( $target_status !== 'publish' ) {
1589 1616 $this->remove_course_item_from_curriculum( $quiz_id, $course_id );
1590 1617 }
1591 1618
1592 - do_action( 'learn-press/course-builder/update-quiz', $data, $quiz_model );
1593 -
1594 - $saved_quiz_status = get_post_status( $quiz_id );
1595 - if ( ! is_string( $saved_quiz_status ) || '' === $saved_quiz_status ) {
1596 - $saved_quiz_status = $target_status;
1619 + $response->status = 'success';
1620 + $response->data->status = $quiz_model->post_status;
1621 + $response->data->button_title = $quiz_model->post_status === 'publish' ?
1622 + __( 'Update', 'learnpress' ) :
1623 + __( 'Publish', 'learnpress' );
1624 + $response->data->quiz_id_new = $insert ? $quiz_id : '';
1625 + if ( $insert && $quiz_id ) {
1626 + $response->data->redirect_url = CourseBuilder::get_link_course_builder( CourseBuilderTemplate::MENU_QUIZZES . "/{$quiz_id}" );
1597 1627 }
1598 -
1599 - if ( $insert ) {
1600 - $response->data->redirect_url = CourseBuilder::get_link_course_builder(
1601 - CourseBuilderTemplate::MENU_QUIZZES . "/{$quiz_id}"
1602 - );
1603 - }
1604 -
1605 - $response->status = 'success';
1606 - $response->data->status = $saved_quiz_status;
1607 - $response->data->button_title = $saved_quiz_status === 'publish' ? __( 'Update', 'learnpress' ) : __( 'Publish', 'learnpress' );
1608 - $response->message = $target_status === 'draft'
1628 + $response->message = $target_status === 'draft'
1609 1629 ? esc_html__( 'Quiz saved as draft', 'learnpress' )
1610 1630 : ( $insert ? esc_html__( 'Insert quiz successfully', 'learnpress' ) : esc_html__( 'Update quiz successfully', 'learnpress' ) );
1611 1631
1612 - $saved_quiz_post = get_post( $quiz_id );
1613 - if ( $saved_quiz_post ) {
1614 - $response->data->quiz_slug = $saved_quiz_post->post_name;
1615 - }
1632 + $response->data->quiz_slug = $quiz_model->post_name;
1616 1633
1617 1634 $course_id_of_item = $this->get_course_by_item_id( $quiz_id );
1618 1635 $response->data->permalink_available = false;
1619 1636 $response->data->permalink_notice = $this->get_item_permalink_unavailable_message();
1620 - if ( 'publish' === $saved_quiz_status && $course_id_of_item ) {
1637 + if ( 'publish' === $quiz_model->post_status && $course_id_of_item ) {
1621 1638 $course = learn_press_get_course( $course_id_of_item );
1622 1639 if ( $course ) {
1623 1640 $response->data->permalink_available = true;
1624 1641 $response->data->quiz_permalink = urldecode( $course->get_item_link( $quiz_id ) );
@@ -1624,33 +1641,33 @@
1624 1641 $response->data->quiz_permalink = urldecode( $course->get_item_link( $quiz_id ) );
1625 1642 }
1626 1643 }
1627 1644
1645 + $quiz_model_for_html = QuizPostModel::find( $quiz_id, true );
1628 1646 if ( $return_html ) {
1629 - $quiz_model_new = QuizPostModel::find( $quiz_id, true );
1630 - $response->data->list_item_html = $quiz_model_new
1631 - ? BuilderListQuizzesTemplate::render_quiz( $quiz_model_new )
1647 + $response->data->list_item_html = $quiz_model_for_html
1648 + ? BuilderListQuizzesTemplate::render_quiz( $quiz_model_for_html )
1632 1649 : '';
1633 1650
1634 1651 if ( $course_id ) {
1635 - $course_model_for_html = CourseModel::find( $course_id, true );
1636 - if ( $course_model_for_html && $quiz_model_new ) {
1652 + $courseModelForHtml = CourseModel::find( $course_id, true );
1653 + if ( $courseModelForHtml && $quiz_model_for_html ) {
1637 1654 $item = new stdClass();
1638 1655 $item->item_id = $quiz_id;
1639 - $item->title = $quiz_model_new->post_title;
1656 + $item->title = $quiz_model_for_html->post_title;
1640 1657 $item->item_type = LP_QUIZ_CPT;
1641 1658 AdminEditCurriculumTemplate::instance()->context_data = [ 'is_course_builder' => true ];
1642 - $response->data->section_item_html = AdminEditCurriculumTemplate::instance()->html_section_item( $course_model_for_html, $item );
1659 + $response->data->section_item_html = AdminEditCurriculumTemplate::instance()->html_section_item( $courseModelForHtml, $item );
1643 1660 }
1644 1661 }
1645 1662 }
1646 1663
1647 - wp_send_json( $response );
1648 - } catch ( Throwable $th ) {
1649 - $response->status = 'error';
1650 - $response->message = $th->getMessage();
1651 - wp_send_json( $response );
1664 + $response->status = Response::STATUS_SUCCESS;
1665 + } catch ( Throwable $e ) {
1666 + $response->message = $e->getMessage();
1652 1667 }
1668 +
1669 + wp_send_json( $response );
1653 1670 }
1654 1671
1655 1672 public function duplicate_quiz() {
1656 1673 $response = new LP_REST_Response();
@@ -1694,9 +1711,9 @@
1694 1711 try {
1695 1712 $data = self::check_valid_quiz();
1696 1713 $quiz_id = $data['quiz_id'] ?? 0;
1697 1714 $status = $data['status'] ?? 'trash';
1698 - $quiz_model = $data['quiz_model'] ?? [];
1715 + $quiz_model = QuizPostModel::find( $quiz_id, false );
1699 1716 $quiz_slug = ! empty( $data['quiz_permalink'] )
1700 1717 ? sanitize_title( wp_unslash( (string) $data['quiz_permalink'] ) )
1701 1718 : '';
1702 1719
@@ -1798,9 +1815,9 @@
1798 1815 }
1799 1816 }
1800 1817
1801 1818 if ( 'delete' !== $status ) {
1802 - $fresh_quiz_model = QuizPostModel::find( $quiz_id, true );
1819 + $fresh_quiz_model = QuizPostModel::find( $quiz_id, false );
1803 1820 $response->data->html = $fresh_quiz_model
1804 1821 ? BuilderListQuizzesTemplate::render_quiz( $fresh_quiz_model )
1805 1822 : '';
1806 1823 }
@@ -1850,93 +1867,75 @@
1850 1867 }
1851 1868 }
1852 1869
1853 1870 public function builder_update_question() {
1854 - $response = new LP_REST_Response();
1871 + $response = new Response();
1855 1872 $response->data = new stdClass();
1856 1873
1857 1874 try {
1858 1875 $data = self::check_valid_question();
1859 1876 $question_id = $data['question_id'] ?? 0;
1860 - $title = $data['question_title'] ?? '';
1861 - $description = $data['question_description'] ?? '';
1877 + $title = LP_Helper::sanitize_params_submitted( $data['question_title'] ?? '' );
1878 + $description = LP_Helper::sanitize_params_submitted(
1879 + $data['question_description'] ?? '',
1880 + 'html'
1881 + );
1862 1882 $is_elementor = $data['is_elementor'] ?? false;
1863 1883 $return_html = ( $data['return_html'] ?? 'no' ) === 'yes';
1864 1884 $insert = $data['insert'];
1865 - $question_slug = ! empty( $data['question_permalink'] )
1866 - ? sanitize_title( wp_unslash( (string) $data['question_permalink'] ) )
1867 - : '';
1885 + $question_slug = LP_Helper::sanitize_params_submitted(
1886 + $data['question_permalink'] ?? '',
1887 + 'key'
1888 + );
1868 1889
1869 1890 // Determine target status (draft or publish)
1870 - $target_status = ! empty( $data['question_status'] ) && in_array( $data['question_status'], [ 'draft', 'publish' ], true )
1871 - ? sanitize_text_field( $data['question_status'] )
1872 - : 'publish';
1891 + $target_status = LP_Helper::sanitize_params_submitted(
1892 + $data['question_status'] ?? '',
1893 + 'key'
1894 + );
1873 1895
1896 + if ( empty( $title ) ) {
1897 + throw new Exception( __( 'Question title is required', 'learnpress' ) );
1898 + }
1899 +
1874 1900 if ( $insert ) {
1875 - if ( ! CourseBuilderAccessPolicy::can_create_item_type( LP_QUESTION_CPT ) ) {
1876 - throw new Exception( __( 'You are not allowed to create questions', 'learnpress' ) );
1877 - }
1878 -
1879 1901 $insert_arg = array(
1880 - 'post_type' => LP_QUESTION_CPT,
1881 - 'post_title' => sanitize_text_field( $title ?? '' ),
1882 - 'post_content' => $description ?? '',
1902 + 'post_title' => $title,
1903 + 'post_content' => $description,
1883 1904 'post_status' => $target_status,
1905 + 'post_author' => get_current_user_id(),
1884 1906 );
1885 1907
1886 - if ( ! empty( $question_slug ) ) {
1887 - $insert_arg['post_name'] = $question_slug;
1888 - }
1908 + $questionPostModelNew = new QuestionPostModel( $insert_arg );
1909 + $questionPostModelNew->check_capabilities_create_item_course();
1889 1910
1890 - $question_id = wp_insert_post( $insert_arg, true );
1911 + $questionPostModelNew->save();
1912 + $question_id = $questionPostModelNew->ID;
1891 1913
1892 - if ( is_wp_error( $question_id ) ) {
1893 - throw new Exception( $question_id->get_error_message() );
1894 - }
1914 + $question_model = $questionPostModelNew;
1915 + $response->message = __( 'Create question successfully', 'learnpress' );
1895 1916 } else {
1896 - $question_model = $data['question_model'];
1897 -
1898 - if ( ! $question_model ) {
1917 + $question_model = $data['question_model'] ?? null;
1918 + if ( ! $question_model instanceof QuestionPostModel ) {
1899 1919 throw new Exception( __( 'Question not found', 'learnpress' ) );
1900 1920 }
1901 1921
1902 - $course_id = $this->get_course_by_item_id( $question_id );
1922 + $question_model->check_capabilities_update_item_course();
1903 1923
1904 - // Support for co-instructor.
1905 - $co_instructor_ids = get_post_meta( $course_id, '_lp_co_teacher', false );
1906 - $co_instructor_ids = ! empty( $co_instructor_ids ) ? $co_instructor_ids : array();
1907 -
1908 - if ( absint( $question_model->post_author ) !== get_current_user_id() && ! current_user_can( 'manage_options' ) && ! in_array( get_current_user_id(), $co_instructor_ids ) ) {
1909 - throw new Exception( __( 'You are not allowed to update this question', 'learnpress' ) );
1910 - }
1911 -
1912 - $update_arg = array(
1913 - 'ID' => $question_id,
1914 - 'post_type' => LP_QUESTION_CPT,
1915 - 'post_status' => $target_status,
1916 - );
1917 -
1918 1924 if ( defined( 'ELEMENTOR_VERSION' ) ) {
1919 1925 \Elementor\Plugin::$instance->documents->get( $question_id )->set_is_built_with_elementor( ! empty( $is_elementor ) );
1920 1926 }
1921 1927
1922 - if ( isset( $data['question_title'] ) ) {
1923 - $update_arg['post_title'] = sanitize_text_field( $title );
1924 - }
1925 -
1926 - if ( isset( $data['question_description'] ) ) {
1927 - $update_arg['post_content'] = wp_kses_post( $description );
1928 - }
1929 -
1928 + $question_model->post_title = $title;
1929 + $question_model->post_content = $description;
1930 + $question_model->post_status = $target_status;
1930 1931 if ( ! empty( $question_slug ) ) {
1931 - $update_arg['post_name'] = $question_slug;
1932 + $question_model->post_name = $question_slug;
1932 1933 }
1933 1934
1934 - $update = wp_update_post( $update_arg );
1935 + $question_model->save();
1935 1936
1936 - if ( is_wp_error( $update ) ) {
1937 - throw new Exception( $update->get_error_message() );
1938 - }
1937 + $response->message = __( 'Update question successfully', 'learnpress' );
1939 1938 }
1940 1939
1941 1940 // Remove question from quizzes if status is not public
1942 1941 if ( $target_status !== 'publish' ) {
@@ -1942,48 +1941,39 @@
1942 1941 if ( $target_status !== 'publish' ) {
1943 1942 $this->remove_question_from_assigned_quizzes( $question_id );
1944 1943 }
1945 1944
1946 - do_action( 'learn-press/course-builder/update-question', $data, $question_model ?? null );
1945 + do_action( 'learn-press/course-builder/update-question', $data, $question_model );
1947 1946
1948 - $saved_question_status = get_post_status( $question_id );
1949 - if ( ! is_string( $saved_question_status ) || '' === $saved_question_status ) {
1950 - $saved_question_status = $target_status;
1947 + $response->status = 'success';
1948 + $response->data->status = $question_model->post_status;
1949 + $response->data->button_title = $question_model->post_status === 'publish' ?
1950 + __( 'Update', 'learnpress' ) :
1951 + __( 'Publish', 'learnpress' );
1952 + $response->data->question_id_new = $insert ? $question_id : '';
1953 + if ( $insert && $question_id ) {
1954 + $response->data->redirect_url = CourseBuilder::get_link_course_builder( CourseBuilderTemplate::MENU_QUESTIONS . "/{$question_id}" );
1951 1955 }
1952 -
1953 - if ( $insert ) {
1954 - $response->data->redirect_url = CourseBuilder::get_link_course_builder(
1955 - CourseBuilderTemplate::MENU_QUESTIONS . "/{$question_id}"
1956 - );
1957 - }
1958 -
1959 - $response->status = 'success';
1960 - $response->data->status = $saved_question_status;
1961 - $response->data->button_title = $saved_question_status === 'publish' ? __( 'Update', 'learnpress' ) : __( 'Publish', 'learnpress' );
1962 -
1963 1956 $response->message = $target_status === 'draft'
1964 1957 ? esc_html__( 'Question saved as draft', 'learnpress' )
1965 1958 : ( $insert ? esc_html__( 'Insert question successfully', 'learnpress' ) : esc_html__( 'Update question successfully', 'learnpress' ) );
1966 1959
1967 - $saved_question = get_post( $question_id );
1968 - if ( $saved_question ) {
1969 - $response->data->question_slug = $saved_question->post_name;
1970 - $response->data->question_permalink = get_permalink( $question_id );
1971 - }
1960 + $response->data->question_slug = $question_model->post_name;
1961 + $response->data->question_permalink = get_permalink( $question_id );
1972 1962
1963 + $question_model_for_html = QuestionPostModel::find( $question_id, true );
1973 1964 if ( $return_html ) {
1974 - $question_model_new = QuestionPostModel::find( $question_id, true );
1975 - $response->data->list_item_html = $question_model_new
1976 - ? BuilderListQuestionsTemplate::render_question( $question_model_new )
1965 + $response->data->list_item_html = $question_model_for_html
1966 + ? BuilderListQuestionsTemplate::render_question( $question_model_for_html )
1977 1967 : '';
1978 1968 }
1979 1969
1980 - wp_send_json( $response );
1981 - } catch ( Throwable $th ) {
1982 - $response->status = 'error';
1983 - $response->message = $th->getMessage();
1984 - wp_send_json( $response );
1970 + $response->status = Response::STATUS_SUCCESS;
1971 + } catch ( Throwable $e ) {
1972 + $response->message = $e->getMessage();
1985 1973 }
1974 +
1975 + wp_send_json( $response );
1986 1976 }
1987 1977
1988 1978 public function move_trash_question() {
1989 1979 $response = new LP_REST_Response();
@@ -1992,9 +1982,9 @@
1992 1982 try {
1993 1983 $data = self::check_valid_question();
1994 1984 $question_id = $data['question_id'] ?? 0;
1995 1985 $status = $data['status'] ?? 'trash';
1996 - $question_model = $data['question_model'] ?? [];
1986 + $question_model = QuestionPostModel::find( $question_id, false );
1997 1987
1998 1988 if ( ! $question_model ) {
1999 1989 throw new Exception( __( 'Question not found', 'learnpress' ) );
2000 1990 }
@@ -2058,9 +2048,9 @@
2058 2048 $response->data->status = $status;
2059 2049 $response->data->button_title = __( 'Publish', 'learnpress' );
2060 2050
2061 2051 if ( 'delete' !== $status ) {
2062 - $fresh_question_model = QuestionPostModel::find( $question_id, true );
2052 + $fresh_question_model = QuestionPostModel::find( $question_id, false );
2063 2053 $response->data->html = $fresh_question_model
2064 2054 ? BuilderListQuestionsTemplate::render_question( $fresh_question_model )
2065 2055 : '';
2066 2056 }
@@ -2138,16 +2128,16 @@
2138 2128 return;
2139 2129 }
2140 2130
2141 2131 try {
2142 - $course_model = CourseModel::find( $course_id, true );
2143 - if ( ! $course_model ) {
2132 + $courseModel = CourseModel::find( $course_id, true );
2133 + if ( ! $courseModel ) {
2144 2134 return;
2145 2135 }
2146 2136
2147 - $course_model->sections_items = null;
2148 - $course_model->total_items = null;
2149 - $course_model->save( true );
2137 + $courseModel->sections_items = null;
2138 + $courseModel->total_items = null;
2139 + $courseModel->save( true );
2150 2140 } catch ( Throwable $e ) {
2151 2141 error_log( __METHOD__ . ': ' . $e->getMessage() );
2152 2142 }
2153 2143 }
@@ -2227,10 +2217,10 @@
2227 2217 throw new Exception( __( 'Permission denied', 'learnpress' ) );
2228 2218 }
2229 2219
2230 2220 $hide_instructor_access_admin_screen = ! empty( $data['hide_instructor_access_admin_screen'] ) && $data['hide_instructor_access_admin_screen'] === 'yes' ? 'yes' : 'no';
2231 - $logo_remove = ! empty( $data['course_builder_logo_remove'] ) && $data['course_builder_logo_remove'] === 'yes';
2232 - $logo_id = absint( $data['course_builder_logo_id'] ?? 0 );
2221 + $logo_remove = ! empty( $data['course_builder_logo_remove'] ) && $data['course_builder_logo_remove'] === 'yes';
2222 + $logo_id = absint( $data['course_builder_logo_id'] ?? 0 );
2233 2223
2234 2224 if ( $logo_remove ) {
2235 2225 $logo_id = 0;
2236 2226 }
@@ -2242,13 +2232,13 @@
2242 2232 if ( $logo_id ) {
2243 2233 $logo_url = wp_get_attachment_image_url( $logo_id, 'full' );
2244 2234 }
2245 2235
2246 - $response->status = 'success';
2247 - $response->message = __( 'Course Builder settings updated.', 'learnpress' );
2248 - $response->data->hide_instructor_access_admin_screen = $hide_instructor_access_admin_screen;
2249 - $response->data->course_builder_logo_id = $logo_id;
2250 - $response->data->course_builder_logo_url = $logo_url;
2236 + $response->status = 'success';
2237 + $response->message = __( 'Course Builder settings updated.', 'learnpress' );
2238 + $response->data->hide_instructor_access_admin_screen = $hide_instructor_access_admin_screen;
2239 + $response->data->course_builder_logo_id = $logo_id;
2240 + $response->data->course_builder_logo_url = $logo_url;
2251 2241
2252 2242 wp_send_json( $response );
2253 2243 } catch ( Throwable $th ) {
2254 2244 $response->status = 'error';