cache = Cache::for('login'); // Initialize magic link support if enabled if (Site::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); // Login success handling add_action('wp_login', [$this, 'handleSuccessfulLogin'], 10, 2); add_filter('lostpassword_url', [$this, 'resetPasswordUrl'], 10, 2); add_filter( 'login_url', [$this, 'loginUrl'], 10, 3 ); add_filter( 'logout_url', [$this, 'logoutUrl'], 10, 2 ); // Allow other features to register handlers do_action('jvbLoginManagerInit', $this); add_action('user_register', array($this, 'saveRegistrationFields'), 999, 2); add_filter('the_seo_framework_sitemap_exclude_ids', [$this, 'excludeLoginSitemap'], 10, 1); } public function excludeLoginSitemap(array $ids): array { $ids[] = $this->getLoginPage(); return $ids; } /************************************************************************** * SETUP & CONFIGURATION **************************************************************************/ /** * Redirect wp-login.php to custom login page */ public function redirectToCustomLogin(): void { // Handle interim login if (isset($_GET['interim-login'])) { // Don't redirect - let WP handle it return; } // 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 = [ 'user_name' => [ 'type' => 'text', 'required' => true, 'label' => 'Your Name', 'placeholder'=> 'Mister Meseeks' ], 'user_email' => [ 'type' => 'email', 'required' => true, 'label' => 'Your Email', 'placeholder'=> 'look@me.com' ] ]; if (Site::has('referrals')) { $fields['referral_code'] = [ 'type' => 'text', 'required'=> false, 'label' => 'Referral Code', 'hint' => 'Have a referral code? Paste it here!' ]; } 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': case 'magic': $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, 'autocomplete' => 'email', 'placeholder' => 'look@me.com', ], 'user_password' => [ 'type' => 'text', 'subtype'=> 'password', 'label' => __('Password', 'jvb'), 'autocomplete' => 'current-password', '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 loginUrl(string $login_url, string $redirect, bool $force_reauth):string { // This will append /custom-login/ to you main site URL as configured in general settings (ie https://domain.com/custom-login/) $login_url = site_url( '/login/', 'login' ); if ( ! empty( $redirect ) ) { $login_url = add_query_arg( 'redirect_to', urlencode( $redirect ), $login_url ); } if ( $force_reauth ) { $login_url = add_query_arg( 'reauth', '1', $login_url ); } return $login_url; } public function logoutUrl(string $logout_url, string $redirect): string { // Build custom logout URL $logout_url = site_url('/login/', 'login'); $logout_url = add_query_arg('action', 'logout', $logout_url); if (!empty($redirect)) { $logout_url = add_query_arg('redirect_to', urlencode($redirect), $logout_url); } // Add nonce for security return wp_nonce_url($logout_url, 'log-out'); } public function resetPasswordUrl(string $url, string $redirect):string { error_log('reset Password Url:'.print_r($url, true)); error_log('reset password redirect: '.print_r($redirect, true)); return str_replace('wp_login.php', 'login/', $url); } 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(); } protected function initMagicLinkSupport(): void { if (!Site::has('magicLink')) { return; } } /********************************************************************* RENDERING *********************************************************************/ public function renderLoginPage(string $template):string { if (!$this->isLoginPage()) { return $template; } $this->setup(); $page = $this->cache->remember( $this->getAction(), function() { return $this->renderPage(); }, 5 ); echo $page; return ''; } protected function renderPage() { ob_start(); jvbInlineStyles('nav'); jvbInlineStyles('dash'); jvbInlineStyles('forms'); $this->customStyles(); $this->renderHeader(); $this->renderForms(); $this->renderFooter(); return ob_get_clean(); } protected function getAction():string { 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'; } return $action; } protected function setup():void { $this->action = $this->getAction(); if ($this->action == 'logout' || array_key_exists('loggedout', $_GET)) { wp_logout(); wp_redirect(esc_attr($_GET['redirect_to'] ?? get_home_url())); exit; } $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'); 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'); ?> action === 'magic') ? '' : '' ?> action); foreach ($this->fields as $name => $config) { echo Form::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') ?>
Toggle dark mode '. jvbIcon('sun-dim', ['title'=> 'Light Mode']). jvbIcon('moon', ['title'=>'Dark Mode']). ''; ?>

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

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); } } } } /************************************************************************* * SECURITY & VALIDATION *************************************************************************/ 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 (!Site::hasIntegration('cloudflare')) { return; } JVB()->connect('cloudflare')->renderTurnstile(); } protected function maybeTurnstileScripts(): void { if (!Site::hasIntegration('cloudflare')) { return; } JVB()->connect('cloudflare')->enqueueTurnstileScripts(); } protected function verifyTurnstile(): bool { if (!Site::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(); $default = apply_filters('jvbLoginLabels', $default, $_GET); if(array_key_exists('type', $_GET) && $_GET['type'] === 'favourites') { if (array_key_exists('favourites', JVB_LOGIN)) { foreach (JVB_LOGIN['favourites'] as $key => $value) { $default[$key] = $value; } } } foreach (['description', 'footer', 'extra'] as $location) { if ($default[$location] === '') { continue; } if (empty($default[$location])) { $default[$location] = ''; continue; } $text = (!is_array($default[$location])) ? [$default[$location]] : $default[$location]; if (!empty($text)) { $default[$location] = '
'; foreach ($text as $d) { $default[$location] .= '

'.$d.'

'; } $default[$location] .= '
'; } } $this->labels = $default; } protected function getDefaultLabels(): array { switch ($this->action) { case 'register': return Site::login()->getLabels('register'); case 'lostpassword': return Site::login()->getLabels('lostPassword'); case 'resetpass': case 'rp': return Site::login()->getLabels('resetPassword'); case 'logout': return Site::login()->getLabels('logout'); case 'magic': return Site::login()->getLabels('magic'); case 'login': default: return Site::login()->getLabels('login'); } } protected function maybeMagicLink(): void { if (!JVB()->magicLink() || !in_array($this->action, ['login', 'lostpassword'])) { return; } ?> Magic Link isLoginPage()) { return; } $this->maybeTurnstileScripts(); wp_enqueue_script('jvb-form'); $action = $this->getAction(); $redirect_to = isset($_GET['redirect_to']) ? esc_url_raw($_GET['redirect_to']) : ''; $has_turnstile = Site::hasIntegration('cloudflare'); ob_start(); ?> document.addEventListener('DOMContentLoaded', async function () { const hasTurnstile = ; const redirectTo = ; window.auth.subscribe(event => { if (event === 'auth-loaded') { const form = document.querySelector('.login form'); if (!form || !window.jvbForm) return; window.jvbForm.registerForm(form, { endpoint: '', showStatus: false, cache: false, }); window.jvbForm.subscribe((event, data) => { if (event === 'form-submit') { const { config } = data; const formElement = config.element; // Collect current form data const formData = new FormData(formElement); const formObject = Object.fromEntries(formData.entries()); let params = new URLSearchParams(window.location.search); if (params.has('key')) { formObject['key'] = params.get('key'); } if (params.has('login')) { formObject['login'] = params.get('login'); } // Add redirect_to from URL if (redirectTo) { formObject.redirect_to = redirectTo; } const submit = formElement.querySelector('[type=submit]'); const oldText = submit.textContent; window.jvbForm.showFormStatus(config.id, 'uploading'); submit.disabled = true; submit.textContent = 'Loading...'; window.auth.fetch(`${jvbSettings.api}auth/`, { method: 'POST', body: JSON.stringify(formObject) }) .then(response => response.json().then(result => ({ response, result }))) .then(({ response, result }) => { if (!response.ok) { window.jvbForm.showFormStatus(config.id, 'error'); window.jvbForm.handleFormError(formElement, result); return; } window.jvbForm.showFormStatus(config.id, 'submitted'); if (result.message) { window.jvbForm.handleFormSuccess(formElement, result); } if (window.auth?.handleLogin && result.auth) { return window.auth.handleLogin(result.auth).then(() => { if (result.redirect) { setTimeout(() => { window.location.href = result.redirect; }, 100); } }); } else if (result.redirect) { setTimeout(() => { window.location.href = result.redirect; }, 100); } }) .catch(error => { console.error('Form submission error:', error); window.jvbForm.showFormStatus(config.id, 'error'); window.jvbForm.handleFormError(formElement, { message: 'Network error. Please check your connection and try again.', code: 'network_error' }); }) .finally(() => { submit.textContent = oldText; submit.disabled = false; }); } }); } }); }); 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; } public function saveRegistrationFields(int $user_id, array $userdata):void { } } // Initialize the login manager new LoginManager();