--- a/tutor/classes/Course.php
+++ b/tutor/classes/Course.php
@@ -2119,12 +2119,17 @@
die( esc_html__( 'Please Sign-In', 'tutor' ) );
}
+ if ( ! tutor_utils()->is_enrolled( $course_id, $user_id ) ) {
+ die( esc_html__( 'User is not enrolled in course', 'tutor' ) );
+ }
+
/**
* Filter hook provided to restrict course completion. This is useful
* for specific cases like prerequisites. WP_Error should be returned
* from the filter value to prevent the completion.
*/
$can_complete = apply_filters( 'tutor_user_can_complete_course', true, $user_id, $course_id );
+
if ( is_wp_error( $can_complete ) ) {
tutor_utils()->redirect_to( $permalink, $can_complete->get_error_message(), 'error' );
} else {
@@ -2992,6 +2997,25 @@
if ( $password_protected ) {
wp_send_json_error( __( 'This course is password protected', 'tutor' ) );
}
+
+ /**
+ * This check was added to address a security issue where users could
+ * enroll in a course via an AJAX call without purchasing it.
+ *
+ * To prevent this, we now verify whether the course is paid.
+ * Additionally, we check if the user is already enrolled, since
+ * Tutor's default behavior enrolls users automatically upon purchase.
+ *
+ * @since 3.9.4
+ */
+ if ( tutor_utils()->is_course_purchasable( $course_id ) ) {
+ $is_enrolled = (bool) tutor_utils()->is_enrolled( $course_id, $user_id );
+
+ if ( ! $is_enrolled ) {
+ wp_send_json_error( __( 'Please purchase the course before enrolling', 'tutor' ) );
+ }
+ }
+
$enroll = tutor_utils()->do_enroll( $course_id, 0, $user_id );
if ( $enroll ) {
wp_send_json_success( __( 'Enrollment successfully done!', 'tutor' ) );
--- a/tutor/classes/Quiz.php
+++ b/tutor/classes/Quiz.php
@@ -283,6 +283,12 @@
$attempt_details = self::attempt_details( Input::post( 'attempt_id', 0, Input::TYPE_INT ) );
$feedback = Input::post( 'feedback', '', Input::TYPE_KSES_POST );
$attempt_info = isset( $attempt_details->attempt_info ) ? $attempt_details->attempt_info : false;
+ $course_id = tutor_utils()->avalue_dot( 'course_id', $attempt_info, 0 );
+
+ if ( ! tutor_utils()->is_instructor_of_this_course( get_current_user_id(), $course_id ) ) {
+ wp_send_json_error( tutor_utils()->error_message() );
+ }
+
if ( $attempt_info ) {
//phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize
$unserialized = unserialize( $attempt_details->attempt_info );
--- a/tutor/ecommerce/CouponController.php
+++ b/tutor/ecommerce/CouponController.php
@@ -577,10 +577,7 @@
*/
public function bulk_action_handler() {
tutor_utils()->checking_nonce();
-
- if ( ! current_user_can( 'manage_options' ) ) {
- tutor_utils()->error_message();
- }
+ tutor_utils()->check_current_user_capability();
// Get and sanitize input data.
$request = Input::sanitize_array( $_POST ); //phpcs:ignore --sanitized already
@@ -630,9 +627,7 @@
public function coupon_permanent_delete() {
tutor_utils()->checking_nonce();
- if ( ! current_user_can( 'manage_options' ) ) {
- tutor_utils()->error_message();
- }
+ tutor_utils()->check_current_user_capability();
// Get and sanitize input data.
$id = Input::post( 'id', 0, Input::TYPE_INT );
--- a/tutor/ecommerce/OrderController.php
+++ b/tutor/ecommerce/OrderController.php
@@ -84,7 +84,7 @@
*
* @since 3.0.0
*/
- add_action( 'wp_ajax_tutor_order_details', array( $this, 'get_order_by_id' ) );
+ add_action( 'wp_ajax_tutor_order_details', array( $this, 'ajax_get_order_details' ) );
/**
* Handle AJAX request for marking an order as paid by order ID.
@@ -258,10 +258,9 @@
*
* @return void
*/
- public function get_order_by_id() {
- if ( ! tutor_utils()->is_nonce_verified() ) {
- $this->json_response( tutor_utils()->error_message( 'nonce' ), null, HttpHelper::STATUS_BAD_REQUEST );
- }
+ public function ajax_get_order_details() {
+ tutor_utils()->check_nonce();
+ tutor_utils()->check_current_user_capability();
$order_id = Input::post( 'order_id' );
--- a/tutor/models/CouponModel.php
+++ b/tutor/models/CouponModel.php
@@ -810,7 +810,7 @@
} else {
$coupon = $this->get_coupon(
array(
- 'coupon_code' => $coupon_code,
+ 'coupon_code' => esc_sql( $coupon_code ),
'coupon_status' => self::STATUS_ACTIVE,
)
);
--- a/tutor/tutor.php
+++ b/tutor/tutor.php
@@ -4,7 +4,7 @@
* Plugin URI: https://tutorlms.com
* 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.
* Author: Themeum
- * Version: 3.9.3
+ * Version: 3.9.4
* Author URI: https://themeum.com
* Requires PHP: 7.4
* Requires at least: 5.3
@@ -26,7 +26,7 @@
*
* @since 1.0.0
*/
-define( 'TUTOR_VERSION', '3.9.3' );
+define( 'TUTOR_VERSION', '3.9.4' );
define( 'TUTOR_FILE', __FILE__ );
/**
--- a/tutor/vendor/composer/installed.php
+++ b/tutor/vendor/composer/installed.php
@@ -3,7 +3,7 @@
'name' => 'themeum/tutor',
'pretty_version' => 'dev-master',
'version' => 'dev-master',
- 'reference' => 'a5dd7291aa5fa582e64a050945b7ec437a833cc5',
+ 'reference' => '043bcc9b76cd1e56219d167b8be313b5aa933109',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -13,7 +13,7 @@
'themeum/tutor' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
- 'reference' => 'a5dd7291aa5fa582e64a050945b7ec437a833cc5',
+ 'reference' => '043bcc9b76cd1e56219d167b8be313b5aa933109',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
--- a/tutor/views/pages/ecommerce/order-list.php
+++ b/tutor/views/pages/ecommerce/order-list.php
@@ -27,7 +27,7 @@
$limit = (int) tutor_utils()->get_option( 'pagination_per_page', 10 );
$offset = ( $limit * $paged_filter ) - $limit;
-$order_controller = new OrderController();
+$order_controller = new OrderController( false );
$get_orders = $order_controller->get_orders( $limit, $offset );
$orders = $get_orders['results'];
--- a/tutor/views/pages/view_attempt.php
+++ b/tutor/views/pages/view_attempt.php
@@ -21,12 +21,18 @@
$attempt_data = $attempt;
$user_id = tutor_utils()->avalue_dot( 'user_id', $attempt_data );
$quiz_id = $attempt && isset( $attempt->quiz_id ) ? $attempt->quiz_id : 0;
+$course_id = tutor_utils()->avalue_dot( 'course_id', $attempt_data );
if ( ! $attempt ) {
- tutor_utils()->tutor_empty_state( __( 'Attemp not found', 'tutor' ) );
+ tutor_utils()->tutor_empty_state( __( 'Attempt not found', 'tutor' ) );
return;
}
if ( 0 === $quiz_id ) {
- tutor_utils()->tutor_empty_state( __( 'Attemp not found', 'tutor' ) );
+ tutor_utils()->tutor_empty_state( __( 'Attempt not found', 'tutor' ) );
+ return;
+}
+
+if ( ! tutor_utils()->is_instructor_of_this_course( get_current_user_id(), $course_id ) ) {
+ tutor_utils()->tutor_empty_state();
return;
}