'per_user', // 'per_user' or 'flat_total' 'referrer_reward_amount' => 25.00, 'referrer_reward_type' => 'fixed', 'referee_reward_type' => 'percentage', // 'percentage' or 'fixed' 'referee_reward_amount' => 20, // 20% or $20 'referee_reward_applies_to' => 'first_order' // 'first_order' or 'all_orders' ]; public function __construct() { global $wpdb; $this->wpdb = $wpdb; $this->cache = new CacheManager('referrals'); $this->referrals_table = BASE . 'referrals'; $this->rewards_table = BASE . 'referral_rewards'; $this->magic_link = new MagicLinkManager(); // Hook into user registration to track referrals add_action('user_register', [$this, 'processReferral'], 10, 1); // Add meta boxes for admin to manage referrals add_action('show_user_profile', [$this, 'displayUserReferralInfo']); add_action('edit_user_profile', [$this, 'displayUserReferralInfo']); // Save referral code changes add_action('personal_options_update', [$this, 'saveUserReferralCode']); add_action('edit_user_profile_update', [$this, 'saveUserReferralCode']); add_filter(BASE.'new_user_email_content', [$this, 'addReferralToWelcomeEmail'], 99, 2); add_filter('jvbAdditionalActions', [$this, 'outputShareWidget']); add_action('wp_enqueue_scripts', [$this, 'enqueueScripts']); // Schedule cron jobs for reports $this->registerCronJobs(); } public function enqueueScripts():void { $requirements = [ 'jvb-utility', 'jvb-a11y', 'jvb-popup', 'jvb-tabs', ]; if (Features::hasIntegration('cloudflare') && JVB()->connect('cloudflare')->isSetUp()) { $requirements[] = 'cloudflare-turnstile'; } wp_enqueue_script( 'jvb-referral', JVB_URL . 'assets/js/min/referral.min.js', $requirements, '1.0.0', true ); } /** * Register cron jobs for automated reporting */ protected function registerCronJobs(): void { add_action(BASE . 'referral_daily_report', [$this, 'sendDailyReport']); add_action(BASE . 'referral_weekly_report', [$this, 'sendWeeklyReport']); // Schedule if not already scheduled if (!wp_next_scheduled(BASE . 'referral_daily_report')) { wp_schedule_event(strtotime('tomorrow 9:00'), 'daily', BASE . 'referral_daily_report'); } if (!wp_next_scheduled(BASE . 'referral_weekly_report')) { wp_schedule_event(strtotime('next Monday 9:00'), 'weekly', BASE . 'referral_weekly_report'); } } /** * Generate or get existing referral code for a user * * @param int $user_id * @param string|null $custom_code Optional custom code * @return string|WP_Error */ public function getUserReferralCode(int $user_id, ?string $custom_code = null) { $user = get_user_by('ID', $user_id); if (!$user) { return new WP_Error('invalid_user', 'User not found'); } // Check if user already has a code $existing_code = get_user_meta($user_id, BASE . 'referral_code', true); if ($existing_code && !$custom_code) { return $existing_code; } // Generate new code if custom provided or none exists $code = $custom_code ?: $this->generateReferralCode($user); // Validate uniqueness if ($this->isCodeTaken($code, $user_id)) { return new WP_Error('code_taken', 'This referral code is already in use'); } // Save the code update_user_meta($user_id, BASE . 'referral_code', $code); return $code; } /** * Generate a referral code based on user info * * @param WP_User $user * @return string */ protected function generateReferralCode(WP_User $user): string { // Create code from first and last name $first = sanitize_title($user->first_name ?: 'user'); $last = sanitize_title($user->last_name ?: wp_generate_password(4, false)); $base_code = strtoupper(substr($first, 0, 4) . substr($last, 0, 4)); // Ensure uniqueness $code = $base_code; $suffix = 1; while ($this->isCodeTaken($code)) { $code = $base_code . $suffix; $suffix++; } return $code; } /** * Check if a referral code is already taken * * @param string $code * @param int|null $exclude_user_id * @return bool */ protected function isCodeTaken(string $code, ?int $exclude_user_id = null): bool { $args = [ 'meta_key' => BASE . 'referral_code', 'meta_value' => $code, 'fields' => 'ID', 'number' => 1 ]; if ($exclude_user_id) { $args['exclude'] = [$exclude_user_id]; } $users = get_users($args); return !empty($users); } /** * Track a new referral when user registers * * @param int $user_id */ public function processReferral(int $user_id): void { // Check if user was created via referral magic link $referral_code = get_user_meta($user_id, BASE . 'pending_referral_code', true); if (!$referral_code) { return; } // Find the referrer $referrer = $this->getUserByReferralCode($referral_code); if (!$referrer) { delete_user_meta($user_id, BASE . 'pending_referral_code'); return; } // Check for duplicates $existing = $this->getReferralByReferee($user_id); if ($existing) { delete_user_meta($user_id, BASE . 'pending_referral_code'); return; } // Create referral record $result = $this->createReferral($referrer->ID, $user_id, $referral_code); if ($result) { // Clean up temp meta delete_user_meta($user_id, BASE . 'pending_referral_code'); // Fire action for tracking do_action('jvb_referral_processed', $user_id, $referrer->ID, $referral_code); } } /** * Create a referral record in the database * * @param int $referrer_id * @param int $referee_id * @param string $code * @return int|false */ protected function createReferral(int $referrer_id, int $referee_id, string $code) { $user = get_user_by('ID', $referee_id); return $this->wpdb->insert( $this->referrals_table, [ 'referrer_id' => $referrer_id, 'referee_id' => $referee_id, 'referee_name' => $user->display_name, 'referee_email' => $user->user_email, 'referee_phone' => get_user_meta($referee_id, BASE . 'phone', true) ?: '', 'referral_code' => $code, 'status' => 'pending', 'referred_at' => current_time('mysql') ], ['%d', '%d', '%s', '%s', '%s', '%s', '%s', '%s'] ); } /** * Get user by referral code * * @param string $code * @return WP_User|null */ public function getUserByReferralCode(string $code): ?WP_User { $users = get_users([ 'meta_key' => BASE . 'referral_code', 'meta_value' => $code, 'number' => 1 ]); return $users[0] ?? null; } /** * Get referral record by referee ID * * @param int $referee_id * @return object|null */ public function getReferralByReferee(int $referee_id): ?object { $result = $this->wpdb->get_row($this->wpdb->prepare( "SELECT * FROM {$this->referrals_table} WHERE referee_id = %d", $referee_id )); return $result ?: null; } /** * Mark a referral as treated/rewarded * * @param int $referral_id * @param bool $treated * @return bool */ public function markAsTreated(int $referral_id, bool $treated = true): bool { $status = $treated ? 'treated' : 'pending'; $result = $this->wpdb->update( $this->referrals_table, [ 'status' => $status, 'treated_at' => $treated ? current_time('mysql') : null ], ['id' => $referral_id], ['%s', '%s'], ['%d'] ); if ($result && $treated) { // Create reward records when marking as treated $this->createRewardRecords($referral_id); } return $result !== false; } /** * Create reward records for both referrer and referee * * @param int $referral_id */ protected function createRewardRecords(int $referral_id): void { $referral = $this->wpdb->get_row($this->wpdb->prepare( "SELECT * FROM {$this->referrals_table} WHERE id = %d", $referral_id )); if (!$referral) { return; } $settings = $this->getRewardSettings(); // Create referrer reward $this->wpdb->insert( $this->rewards_table, [ 'referral_id' => $referral_id, 'user_id' => $referral->referrer_id, 'reward_type' => 'referrer', 'amount' => $settings['referrer_reward_amount'], 'status' => 'available', 'created_at' => current_time('mysql') ], ['%d', '%d', '%s', '%f', '%s', '%s'] ); // Create referee reward $referee_amount = $settings['referee_reward_type'] === 'percentage' ? $settings['referee_reward_amount'] // Store as percentage : $settings['referee_reward_amount']; // Store as fixed amount $this->wpdb->insert( $this->rewards_table, [ 'referral_id' => $referral_id, 'user_id' => $referral->referee_id, 'reward_type' => 'referee', 'amount' => $referee_amount, 'reward_calculation' => $settings['referee_reward_type'], 'status' => 'available', 'created_at' => current_time('mysql') ], ['%d', '%d', '%s', '%f', '%s', '%s', '%s'] ); } /** * Get referrals for a user * * @param int $user_id * @param array $args * @return array */ public function getUserReferrals(int $user_id, array $args = []): array { $defaults = [ 'status' => 'all', 'limit' => 100, 'offset' => 0, 'orderby' => 'referred_at', 'order' => 'DESC' ]; $args = wp_parse_args($args, $defaults); $where = $this->wpdb->prepare("WHERE referrer_id = %d", $user_id); if ($args['status'] !== 'all') { $where .= $this->wpdb->prepare(" AND status = %s", $args['status']); } $query = "SELECT * FROM {$this->referrals_table} {$where} ORDER BY {$args['orderby']} {$args['order']} LIMIT {$args['limit']} OFFSET {$args['offset']}"; return $this->wpdb->get_results($query); } /** * Get referral statistics for a user * * @param int $user_id * @return array */ public function getUserStats(int $user_id): array { $cache_key = 'stats_' . $user_id; $cached = $this->cache->get($cache_key); if ($cached !== false) { return $cached; } $stats = $this->wpdb->get_row($this->wpdb->prepare( "SELECT COUNT(*) as total_referrals, SUM(CASE WHEN status = 'treated' THEN 1 ELSE 0 END) as treated_count, SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending_count FROM {$this->referrals_table} WHERE referrer_id = %d", $user_id ), ARRAY_A); // Get total rewards $rewards = $this->wpdb->get_row($this->wpdb->prepare( "SELECT SUM(CASE WHEN status = 'available' THEN amount ELSE 0 END) as available_rewards, SUM(CASE WHEN status = 'redeemed' THEN amount ELSE 0 END) as redeemed_rewards FROM {$this->rewards_table} WHERE user_id = %d AND reward_type = 'referrer'", $user_id ), ARRAY_A); $stats = array_merge($stats, $rewards); $this->cache->set($cache_key, $stats, HOUR_IN_SECONDS); return $stats; } /** * Get top referrers for a time period * * @param int $limit * @param string $period 'day'|'week'|'month'|'all' * @return array */ public function getTopReferrers(int $limit = 10, string $period = 'all'): array { $where = ''; if ($period !== 'all') { $date_where = match($period) { 'day' => "referred_at >= DATE_SUB(NOW(), INTERVAL 1 DAY)", 'week' => "referred_at >= DATE_SUB(NOW(), INTERVAL 1 WEEK)", 'month' => "referred_at >= DATE_SUB(NOW(), INTERVAL 1 MONTH)", default => "1=1" }; $where = "WHERE {$date_where}"; } $query = "SELECT referrer_id, COUNT(*) as referral_count, SUM(CASE WHEN status = 'treated' THEN 1 ELSE 0 END) as treated_count FROM {$this->referrals_table} {$where} GROUP BY referrer_id ORDER BY referral_count DESC LIMIT {$limit}"; $results = $this->wpdb->get_results($query); // Enrich with user data foreach ($results as &$result) { $user = get_user_by('ID', $result->referrer_id); $result->user_name = $user ? $user->display_name : 'Unknown'; $result->user_email = $user ? $user->user_email : ''; } return $results; } /** * Send daily report if there are new referrals */ public function sendDailyReport(): void { $yesterday = date('Y-m-d', strtotime('-1 day')); // Get new referrals from yesterday $new_referrals = $this->wpdb->get_results($this->wpdb->prepare( "SELECT r.*, u.display_name as referrer_name FROM {$this->referrals_table} r JOIN {$this->wpdb->users} u ON r.referrer_id = u.ID WHERE DATE(r.referred_at) = %s ORDER BY r.referred_at DESC", $yesterday )); // Only send if there's at least 1 new referral if (empty($new_referrals)) { return; } // Build email content $content = '

Daily Referral Report

'; $content .= '

' . count($new_referrals) . ' new referral' . (count($new_referrals) !== 1 ? 's' : '') . ' yesterday (' . $yesterday . ')

'; $content .= ''; $content .= ''; $content .= ''; $content .= ''; $content .= ''; $content .= ''; $content .= ''; foreach ($new_referrals as $ref) { $content .= ''; $content .= sprintf('', esc_html($ref->referee_name)); $content .= sprintf('', esc_html($ref->referee_email)); $content .= sprintf('', esc_html($ref->referrer_name)); $content .= sprintf('', esc_html($ref->referral_code)); $content .= ''; } $content .= '
RefereeEmailReferrerCode
%s%s%s%s
'; // Get admin email $to = get_option('admin_email'); $subject = sprintf('[%s] %d New Referral%s', get_bloginfo('name'), count($new_referrals), count($new_referrals) !== 1 ? 's' : ''); jvbMail($to, $subject, $content); } /** * Send weekly report with top referrers */ public function sendWeeklyReport(): void { $top_referrers = $this->getTopReferrers(10, 'week'); $total_referrals = $this->wpdb->get_var( "SELECT COUNT(*) FROM {$this->referrals_table} WHERE referred_at >= DATE_SUB(NOW(), INTERVAL 1 WEEK)" ); if ($total_referrals == 0) { return; } $to = get_option('admin_email'); $subject = '[' . get_bloginfo('name') . '] Weekly Referral Summary - ' . date('F j, Y'); $message = $this->generateWeeklyReportEmail($top_referrers, $total_referrals); wp_mail($to, $subject, $message, ['Content-Type: text/html; charset=UTF-8']); } /** * Generate CSV content from referrals * * @param array $referrals * @return string */ protected function generateCSV(array $referrals): string { $csv = "Referred By,Referee Name,Referee Email,Referee Phone,Referral Code,Status,Referred At,Treated At\n"; foreach ($referrals as $referral) { $csv .= sprintf( '"%s","%s","%s","%s","%s","%s","%s","%s"' . "\n", $referral->referrer_name ?? 'Unknown', $referral->referee_name, $referral->referee_email, $referral->referee_phone, $referral->referral_code, $referral->status, $referral->referred_at, $referral->treated_at ?? 'Not yet' ); } return $csv; } /** * Generate HTML email for daily report * * @param array $referrals * @param string $period * @return string */ protected function generateReportEmail(array $referrals, string $period): string { $count = count($referrals); $content = sprintf('

You have %d new referral%s today.

', $count, $count !== 1 ? 's' : '' ); $content .= ''; $content .= ''; $content .= ''; $content .= ''; $content .= ''; $content .= ''; $content .= ''; $content .= ''; foreach ($referrals as $referral) { $content .= ''; $content .= sprintf('', esc_html($referral->referrer_name ?? 'Unknown')); $content .= sprintf('', esc_html($referral->referee_name)); $content .= sprintf('', esc_html($referral->referee_email)); $content .= sprintf('', esc_html(ucfirst($referral->status))); $content .= sprintf('', esc_html(date('g:i A', strtotime($referral->referred_at)))); $content .= ''; } $content .= '
Referred ByNew UserEmailStatusTime
%s%s%s%s%s
'; $content .= '

See attached CSV for full details.

'; return jvbGetEmailTemplate($content, 'Daily Referral Report'); } /** * Generate HTML email for weekly report * * @param array $top_referrers * @param int $total_referrals * @return string */ protected function generateWeeklyReportEmail(array $top_referrers, int $total_referrals): string { $content = sprintf( '

This week you had %d total referral%s.

', $total_referrals, $total_referrals !== 1 ? 's' : '' ); $content .= '

Top 10 Referrers This Week

'; $content .= ''; $content .= ''; $content .= ''; $content .= ''; $content .= ''; $content .= ''; $content .= ''; $rank = 1; foreach ($top_referrers as $referrer) { $content .= ''; $content .= sprintf('', $rank++); $content .= sprintf('', esc_html($referrer->user_name)); $content .= sprintf('', $referrer->referral_count); $content .= sprintf('', $referrer->treated_count); $content .= ''; } $content .= '
RankUserTotal ReferralsTreated
%d%s%d%d
'; return jvbGetEmailTemplate($content, 'Weekly Referral Summary'); } /** * Get reward settings * * @return array */ public function getRewardSettings(): array { $saved = get_option(BASE . 'referral_settings', []); return wp_parse_args($saved, $this->default_settings); } /** * Display referral info in user profile * * @param WP_User $user */ public function displayUserReferralInfo(WP_User $user): void { if (!current_user_can('edit_user', $user->ID)) { return; } $referral_code = get_user_meta($user->ID, BASE . 'referral_code', true); $stats = $this->getUserStats($user->ID); $referrals = $this->getUserReferrals($user->ID, ['limit' => 10]); ?>

Referral Information

Users can sign up with this code. Share link:

Referral Statistics

Total Referrals:
Treated:
Pending:
Available Rewards: $
Redeemed Rewards: $

Recent Referrals

Name Email Status Referred At Actions
referee_name); ?> referee_email); ?> status)); ?> referred_at); ?> status === 'pending'): ?>
add('invalid_referral_code', 'Referral code can only contain letters and numbers.'); }); return; } // Check if code is unique if ($this->isCodeTaken($code, $user_id)) { add_action('user_profile_update_errors', function($errors) { $errors->add('referral_code_taken', 'This referral code is already in use.'); }); return; } update_user_meta($user_id, BASE . 'referral_code', strtoupper($code)); } /** * Display user's referral code and share options * Use in templates or dashboard with: echo jvbReferralShareWidget(); * * @return string HTML output */ public function outputShareWidget(array $actions):array { $user_id = get_current_user_id(); $content = ''; $actions[] =[ 'button' => '', 'content' => $content ]; return $actions; } function getUnloggedInReferral(): string { ob_start(); JVB()->connect('cloudflare')->renderTurnstile(); $turnstile = ob_get_clean(); $meta = new MetaForm(); $codeForm = '
'.jvbFormStatus().$meta->return('referral_name', null, [ 'required' => true, 'type' => 'text', 'label' => 'Your Name', 'placeholder'=> 'Mister Meeseeks', 'autocomplete'=>'name' ]). $meta->return('referral_email', null, [ 'required' => true, 'type' => 'email', 'label' => 'Your Email', 'placeholder'=> 'look@me.com', 'autocomplete'=> 'email' ]). $meta->return('referral_code', null, [ 'required' => true, 'type' => 'text', 'label' => 'Referral Code', 'pattern' => '[A-Za-z0-9]+', 'maxLength' => 20, 'autocomplete'=>'off' ]).'

We\'ll send you a link to complete your registration.

'.$turnstile.'
'; $loginForm = '
'.jvbFormStatus().$meta->return('login_email', null, [ 'required' => true, 'type' => 'email', 'label' => 'Your Email', 'autocomplete'=>'email' ]).' '.$turnstile.'
'; $tabs = [ 'enterCode' => [ 'title' => 'Have a Code?', 'description' => [ 'Enter the code given to you to get 20% off your first treatment!' ], 'content' => $codeForm ], 'login' => [ 'title' => 'Login', 'description' => [ 'Login to see your rewards' ], 'content' => $loginForm ] ]; return jvbRenderTabs($tabs, true); } protected function getReferralSuccessMessage(string $code): string { $referrer = $this->getUserByReferralCode($code); if (!$referrer) { return ''; } $settings = $this->getRewardSettings(); $reward_amount = $settings['referee_reward_amount'] ?? 20; $reward_type = $settings['referee_reward_type'] ?? 'percentage'; $reward_text = $reward_type === 'percentage' ? $reward_amount . '% off' : '$' . number_format($reward_amount, 2) . ' off'; $booking_url = apply_filters('jvb_referral_booking_url', home_url('/contact')); ob_start(); ?>

Success! Your Reward is Ready!

You'll receive:

your first treatment!

Your referral code has been applied. Book your free consultation now to claim your reward!

Book Your Free Consultation
Referred by display_name); ?>
getUserReferralCode($user_id); if (is_wp_error($referral_code)) { return ''; } } $share_url = home_url('/?ref=' . $referral_code); ob_start(); ?>

Share the ♡

Invite your friends.

Earn rewards when they book!

20]); ?> Email 20]); ?> Facebook 20]); ?> Twitter

Your Referral Link

Your Code

- Total Referrals
- Successful
- Pending
$0.00 Available Rewards
getReferralByReferee($user->ID); if (!$referral) { return $content; } $settings = get_option(BASE . 'referral_settings', []); $reward_amount = $settings['referee_reward_amount'] ?? 20; $reward_type = $settings['referee_reward_type'] ?? 'percentage'; $reward_text = $reward_type === 'percentage' ? $reward_amount . '% off' : '$' . number_format($reward_amount, 2) . ' off'; $bonus_content = '
'; $bonus_content .= '

🎉 Welcome Bonus!

'; $bonus_content .= '

Since you were referred by a friend, you\'ve earned ' . $reward_text . ' your first booking!

'; $bonus_content .= '

Your reward will be automatically applied when you book.

'; $bonus_content .= '
'; // Insert bonus content after the first paragraph $parts = explode('

', $content, 2); if (count($parts) === 2) { return $parts[0] . '

' . $bonus_content . $parts[1]; } return $content . $bonus_content; } /** * Send referral invitation via email with magic link * * @param int $user_id Referrer's user ID * @param string $invitee_email Email of person to invite * @param string $invitee_name Name of person to invite * @return array|WP_Error Result with success/error */ public function sendReferralInvitation(int $user_id, string $invitee_email, string $invitee_name):array|WP_Error { // Verify user exists if (!$this->checkUser($user_id)) { return new WP_Error('invalid_user', 'Invalid user ID'); } // Check email rate limit (15/hour) $rate_check = $this->checkEmailRateLimit($user_id); if ($rate_check !== true) { return new WP_Error('rate_limit', 'You can only send 15 invitations per hour. Please try again later.'); } // Validate email $invitee_email = sanitize_email($invitee_email); if (!is_email($invitee_email)) { return new WP_Error('invalid_email', 'Invalid email address'); } // Check if this email has already been invited or registered if ($this->isEmailInvited($invitee_email)) { return new WP_Error('already_invited', 'This person has already been invited'); } if (email_exists($invitee_email)) { return new WP_Error('user_exists', 'This person already has an account'); } // Get referrer info $referrer = get_user_by('ID', $user_id); $referral_code = $this->getUserReferralCode($user_id); if (is_wp_error($referral_code)) { return $referral_code; } // Get reward text for email $settings = $this->getRewardSettings(); $reward_text = $settings['referee_reward_type'] === 'percentage' ? "Get {$settings['referee_reward_amount']}% off your first treatment!" : "Get \${$settings['referee_reward_amount']} off your first treatment!"; // Record the invitation attempt (for tracking) $this->recordInvitationAttempt($user_id, $invitee_email, $invitee_name); // Send magic link via MagicLinkManager $result = $this->magic_link->sendMagicLink( $invitee_email, MagicLinkManager::TYPE_REFERRAL, [ 'name' => sanitize_text_field($invitee_name), 'referral_code' => $referral_code, 'referrer_id' => $user_id, 'referrer_name' => $referrer->display_name, 'reward_text' => $reward_text ] ); if (is_wp_error($result)) { return $result; } return [ 'success' => true, 'message' => 'Invitation sent successfully', 'email' => $invitee_email, 'name' => $invitee_name ]; } /** * Send multiple referral invitations * * @param int $user_id Referrer's user ID * @param array $invitations Array of ['email' => '', 'name' => ''] * @return array Results with success/failed arrays */ public function sendBatchReferralInvitations(int $user_id, array $invitations): array { $results = [ 'success' => [], 'failed' => [] ]; foreach ($invitations as $invite) { $email = $invite['email'] ?? ''; $name = $invite['name'] ?? ''; if (empty($email) || empty($name)) { $results['failed'][] = [ 'email' => $email, 'name' => $name, 'reason' => 'Missing email or name' ]; continue; } $result = $this->sendReferralInvitation($user_id, $email, $name); if (is_wp_error($result)) { $results['failed'][] = [ 'email' => $email, 'name' => $name, 'reason' => $result->get_error_message() ]; } else { $results['success'][] = [ 'email' => $email, 'name' => $name ]; } // Small delay between sends to be respectful usleep(100000); // 0.1 seconds } return [ 'success' => !empty($results['success']), 'results' => $results, 'summary' => sprintf( 'Sent %d invitations, %d failed', count($results['success']), count($results['failed']) ) ]; } /** * Check email invitation rate limit (15 per hour) * * @param int $user_id * @return true|string True if allowed, error message if limited */ protected function checkEmailRateLimit(int $user_id):bool|string { $hourly_key = 'referral_invites_hour_' . $user_id; $count = (int) get_transient($hourly_key); if ($count >= 15) { return 'hourly_limit_reached'; } set_transient($hourly_key, $count + 1, HOUR_IN_SECONDS); return true; } /** * Check if an email has already been invited * * @param string $email * @return bool */ protected function isEmailInvited(string $email): bool { // Check invitation tracking table $invitation_key = 'referral_invite_' . md5($email); $invited = get_transient($invitation_key); if ($invited) { return true; } // Check if there's a pending referral for this email $existing = $this->wpdb->get_var($this->wpdb->prepare( "SELECT id FROM {$this->referrals_table} WHERE referee_email = %s", $email )); return !empty($existing); } /** * Record invitation attempt (for tracking and preventing duplicates) * * @param int $user_id * @param string $email * @param string $name */ protected function recordInvitationAttempt(int $user_id, string $email, string $name): void { // Store for 30 days (same as magic link invitation validity) $invitation_key = 'referral_invite_' . md5($email); $data = [ 'inviter_id' => $user_id, 'email' => $email, 'name' => $name, 'sent_at' => current_time('mysql') ]; set_transient($invitation_key, $data, 30 * DAY_IN_SECONDS); // Also log in user meta for tracking $sent_invites = get_user_meta($user_id, BASE . 'referral_invites_sent', true) ?: []; $sent_invites[] = [ 'email' => $email, 'name' => $name, 'sent_at' => current_time('mysql') ]; update_user_meta($user_id, BASE . 'referral_invites_sent', $sent_invites); } /** * Get user's invitation stats * * @param int $user_id * @return array */ public function getUserInvitationStats(int $user_id): array { $sent_invites = get_user_meta($user_id, BASE . 'referral_invites_sent', true) ?: []; // Count invites sent in last hour $one_hour_ago = strtotime('-1 hour'); $recent_count = 0; foreach ($sent_invites as $invite) { if (strtotime($invite['sent_at']) > $one_hour_ago) { $recent_count++; } } return [ 'total_sent' => count($sent_invites), 'sent_last_hour' => $recent_count, 'remaining_this_hour' => max(0, 15 - $recent_count), 'can_send_more' => $recent_count < 15 ]; } /** * Export referrals for Jane App cross-reference * * @param string $start_date Y-m-d format * @param string $end_date Y-m-d format * @return string CSV content */ public function exportReferrals(string $start_date, string $end_date): string { $referrals = $this->wpdb->get_results($this->wpdb->prepare( "SELECT r.id, r.referee_name, r.referee_email, r.referee_phone, r.referral_code, r.referred_at, r.status, r.treated_at, u.display_name as referrer_name, u.user_email as referrer_email FROM {$this->referrals_table} r JOIN {$this->wpdb->users} u ON r.referrer_id = u.ID WHERE DATE(r.referred_at) BETWEEN %s AND %s ORDER BY r.referred_at DESC", $start_date, $end_date )); // Build CSV $csv_lines = []; // Headers $csv_lines[] = [ 'Referral ID', 'Referee Name', 'Referee Email', 'Referee Phone', 'Referral Code', 'Referred Date', 'Status', 'Treated Date', 'Referrer Name', 'Referrer Email' ]; // Data rows foreach ($referrals as $ref) { $csv_lines[] = [ $ref->id, $ref->referee_name, $ref->referee_email, $ref->referee_phone ?: 'N/A', $ref->referral_code, $ref->referred_at, ucfirst($ref->status), $ref->treated_at ?: 'N/A', $ref->referrer_name, $ref->referrer_email ]; } // Convert to CSV string $output = fopen('php://temp', 'r+'); foreach ($csv_lines as $line) { fputcsv($output, $line); } rewind($output); $csv_content = stream_get_contents($output); fclose($output); return $csv_content; } }