--- 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/classes/Utils.php
+++ b/tutor/classes/Utils.php
@@ -3281,6 +3281,7 @@
ON cmeta.comment_id = c.comment_ID
AND cmeta.meta_key = 'tutor_rating'
WHERE user_meta.meta_key = '_is_tutor_instructor'
+ AND inst_status.meta_key = '_tutor_instructor_status'
AND ( user.display_name LIKE %s OR user.user_email = %s )
{$status}
{$category_where}
@@ -5777,9 +5778,9 @@
'icon_classes' => 'tutor-icon-brand-facebook',
),
'_tutor_profile_twitter' => array(
- 'label' => __( 'Twitter', 'tutor' ),
- 'placeholder' => 'https://twitter.com/username',
- 'icon_classes' => 'tutor-icon-brand-twitter',
+ 'label' => __( 'X', 'tutor' ),
+ 'placeholder' => 'https://x.com/username',
+ 'icon_classes' => 'tutor-icon-brand-x-twitter',
),
'_tutor_profile_linkedin' => array(
'label' => __( 'Linkedin', 'tutor' ),
--- a/tutor/classes/WooCommerce.php
+++ b/tutor/classes/WooCommerce.php
@@ -757,7 +757,7 @@
return;
}
- $has_enrollment = tutor_utils()->is_enrolled( $course_id, $customer_id, false );
+ $has_enrollment = tutor_utils()->is_enrolled( $course_id, $customer_id, true );
if ( ! $has_enrollment ) {
tutor_utils()->do_enroll( $course_id, $order_id, $customer_id );
}
--- a/tutor/ecommerce/CheckoutController.php
+++ b/tutor/ecommerce/CheckoutController.php
@@ -19,7 +19,6 @@
use TutorModelsBillingModel;
use TutorTraitsJsonResponse;
use TutorHelpersValidationHelper;
-use TutorProEcommerceGuestCheckoutGuestCheckout;
if ( ! defined( 'ABSPATH' ) ) {
exit;
@@ -989,13 +988,14 @@
* @return void
*/
public function restrict_checkout_page() {
- if ( ! is_page( self::get_page_id() ) ) {
+ $page_id = self::get_page_id();
+ if ( ! $page_id || ! is_page( $page_id ) ) {
return;
}
$cart_page_url = CartController::get_page_url();
- if ( ! is_user_logged_in() && ! GuestCheckout::is_enable() ) {
+ if ( ! is_user_logged_in() && ! apply_filters( 'tutor_is_guest_checkout_enabled', false ) ) {
wp_safe_redirect( $cart_page_url );
exit;
}
--- 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/includes/droip/backend/ElementGenerator/ActionsGenerator.php
+++ b/tutor/includes/droip/backend/ElementGenerator/ActionsGenerator.php
@@ -12,6 +12,7 @@
use TUTOR_CERTCertificate;
use TutorProSubscriptionSubscription;
use TutorProSubscriptionModelsPlanModel;
+use TutorProSubscriptionModelsSubscriptionModel;
use TutorEcommerceCheckoutController;
use TutorProGiftCourseGiftCourse;
use TutorProSubscriptionSettings;
@@ -82,6 +83,27 @@
if (! $entry_box_button_logic->show_enroll_btn) {
return '';
}
+
+ if (tutor()->has_pro && Subscription::is_enabled()) {
+ $subscription_model = new SubscriptionModel();
+ $tutor_subscription_enrollment = false;
+
+ // For hybrid mode.
+ if (Course::PRICE_TYPE_PAID === tutor_utils()->price_type($course_id) && $subscription_model->has_course_access($course_id)) {
+ $tutor_subscription_enrollment = true;
+ }
+
+ // For membership only mode.
+ if (Settings::membership_only_mode_enabled() && $subscription_model->has_course_access($course_id)) {
+ $tutor_subscription_enrollment = true;
+ }
+
+ if ($tutor_subscription_enrollment) {
+ $extra_attributes .= " data-tutor_subscription_enrollment='true'";
+ }
+ }
+
+
return $this->generate_child_element_with_parent_droip_data($extra_attributes);
}
@@ -209,7 +231,14 @@
case 'membership_btn': {
$pricing_page = Settings::get_pricing_page_url();
if ($pricing_page) {
- $extra_attributes .= " data-pricing_url='" . $pricing_page . "'";
+ if (is_user_logged_in()) {
+ $extra_attributes .= " data-pricing_url='" . $pricing_page . "'";
+ } else {
+ $login_url = wp_login_url(wp_get_referer());
+
+ $extra_attributes .= " data-login_url='" . $login_url . "'";
+ }
+
return $this->generate_child_element_with_parent_droip_data($extra_attributes);
}
@@ -218,15 +247,15 @@
case 'gift_course_btn': {
if (is_user_logged_in()) {
- $extra_attributes .= " data-tutor-modal-target='tutor-gift-this-course-modal'";
+ $modal_id = 'tutor-gift-this-course-modal-' . wp_generate_uuid4();
+ $extra_attributes .= " data-tutor-modal-target='$modal_id'";
$btn = $this->generate_child_element_with_parent_droip_data($extra_attributes);
-
// Capture template output
ob_start();
tutor_load_template(
'single.course.gift-this-course-modal',
- array('course_id' => $course_id),
+ array('course_id' => $course_id, 'modal_id' => $modal_id),
true
);
@@ -270,50 +299,51 @@
$entry_box_button_logic->show_view_cart_btn = false;
}
- if ($is_paid_course) {
-
- if (tutor()->has_pro && Subscription::is_enabled() && $course_id) {
+ // Remove this part. These logic come from tutor_entry_box_buttons function.
+ // if ($is_paid_course) {
- $plan_model = new PlanModel();
- // Checking is course has subscription plan then show buy now button.
- $selling_option = Course::get_selling_option($course_id);
- if (!$selling_option) {
- $selling_option = Course::SELLING_OPTION_ALL;
- }
- if ($selling_option === Course::SELLING_OPTION_SUBSCRIPTION || $selling_option === Course::SELLING_OPTION_BOTH || $selling_option === Course::SELLING_OPTION_ALL) {
+ // if (tutor()->has_pro && Subscription::is_enabled() && $course_id) {
- $items = $plan_model->get_subscription_plans($course_id, PlanModel::STATUS_ACTIVE);
-
- if (count($items) > 0) {
- $entry_box_button_logic->show_subscribe_now_btn = true;
- }
- }
-
- // Checking is course has membership plan enabled
- $selling_option = Course::get_selling_option($course_id);
- if (!$selling_option) {
- $selling_option = Course::SELLING_OPTION_ALL;
- }
- if ($selling_option === Course::SELLING_OPTION_MEMBERSHIP || $selling_option === Course::SELLING_OPTION_ALL) {
- $active_membership_plans = $plan_model->get_membership_plans(PlanModel::STATUS_ACTIVE);
- if (count($active_membership_plans) > 0) {
- $entry_box_button_logic->show_membership_btn = true;
- }
- }
- }
-
- // Checking is course can be gifted then show gift course button.
- if (class_exists('TutorProGiftCourseInitGift') && class_exists('TutorProGiftCourseGiftCourse')) {
- $init_gift = new TutorProGiftCourseInitGift();
- if (tutor()->has_pro && $init_gift->is_enabled() && $course_id) {
- $can_gift_this_course = GiftCourse::can_gift_course($course_id);
-
- if ($can_gift_this_course) {
- $entry_box_button_logic->show_gift_course_btn = true;
- }
- }
- }
- }
+ // $plan_model = new PlanModel();
+ // // Checking is course has subscription plan then show buy now button.
+ // $selling_option = Course::get_selling_option($course_id);
+ // if (!$selling_option) {
+ // $selling_option = Course::SELLING_OPTION_ALL;
+ // }
+ // if ($selling_option === Course::SELLING_OPTION_SUBSCRIPTION || $selling_option === Course::SELLING_OPTION_BOTH || $selling_option === Course::SELLING_OPTION_ALL) {
+
+ // $items = $plan_model->get_subscription_plans($course_id, PlanModel::STATUS_ACTIVE);
+
+ // if (count($items) > 0) {
+ // $entry_box_button_logic->show_subscribe_now_btn = true;
+ // }
+ // }
+
+ // // Checking is course has membership plan enabled
+ // $selling_option = Course::get_selling_option($course_id);
+ // if (!$selling_option) {
+ // $selling_option = Course::SELLING_OPTION_ALL;
+ // }
+ // if ($selling_option === Course::SELLING_OPTION_MEMBERSHIP || $selling_option === Course::SELLING_OPTION_ALL) {
+ // $active_membership_plans = $plan_model->get_membership_plans(PlanModel::STATUS_ACTIVE);
+ // if (count($active_membership_plans) > 0) {
+ // $entry_box_button_logic->show_membership_btn = true;
+ // }
+ // }
+ // }
+
+ // // Checking is course can be gifted then show gift course button.
+ // if (class_exists('TutorProGiftCourseInitGift') && class_exists('TutorProGiftCourseGiftCourse')) {
+ // $init_gift = new TutorProGiftCourseInitGift();
+ // if (tutor()->has_pro && $init_gift->is_enabled() && $course_id) {
+ // $can_gift_this_course = GiftCourse::can_gift_course($course_id);
+
+ // if ($can_gift_this_course) {
+ // $entry_box_button_logic->show_gift_course_btn = true;
+ // }
+ // }
+ // }
+ // }
return $entry_box_button_logic;
}
--- a/tutor/includes/droip/backend/ElementGenerator/PriceGenerator.php
+++ b/tutor/includes/droip/backend/ElementGenerator/PriceGenerator.php
@@ -39,20 +39,23 @@
}
$selling_option = Course::get_selling_option($course_id);
+
+ $is_membership_only_mode_enabled = apply_filters( 'tutor_membership_only_mode', false );
+
if(!$selling_option){
$selling_option = Course::SELLING_OPTION_ALL;
}
switch ($type) {
case 'free': {
- if (! $this->isPaidCourse($course_id)) {
+ if (! $this->isPaidCourse($course_id) && !$is_membership_only_mode_enabled) {
return $this->generate_common_element();
}
return "";
}
case 'paid': {
if ($selling_option === Course::SELLING_OPTION_ALL || $selling_option === Course::SELLING_OPTION_ONE_TIME || $selling_option === Course::SELLING_OPTION_BOTH) {
- if ($this->isPaidCourse($course_id)) {
+ if ($this->isPaidCourse($course_id) && !$is_membership_only_mode_enabled) {
return $this->generate_common_element();
}
}
@@ -60,7 +63,7 @@
}
case 'subscription': {
if ($selling_option === Course::SELLING_OPTION_ALL || $selling_option === Course::SELLING_OPTION_SUBSCRIPTION || $selling_option === Course::SELLING_OPTION_BOTH) {
- if (tutor()->has_pro && Subscription::is_enabled()) {
+ if (tutor()->has_pro && Subscription::is_enabled() && !$is_membership_only_mode_enabled) {
$plan_model = new PlanModel();
$active_subscription_plans = $plan_model->get_subscription_plans($course_id, PlanModel::STATUS_ACTIVE);
if ($this->isPaidCourse($course_id) && count($active_subscription_plans) > 0) {
--- a/tutor/includes/droip/backend/Hooks.php
+++ b/tutor/includes/droip/backend/Hooks.php
@@ -397,7 +397,7 @@
} else if ($args['name'] === 'TUTOR_LMS-subscriptions') {
if (tutor()->has_pro && Subscription::is_enabled()) {
$plan_model = new PlanModel();
- $items = $plan_model->get_subscription_plans($args['post_parent']);
+ $items = $plan_model->get_subscription_plans($args['post_parent'], PlanModel::STATUS_ACTIVE); // get all active subscription plans
return [
'data' => $items,
'pagination' => null,
--- a/tutor/includes/droip/backend/VisibilityCondition.php
+++ b/tutor/includes/droip/backend/VisibilityCondition.php
@@ -34,8 +34,11 @@
switch ($type) {
case 'courses': {
$conditions = self::get_course_type_conditions($conditions);
+ break;
}
}
+
+ $conditions = self::get_lms_setting_type_conditions($conditions);
} elseif ($collectionType === 'user') {
switch ($type) {
case 'courses': {
@@ -602,12 +605,37 @@
return $conditions;
}
+ private static function get_lms_setting_type_conditions($conditions)
+ {
+ if (!isset($conditions['lms-settings']['fields'])) {
+ $conditions['lms-settings']['fields'] = array();
+ }
+
+ $conditions['lms-settings'] = array(
+ 'title' => 'LMS Settings',
+ 'fields' => array_merge(
+ $conditions['lms-settings']['fields'],
+ array(
+ array(
+ 'source' => TDE_APP_PREFIX,
+ 'value' => 'is_membership_only_mode_enabled',
+ 'title' => 'Membership-Only Mode',
+ 'operator_type' => 'boolean_operators',
+ )
+ )
+ ),
+ );
+
+ return $conditions;
+ }
+
public static function element_visibility_condition_check($default_value, $condition, $options)
{
$source = $condition['source'];
$field = $condition['field']['value'];
$operator = $condition['operator']['value'];
$operand = $condition['operand']['value'];
+ $field_group = isset($condition['field']['group']) ? $condition['field']['group'] : '';
if (isset($options['TUTOR_LMS-subscriptions'])) {
$subscription = (array) $options['TUTOR_LMS-subscriptions'];
@@ -619,7 +647,11 @@
$membership_feature = (array) $options['membership-feature'];
$fieldValue = self::get_membership_feature_field_value($membership_feature, $field);
} else {
- $fieldValue = self::get_course_field_value($field, $options);
+ if ($field_group === 'lms-settings') {
+ $fieldValue = self::get_lms_settings_field_value($field);
+ } else {
+ $fieldValue = self::get_course_field_value($field, $options);
+ }
}
if ($source === TDE_APP_PREFIX) {
@@ -802,4 +834,19 @@
}
}
}
-}
+
+ private static function get_lms_settings_field_value($field)
+ {
+ switch ($field) {
+ case 'is_membership_only_mode_enabled': {
+ $is_membership_only_mode_enabled = apply_filters('tutor_membership_only_mode', false);
+ return $is_membership_only_mode_enabled;
+ }
+
+ default: {
+ return null;
+ }
+ }
+ return null;
+ }
+}
No newline at end of file
--- a/tutor/includes/tutor-general-functions.php
+++ b/tutor/includes/tutor-general-functions.php
@@ -1329,7 +1329,7 @@
}
}
- return apply_filters( 'tutor_enrollment_buttons', $conditional_buttons );
+ return apply_filters( 'tutor_enrollment_buttons', $conditional_buttons, $course_id, $user_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.2
+ * 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.2' );
+define( 'TUTOR_VERSION', '3.9.4' );
define( 'TUTOR_FILE', __FILE__ );
/**
--- a/tutor/vendor/composer/installed.php
+++ b/tutor/vendor/composer/installed.php
@@ -1,9 +1,9 @@
<?php return array(
'root' => array(
'name' => 'themeum/tutor',
- 'pretty_version' => 'dev-4.0.0-dev',
- 'version' => 'dev-4.0.0-dev',
- 'reference' => '8ea547bfe84f30f1e1f68c90ccdc9a748beb25cb',
+ 'pretty_version' => 'dev-master',
+ 'version' => 'dev-master',
+ 'reference' => '043bcc9b76cd1e56219d167b8be313b5aa933109',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -11,9 +11,9 @@
),
'versions' => array(
'themeum/tutor' => array(
- 'pretty_version' => 'dev-4.0.0-dev',
- 'version' => 'dev-4.0.0-dev',
- 'reference' => '8ea547bfe84f30f1e1f68c90ccdc9a748beb25cb',
+ 'pretty_version' => 'dev-master',
+ 'version' => 'dev-master',
+ '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;
}