__('Invalid nonce', 'acadlix'), 'error_code' => 'invalid_nonce' ], 403); } if (is_user_logged_in()) { wp_send_json_success(array('logged_in' => true, 'user_id' => get_current_user_id())); } else { wp_send_json_success(array('logged_in' => false, 'user_id' => 0)); } // Always die in AJAX functions wp_die(); } public function acadlix_login() { try { $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : ''; if ( empty($nonce) || !wp_verify_nonce($nonce, 'acadlix_auth_nonce') ) { wp_send_json_error([ 'message' => __('Invalid nonce', 'acadlix'), 'error_code' => 'invalid_nonce' ], 403); } $username = isset($_POST['username']) ? sanitize_text_field(wp_unslash($_POST['username'])) : ''; $password = isset($_POST['password']) ? wp_unslash($_POST['password']) // phpcs:ignore : ''; $remember = isset($_POST['remember']) ? (bool) wp_unslash($_POST['remember']) // phpcs:ignore : true; /** * 1. PRE-VALIDATION HOOK * For: rate limits, IP blocking, captcha, maintenance mode */ $pre = apply_filters('acadlix_login_pre_validate', null, $username, $password); if ($pre instanceof WP_Error) { wp_send_json_error([ 'error_stage' => 'pre_validate', 'message' => $pre->get_error_message(), ]); } /** * 2. CORE USER VALIDATION */ $user = apply_filters( 'acadlix_login_validate_credentials', wp_authenticate($username, $password), $username, $password ); if (is_wp_error($user)) { wp_send_json_error([ 'error_stage' => 'credentials', 'message' => __('Invalid email/username or password.', 'acadlix') ]); } /** * 3. AUTHENTICATED BUT NOT LOGGED-IN YET */ $after_auth = apply_filters('acadlix_login_after_authenticate', [ 'allow_login' => true, 'user' => $user ], $user); if ($after_auth instanceof WP_Error) { wp_send_json_error([ 'error_stage' => 'after_authenticate', 'message' => $after_auth->get_error_message(), ]); } if (!$after_auth['allow_login']) { // PRO plugin will return custom JSON (e.g., MFA REQUIRED) wp_send_json_success($after_auth); } /** * 4. PRE-SIGNON HOOK * Good for logging, security events, device fingerprinting */ do_action('acadlix_login_pre_signon', $user); /** * 5. LOG THE USER IN */ $creds = array( 'user_login' => $username, 'user_password' => $password, 'remember' => $remember, ); $user = wp_signon($creds, false); if (is_wp_error($user)) { wp_send_json_error([ 'error_stage' => 'signon', 'message' => $user->get_error_message(), ]); } /** * 6. FINAL RESPONSE (can be customized entirely) */ $response = apply_filters('acadlix_login_response', [ 'message' => __('Login successful', 'acadlix'), 'user' => [ 'ID' => $user->ID, 'user_email' => $user->user_email, 'display_name' => $user->display_name, ] ], $user); if ($response instanceof WP_Error) { wp_send_json_error([ 'error_stage' => 'final_response', 'message' => $response->get_error_message(), ]); } wp_send_json_success($response); } catch (\Throwable $th) { wp_send_json_error([ 'message' => $th->getMessage(), 'error_code' => $th->getCode() ]); } } /** * Process dynamic registration form data */ protected function process_registration_data($data = []) { $result = [ 'username' => '', 'email' => '', 'password' => '', 'confirm_password' => '', 'meta' => [], ]; if (empty($data) || !is_array($data)) { return $result; } foreach ($data as $field) { if (empty($field['id'])) { continue; } $id = $field['id']; $value = $field['value'] ?? ''; $meta_key = $field['meta_key'] ?? ''; $is_meta = $field['is_meta'] ?? false; $type = $field['type'] ?? 'text'; /* |-------------------------------------------------------------------------- | 1. PASSWORD (DO NOT SANITIZE) |-------------------------------------------------------------------------- */ if ($id === 'password') { $result['password'] = wp_unslash($value); // phpcs:ignore continue; } if ($id === 'confirm_password') { $result['confirm_password'] = wp_unslash($value); // phpcs:ignore continue; } /* |-------------------------------------------------------------------------- | 2. CORE WORDPRESS FIELDS |-------------------------------------------------------------------------- */ if (!$is_meta) { switch ($meta_key) { case 'user_login': $result['username'] = sanitize_user(wp_unslash($value)); break; case 'user_email': $result['email'] = sanitize_email(wp_unslash($value)); break; case 'user_url': $result['user_url'] = esc_url_raw(wp_unslash($value)); break; default: if (!empty($meta_key)) { $result['meta'][$meta_key] = sanitize_text_field(wp_unslash($value)); } } continue; } /* |-------------------------------------------------------------------------- | 3. USER META FIELDS |-------------------------------------------------------------------------- */ if ($is_meta && !empty($meta_key)) { $result['meta'][$meta_key] = sanitize_text_field(wp_unslash($value)); } /* |-------------------------------------------------------------------------- | 4. SPECIAL FIELD: PHONE SETTINGS |-------------------------------------------------------------------------- */ if ($id === 'phone_number' && !empty($field['settings'])) { $result[$id] = sanitize_text_field(wp_unslash($value)); if (!empty($field['settings']['phonecode']['meta_key'])) { $result['meta'][ $field['settings']['phonecode']['meta_key'] ] = sanitize_text_field( $field['settings']['phonecode']['value'] ?? '' ); $result['phonecode'] = sanitize_text_field( $field['settings']['phonecode']['value'] ?? '' ); } if (!empty($field['settings']['isocode']['meta_key'])) { $result['meta'][ $field['settings']['isocode']['meta_key'] ] = sanitize_text_field( $field['settings']['isocode']['value'] ?? '' ); $result['isocode'] = sanitize_text_field( $field['settings']['isocode']['value'] ?? '' ); } } } return $result; } public function acadlix_register() { try { $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : ''; if ( empty($nonce) || !wp_verify_nonce($nonce, 'acadlix_auth_nonce') ) { wp_send_json_error([ 'message' => __('Invalid nonce', 'acadlix'), 'error_code' => 'invalid_nonce' ], 403); } $form = isset($_POST['data']) ? json_decode(wp_unslash($_POST['data']), true) : []; $processed = $this->process_registration_data($form); $email = $processed['email']; $username = !empty($processed['username']) ? $processed['username'] : $email; $password = $processed['password']; $user_url = $processed['user_url'] ?? ''; $phone_number = $processed['phone_number'] ?? ''; $phonecode = $processed['phonecode'] ?? ''; $user_meta = $processed['meta']; $first_name = $user_meta['first_name'] ?? ''; $last_name = $user_meta['last_name'] ?? ''; $display_name = trim("$first_name $last_name") ?: $username; // check if phone number are already used by another user if (!empty($phone_number)) { // 1. Find all user IDs that use this phone number $user_ids_with_number = get_users(array( 'meta_key' => acadlix()->helper()->acadlix_get_option("acadlix_phone_user_meta_key", '_acadlix_profile_phone_number'), 'meta_value' => $phone_number, 'fields' => 'ID', )); if (!empty($user_ids_with_number)) { // 2. Out of those users, check if any have the same phone code $existing_user = get_users(array( 'include' => $user_ids_with_number, 'meta_key' => acadlix()->helper()->acadlix_get_option("acadlix_phonecode_user_meta_key", '_acadlix_profile_phonecode'), 'meta_value' => $phonecode, 'fields' => 'ID', 'number' => 1 )); if (!empty($existing_user)) { wp_send_json_error(['message' => __('This phone number with this country code is already in use.', 'acadlix')]); } } } /** * 1. PRE-VALIDATION * For captcha, IP checks, maintenance mode, closed registration etc. */ $pre = apply_filters('acadlix_register_pre_validate', null, $username, $email, $password); if ($pre instanceof WP_Error) { wp_send_json_error([ 'error_stage' => 'pre_validate', 'message' => $pre->get_error_message(), ]); } /** * 2. FIELD VALIDATION HOOK * Add: password strength, email domain restriction, username rules, etc */ $validate = apply_filters('acadlix_register_validate_fields', null, $username, $email, $password); if ($validate instanceof WP_Error) { wp_send_json_error([ 'error_stage' => 'field_validation', 'message' => $validate->get_error_message(), ]); } /** * 3. CORE REGISTRATION LOGIC (filterable) * PRO plugin can override user creation logic completely. */ // $user_meta = [ // acadlix()->helper()->acadlix_get_option("acadlix_phonecode_user_meta_key", '_acadlix_profile_phonecode') => $phonecode, // acadlix()->helper()->acadlix_get_option("acadlix_isocode_user_meta_key", '_acadlix_profile_isocode') => $isocode, // acadlix()->helper()->acadlix_get_option("acadlix_phone_user_meta_key", '_acadlix_profile_phone_number') => $phone_number, // ]; $user_id = $this->acadlix_register_user( $username, $email, $password, ['user_url' => $user_url, 'display_name' => $display_name], $user_meta, true ); if (is_wp_error($user_id)) { wp_send_json_error([ 'error_stage' => 'create_user', 'message' => $user_id->get_error_message(), ]); } /** * 4. FINAL RESPONSE TO FRONTEND (customizable) */ $user = get_user_by('id', $user_id); $response = apply_filters('acadlix_register_response', [ 'user_id' => $user_id, 'message' => __('Registration successful', 'acadlix'), 'user' => [ 'ID' => $user->ID, 'user_email' => $user->user_email, 'display_name' => $user->display_name, ] ], $user_id); if ($response instanceof WP_Error) { wp_send_json_error([ 'error_stage' => 'final_response', 'message' => $response->get_error_message(), ]); } wp_send_json_success($response); } catch (\Throwable $th) { wp_send_json_error([ 'message' => $th->getMessage(), 'error_code' => $th->getCode() ]); } } protected function acadlix_register_user( $username, $email, $password = '', $user_data = [], $user_meta = [], $auto_login = false ) { if (empty($username) || empty($email)) { return new WP_Error('missing_fields', __('Username and email are required.', 'acadlix')); } if (!is_email($email)) { return new WP_Error('invalid_email', __('Invalid email address.', 'acadlix')); } if (email_exists($email)) { return new WP_Error('email_exists', __('Email already registered.', 'acadlix')); } if (empty($password)) { $password = wp_generate_password(12, false); } $user_can_register = acadlix()->helper()->acadlix_get_option("users_can_register", false); if (!$user_can_register) { return new WP_Error('registration_disabled', __('User registration is currently disabled.', 'acadlix')); } // Create user $user_id = apply_filters( 'acadlix_register_user', wp_create_user($username, $password, $email), $username, $email, $password ); if (is_wp_error($user_id)) { return $user_id; // return error if user creation failed WP_Error } do_action('acadlix_register_after_create', $user_id, $username, $email); // Update user data if (!empty($user_data)) { $user_data['ID'] = $user_id; wp_update_user($user_data); } // Add user meta if (!empty($user_meta)) { foreach ($user_meta as $meta_key => $meta_value) { update_user_meta($user_id, $meta_key, $meta_value); } } $auto_login = apply_filters( 'acadlix_register_auto_login', $auto_login, $user_id ); if (is_wp_error($auto_login)) { return $auto_login; } // Auto login if ($auto_login) { wp_set_current_user($user_id); wp_set_auth_cookie($user_id, true); do_action('wp_login', $username, get_user_by('id', $user_id)); // phpcs:ignore } return $user_id; } public function acadlix_forgot_password() { try { $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : ''; if ( empty($nonce) || !wp_verify_nonce($nonce, 'acadlix_auth_nonce') ) { wp_send_json_error([ 'message' => __('Invalid nonce', 'acadlix'), 'error_code' => 'invalid_nonce' ], 403); } $user_identifier = isset($_POST['username']) ? sanitize_text_field(wp_unslash($_POST['username'])) : ''; if (empty($user_identifier)) { wp_send_json_error( [ 'message' => __('Please enter a username or email address.', 'acadlix'), ] ); } // --------------------------- // reCAPTCHA (optional) // --------------------------- if (isset($_POST['g-recaptcha-response'])) { $recaptcha_response = sanitize_text_field( wp_unslash($_POST['g-recaptcha-response']) ); $recaptchav3 = acadlix() ->authentications() ->recaptchav3() ->verify($recaptcha_response); if (is_wp_error($recaptchav3)) { wp_send_json_error( [ 'message' => $recaptchav3->get_error_message(), 'error_code' => $recaptchav3->get_error_code(), ] ); } } // // Register the user $result = retrieve_password($user_identifier); if (is_wp_error($result)) { return wp_send_json_error(['message' => $result->get_error_message()]); } return wp_send_json_success(array( 'message' => __('Password reset email sent successfully.', 'acadlix'), )); } catch (\Throwable $th) { // ❗ Do not expose internal errors wp_send_json_error( [ 'message' => __('Unexpected error occurred.', 'acadlix'), 'error_code' => 'server_error', ], 500 ); } } public function acadlix_password_reset_email($message, $key, $user_login, $user_data) { $dashboard_page_id = acadlix()->helper()->acadlix_get_option('acadlix_dashboard_page_id'); if (!$dashboard_page_id) { return $message; // fallback to default message if dashboard page not set } $dashboard_url = get_permalink($dashboard_page_id); if (empty($dashboard_url)) { return $message; // fallback to default message if dashboard slug not found } $reset_url = trailingslashit($dashboard_url) . 'reset-password/?reset_key=' . $key . '&user_id=' . $user_data->ID; // Customize the email message here $message = "Hello,\n\n"; $message .= "You requested a password reset for your account. Please click the link below to reset your password:\n\n"; $message .= "$reset_url\n\n"; $message .= "If you did not request this, please ignore this email.\n\n"; $message .= "Thank you."; return $message; } public function acadlix_reset_password() { try { $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : ''; if ( empty($nonce) || !wp_verify_nonce($nonce, 'acadlix_auth_nonce') ) { wp_send_json_error([ 'message' => __('Invalid nonce', 'acadlix'), 'error_code' => 'invalid_nonce' ], 403); } $reset_key = isset($_POST['reset_key']) ? sanitize_text_field(wp_unslash($_POST['reset_key'])) : ''; $user_id = isset($_POST['user_id']) ? intval($_POST['user_id']) : 0; $password = isset($_POST['password']) ? wp_unslash($_POST['password']) : ''; // phpcs:ignore $confirm_password = isset($_POST['confirm_password']) ? wp_unslash($_POST['confirm_password']) : ''; // phpcs:ignore /** * 1. PRE-VALIDATION HOOK * For: rate limits, IP blocking, payload validation */ $pre = apply_filters('acadlix_reset_password_pre_validate', null, $user_id, $reset_key); if ($pre instanceof WP_Error) { wp_send_json_error([ 'error_stage' => 'pre_validate', 'message' => $pre->get_error_message(), ]); } if (empty($reset_key) || empty($user_id) || empty($password) || empty($confirm_password)) { wp_send_json_error([ 'message' => __('Missing required parameters.', 'acadlix'), 'error_code' => 'missing_parameters' ], 400); } if ($password !== $confirm_password) { wp_send_json_error([ 'message' => __('Password and confirm password do not match.', 'acadlix'), 'error_code' => 'password_mismatch' ], 400); } // Validate the reset key $user = get_user_by('id', $user_id); if (!$user) { wp_send_json_error([ 'message' => __('Invalid user ID.', 'acadlix'), 'error_code' => 'invalid_user' ], 400); } $check_key = check_password_reset_key($reset_key, $user->user_login); if (is_wp_error($check_key)) { wp_send_json_error([ 'message' => $check_key->get_error_message(), 'error_code' => $check_key->get_error_code() ], 400); } // Reset the password reset_password($user, $password); /** * 3. POST-RESET HOOK * Good for triggering confirmation emails or logging the security event */ do_action('acadlix_after_password_reset', $user); /** * 4. AUTO-LOGIN PRE-SIGNON HOOK * We reuse your login hook to maintain consistency across the app */ do_action('acadlix_login_pre_signon', $user); /** * 5. LOG THE USER IN * Since we have the plaintext password, we can run a standard wp_signon */ $creds = array( 'user_login' => $user->user_login, 'user_password' => $password, 'remember' => true, // Automatically remember them after reset ); $logged_in_user = wp_signon($creds, false); if (is_wp_error($logged_in_user)) { wp_send_json_error([ 'error_stage' => 'signon', 'message' => $logged_in_user->get_error_message(), ]); } /** * 6. FINAL RESPONSE * Reusing the 'acadlix_login_response' filter so the React app receives * the exact same payload structure as a normal login. */ $redirect_url = ''; $dashboard_page_id = acadlix()->helper()->acadlix_get_option('acadlix_dashboard_page_id'); if ($dashboard_page_id) { $redirect_url = get_permalink($dashboard_page_id); } $response = apply_filters('acadlix_login_response', [ 'message' => __('Password reset successfully. Logging you in...', 'acadlix'), 'redirect_url' => $redirect_url, 'user' => [ 'ID' => $logged_in_user->ID, 'user_email' => $logged_in_user->user_email, 'display_name' => $logged_in_user->display_name, ] ], $logged_in_user); if ($response instanceof WP_Error) { wp_send_json_error([ 'error_stage' => 'final_response', 'message' => $response->get_error_message(), ]); } wp_send_json_success($response); } catch (\Throwable $th) { wp_send_json_error([ 'message' => $th->getMessage(), 'error_code' => $th->getCode() ]); } } public function acadlix_verify_captcha($return) { if (!acadlix()->authentications()->recaptchav3()->is_enabled()) { return $return; // skip if captcha not enabled } if (empty($_POST['g-recaptcha-response'])) { // phpcs:ignore return new WP_Error('captcha_missing', __('Captcha is required.', 'acadlix')); } $captcha_token = sanitize_text_field( wp_unslash($_POST['g-recaptcha-response']) // phpcs:ignore ); // Call your existing captcha class $verify = acadlix()->authentications()->recaptchav3()->verify( $captcha_token ); if (is_wp_error($verify)) { return new WP_Error('captcha_invalid', $verify->get_error_message()); } return $return; // no error → continue login pipeline } public function acadlix_filter_courses() { $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : ''; if ( empty($nonce) || !wp_verify_nonce($nonce, 'acadlix_course_nonce') ) { wp_send_json_error([ 'message' => __('Invalid nonce', 'acadlix'), 'error_code' => 'invalid_nonce' ], 403); } $context = isset($_POST['context']) ? sanitize_text_field(wp_unslash($_POST['context'])) : ''; $term_id = isset($_POST['term_id']) ? intval($_POST['term_id']) : 0; $categories = isset($_POST['categories']) ? json_decode(wp_unslash($_POST['categories']), true) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized : []; $search = isset($_POST['search']) ? sanitize_text_field(wp_unslash($_POST['search'])) : ''; $page = isset($_POST['page']) ? max(1, intval($_POST['page'])) : 1; $coursesPerPage = acadlix()->helper()->acadlix_get_option('acadlix_no_of_courses_per_page'); $allCourseView = acadlix()->view()->allCourse(); $query = $allCourseView->setup_query( $search, $categories, $term_id, $context, $page, $coursesPerPage ); $total_courses = $query['course_count']; $courses = $query['courses']; $allCourseView->setPage($page); $allCourseView->setCourseCount($total_courses); ob_start(); add_filter('acadlix_course_page_context', function () { return ACADLIX_COURSE_CPT; }); $allCourseView->render_all_courses($courses); $rendered_courses = ob_get_clean(); wp_send_json_success([ 'courses' => $rendered_courses, ]); } public static function instance() { if (!self::$_instance) { self::$_instance = new self(); } return self::$_instance; } }