From 42fa8304ddb811b0f725f245130f70c0f5e86a6c Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Tue, 04 Nov 2025 06:12:02 +0000
Subject: [PATCH] =Refactored LoginManager to be more extensible and configurable, as well as an AjaxRateLimiter

---
 inc/rest/routes/ReferralRoutes.php |  324 ++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 305 insertions(+), 19 deletions(-)

diff --git a/inc/rest/routes/ReferralRoutes.php b/inc/rest/routes/ReferralRoutes.php
index d7f097f..1589135 100644
--- a/inc/rest/routes/ReferralRoutes.php
+++ b/inc/rest/routes/ReferralRoutes.php
@@ -31,6 +31,49 @@
 			'permission_callback' => [$this, 'checkPermission']
 		]);
 
+		register_rest_route($this->namespace, '/referrals/register', [
+			'methods' => 'POST',
+			'callback' => [$this, 'registerWithReferral'],
+			'permission_callback' => '__return_true',
+			'args' => [
+				'name' => [
+					'required' => true,
+					'type' => 'string',
+					'sanitize_callback' => 'sanitize_text_field'
+				],
+				'email' => [
+					'required' => true,
+					'type' => 'string',
+					'format' => 'email',
+					'validate_callback' => function($param) {
+						return is_email($param);
+					}
+				],
+				'code' => [
+					'required' => true,
+					'type' => 'string',
+					'sanitize_callback' => function($code) {
+						return strtoupper(sanitize_text_field($code));
+					}
+				]
+			]
+		]);
+
+		register_rest_route($this->namespace, '/referrals/check-code', [
+			'methods' => 'POST',
+			'callback' => [$this, 'checkReferralCode'],
+			'permission_callback' => '__return_true',
+			'args' => [
+				'code' => [
+					'required' => true,
+					'type' => 'string',
+					'sanitize_callback' => function($code) {
+						return strtoupper(sanitize_text_field($code));
+					}
+				]
+			]
+		]);
+
 		// Get or create referral code
 		register_rest_route($this->namespace, "/{$this->route}/code", [
 			'methods' => 'GET',
@@ -38,22 +81,6 @@
 			'permission_callback' => [$this, 'checkPermission']
 		]);
 
-		// Update referral code
-		register_rest_route($this->namespace, "/{$this->route}/code", [
-			'methods' => 'POST',
-			'callback' => [$this, 'updateReferralCode'],
-			'permission_callback' => [$this, 'checkPermission'],
-			'args' => [
-				'code' => [
-					'required' => true,
-					'type' => 'string',
-					'validate_callback' => function($param) {
-						return preg_match('/^[A-Z0-9]+$/i', $param);
-					}
-				]
-			]
-		]);
-
 		// Track referral click (public endpoint)
 		register_rest_route($this->namespace, "/{$this->route}/track", [
 			'methods' => 'POST',
@@ -84,13 +111,76 @@
 			]
 		]);
 
-		// Get user stats
-		register_rest_route($this->namespace, "/{$this->route}/stats", [
+		// Send referral invitation
+		register_rest_route($this->namespace, '/'.$this->route.'/invite', [
+			'methods' => 'POST',
+			'callback' => [$this, 'sendInvitation'],
+			'permission_callback' => [$this, 'checkPermission'],
+			'args' => [
+				'email' => [
+					'required' => true,
+					'type' => 'string',
+					'format' => 'email',
+					'validate_callback' => function($param) {
+						return is_email($param);
+					}
+				],
+				'name' => [
+					'required' => true,
+					'type' => 'string',
+					'sanitize_callback' => 'sanitize_text_field'
+				]
+			]
+		]);
+
+		// Send batch invitations
+		register_rest_route($this->namespace, '/'.$this->route.'/invite/batch', [
+			'methods' => 'POST',
+			'callback' => [$this, 'sendBatchInvitations'],
+			'permission_callback' => [$this, 'checkPermission'],
+			'args' => [
+				'invitations' => [
+					'required' => true,
+					'type' => 'array',
+					'validate_callback' => function($param) {
+						return is_array($param) && !empty($param);
+					}
+				]
+			]
+		]);
+
+		// Get invitation stats for current user
+		register_rest_route($this->namespace, '/'.$this->route.'/invite/stats', [
 			'methods' => 'GET',
-			'callback' => [$this, 'getUserStats'],
+			'callback' => [$this, 'getInvitationStats'],
 			'permission_callback' => [$this, 'checkPermission']
 		]);
 
+		// Export referrals for Jane App
+		register_rest_route($this->namespace, '/'.$this->route.'/export', [
+			'methods' => 'POST',
+			'callback' => [$this, 'exportReferrals'],
+			'permission_callback' => function() {
+				return current_user_can('manage_options');
+			},
+			'args' => [
+				'start_date' => [
+					'required' => true,
+					'type' => 'string',
+					'validate_callback' => function($param) {
+						return (bool) strtotime($param);
+					}
+				],
+				'end_date' => [
+					'required' => true,
+					'type' => 'string',
+					'validate_callback' => function($param) {
+						return (bool) strtotime($param);
+					}
+				]
+			]
+		]);
+
 		// Get top referrers (admin only)
 		register_rest_route($this->namespace, "/{$this->route}/leaderboard", [
 			'methods' => 'GET',
@@ -310,4 +400,200 @@
 			'settings' => $settings
 		]);
 	}
+
+	/**
+	 * Send a single referral invitation
+	 *
+	 * @param WP_REST_Request $request
+	 * @return WP_REST_Response
+	 */
+	public function sendInvitation(WP_REST_Request $request): WP_REST_Response
+	{
+		$user_id = get_current_user_id();
+		$email = sanitize_email($request->get_param('email'));
+		$name = sanitize_text_field($request->get_param('name'));
+
+		// Send invitation via ReferralManager
+		$referral_manager = JVB()->referrals();
+		$result = $referral_manager->sendReferralInvitation($user_id, $email, $name);
+
+		if (is_wp_error($result)) {
+			return new WP_REST_Response([
+				'success' => false,
+				'message' => $result->get_error_message(),
+				'code' => $result->get_error_code()
+			], 400);
+		}
+
+		return new WP_REST_Response($result, 200);
+	}
+
+	/**
+	 * Send batch referral invitations
+	 *
+	 * @param WP_REST_Request $request
+	 * @return WP_REST_Response
+	 */
+	public function sendBatchInvitations(WP_REST_Request $request): WP_REST_Response
+	{
+		$user_id = get_current_user_id();
+		$invitations = $request->get_param('invitations');
+
+		// Validate invitation format
+		foreach ($invitations as $invite) {
+			if (empty($invite['email']) || empty($invite['name'])) {
+				return new WP_REST_Response([
+					'success' => false,
+					'message' => 'Each invitation must have email and name'
+				], 400);
+			}
+		}
+
+		// Send batch via ReferralManager
+		$referral_manager = JVB()->referrals();
+		$result = $referral_manager->sendBatchReferralInvitations($user_id, $invitations);
+
+		return new WP_REST_Response($result, 200);
+	}
+
+	/**
+	 * Get invitation stats for current user
+	 *
+	 * @param WP_REST_Request $request
+	 * @return WP_REST_Response
+	 */
+	public function getInvitationStats(WP_REST_Request $request): WP_REST_Response
+	{
+		$user_id = get_current_user_id();
+
+		$referral_manager = JVB()->referrals();
+		$stats = $referral_manager->getUserInvitationStats($user_id);
+
+		return new WP_REST_Response([
+			'success' => true,
+			'stats' => $stats
+		], 200);
+	}
+
+	/**
+	 * Export referrals for Jane App cross-reference
+	 * Admin only
+	 *
+	 * @param WP_REST_Request $request
+	 * @return WP_REST_Response
+	 */
+	public function exportReferrals(WP_REST_Request $request): WP_REST_Response
+	{
+		$start_date = sanitize_text_field($request->get_param('start_date'));
+		$end_date = sanitize_text_field($request->get_param('end_date'));
+
+		$referral_manager = JVB()->referrals();
+		$csv_content = $referral_manager->exportReferrals($start_date, $end_date);
+
+		// Return CSV for download
+		return new WP_REST_Response([
+			'success' => true,
+			'csv' => $csv_content,
+			'filename' => sprintf('referrals_%s_to_%s.csv', $start_date, $end_date)
+		], 200);
+	}
+
+	public function registerWithReferral(WP_REST_Request $request): WP_REST_Response
+	{
+		$name = sanitize_text_field($request->get_param('name'));
+		$email = sanitize_email($request->get_param('email'));
+		$code = strtoupper(sanitize_text_field($request->get_param('code')));
+
+		// Validate email
+		if (!is_email($email)) {
+			return new WP_REST_Response([
+				'success' => false,
+				'message' => 'Invalid email address'
+			], 400);
+		}
+
+		// Check if user exists
+		if (email_exists($email)) {
+			return new WP_REST_Response([
+				'success' => false,
+				'message' => 'An account with this email already exists'
+			], 400);
+		}
+
+		// Validate referral code
+		$referral_manager = JVB()->referrals();
+		$referrer = $referral_manager->getUserByReferralCode($code);
+
+		if (!$referrer) {
+			return new WP_REST_Response([
+				'success' => false,
+				'message' => 'Invalid referral code'
+			], 404);
+		}
+
+		// Get reward text
+		$settings = $referral_manager->getRewardSettings();
+		$reward_amount = $settings['referee_reward_amount'] ?? 20;
+		$reward_type = $settings['referee_reward_type'] ?? 'percentage';
+		$reward_text = $reward_type === 'percentage'
+			? "{$reward_amount}% off your first treatment!"
+			: "\${$reward_amount} off your first treatment!";
+
+		// Send magic link with referral context via MagicLinkManager
+		$magic_link_manager = new \JVBase\managers\MagicLinkManager();
+
+		$result = $magic_link_manager->sendMagicLink(
+			$email,
+			\JVBase\managers\MagicLinkManager::TYPE_REFERRAL,
+			[
+				'name' => $name,
+				'referral_code' => $code,
+				'referrer_id' => $referrer->ID,
+				'referrer_name' => $referrer->display_name,
+				'reward_text' => $reward_text
+			]
+		);
+
+		if (is_wp_error($result)) {
+			return new WP_REST_Response([
+				'success' => false,
+				'message' => 'Failed to send registration link. Please try again.'
+			], 500);
+		}
+
+		return new WP_REST_Response([
+			'success' => true,
+			'message' => 'Check your email! We sent you a link to complete your registration.',
+			'email' => $email
+		], 200);
+	}
+
+	public function checkReferralCode(WP_REST_Request $request): WP_REST_Response
+	{
+		$code = strtoupper(sanitize_text_field($request->get_param('code')));
+
+		if (empty($code)) {
+			return new WP_REST_Response([
+				'success' => false,
+				'message' => 'Code is required'
+			], 400);
+		}
+
+		$referral_manager = JVB()->referrals();
+		$referrer = $referral_manager->getUserByReferralCode($code);
+
+		if (!$referrer) {
+			return new WP_REST_Response([
+				'success' => false,
+				'message' => 'Invalid referral code'
+			], 404);
+		}
+
+		// Return basic referrer info (no sensitive data)
+		return new WP_REST_Response([
+			'success' => true,
+			'code' => $code,
+			'referrer_name' => $referrer->display_name,
+		], 200);
+	}
 }

--
Gitblit v1.10.0