PluginProbe
Tutor LMS – eLearning and online course solution / 3.4.2
Tutor LMS – eLearning and online course solution v3.4.2
4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 4.0.2 4.0.1 4.0.0 3.9.15 3.9.14 3.9.13 3.9.12 3.9.11 trunk 1.0.0 1.0.0-alpha 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 All 191 releases
← All changes | classes/Course.php +294 -795 4.0.53.4.2 View file →
@@ -9,26 +9,20 @@
9 9 */
10 10
11 11 namespace TUTOR;
12 12
13 -defined( 'ABSPATH' ) || exit;
13 +if ( ! defined( 'ABSPATH' ) ) {
14 + exit;
15 +}
14 16
15 -use Tutor\Components\Button;
16 -use Tutor\Components\Constants\Size;
17 -use Tutor\Components\Constants\Variant;
18 -use Tutor\Components\Progress;
19 -use Tutor\Ecommerce\Tax;
20 -use Tutor\Models\QuizModel;
17 +use stdClass;
18 +use TUTOR\Input;
21 19 use Tutor\Helpers\HttpHelper;
22 20 use Tutor\Models\CourseModel;
23 -use Tutor\Components\Tooltip;
24 21 use Tutor\Ecommerce\Ecommerce;
25 -use Tutor\Helpers\DateTimeHelper;
26 22 use Tutor\Traits\JsonResponse;
27 23 use Tutor\Helpers\ValidationHelper;
28 -use Tutor\Models\EnrollmentModel;
29 -use Tutor\Options_V2;
30 -use TUTOR_ASSIGNMENTS\Assignments;
24 +use TutorPro\CourseBundle\Models\BundleModel;
31 25
32 26 /**
33 27 * Course Class
34 28 *
@@ -69,46 +63,10 @@
69 63 const SELLING_OPTION_BOTH = 'both'; // onetime and subscription.
70 64 const SELLING_OPTION_MEMBERSHIP = 'membership';
71 65 const SELLING_OPTION_ALL = 'all';
72 66
73 - /**
74 - * Tax collection settings meta
75 - *
76 - * @since 3.7.0
77 - */
78 - const TAX_ON_SINGLE_META = 'tutor_tax_on_single';
79 - const TAX_ON_SUBSCRIPTION_META = 'tutor_tax_on_subscription';
80 67
81 68 /**
82 - * Additional data meta
83 - *
84 - * @since 3.6.0
85 - */
86 - const COURSE_BENEFITS_META = '_tutor_course_benefits';
87 - const COURSE_REQUIREMENTS_META = '_tutor_course_requirements';
88 - const COURSE_TARGET_AUDIENCE_META = '_tutor_course_target_audience';
89 - const COURSE_MATERIAL_INCLUDE_META = '_tutor_course_material_includes';
90 - const COURSE_DURATION_META = '_course_duration';
91 -
92 - /**
93 - * Course settings meta
94 - *
95 - * @since 3.6.0
96 - */
97 - const COURSE_ENABLE_QA_META = '_tutor_enable_qa';
98 - const PUBLIC_COURSE_META = '_tutor_is_public_course';
99 - const COURSE_SETTINGS_META = '_tutor_course_settings';
100 - const COURSE_LEVEL_META = '_tutor_course_level';
101 -
102 - /**
103 - * Meta keys used to identify and store Tutor course order data.
104 - *
105 - * @since 4.0.0
106 - */
107 - const IS_TUTOR_ORDER_FOR_COURSE_META = '_is_tutor_order_for_course';
108 - const TUTOR_ORDER_FOR_COURSE_ID_META = '_tutor_order_for_course_id_';
109 -
110 - /**
111 69 * Additional course meta info
112 70 *
113 71 * @var array
114 72 */
@@ -160,8 +118,15 @@
160 118 */
161 119 add_action( 'save_tutor_course', array( $this, 'attach_product_with_course' ), 10, 2 );
162 120
163 121 /**
122 + * Add course level to course settings
123 + *
124 + * @since v.1.4.1
125 + */
126 + add_filter( 'tutor_course_settings_tabs', array( $this, 'add_course_level_to_settings' ) );
127 +
128 + /**
164 129 * Enable Disable Course Details Page Feature
165 130 *
166 131 * @since v.1.4.8
167 132 */
@@ -207,14 +172,9 @@
207 172 * Delete course data after deleted course
208 173 *
209 174 * @since v.1.6.6
210 175 */
211 - add_action(
212 - 'deleted_post',
213 - function ( $post_id ) {
214 - ( new CourseModel() )->delete_course_data( $post_id );
215 - }
216 - );
176 + add_action( 'deleted_post', array( new CourseModel(), 'delete_course_data' ) );
217 177
218 178 /**
219 179 * Delete course data after deleted course
220 180 *
@@ -257,9 +217,9 @@
257 217 * @since 1.9.8
258 218 */
259 219 add_action( 'tutor_do_enroll_after_login_if_attempt', array( $this, 'enroll_after_login_if_attempt' ), 10, 2 );
260 220
261 - add_action( 'wp_ajax_tutor_update_course_content_order', array( $this, 'ajax_update_course_content_order' ) );
221 + add_action( 'wp_ajax_tutor_update_course_content_order', array( $this, 'tutor_update_course_content_order' ) );
262 222
263 223 add_action( 'wp_ajax_tutor_get_wc_product', array( $this, 'get_wc_product' ) );
264 224 add_action( 'wp_ajax_tutor_get_wc_products', array( $this, 'get_wc_products' ) );
265 225
@@ -307,39 +267,11 @@
307 267 add_filter( 'tutor_user_list_args', array( $this, 'user_list_args_for_instructor' ) );
308 268
309 269 add_filter( 'template_include', array( $this, 'handle_password_protected' ) );
310 270 add_action( 'login_form_postpass', array( $this, 'handle_password_submit' ) );
311 - add_filter( 'post_password_required', array( $this, 'bypass_password_for_enrolled' ), 10, 2 );
312 - add_filter( 'the_preview', array( $this, 'handle_schedule_courses' ) );
313 -
314 - add_action( 'tutor_course_action_btn', array( $this, 'render_course_action_btn' ) );
315 - add_action( 'wp_ajax_tutor_complete_course', array( $this, 'ajax_tutor_complete_course' ) );
316 271 }
317 272
318 273 /**
319 - * Handle schedule courses preview for instructors.
320 - *
321 - * @since 3.5.0
322 - *
323 - * @param \WP_Post $content the preview post content.
324 - *
325 - * @return \WP_Post
326 - */
327 - public function handle_schedule_courses( $content ) {
328 - global $wp_query;
329 - $course_coming_soon_enabled = (int) get_post_meta( $content->ID, '_tutor_course_enable_coming_soon', true );
330 - $is_instructor = tutor_utils()->is_instructor_of_this_course( get_current_user_id(), $content->ID, true );
331 - if ( ! CourseModel::get_post_types( $content ) || current_user_can( 'administrator' ) || $is_instructor || $course_coming_soon_enabled ) {
332 - return $content;
333 - }
334 -
335 - $wp_query->set_404();
336 - status_header( 404 );
337 - nocache_headers();
338 - return $content;
339 - }
340 -
341 - /**
342 274 * Handle password protected course and bundle form submission.
343 275 *
344 276 * @since 3.2.1
345 277 *
@@ -355,31 +287,8 @@
355 287 }
356 288 }
357 289
358 290 /**
359 - * Bypass password protection for enrolled users.
360 - *
361 - * @since 4.0.0
362 - *
363 - * @param bool $required Whether the password is required.
364 - * @param \WP_Post $post The post object.
365 - *
366 - * @return bool false if the current user is enrolled, original value otherwise.
367 - */
368 - public function bypass_password_for_enrolled( $required, $post ) {
369 - if ( ! $required ) {
370 - return $required;
371 - }
372 -
373 - $post_types = array( tutor()->course_post_type, tutor()->bundle_post_type );
374 - if ( in_array( $post->post_type, $post_types, true ) && EnrollmentModel::is_enrolled( $post->ID ) ) {
375 - return false;
376 - }
377 -
378 - return $required;
379 - }
380 -
381 - /**
382 291 * Handle password protected course/bundle.
383 292 *
384 293 * @since 3.0.0
385 294 *
@@ -553,34 +462,48 @@
553 462 }
554 463 }
555 464
556 465 /**
557 - * Setup course categories and tags.
466 + * Setup course categories and tags
558 467 *
559 468 * @since 3.0.0
560 469 *
561 - * @param int $post_id Post ID.
562 - * @param array $params Array of params.
470 + * @param int $post_id post id.
471 + * @param array $params array of params.
563 472 *
564 473 * @return void
565 474 */
566 475 public function setup_course_categories_tags( $post_id, $params ) {
567 476 if ( isset( $params['course_categories'] ) && is_array( $params['course_categories'] ) ) {
568 - $valid_category_ids = ValidationHelper::validate_term_ids(
569 - $params['course_categories'],
570 - CourseModel::COURSE_CATEGORY
571 - );
572 - wp_set_object_terms( $post_id, $valid_category_ids, CourseModel::COURSE_CATEGORY );
477 + $category_names = array();
478 +
479 + foreach ( $params['course_categories'] as $category_id ) {
480 + $term = get_term( $category_id, CourseModel::COURSE_CATEGORY );
481 +
482 + if ( ! is_wp_error( $term ) && $term ) {
483 + $category_names[] = $term->name;
484 + }
485 + }
486 +
487 + // Set category names on the post.
488 + wp_set_object_terms( $post_id, $category_names, CourseModel::COURSE_CATEGORY );
573 489 } else {
574 490 wp_set_object_terms( $post_id, array(), CourseModel::COURSE_CATEGORY );
575 491 }
576 492
577 493 if ( isset( $params['course_tags'] ) && is_array( $params['course_tags'] ) ) {
578 - $valid_tag_ids = ValidationHelper::validate_term_ids(
579 - $params['course_tags'],
580 - CourseModel::COURSE_TAG
581 - );
582 - wp_set_object_terms( $post_id, $valid_tag_ids, CourseModel::COURSE_TAG );
494 + $tag_names = array();
495 +
496 + foreach ( $params['course_tags'] as $tag_id ) {
497 + $term = get_term( $tag_id, CourseModel::COURSE_TAG );
498 +
499 + if ( ! is_wp_error( $term ) && $term ) {
500 + $tag_names[] = $term->name;
501 + }
502 + }
503 +
504 + // Set tag names on the post.
505 + wp_set_object_terms( $post_id, $tag_names, CourseModel::COURSE_TAG );
583 506 } else {
584 507 wp_set_object_terms( $post_id, array(), CourseModel::COURSE_TAG );
585 508 }
586 509 }
@@ -794,8 +717,11 @@
794 717 try {
795 718 if ( isset( $params['pricing']['type'] ) ) {
796 719 update_post_meta( $post_id, self::COURSE_PRICE_TYPE_META, $params['pricing']['type'] );
797 720 }
721 + if ( isset( $params['pricing']['product_id'] ) ) {
722 + update_post_meta( $post_id, '_tutor_course_product_id', $params['pricing']['product_id'] );
723 + }
798 724 } catch ( \Throwable $th ) {
799 725 throw new \Exception( $th->getMessage() );
800 726 }
801 727 }
@@ -803,21 +729,8 @@
803 729 update_post_meta( $post_id, '_tutor_enable_qa', $params['enable_qna'] ?? 'yes' );
804 730 update_post_meta( $post_id, '_tutor_is_public_course', $params['is_public_course'] ?? 'no' );
805 731 update_post_meta( $post_id, '_tutor_course_level', $params['course_level'] );
806 732
807 - /**
808 - * Save tax collection settings
809 - *
810 - * @since 3.7.0
811 - */
812 - if ( isset( $params['tax_on_single'] ) ) {
813 - update_post_meta( $post_id, self::TAX_ON_SINGLE_META, $params['tax_on_single'] );
814 - }
815 -
816 - if ( isset( $params['tax_on_subscription'] ) ) {
817 - update_post_meta( $post_id, self::TAX_ON_SUBSCRIPTION_META, $params['tax_on_subscription'] );
818 - }
819 -
820 733 do_action( 'tutor_after_prepare_update_post_meta', $post_id, $params );
821 734 }
822 735
823 736 /**
@@ -917,9 +830,9 @@
917 830
918 831 update_post_meta( $course_id, self::COURSE_PRICE_TYPE_META, self::PRICE_TYPE_FREE );
919 832
920 833 $link = admin_url( 'admin.php?page=create-course' );
921 - if ( tutor()->has_pro && Input::post( 'from_dashboard', false, Input::TYPE_BOOL ) ) {
834 + if ( Input::post( 'from_dashboard', false, Input::TYPE_BOOL ) ) {
922 835 $link = tutor_utils()->tutor_dashboard_url( 'create-course' );
923 836 }
924 837
925 838 $link = add_query_arg( array( 'course_id' => $course_id ), $link );
@@ -937,20 +850,20 @@
937 850 * Get course list
938 851 *
939 852 * @since 3.0.0
940 853 *
941 - * @since 3.2.0 Refactor the arguments & response as per new design.
854 + * @since 3.2.0
942 855 *
856 + * Refactor the arguments & response as per new design
857 + *
943 858 * @return void
944 859 */
945 860 public function ajax_course_list() {
946 - tutor_utils()->check_nonce();
947 861 $this->check_access();
948 862
949 863 $limit = Input::post( 'limit', 10, Input::TYPE_INT );
950 864 $offset = Input::post( 'offset', 0, Input::TYPE_INT );
951 865 $search_term = '';
952 - $post_status = Input::post( 'post_status', null );
953 866
954 867 $filter = json_decode( wp_unslash( $_POST['filter'] ) ); //phpcs:ignore --sanitized already
955 868 if ( ! empty( $filter ) && property_exists( $filter, 'search' ) ) {
956 869 $search_term = Input::sanitize( $filter->search );
@@ -956,9 +869,8 @@
956 869 $search_term = Input::sanitize( $filter->search );
957 870 }
958 871
959 872 $args = array(
960 - 'post_status' => is_null( $post_status ) ? 'publish' : $post_status,
961 873 'posts_per_page' => $limit,
962 874 'offset' => $offset,
963 875 's' => $search_term,
964 876 );
@@ -964,9 +876,14 @@
964 876 );
965 877
966 878 $exclude = Input::post( 'exclude', array(), Input::TYPE_ARRAY );
967 879 if ( count( $exclude ) ) {
968 - $exclude = array_filter( $exclude, fn( $id ) => is_numeric( $id ) && $id > 0 );
880 + $exclude = array_filter(
881 + $exclude,
882 + function ( $id ) {
883 + return is_numeric( $id );
884 + }
885 + );
969 886 $args['post__not_in'] = $exclude;
970 887 }
971 888
972 889 $courses = CourseModel::get_courses_by_args( $args );
@@ -1102,17 +1019,12 @@
1102 1019 'course_benefits' => 'sanitize_textarea_field',
1103 1020 'course_target_audience' => 'sanitize_textarea_field',
1104 1021 'course_material_includes' => 'sanitize_textarea_field',
1105 1022 'course_requirements' => 'sanitize_textarea_field',
1106 - ),
1107 - true
1023 + )
1108 1024 );
1109 1025
1110 1026 $course_id = (int) $params['course_id'];
1111 - if ( ! $course_id ) {
1112 - $this->response_bad_request( __( 'Invalid course id', 'tutor' ) );
1113 - }
1114 -
1115 1027 $this->check_access( $course_id );
1116 1028
1117 1029 $errors = array();
1118 1030 $validation = $this->validate_inputs( $params );
@@ -1201,32 +1113,15 @@
1201 1113 public function ajax_unlink_page_builder() {
1202 1114 tutor_utils()->check_nonce();
1203 1115
1204 1116 $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
1205 - if ( ! $course_id ) {
1206 - $this->response_bad_request( __( 'Invalid course id', 'tutor' ) );
1207 - }
1208 -
1117 + $builder = Input::post( 'builder' );
1209 1118 $this->check_access( $course_id );
1210 1119
1211 - $builder = Input::post( 'builder' );
1212 1120 if ( 'elementor' === $builder ) {
1213 1121 delete_post_meta( $course_id, '_elementor_edit_mode' );
1214 1122 } elseif ( 'droip' === $builder ) {
1215 1123 delete_post_meta( $course_id, 'droip_editor_mode' );
1216 - } elseif ( 'divi' === $builder ) {
1217 - $old_post_content = get_post_meta( $course_id, '_et_pb_old_content', true );
1218 - $course = get_post( $course_id );
1219 - $course->post_content = $old_post_content;
1220 - $result = wp_update_post( $course );
1221 -
1222 - if ( $result && ! is_wp_error( $result ) ) {
1223 - update_post_meta( $course_id, '_et_pb_use_builder', 'off' );
1224 - update_post_meta( $course_id, '_et_pb_old_content', '' );
1225 - delete_post_meta( $course_id, '_et_dynamic_cached_shortcodes' );
1226 - delete_post_meta( $course_id, '_et_dynamic_cached_attributes' );
1227 - delete_post_meta( $course_id, '_et_builder_module_features_cache' );
1228 - }
1229 1124 }
1230 1125
1231 1126 $this->json_response(
1232 1127 __( 'Builder unlinked successfully.', 'tutor' ),
@@ -1259,18 +1154,15 @@
1259 1154
1260 1155 $topic_contents = tutor_utils()->get_course_contents_by_topic( $post->ID, -1 );
1261 1156
1262 1157 if ( $topic_contents->have_posts() ) {
1263 - foreach ( $topic_contents->get_posts() as $content ) {
1264 - if ( tutor()->quiz_post_type === $content->post_type ) {
1265 - $questions = tutor_utils()->get_questions_by_quiz( $content->ID );
1266 - $content = (object) array_merge(
1267 - (array) $content,
1268 - array( 'total_question' => is_array( $questions ) ? count( $questions ) : 0 )
1269 - );
1158 + foreach ( $topic_contents->get_posts() as $post ) {
1159 + if ( tutor()->quiz_post_type === $post->post_type ) {
1160 + $questions = tutor_utils()->get_questions_by_quiz( $post->ID );
1161 + $post->total_question = is_array( $questions ) ? count( $questions ) : 0;
1270 1162 }
1271 1163
1272 - array_push( $current_topic['contents'], $content );
1164 + array_push( $current_topic['contents'], $post );
1273 1165 }
1274 1166 }
1275 1167
1276 1168 $current_topic = apply_filters( 'tutor_filter_course_content', $current_topic );
@@ -1285,23 +1177,20 @@
1285 1177 /**
1286 1178 * Get course contents
1287 1179 *
1288 1180 * @since 3.0.0
1289 - *
1290 - * @return void
1291 1181 */
1292 1182 public function ajax_course_contents() {
1293 1183 tutor_utils()->check_nonce();
1294 1184
1295 - $errors = array();
1296 1185 $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
1297 1186
1298 - if ( ! $course_id || tutor()->course_post_type !== get_post_type( $course_id ) ) {
1187 + $this->check_access( $course_id );
1188 +
1189 + if ( tutor()->course_post_type !== get_post_type( $course_id ) ) {
1299 1190 $errors['course_id'] = __( 'Invalid course id', 'tutor' );
1300 1191 }
1301 1192
1302 - $this->check_access( $course_id );
1303 -
1304 1193 if ( ! empty( $errors ) ) {
1305 1194 $this->json_response( __( 'Invalid input', 'tutor' ), $errors, HttpHelper::STATUS_UNPROCESSABLE_ENTITY );
1306 1195 }
1307 1196
@@ -1325,14 +1214,14 @@
1325 1214
1326 1215 $errors = array();
1327 1216 $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
1328 1217
1329 - if ( ! $course_id || tutor()->course_post_type !== get_post_type( $course_id ) ) {
1218 + $this->check_access( $course_id );
1219 +
1220 + if ( tutor()->course_post_type !== get_post_type( $course_id ) ) {
1330 1221 $errors['course_id'] = __( 'Invalid course id', 'tutor' );
1331 1222 }
1332 1223
1333 - $this->check_access( $course_id );
1334 -
1335 1224 if ( ! empty( $errors ) ) {
1336 1225 $this->json_response( __( 'Invalid input', 'tutor' ), $errors, HttpHelper::STATUS_UNPROCESSABLE_ENTITY );
1337 1226 }
1338 1227
@@ -1343,9 +1232,9 @@
1343 1232 $price = 0;
1344 1233 $sale_price = 0;
1345 1234 $product_id = tutor_utils()->get_course_product_id( $course_id );
1346 1235
1347 - if ( WooCommerce::MONETIZE_BY === $monetize_by ) {
1236 + if ( 'wc' === $monetize_by ) {
1348 1237 $product = wc_get_product( $product_id );
1349 1238 if ( $product ) {
1350 1239 $product_name = $product->get_name();
1351 1240 $price = $product->get_regular_price();
@@ -1352,9 +1241,9 @@
1352 1241 $sale_price = $product->get_sale_price();
1353 1242 }
1354 1243 }
1355 1244
1356 - if ( Ecommerce::MONETIZE_BY === $monetize_by ) {
1245 + if ( 'tutor' === $monetize_by ) {
1357 1246 $price = get_post_meta( $course_id, self::COURSE_PRICE_META, true );
1358 1247 $sale_price = get_post_meta( $course_id, self::COURSE_SALE_PRICE_META, true );
1359 1248 }
1360 1249
@@ -1369,12 +1258,12 @@
1369 1258 $video_intro = get_post_meta( $course_id, '_video', true );
1370 1259 if ( $video_intro ) {
1371 1260 $source = $video_intro['source'] ?? '';
1372 1261 if ( 'html5' === $source ) {
1373 - $poster_url = wp_get_attachment_url( $video_intro['poster'] ?? 0 );
1374 - $source_html5 = wp_get_attachment_url( $video_intro['source_video_id'] ?? 0 );
1375 - $video_intro['poster_url'] = $poster_url;
1376 - $video_intro['source_html5'] = $source_html5;
1262 + $poster_url = wp_get_attachment_url( $video['poster'] ?? 0 );
1263 + $source_html5 = wp_get_attachment_url( $video['source_video_id'] ?? 0 );
1264 + $video['poster_url'] = $poster_url;
1265 + $video['source_html5'] = $source_html5;
1377 1266 }
1378 1267 }
1379 1268
1380 1269 $course = get_post( $course_id, ARRAY_A );
@@ -1413,16 +1302,8 @@
1413 1302 'certificate' => false,
1414 1303 ),
1415 1304 );
1416 1305
1417 - $tax_on_single = get_post_meta( $course_id, self::TAX_ON_SINGLE_META, true );
1418 - $tax_on_subscription = get_post_meta( $course_id, self::TAX_ON_SUBSCRIPTION_META, true );
1419 -
1420 - $data['tax_collection'] = array(
1421 - 'tax_on_single' => '' === $tax_on_single ? '1' : $tax_on_single,
1422 - 'tax_on_subscription' => '' === $tax_on_subscription ? '1' : $tax_on_subscription,
1423 - );
1424 -
1425 1306 $data = apply_filters( 'tutor_course_details_response', array_merge( $course, $data ) );
1426 1307
1427 1308 $this->json_response( __( 'Data retrieved successfully!', 'tutor' ), $data );
1428 1309 }
@@ -1487,10 +1368,9 @@
1487 1368 wp_enqueue_script( 'mce-view' );
1488 1369 wp_enqueue_editor();
1489 1370
1490 1371 wp_enqueue_media();
1491 - wp_enqueue_script( 'tutor-course-builder', tutor()->url . 'assets/js/tutor-course-builder.js', array( 'wp-date', 'wp-i18n', 'wp-element', 'wp-api' ), TUTOR_VERSION, true );
1492 - wp_set_script_translations( 'tutor-course-builder', 'tutor', tutor()->path . 'languages/' );
1372 + wp_enqueue_script( 'tutor-course-builder', tutor()->url . 'assets/js/tutor-course-builder.min.js', array( 'wp-date', 'wp-i18n', 'wp-element', 'wp-api' ), TUTOR_VERSION, true );
1493 1373
1494 1374 wp_localize_script(
1495 1375 'mce-view',
1496 1376 'mceViewL10n',
@@ -1529,9 +1409,9 @@
1529 1409 $default_data = ( new Assets( false ) )->get_default_localized_data();
1530 1410
1531 1411 if ( isset( $default_data['current_user']['data']['id'] ) ) {
1532 1412 $tutor_user = tutor_utils()->get_tutor_user( $default_data['current_user']['data']['id'] );
1533 - $default_data['current_user']['data']['tutor_profile_photo_url'] = is_object( $tutor_user ) ? $tutor_user->tutor_profile_photo_url : '';
1413 + $default_data['current_user']['data']['tutor_profile_photo_url'] = $tutor_user->tutor_profile_photo_url;
1534 1414 }
1535 1415
1536 1416 /**
1537 1417 * Localized only options to protect sensitive info like API keys.
@@ -1536,9 +1416,8 @@
1536 1416 /**
1537 1417 * Localized only options to protect sensitive info like API keys.
1538 1418 */
1539 1419 $required_options = array(
1540 - 'learning_mode',
1541 1420 'monetize_by',
1542 1421 'enable_course_marketplace',
1543 1422 'course_permalink_base',
1544 1423 'supported_video_sources',
@@ -1558,12 +1437,8 @@
1558 1437 $settings['course_builder_logo_url'] = wp_get_attachment_image_url( $full_settings['tutor_frontend_course_page_logo_id'] ?? 0, 'full' );
1559 1438 $settings['chatgpt_key_exist'] = tutor()->has_pro && ! empty( $full_settings['chatgpt_api_key'] ?? '' );
1560 1439 $settings['youtube_api_key_exist'] = ! empty( $full_settings['lesson_video_duration_youtube_api_key'] ?? '' );
1561 1440
1562 - $settings['enable_tax'] = Tax::get_setting( 'enable_tax', true );
1563 - $settings['is_tax_included_in_price'] = Tax::is_tax_included_in_price();
1564 - $settings['enable_individual_tax_control'] = Tax::get_setting( 'enable_individual_tax_control' );
1565 -
1566 1441 $new_data = array( 'settings' => $settings );
1567 1442
1568 1443 $data = array_merge( $default_data, $new_data );
1569 1444
@@ -1609,15 +1484,10 @@
1609 1484 $data['timezones'] = tutor_global_timezone_lists();
1610 1485 $data['difficulty_levels'] = $difficulty_levels;
1611 1486 $data['supported_video_sources'] = $supported_video_sources;
1612 1487 $data['wp_rest_nonce'] = wp_create_nonce( 'wp_rest' );
1488 + $data['max_upload_size'] = size_format( wp_max_upload_size() );
1613 1489
1614 - if ( 'en_US' !== $data['local'] ) {
1615 - $data['course_builder_basic_locales'] = tutils()->get_script_locale_data( 'tutor-course-builder-basic', $data['local'] );
1616 - $data['course_builder_curriculum_locales'] = tutils()->get_script_locale_data( 'tutor-course-builder-curriculum', $data['local'] );
1617 - $data['course_builder_additional_locales'] = tutils()->get_script_locale_data( 'tutor-course-builder-additional', $data['local'] );
1618 - }
1619 -
1620 1490 $data = apply_filters( 'tutor_course_builder_localized_data', $data );
1621 1491
1622 1492 return $data;
1623 1493 }
@@ -1740,38 +1610,23 @@
1740 1610 /**
1741 1611 * Update course content order
1742 1612 *
1743 1613 * @since 1.0.0
1744 - * @since 3.9.9 Check if user can manage course before updating order.
1745 - *
1746 1614 * @return void
1747 1615 */
1748 - public function ajax_update_course_content_order() {
1616 + public function tutor_update_course_content_order() {
1749 1617 tutor_utils()->checking_nonce();
1750 1618
1751 - $sorting_order = Input::post( 'tutor_topics_lessons_sorting', '' );
1752 - $sorting_order = json_decode( $sorting_order, true ) ?? array();
1753 -
1754 - if ( ! tutor_utils()->count( $sorting_order ) ) {
1755 - wp_send_json_error( __( 'Sorting order is required', 'tutor' ) );
1756 - }
1757 -
1758 - $topic_id = (int) isset( $sorting_order[0], $sorting_order[0]['topic_id'] ) ? $sorting_order[0]['topic_id'] : 0;
1759 - $course_id = wp_get_post_parent_id( $topic_id );
1760 -
1761 - if ( ! $topic_id || ! $course_id ) {
1762 - $this->response_bad_request( tutor_utils()->error_message( 'invalid_req' ) );
1763 - }
1764 -
1765 - if ( ! tutor_utils()->can_user_manage( 'course', $course_id ) && ! User::is_admin() ) {
1766 - $this->json_response( tutor_utils()->error_message(), null, HttpHelper::STATUS_UNAUTHORIZED );
1767 - }
1768 -
1769 1619 if ( Input::has( 'content_parent' ) ) {
1770 1620 $content_parent = Input::post( 'content_parent', array(), Input::TYPE_ARRAY );
1771 1621 $topic_id = tutor_utils()->array_get( 'parent_topic_id', $content_parent );
1772 1622 $content_id = tutor_utils()->array_get( 'content_id', $content_parent );
1773 1623
1624 + if ( ! tutor_utils()->can_user_manage( 'topic', $topic_id ) ) {
1625 + wp_send_json_success( array( 'message' => __( 'Access Denied!', 'tutor' ) ) );
1626 + exit;
1627 + }
1628 +
1774 1629 // Update the parent topic id of the content.
1775 1630 global $wpdb;
1776 1631 $wpdb->update( $wpdb->posts, array( 'post_parent' => $topic_id ), array( 'ID' => $content_id ) );
1777 1632 }
@@ -1776,11 +1631,11 @@
1776 1631 $wpdb->update( $wpdb->posts, array( 'post_parent' => $topic_id ), array( 'ID' => $content_id ) );
1777 1632 }
1778 1633
1779 1634 // Save course content order.
1780 - $this->save_course_content_order( $sorting_order );
1635 + $this->save_course_content_order();
1781 1636
1782 - $this->response_success( __( 'Course content order updated successfully!', 'tutor' ) );
1637 + wp_send_json_success();
1783 1638 }
1784 1639
1785 1640 /**
1786 1641 * Restrict new student entry
@@ -1805,11 +1660,9 @@
1805 1660 /**
1806 1661 * Restrict media
1807 1662 *
1808 1663 * @since 1.0.0
1809 - *
1810 1664 * @param string $where where clause.
1811 - *
1812 1665 * @return string
1813 1666 */
1814 1667 public function restrict_media( $where ) {
1815 1668 $action = Input::post( 'action' );
@@ -1825,58 +1678,58 @@
1825 1678 /**
1826 1679 * Save course content order
1827 1680 *
1828 1681 * @since 1.0.0
1829 - * @since 3.9.8 param $order added.
1830 - *
1831 - * @param array $sort_order the lesson and topic order array.
1832 - *
1833 1682 * @return void
1834 1683 */
1835 - private function save_course_content_order( $sort_order = array() ) {
1684 + private function save_course_content_order() {
1836 1685 global $wpdb;
1837 1686
1838 - if ( ! tutor_utils()->count( $sort_order ) ) {
1839 - return;
1840 - }
1687 + $new_order = Input::post( 'tutor_topics_lessons_sorting' );
1688 + if ( ! empty( $new_order ) ) {
1689 + $order = json_decode( $new_order, true );
1841 1690
1842 - $i = 0;
1843 - foreach ( $sort_order as $topic ) {
1844 - ++$i;
1845 - $wpdb->update(
1846 - $wpdb->posts,
1847 - array( 'menu_order' => $i ),
1848 - array( 'ID' => $topic['topic_id'] )
1849 - );
1691 + if ( is_array( $order ) && count( $order ) ) {
1692 + $i = 0;
1693 + foreach ( $order as $topic ) {
1694 + $i++;
1695 + $wpdb->update(
1696 + $wpdb->posts,
1697 + array( 'menu_order' => $i ),
1698 + array( 'ID' => $topic['topic_id'] )
1699 + );
1850 1700
1851 - /**
1852 - * Removing All lesson with topic
1853 - */
1854 - $wpdb->update(
1855 - $wpdb->posts,
1856 - array( 'post_parent' => 0 ),
1857 - array( 'post_parent' => $topic['topic_id'] )
1858 - );
1701 + /**
1702 + * Removing All lesson with topic
1703 + */
1859 1704
1860 - /**
1861 - * Lesson Attaching with topic ID
1862 - * Sorting lesson
1863 - */
1864 - if ( isset( $topic['lesson_ids'] ) ) {
1865 - $lesson_ids = $topic['lesson_ids'];
1866 - } else {
1867 - $lesson_ids = array();
1868 - }
1869 - if ( count( $lesson_ids ) ) {
1870 - foreach ( $lesson_ids as $lesson_key => $lesson_id ) {
1871 1705 $wpdb->update(
1872 1706 $wpdb->posts,
1873 - array(
1874 - 'post_parent' => $topic['topic_id'],
1875 - 'menu_order' => $lesson_key,
1876 - ),
1877 - array( 'ID' => $lesson_id )
1707 + array( 'post_parent' => 0 ),
1708 + array( 'post_parent' => $topic['topic_id'] )
1878 1709 );
1710 +
1711 + /**
1712 + * Lesson Attaching with topic ID
1713 + * Sorting lesson
1714 + */
1715 + if ( isset( $topic['lesson_ids'] ) ) {
1716 + $lesson_ids = $topic['lesson_ids'];
1717 + } else {
1718 + $lesson_ids = array();
1719 + }
1720 + if ( count( $lesson_ids ) ) {
1721 + foreach ( $lesson_ids as $lesson_key => $lesson_id ) {
1722 + $wpdb->update(
1723 + $wpdb->posts,
1724 + array(
1725 + 'post_parent' => $topic['topic_id'],
1726 + 'menu_order' => $lesson_key,
1727 + ),
1728 + array( 'ID' => $lesson_id )
1729 + );
1730 + }
1731 + }
1879 1732 }
1880 1733 }
1881 1734 }
1882 1735 }
@@ -1947,14 +1800,12 @@
1947 1800 }
1948 1801 //phpcs:enable WordPress.Security.NonceVerification.Missing
1949 1802 }
1950 1803
1951 - $sorting_order = Input::post( 'tutor_topics_lessons_sorting', '' );
1952 - $sorting_order = json_decode( $sorting_order, true ) ?? array();
1953 1804 /**
1954 1805 * Sorting Topics and lesson
1955 1806 */
1956 - $this->save_course_content_order( $sorting_order );
1807 + $this->save_course_content_order();
1957 1808
1958 1809 // Additional data like course intro video.
1959 1810 if ( $additional_data_edit ) {
1960 1811 // Sanitize data through helper method.
@@ -1976,23 +1827,12 @@
1976 1827
1977 1828 /**
1978 1829 * Adding author to instructor automatically
1979 1830 */
1980 - $requested_author_id = Input::post( 'post_author_override', 0, Input::TYPE_INT );
1981 1831
1982 - /**
1983 - * Only accept the requested author override if it targets a real,approved instructor or admin
1984 - *
1985 - * @since 4.0.4
1986 - */
1987 - $author_id = $post->post_author;
1988 - if ( $requested_author_id && ( User::is_admin() || User::is_instructor() ) ) {
1989 - if ( User::is_admin( $requested_author_id ) || User::is_instructor( $requested_author_id, true ) ) {
1990 - $author_id = $requested_author_id;
1991 - }
1992 - }
1993 -
1994 - $attached = (int) $wpdb->get_var(
1832 + // Override post author id.
1833 + $author_id = isset( $_POST['post_author_override'] ) ? $_POST['post_author_override'] : $post->post_author; //phpcs:ignore
1834 + $attached = (int) $wpdb->get_var(
1995 1835 $wpdb->prepare(
1996 1836 "SELECT COUNT(umeta_id) FROM {$wpdb->usermeta}
1997 1837 WHERE user_id = %d
1998 1838 AND meta_key = '_tutor_instructor_course_id'
@@ -2017,20 +1857,8 @@
2017 1857 update_post_meta( $post_ID, $key, ( isset( $_POST[ $key ] ) ? 'yes' : 'no' ) );
2018 1858 }
2019 1859 }
2020 1860
2021 - /**
2022 - * Update course content if main author is changed and multi-instructor addon is disabled.
2023 - *
2024 - * @since 4.0.4
2025 - */
2026 - if ( ! $attached && ! tutor_utils()->is_addon_enabled( 'tutor-multi-instructors' ) ) {
2027 - CourseModel::update_course_content_author( $post_ID, (int) $author_id );
2028 - // Remove all existing instructors from the course and add the new one.
2029 - delete_metadata( 'user', 0, '_tutor_instructor_course_id', $post_ID, true );
2030 - add_user_meta( $author_id, '_tutor_instructor_course_id', $post_ID );
2031 - }
2032 -
2033 1861 do_action( 'tutor_save_course_after', $post_ID, $post );
2034 1862 }
2035 1863
2036 1864 /**
@@ -2172,14 +2000,8 @@
2172 2000 */
2173 2001
2174 2002 $is_purchasable = tutor_utils()->is_course_purchasable( $course_id );
2175 2003
2176 - $course = get_post( $course_id );
2177 -
2178 - if ( 'private' === $course->post_status && ! current_user_can( 'read_private_tutor_courses' ) ) {
2179 - wp_send_json_error( __( 'You do not have permission to enroll in this course', 'tutor' ) );
2180 - }
2181 -
2182 2004 /**
2183 2005 * If is is not purchasable, it's free, and enroll right now
2184 2006 * If purchasable, then process purchase.
2185 2007 *
@@ -2189,9 +2011,9 @@
2189 2011 // Process purchase.
2190 2012
2191 2013 } else {
2192 2014 // Free enroll.
2193 - EnrollmentModel::do_enroll( $course_id );
2015 + tutor_utils()->do_enroll( $course_id );
2194 2016 }
2195 2017
2196 2018 $referer_url = wp_get_referer();
2197 2019 wp_safe_redirect( tutor_utils()->get_nocache_url( $referer_url ) );
@@ -2201,11 +2023,8 @@
2201 2023 /**
2202 2024 * Mark complete completed
2203 2025 *
2204 2026 * @since 1.0.0
2205 - *
2206 - * @since 3.7.1 Filter hook: tutor_user_can_complete_course added
2207 - *
2208 2027 * @return void
2209 2028 */
2210 2029 public function mark_course_complete() {
2211 2030 $tutor_action = Input::post( 'tutor_action' );
@@ -2213,10 +2032,8 @@
2213 2032 if ( 'tutor_complete_course' !== $tutor_action || ! $course_id ) {
2214 2033 return;
2215 2034 }
2216 2035
2217 - $permalink = get_the_permalink( $course_id );
2218 -
2219 2036 // Checking nonce.
2220 2037 tutor_utils()->checking_nonce();
2221 2038
2222 2039 $user_id = get_current_user_id();
@@ -2225,78 +2042,20 @@
2225 2042 if ( ! $user_id ) {
2226 2043 die( esc_html__( 'Please Sign-In', 'tutor' ) );
2227 2044 }
2228 2045
2229 - if ( ! EnrollmentModel::is_enrolled( $course_id, $user_id ) ) {
2230 - die( esc_html__( 'User is not enrolled in course', 'tutor' ) );
2231 - }
2046 + CourseModel::mark_course_as_completed( $course_id, $user_id );
2232 2047
2233 - /**
2234 - * Filter hook provided to restrict course completion. This is useful
2235 - * for specific cases like prerequisites. WP_Error should be returned
2236 - * from the filter value to prevent the completion.
2237 - */
2238 - $can_complete = apply_filters( 'tutor_user_can_complete_course', CourseModel::can_complete_course( $course_id, $user_id ), $user_id, $course_id );
2048 + $permalink = get_the_permalink( $course_id );
2239 2049
2240 - if ( is_wp_error( $can_complete ) ) {
2241 - tutor_utils()->redirect_to( $permalink, $can_complete->get_error_message(), 'error' );
2242 - } elseif ( ! $can_complete ) {
2243 - tutor_utils()->redirect_to( $permalink, __( 'You do not have permission to complete this course.', 'tutor' ), 'error' );
2244 - } else {
2245 - CourseModel::mark_course_as_completed( $course_id, $user_id );
2246 - // Set temporary identifier to show review pop up.
2247 - self::set_review_popup_data( $user_id, $course_id );
2050 + // Set temporary identifier to show review pop up.
2051 + self::set_review_popup_data( $user_id, $course_id, $permalink );
2248 2052
2249 - wp_safe_redirect( $permalink );
2250 - exit;
2251 - }
2053 + wp_safe_redirect( $permalink );
2054 + exit;
2252 2055 }
2253 2056
2254 2057 /**
2255 - * Ajax course complete handler
2256 - *
2257 - * @since 4.0.0
2258 - *
2259 - * @return void
2260 - */
2261 - public function ajax_tutor_complete_course() {
2262 - $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
2263 -
2264 - // Checking nonce.
2265 - if ( ! tutor_utils()->is_nonce_verified() ) {
2266 - $this->response_bad_request( tutor_utils()->error_message( 'nonce' ) );
2267 - }
2268 -
2269 - if ( ! $course_id ) {
2270 - $this->response_bad_request( __( 'Invalid course', 'tutor' ) );
2271 - }
2272 -
2273 - $user_id = get_current_user_id();
2274 - if ( ! EnrollmentModel::is_enrolled( $course_id, $user_id ) ) {
2275 - $this->response_bad_request( __( 'You are not enrolled in this course', 'tutor' ) );
2276 - }
2277 -
2278 - /**
2279 - * Filter hook provided to restrict course completion. This is useful
2280 - * for specific cases like prerequisites. WP_Error should be returned
2281 - * from the filter value to prevent the completion.
2282 - */
2283 - $can_complete = apply_filters( 'tutor_user_can_complete_course', CourseModel::can_complete_course( $course_id, $user_id ), $user_id, $course_id );
2284 -
2285 - if ( is_wp_error( $can_complete ) ) {
2286 - $this->response_bad_request( $can_complete->get_error_message() );
2287 - } elseif ( ! $can_complete ) {
2288 - $this->response_bad_request( __( 'You do not have permission to complete this course.', 'tutor' ) );
2289 - } else {
2290 - CourseModel::mark_course_as_completed( $course_id, $user_id );
2291 - // Set temporary identifier to show review pop up.
2292 - self::set_review_popup_data( $user_id, $course_id );
2293 -
2294 - $this->response_success( __( 'Course completed successfully', 'tutor' ) );
2295 - }
2296 - }
2297 -
2298 - /**
2299 2058 * Set data for review popup.
2300 2059 *
2301 2060 * @since 2.2.5
2302 2061 * @since 2.4.0 removed $permalink param. store user meta instead of option data.
@@ -2322,34 +2081,18 @@
2322 2081 * @since 1.0.0
2323 2082 * @return void
2324 2083 */
2325 2084 public function popup_review_form() {
2326 - if ( ! is_user_logged_in() ) {
2327 - return;
2328 - }
2085 + if ( is_user_logged_in() ) {
2086 + $user_id = get_current_user_id();
2087 + $course_id = get_the_ID();
2088 + $meta_key = User::get_review_popup_meta( $course_id );
2089 + $review_course_id = (int) get_user_meta( $user_id, $meta_key, true );
2329 2090
2330 - $is_learning_area = tutor_utils()->is_learning_area();
2331 - $is_legacy_learning = tutor_utils()->is_legacy_learning_mode();
2332 - if ( $is_legacy_learning && $is_learning_area ) {
2333 - return;
2091 + if ( is_single() && $course_id === $review_course_id ) {
2092 + include tutor()->path . 'views/modal/review.php';
2093 + }
2334 2094 }
2335 -
2336 - $course_id = get_the_ID();
2337 -
2338 - if ( $is_learning_area && ! Input::has( 'subpage' ) ) {
2339 - $course_id = wp_get_post_parent_id( wp_get_post_parent_id( get_the_ID() ) );
2340 - }
2341 -
2342 - if ( empty( $course_id ) ) {
2343 - return;
2344 - }
2345 -
2346 - $user_id = get_current_user_id();
2347 - $meta_key = User::get_review_popup_meta( $course_id );
2348 - $review_course_id = (int) get_user_meta( $user_id, $meta_key, true );
2349 - if ( is_single() && $course_id === $review_course_id ) {
2350 - include tutor()->path . 'views/modal/review.php';
2351 - }
2352 2095 }
2353 2096
2354 2097 /**
2355 2098 * Review popup data clear
@@ -2377,9 +2120,8 @@
2377 2120 /**
2378 2121 * Delete course delete from frontend dashboard
2379 2122 *
2380 2123 * @since 2.0.0
2381 - *
2382 2124 * @return void
2383 2125 */
2384 2126 public function tutor_delete_dashboard_course() {
2385 2127 tutor_utils()->checking_nonce();
@@ -2398,9 +2140,9 @@
2398 2140 wp_send_json_error( array( 'message' => __( 'Only main instructor can delete this course', 'tutor' ) ) );
2399 2141 }
2400 2142
2401 2143 // Check if user is only an instructor.
2402 - if ( ! User::is_admin() ) {
2144 + if ( ! current_user_can( 'administrator' ) ) {
2403 2145 // Check if instructor can trash course.
2404 2146 $can_trash_post = tutor_utils()->get_option( 'instructor_can_delete_course' );
2405 2147
2406 2148 if ( ! $can_trash_post ) {
@@ -2410,9 +2152,9 @@
2410 2152
2411 2153 $trash_course = wp_update_post(
2412 2154 array(
2413 2155 'ID' => $course_id,
2414 - 'post_status' => CourseModel::STATUS_TRASH,
2156 + 'post_status' => 'trash',
2415 2157 )
2416 2158 );
2417 2159
2418 2160 if ( $trash_course ) {
@@ -2471,24 +2213,21 @@
2471 2213 *
2472 2214 * @return void
2473 2215 */
2474 2216 public function attach_product_with_course( $post_ID, $post_data ) {
2217 +
2475 2218 $monetize_by = tutor_utils()->get_option( 'monetize_by' );
2476 2219 $product_id = Input::post( '_tutor_course_product_id', 0, Input::TYPE_INT );
2477 2220
2478 - /**
2479 - * For native monetization, just return
2480 - * No need to attach anything.
2481 - */
2482 2221 if ( Ecommerce::MONETIZE_BY === $monetize_by ) {
2483 2222 return;
2484 2223 }
2485 2224
2486 2225 /**
2487 - * When course moved paid to free
2488 - * Keep the product linked and return.
2226 + * Unlink product from course.
2489 2227 */
2490 2228 if ( -1 === $product_id ) {
2229 + delete_post_meta( $post_ID, self::COURSE_PRODUCT_ID_META );
2491 2230 return;
2492 2231 }
2493 2232
2494 2233 /**
@@ -2515,31 +2254,36 @@
2515 2254 update_post_meta( $post_ID, self::COURSE_PRICE_TYPE_META, self::PRICE_TYPE_PAID );
2516 2255
2517 2256 if ( 'wc' === $monetize_by ) {
2518 2257
2519 - $is_update = ( $product_id && wc_get_product( $product_id ) ) ? true : false;
2258 + $is_update = ( $attached_product_id && wc_get_product( $attached_product_id ) ) ? true : false;
2520 2259
2521 2260 if ( $is_update ) {
2261 + $attached_product_id = $product_id;
2522 2262 update_post_meta( $post_ID, self::COURSE_PRODUCT_ID_META, $product_id );
2523 2263
2524 - $product_id = self::create_wc_product( $course->post_title, $course_price, $sale_price, $product_id );
2264 + $product_id = self::create_wc_product( $course->post_title, $course_price, $sale_price, $attached_product_id );
2525 2265 $product_obj = wc_get_product( $product_id );
2526 2266 if ( $product_obj->is_type( 'subscription' ) ) {
2527 - update_post_meta( $product_id, '_subscription_price', $course_price );
2267 + update_post_meta( $attached_product_id, '_subscription_price', $course_price );
2528 2268 }
2529 2269
2530 2270 // Set course regular & sale price.
2531 - self::set_course_regular_and_sale_price( $post_ID, $product_obj->get_regular_price(), $product_obj->get_sale_price() );
2271 + update_post_meta( $post_ID, self::COURSE_PRICE_META, $product_obj->get_regular_price() );
2272 + update_post_meta( $post_ID, self::COURSE_SALE_PRICE_META, $product_obj->get_sale_price() );
2532 2273 } else {
2533 2274 // Create new WC product name with course title.
2534 2275 $product_id = self::create_wc_product( $course->post_title, $course_price, $sale_price );
2535 2276 if ( $product_id ) {
2536 2277 $product_obj = wc_get_product( $product_id );
2278 + update_post_meta( $post_ID, self::COURSE_PRODUCT_ID_META, $product_id );
2279 + // Mark product for woocommerce.
2280 + update_post_meta( $product_id, '_virtual', 'yes' );
2281 + update_post_meta( $product_id, '_tutor_product', 'yes' );
2537 2282
2538 - self::sync_course_with_wc_product( $post_ID, $product_id );
2539 -
2540 2283 // Set course regular & sale price.
2541 - self::set_course_regular_and_sale_price( $post_ID, $product_obj->get_regular_price(), $product_obj->get_sale_price() );
2284 + update_post_meta( $post_ID, self::COURSE_PRICE_META, $product_obj->get_regular_price() );
2285 + update_post_meta( $post_ID, self::COURSE_SALE_PRICE_META, $product_obj->get_sale_price() );
2542 2286 }
2543 2287 }
2544 2288
2545 2289 $course_post_thumbnail = Input::post( 'thumbnail_id', 0, Input::TYPE_INT );
@@ -2587,8 +2331,33 @@
2587 2331 }
2588 2332 }
2589 2333
2590 2334 /**
2335 + * Add Course level to course settings
2336 + *
2337 + * @since 1.4.1
2338 + *
2339 + * @param array $args arguments.
2340 + * @return array
2341 + */
2342 + public function add_course_level_to_settings( $args ) {
2343 + $course_id = get_the_ID();
2344 + $levels = tutor_utils()->course_levels();
2345 + $course_level = get_post_meta( $course_id, '_tutor_course_level', true );
2346 +
2347 + $args['general']['fields']['_tutor_course_level'] = array(
2348 + 'type' => 'select',
2349 + 'label' => __( 'Difficulty Level', 'tutor' ),
2350 + 'label_title' => __( 'Enable', 'tutor' ),
2351 + 'options' => $levels,
2352 + 'value' => $course_level ? $course_level : 'intermediate',
2353 + 'desc' => __( 'Course difficulty level', 'tutor' ),
2354 + );
2355 +
2356 + return $args;
2357 + }
2358 +
2359 + /**
2591 2360 * Check if course starting
2592 2361 *
2593 2362 * @since 1.4.8
2594 2363 * @return void
@@ -2608,9 +2377,8 @@
2608 2377 /**
2609 2378 * Add Course level to course settings
2610 2379 *
2611 2380 * @since 1.4.8
2612 - *
2613 2381 * @return void
2614 2382 */
2615 2383 public function course_elements_enable_disable() {
2616 2384 add_filter( 'tutor_course/single/completing-progress-bar', array( $this, 'enable_disable_course_progress_bar' ) );
@@ -2643,13 +2411,12 @@
2643 2411 *
2644 2412 * @since 1.4.8
2645 2413 *
2646 2414 * @param string $html HTML string.
2647 - *
2648 2415 * @return string
2649 2416 */
2650 2417 public function enable_disable_material_includes( $html ) {
2651 - $disable_option = ! (bool) get_tutor_option( 'enable_course_material', true );
2418 + $disable_option = ! (bool) get_tutor_option( 'enable_course_material', true, true );
2652 2419 if ( $disable_option ) {
2653 2420 return '';
2654 2421 }
2655 2422 return $html;
@@ -2660,9 +2427,8 @@
2660 2427 *
2661 2428 * @since 1.4.8
2662 2429 *
2663 2430 * @param string $html HTML string.
2664 - *
2665 2431 * @return string
2666 2432 */
2667 2433 public function enable_disable_course_content( $html ) {
2668 2434 $disable_option = ! (bool) tutor_utils()->get_option( 'enable_course_description', true, true );
@@ -2677,9 +2443,8 @@
2677 2443 *
2678 2444 * @since 1.4.8
2679 2445 *
2680 2446 * @param string $html HTML string.
2681 - *
2682 2447 * @return string
2683 2448 */
2684 2449 public function enable_disable_course_benefits( $html ) {
2685 2450 $disable_option = ! (bool) tutor_utils()->get_option( 'enable_course_benefits', true, true );
@@ -2694,9 +2459,8 @@
2694 2459 *
2695 2460 * @since 1.4.8
2696 2461 *
2697 2462 * @param string $html HTML string.
2698 - *
2699 2463 * @return string
2700 2464 */
2701 2465 public function enable_disable_course_requirements( $html ) {
2702 2466 $disable_option = ! (bool) tutor_utils()->get_option( 'enable_course_requirements', true, true );
@@ -2711,9 +2475,8 @@
2711 2475 *
2712 2476 * @since 1.4.8
2713 2477 *
2714 2478 * @param string $html HTML string.
2715 - *
2716 2479 * @return string
2717 2480 */
2718 2481 public function enable_disable_course_target_audience( $html ) {
2719 2482 $disable_option = ! (bool) tutor_utils()->get_option( 'enable_course_target_audience', true, true );
@@ -2758,9 +2521,9 @@
2758 2521 unset( $items['reviews'] );
2759 2522 }
2760 2523
2761 2524 // Whether enrollment require.
2762 - $is_enrolled = EnrollmentModel::is_enrolled();
2525 + $is_enrolled = tutor_utils()->is_enrolled();
2763 2526
2764 2527 return array_filter(
2765 2528 $items,
2766 2529 function ( $item ) use ( $is_enrolled ) {
@@ -2775,9 +2538,8 @@
2775 2538 /**
2776 2539 * Filter product in shop page
2777 2540 *
2778 2541 * @since 1.4.9
2779 - *
2780 2542 * @return void|null
2781 2543 */
2782 2544 public function filter_product_in_shop_page() {
2783 2545 $hide_course_from_shop_page = (bool) get_tutor_option( 'hide_course_from_shop_page' );
@@ -2783,11 +2545,11 @@
2783 2545 $hide_course_from_shop_page = (bool) get_tutor_option( 'hide_course_from_shop_page' );
2784 2546 if ( ! $hide_course_from_shop_page ) {
2785 2547 return;
2786 2548 }
2787 - add_filter( 'woocommerce_product_query', array( $this, 'filter_woocommerce_product_query' ) );
2788 - add_filter( 'edd_downloads_query', array( $this, 'filter_edd_downloads_query' ), 10 );
2789 - add_filter( 'pre_get_posts', array( $this, 'filter_archive_meta_query' ), 1 );
2549 + add_action( 'woocommerce_product_query', array( $this, 'filter_woocommerce_product_query' ) );
2550 + add_filter( 'edd_downloads_query', array( $this, 'filter_edd_downloads_query' ), 10, 2 );
2551 + add_action( 'pre_get_posts', array( $this, 'filter_archive_meta_query' ), 1 );
2790 2552 }
2791 2553
2792 2554
2793 2555 /**
@@ -2793,9 +2555,8 @@
2793 2555 /**
2794 2556 * Tutor product meta query
2795 2557 *
2796 2558 * @since 1.4.9
2797 - *
2798 2559 * @return array
2799 2560 */
2800 2561 public function tutor_product_meta_query() {
2801 2562 $meta_query = array(
@@ -2810,9 +2571,8 @@
2810 2571 *
2811 2572 * @since 1.4.9
2812 2573 *
2813 2574 * @param \WP_Query $wp_query WP Query instance.
2814 - *
2815 2575 * @return \WP_Query
2816 2576 */
2817 2577 public function filter_woocommerce_product_query( $wp_query ) {
2818 2578 $product_ids = $this->get_connected_wc_product_ids();
@@ -2854,9 +2614,8 @@
2854 2614 *
2855 2615 * @since 1.4.9
2856 2616 *
2857 2617 * @param \WP_Query $query WP Query instance.
2858 - *
2859 2618 * @return \WP_Query
2860 2619 */
2861 2620 public function filter_edd_downloads_query( $query ) {
2862 2621 $query['meta_query'][] = $this->tutor_product_meta_query();
@@ -2868,9 +2627,8 @@
2868 2627 *
2869 2628 * @since 1.4.9
2870 2629 *
2871 2630 * @param \WP_Query $wp_query WP Query instance.
2872 - *
2873 2631 * @return \WP_Query
2874 2632 */
2875 2633 public function filter_archive_meta_query( $wp_query ) {
2876 2634 if ( ! is_admin() && $wp_query->is_archive && $wp_query->get( 'post_type' ) === 'download' ) {
@@ -2884,9 +2642,8 @@
2884 2642 *
2885 2643 * @since 1.5.8
2886 2644 *
2887 2645 * @param string $html HTML string.
2888 - *
2889 2646 * @return string
2890 2647 */
2891 2648 public function remove_price_if_enrolled( $html ) {
2892 2649 $should_removed = apply_filters( 'should_remove_price_if_enrolled', true );
@@ -2892,9 +2649,9 @@
2892 2649 $should_removed = apply_filters( 'should_remove_price_if_enrolled', true );
2893 2650
2894 2651 if ( $should_removed ) {
2895 2652 $course_id = get_the_ID();
2896 - $enrolled = EnrollmentModel::is_enrolled( $course_id );
2653 + $enrolled = tutor_utils()->is_enrolled( $course_id );
2897 2654 if ( $enrolled ) {
2898 2655 $html = '';
2899 2656 }
2900 2657 }
@@ -2901,119 +2658,112 @@
2901 2658 return $html;
2902 2659 }
2903 2660
2904 2661 /**
2905 - * Get course completion missing requirements message.
2662 + * Check if all lessons and quizzes done before mark course complete.
2906 2663 *
2907 - * @since 4.0.0
2664 + * @since 1.5.8
2908 2665 *
2909 - * @param int $course_id Course ID.
2910 - * @param int $user_id User ID.
2911 - *
2912 - * @return string|null
2666 + * @param string $html HTML string.
2667 + * @return string
2913 2668 */
2914 - public static function get_course_completion_restrict_msg( $course_id = 0, $user_id = 0 ) {
2915 - $course_id = tutor_utils()->get_post_id( $course_id );
2916 - $user_id = tutor_utils()->get_user_id( $user_id );
2669 + public function tutor_lms_hide_course_complete_btn( $html ) {
2917 2670
2918 - if ( 'strict' !== tutor_utils()->get_option( 'course_completion_process' ) ) {
2919 - return null;
2671 + $completion_mode = tutor_utils()->get_option( 'course_completion_process' );
2672 + if ( 'strict' !== $completion_mode ) {
2673 + return $html;
2920 2674 }
2921 2675
2922 - $completed_lessons = tutor_utils()->get_completed_lesson_count_by_course( $course_id, $user_id );
2923 - $total_lessons = tutor_utils()->get_lesson_count_by_course( $course_id ); // @phpstan-ignore method.notFound
2676 + $completed_lesson = tutor_utils()->get_completed_lesson_count_by_course();
2677 + $lesson_count = tutor_utils()->get_lesson_count_by_course();
2924 2678
2925 - if ( $completed_lessons < $total_lessons ) {
2926 - return __( 'Complete all lessons to mark this course as complete', 'tutor' );
2679 + if ( $completed_lesson < $lesson_count ) {
2680 + return '<div class="tutor-alert tutor-warning tutor-mt-28">
2681 + <div class="tutor-alert-text">
2682 + <span class="tutor-alert-icon tutor-fs-4 tutor-icon-circle-info tutor-mr-12"></span>
2683 + <span>' . __( 'Complete all lessons to mark this course as complete', 'tutor' ) . '</span>
2684 + </div>
2685 + </div>';
2927 2686 }
2928 2687
2929 - $course_contents = tutor_utils()->get_course_contents_by_id( $course_id );
2688 + $quizzes = array();
2689 + $assignments = array();
2930 2690
2931 - $required_quiz_pass_count = 0;
2932 - $required_assignment_pass_count = 0;
2691 + $course_contents = tutor_utils()->get_course_contents_by_id();
2692 + if ( tutor_utils()->count( $course_contents ) ) {
2693 + foreach ( $course_contents as $content ) {
2694 + if ( 'tutor_quiz' === $content->post_type ) {
2695 + $quizzes[] = $content;
2696 + }
2697 + if ( 'tutor_assignments' === $content->post_type ) {
2698 + $assignments[] = $content;
2699 + }
2700 + }
2701 + }
2933 2702
2934 - $is_assignment_addon_enabled = tutor_utils()->is_addon_enabled( 'tutor-assignments' );
2703 + $required_assignment_pass = 0;
2935 2704
2936 - foreach ( $course_contents as $content ) {
2705 + foreach ( $assignments as $row ) {
2937 2706
2938 - if ( tutor()->quiz_post_type === $content->post_type ) {
2939 - if ( ! QuizModel::is_quiz_passed( $content->ID, $user_id ) ) {
2940 - ++$required_quiz_pass_count;
2941 - }
2942 - }
2707 + $submitted_assignment = tutor_utils()->is_assignment_submitted( $row->ID );
2708 + $is_reviewed_by_instructor = null === $submitted_assignment
2709 + ? false
2710 + : get_comment_meta( $submitted_assignment->comment_ID, 'evaluate_time', true );
2943 2711
2944 - if ( $is_assignment_addon_enabled && tutor()->assignment_post_type === $content->post_type ) {
2945 - if ( ! Assignments::is_assignment_passed( $content->ID, $user_id ) ) {
2946 - ++$required_assignment_pass_count;
2712 + if ( $submitted_assignment && $is_reviewed_by_instructor ) {
2713 + $pass_mark = tutor_utils()->get_assignment_option( $submitted_assignment->comment_post_ID, 'pass_mark' );
2714 + $given_mark = get_comment_meta( $submitted_assignment->comment_ID, 'assignment_mark', true );
2715 +
2716 + if ( $given_mark < $pass_mark ) {
2717 + $required_assignment_pass++;
2947 2718 }
2719 + } else {
2720 + $required_assignment_pass++;
2948 2721 }
2949 2722 }
2950 2723
2951 - if ( ! $required_quiz_pass_count && ! $required_assignment_pass_count ) {
2952 - return null;
2953 - }
2724 + $is_quiz_pass = true;
2725 + $required_quiz_pass = 0;
2954 2726
2955 - return self::get_course_completion_requirement_message(
2956 - $required_quiz_pass_count,
2957 - $required_assignment_pass_count
2958 - );
2959 - }
2727 + if ( tutor_utils()->count( $quizzes ) ) {
2728 + foreach ( $quizzes as $quiz ) {
2960 2729
2961 - /**
2962 - * Build missing course completion requirements message.
2963 - *
2964 - * @since 4.0.0
2965 - *
2966 - * @param int $quiz_count Total quiz count.
2967 - * @param int $assignment_count Total assignment count.
2968 - *
2969 - * @return string
2970 - */
2971 - private static function get_course_completion_requirement_message( $quiz_count, $assignment_count ) {
2972 - $quiz_label = _n( 'quiz', 'quizzes', $quiz_count, 'tutor' );
2973 - $assignment_label = _n( 'assignment', 'assignments', $assignment_count, 'tutor' );
2730 + $attempt = tutor_utils()->get_quiz_attempt( $quiz->ID );
2731 + if ( $attempt ) {
2732 + $passing_grade = tutor_utils()->get_quiz_option( $quiz->ID, 'passing_grade', 0 );
2733 + $earned_percentage = $attempt->earned_marks > 0 ? ( number_format( ( $attempt->earned_marks * 100 ) / $attempt->total_marks ) ) : 0;
2974 2734
2975 - if ( $quiz_count && ! $assignment_count ) {
2976 - return sprintf(
2977 - /* translators: %1$s: item count; %2$s: item label */
2978 - __( 'You have to pass %1$s %2$s to complete this course.', 'tutor' ),
2979 - $quiz_count,
2980 - $quiz_label
2981 - );
2735 + if ( $earned_percentage < $passing_grade ) {
2736 + $required_quiz_pass++;
2737 + $is_quiz_pass = false;
2738 + }
2739 + } else {
2740 + $required_quiz_pass++;
2741 + $is_quiz_pass = false;
2742 + }
2743 + }
2982 2744 }
2983 2745
2984 - if ( ! $quiz_count && $assignment_count ) {
2985 - return sprintf(
2986 - /* translators: %1$s: item count; %2$s: item label */
2987 - __( 'You have to pass %1$s %2$s to complete this course.', 'tutor' ),
2988 - $assignment_count,
2989 - $assignment_label
2990 - );
2991 - }
2746 + if ( ! $is_quiz_pass || $required_assignment_pass > 0 ) {
2747 + $_msg = '';
2748 + $quiz_str = _n( 'quiz', 'quizzes', $required_quiz_pass, 'tutor' );
2749 + $assignment_str = _n( 'assignment', 'assignments', $required_assignment_pass, 'tutor' );
2992 2750
2993 - return sprintf(
2994 - /* translators: %1$s: quiz count; %2$s: quiz label; %3$s: assignment count; %4$s: assignment label */
2995 - __( 'You have to pass %1$s %2$s and %3$s %4$s to complete this course.', 'tutor' ),
2996 - $quiz_count,
2997 - $quiz_label,
2998 - $assignment_count,
2999 - $assignment_label
3000 - );
3001 - }
2751 + if ( ! $is_quiz_pass && 0 == $required_assignment_pass ) {
2752 + /* translators: %1$s: number of quiz/assignment pass required; %2$s: quiz/assignment string */
2753 + $_msg = sprintf( __( 'You have to pass %1$s %2$s to complete this course.', 'tutor' ), $required_quiz_pass, $quiz_str );
2754 + }
3002 2755
3003 - /**
3004 - * Check if all lessons and quizzes done before mark course complete.
3005 - *
3006 - * @since 1.5.8
3007 - *
3008 - * @param string $html HTML string.
3009 - *
3010 - * @return string
3011 - */
3012 - public function tutor_lms_hide_course_complete_btn( $html ) {
3013 - $_msg = self::get_course_completion_restrict_msg();
2756 + if ( $is_quiz_pass && $required_assignment_pass > 0 ) {
2757 + //phpcs:ignore
2758 + $_msg = sprintf( __( 'You have to pass %1$s %2$s to complete this course.', 'tutor' ), $required_assignment_pass, $assignment_str );
2759 + }
3014 2760
3015 - if ( $_msg ) {
2761 + if ( ! $is_quiz_pass && $required_assignment_pass > 0 ) {
2762 + /* translators: %1$s: number of quiz pass required; %2$s: quiz string; %3$s: number of assignment pass required; %4$s: assignment string */
2763 + $_msg = sprintf( __( 'You have to pass %1$s %2$s and %3$s %4$s to complete this course.', 'tutor' ), $required_quiz_pass, $quiz_str, $required_assignment_pass, $assignment_str );
2764 + }
2765 +
3016 2766 return '<div class="tutor-alert tutor-warning tutor-mt-28">
3017 2767 <div class="tutor-alert-text">
3018 2768 <span class="tutor-alert-icon tutor-fs-4 tutor-icon-circle-info tutor-mr-12"></span>
3019 2769 <span>' . $_msg . '</span>
@@ -3029,9 +2779,8 @@
3029 2779 *
3030 2780 * @since 1.5.8
3031 2781 *
3032 2782 * @param string $html HTML string.
3033 - *
3034 2783 * @return string
3035 2784 */
3036 2785 public function get_generate_greadbook( $html ) {
3037 2786 if ( ! tutor_utils()->is_completed_course() ) {
@@ -3043,9 +2792,8 @@
3043 2792 /**
3044 2793 * Add social share content in header
3045 2794 *
3046 2795 * @since 1.6.3
3047 - *
3048 2796 * @return void
3049 2797 */
3050 2798 public function social_share_content() {
3051 2799 global $wp_query, $post;
@@ -3069,9 +2817,8 @@
3069 2817 *
3070 2818 * @since 1.8.2
3071 2819 *
3072 2820 * @param integer $post_id post ID.
3073 - *
3074 2821 * @return void
3075 2822 */
3076 2823 public function delete_associated_enrollment( $post_id ) {
3077 2824 global $wpdb;
@@ -3082,12 +2829,11 @@
3082 2829 post_id
3083 2830 FROM
3084 2831 {$wpdb->postmeta}
3085 2832 WHERE
3086 - meta_key=%s
2833 + meta_key='_tutor_enrolled_by_order_id'
3087 2834 AND meta_value = %d
3088 2835 ",
3089 - EnrollmentModel::ENROLLMENT_ORDER_ID_META,
3090 2836 $post_id
3091 2837 )
3092 2838 );
3093 2839
@@ -3103,35 +2849,21 @@
3103 2849 /**
3104 2850 * Reset course progress.
3105 2851 *
3106 2852 * @since 1.5.8
3107 - *
3108 2853 * @return void
3109 2854 */
3110 2855 public function tutor_reset_course_progress() {
3111 2856 tutor_utils()->checking_nonce();
3112 - $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
3113 - $context = Input::post( 'context', '' );
3114 - $course_reset_progress = tutor_utils()->get_option( 'course_reset_progress', false );
3115 - $course_retake_feature = tutor_utils()->get_option( 'course_retake_feature', false );
2857 + $course_id = Input::post( 'course_id' );
3116 2858
3117 - if ( ! $course_reset_progress && 'learning-area-sidebar' === $context ) {
3118 - $this->response_bad_request( __( 'You do not have permission to reset this course.', 'tutor' ) );
2859 + if ( ! $course_id || ! is_numeric( $course_id ) || ! tutor_utils()->is_enrolled( $course_id ) ) {
2860 + wp_send_json_error( array( 'message' => __( 'Invalid Course ID or Access Denied.', 'tutor' ) ) );
3119 2861 return;
3120 2862 }
3121 2863
3122 - if ( ! $course_retake_feature && ( 'course-landing' === $context || 'learning-area' === $context ) ) {
3123 - $this->response_bad_request( __( 'You do not have permission to retake this course.', 'tutor' ) );
3124 - return;
3125 - }
3126 -
3127 - if ( ! $course_id || ! is_numeric( $course_id ) || ! EnrollmentModel::is_enrolled( $course_id ) ) {
3128 - $this->response_bad_request( __( 'Invalid Course ID or Access Denied.', 'tutor' ) );
3129 - return;
3130 - }
3131 -
3132 2864 tutor_utils()->delete_course_progress( $course_id );
3133 - $this->json_response( '', array( 'redirect_to' => tutor_utils()->get_course_first_lesson( $course_id ) ) );
2865 + wp_send_json_success( array( 'redirect_to' => tutor_utils()->get_course_first_lesson( $course_id ) ) );
3134 2866 }
3135 2867
3136 2868 /**
3137 2869 * Do enroll if guest attempt to enroll and course is free
@@ -3139,9 +2871,9 @@
3139 2871 * @since 1.9.8
3140 2872 *
3141 2873 * @param integer $course_id course ID.
3142 2874 * @param integer $user_id user ID.
3143 - *
2875 +
3144 2876 * @return void
3145 2877 */
3146 2878 public function enroll_after_login_if_attempt( int $course_id, int $user_id ) {
3147 2879 $course_id = sanitize_text_field( $course_id );
@@ -3149,9 +2881,9 @@
3149 2881
3150 2882 if ( $course_id && $is_allowed ) {
3151 2883 $is_purchasable = tutor_utils()->is_course_purchasable( $course_id );
3152 2884 if ( ! $is_purchasable ) {
3153 - EnrollmentModel::do_enroll( $course_id, $order_id = 0, $user_id );
2885 + tutor_utils()->do_enroll( $course_id, $order_id = 0, $user_id );
3154 2886 do_action( 'guest_attempt_after_enrollment', $course_id );
3155 2887 }
3156 2888 }
3157 2889 }
@@ -3159,9 +2891,8 @@
3159 2891 /**
3160 2892 * Handle course enrollment
3161 2893 *
3162 2894 * @since 2.1.0
3163 - *
3164 2895 * @return void
3165 2896 */
3166 2897 public function course_enrollment() {
3167 2898 tutor_utils()->checking_nonce();
@@ -3168,46 +2899,21 @@
3168 2899
3169 2900 $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
3170 2901 $user_id = get_current_user_id();
3171 2902
3172 - if ( ! $course_id || ! $user_id ) {
3173 - wp_send_json_error( tutor_utils()->error_message( 'invalid_req' ) );
3174 - }
3175 -
3176 - $password_protected = post_password_required( $course_id );
3177 - if ( $password_protected ) {
3178 - wp_send_json_error( __( 'This course is password protected', 'tutor' ) );
3179 - }
3180 -
3181 - $course = get_post( $course_id );
3182 -
3183 - if ( CourseModel::STATUS_PRIVATE === $course->post_status && ! current_user_can( 'read_private_tutor_courses' ) ) {
3184 - wp_send_json_error( __( 'You do not have permission to enroll in this course', 'tutor' ) );
3185 - }
3186 -
3187 - /**
3188 - * This check was added to address a security issue where users could
3189 - * enroll in a course via an AJAX call without purchasing it.
3190 - *
3191 - * To prevent this, we now verify whether the course is paid.
3192 - * Additionally, we check if the user is already enrolled, since
3193 - * Tutor's default behavior enrolls users automatically upon purchase.
3194 - *
3195 - * @since 3.9.4
3196 - */
3197 - if ( tutor_utils()->is_course_purchasable( $course_id ) ) {
3198 - $is_enrolled = (bool) EnrollmentModel::is_enrolled( $course_id, $user_id );
3199 -
3200 - if ( ! $is_enrolled ) {
3201 - wp_send_json_error( __( 'Please purchase the course before enrolling', 'tutor' ) );
2903 + if ( $course_id ) {
2904 + $password_protected = post_password_required( $course_id );
2905 + if ( $password_protected ) {
2906 + wp_send_json_error( __( 'This course is password protected', 'tutor' ) );
3202 2907 }
3203 - }
3204 -
3205 - $enroll = EnrollmentModel::do_enroll( $course_id, 0, $user_id );
3206 - if ( $enroll ) {
3207 - wp_send_json_success( __( 'Enrollment successfully done!', 'tutor' ) );
2908 + $enroll = tutor_utils()->do_enroll( $course_id, 0, $user_id );
2909 + if ( $enroll ) {
2910 + wp_send_json_success( __( 'Enrollment successfully done!', 'tutor' ) );
2911 + } else {
2912 + wp_send_json_error( __( 'Enrollment failed, please try again!', 'tutor' ) );
2913 + }
3208 2914 } else {
3209 - wp_send_json_error( __( 'Enrollment failed, please try again!', 'tutor' ) );
2915 + wp_send_json_error( __( 'Invalid course ID', 'tutor' ) );
3210 2916 }
3211 2917 }
3212 2918
3213 2919 /**
@@ -3294,10 +3000,14 @@
3294 3000 'regular_price' => $regular_price,
3295 3001 'sale_price' => $sale_price,
3296 3002 );
3297 3003
3298 - $card_data = apply_filters( 'tutor_course_mini_info', $info, $post );
3004 + if ( 'course-bundle' === $post->post_type && tutor_utils()->is_addon_enabled( 'tutor-pro/addons/course-bundle/course-bundle.php' ) ) {
3005 + $info['total_course'] = count( BundleModel::get_bundle_course_ids( $post->ID ) );
3006 + }
3299 3007
3008 + $card_data = apply_filters( 'tutor_add_course_plan_info', $info, $post );
3009 +
3300 3010 return $card_data;
3301 3011 }
3302 3012
3303 3013 /**
@@ -3318,9 +3028,9 @@
3318 3028 $info['last_updated'] = tutor_i18n_get_formated_date( $post->post_modified_at );
3319 3029 $info['course_duration'] = tutor_utils()->get_course_duration( $post->ID, false );
3320 3030 $info['total_enrolled'] = tutor_utils()->count_enrolled_users_by_course( $post->ID );
3321 3031
3322 - $card_data = apply_filters( 'tutor_course_card_data', $info, $post );
3032 + $card_data = apply_filters( 'tutor_add_course_plan_info', $info, $post );
3323 3033
3324 3034 return $card_data;
3325 3035 }
3326 3036
@@ -3359,217 +3069,6 @@
3359 3069 $args['role__in'] = array_map( 'sanitize_text_field', $filter->role );
3360 3070 }
3361 3071
3362 3072 return $args;
3363 - }
3364 -
3365 - /**
3366 - * Get a list of possible course status.
3367 - *
3368 - * @since 3.6.2
3369 - *
3370 - * @return array
3371 - */
3372 - public static function course_status_list() {
3373 - return array(
3374 - 'publish',
3375 - 'private',
3376 - 'draft',
3377 - 'trash',
3378 - 'pending',
3379 - 'future',
3380 - );
3381 - }
3382 -
3383 - /**
3384 - * Link a course/bundle post to a WooCommerce product.
3385 - *
3386 - * @since 3.8.2
3387 - *
3388 - * @param int $post_ID The WordPress post ID of the course.
3389 - * @param int $product_id The WooCommerce product ID to associate with the course.
3390 - *
3391 - * @return void
3392 - */
3393 - public static function sync_course_with_wc_product( $post_ID, $product_id ) {
3394 -
3395 - update_post_meta( $post_ID, self::COURSE_PRODUCT_ID_META, $product_id );
3396 -
3397 - // Mark product for woocommerce.
3398 - update_post_meta( $product_id, '_virtual', 'yes' );
3399 - update_post_meta( $product_id, '_tutor_product', 'yes' );
3400 - }
3401 -
3402 - /**
3403 - * Map Tutor's course prices to WooCommerce.
3404 - *
3405 - * @since 3.8.2
3406 - *
3407 - * @param int $post_ID The WordPress post ID of the course.
3408 - * @param string|int|float $regular_price The regular price.
3409 - * @param string|int|float $sale_price The sale price.
3410 - * @return void
3411 - */
3412 - private static function set_course_regular_and_sale_price( $post_ID, $regular_price, $sale_price ) {
3413 -
3414 - // Set course regular & sale price.
3415 - update_post_meta( $post_ID, self::COURSE_PRICE_META, $regular_price );
3416 - update_post_meta( $post_ID, self::COURSE_SALE_PRICE_META, $sale_price );
3417 - }
3418 -
3419 - /**
3420 - * Render start/resume button in enrolled course action btn.
3421 - *
3422 - * @since 4.0.0
3423 - *
3424 - * @param int $course_id course id.
3425 - *
3426 - * @return void
3427 - */
3428 - public function render_course_action_btn( int $course_id ) {
3429 - $is_completed = tutor_utils()->is_completed_course( $course_id );
3430 - if ( $is_completed ) {
3431 - $certificate_addon_active = tutor_utils()->is_addon_enabled( 'tutor-certificate' );
3432 - if ( ! $certificate_addon_active ) {
3433 - Button::make()
3434 - ->tag( 'a' )
3435 - ->label( __( 'Completed', 'tutor' ) )
3436 - ->icon( Icon::COMPLETED_CIRCLE )
3437 - ->variant( Variant::PRIMARY )
3438 - ->size( Size::X_SMALL )
3439 - ->attr( 'href', esc_url( get_the_permalink( $course_id ) ) )
3440 - ->render();
3441 - }
3442 - return;
3443 - }
3444 -
3445 - $course_progress = tutor_utils()->get_course_completed_percent( $course_id, 0, true );
3446 - $button_text = $course_progress['completed_percent'] > 0 ? __( 'Resume', 'tutor' ) : __( 'Start', 'tutor' );
3447 - $button_url = tutor_utils()->get_course_first_lesson( $course_id );
3448 -
3449 - if ( ! $button_url ) {
3450 - $button_url = get_the_permalink( $course_id );
3451 - }
3452 -
3453 - Button::make()
3454 - ->tag( 'a' )
3455 - ->label( $button_text )
3456 - ->icon( Icon::PLAY_2 )
3457 - ->variant( Variant::PRIMARY )
3458 - ->size( Size::X_SMALL )
3459 - ->attr( 'href', esc_url( $button_url ) )
3460 - ->render();
3461 - }
3462 -
3463 - /**
3464 - * Get the content for the course completion modal.
3465 - *
3466 - * This method returns HTML for displaying a modal that informs the user
3467 - * that they have not completed all required lessons and assessments. It includes
3468 - * the user's current progress shown as a percentage and a progress bar.
3469 - *
3470 - * @since 4.0.0
3471 - *
3472 - * @param float $course_progress The completion percentage of the course.
3473 - *
3474 - * @return string The rendered HTML content for the modal.
3475 - */
3476 - public static function get_complete_modal_content( float $course_progress = 0 ): string {
3477 - ob_start();
3478 - ?>
3479 - <div>
3480 - <p class="tutor-p3 tutor-text-secondary tutor-text-center tutor-mb-7 tutor-px-11">
3481 - <?php esc_html_e( 'You have not completed all required lessons and assessments. ', 'tutor' ); ?>
3482 - </p>
3483 - <div class="tutor-finish-course-progress tutor-border tutor-p-5 tutor-flex tutor-flex-column tutor-gap-4 tutor-surface-base tutor-rounded-md">
3484 - <div class="tutor-flex tutor-items-center tutor-justify-between">
3485 - <div class="tutor-p3 tutor-text-secondary">
3486 - <?php esc_html_e( 'Your Progress', 'tutor' ); ?>
3487 - </div>
3488 - <div class="tutor-p1 tutor-font-bold">
3489 - <?php echo esc_html( number_format_i18n( $course_progress ) . '%' ); ?>
3490 - </div>
3491 - </div>
3492 - <?php
3493 - Progress::make()->type( 'bar' )->value( $course_progress )->render();
3494 - ?>
3495 - </div>
3496 - </div>
3497 - <?php
3498 - return ob_get_clean();
3499 - }
3500 -
3501 - /**
3502 - * Render the course complete button.
3503 - *
3504 - * Displays a button that allows the user to complete the course. If the course
3505 - * progress is less than 100%, clicking the button shows a modal informing the user
3506 - * that not all requirements are fulfilled. If the course progress is 100%,
3507 - * clicking the button attempts to complete the course.
3508 - *
3509 - * @since 4.0.0
3510 - *
3511 - * @param string $modal_id The HTML ID of the modal to display if not complete.
3512 - * @param int $course_id The ID of the course.
3513 - * @param float $course_progress The current completion percentage of the course.
3514 - * @param string $size The button size.
3515 - * @param string $tooltip Optional. Tooltip message.
3516 - * @param bool $block Optional. Whether the button is full-width.
3517 - *
3518 - * @return void
3519 - */
3520 - public static function render_course_complete_btn( string $modal_id, int $course_id, float $course_progress = 0, string $size = Size::MEDIUM, string $tooltip = '', bool $block = false ): void {
3521 - $button = Button::make()
3522 - ->variant( Variant::SECONDARY )
3523 - ->label( __( 'Complete the Course', 'tutor' ) )
3524 - ->icon( Icon::TICK_MARK )
3525 - ->size( $size )
3526 - ->block( $block )
3527 - ->attr( 'type', 'button' );
3528 -
3529 - if ( ! empty( $tooltip ) ) {
3530 - $button->disabled();
3531 - }
3532 -
3533 - if ( $course_progress < 100 ) {
3534 - $button->attr( '@click', "TutorCore.modal.showModal('{$modal_id}')" );
3535 - } else {
3536 - $button->attr( '@click', "handleCourseComplete({$course_id})" );
3537 - $button->attr( ':class', "courseCompleteMutation?.isPending ? 'tutor-btn-loading' : ''" );
3538 - $button->attr( ':disabled', 'courseCompleteMutation?.isPending' );
3539 - }
3540 -
3541 - if ( ! empty( $tooltip ) ) {
3542 - Tooltip::make()
3543 - ->content( $tooltip )
3544 - ->placement( Tooltip::PLACEMENT_BOTTOM )
3545 - ->arrow( Tooltip::ARROW_CENTER )
3546 - ->trigger_element( $button->get() )
3547 - ->render();
3548 - } else {
3549 - $button->render();
3550 - }
3551 - }
3552 -
3553 - /**
3554 - * Render course retake button
3555 - *
3556 - * @since 4.0.0
3557 - *
3558 - * @param string $modal_id Modal id.
3559 - * @param string $size The button size.
3560 - * @param bool $block Optional. Whether the button is full-width.
3561 - *
3562 - * @return void
3563 - */
3564 - public static function render_course_retake_btn( string $modal_id, string $size = Size::MEDIUM, bool $block = false ): void {
3565 - Button::make()
3566 - ->variant( Variant::SECONDARY )
3567 - ->label( __( 'Retake this Course', 'tutor' ) )
3568 - ->icon( Icon::RELOAD_4 )
3569 - ->size( $size )
3570 - ->block( $block )
3571 - ->attr( 'type', 'button' )
3572 - ->attr( '@click', "TutorCore.modal.showModal('{$modal_id}')" )
3573 - ->render();
3574 3073 }
3575 3074 }