| | |
| | | class MagicLinkManager |
| | | { |
| | | protected CacheManager $cache; |
| | | protected EmailManager $email; |
| | | protected CacheManager $referral_cache; |
| | | |
| | | // Token settings |
| | | protected int $token_expiry = 900; // 15 minutes in seconds |
| | |
| | | public function __construct() |
| | | { |
| | | $this->cache = CacheManager::for('magic_links', $this->token_expiry); |
| | | $this->email = new EmailManager(); |
| | | $this->referral_cache = CacheManager::for('referral_magic_links', 14 * DAY_IN_SECONDS); |
| | | |
| | | // Hook into WordPress auth flow |
| | | add_action('template_redirect', [$this, 'handleMagicLinkClick']); |
| | |
| | | 'created' => time() |
| | | ], $data); |
| | | |
| | | $this->cache->set($token, $token_data); |
| | | // Use longer expiry for referral tokens |
| | | if ($type === self::TYPE_REFERRAL) { |
| | | $this->referral_cache->set($token, $token_data); |
| | | } else { |
| | | $this->cache->set($token, $token_data); |
| | | } |
| | | |
| | | return $token; |
| | | } |
| | |
| | | */ |
| | | public function verifyToken(string $token, string $email): array|WP_Error |
| | | { |
| | | // Try regular cache first, then referral cache |
| | | $token_data = $this->cache->get($token); |
| | | |
| | | if (!$token_data) { |
| | | $token_data = $this->referral_cache->get($token); |
| | | } |
| | | |
| | | if (!$token_data) { |
| | | error_log('Token not found. Checking cache stats...'); |
| | | return new WP_Error('invalid_token', 'Invalid or expired token'); |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | // Delete token after verification (single use) |
| | | $this->cache->delete($token); |
| | | // Check which cache it's in and delete from the correct one |
| | | if ($token_data['type'] === 'referral') { |
| | | $this->referral_cache->delete($token); |
| | | } else { |
| | | $this->cache->delete($token); |
| | | } |
| | | |
| | | return $token_data; |
| | | } |
| | |
| | | $subject = 'Sign in to ' . get_bloginfo('name'); |
| | | $message = $this->getLoginEmailTemplate($user->display_name, $magic_url); |
| | | |
| | | $sent = $this->email->sendEmail($email, $subject, $message, 'Log in to '. get_bloginfo('name')); |
| | | $sent = JVB()->email()->sendEmail($email, $subject, $message, 'Log in to '. get_bloginfo('name')); |
| | | |
| | | return $sent ? true : new WP_Error('email_failed', 'Failed to send magic link'); |
| | | } |
| | |
| | | $subject = 'Complete your ' . get_bloginfo('name') . ' registration'; |
| | | $message = $this->getSignupEmailTemplate($context['name'] ?? '', $magic_url); |
| | | |
| | | $sent = $this->email->sendEmail($email, $subject, $message, 'Complete Registration'); |
| | | $sent = JVB()->email()->sendEmail($email, $subject, $message, 'Complete Registration'); |
| | | |
| | | return $sent ? true : new WP_Error('email_failed', 'Failed to send signup link'); |
| | | } |
| | |
| | | $token_data = [ |
| | | 'referral_code' => $context['referral_code'], |
| | | 'name' => $context['name'] ?? '', |
| | | 'role' => $context['role'] ?? 'subscriber' |
| | | 'role' => $context['role'] ?? 'subscriber', |
| | | 'email' => $email |
| | | ]; |
| | | |
| | | $token = $this->generateToken($email, self::TYPE_REFERRAL, $token_data); |
| | |
| | | $referrer_name = $context['referrer_name'] ?? 'A friend'; |
| | | $reward_text = $context['reward_text'] ?? ''; |
| | | |
| | | $subject = $referrer_name . ' invited you to join ' . get_bloginfo('name'); |
| | | $message = $this->getReferralEmailTemplate($context['name'] ?? '', $referrer_name, $magic_url, $reward_text); |
| | | $subject = (array_key_exists('subject', $context) && $context['subject'] !== '') ? $context['subject'] : $referrer_name . ' invited you to join ' . get_bloginfo('name'); |
| | | $message = $this->getReferralEmailTemplate($context['name'] ?? '', $referrer_name, $magic_url, $reward_text, $context); |
| | | |
| | | $sent = $this->email->sendEmail($email, $subject, $message, 'Accept Invitation'); |
| | | $sent = JVB()->email()->sendEmail($email, $subject, $message, 'Accept Invitation'); |
| | | |
| | | return $sent ? true : new WP_Error('email_failed', 'Failed to send referral link'); |
| | | } |
| | |
| | | $subject = 'Reset your password'; |
| | | $message = $this->getResetEmailTemplate($user->display_name, $magic_url); |
| | | |
| | | $sent = $this->email->sendEmail($email, $subject, $message, 'Reset Password'); |
| | | $sent = JVB()->email()->sendEmail($email, $subject, $message, 'Reset Password'); |
| | | |
| | | return $sent ? true : new WP_Error('email_failed', 'Failed to send reset link'); |
| | | } |
| | |
| | | |
| | | $action = sanitize_text_field($_GET['action']); |
| | | $token = sanitize_text_field($_GET['magic_token']); |
| | | $email = sanitize_email($_GET['email']); |
| | | $email = sanitize_email(rawurldecode($_GET['email'])); |
| | | |
| | | if (!in_array($action, ['magic_login', 'magic_signup', 'magic_referral', 'magic_reset'])) { |
| | | return; |
| | |
| | | */ |
| | | protected function processSignup(array $token_data): void |
| | | { |
| | | if (!array_key_exists('email', $token_data) || !array_key_exists('name', $token_data)) { |
| | | JVB()->error()->log('[MagicLinkManager]Could not process Signup'); |
| | | return; |
| | | } |
| | | $user_id = wp_create_user( |
| | | $token_data['email'], |
| | | wp_generate_password(20, true, true), |
| | |
| | | /** |
| | | * Process referral signup via magic link |
| | | */ |
| | | /** |
| | | * Process referral signup via magic link |
| | | */ |
| | | protected function processReferralSignup(array $token_data): void |
| | | { |
| | | $user_id = wp_create_user( |
| | | $token_data['email'], |
| | | wp_generate_password(20, true, true), |
| | | $token_data['email'] |
| | | ); |
| | | |
| | | if (is_wp_error($user_id)) { |
| | | wp_die('Failed to create account: ' . $user_id->get_error_message()); |
| | | if (!array_key_exists('email', $token_data) || !array_key_exists('name', $token_data)) { |
| | | JVB()->error()->log('[MagicLinkManager]Could not process Referral Signup'); |
| | | return; |
| | | } |
| | | |
| | | if (!empty($token_data['name'])) { |
| | | wp_update_user([ |
| | | 'ID' => $user_id, |
| | | 'display_name' => $token_data['name'], |
| | | 'first_name' => $token_data['name'] |
| | | ]); |
| | | $email = sanitize_email($token_data['email']); |
| | | if (email_exists($email)) { |
| | | wp_die('Looks like you already have an account!'); |
| | | } |
| | | |
| | | // Store referral code for ReferralManager |
| | | if (session_status() === PHP_SESSION_NONE) { |
| | | session_start(); |
| | | $role = JVB()->referrals()->getRole(); |
| | | $pass = wp_generate_password(20, true, true); |
| | | $name = sanitize_text_field($token_data['name']); |
| | | $user_id = wp_insert_user([ |
| | | 'user_login' => $email, |
| | | 'user_email' => $email, |
| | | 'user_pass' => $pass, |
| | | 'display_name' => $name, |
| | | 'role' => $role |
| | | ]); |
| | | if (!is_wp_error($user_id)) { |
| | | $response = JVB()->routes('login')->login($email, $pass, true); |
| | | if ($response) { |
| | | wp_safe_redirect(home_url('/dash?welcome=1&referral=1')); |
| | | exit; |
| | | } |
| | | } else { |
| | | JVB()->error()->log( |
| | | '[MagicLinkManager]', |
| | | $user_id->get_error_message(), |
| | | $token_data |
| | | ); |
| | | } |
| | | $_SESSION[BASE . 'referral_code'] = $token_data['referral_code']; |
| | | setcookie( |
| | | BASE . 'referral_code', |
| | | $token_data['referral_code'], |
| | | time() + (86400 * 30), |
| | | '/' |
| | | ); |
| | | |
| | | $user = get_user_by('ID', $user_id); |
| | | wp_set_current_user($user_id); |
| | | wp_set_auth_cookie($user_id, true); |
| | | |
| | | do_action('user_register', $user_id); |
| | | do_action('wp_login', $user->user_login, $user); |
| | | |
| | | wp_safe_redirect(home_url('/dash?welcome=1&referral=1')); |
| | | exit; |
| | | } |
| | | |
| | | /** |
| | |
| | | |
| | | protected function getLoginEmailTemplate(string $name, string $magic_url): string |
| | | { |
| | | $content = '<h2>Hey ' . esc_html($name) . '!</h2>'; |
| | | $content .= '<p>Click the button below to sign in to your account. This link expires in 15 minutes.</p>'; |
| | | $content .= '<p style="text-align: center; margin: 30px 0;">'; |
| | | $content .= '<a href="' . $magic_url . '" style="background: #2271b1; color: #fff; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block;">Sign In</a>'; |
| | | $content .= '</p>'; |
| | | $content .= '<p style="color: #666; font-size: 14px;">If you didn\'t request this, you can safely ignore this email.</p>'; |
| | | |
| | | $content = JVB()->email()->h2('Hey ' . esc_html($name) . '!'); |
| | | $content .= '<p>Click the button below to sign in to your account instantly - no password needed!</p>'; |
| | | $content .= JVB()->email()->button($magic_url, 'Sign In Now'); |
| | | $content .= '<p>Or copy and paste this link into your browser:</p>'; |
| | | $content .= JVB()->email()->link($magic_url); |
| | | $content .= JVB()->email()->divider(); |
| | | $content .= '<p>If you didn\'t request this, you can safely ignore this email. This link expires in 15 minutes.</p>'; |
| | | return $content; |
| | | } |
| | | |
| | | protected function getSignupEmailTemplate(string $name, string $magic_url): string |
| | | { |
| | | $content = '<h2>Welcome' . ($name ? ', ' . esc_html($name) : '') . '!</h2>'; |
| | | $content .= '<p>You\'re almost there! Click the button below to complete your registration and access your account.</p>'; |
| | | $content .= '<p style="text-align: center; margin: 30px 0;">'; |
| | | $content .= '<a href="' . $magic_url . '" style="background: #2271b1; color: #fff; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block;">Complete Registration</a>'; |
| | | $content .= '</p>'; |
| | | $content .= '<p style="color: #666; font-size: 14px;">This link expires in 15 minutes.</p>'; |
| | | |
| | | $content = JVB()->email()->h2('Welcome' . ($name ? ', ' . esc_html($name) : '') . '!'); |
| | | $content .= '<p>Click the button below to complete your registration and access your account!</p>'; |
| | | $content .= JVB()->email()->button($magic_url, 'Complete Registration'); |
| | | $content .= '<p>Or copy and paste this link:</p>'; |
| | | $content .= JVB()->email()->link($magic_url); |
| | | $content .= JVB()->email()->spacer(10); |
| | | $content .= '<p><small>This link expires in 24 hours.</small></p>'; |
| | | return $content; |
| | | } |
| | | |
| | | protected function getReferralEmailTemplate(string $name, string $referrer_name, string $magic_url, string $reward_text): string |
| | | protected function getReferralEmailTemplate(string $name, string $referrer_name, string $magic_url, string $reward_text, array $context): string |
| | | { |
| | | $content = '<h2>Hey' . ($name ? ' ' . esc_html($name) : '') . '!</h2>'; |
| | | $content .= '<p><strong>' . esc_html($referrer_name) . '</strong> thinks you\'d love ' . get_bloginfo('name') . '!</p>'; |
| | | $content = JVB()->email()->h2('Hey' . ($name ? ' ' . esc_html($name) : '') . '!'); |
| | | $content .= sprintf( |
| | | '<p><strong>%s</strong> thinks you\'d love %s!</p>', |
| | | esc_html($referrer_name), |
| | | esc_html(get_bloginfo('name')) |
| | | ); |
| | | |
| | | if ($reward_text) { |
| | | $content .= '<p>' . esc_html($reward_text) . '</p>'; |
| | | if (!empty($context['message'])) { |
| | | $content .= JVB()->email()->callout(nl2br(esc_html($context['message']))); |
| | | } |
| | | |
| | | $content .= '<p style="text-align: center; margin: 30px 0;">'; |
| | | $content .= '<a href="' . $magic_url . '" style="background: #2271b1; color: #fff; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block;">Join Now</a>'; |
| | | $content .= '</p>'; |
| | | $content .= '<p style="color: #666; font-size: 14px;">This link expires in 15 minutes.</p>'; |
| | | if ($reward_text) { |
| | | $content .= JVB()->email()->alert( |
| | | '<strong>Special Welcome Offer:</strong> ' . esc_html($reward_text), |
| | | 'success' |
| | | ); |
| | | } |
| | | |
| | | return $content; |
| | | } |
| | | |
| | | protected function getResetEmailTemplate(string $name, string $magic_url): string |
| | | { |
| | | $content = '<h2>Hey ' . esc_html($name) . '!</h2>'; |
| | | $content .= '<p>We received a request to reset your password. Click the button below to sign in and update your password.</p>'; |
| | | $content .= '<p style="text-align: center; margin: 30px 0;">'; |
| | | $content .= '<a href="' . $magic_url . '" style="background: #2271b1; color: #fff; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block;">Reset Password</a>'; |
| | | $content .= '</p>'; |
| | | $content .= '<p style="color: #666; font-size: 14px;">If you didn\'t request this, you can safely ignore this email. This link expires in 15 minutes.</p>'; |
| | | $content .= JVB()->email()->button($magic_url, 'Join Now'); |
| | | $content .= '<p>Or copy and paste this link:</p>'; |
| | | $content .= JVB()->email()->link($magic_url); |
| | | $content .= JVB()->email()->spacer(10); |
| | | $content .= '<p><small>This invitation expires in 14 days.</small></p>'; |
| | | |
| | | return $content; |
| | | } |
| | | } |
| | | |
| | | new MagicLinkManager(); |