Skip to content

Commit c9d8633

Browse files
authored
Merge pull request #2270 from themeum/dev
3.9.4
2 parents 35613f8 + 043bcc9 commit c9d8633

File tree

12 files changed

+59
-21
lines changed

12 files changed

+59
-21
lines changed

assets/react/v3/entries/coupon-details/components/coupon/CouponInfo.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ const styles = {
150150
position: absolute;
151151
right: ${spacing[0]};
152152
top: ${spacing[0]};
153+
min-height: auto;
154+
padding-inline: ${spacing[4]};
153155
154156
&:hover,
155157
&:active,

assets/react/v3/entries/order-details/components/order/PaymentBadge.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ const badgeMap: Record<PaymentStatus, { label: string; type: Variant }> = {
88
'partially-refunded': { label: __('Partially refunded', 'tutor'), type: 'secondary' },
99
refunded: { label: __('Refunded', 'tutor'), type: 'critical' },
1010
unpaid: { label: __('Unpaid', 'tutor'), type: 'warning' },
11+
pending: { label: __('Pending', 'tutor'), type: 'warning' },
1112
};
1213

1314
export function PaymentBadge({ status }: { status: PaymentStatus }) {
14-
return <TutorBadge variant={badgeMap[status].type}>{badgeMap[status].label}</TutorBadge>;
15+
return <TutorBadge variant={badgeMap[status]?.type ?? 'secondary'}>{badgeMap[status]?.label ?? status}</TutorBadge>;
1516
}

assets/react/v3/entries/order-details/services/order.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ interface OrderCoursePlan extends OrderSummary {
4141
export type OrderSummaryItem = Prettify<OrderCourse | OrderBundle | OrderCoursePlan>;
4242

4343
export type DiscountType = 'flat' | 'percentage';
44-
export type PaymentStatus = 'paid' | 'unpaid' | 'failed' | 'partially-refunded' | 'refunded';
44+
export type PaymentStatus = 'pending' | 'paid' | 'unpaid' | 'failed' | 'partially-refunded' | 'refunded';
4545
export type OrderStatus = 'incomplete' | 'completed' | 'cancelled' | 'trash';
4646
export type ActivityType =
4747
| 'order-placed'

classes/Course.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2119,12 +2119,17 @@ public function mark_course_complete() {
21192119
die( esc_html__( 'Please Sign-In', 'tutor' ) );
21202120
}
21212121

2122+
if ( ! tutor_utils()->is_enrolled( $course_id, $user_id ) ) {
2123+
die( esc_html__( 'User is not enrolled in course', 'tutor' ) );
2124+
}
2125+
21222126
/**
21232127
* Filter hook provided to restrict course completion. This is useful
21242128
* for specific cases like prerequisites. WP_Error should be returned
21252129
* from the filter value to prevent the completion.
21262130
*/
21272131
$can_complete = apply_filters( 'tutor_user_can_complete_course', true, $user_id, $course_id );
2132+
21282133
if ( is_wp_error( $can_complete ) ) {
21292134
tutor_utils()->redirect_to( $permalink, $can_complete->get_error_message(), 'error' );
21302135
} else {
@@ -2992,6 +2997,25 @@ public function course_enrollment() {
29922997
if ( $password_protected ) {
29932998
wp_send_json_error( __( 'This course is password protected', 'tutor' ) );
29942999
}
3000+
3001+
/**
3002+
* This check was added to address a security issue where users could
3003+
* enroll in a course via an AJAX call without purchasing it.
3004+
*
3005+
* To prevent this, we now verify whether the course is paid.
3006+
* Additionally, we check if the user is already enrolled, since
3007+
* Tutor's default behavior enrolls users automatically upon purchase.
3008+
*
3009+
* @since 3.9.4
3010+
*/
3011+
if ( tutor_utils()->is_course_purchasable( $course_id ) ) {
3012+
$is_enrolled = (bool) tutor_utils()->is_enrolled( $course_id, $user_id );
3013+
3014+
if ( ! $is_enrolled ) {
3015+
wp_send_json_error( __( 'Please purchase the course before enrolling', 'tutor' ) );
3016+
}
3017+
}
3018+
29953019
$enroll = tutor_utils()->do_enroll( $course_id, 0, $user_id );
29963020
if ( $enroll ) {
29973021
wp_send_json_success( __( 'Enrollment successfully done!', 'tutor' ) );

classes/Quiz.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,12 @@ public function tutor_instructor_feedback() {
283283
$attempt_details = self::attempt_details( Input::post( 'attempt_id', 0, Input::TYPE_INT ) );
284284
$feedback = Input::post( 'feedback', '', Input::TYPE_KSES_POST );
285285
$attempt_info = isset( $attempt_details->attempt_info ) ? $attempt_details->attempt_info : false;
286+
$course_id = tutor_utils()->avalue_dot( 'course_id', $attempt_info, 0 );
287+
288+
if ( ! tutor_utils()->is_instructor_of_this_course( get_current_user_id(), $course_id ) ) {
289+
wp_send_json_error( tutor_utils()->error_message() );
290+
}
291+
286292
if ( $attempt_info ) {
287293
//phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize
288294
$unserialized = unserialize( $attempt_details->attempt_info );

ecommerce/CouponController.php

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -577,10 +577,7 @@ public function get_coupons( $limit = 10, $offset = 0 ) {
577577
*/
578578
public function bulk_action_handler() {
579579
tutor_utils()->checking_nonce();
580-
581-
if ( ! current_user_can( 'manage_options' ) ) {
582-
tutor_utils()->error_message();
583-
}
580+
tutor_utils()->check_current_user_capability();
584581

585582
// Get and sanitize input data.
586583
$request = Input::sanitize_array( $_POST ); //phpcs:ignore --sanitized already
@@ -630,9 +627,7 @@ public function bulk_action_handler() {
630627
public function coupon_permanent_delete() {
631628
tutor_utils()->checking_nonce();
632629

633-
if ( ! current_user_can( 'manage_options' ) ) {
634-
tutor_utils()->error_message();
635-
}
630+
tutor_utils()->check_current_user_capability();
636631

637632
// Get and sanitize input data.
638633
$id = Input::post( 'id', 0, Input::TYPE_INT );

ecommerce/OrderController.php

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public function __construct( $register_hooks = true ) {
8484
*
8585
* @since 3.0.0
8686
*/
87-
add_action( 'wp_ajax_tutor_order_details', array( $this, 'get_order_by_id' ) );
87+
add_action( 'wp_ajax_tutor_order_details', array( $this, 'ajax_get_order_details' ) );
8888

8989
/**
9090
* Handle AJAX request for marking an order as paid by order ID.
@@ -258,10 +258,9 @@ public function create_order( int $user_id, array $items, string $payment_status
258258
*
259259
* @return void
260260
*/
261-
public function get_order_by_id() {
262-
if ( ! tutor_utils()->is_nonce_verified() ) {
263-
$this->json_response( tutor_utils()->error_message( 'nonce' ), null, HttpHelper::STATUS_BAD_REQUEST );
264-
}
261+
public function ajax_get_order_details() {
262+
tutor_utils()->check_nonce();
263+
tutor_utils()->check_current_user_capability();
265264

266265
$order_id = Input::post( 'order_id' );
267266

models/CouponModel.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -810,7 +810,7 @@ public function get_coupon_details_for_checkout( $coupon_code = '' ) {
810810
} else {
811811
$coupon = $this->get_coupon(
812812
array(
813-
'coupon_code' => $coupon_code,
813+
'coupon_code' => esc_sql( $coupon_code ),
814814
'coupon_status' => self::STATUS_ACTIVE,
815815
)
816816
);

readme.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Tags: lms, course, elearning, education, learning management system
55
Requires at least: 5.3
66
Tested up to: 6.8
77
Requires PHP: 7.4
8-
Stable tag: 3.9.3
8+
Stable tag: 3.9.4
99
License: GPLv3
1010
License URI: https://www.gnu.org/licenses/gpl-3.0.html
1111

@@ -319,6 +319,11 @@ Tutor LMS allows you to offer certificates to your students upon course completi
319319

320320
== Changelog ==
321321

322+
= 3.9.4 - Dec 18, 2025
323+
324+
Update: Compatibility with WordPress 6.9
325+
Fix: Security vulnerabilities
326+
322327
= 3.9.3 - Nov 27, 2025
323328

324329
Fix: Grades were auto-generated for all quizzes after a student attempted any quiz. (Pro)

tutor.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* Plugin URI: https://tutorlms.com
55
* Description: Tutor is a complete solution for creating a Learning Management System in WordPress way. It can help you to create small to large scale online education site very conveniently. Power features like report, certificate, course preview, private file sharing make Tutor a robust plugin for any educational institutes.
66
* Author: Themeum
7-
* Version: 3.9.3
7+
* Version: 3.9.4
88
* Author URI: https://themeum.com
99
* Requires PHP: 7.4
1010
* Requires at least: 5.3
@@ -26,7 +26,7 @@
2626
*
2727
* @since 1.0.0
2828
*/
29-
define( 'TUTOR_VERSION', '3.9.3' );
29+
define( 'TUTOR_VERSION', '3.9.4' );
3030
define( 'TUTOR_FILE', __FILE__ );
3131

3232
/**

0 commit comments

Comments
 (0)