From aeb5a13bfa203281aaa5573e19fe5aa6ac012152 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Fri, 02 Jan 2026 06:03:55 +0000
Subject: [PATCH] Merge branch 'main' of https://github.com/jakevdwerf/jvb
---
inc/managers/LoginManager.php | 809 ++++++++++++++++++---------------------------------------
1 files changed, 256 insertions(+), 553 deletions(-)
diff --git a/inc/managers/LoginManager.php b/inc/managers/LoginManager.php
index dd12a81..2e5c569 100644
--- a/inc/managers/LoginManager.php
+++ b/inc/managers/LoginManager.php
@@ -17,10 +17,8 @@
class LoginManager
{
protected Features $siteFeatures;
- protected ?MagicLinkManager $magicLink = null;
protected ?MetaForm $metaForm = null;
- protected EmailManager $emailManager;
- protected AjaxRateLimiter $rateLimiter;
+ protected CacheManager $cache;
protected array $forms =[];
@@ -30,7 +28,6 @@
protected string $title = '';
// Token handlers registry
- protected array $tokenHandlers = [];
protected array $messageHandlers = [];
private array $allowed_file_types = [
@@ -44,12 +41,9 @@
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 = CacheManager::for('login');
// Initialize magic link support if enabled
if ($this->siteFeatures->has('magicLink')) {
@@ -66,19 +60,22 @@
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( '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 +85,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,19 +117,27 @@
$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 (Features::forSite()->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']) {
@@ -184,6 +194,7 @@
$fields = $this->getRegistrationFormFields();
break;
case 'lostpassword':
+ case 'magic':
$fields = [
'user_email' => [
'type' => 'email',
@@ -281,6 +292,34 @@
}
}
}
+ 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
+ $logout_url = wp_nonce_url($logout_url, 'log-out');
+
+ return $logout_url;
+}
public function getLoginPage():int|false
{
return (int)get_option(BASE.'login_page');
@@ -297,118 +336,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')) {
return;
}
- $this->magicLink = new MagicLinkManager();
}
-
-
/*********************************************************************
RENDERING
*********************************************************************/
@@ -418,6 +352,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 +374,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 +395,23 @@
} else {
$action = 'login';
}
+ return $action;
+ }
- $this->action = $action;
+ protected function setup():void
+ {
+ $this->action = $this->getAction();
+ if (in_array($this->action, ['logout']) || array_key_exists('loggedout', $_GET)) {
+ wp_logout();
+ wp_redirect(esc_attr($_GET['redirect_to'] ?? get_home_url()));
+ exit;
+ }
+ if (in_array($this->action, ['rp', 'resetpass']) && !is_user_logged_in()) {
+ wp_redirect(wp_login_url());
+ exit;
+ } elseif (is_user_logged_in()) {
+ wp_redirect(get_home_url(null, '/dash/'));
+ }
$this->setupLabels();
$this->setupFields();
$this->setupTitle();
@@ -496,6 +456,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,21 +533,23 @@
protected function renderForms():void
{
-
+ $this->metaForm = new MetaForm();
$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);
@@ -590,7 +558,7 @@
$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 +588,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 +629,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 +647,7 @@
}
}
if ($out == '') {
- $out =jvbIcon('home');
+ $out =jvbIcon('house');
}
?><?= $out ?>
</a>
@@ -709,339 +679,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 +694,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'] ?? '';
@@ -1151,18 +751,35 @@
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
@@ -1197,6 +814,23 @@
'footer' => JVB_LOGIN['reset_pass']['footer'] ?? '',
'submit' => JVB_LOGIN['reset_pass']['submit'] ?? 'Reset Password',
];
+ case 'logout':
+ return [
+ 'title' => JVB_LOGIN['logout']['title'] ?? 'Logged Out!',
+ 'description' => JVB_LOGIN['logout']['description'] ?? [],
+ 'extra' => JVB_LOGIN['logout']['extra'] ?? [],
+ 'footer' => JVB_LOGIN['logout']['footer'] ?? '',
+ 'submit' => JVB_LOGIN['logout']['submit'] ?? '',
+ ];
+ case 'magic':
+ return [
+ 'title' => JVB_LOGIN['magic']['title'] ?? 'Log in with Magic Link',
+ 'description' => JVB_LOGIN['magic']['description'] ?? ['Enter your email.','You\'ll get an email with a magic link.','Click it, and you\'re logged in!'],
+ 'extra' => JVB_LOGIN['magic']['extra'] ?? [],
+ 'footer' => JVB_LOGIN['magic']['footer'] ?? '',
+ 'submit' => JVB_LOGIN['magic']['submit'] ?? jvbIcon('magic-wand').'Send Magic Link',
+
+ ];
case 'login':
default:
return [
@@ -1211,37 +845,14 @@
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
}
@@ -1257,20 +868,107 @@
$this->maybeTurnstileScripts();
wp_enqueue_script('jvb-form');
+ $action = $this->getAction();
+ ob_start();
+ ?>
- $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) {
+ if (!form) return;
+
+ if (!window.jvbForm) {
console.error('jvbForm not loaded');
+ return;
}
- });";
+
+ window.LoginController = new window.jvbForm();
+ window.LoginController.registerForm(form, {
+ autosave: false,
+ endpoint: <?= "'{$action}'" ?>,
+ formStatus: false,
+ cache: false,
+ });
+
+ window.LoginController.subscribe((event, data) => {
+ if (event === 'form-submit') {
+ handleFormSubmission(data);
+ }
+ });
+
+
+ async function handleFormSubmission(data) {
+ let realFormData = data.fullData;
+ const { formId, config, data: formData } = data;
+
+
+ const form = config.element;
+
+ const submit = form.querySelector('[type=submit]');
+ let oldText = submit.textContent;
+
+
+ window.LoginController.showFormStatus(formId, 'uploading');
+
+ try {
+ submit.disabled = true;
+ submit.textContent = 'Loading...';
+ const response = await fetch(`${jvbSettings.api}<?=($action === 'magic') ? $action : 'auth/'.$action?>`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-WP-Nonce': window.auth.getNonce()
+ },
+ body: JSON.stringify(realFormData)
+ });
+
+ const result = await response.json();
+
+ // Handle errors
+ if (!response.ok) {
+ window.LoginController.showFormStatus(formId, 'error');
+ window.LoginController.handleFormError(form, result);
+ return;
+ }
+
+ // Handle success
+ window.LoginController.showFormStatus(formId, 'submitted');
+
+ // Show success message briefly before redirect
+ if (result.message) {
+ window.LoginController.handleFormSuccess(form, result);
+ }
+
+ if (window.auth && typeof window.auth.handleLogin === 'function' && Object.hasOwn(result, 'auth')) {
+ console.log('Awaiting Auth...');
+ await window.auth.handleLogin(result.auth); // Pass the full result
+ }
+
+ // Handle redirect
+ if (result.redirect) {
+ setTimeout(() => {
+ window.location.href = result.redirect;
+ }, 200); // Brief delay to show success message
+ }
+
+ } catch (error) {
+ console.error('Form submission error:', error);
+ window.LoginController.showFormStatus(formId, 'error');
+ window.LoginController.handleFormError(form, {
+ 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 +1000,11 @@
wp_safe_redirect($login_url);
exit;
}
+
+ public function saveRegistrationFields(int $user_id, array $userdata):void
+ {
+
+ }
}
// Initialize the login manager
--
Gitblit v1.10.0