From 48721c85ebcfa973ee81719d2467ca80e4253dc9 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Fri, 01 May 2026 17:30:03 +0000
Subject: [PATCH] =Edmonton Ink hard test begins! Real testing of the managers and reset routes will commence. So far, just ensuring our classes are all loaded correctly: Site() and its sub-classes Membership, Login, etc. Care should be taken to load conditionally on 'init', as we finish defining most settings by 'plugins_loaded' at priority 5

---
 inc/managers/LoginManager.php |  910 +++++++++++++++++---------------------------------------
 1 files changed, 285 insertions(+), 625 deletions(-)

diff --git a/inc/managers/LoginManager.php b/inc/managers/LoginManager.php
index dd12a81..ac51292 100644
--- a/inc/managers/LoginManager.php
+++ b/inc/managers/LoginManager.php
@@ -1,13 +1,11 @@
 <?php
 namespace JVBase\managers;
 
-use JVBase\blocks\CustomBlocks;
+use JVBase\base\Site;
 use JVBase\forms\TaxonomySelector;
-use JVBase\meta\MetaManager;
-use JVBase\meta\MetaForm;
-use JVBase\managers\AjaxRateLimiter;
-use JVBase\utility\Features;
-use WP_Error;
+use JVBase\meta\Form;
+
+use JVBase\registrar\Registrar;use WP_Error;
 use WP_User;
 
 if (!defined('ABSPATH')) {
@@ -16,11 +14,8 @@
 
 class LoginManager
 {
-	protected Features $siteFeatures;
-	protected ?MagicLinkManager $magicLink = null;
-	protected ?MetaForm $metaForm = null;
-	protected EmailManager $emailManager;
-	protected AjaxRateLimiter $rateLimiter;
+	protected ?Form $form = null;
+	protected Cache $cache;
 
 
 	protected array $forms =[];
@@ -30,7 +25,6 @@
 	protected string $title = '';
 
 	// Token handlers registry
-	protected array $tokenHandlers = [];
 	protected array $messageHandlers = [];
 
 	private array $allowed_file_types = [
@@ -43,16 +37,10 @@
 
 	public function __construct()
 	{
-		$this->siteFeatures = Features::forSite();
-		$this->metaForm = new MetaForm();
-		$this->emailManager = new EmailManager();
-		$this->rateLimiter = new AjaxRateLimiter();
-
-		// Register default token handlers
-		$this->registerDefaultHandlers();
+		$this->cache = Cache::for('login');
 
 		// Initialize magic link support if enabled
-		if ($this->siteFeatures->has('magicLink')) {
+		if (Site::has('magicLink')) {
 			$this->initMagicLinkSupport();
 		}
 
@@ -66,19 +54,23 @@
 
         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);
 
+		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
 	**************************************************************************/
@@ -88,12 +80,17 @@
 	 */
 	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');
+		$custom_login_page = home_url('/login/');
 		$query_args = $_GET;
 
 		// Remove WordPress internal args
@@ -115,32 +112,40 @@
 			$select = [];
 			//Basic fields, for any
 			$fields = [
-				'name'	=> [
+				'user_name'	=> [
 					'type'		=> 'text',
 					'required'	=> true,
 					'label'		=> 'Your Name',
 					'placeholder'=> 'Mister Meseeks'
 				],
-				'email'	=> [
+				'user_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'] ?? '';
+			if (Site::has('referrals')) {
+				$fields['referral_code'] = [
+					'type'	=> 'text',
+					'required'=> false,
+					'label'	=> 'Referral Code',
+					'hint'	=> 'Have a referral code? Paste it here!'
+				];
+			}
+			$canRegister = Registrar::getFeatured('can_register', 'user');
+			if (!empty($canRegister)) {
+				foreach ($canRegister as $role) {
+					$registrar = Registrar::getInstance($role);
+					$config = $registrar->getConfig('register');
+					$icon = $registrar->getIcon('user');
 					$icon = ($icon !== '') ? jvbIcon($icon) : '';
-					$select[$slug] = '<span class="label">'.$icon.$config['label'].'</span><span class="text">'.$config['register']['text']??''.'</span>';
-					if (!empty($config['register']['fields']??[])){
-						foreach ($config['register']['fields'] as $field) {
+					$select[$role] = '<span class="label">'.$icon.$registrar->getSingular().'</span><span class="text">'.$config['description']??Site::login()->getDescription('register')??''.'</span>';
+					if (!empty($config['fields'])){
+						foreach ($config['fields'] as $field) {
 							$field['condition'] = [
 								'field'	=> 'user_select',
-								'value'	=> $slug,
+								'value'	=> $role,
 								'operator'	=> '=='
 							];
 							$fields[] = $field;
@@ -184,6 +189,7 @@
 				$fields = $this->getRegistrationFormFields();
 				break;
 			case 'lostpassword':
+			case 'magic':
 				$fields = [
 					'user_email' => [
 						'type' => 'email',
@@ -216,12 +222,14 @@
 						'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' => [
@@ -281,6 +289,40 @@
 			}
 		}
 	}
+	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');
@@ -297,118 +339,13 @@
 		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 '<h2>Join the Scene, '.$name.'</h2>
-                    <p style="text-align:center;">'.jvbCommaList($names).$message.'</p>';
-				},
-				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 '<h2>'.(JVB_LOGIN['login_from_favourite_header'] ?? 'Save your Favourites').'</h2>';
-				},
-				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')) {
+		if (!Site::has('magicLink')) {
 			return;
 		}
-		$this->magicLink = new MagicLinkManager();
 	}
 
-
-
 	/*********************************************************************
 		RENDERING
 	*********************************************************************/
@@ -418,6 +355,18 @@
 			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');
@@ -428,11 +377,10 @@
 		$this->renderForms();
 		$this->renderFooter();
 
-		echo  ob_get_clean();
-		return '';
+		return ob_get_clean();
 	}
 
-	protected function setup():void
+	protected function getAction():string
 	{
 		if (array_key_exists('action', $_GET)) {
 			switch ($_GET['action']){
@@ -450,8 +398,17 @@
 		} else {
 			$action = 'login';
 		}
+		return $action;
+	}
 
-		$this->action = $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();
@@ -478,11 +435,9 @@
 	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];
-
+			$small = wp_get_attachment_image_src($logo, 'medium')[0]??'';
+			$large = wp_get_attachment_image_src($logo, 'large')[0]??'';
 		}
 		echo '<style>
 			.login header,
@@ -496,6 +451,12 @@
 				justify-content: center;
 				position: relative;
 			}
+			.login .fstatus.fstatus {
+				--wrap: nowrap;
+				top:0;
+				bottom:unset;
+				right: 0;
+			}
 			.login main::before {
 				background-size: 20vw;
 				inset: 0;
@@ -567,30 +528,31 @@
 
 	protected function renderForms():void
 	{
-
 		$form = $this->action.'form';
-
 		?>
 		<section class="login-box col btw">
 			<h1><?=$this->labels['title']?></h1>
 			<?= $this->labels['description'] ?>
+
+
 			<form name="<?=$form?>" method="post" data-action="jvb_<?=$this->action?>">
+				<?= jvbFormStatus() ?>
 				<?php wp_nonce_field('jvb_'.$this->action, '_wpnonce'); ?>
 				<input type="hidden" name="action" value="jvb_<?=$this->action?>">
 				<input type="hidden" name="redirect_to" value="<?= esc_attr($_GET['redirect_to'] ?? '') ?>">
 				<input type="hidden" name="request_id" value="<?= wp_generate_password(16, false) ?>">
-
+				<?= ($this->action === 'magic') ? '<input type="hidden" name="type" value="login">' : '' ?>
 				<?php
-				$this->addHiddenTokenFields();
+				do_action('jvb_add_token_inputs', $this->action);
 
 				foreach ($this->fields as $name => $config) {
-					$this->metaForm->render($name, '', $config);
+					echo Form::render($name, '', $config);
 				}
 
 				$this->maybeTurnstile();
 				 ?>
 				 <div class="row btw nowrap">
-					<button type="submit" class="button button-primary button-large">Log In</button>
+					<button type="submit" class="button button-primary button-large"><?=$this->labels['submit']?></button>
 					<?php $this->maybeMagicLink(); ?>
 				</div>
 			</form>
@@ -620,7 +582,8 @@
 						<a href="<?= add_query_arg('action', 'lostpassword', get_the_permalink()) ?>">Forgot Password?</a>
 						<?php
 						break;
-					case 'lostpassword': ?>
+					case 'lostpassword':
+					case 'magic': ?>
 						<a href="<?= get_the_permalink() ?>">Login Instead</a>
 						<a href="<?= add_query_arg('action', 'register', get_the_permalink()) ?>">Create Account</a>
 						<?php
@@ -660,9 +623,10 @@
             $checked = (is_user_logged_in() && current_user_can('prefers_dark_theme', true)) ? ' checked' : '';
             $title = ($checked == '') ? 'Toggle Dark Mode' : 'Toggle Light Mode';
             echo '<label title="'.$title.'" id="theme-switch" class="toggle-switch" for="theme-switcher">
-                    <input class="theme-switch row" id="theme-switcher" type="checkbox"'.$checked.' data-setting="theme" data-theme role="switch" name="dark-mode"><span class="slider">'.
-					jvbIcon('light', ['title'=> 'Light Mode']).
-					jvbIcon('dark', ['title'=>'Dark Mode']).
+    				<span class="screen-reader-text">Toggle dark mode</span>
+                    <input class="theme-switch row" id="theme-switcher" name="theme-switcher" type="checkbox"'.$checked.' data-setting="theme" data-theme name="dark-mode" aria-label="Toggle dark mode"><span class="slider">'.
+					jvbIcon('sun-dim', ['title'=> 'Light Mode']).
+					jvbIcon('moon', ['title'=>'Dark Mode']).
 					'</span></label>';
             ?>
             <p class="title">
@@ -677,7 +641,7 @@
 						}
 					}
 					if ($out == '') {
-						$out =jvbIcon('home');
+						$out =jvbIcon('house');
 					}
                     ?><?= $out ?>
                 </a>
@@ -709,339 +673,6 @@
         <?php
     }
 
-	protected function addHiddenTokenFields(): void
-	{
-		foreach ($this->tokenHandlers as $priority => $handlers) {
-			foreach ($handlers as $token_key => $handler) {
-				if (isset($_GET[$token_key])) {
-					$value = sanitize_text_field($_GET[$token_key]);
-					echo '<input type="hidden" name="' . esc_attr($token_key) . '" value="' . esc_attr($value) . '">';
-				}
-			}
-		}
-
-		if (isset($_GET['email'])) {
-			echo '<input type="hidden" name="token_email" value="' . esc_attr(sanitize_email($_GET['email'])) . '">';
-		}
-	}
-
-	/*************************************************************************
-		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
 	**********************************************************************/
@@ -1057,47 +688,10 @@
 		}
 	}
 
-
-	/***********************************************************************
-		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 = '<h2>Welcome, ' . esc_html($user->display_name) . '!</h2>';
-		$message .= '<p>Your account has been created. Click the button below to set your password and get started:</p>';
-		$message .= jvbMailButton($reset_url, 'Set Your Password');
-		$message .= '<p>This link expires in 24 hours.</p>';
-
-		$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'] ?? '';
@@ -1117,7 +711,7 @@
 
 	protected function maybeTurnstile(): void
 	{
-		if (!Features::hasIntegration('cloudflare')) {
+		if (!Site::hasIntegration('cloudflare')) {
 			return;
 		}
 		JVB()->connect('cloudflare')->renderTurnstile();
@@ -1125,7 +719,7 @@
 
 	protected function maybeTurnstileScripts(): void
 	{
-		if (!Features::hasIntegration('cloudflare')) {
+		if (!Site::hasIntegration('cloudflare')) {
 			return;
 		}
 		JVB()->connect('cloudflare')->enqueueTurnstileScripts();
@@ -1133,7 +727,7 @@
 
 	protected function verifyTurnstile(): bool
 	{
-		if (!Features::hasIntegration('cloudflare')) {
+		if (!Site::hasIntegration('cloudflare')) {
 			return true; // Not enabled, pass verification
 		}
 
@@ -1151,97 +745,67 @@
 	protected function setupLabels(): void
 	{
 		$default = $this->getDefaultLabels();
-		$this->labels = apply_filters('jvbLoginLabels', $default, $_GET);
+		$default = 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] = '<div class="'.$location.'">';
-				foreach ($text as $d) {
-					$this->labels[$location] .= '<p>'.$d.'</p>';
+		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;
 				}
-				$this->labels[$location] .= '</div>';
 			}
 		}
+
+		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] = '<div class="'.$location.'">';
+				foreach ($text as $d) {
+					$default[$location] .= '<p>'.$d.'</p>';
+				}
+				$default[$location] .= '</div>';
+			}
+		}
+		$this->labels = $default;
 	}
 
 	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.)'],
-				];
+				return Site::login()->getLabels('register');
 			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'],
-				];
+				return Site::login()->getLabels('lostPassword');
 			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 'rp':
+				return Site::login()->getLabels('resetPassword');
+			case 'logout':
+				return Site::login()->getLabels('logout');
+			case 'magic':
+				return Site::login()->getLabels('magic');
 			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',
-				];
+				return Site::login()->getLabels('login');
 		}
 	}
 
 	protected function maybeMagicLink(): void
 	{
-		if (!$this->magicLink || !in_array($this->action, ['login', 'lostpassword'])) {
+		if (!JVB()->magicLink() || !in_array($this->action, ['login', 'lostpassword'])) {
 			return;
 		}
 		?>
-		<button type="button" id="magic-link-btn" class="button button-secondary button-large">
-			<?= jvbIcon('email', ['size' => 20]); ?>
-			Get Login Link
-		</button>
-		<script type="text/javascript">
-			document.getElementById('magic-link-btn')?.addEventListener('click', function(e) {
-				e.preventDefault();
-				const email = document.querySelector('input[name="user_email"]')?.value;
-				if (!email) {
-					alert('Please enter your email address first');
-					return;
-				}
-
-				fetch('<?= rest_url('jvb/v1/magic-link'); ?>', {
-					method: 'POST',
-					headers: {
-						'Content-Type': 'application/json',
-						'X-WP-Nonce': '<?= wp_create_nonce('wp_rest') ?>'
-					},
-					body: JSON.stringify({ email: email, type: 'login' })
-				})
-				.then(r => r.json())
-				.then(data => {
-					alert(data.success ? 'Check your email!' : (data.message || 'Failed to send link'));
-				});
-			});
-		</script>
+		<a class="button" href="<?= add_query_arg('action', 'magic', wp_login_url()) ?>" title="Email yourself a link to log you in auto-magically!">
+			<?= jvbIcon('magic-wand'); ?>
+			Magic Link
+		</a>
 		<?php
 	}
 
@@ -1250,28 +814,119 @@
 		SCRIPTS
 	************************************************************************/
 	public function enqueueScripts(): void
-	{
-		if (!$this->isLoginPage()) {
-			return;
-		}
+{
+    if (!$this->isLoginPage()) {
+        return;
+    }
 
-		$this->maybeTurnstileScripts();
-		wp_enqueue_script('jvb-form');
+    $this->maybeTurnstileScripts();
+    wp_enqueue_script('jvb-form');
+    $action = $this->getAction();
 
-		$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
+    $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 = <?= json_encode($has_turnstile) ?>;
+		const redirectTo = <?= json_encode($redirect_to) ?>;
+
+		window.auth.subscribe(event => {
+			if (event === 'auth-loaded') {
+				const form = document.querySelector('.login form');
+				if (!form || !window.jvbForm) return;
+
+				window.jvbForm.registerForm(form, {
+					endpoint: '<?= $action ?>',
+					showStatus: false,
+					cache: false,
 				});
-			} else if (form && !window.jvbForm) {
-				console.error('jvbForm not loaded');
-			}
-		});";
 
+				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/<?= $action ?>`, {
+							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;
+						});
+					}
+				});
+			}
+		});
+	});
+
+	<?php
+		$script = ob_get_clean();
 		wp_add_inline_script('jvb-form', $script);
 	}
 
@@ -1302,6 +957,11 @@
 		wp_safe_redirect($login_url);
 		exit;
 	}
+
+	public function saveRegistrationFields(int $user_id, array $userdata):void
+	{
+
+	}
 }
 
 // Initialize the login manager

--
Gitblit v1.10.0