siteFeatures = Features::forSite(); $this->metaForm = new MetaForm(); $this->emailManager = new EmailManager(); $this->rateLimiter = new AjaxRateLimiter(); // Register default token handlers $this->registerDefaultHandlers(); // Initialize magic link support if enabled if ($this->siteFeatures->has('magicLink')) { $this->initMagicLinkSupport(); } // Create login page if it doesn't exist $this->ensureLoginPageExists(); // Redirect wp-login.php to custom page add_action('login_init', [$this, 'redirectToCustomLogin']); add_action('template_include', [$this, 'renderLoginPage']); add_action('wp_enqueue_scripts', [$this, 'enqueueScripts'], 15); // Handle form submissions via AJAX add_action('wp_ajax_nopriv_jvb_login', [$this, 'handleAjaxLogin']); add_action('wp_ajax_nopriv_jvb_register', [$this, 'handleAjaxRegister']); add_action('wp_ajax_nopriv_jvb_lostpassword', [$this, 'handleAjaxLostPassword']); add_action('wp_ajax_nopriv_jvb_resetpass', [$this, 'handleAjaxResetPassword']); // Login success handling add_action('wp_login', [$this, 'handleSuccessfulLogin'], 10, 2); // Allow other features to register handlers do_action('jvbLoginManagerInit', $this); } /************************************************************************** * SETUP & CONFIGURATION **************************************************************************/ /** * Redirect wp-login.php to custom login page */ public function redirectToCustomLogin(): void { // Don't redirect if AJAX or REST if ((defined('DOING_AJAX') && DOING_AJAX) || (defined('REST_REQUEST') && REST_REQUEST)) { return; } // Build custom login URL with all query args $custom_login_page = home_url('/login'); $query_args = $_GET; // Remove WordPress internal args unset($query_args['interim-login'], $query_args['wp-auth-check']); if (!empty($query_args)) { $custom_login_page = add_query_arg($query_args, $custom_login_page); } wp_safe_redirect($custom_login_page); exit; } protected function getRegistrationFormFields():array { $form = get_option(BASE.'registration_form_fields'); if (!$form) { $form = []; $select = []; //Basic fields, for any $fields = [ 'name' => [ 'type' => 'text', 'required' => true, 'label' => 'Your Name', 'placeholder'=> 'Mister Meseeks' ], 'email' => [ 'type' => 'email', 'required' => true, 'label' => 'Your Email', 'placeholder'=> 'look@me.com' ] ]; if (count(JVB_USER) > 1) { foreach (JVB_USER as $slug => $config) { if (!array_key_exists('can_register', $config) || !$config['can_register']) { continue; } $icon = $config['icon'] ?? ''; $icon = ($icon !== '') ? jvbIcon($icon) : ''; $select[$slug] = ''.$icon.$config['label'].''.$config['register']['text']??''.''; if (!empty($config['register']['fields']??[])){ foreach ($config['register']['fields'] as $field) { $field['condition'] = [ 'field' => 'user_select', 'value' => $slug, 'operator' => '==' ]; $fields[] = $field; } } } if (!empty($select)) { $select = array_merge( [ 'subscriber' => 'Subscriber', ], $select ); $form = array_merge( [ 'user_select' => [ 'type' => 'radio', 'label' => 'Register as', 'options' => $select, 'required' => true, 'default' => 'subscriber' ] ], $fields ); } }else { $form = $fields; } update_option(BASE.'registration_form_fields', $form); } return $form; } protected function setupFields():void { $fields = []; switch($this->action) { case 'register': $fields = $this->getRegistrationFormFields(); break; case 'lostpassword': $fields = [ 'user_email' => [ 'type' => 'email', 'label' => __('Email Address', 'jvb'), 'required' => true, 'placeholder' => 'look@me.com', ], ]; break; case 'rp': case 'resetpass': $fields = [ 'pass1' => [ 'type' => 'text', 'subtype' => 'password', 'label' => __('New Password', 'jvb'), 'required' => true, ], 'pass2' => [ 'type' => 'text', 'subtype' => 'password', 'label' => __('Confirm Password', 'jvb'), 'required' => true, ], ]; break; case 'login': $fields = [ 'user_email' => [ 'type' => 'email', 'label' => __('Email Address', 'jvb'), 'required' => true, 'placeholder' => 'look@me.com', ], 'user_password' => [ 'type' => 'text', 'subtype'=> 'password', 'label' => __('Password', 'jvb'), 'required' => true, ], 'remember_me' => [ 'type' => 'true_false', 'label' => __('Remember Me', 'jvb'), 'default' => true ] ]; break; case 'postpass': $fields = [ 'post_password' => [ 'type' => 'text', 'subtype' => 'password', 'label' => __('Password', 'jvb'), 'required' => true, 'hint' => 'This post is password protected. Please enter the password to view it.', ], ]; break; case 'confirmaction': break; } $this->fields = $fields; } /** * Ensure login page exists */ protected function ensureLoginPageExists(): void { $login_page = $this->getLoginPage(); if (!$login_page || !is_int($login_page)) { $page_id = get_page_by_path('login'); if (!$page_id) { $page_id = wp_insert_post([ 'post_title' => 'Login', 'post_name' => 'login', 'post_content' => '[jvb_login_form]', 'post_status' => 'publish', 'post_type' => 'page', 'post_author' => 1 ]); } if ($page_id && !is_wp_error($page_id)) { if (is_object($page_id)) { $page_id = (int)$page_id->ID; } update_option(BASE.'login_page', $page_id); // Hide from menus/search update_post_meta($page_id, '_wp_page_template', 'default'); update_post_meta($page_id, BASE . 'exclude_from_search', true); } } } public function getLoginPage():int|false { return (int)get_option(BASE.'login_page'); } public function isLoginPage():bool { return is_page($this->getLoginPage()); } public static function isLogin():bool { $self = new self; return $self->isLoginPage(); } /************************************************************************** TOKEN & MESSAGE HANDLERS Extensible by other classes **************************************************************************/ public function registerTokenHandler(string $token_key, callable $handler, int $priority = 10): void { if (!isset($this->tokenHandlers[$priority])) { $this->tokenHandlers[$priority] = []; } $this->tokenHandlers[$priority][$token_key] = $handler; ksort($this->tokenHandlers); } public function registerMessageHandler(string $type, callable $handler, ?callable $condition = null): void { $this->messageHandlers[$type] = [ 'handler' => $handler, 'condition' => $condition ]; } protected function registerDefaultHandlers(): void { // Invitation handler if ($this->siteFeatures->has('invitations')) { $this->registerTokenHandler('invite', function($token, $email, $user_id) { if (isset($_POST['invite_token'])) { JVB()->routes('invites')->acceptInvitation( sanitize_text_field($_POST['invite_token']), sanitize_email($_POST['invite_email']), $user_id ); } }); $this->registerMessageHandler('invitation', function() { $data = JVB()->routes('invites')->verifyInvitation( sanitize_text_field($_GET['invite']), sanitize_email($_GET['email']) ); $name = $data->name; $inviters = json_decode($data->inviters, true); $names = []; foreach ($inviters as $inviter) { $artist = jvbContentFromUser((int)$inviter['user_id']); $names[] = ($artist['name'] === '') ? $artist['display_name'] : $artist['name']; } $message = (count($names) > 1) ? 'are already here, and have invited you to join in!' : ' is already here, and invited you to join in!'; return '

Join the Scene, '.$name.'

'.jvbCommaList($names).$message.'

'; }, function() { return isset($_GET['invite']) && isset($_GET['email']); } ); } // List sharing handler (Favourites) if ($this->siteFeatures->has('favourites')) { $this->registerTokenHandler('list_token', function($token, $email, $user_id) { if (!empty($_GET['list_token']) && !empty($_GET['email'])) { JVB()->routes('favourites')->acceptListInvitation( sanitize_text_field($_GET['list_token']), sanitize_email($_GET['email']), $user_id ); } }); $this->registerMessageHandler('favourites', function() { return '

'.(JVB_LOGIN['login_from_favourite_header'] ?? 'Save your Favourites').'

'; }, function() { return isset($_GET['type']) && $_GET['type'] === 'favourites'; } ); } // Referral handler - FIXED VERSION $this->registerTokenHandler('referral_code', function($code, $email, $user_id) { // $code is already sanitized from processTokenHandlers if (session_status() === PHP_SESSION_NONE) { session_start(); } $_SESSION[BASE . 'referral_code'] = $code; setcookie( BASE . 'referral_code', $code, time() + (86400 * 30), '/' ); }, 5); } protected function initMagicLinkSupport(): void { if (!Features::forSite()->has('magicLink')) { return; } $this->magicLink = new MagicLinkManager(); } /********************************************************************* RENDERING *********************************************************************/ public function renderLoginPage(string $template):string { if (!$this->isLoginPage()) { return $template; } $this->setup(); ob_start(); jvbInlineStyles('nav'); jvbInlineStyles('dash'); jvbInlineStyles('forms'); $this->customStyles(); $this->renderHeader(); $this->renderForms(); $this->renderFooter(); echo ob_get_clean(); return ''; } protected function setup():void { if (array_key_exists('action', $_GET)) { switch ($_GET['action']){ case 'lostpassword': case 'retrievepassword': // Alias $action = 'lostpassword'; break; case 'rp': case 'resetpass': $action = 'resetpass'; break; default: $action = $_GET['action']; } } else { $action = 'login'; } $this->action = $action; $this->setupLabels(); $this->setupFields(); $this->setupTitle(); } protected function setupTitle():void { switch ($this->action) { case 'lostpassword': $title = 'Lost Your Password?'; break; case 'resetpass': $title = 'Reset Your Password'; break; case 'register': $title = 'Create Your Account'; break; default: $title = 'Log In To Your Account'; } $this->title = $title; } protected function customStyles():void { $logo = get_theme_mod('custom_logo'); $small = $large = ''; if ($logo) { $small = wp_get_attachment_image_src($logo, 'medium')[0]; $large = wp_get_attachment_image_src($logo, 'large')[0]; } echo ''; } protected function renderForms():void { $form = $this->action.'form'; ?>

labels['title']?>

labels['description'] ?>
action, '_wpnonce'); ?> addHiddenTokenFields(); foreach ($this->fields as $name => $config) { $this->metaForm->render($name, '', $config); } $this->maybeTurnstile(); ?>
maybeMagicLink(); ?>
labels['extra'])) { echo '
'; foreach($this->labels['extra'] as $extra) { echo '

'.$extra.'

'; } echo '
'; } else if ($this->labels['extra']!=='') { echo '
'.$this->labels['extra'].'
'; } ?>
action) { case 'login': ?> Forgot Password? Create Account Or Login Forgot Password? Login Instead Create Account
> <?= $this->title ?> | <?= get_bloginfo('name') ?>
'. jvbIcon('light', ['title'=> 'Light Mode']). jvbIcon('dark', ['title'=>'Dark Mode']). ''; ?>

0) { $url = wp_get_attachment_image_url( $icon); if ($url) { $out = ''; } } if ($out == '') { $out =jvbIcon('home'); } ?>

tokenHandlers as $priority => $handlers) { foreach ($handlers as $token_key => $handler) { if (isset($_GET[$token_key])) { $value = sanitize_text_field($_GET[$token_key]); echo ''; } } } if (isset($_GET['email'])) { echo ''; } } /************************************************************************* AJAX HANDLERS *************************************************************************/ public function handleAjaxLogin(): void { check_ajax_referer('jvb_login', '_wpnonce'); // Rate limiting if (!$this->checkAjaxRateLimit('login')) { wp_send_json_error([ 'message' => 'Too many attempts. Please wait a moment.', 'code' => 'rate_limit' ], 429); } // Duplicate submission check if (!$this->checkRequestId()) { wp_send_json_error([ 'message' => 'Duplicate request detected', 'code' => 'duplicate_request' ], 409); } $email = sanitize_email($_POST['user_email'] ?? ''); $password = $_POST['user_password'] ?? ''; $remember = !empty($_POST['remember_me']); if (empty($email) || empty($password)) { wp_send_json_error([ 'message' => 'Please fill in all fields', 'field' => empty($email) ? 'user_email' : 'user_password', 'code' => 'missing_fields' ]); } // Verify Turnstile if enabled if (!$this->verifyTurnstile()) { wp_send_json_error([ 'message' => 'Security verification failed', 'code' => 'turnstile_failed' ]); } $user = get_user_by('email', $email); if (!$user) { wp_send_json_error([ 'message' => 'Unknown email address', 'field' => 'user_email', 'code' => 'invalid_email' ]); } $user = wp_authenticate($user->user_login, $password); if (is_wp_error($user)) { wp_send_json_error([ 'message' => $user->get_error_message(), 'field' => 'user_password', 'code' => $user->get_error_code() ]); } wp_clear_auth_cookie(); wp_set_current_user($user->ID); wp_set_auth_cookie($user->ID, $remember); do_action('wp_login', $user->user_login, $user); $redirect = $_POST['redirect_to'] ?? home_url('/dash'); wp_send_json_success(['redirect' => $redirect]); } public function handleAjaxRegister(): void { check_ajax_referer('jvb_register', '_wpnonce'); // Rate limiting if (!$this->checkAjaxRateLimit('register')) { wp_send_json_error([ 'message' => 'Too many attempts. Please wait a moment.', 'code' => 'rate_limit' ], 429); } // Duplicate submission check if (!$this->checkRequestId()) { wp_send_json_error([ 'message' => 'Duplicate request detected', 'code' => 'duplicate_request' ], 409); } // Verify Turnstile if (!$this->verifyTurnstile()) { wp_send_json_error([ 'message' => 'Security verification failed', 'code' => 'turnstile_failed' ]); } $name = sanitize_text_field($_POST['name'] ?? ''); $email = sanitize_email($_POST['email'] ?? ''); $user_type = sanitize_text_field($_POST['user_select'] ?? 'subscriber'); // Spam prevention - if subscriber is selected and there are other options if ($user_type === 'subscriber' && count(JVB_USER) > 0) { $registerable = array_filter(JVB_USER, fn($config) => $config['can_register'] ?? false); if (!empty($registerable)) { wp_send_json_error([ 'message' => 'Please select a valid account type', 'field' => 'user_select', 'code' => 'invalid_user_type' ]); } } // Validate fields if (empty($name)) { wp_send_json_error([ 'message' => 'Name is required', 'field' => 'name', 'code' => 'missing_name' ]); } if (empty($email)) { wp_send_json_error([ 'message' => 'Email is required', 'field' => 'email', 'code' => 'missing_email' ]); } // Check if role can register if ($user_type !== 'subscriber') { if (!isset(JVB_USER[$user_type]) || empty(JVB_USER[$user_type]['can_register'])) { wp_send_json_error([ 'message' => 'Invalid account type', 'field' => 'user_select', 'code' => 'invalid_user_type' ]); } } // Check if email exists if (email_exists($email)) { wp_send_json_error([ 'message' => 'Email already registered', 'field' => 'email', 'code' => 'duplicate_email' ]); } // Create user $user_id = wp_create_user($email, wp_generate_password(), $email); if (is_wp_error($user_id)) { wp_send_json_error([ 'message' => $user_id->get_error_message(), 'code' => 'user_creation_failed' ]); } // Update user data wp_update_user([ 'ID' => $user_id, 'display_name' => $name, 'first_name' => $name ]); // Set role $user = new WP_User($user_id); if ($user_type === 'subscriber') { $user->set_role('subscriber'); } else { $role = JVB_USER[$user_type]['role'] ?? 'subscriber'; $user->set_role($role); // Check if needs approval if (Features::forMembership()->has('memberVerified') && in_array($role, JVB_MEMBERSHIP['memberVerified'] ?? [])) { $user->add_cap('skip_moderation', false); update_user_meta($user_id, BASE . 'pending_approval', true); } } // Save additional fields update_user_meta($user_id, BASE . 'user_type', $user_type); // Process additional fields from form foreach ($_POST as $key => $value) { if (in_array($key, ['name', 'email', 'action', '_wpnonce', 'request_id', 'user_select'])) { continue; } update_user_meta($user_id, BASE . $key, sanitize_text_field($value)); } // Handle token handlers $this->processTokenHandlers($user_id, $email); // Send welcome email with password setup link $this->sendWelcomeEmail($user_id); // Trigger registration action for other systems do_action('jvbAfterUserRegistration', $user_id, $user_type, $_POST); wp_send_json_success([ 'message' => 'Registration successful! Check your email.', 'title' => $this->labels['successTitle'] ?? 'Success!', 'description' => $this->labels['successDescription'] ?? 'Check your email for next steps', 'user_id' => $user_id // Important for file upload dependencies! ]); } public function handleAjaxLostPassword(): void { check_ajax_referer('jvb_lostpassword', '_wpnonce'); // Rate limiting if (!$this->checkAjaxRateLimit('lostpassword')) { wp_send_json_error([ 'message' => 'Too many attempts. Please wait a moment.', 'code' => 'rate_limit' ], 429); } $email = sanitize_email($_POST['user_email'] ?? ''); if (empty($email)) { wp_send_json_error([ 'message' => 'Email required', 'field' => 'user_email', 'code' => 'missing_email' ]); } // Verify Turnstile if (!$this->verifyTurnstile()) { wp_send_json_error([ 'message' => 'Security verification failed', 'code' => 'turnstile_failed' ]); } // Use WordPress's built-in function $result = retrieve_password($email); if (is_wp_error($result)) { wp_send_json_error([ 'message' => $result->get_error_message(), 'code' => $result->get_error_code() ]); } wp_send_json_success(['message' => 'Check your email for reset link']); } public function handleAjaxResetPassword(): void { check_ajax_referer('jvb_resetpass', '_wpnonce'); // Rate limiting if (!$this->checkAjaxRateLimit('resetpass')) { wp_send_json_error([ 'message' => 'Too many attempts. Please wait a moment.', 'code' => 'rate_limit' ], 429); } $key = sanitize_text_field($_POST['key'] ?? $_GET['key'] ?? ''); $login = sanitize_text_field($_POST['login'] ?? $_GET['login'] ?? ''); $pass1 = $_POST['pass1'] ?? ''; $pass2 = $_POST['pass2'] ?? ''; if (empty($key) || empty($login)) { wp_send_json_error([ 'message' => 'Invalid reset link', 'code' => 'invalid_key' ]); } if (empty($pass1) || empty($pass2)) { wp_send_json_error([ 'message' => 'Please enter a password', 'field' => empty($pass1) ? 'pass1' : 'pass2', 'code' => 'missing_password' ]); } if ($pass1 !== $pass2) { wp_send_json_error([ 'message' => 'Passwords do not match', 'field' => 'pass2', 'code' => 'password_mismatch' ]); } // Verify reset key $user = check_password_reset_key($key, $login); if (is_wp_error($user)) { wp_send_json_error([ 'message' => 'Invalid or expired reset link', 'code' => 'invalid_key' ]); } // Reset password reset_password($user, $pass1); wp_send_json_success([ 'message' => 'Password reset successfully', 'redirect' => home_url('/login') ]); } /********************************************************************** TOKEN PROCESSING **********************************************************************/ protected function processTokenHandlers(int $user_id, string $email): void { foreach ($this->tokenHandlers as $priority => $handlers) { foreach ($handlers as $token_key => $handler) { if (isset($_POST[$token_key]) || isset($_GET[$token_key])) { $token_value = $_POST[$token_key] ?? $_GET[$token_key]; call_user_func($handler, sanitize_text_field($token_value), $email, $user_id); } } } } /*********************************************************************** EMAIL SENDING ***********************************************************************/ protected function sendWelcomeEmail(int $user_id): void { $user = get_userdata($user_id); if (!$user) { return; } // Generate password reset key $key = get_password_reset_key($user); if (is_wp_error($key)) { error_log('Failed to generate password reset key: ' . $key->get_error_message()); return; } $reset_url = add_query_arg([ 'action' => 'rp', 'key' => $key, 'login' => rawurlencode($user->user_login) ], home_url('/login')); $subject = $this->labels['email'] ?? 'Welcome to ' . get_bloginfo('name'); $message = '

Welcome, ' . esc_html($user->display_name) . '!

'; $message .= '

Your account has been created. Click the button below to set your password and get started:

'; $message .= jvbMailButton($reset_url, 'Set Your Password'); $message .= '

This link expires in 24 hours.

'; $this->emailManager->sendEmail($user->user_email, $subject, $message); } /************************************************************************* * SECURITY & VALIDATION *************************************************************************/ protected function checkAjaxRateLimit(string $action): bool { return $this->rateLimiter->checkLimit($action); } protected function checkRequestId(): bool { $request_id = $_POST['request_id'] ?? ''; if (empty($request_id)) { return true; // No request_id provided, allow (for backward compat) } $cache_key = 'request_' . $request_id; if (get_transient($cache_key)) { return false; // Duplicate request } // Store request ID for 1 minute to prevent duplicates set_transient($cache_key, true, 60); return true; } protected function maybeTurnstile(): void { if (!Features::hasIntegration('cloudflare')) { return; } JVB()->connect('cloudflare')->renderTurnstile(); } protected function maybeTurnstileScripts(): void { if (!Features::hasIntegration('cloudflare')) { return; } JVB()->connect('cloudflare')->enqueueTurnstileScripts(); } protected function verifyTurnstile(): bool { if (!Features::hasIntegration('cloudflare')) { return true; // Not enabled, pass verification } $token = $_POST['cf-turnstile-response'] ?? ''; if (empty($token)) { return false; } return JVB()->connect('cloudflare')->verifyTurnstile($token); } /************************************************************************ LABELS & UI ************************************************************************/ protected function setupLabels(): void { $default = $this->getDefaultLabels(); $this->labels = apply_filters('jvbLoginLabels', $default, $_GET); foreach (['description', 'footer', 'extra'] as $location) { $text = (!is_array($this->labels[$location])) ? [$this->labels[$location]] : $this->labels[$location]; if (!empty($text)) { $this->labels[$location] = '
'; foreach ($text as $d) { $this->labels[$location] .= '

'.$d.'

'; } $this->labels[$location] .= '
'; } } } protected function getDefaultLabels(): array { switch ($this->action) { case 'register': return [ 'title' => JVB_LOGIN['register']['title'] ?? 'Create Your Account', 'description' => JVB_LOGIN['register']['description'] ?? [], 'extra' => JVB_LOGIN['register']['extra'] ?? [], 'footer' => JVB_LOGIN['register']['footer'] ?? '', 'email' => JVB_LOGIN['register']['email']['subject'] ?? '['.get_bloginfo('name').'] Finish Creating Your Account', 'submit' => JVB_LOGIN['register']['submit'] ?? 'Create Account', 'successTitle' => JVB_LOGIN['register']['success']['title'] ?? 'Success!', 'successDescription' => JVB_LOGIN['register']['success']['description'] ?? ['See your email for next steps','(Check your spam folder if you cannot find it after a couple minutes.)'], ]; case 'lostpassword': return [ 'title' => JVB_LOGIN['forgot_password']['title'] ?? 'Reset Password', 'description' => JVB_LOGIN['forgot_password']['description'] ?? [], 'extra' => JVB_LOGIN['forgot_password']['extra'] ?? [], 'footer' => JVB_LOGIN['forgot_password']['footer'] ?? '', 'submit' => JVB_LOGIN['forgot_password']['submit'] ?? 'Send Reset Link', 'successTitle' => JVB_LOGIN['forgot_password']['success']['title'] ?? 'Success!', 'successDescription' => JVB_LOGIN['forgot_password']['success']['description'] ?? ['Check your email for reset instructions'], ]; case 'resetpass': return [ 'title' => JVB_LOGIN['reset_pass']['title'] ?? 'Reset Your Password', 'description' => JVB_LOGIN['reset_pass']['description'] ?? [], 'extra' => JVB_LOGIN['reset_pass']['extra'] ?? [], 'footer' => JVB_LOGIN['reset_pass']['footer'] ?? '', 'submit' => JVB_LOGIN['reset_pass']['submit'] ?? 'Reset Password', ]; case 'login': default: return [ 'title' => JVB_LOGIN['login']['title'] ?? 'Sign in', 'description' => JVB_LOGIN['login']['description'] ?? [], 'extra' => JVB_LOGIN['login']['extra'] ?? [], 'footer' => JVB_LOGIN['login']['footer'] ?? '', 'submit' => JVB_LOGIN['login']['submit'] ?? 'Sign In', ]; } } protected function maybeMagicLink(): void { if (!$this->magicLink || !in_array($this->action, ['login', 'lostpassword'])) { return; } ?> isLoginPage()) { return; } $this->maybeTurnstileScripts(); wp_enqueue_script('jvb-form'); $script = " document.addEventListener('DOMContentLoaded', () => { const form = document.querySelector('.login form'); if (form && window.jvbForm) { let controller = new window.jvbForm(); controller.registerForm(form, { autosave: false, endpoint: false }); } else if (form && !window.jvbForm) { console.error('jvbForm not loaded'); } });"; wp_add_inline_script('jvb-form', $script); } /************************************************************************* SUCCESS HANDLING *************************************************************************/ public function handleSuccessfulLogin(string $username, WP_User $user): void { if (isOurPeople() && !user_can($user, 'manage_options')) { wp_redirect(get_home_url(null, '/dash')); exit; } } /** * Handle login errors */ protected function handleLoginError(WP_Error $error): void { $login_url = wp_login_url(); $login_url = add_query_arg('login_error', urlencode($error->get_error_code()), $login_url); if (isset($_REQUEST['redirect_to'])) { $login_url = add_query_arg('redirect_to', urlencode($_REQUEST['redirect_to']), $login_url); } wp_safe_redirect($login_url); exit; } } // Initialize the login manager new LoginManager();