PluginProbe
Tutor LMS – eLearning and online course solution / 3.4.1
Tutor LMS – eLearning and online course solution v3.4.1
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 +277 -748 4.0.23.4.1 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 */
@@ -252,9 +217,9 @@
252 217 * @since 1.9.8
253 218 */
254 219 add_action( 'tutor_do_enroll_after_login_if_attempt', array( $this, 'enroll_after_login_if_attempt' ), 10, 2 );
255 220
256 - 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' ) );
257 222
258 223 add_action( 'wp_ajax_tutor_get_wc_product', array( $this, 'get_wc_product' ) );
259 224 add_action( 'wp_ajax_tutor_get_wc_products', array( $this, 'get_wc_products' ) );
260 225
@@ -302,39 +267,11 @@
302 267 add_filter( 'tutor_user_list_args', array( $this, 'user_list_args_for_instructor' ) );
303 268
304 269 add_filter( 'template_include', array( $this, 'handle_password_protected' ) );
305 270 add_action( 'login_form_postpass', array( $this, 'handle_password_submit' ) );
306 - add_filter( 'post_password_required', array( $this, 'bypass_password_for_enrolled' ), 10, 2 );
307 - add_filter( 'the_preview', array( $this, 'handle_schedule_courses' ) );
308 -
309 - add_action( 'tutor_course_action_btn', array( $this, 'render_course_action_btn' ) );
310 - add_action( 'wp_ajax_tutor_complete_course', array( $this, 'ajax_tutor_complete_course' ) );
311 271 }
312 272
313 273 /**
314 - * Handle schedule courses preview for instructors.
315 - *
316 - * @since 3.5.0
317 - *
318 - * @param \WP_Post $content the preview post content.
319 - *
320 - * @return \WP_Post
321 - */
322 - public function handle_schedule_courses( $content ) {
323 - global $wp_query;
324 - $course_coming_soon_enabled = (int) get_post_meta( $content->ID, '_tutor_course_enable_coming_soon', true );
325 - $is_instructor = tutor_utils()->is_instructor_of_this_course( get_current_user_id(), $content->ID, true );
326 - if ( ! CourseModel::get_post_types( $content ) || current_user_can( 'administrator' ) || $is_instructor || $course_coming_soon_enabled ) {
327 - return $content;
328 - }
329 -
330 - $wp_query->set_404();
331 - status_header( 404 );
332 - nocache_headers();
333 - return $content;
334 - }
335 -
336 - /**
337 274 * Handle password protected course and bundle form submission.
338 275 *
339 276 * @since 3.2.1
340 277 *
@@ -350,31 +287,8 @@
350 287 }
351 288 }
352 289
353 290 /**
354 - * Bypass password protection for enrolled users.
355 - *
356 - * @since 4.0.0
357 - *
358 - * @param bool $required Whether the password is required.
359 - * @param WP_Post $post The post object.
360 - *
361 - * @return bool false if the current user is enrolled, original value otherwise.
362 - */
363 - public function bypass_password_for_enrolled( $required, $post ) {
364 - if ( ! $required ) {
365 - return $required;
366 - }
367 -
368 - $post_types = array( tutor()->course_post_type, tutor()->bundle_post_type );
369 - if ( in_array( $post->post_type, $post_types, true ) && EnrollmentModel::is_enrolled( $post->ID ) ) {
370 - return false;
371 - }
372 -
373 - return $required;
374 - }
375 -
376 - /**
377 291 * Handle password protected course/bundle.
378 292 *
379 293 * @since 3.0.0
380 294 *
@@ -473,14 +387,12 @@
473 387 * @return void
474 388 */
475 389 public function validate_video_source( $params, &$errors ) {
476 390 if ( isset( $params['video'] ) ) {
477 - $video_source_type = isset( $params['video']['source'] ) ? $params['video']['source'] : '';
478 - if ( tutor_is_rest() ) {
479 - $video_source_type = isset( $params['video']['source_type'] ) ? $params['video']['source_type'] : '';
480 - }
391 + $video_source = isset( $params['video']['source'] ) ? $params['video']['source'] : '';
392 + $video_source_type = isset( $params['video']['source_type'] ) ? $params['video']['source_type'] : '';
481 393
482 - if ( '' === $video_source_type ) {
394 + if ( '' === $video_source ) {
483 395 $errors['video_source'] = __( 'Video source is required', 'tutor' );
484 396 } elseif ( ! $this->is_valid_video_source_type( $video_source_type ) ) {
485 397 $errors['video_source'] = __( 'Invalid video source', 'tutor' );
486 398 }
@@ -548,34 +460,48 @@
548 460 }
549 461 }
550 462
551 463 /**
552 - * Setup course categories and tags.
464 + * Setup course categories and tags
553 465 *
554 466 * @since 3.0.0
555 467 *
556 - * @param int $post_id Post ID.
557 - * @param array $params Array of params.
468 + * @param int $post_id post id.
469 + * @param array $params array of params.
558 470 *
559 471 * @return void
560 472 */
561 473 public function setup_course_categories_tags( $post_id, $params ) {
562 474 if ( isset( $params['course_categories'] ) && is_array( $params['course_categories'] ) ) {
563 - $valid_category_ids = ValidationHelper::validate_term_ids(
564 - $params['course_categories'],
565 - CourseModel::COURSE_CATEGORY
566 - );
567 - wp_set_object_terms( $post_id, $valid_category_ids, CourseModel::COURSE_CATEGORY );
475 + $category_names = array();
476 +
477 + foreach ( $params['course_categories'] as $category_id ) {
478 + $term = get_term( $category_id, CourseModel::COURSE_CATEGORY );
479 +
480 + if ( ! is_wp_error( $term ) && $term ) {
481 + $category_names[] = $term->name;
482 + }
483 + }
484 +
485 + // Set category names on the post.
486 + wp_set_object_terms( $post_id, $category_names, CourseModel::COURSE_CATEGORY );
568 487 } else {
569 488 wp_set_object_terms( $post_id, array(), CourseModel::COURSE_CATEGORY );
570 489 }
571 490
572 491 if ( isset( $params['course_tags'] ) && is_array( $params['course_tags'] ) ) {
573 - $valid_tag_ids = ValidationHelper::validate_term_ids(
574 - $params['course_tags'],
575 - CourseModel::COURSE_TAG
576 - );
577 - wp_set_object_terms( $post_id, $valid_tag_ids, CourseModel::COURSE_TAG );
492 + $tag_names = array();
493 +
494 + foreach ( $params['course_tags'] as $tag_id ) {
495 + $term = get_term( $tag_id, CourseModel::COURSE_TAG );
496 +
497 + if ( ! is_wp_error( $term ) && $term ) {
498 + $tag_names[] = $term->name;
499 + }
500 + }
501 +
502 + // Set tag names on the post.
503 + wp_set_object_terms( $post_id, $tag_names, CourseModel::COURSE_TAG );
578 504 } else {
579 505 wp_set_object_terms( $post_id, array(), CourseModel::COURSE_TAG );
580 506 }
581 507 }
@@ -789,8 +715,11 @@
789 715 try {
790 716 if ( isset( $params['pricing']['type'] ) ) {
791 717 update_post_meta( $post_id, self::COURSE_PRICE_TYPE_META, $params['pricing']['type'] );
792 718 }
719 + if ( isset( $params['pricing']['product_id'] ) ) {
720 + update_post_meta( $post_id, '_tutor_course_product_id', $params['pricing']['product_id'] );
721 + }
793 722 } catch ( \Throwable $th ) {
794 723 throw new \Exception( $th->getMessage() );
795 724 }
796 725 }
@@ -798,21 +727,8 @@
798 727 update_post_meta( $post_id, '_tutor_enable_qa', $params['enable_qna'] ?? 'yes' );
799 728 update_post_meta( $post_id, '_tutor_is_public_course', $params['is_public_course'] ?? 'no' );
800 729 update_post_meta( $post_id, '_tutor_course_level', $params['course_level'] );
801 730
802 - /**
803 - * Save tax collection settings
804 - *
805 - * @since 3.7.0
806 - */
807 - if ( isset( $params['tax_on_single'] ) ) {
808 - update_post_meta( $post_id, self::TAX_ON_SINGLE_META, $params['tax_on_single'] );
809 - }
810 -
811 - if ( isset( $params['tax_on_subscription'] ) ) {
812 - update_post_meta( $post_id, self::TAX_ON_SUBSCRIPTION_META, $params['tax_on_subscription'] );
813 - }
814 -
815 731 do_action( 'tutor_after_prepare_update_post_meta', $post_id, $params );
816 732 }
817 733
818 734 /**
@@ -912,9 +828,9 @@
912 828
913 829 update_post_meta( $course_id, self::COURSE_PRICE_TYPE_META, self::PRICE_TYPE_FREE );
914 830
915 831 $link = admin_url( 'admin.php?page=create-course' );
916 - if ( tutor()->has_pro && Input::post( 'from_dashboard', false, Input::TYPE_BOOL ) ) {
832 + if ( Input::post( 'from_dashboard', false, Input::TYPE_BOOL ) ) {
917 833 $link = tutor_utils()->tutor_dashboard_url( 'create-course' );
918 834 }
919 835
920 836 $link = add_query_arg( array( 'course_id' => $course_id ), $link );
@@ -932,20 +848,20 @@
932 848 * Get course list
933 849 *
934 850 * @since 3.0.0
935 851 *
936 - * @since 3.2.0 Refactor the arguments & response as per new design.
852 + * @since 3.2.0
937 853 *
854 + * Refactor the arguments & response as per new design
855 + *
938 856 * @return void
939 857 */
940 858 public function ajax_course_list() {
941 - tutor_utils()->check_nonce();
942 859 $this->check_access();
943 860
944 861 $limit = Input::post( 'limit', 10, Input::TYPE_INT );
945 862 $offset = Input::post( 'offset', 0, Input::TYPE_INT );
946 863 $search_term = '';
947 - $post_status = Input::post( 'post_status', null );
948 864
949 865 $filter = json_decode( wp_unslash( $_POST['filter'] ) ); //phpcs:ignore --sanitized already
950 866 if ( ! empty( $filter ) && property_exists( $filter, 'search' ) ) {
951 867 $search_term = Input::sanitize( $filter->search );
@@ -951,9 +867,8 @@
951 867 $search_term = Input::sanitize( $filter->search );
952 868 }
953 869
954 870 $args = array(
955 - 'post_status' => is_null( $post_status ) ? 'publish' : $post_status,
956 871 'posts_per_page' => $limit,
957 872 'offset' => $offset,
958 873 's' => $search_term,
959 874 );
@@ -959,9 +874,14 @@
959 874 );
960 875
961 876 $exclude = Input::post( 'exclude', array(), Input::TYPE_ARRAY );
962 877 if ( count( $exclude ) ) {
963 - $exclude = array_filter( $exclude, fn( $id ) => is_numeric( $id ) && $id > 0 );
878 + $exclude = array_filter(
879 + $exclude,
880 + function ( $id ) {
881 + return is_numeric( $id );
882 + }
883 + );
964 884 $args['post__not_in'] = $exclude;
965 885 }
966 886
967 887 $courses = CourseModel::get_courses_by_args( $args );
@@ -1101,12 +1021,8 @@
1101 1021 )
1102 1022 );
1103 1023
1104 1024 $course_id = (int) $params['course_id'];
1105 - if ( ! $course_id ) {
1106 - $this->response_bad_request( __( 'Invalid course id', 'tutor' ) );
1107 - }
1108 -
1109 1025 $this->check_access( $course_id );
1110 1026
1111 1027 $errors = array();
1112 1028 $validation = $this->validate_inputs( $params );
@@ -1195,32 +1111,15 @@
1195 1111 public function ajax_unlink_page_builder() {
1196 1112 tutor_utils()->check_nonce();
1197 1113
1198 1114 $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
1199 - if ( ! $course_id ) {
1200 - $this->response_bad_request( __( 'Invalid course id', 'tutor' ) );
1201 - }
1202 -
1115 + $builder = Input::post( 'builder' );
1203 1116 $this->check_access( $course_id );
1204 1117
1205 - $builder = Input::post( 'builder' );
1206 1118 if ( 'elementor' === $builder ) {
1207 1119 delete_post_meta( $course_id, '_elementor_edit_mode' );
1208 1120 } elseif ( 'droip' === $builder ) {
1209 1121 delete_post_meta( $course_id, 'droip_editor_mode' );
1210 - } elseif ( 'divi' === $builder ) {
1211 - $old_post_content = get_post_meta( $course_id, '_et_pb_old_content', true );
1212 - $course = get_post( $course_id );
1213 - $course->post_content = $old_post_content;
1214 - $result = wp_update_post( $course );
1215 -
1216 - if ( $result && ! is_wp_error( $result ) ) {
1217 - update_post_meta( $course_id, '_et_pb_use_builder', 'off' );
1218 - update_post_meta( $course_id, '_et_pb_old_content', '' );
1219 - delete_post_meta( $course_id, '_et_dynamic_cached_shortcodes' );
1220 - delete_post_meta( $course_id, '_et_dynamic_cached_attributes' );
1221 - delete_post_meta( $course_id, '_et_builder_module_features_cache' );
1222 - }
1223 1122 }
1224 1123
1225 1124 $this->json_response(
1226 1125 __( 'Builder unlinked successfully.', 'tutor' ),
@@ -1276,23 +1175,20 @@
1276 1175 /**
1277 1176 * Get course contents
1278 1177 *
1279 1178 * @since 3.0.0
1280 - *
1281 - * @return void
1282 1179 */
1283 1180 public function ajax_course_contents() {
1284 1181 tutor_utils()->check_nonce();
1285 1182
1286 - $errors = array();
1287 1183 $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
1288 1184
1289 - if ( ! $course_id || tutor()->course_post_type !== get_post_type( $course_id ) ) {
1185 + $this->check_access( $course_id );
1186 +
1187 + if ( tutor()->course_post_type !== get_post_type( $course_id ) ) {
1290 1188 $errors['course_id'] = __( 'Invalid course id', 'tutor' );
1291 1189 }
1292 1190
1293 - $this->check_access( $course_id );
1294 -
1295 1191 if ( ! empty( $errors ) ) {
1296 1192 $this->json_response( __( 'Invalid input', 'tutor' ), $errors, HttpHelper::STATUS_UNPROCESSABLE_ENTITY );
1297 1193 }
1298 1194
@@ -1316,14 +1212,14 @@
1316 1212
1317 1213 $errors = array();
1318 1214 $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
1319 1215
1320 - if ( ! $course_id || tutor()->course_post_type !== get_post_type( $course_id ) ) {
1216 + $this->check_access( $course_id );
1217 +
1218 + if ( tutor()->course_post_type !== get_post_type( $course_id ) ) {
1321 1219 $errors['course_id'] = __( 'Invalid course id', 'tutor' );
1322 1220 }
1323 1221
1324 - $this->check_access( $course_id );
1325 -
1326 1222 if ( ! empty( $errors ) ) {
1327 1223 $this->json_response( __( 'Invalid input', 'tutor' ), $errors, HttpHelper::STATUS_UNPROCESSABLE_ENTITY );
1328 1224 }
1329 1225
@@ -1334,9 +1230,9 @@
1334 1230 $price = 0;
1335 1231 $sale_price = 0;
1336 1232 $product_id = tutor_utils()->get_course_product_id( $course_id );
1337 1233
1338 - if ( WooCommerce::MONETIZE_BY === $monetize_by ) {
1234 + if ( 'wc' === $monetize_by ) {
1339 1235 $product = wc_get_product( $product_id );
1340 1236 if ( $product ) {
1341 1237 $product_name = $product->get_name();
1342 1238 $price = $product->get_regular_price();
@@ -1343,9 +1239,9 @@
1343 1239 $sale_price = $product->get_sale_price();
1344 1240 }
1345 1241 }
1346 1242
1347 - if ( Ecommerce::MONETIZE_BY === $monetize_by ) {
1243 + if ( 'tutor' === $monetize_by ) {
1348 1244 $price = get_post_meta( $course_id, self::COURSE_PRICE_META, true );
1349 1245 $sale_price = get_post_meta( $course_id, self::COURSE_SALE_PRICE_META, true );
1350 1246 }
1351 1247
@@ -1404,16 +1300,8 @@
1404 1300 'certificate' => false,
1405 1301 ),
1406 1302 );
1407 1303
1408 - $tax_on_single = get_post_meta( $course_id, self::TAX_ON_SINGLE_META, true );
1409 - $tax_on_subscription = get_post_meta( $course_id, self::TAX_ON_SUBSCRIPTION_META, true );
1410 -
1411 - $data['tax_collection'] = array(
1412 - 'tax_on_single' => '' === $tax_on_single ? '1' : $tax_on_single,
1413 - 'tax_on_subscription' => '' === $tax_on_subscription ? '1' : $tax_on_subscription,
1414 - );
1415 -
1416 1304 $data = apply_filters( 'tutor_course_details_response', array_merge( $course, $data ) );
1417 1305
1418 1306 $this->json_response( __( 'Data retrieved successfully!', 'tutor' ), $data );
1419 1307 }
@@ -1478,10 +1366,9 @@
1478 1366 wp_enqueue_script( 'mce-view' );
1479 1367 wp_enqueue_editor();
1480 1368
1481 1369 wp_enqueue_media();
1482 - 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 );
1483 - wp_set_script_translations( 'tutor-course-builder', 'tutor', tutor()->path . 'languages/' );
1370 + 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 );
1484 1371
1485 1372 wp_localize_script(
1486 1373 'mce-view',
1487 1374 'mceViewL10n',
@@ -1527,9 +1414,8 @@
1527 1414 /**
1528 1415 * Localized only options to protect sensitive info like API keys.
1529 1416 */
1530 1417 $required_options = array(
1531 - 'learning_mode',
1532 1418 'monetize_by',
1533 1419 'enable_course_marketplace',
1534 1420 'course_permalink_base',
1535 1421 'supported_video_sources',
@@ -1549,12 +1435,8 @@
1549 1435 $settings['course_builder_logo_url'] = wp_get_attachment_image_url( $full_settings['tutor_frontend_course_page_logo_id'] ?? 0, 'full' );
1550 1436 $settings['chatgpt_key_exist'] = tutor()->has_pro && ! empty( $full_settings['chatgpt_api_key'] ?? '' );
1551 1437 $settings['youtube_api_key_exist'] = ! empty( $full_settings['lesson_video_duration_youtube_api_key'] ?? '' );
1552 1438
1553 - $settings['enable_tax'] = Tax::get_setting( 'enable_tax', true );
1554 - $settings['is_tax_included_in_price'] = Tax::is_tax_included_in_price();
1555 - $settings['enable_individual_tax_control'] = Tax::get_setting( 'enable_individual_tax_control' );
1556 -
1557 1439 $new_data = array( 'settings' => $settings );
1558 1440
1559 1441 $data = array_merge( $default_data, $new_data );
1560 1442
@@ -1600,15 +1482,10 @@
1600 1482 $data['timezones'] = tutor_global_timezone_lists();
1601 1483 $data['difficulty_levels'] = $difficulty_levels;
1602 1484 $data['supported_video_sources'] = $supported_video_sources;
1603 1485 $data['wp_rest_nonce'] = wp_create_nonce( 'wp_rest' );
1486 + $data['max_upload_size'] = size_format( wp_max_upload_size() );
1604 1487
1605 - if ( 'en_US' !== $data['local'] ) {
1606 - $data['course_builder_basic_locales'] = tutils()->get_script_locale_data( 'tutor-course-builder-basic', $data['local'] );
1607 - $data['course_builder_curriculum_locales'] = tutils()->get_script_locale_data( 'tutor-course-builder-curriculum', $data['local'] );
1608 - $data['course_builder_additional_locales'] = tutils()->get_script_locale_data( 'tutor-course-builder-additional', $data['local'] );
1609 - }
1610 -
1611 1488 $data = apply_filters( 'tutor_course_builder_localized_data', $data );
1612 1489
1613 1490 return $data;
1614 1491 }
@@ -1731,38 +1608,23 @@
1731 1608 /**
1732 1609 * Update course content order
1733 1610 *
1734 1611 * @since 1.0.0
1735 - * @since 3.9.9 Check if user can manage course before updating order.
1736 - *
1737 1612 * @return void
1738 1613 */
1739 - public function ajax_update_course_content_order() {
1614 + public function tutor_update_course_content_order() {
1740 1615 tutor_utils()->checking_nonce();
1741 1616
1742 - $sorting_order = Input::post( 'tutor_topics_lessons_sorting', '' );
1743 - $sorting_order = json_decode( $sorting_order, true ) ?? array();
1744 -
1745 - if ( ! tutor_utils()->count( $sorting_order ) ) {
1746 - wp_send_json_error( __( 'Sorting order is required', 'tutor' ) );
1747 - }
1748 -
1749 - $topic_id = (int) isset( $sorting_order[0], $sorting_order[0]['topic_id'] ) ? $sorting_order[0]['topic_id'] : 0;
1750 - $course_id = wp_get_post_parent_id( $topic_id );
1751 -
1752 - if ( ! $topic_id || ! $course_id ) {
1753 - wp_send_json_error( tutor_utils()->error_message( 'invalid_req' ) );
1754 - }
1755 -
1756 - if ( ! tutor_utils()->can_user_manage( 'course', $course_id ) || ! User::is_admin() ) {
1757 - wp_send_json_error( tutor_utils()->error_message() );
1758 - }
1759 -
1760 1617 if ( Input::has( 'content_parent' ) ) {
1761 1618 $content_parent = Input::post( 'content_parent', array(), Input::TYPE_ARRAY );
1762 1619 $topic_id = tutor_utils()->array_get( 'parent_topic_id', $content_parent );
1763 1620 $content_id = tutor_utils()->array_get( 'content_id', $content_parent );
1764 1621
1622 + if ( ! tutor_utils()->can_user_manage( 'topic', $topic_id ) ) {
1623 + wp_send_json_success( array( 'message' => __( 'Access Denied!', 'tutor' ) ) );
1624 + exit;
1625 + }
1626 +
1765 1627 // Update the parent topic id of the content.
1766 1628 global $wpdb;
1767 1629 $wpdb->update( $wpdb->posts, array( 'post_parent' => $topic_id ), array( 'ID' => $content_id ) );
1768 1630 }
@@ -1767,9 +1629,9 @@
1767 1629 $wpdb->update( $wpdb->posts, array( 'post_parent' => $topic_id ), array( 'ID' => $content_id ) );
1768 1630 }
1769 1631
1770 1632 // Save course content order.
1771 - $this->save_course_content_order( $sorting_order );
1633 + $this->save_course_content_order();
1772 1634
1773 1635 wp_send_json_success();
1774 1636 }
1775 1637
@@ -1796,11 +1658,9 @@
1796 1658 /**
1797 1659 * Restrict media
1798 1660 *
1799 1661 * @since 1.0.0
1800 - *
1801 1662 * @param string $where where clause.
1802 - *
1803 1663 * @return string
1804 1664 */
1805 1665 public function restrict_media( $where ) {
1806 1666 $action = Input::post( 'action' );
@@ -1816,58 +1676,58 @@
1816 1676 /**
1817 1677 * Save course content order
1818 1678 *
1819 1679 * @since 1.0.0
1820 - * @since 3.9.8 param $order added.
1821 - *
1822 - * @param array $sort_order the lesson and topic order array.
1823 - *
1824 1680 * @return void
1825 1681 */
1826 - private function save_course_content_order( $sort_order = array() ) {
1682 + private function save_course_content_order() {
1827 1683 global $wpdb;
1828 1684
1829 - if ( ! tutor_utils()->count( $sort_order ) ) {
1830 - return;
1831 - }
1685 + $new_order = Input::post( 'tutor_topics_lessons_sorting' );
1686 + if ( ! empty( $new_order ) ) {
1687 + $order = json_decode( $new_order, true );
1832 1688
1833 - $i = 0;
1834 - foreach ( $sort_order as $topic ) {
1835 - ++$i;
1836 - $wpdb->update(
1837 - $wpdb->posts,
1838 - array( 'menu_order' => $i ),
1839 - array( 'ID' => $topic['topic_id'] )
1840 - );
1689 + if ( is_array( $order ) && count( $order ) ) {
1690 + $i = 0;
1691 + foreach ( $order as $topic ) {
1692 + $i++;
1693 + $wpdb->update(
1694 + $wpdb->posts,
1695 + array( 'menu_order' => $i ),
1696 + array( 'ID' => $topic['topic_id'] )
1697 + );
1841 1698
1842 - /**
1843 - * Removing All lesson with topic
1844 - */
1845 - $wpdb->update(
1846 - $wpdb->posts,
1847 - array( 'post_parent' => 0 ),
1848 - array( 'post_parent' => $topic['topic_id'] )
1849 - );
1699 + /**
1700 + * Removing All lesson with topic
1701 + */
1850 1702
1851 - /**
1852 - * Lesson Attaching with topic ID
1853 - * Sorting lesson
1854 - */
1855 - if ( isset( $topic['lesson_ids'] ) ) {
1856 - $lesson_ids = $topic['lesson_ids'];
1857 - } else {
1858 - $lesson_ids = array();
1859 - }
1860 - if ( count( $lesson_ids ) ) {
1861 - foreach ( $lesson_ids as $lesson_key => $lesson_id ) {
1862 1703 $wpdb->update(
1863 1704 $wpdb->posts,
1864 - array(
1865 - 'post_parent' => $topic['topic_id'],
1866 - 'menu_order' => $lesson_key,
1867 - ),
1868 - array( 'ID' => $lesson_id )
1705 + array( 'post_parent' => 0 ),
1706 + array( 'post_parent' => $topic['topic_id'] )
1869 1707 );
1708 +
1709 + /**
1710 + * Lesson Attaching with topic ID
1711 + * Sorting lesson
1712 + */
1713 + if ( isset( $topic['lesson_ids'] ) ) {
1714 + $lesson_ids = $topic['lesson_ids'];
1715 + } else {
1716 + $lesson_ids = array();
1717 + }
1718 + if ( count( $lesson_ids ) ) {
1719 + foreach ( $lesson_ids as $lesson_key => $lesson_id ) {
1720 + $wpdb->update(
1721 + $wpdb->posts,
1722 + array(
1723 + 'post_parent' => $topic['topic_id'],
1724 + 'menu_order' => $lesson_key,
1725 + ),
1726 + array( 'ID' => $lesson_id )
1727 + );
1728 + }
1729 + }
1870 1730 }
1871 1731 }
1872 1732 }
1873 1733 }
@@ -1938,14 +1798,12 @@
1938 1798 }
1939 1799 //phpcs:enable WordPress.Security.NonceVerification.Missing
1940 1800 }
1941 1801
1942 - $sorting_order = Input::post( 'tutor_topics_lessons_sorting', '' );
1943 - $sorting_order = json_decode( $sorting_order, true ) ?? array();
1944 1802 /**
1945 1803 * Sorting Topics and lesson
1946 1804 */
1947 - $this->save_course_content_order( $sorting_order );
1805 + $this->save_course_content_order();
1948 1806
1949 1807 // Additional data like course intro video.
1950 1808 if ( $additional_data_edit ) {
1951 1809 // Sanitize data through helper method.
@@ -2140,14 +1998,8 @@
2140 1998 */
2141 1999
2142 2000 $is_purchasable = tutor_utils()->is_course_purchasable( $course_id );
2143 2001
2144 - $course = get_post( $course_id );
2145 -
2146 - if ( 'private' === $course->post_status && ! current_user_can( 'read_private_tutor_courses' ) ) {
2147 - wp_send_json_error( __( 'You do not have permission to enroll in this course', 'tutor' ) );
2148 - }
2149 -
2150 2002 /**
2151 2003 * If is is not purchasable, it's free, and enroll right now
2152 2004 * If purchasable, then process purchase.
2153 2005 *
@@ -2157,9 +2009,9 @@
2157 2009 // Process purchase.
2158 2010
2159 2011 } else {
2160 2012 // Free enroll.
2161 - EnrollmentModel::do_enroll( $course_id );
2013 + tutor_utils()->do_enroll( $course_id );
2162 2014 }
2163 2015
2164 2016 $referer_url = wp_get_referer();
2165 2017 wp_safe_redirect( tutor_utils()->get_nocache_url( $referer_url ) );
@@ -2169,11 +2021,8 @@
2169 2021 /**
2170 2022 * Mark complete completed
2171 2023 *
2172 2024 * @since 1.0.0
2173 - *
2174 - * @since 3.7.1 Filter hook: tutor_user_can_complete_course added
2175 - *
2176 2025 * @return void
2177 2026 */
2178 2027 public function mark_course_complete() {
2179 2028 $tutor_action = Input::post( 'tutor_action' );
@@ -2181,10 +2030,8 @@
2181 2030 if ( 'tutor_complete_course' !== $tutor_action || ! $course_id ) {
2182 2031 return;
2183 2032 }
2184 2033
2185 - $permalink = get_the_permalink( $course_id );
2186 -
2187 2034 // Checking nonce.
2188 2035 tutor_utils()->checking_nonce();
2189 2036
2190 2037 $user_id = get_current_user_id();
@@ -2193,78 +2040,20 @@
2193 2040 if ( ! $user_id ) {
2194 2041 die( esc_html__( 'Please Sign-In', 'tutor' ) );
2195 2042 }
2196 2043
2197 - if ( ! EnrollmentModel::is_enrolled( $course_id, $user_id ) ) {
2198 - die( esc_html__( 'User is not enrolled in course', 'tutor' ) );
2199 - }
2044 + CourseModel::mark_course_as_completed( $course_id, $user_id );
2200 2045
2201 - /**
2202 - * Filter hook provided to restrict course completion. This is useful
2203 - * for specific cases like prerequisites. WP_Error should be returned
2204 - * from the filter value to prevent the completion.
2205 - */
2206 - $can_complete = apply_filters( 'tutor_user_can_complete_course', CourseModel::can_complete_course( $course_id, $user_id ), $user_id, $course_id );
2046 + $permalink = get_the_permalink( $course_id );
2207 2047
2208 - if ( is_wp_error( $can_complete ) ) {
2209 - tutor_utils()->redirect_to( $permalink, $can_complete->get_error_message(), 'error' );
2210 - } elseif ( ! $can_complete ) {
2211 - tutor_utils()->redirect_to( $permalink, __( 'You do not have permission to complete this course.', 'tutor' ), 'error' );
2212 - } else {
2213 - CourseModel::mark_course_as_completed( $course_id, $user_id );
2214 - // Set temporary identifier to show review pop up.
2215 - self::set_review_popup_data( $user_id, $course_id, $permalink );
2048 + // Set temporary identifier to show review pop up.
2049 + self::set_review_popup_data( $user_id, $course_id, $permalink );
2216 2050
2217 - wp_safe_redirect( $permalink );
2218 - exit;
2219 - }
2051 + wp_safe_redirect( $permalink );
2052 + exit;
2220 2053 }
2221 2054
2222 2055 /**
2223 - * Ajax course complete handler
2224 - *
2225 - * @since 4.0.0
2226 - *
2227 - * @return void
2228 - */
2229 - public function ajax_tutor_complete_course() {
2230 - $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
2231 -
2232 - // Checking nonce.
2233 - if ( ! tutor_utils()->is_nonce_verified() ) {
2234 - $this->response_bad_request( tutor_utils()->error_message( 'nonce' ) );
2235 - }
2236 -
2237 - if ( ! $course_id ) {
2238 - $this->response_bad_request( __( 'Invalid course', 'tutor' ) );
2239 - }
2240 -
2241 - $user_id = get_current_user_id();
2242 - if ( ! EnrollmentModel::is_enrolled( $course_id, $user_id ) ) {
2243 - $this->response_bad_request( __( 'You are not enrolled in this course', 'tutor' ) );
2244 - }
2245 -
2246 - /**
2247 - * Filter hook provided to restrict course completion. This is useful
2248 - * for specific cases like prerequisites. WP_Error should be returned
2249 - * from the filter value to prevent the completion.
2250 - */
2251 - $can_complete = apply_filters( 'tutor_user_can_complete_course', CourseModel::can_complete_course( $course_id, $user_id ), $user_id, $course_id );
2252 -
2253 - if ( is_wp_error( $can_complete ) ) {
2254 - $this->response_bad_request( $can_complete->get_error_message() );
2255 - } elseif ( ! $can_complete ) {
2256 - $this->response_bad_request( __( 'You do not have permission to complete this course.', 'tutor' ) );
2257 - } else {
2258 - CourseModel::mark_course_as_completed( $course_id, $user_id );
2259 - // Set temporary identifier to show review pop up.
2260 - self::set_review_popup_data( $user_id, $course_id );
2261 -
2262 - $this->response_success( __( 'Course completed successfully', 'tutor' ) );
2263 - }
2264 - }
2265 -
2266 - /**
2267 2056 * Set data for review popup.
2268 2057 *
2269 2058 * @since 2.2.5
2270 2059 * @since 2.4.0 removed $permalink param. store user meta instead of option data.
@@ -2290,34 +2079,18 @@
2290 2079 * @since 1.0.0
2291 2080 * @return void
2292 2081 */
2293 2082 public function popup_review_form() {
2294 - if ( ! is_user_logged_in() ) {
2295 - return;
2296 - }
2083 + if ( is_user_logged_in() ) {
2084 + $user_id = get_current_user_id();
2085 + $course_id = get_the_ID();
2086 + $meta_key = User::get_review_popup_meta( $course_id );
2087 + $review_course_id = (int) get_user_meta( $user_id, $meta_key, true );
2297 2088
2298 - $is_learning_area = tutor_utils()->is_learning_area();
2299 - $is_legacy_learning = tutor_utils()->is_legacy_learning_mode();
2300 - if ( $is_legacy_learning && $is_learning_area ) {
2301 - return;
2089 + if ( is_single() && $course_id === $review_course_id ) {
2090 + include tutor()->path . 'views/modal/review.php';
2091 + }
2302 2092 }
2303 -
2304 - $course_id = get_the_ID();
2305 -
2306 - if ( $is_learning_area && ! Input::has( 'subpage' ) ) {
2307 - $course_id = wp_get_post_parent_id( wp_get_post_parent_id( get_the_ID() ) );
2308 - }
2309 -
2310 - if ( empty( $course_id ) ) {
2311 - return;
2312 - }
2313 -
2314 - $user_id = get_current_user_id();
2315 - $meta_key = User::get_review_popup_meta( $course_id );
2316 - $review_course_id = (int) get_user_meta( $user_id, $meta_key, true );
2317 - if ( is_single() && $course_id === $review_course_id ) {
2318 - include tutor()->path . 'views/modal/review.php';
2319 - }
2320 2093 }
2321 2094
2322 2095 /**
2323 2096 * Review popup data clear
@@ -2345,9 +2118,8 @@
2345 2118 /**
2346 2119 * Delete course delete from frontend dashboard
2347 2120 *
2348 2121 * @since 2.0.0
2349 - *
2350 2122 * @return void
2351 2123 */
2352 2124 public function tutor_delete_dashboard_course() {
2353 2125 tutor_utils()->checking_nonce();
@@ -2366,9 +2138,9 @@
2366 2138 wp_send_json_error( array( 'message' => __( 'Only main instructor can delete this course', 'tutor' ) ) );
2367 2139 }
2368 2140
2369 2141 // Check if user is only an instructor.
2370 - if ( ! User::is_admin() ) {
2142 + if ( ! current_user_can( 'administrator' ) ) {
2371 2143 // Check if instructor can trash course.
2372 2144 $can_trash_post = tutor_utils()->get_option( 'instructor_can_delete_course' );
2373 2145
2374 2146 if ( ! $can_trash_post ) {
@@ -2378,9 +2150,9 @@
2378 2150
2379 2151 $trash_course = wp_update_post(
2380 2152 array(
2381 2153 'ID' => $course_id,
2382 - 'post_status' => CourseModel::STATUS_TRASH,
2154 + 'post_status' => 'trash',
2383 2155 )
2384 2156 );
2385 2157
2386 2158 if ( $trash_course ) {
@@ -2439,24 +2211,21 @@
2439 2211 *
2440 2212 * @return void
2441 2213 */
2442 2214 public function attach_product_with_course( $post_ID, $post_data ) {
2215 +
2443 2216 $monetize_by = tutor_utils()->get_option( 'monetize_by' );
2444 2217 $product_id = Input::post( '_tutor_course_product_id', 0, Input::TYPE_INT );
2445 2218
2446 - /**
2447 - * For native monetization, just return
2448 - * No need to attach anything.
2449 - */
2450 2219 if ( Ecommerce::MONETIZE_BY === $monetize_by ) {
2451 2220 return;
2452 2221 }
2453 2222
2454 2223 /**
2455 - * When course moved paid to free
2456 - * Keep the product linked and return.
2224 + * Unlink product from course.
2457 2225 */
2458 2226 if ( -1 === $product_id ) {
2227 + delete_post_meta( $post_ID, self::COURSE_PRODUCT_ID_META );
2459 2228 return;
2460 2229 }
2461 2230
2462 2231 /**
@@ -2483,31 +2252,36 @@
2483 2252 update_post_meta( $post_ID, self::COURSE_PRICE_TYPE_META, self::PRICE_TYPE_PAID );
2484 2253
2485 2254 if ( 'wc' === $monetize_by ) {
2486 2255
2487 - $is_update = ( $product_id && wc_get_product( $product_id ) ) ? true : false;
2256 + $is_update = ( $attached_product_id && wc_get_product( $attached_product_id ) ) ? true : false;
2488 2257
2489 2258 if ( $is_update ) {
2259 + $attached_product_id = $product_id;
2490 2260 update_post_meta( $post_ID, self::COURSE_PRODUCT_ID_META, $product_id );
2491 2261
2492 - $product_id = self::create_wc_product( $course->post_title, $course_price, $sale_price, $product_id );
2262 + $product_id = self::create_wc_product( $course->post_title, $course_price, $sale_price, $attached_product_id );
2493 2263 $product_obj = wc_get_product( $product_id );
2494 2264 if ( $product_obj->is_type( 'subscription' ) ) {
2495 - update_post_meta( $product_id, '_subscription_price', $course_price );
2265 + update_post_meta( $attached_product_id, '_subscription_price', $course_price );
2496 2266 }
2497 2267
2498 2268 // Set course regular & sale price.
2499 - self::set_course_regular_and_sale_price( $post_ID, $product_obj->get_regular_price(), $product_obj->get_sale_price() );
2269 + update_post_meta( $post_ID, self::COURSE_PRICE_META, $product_obj->get_regular_price() );
2270 + update_post_meta( $post_ID, self::COURSE_SALE_PRICE_META, $product_obj->get_sale_price() );
2500 2271 } else {
2501 2272 // Create new WC product name with course title.
2502 2273 $product_id = self::create_wc_product( $course->post_title, $course_price, $sale_price );
2503 2274 if ( $product_id ) {
2504 2275 $product_obj = wc_get_product( $product_id );
2276 + update_post_meta( $post_ID, self::COURSE_PRODUCT_ID_META, $product_id );
2277 + // Mark product for woocommerce.
2278 + update_post_meta( $product_id, '_virtual', 'yes' );
2279 + update_post_meta( $product_id, '_tutor_product', 'yes' );
2505 2280
2506 - self::sync_course_with_wc_product( $post_ID, $product_id );
2507 -
2508 2281 // Set course regular & sale price.
2509 - self::set_course_regular_and_sale_price( $post_ID, $product_obj->get_regular_price(), $product_obj->get_sale_price() );
2282 + update_post_meta( $post_ID, self::COURSE_PRICE_META, $product_obj->get_regular_price() );
2283 + update_post_meta( $post_ID, self::COURSE_SALE_PRICE_META, $product_obj->get_sale_price() );
2510 2284 }
2511 2285 }
2512 2286
2513 2287 $course_post_thumbnail = Input::post( 'thumbnail_id', 0, Input::TYPE_INT );
@@ -2555,8 +2329,33 @@
2555 2329 }
2556 2330 }
2557 2331
2558 2332 /**
2333 + * Add Course level to course settings
2334 + *
2335 + * @since 1.4.1
2336 + *
2337 + * @param array $args arguments.
2338 + * @return array
2339 + */
2340 + public function add_course_level_to_settings( $args ) {
2341 + $course_id = get_the_ID();
2342 + $levels = tutor_utils()->course_levels();
2343 + $course_level = get_post_meta( $course_id, '_tutor_course_level', true );
2344 +
2345 + $args['general']['fields']['_tutor_course_level'] = array(
2346 + 'type' => 'select',
2347 + 'label' => __( 'Difficulty Level', 'tutor' ),
2348 + 'label_title' => __( 'Enable', 'tutor' ),
2349 + 'options' => $levels,
2350 + 'value' => $course_level ? $course_level : 'intermediate',
2351 + 'desc' => __( 'Course difficulty level', 'tutor' ),
2352 + );
2353 +
2354 + return $args;
2355 + }
2356 +
2357 + /**
2559 2358 * Check if course starting
2560 2359 *
2561 2360 * @since 1.4.8
2562 2361 * @return void
@@ -2576,9 +2375,8 @@
2576 2375 /**
2577 2376 * Add Course level to course settings
2578 2377 *
2579 2378 * @since 1.4.8
2580 - *
2581 2379 * @return void
2582 2380 */
2583 2381 public function course_elements_enable_disable() {
2584 2382 add_filter( 'tutor_course/single/completing-progress-bar', array( $this, 'enable_disable_course_progress_bar' ) );
@@ -2611,9 +2409,8 @@
2611 2409 *
2612 2410 * @since 1.4.8
2613 2411 *
2614 2412 * @param string $html HTML string.
2615 - *
2616 2413 * @return string
2617 2414 */
2618 2415 public function enable_disable_material_includes( $html ) {
2619 2416 $disable_option = ! (bool) get_tutor_option( 'enable_course_material', true, true );
@@ -2628,9 +2425,8 @@
2628 2425 *
2629 2426 * @since 1.4.8
2630 2427 *
2631 2428 * @param string $html HTML string.
2632 - *
2633 2429 * @return string
2634 2430 */
2635 2431 public function enable_disable_course_content( $html ) {
2636 2432 $disable_option = ! (bool) tutor_utils()->get_option( 'enable_course_description', true, true );
@@ -2645,9 +2441,8 @@
2645 2441 *
2646 2442 * @since 1.4.8
2647 2443 *
2648 2444 * @param string $html HTML string.
2649 - *
2650 2445 * @return string
2651 2446 */
2652 2447 public function enable_disable_course_benefits( $html ) {
2653 2448 $disable_option = ! (bool) tutor_utils()->get_option( 'enable_course_benefits', true, true );
@@ -2662,9 +2457,8 @@
2662 2457 *
2663 2458 * @since 1.4.8
2664 2459 *
2665 2460 * @param string $html HTML string.
2666 - *
2667 2461 * @return string
2668 2462 */
2669 2463 public function enable_disable_course_requirements( $html ) {
2670 2464 $disable_option = ! (bool) tutor_utils()->get_option( 'enable_course_requirements', true, true );
@@ -2679,9 +2473,8 @@
2679 2473 *
2680 2474 * @since 1.4.8
2681 2475 *
2682 2476 * @param string $html HTML string.
2683 - *
2684 2477 * @return string
2685 2478 */
2686 2479 public function enable_disable_course_target_audience( $html ) {
2687 2480 $disable_option = ! (bool) tutor_utils()->get_option( 'enable_course_target_audience', true, true );
@@ -2726,9 +2519,9 @@
2726 2519 unset( $items['reviews'] );
2727 2520 }
2728 2521
2729 2522 // Whether enrollment require.
2730 - $is_enrolled = EnrollmentModel::is_enrolled();
2523 + $is_enrolled = tutor_utils()->is_enrolled();
2731 2524
2732 2525 return array_filter(
2733 2526 $items,
2734 2527 function ( $item ) use ( $is_enrolled ) {
@@ -2743,9 +2536,8 @@
2743 2536 /**
2744 2537 * Filter product in shop page
2745 2538 *
2746 2539 * @since 1.4.9
2747 - *
2748 2540 * @return void|null
2749 2541 */
2750 2542 public function filter_product_in_shop_page() {
2751 2543 $hide_course_from_shop_page = (bool) get_tutor_option( 'hide_course_from_shop_page' );
@@ -2761,9 +2553,8 @@
2761 2553 /**
2762 2554 * Tutor product meta query
2763 2555 *
2764 2556 * @since 1.4.9
2765 - *
2766 2557 * @return array
2767 2558 */
2768 2559 public function tutor_product_meta_query() {
2769 2560 $meta_query = array(
@@ -2778,9 +2569,8 @@
2778 2569 *
2779 2570 * @since 1.4.9
2780 2571 *
2781 2572 * @param \WP_Query $wp_query WP Query instance.
2782 - *
2783 2573 * @return \WP_Query
2784 2574 */
2785 2575 public function filter_woocommerce_product_query( $wp_query ) {
2786 2576 $product_ids = $this->get_connected_wc_product_ids();
@@ -2822,9 +2612,8 @@
2822 2612 *
2823 2613 * @since 1.4.9
2824 2614 *
2825 2615 * @param \WP_Query $query WP Query instance.
2826 - *
2827 2616 * @return \WP_Query
2828 2617 */
2829 2618 public function filter_edd_downloads_query( $query ) {
2830 2619 $query['meta_query'][] = $this->tutor_product_meta_query();
@@ -2836,9 +2625,8 @@
2836 2625 *
2837 2626 * @since 1.4.9
2838 2627 *
2839 2628 * @param \WP_Query $wp_query WP Query instance.
2840 - *
2841 2629 * @return \WP_Query
2842 2630 */
2843 2631 public function filter_archive_meta_query( $wp_query ) {
2844 2632 if ( ! is_admin() && $wp_query->is_archive && $wp_query->get( 'post_type' ) === 'download' ) {
@@ -2852,9 +2640,8 @@
2852 2640 *
2853 2641 * @since 1.5.8
2854 2642 *
2855 2643 * @param string $html HTML string.
2856 - *
2857 2644 * @return string
2858 2645 */
2859 2646 public function remove_price_if_enrolled( $html ) {
2860 2647 $should_removed = apply_filters( 'should_remove_price_if_enrolled', true );
@@ -2860,9 +2647,9 @@
2860 2647 $should_removed = apply_filters( 'should_remove_price_if_enrolled', true );
2861 2648
2862 2649 if ( $should_removed ) {
2863 2650 $course_id = get_the_ID();
2864 - $enrolled = EnrollmentModel::is_enrolled( $course_id );
2651 + $enrolled = tutor_utils()->is_enrolled( $course_id );
2865 2652 if ( $enrolled ) {
2866 2653 $html = '';
2867 2654 }
2868 2655 }
@@ -2869,119 +2656,112 @@
2869 2656 return $html;
2870 2657 }
2871 2658
2872 2659 /**
2873 - * Get course completion missing requirements message.
2660 + * Check if all lessons and quizzes done before mark course complete.
2874 2661 *
2875 - * @since 4.0.0
2662 + * @since 1.5.8
2876 2663 *
2877 - * @param int $course_id Course ID.
2878 - * @param int $user_id User ID.
2879 - *
2880 - * @return string|null
2664 + * @param string $html HTML string.
2665 + * @return string
2881 2666 */
2882 - public static function get_course_completion_restrict_msg( $course_id = 0, $user_id = 0 ) {
2883 - $course_id = tutor_utils()->get_post_id( $course_id );
2884 - $user_id = tutor_utils()->get_user_id( $user_id );
2667 + public function tutor_lms_hide_course_complete_btn( $html ) {
2885 2668
2886 - if ( 'strict' !== tutor_utils()->get_option( 'course_completion_process' ) ) {
2887 - return null;
2669 + $completion_mode = tutor_utils()->get_option( 'course_completion_process' );
2670 + if ( 'strict' !== $completion_mode ) {
2671 + return $html;
2888 2672 }
2889 2673
2890 - $completed_lessons = tutor_utils()->get_completed_lesson_count_by_course( $course_id, $user_id );
2891 - $total_lessons = tutor_utils()->get_lesson_count_by_course( $course_id );
2674 + $completed_lesson = tutor_utils()->get_completed_lesson_count_by_course();
2675 + $lesson_count = tutor_utils()->get_lesson_count_by_course();
2892 2676
2893 - if ( $completed_lessons < $total_lessons ) {
2894 - return __( 'Complete all lessons to mark this course as complete', 'tutor' );
2677 + if ( $completed_lesson < $lesson_count ) {
2678 + return '<div class="tutor-alert tutor-warning tutor-mt-28">
2679 + <div class="tutor-alert-text">
2680 + <span class="tutor-alert-icon tutor-fs-4 tutor-icon-circle-info tutor-mr-12"></span>
2681 + <span>' . __( 'Complete all lessons to mark this course as complete', 'tutor' ) . '</span>
2682 + </div>
2683 + </div>';
2895 2684 }
2896 2685
2897 - $course_contents = tutor_utils()->get_course_contents_by_id( $course_id );
2686 + $quizzes = array();
2687 + $assignments = array();
2898 2688
2899 - $required_quiz_pass_count = 0;
2900 - $required_assignment_pass_count = 0;
2689 + $course_contents = tutor_utils()->get_course_contents_by_id();
2690 + if ( tutor_utils()->count( $course_contents ) ) {
2691 + foreach ( $course_contents as $content ) {
2692 + if ( 'tutor_quiz' === $content->post_type ) {
2693 + $quizzes[] = $content;
2694 + }
2695 + if ( 'tutor_assignments' === $content->post_type ) {
2696 + $assignments[] = $content;
2697 + }
2698 + }
2699 + }
2901 2700
2902 - $is_assignment_addon_enabled = tutor_utils()->is_addon_enabled( 'tutor-assignments' );
2701 + $required_assignment_pass = 0;
2903 2702
2904 - foreach ( $course_contents as $content ) {
2703 + foreach ( $assignments as $row ) {
2905 2704
2906 - if ( tutor()->quiz_post_type === $content->post_type ) {
2907 - if ( ! QuizModel::is_quiz_passed( $content->ID, $user_id ) ) {
2908 - ++$required_quiz_pass_count;
2909 - }
2910 - }
2705 + $submitted_assignment = tutor_utils()->is_assignment_submitted( $row->ID );
2706 + $is_reviewed_by_instructor = null === $submitted_assignment
2707 + ? false
2708 + : get_comment_meta( $submitted_assignment->comment_ID, 'evaluate_time', true );
2911 2709
2912 - if ( $is_assignment_addon_enabled && tutor()->assignment_post_type === $content->post_type ) {
2913 - if ( ! Assignments::is_assignment_passed( $content->ID, $user_id ) ) {
2914 - ++$required_assignment_pass_count;
2710 + if ( $submitted_assignment && $is_reviewed_by_instructor ) {
2711 + $pass_mark = tutor_utils()->get_assignment_option( $submitted_assignment->comment_post_ID, 'pass_mark' );
2712 + $given_mark = get_comment_meta( $submitted_assignment->comment_ID, 'assignment_mark', true );
2713 +
2714 + if ( $given_mark < $pass_mark ) {
2715 + $required_assignment_pass++;
2915 2716 }
2717 + } else {
2718 + $required_assignment_pass++;
2916 2719 }
2917 2720 }
2918 2721
2919 - if ( ! $required_quiz_pass_count && ! $required_assignment_pass_count ) {
2920 - return null;
2921 - }
2722 + $is_quiz_pass = true;
2723 + $required_quiz_pass = 0;
2922 2724
2923 - return self::get_course_completion_requirement_message(
2924 - $required_quiz_pass_count,
2925 - $required_assignment_pass_count
2926 - );
2927 - }
2725 + if ( tutor_utils()->count( $quizzes ) ) {
2726 + foreach ( $quizzes as $quiz ) {
2928 2727
2929 - /**
2930 - * Build missing course completion requirements message.
2931 - *
2932 - * @since 4.0.0
2933 - *
2934 - * @param int $quiz_count Total quiz count.
2935 - * @param int $assignment_count Total assignment count.
2936 - *
2937 - * @return string
2938 - */
2939 - private static function get_course_completion_requirement_message( $quiz_count, $assignment_count ) {
2940 - $quiz_label = _n( 'quiz', 'quizzes', $quiz_count, 'tutor' );
2941 - $assignment_label = _n( 'assignment', 'assignments', $assignment_count, 'tutor' );
2728 + $attempt = tutor_utils()->get_quiz_attempt( $quiz->ID );
2729 + if ( $attempt ) {
2730 + $passing_grade = tutor_utils()->get_quiz_option( $quiz->ID, 'passing_grade', 0 );
2731 + $earned_percentage = $attempt->earned_marks > 0 ? ( number_format( ( $attempt->earned_marks * 100 ) / $attempt->total_marks ) ) : 0;
2942 2732
2943 - if ( $quiz_count && ! $assignment_count ) {
2944 - return sprintf(
2945 - /* translators: %1$s: item count; %2$s: item label */
2946 - __( 'You have to pass %1$s %2$s to complete this course.', 'tutor' ),
2947 - $quiz_count,
2948 - $quiz_label
2949 - );
2733 + if ( $earned_percentage < $passing_grade ) {
2734 + $required_quiz_pass++;
2735 + $is_quiz_pass = false;
2736 + }
2737 + } else {
2738 + $required_quiz_pass++;
2739 + $is_quiz_pass = false;
2740 + }
2741 + }
2950 2742 }
2951 2743
2952 - if ( ! $quiz_count && $assignment_count ) {
2953 - return sprintf(
2954 - /* translators: %1$s: item count; %2$s: item label */
2955 - __( 'You have to pass %1$s %2$s to complete this course.', 'tutor' ),
2956 - $assignment_count,
2957 - $assignment_label
2958 - );
2959 - }
2744 + if ( ! $is_quiz_pass || $required_assignment_pass > 0 ) {
2745 + $_msg = '';
2746 + $quiz_str = _n( 'quiz', 'quizzes', $required_quiz_pass, 'tutor' );
2747 + $assignment_str = _n( 'assignment', 'assignments', $required_assignment_pass, 'tutor' );
2960 2748
2961 - return sprintf(
2962 - /* translators: %1$s: quiz count; %2$s: quiz label; %3$s: assignment count; %4$s: assignment label */
2963 - __( 'You have to pass %1$s %2$s and %3$s %4$s to complete this course.', 'tutor' ),
2964 - $quiz_count,
2965 - $quiz_label,
2966 - $assignment_count,
2967 - $assignment_label
2968 - );
2969 - }
2749 + if ( ! $is_quiz_pass && 0 == $required_assignment_pass ) {
2750 + /* translators: %1$s: number of quiz/assignment pass required; %2$s: quiz/assignment string */
2751 + $_msg = sprintf( __( 'You have to pass %1$s %2$s to complete this course.', 'tutor' ), $required_quiz_pass, $quiz_str );
2752 + }
2970 2753
2971 - /**
2972 - * Check if all lessons and quizzes done before mark course complete.
2973 - *
2974 - * @since 1.5.8
2975 - *
2976 - * @param string $html HTML string.
2977 - *
2978 - * @return string
2979 - */
2980 - public function tutor_lms_hide_course_complete_btn( $html ) {
2981 - $_msg = self::get_course_completion_restrict_msg();
2754 + if ( $is_quiz_pass && $required_assignment_pass > 0 ) {
2755 + //phpcs:ignore
2756 + $_msg = sprintf( __( 'You have to pass %1$s %2$s to complete this course.', 'tutor' ), $required_assignment_pass, $assignment_str );
2757 + }
2982 2758
2983 - if ( $_msg ) {
2759 + if ( ! $is_quiz_pass && $required_assignment_pass > 0 ) {
2760 + /* translators: %1$s: number of quiz pass required; %2$s: quiz string; %3$s: number of assignment pass required; %4$s: assignment string */
2761 + $_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 );
2762 + }
2763 +
2984 2764 return '<div class="tutor-alert tutor-warning tutor-mt-28">
2985 2765 <div class="tutor-alert-text">
2986 2766 <span class="tutor-alert-icon tutor-fs-4 tutor-icon-circle-info tutor-mr-12"></span>
2987 2767 <span>' . $_msg . '</span>
@@ -2997,9 +2777,8 @@
2997 2777 *
2998 2778 * @since 1.5.8
2999 2779 *
3000 2780 * @param string $html HTML string.
3001 - *
3002 2781 * @return string
3003 2782 */
3004 2783 public function get_generate_greadbook( $html ) {
3005 2784 if ( ! tutor_utils()->is_completed_course() ) {
@@ -3011,9 +2790,8 @@
3011 2790 /**
3012 2791 * Add social share content in header
3013 2792 *
3014 2793 * @since 1.6.3
3015 - *
3016 2794 * @return void
3017 2795 */
3018 2796 public function social_share_content() {
3019 2797 global $wp_query, $post;
@@ -3037,9 +2815,8 @@
3037 2815 *
3038 2816 * @since 1.8.2
3039 2817 *
3040 2818 * @param integer $post_id post ID.
3041 - *
3042 2819 * @return void
3043 2820 */
3044 2821 public function delete_associated_enrollment( $post_id ) {
3045 2822 global $wpdb;
@@ -3050,12 +2827,11 @@
3050 2827 post_id
3051 2828 FROM
3052 2829 {$wpdb->postmeta}
3053 2830 WHERE
3054 - meta_key=%s
2831 + meta_key='_tutor_enrolled_by_order_id'
3055 2832 AND meta_value = %d
3056 2833 ",
3057 - EnrollmentModel::ENROLLMENT_ORDER_ID_META,
3058 2834 $post_id
3059 2835 )
3060 2836 );
3061 2837
@@ -3071,35 +2847,21 @@
3071 2847 /**
3072 2848 * Reset course progress.
3073 2849 *
3074 2850 * @since 1.5.8
3075 - *
3076 2851 * @return void
3077 2852 */
3078 2853 public function tutor_reset_course_progress() {
3079 2854 tutor_utils()->checking_nonce();
3080 - $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
3081 - $context = Input::post( 'context', '' );
3082 - $course_reset_progress = tutor_utils()->get_option( 'course_reset_progress', false );
3083 - $course_retake_feature = tutor_utils()->get_option( 'course_retake_feature', false );
2855 + $course_id = Input::post( 'course_id' );
3084 2856
3085 - if ( ! $course_reset_progress && 'learning-area-sidebar' === $context ) {
3086 - $this->response_bad_request( __( 'You do not have permission to reset this course.', 'tutor' ) );
2857 + if ( ! $course_id || ! is_numeric( $course_id ) || ! tutor_utils()->is_enrolled( $course_id ) ) {
2858 + wp_send_json_error( array( 'message' => __( 'Invalid Course ID or Access Denied.', 'tutor' ) ) );
3087 2859 return;
3088 2860 }
3089 2861
3090 - if ( ! $course_retake_feature && ( 'course-landing' === $context || 'learning-area' === $context ) ) {
3091 - $this->response_bad_request( __( 'You do not have permission to retake this course.', 'tutor' ) );
3092 - return;
3093 - }
3094 -
3095 - if ( ! $course_id || ! is_numeric( $course_id ) || ! EnrollmentModel::is_enrolled( $course_id ) ) {
3096 - $this->response_bad_request( __( 'Invalid Course ID or Access Denied.', 'tutor' ) );
3097 - return;
3098 - }
3099 -
3100 2862 tutor_utils()->delete_course_progress( $course_id );
3101 - $this->json_response( '', array( 'redirect_to' => tutor_utils()->get_course_first_lesson( $course_id ) ) );
2863 + wp_send_json_success( array( 'redirect_to' => tutor_utils()->get_course_first_lesson( $course_id ) ) );
3102 2864 }
3103 2865
3104 2866 /**
3105 2867 * Do enroll if guest attempt to enroll and course is free
@@ -3107,9 +2869,9 @@
3107 2869 * @since 1.9.8
3108 2870 *
3109 2871 * @param integer $course_id course ID.
3110 2872 * @param integer $user_id user ID.
3111 - *
2873 +
3112 2874 * @return void
3113 2875 */
3114 2876 public function enroll_after_login_if_attempt( int $course_id, int $user_id ) {
3115 2877 $course_id = sanitize_text_field( $course_id );
@@ -3117,9 +2879,9 @@
3117 2879
3118 2880 if ( $course_id && $is_allowed ) {
3119 2881 $is_purchasable = tutor_utils()->is_course_purchasable( $course_id );
3120 2882 if ( ! $is_purchasable ) {
3121 - EnrollmentModel::do_enroll( $course_id, $order_id = 0, $user_id );
2883 + tutor_utils()->do_enroll( $course_id, $order_id = 0, $user_id );
3122 2884 do_action( 'guest_attempt_after_enrollment', $course_id );
3123 2885 }
3124 2886 }
3125 2887 }
@@ -3127,9 +2889,8 @@
3127 2889 /**
3128 2890 * Handle course enrollment
3129 2891 *
3130 2892 * @since 2.1.0
3131 - *
3132 2893 * @return void
3133 2894 */
3134 2895 public function course_enrollment() {
3135 2896 tutor_utils()->checking_nonce();
@@ -3136,46 +2897,21 @@
3136 2897
3137 2898 $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
3138 2899 $user_id = get_current_user_id();
3139 2900
3140 - if ( ! $course_id || ! $user_id ) {
3141 - wp_send_json_error( tutor_utils()->error_message( 'invalid_req' ) );
3142 - }
3143 -
3144 - $password_protected = post_password_required( $course_id );
3145 - if ( $password_protected ) {
3146 - wp_send_json_error( __( 'This course is password protected', 'tutor' ) );
3147 - }
3148 -
3149 - $course = get_post( $course_id );
3150 -
3151 - if ( CourseModel::STATUS_PRIVATE === $course->post_status && ! current_user_can( 'read_private_tutor_courses' ) ) {
3152 - wp_send_json_error( __( 'You do not have permission to enroll in this course', 'tutor' ) );
3153 - }
3154 -
3155 - /**
3156 - * This check was added to address a security issue where users could
3157 - * enroll in a course via an AJAX call without purchasing it.
3158 - *
3159 - * To prevent this, we now verify whether the course is paid.
3160 - * Additionally, we check if the user is already enrolled, since
3161 - * Tutor's default behavior enrolls users automatically upon purchase.
3162 - *
3163 - * @since 3.9.4
3164 - */
3165 - if ( tutor_utils()->is_course_purchasable( $course_id ) ) {
3166 - $is_enrolled = (bool) EnrollmentModel::is_enrolled( $course_id, $user_id );
3167 -
3168 - if ( ! $is_enrolled ) {
3169 - wp_send_json_error( __( 'Please purchase the course before enrolling', 'tutor' ) );
2901 + if ( $course_id ) {
2902 + $password_protected = post_password_required( $course_id );
2903 + if ( $password_protected ) {
2904 + wp_send_json_error( __( 'This course is password protected', 'tutor' ) );
3170 2905 }
3171 - }
3172 -
3173 - $enroll = EnrollmentModel::do_enroll( $course_id, 0, $user_id );
3174 - if ( $enroll ) {
3175 - wp_send_json_success( __( 'Enrollment successfully done!', 'tutor' ) );
2906 + $enroll = tutor_utils()->do_enroll( $course_id, 0, $user_id );
2907 + if ( $enroll ) {
2908 + wp_send_json_success( __( 'Enrollment successfully done!', 'tutor' ) );
2909 + } else {
2910 + wp_send_json_error( __( 'Enrollment failed, please try again!', 'tutor' ) );
2911 + }
3176 2912 } else {
3177 - wp_send_json_error( __( 'Enrollment failed, please try again!', 'tutor' ) );
2913 + wp_send_json_error( __( 'Invalid course ID', 'tutor' ) );
3178 2914 }
3179 2915 }
3180 2916
3181 2917 /**
@@ -3262,10 +2998,14 @@
3262 2998 'regular_price' => $regular_price,
3263 2999 'sale_price' => $sale_price,
3264 3000 );
3265 3001
3266 - $card_data = apply_filters( 'tutor_course_mini_info', $info, $post );
3002 + if ( 'course-bundle' === $post->post_type && tutor_utils()->is_addon_enabled( 'tutor-pro/addons/course-bundle/course-bundle.php' ) ) {
3003 + $info['total_course'] = count( BundleModel::get_bundle_course_ids( $post->ID ) );
3004 + }
3267 3005
3006 + $card_data = apply_filters( 'tutor_add_course_plan_info', $info, $post );
3007 +
3268 3008 return $card_data;
3269 3009 }
3270 3010
3271 3011 /**
@@ -3286,9 +3026,9 @@
3286 3026 $info['last_updated'] = tutor_i18n_get_formated_date( $post->post_modified_at );
3287 3027 $info['course_duration'] = tutor_utils()->get_course_duration( $post->ID, false );
3288 3028 $info['total_enrolled'] = tutor_utils()->count_enrolled_users_by_course( $post->ID );
3289 3029
3290 - $card_data = apply_filters( 'tutor_course_card_data', $info, $post );
3030 + $card_data = apply_filters( 'tutor_add_course_plan_info', $info, $post );
3291 3031
3292 3032 return $card_data;
3293 3033 }
3294 3034
@@ -3327,217 +3067,6 @@
3327 3067 $args['role__in'] = array_map( 'sanitize_text_field', $filter->role );
3328 3068 }
3329 3069
3330 3070 return $args;
3331 - }
3332 -
3333 - /**
3334 - * Get a list of possible course status.
3335 - *
3336 - * @since 3.6.2
3337 - *
3338 - * @return array
3339 - */
3340 - public static function course_status_list() {
3341 - return array(
3342 - 'publish',
3343 - 'private',
3344 - 'draft',
3345 - 'trash',
3346 - 'pending',
3347 - 'future',
3348 - );
3349 - }
3350 -
3351 - /**
3352 - * Link a course/bundle post to a WooCommerce product.
3353 - *
3354 - * @since 3.8.2
3355 - *
3356 - * @param int $post_ID The WordPress post ID of the course.
3357 - * @param int $product_id The WooCommerce product ID to associate with the course.
3358 - *
3359 - * @return void
3360 - */
3361 - public static function sync_course_with_wc_product( $post_ID, $product_id ) {
3362 -
3363 - update_post_meta( $post_ID, self::COURSE_PRODUCT_ID_META, $product_id );
3364 -
3365 - // Mark product for woocommerce.
3366 - update_post_meta( $product_id, '_virtual', 'yes' );
3367 - update_post_meta( $product_id, '_tutor_product', 'yes' );
3368 - }
3369 -
3370 - /**
3371 - * Map Tutor's course prices to WooCommerce.
3372 - *
3373 - * @since 3.8.2
3374 - *
3375 - * @param int $post_ID The WordPress post ID of the course.
3376 - * @param string|int|float $regular_price The regular price.
3377 - * @param string|int|float $sale_price The sale price.
3378 - * @return void
3379 - */
3380 - private static function set_course_regular_and_sale_price( $post_ID, $regular_price, $sale_price ) {
3381 -
3382 - // Set course regular & sale price.
3383 - update_post_meta( $post_ID, self::COURSE_PRICE_META, $regular_price );
3384 - update_post_meta( $post_ID, self::COURSE_SALE_PRICE_META, $sale_price );
3385 - }
3386 -
3387 - /**
3388 - * Render start/resume button in enrolled course action btn.
3389 - *
3390 - * @since 4.0.0
3391 - *
3392 - * @param int $course_id course id.
3393 - *
3394 - * @return void
3395 - */
3396 - public function render_course_action_btn( int $course_id ) {
3397 - $is_completed = tutor_utils()->is_completed_course( $course_id );
3398 - if ( $is_completed ) {
3399 - $certificate_addon_active = tutor_utils()->is_addon_enabled( 'tutor-certificate' );
3400 - if ( ! $certificate_addon_active ) {
3401 - Button::make()
3402 - ->tag( 'a' )
3403 - ->label( __( 'Completed', 'tutor' ) )
3404 - ->icon( Icon::COMPLETED_CIRCLE )
3405 - ->variant( Variant::PRIMARY )
3406 - ->size( Size::X_SMALL )
3407 - ->attr( 'href', esc_url( get_the_permalink( $course_id ) ) )
3408 - ->render();
3409 - }
3410 - return;
3411 - }
3412 -
3413 - $course_progress = tutor_utils()->get_course_completed_percent( $course_id, 0, true );
3414 - $button_text = $course_progress['completed_percent'] > 0 ? __( 'Resume', 'tutor' ) : __( 'Start', 'tutor' );
3415 - $button_url = tutor_utils()->get_course_first_lesson( $course_id );
3416 -
3417 - if ( ! $button_url ) {
3418 - $button_url = get_the_permalink( $course_id );
3419 - }
3420 -
3421 - Button::make()
3422 - ->tag( 'a' )
3423 - ->label( $button_text )
3424 - ->icon( Icon::PLAY_2 )
3425 - ->variant( Variant::PRIMARY )
3426 - ->size( Size::X_SMALL )
3427 - ->attr( 'href', esc_url( $button_url ) )
3428 - ->render();
3429 - }
3430 -
3431 - /**
3432 - * Get the content for the course completion modal.
3433 - *
3434 - * This method returns HTML for displaying a modal that informs the user
3435 - * that they have not completed all required lessons and assessments. It includes
3436 - * the user's current progress shown as a percentage and a progress bar.
3437 - *
3438 - * @since 4.0.0
3439 - *
3440 - * @param float $course_progress The completion percentage of the course.
3441 - *
3442 - * @return string The rendered HTML content for the modal.
3443 - */
3444 - public static function get_complete_modal_content( float $course_progress = 0 ): string {
3445 - ob_start();
3446 - ?>
3447 - <div>
3448 - <p class="tutor-p3 tutor-text-secondary tutor-text-center tutor-mb-7 tutor-px-11">
3449 - <?php esc_html_e( 'You have not completed all required lessons and assessments. ', 'tutor' ); ?>
3450 - </p>
3451 - <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">
3452 - <div class="tutor-flex tutor-items-center tutor-justify-between">
3453 - <div class="tutor-p3 tutor-text-secondary">
3454 - <?php esc_html_e( 'Your Progress', 'tutor' ); ?>
3455 - </div>
3456 - <div class="tutor-p1 tutor-font-bold">
3457 - <?php echo esc_html( number_format_i18n( $course_progress ) . '%' ); ?>
3458 - </div>
3459 - </div>
3460 - <?php
3461 - Progress::make()->type( 'bar' )->value( $course_progress )->render();
3462 - ?>
3463 - </div>
3464 - </div>
3465 - <?php
3466 - return ob_get_clean();
3467 - }
3468 -
3469 - /**
3470 - * Render the course complete button.
3471 - *
3472 - * Displays a button that allows the user to complete the course. If the course
3473 - * progress is less than 100%, clicking the button shows a modal informing the user
3474 - * that not all requirements are fulfilled. If the course progress is 100%,
3475 - * clicking the button attempts to complete the course.
3476 - *
3477 - * @since 4.0.0
3478 - *
3479 - * @param string $modal_id The HTML ID of the modal to display if not complete.
3480 - * @param int $course_id The ID of the course.
3481 - * @param float $course_progress The current completion percentage of the course.
3482 - * @param string $size The button size.
3483 - * @param string $tooltip Optional. Tooltip message.
3484 - * @param bool $block Optional. Whether the button is full-width.
3485 - *
3486 - * @return void
3487 - */
3488 - 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 {
3489 - $button = Button::make()
3490 - ->variant( Variant::SECONDARY )
3491 - ->label( __( 'Complete the Course', 'tutor' ) )
3492 - ->icon( Icon::TICK_MARK )
3493 - ->size( $size )
3494 - ->block( $block )
3495 - ->attr( 'type', 'button' );
3496 -
3497 - if ( ! empty( $tooltip ) ) {
3498 - $button->disabled();
3499 - }
3500 -
3501 - if ( $course_progress < 100 ) {
3502 - $button->attr( '@click', "TutorCore.modal.showModal('{$modal_id}')" );
3503 - } else {
3504 - $button->attr( '@click', "handleCourseComplete({$course_id})" );
3505 - $button->attr( ':class', "courseCompleteMutation?.isPending ? 'tutor-btn-loading' : ''" );
3506 - $button->attr( ':disabled', 'courseCompleteMutation?.isPending' );
3507 - }
3508 -
3509 - if ( ! empty( $tooltip ) ) {
3510 - Tooltip::make()
3511 - ->content( $tooltip )
3512 - ->placement( Tooltip::PLACEMENT_BOTTOM )
3513 - ->arrow( Tooltip::ARROW_CENTER )
3514 - ->trigger_element( $button->get() )
3515 - ->render();
3516 - } else {
3517 - $button->render();
3518 - }
3519 - }
3520 -
3521 - /**
3522 - * Render course retake button
3523 - *
3524 - * @since 4.0.0
3525 - *
3526 - * @param string $modal_id Modal id.
3527 - * @param string $size The button size.
3528 - * @param bool $block Optional. Whether the button is full-width.
3529 - *
3530 - * @return void
3531 - */
3532 - public static function render_course_retake_btn( string $modal_id, string $size = Size::MEDIUM, bool $block = false ): void {
3533 - Button::make()
3534 - ->variant( Variant::SECONDARY )
3535 - ->label( __( 'Retake this Course', 'tutor' ) )
3536 - ->icon( Icon::RELOAD_4 )
3537 - ->size( $size )
3538 - ->block( $block )
3539 - ->attr( 'type', 'button' )
3540 - ->attr( '@click', "TutorCore.modal.showModal('{$modal_id}')" )
3541 - ->render();
3542 3071 }
3543 3072 }