'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'; // 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); if (is_user_logged_in()) { add_action('wp_footer', [$this, 'outputShareWidget']); } add_action('template_redirect', [$this, 'trackReferralCode']);; // Schedule cron jobs for reports $this->registerCronJobs(); } /** * 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 there's a referral code in the session/cookie $referral_code = $this->getReferralCodeFromSession(); if (!$referral_code) { return; } // Find the referrer $referrer = $this->getUserByReferralCode($referral_code); if (!$referrer) { return; } // Check if this user was already referred (prevent duplicates) $existing = $this->getReferralByReferee($user_id); if ($existing) { return; } // Create referral record $this->createReferral($referrer->ID, $user_id, $referral_code); // Clear the session $this->clearReferralSession(); } /** * 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 */ protected 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 { // Get referrals from the last 24 hours $referrals = $this->wpdb->get_results( "SELECT r.*, u.display_name as referrer_name, u.user_email as referrer_email FROM {$this->referrals_table} r LEFT JOIN {$this->wpdb->users} u ON r.referrer_id = u.ID WHERE r.referred_at >= DATE_SUB(NOW(), INTERVAL 1 DAY) ORDER BY r.referred_at DESC" ); if (empty($referrals)) { return; // No referrals, no email } // Generate CSV $csv_content = $this->generateCSV($referrals); $csv_filename = 'referrals-' . date('Y-m-d') . '.csv'; // Save CSV temporarily $upload_dir = wp_upload_dir(); $csv_path = $upload_dir['basedir'] . '/' . $csv_filename; file_put_contents($csv_path, $csv_content); // Send email with attachment $to = get_option('admin_email'); $subject = '[' . get_bloginfo('name') . '] Daily Referral Report - ' . date('F j, Y'); $message = $this->generateReportEmail($referrals, 'daily'); $attachments = [$csv_path]; wp_mail($to, $subject, $message, ['Content-Type: text/html; charset=UTF-8'], $attachments); // Clean up temporary file unlink($csv_path); } /** * 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 */ protected function getRewardSettings(): array { $saved = get_option(BASE . 'referral_settings', []); return wp_parse_args($saved, $this->default_settings); } /** * Session/Cookie handling for referral codes */ protected function getReferralCodeFromSession(): ?string { if (session_status() === PHP_SESSION_NONE) { session_start(); } return $_SESSION[BASE . 'referral_code'] ?? $_COOKIE[BASE . 'referral_code'] ?? null; } protected function clearReferralSession(): void { if (session_status() === PHP_SESSION_NONE) { session_start(); } unset($_SESSION[BASE . 'referral_code']); setcookie(BASE . 'referral_code', '', time() - 3600, '/'); } /** * 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)); } public function trackReferralCode(): void { if (!isset($_GET['ref'])) { return; } $referral_code = strtoupper(sanitize_text_field($_GET['ref'])); // Start session if not already started if (session_status() === PHP_SESSION_NONE) { session_start(); } // Store in both session and cookie (30 day expiry) $_SESSION[BASE . 'referral_code'] = $referral_code; setcookie(BASE . 'referral_code', $referral_code, time() + (30 * DAY_IN_SECONDS), '/'); // Optional: Redirect to clean URL (removes ?ref= from address bar) $clean_url = remove_query_arg('ref'); wp_safe_redirect($clean_url); exit; } /** * Display user's referral code and share options * Use in templates or dashboard with: echo jvbReferralShareWidget(); * * @return string HTML output */ public function outputShareWidget(): string { $user_id = get_current_user_id(); if (!$user_id) { return ''; } $referral_code = get_user_meta($user_id, BASE . 'referral_code', true); // Generate code if user doesn't have one if (empty($referral_code)) { $manager = new \JVBase\managers\ReferralManager(); $referral_code = $manager->getUserReferralCode($user_id); } $share_url = home_url('/?ref=' . $referral_code); $encoded_url = urlencode($share_url); $site_name = get_bloginfo('name'); ob_start(); ?>

Share & Earn Rewards

Share your unique referral code with friends and earn rewards when they book!

-
Total Referrals
-
Completed
-
Pending
$0
Earned
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; } }