| | |
| | | */ |
| | | class ReferralManager |
| | | { |
| | | protected $wpdb; |
| | | protected MagicLinkManager $magic_link; |
| | | protected Cache $cache; |
| | | protected Cache $requestCache; |
| | |
| | | |
| | | // Default reward settings |
| | | protected array $default_settings = [ |
| | | 'referrer_reward_applies_to' => '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' |
| | | 'from_user_reward_applies_to' => 'per_user', // 'per_user' or 'flat_total' |
| | | 'from_user_reward_amount' => 25.00, |
| | | 'from_user_reward_type' => 'fixed', |
| | | 'to_user_reward_type' => 'percentage', // 'percentage' or 'fixed' |
| | | 'to_user_reward_amount' => 20, // 20% or $20 |
| | | 'to_user_reward_applies_to' => 'first_order', // 'first_order' or 'all_orders' |
| | | ]; |
| | | |
| | | protected string $role; |
| | |
| | | $this->defineTables(); |
| | | $this->role = Site::getDefaultReferralRole(); |
| | | $this->default_settings['referral_role'] = $this->role; |
| | | global $wpdb; |
| | | $this->wpdb = $wpdb; |
| | | |
| | | $this->cache = Cache::for('referrals', WEEK_IN_SECONDS); |
| | | $this->requestCache = Cache::for('referral_requests', WEEK_IN_SECONDS)->connect('referrals', true); |
| | | $this->statsCache = Cache::for('referral_stats', WEEK_IN_SECONDS)->connect('referrals', true); |
| | |
| | | $this->statsCache->flush(); |
| | | } |
| | | |
| | | $this->referrals_table = $wpdb->prefix . BASE . 'referrals'; |
| | | $this->rewards_table = $wpdb->prefix . BASE . 'referral_rewards'; |
| | | |
| | | $this->referralPage = $this->getReferralPageId(); |
| | | $this->settings = $this->getRewardSettings(); |
| | | |
| | |
| | | 'id' => 'bigint(20) unsigned NOT NULL AUTO_INCREMENT', |
| | | 'referral_id' => 'bigint(20) unsigned NOT NULL', |
| | | 'user_id' => "{$table->getUserIDType()} NOT NULL", |
| | | 'reward_type' => "ENUM('referrer', 'referee') NOT NULL", |
| | | 'reward_type' => "ENUM('from_user', 'to_user') NOT NULL", |
| | | 'amount' => 'decimal(10,2) NOT NULL', |
| | | 'reward_calculation'=> "ENUM('percentage', 'fixed')", |
| | | 'status' => "ENUM('available', 'redeemed', 'expired', 'cancelled') DEFAULT 'available'", |
| | |
| | | return false; // No referral code - regular registration |
| | | } |
| | | |
| | | // Find the referrer |
| | | $referrer = $this->codes->pluck('user_id', ['code' => $referral['referral_code']]); |
| | | if (empty($referrer)) { |
| | | // Find the from_user |
| | | $from_user = $this->codes->pluck('user_id', ['code' => $referral['referral_code']]); |
| | | if (empty($from_user)) { |
| | | //This should not happen, but whatever |
| | | return false; |
| | | } |
| | | $referrer = $referrer[0]; |
| | | |
| | | // Check if this email already has a referral record |
| | | if ($this->isEmailInvited($referral['to_email'])) { |
| | | return false; |
| | | } |
| | | |
| | | $from_user = $from_user[0]; |
| | | $record = $this->referrals->findOrCreate([ |
| | | 'to_user' => $user_id, |
| | | 'referral_code' => $referral['referral_code'], |
| | | ], [ |
| | | 'from_user' => $referrer, |
| | | 'from_user' => $from_user, |
| | | 'to_email' => $referral['to_email'], |
| | | 'to_name' => $userData['first_name'], |
| | | // 'to_phone' => |
| | |
| | | $this->cache->flush(); |
| | | |
| | | // Fire action for tracking |
| | | do_action('jvb_referral_processed', $user_id, $referrer->ID, $referral['referral_code']); |
| | | do_action('jvb_referral_processed', $user_id, $from_user->ID, $referral['referral_code']); |
| | | |
| | | // Send notification to referrer |
| | | $this->sendReferrerNotification($referrer->ID, $userData['display_name']); |
| | | // Send notification to from_user |
| | | $this->sendReferrerNotification($from_user->ID, $userData['display_name']); |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * Create a referral record in the database |
| | | * |
| | | * @param int $referrer_id |
| | | * @param int $referee_id |
| | | * @param int $from_user_id |
| | | * @param int $to_user_id |
| | | * @param string $code |
| | | * @return int|false |
| | | */ |
| | | public function createReferral(int $referrer_id, int $referee_id, string $code) |
| | | public function createReferral(int $from_user_id, int $to_user_id, string $code) |
| | | { |
| | | $user = get_user_by('ID', $referee_id); |
| | | $user = get_user_by('ID', $to_user_id); |
| | | |
| | | return $this->wpdb->insert( |
| | | $this->referrals_table, |
| | | return $this->referrals->findOrCreate([ |
| | | [ |
| | | '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) ?: '', |
| | | 'to_user' => $to_user_id, |
| | | ], |
| | | [ |
| | | 'from_user' => $from_user_id, |
| | | 'to_name' => $user->display_name, |
| | | 'to_email' => $user->user_email, |
| | | 'to_phone' => get_user_meta($to_user_id, BASE . 'phone', true) ?: '', |
| | | 'referral_code' => $code, |
| | | 'status' => 'pending', |
| | | 'referred_at' => current_time('mysql') |
| | | ], |
| | | ['%d', '%d', '%s', '%s', '%s', '%s', '%s', '%s'] |
| | | ); |
| | | ] |
| | | ]); |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | /** |
| | | * Get referral record by referee ID |
| | | * Get referral record by to_user ID |
| | | * |
| | | * @param int $referee_id |
| | | * @param int $to_user_id |
| | | * @return object|null |
| | | */ |
| | | public function getReferralByReferee(int $referee_id): ?object |
| | | public function getReferralByReferee(int $to_user_id): ?object |
| | | { |
| | | $result = $this->wpdb->get_row($this->wpdb->prepare( |
| | | "SELECT * FROM {$this->referrals_table} WHERE referee_id = %d", |
| | | $referee_id |
| | | )); |
| | | $result = $this->referrals->get([ |
| | | 'to_user' => $to_user_id |
| | | ]); |
| | | |
| | | return $result ?: null; |
| | | } |
| | |
| | | { |
| | | $status = $treated ? 'treated' : 'pending'; |
| | | |
| | | $result = $this->wpdb->update( |
| | | $this->referrals_table, |
| | | $result = $this->referrals->update( |
| | | [ |
| | | 'status' => $status, |
| | | 'treated_at' => $treated ? current_time('mysql') : null |
| | | ], |
| | | ['id' => $referral_id], |
| | | ['%s', '%s'], |
| | | ['%d'] |
| | | [ |
| | | 'id' => $referral_id |
| | | ] |
| | | ); |
| | | |
| | | if ($result && $treated) { |
| | |
| | | } |
| | | |
| | | /** |
| | | * Create reward records for both referrer and referee |
| | | * Create reward records for both from_user and to_user |
| | | * |
| | | * @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 |
| | | )); |
| | | $referral = $this->referrals->get(['id' => $referral_id]); |
| | | |
| | | if (!$referral) { |
| | | return; |
| | | } |
| | | |
| | | // Create referrer reward |
| | | $this->wpdb->insert( |
| | | $this->rewards_table, |
| | | [ |
| | | // Create from_user reward |
| | | $fromUserReward = $this->rewards->insert([ |
| | | 'referral_id' => $referral_id, |
| | | 'user_id' => $referral->referrer_id, |
| | | 'reward_type' => 'referrer', |
| | | 'amount' => $this->settings['referrer_reward_amount'], |
| | | 'status' => 'available', |
| | | 'created_at' => current_time('mysql') |
| | | ], |
| | | ['%d', '%d', '%s', '%f', '%s', '%s'] |
| | | ); |
| | | 'user_id' => $referral->from_user, |
| | | 'reward_type' => 'from_user', |
| | | 'amount' => $this->settings['from_user_reward_amount'], |
| | | 'reward_calculation' => $this->settings['from_user_reward_type'] |
| | | ]); |
| | | |
| | | // Create referee reward |
| | | $referee_amount = $this->settings['referee_reward_type'] === 'percentage' |
| | | ? $this->settings['referee_reward_amount'] // Store as percentage |
| | | : $this->settings['referee_reward_amount']; // Store as fixed amount |
| | | |
| | | $this->wpdb->insert( |
| | | $this->rewards_table, |
| | | [ |
| | | // Create to_user reward |
| | | $to_user_amount = $this->settings['to_user_reward_type'] === 'percentage' |
| | | ? $this->settings['to_user_reward_amount'] // Store as percentage |
| | | : $this->settings['to_user_reward_amount']; // Store as fixed amount |
| | | |
| | | $toUserReward = $this->rewards->insert([ |
| | | 'referral_id' => $referral_id, |
| | | 'user_id' => $referral->referee_id, |
| | | 'reward_type' => 'referee', |
| | | 'amount' => $referee_amount, |
| | | 'reward_calculation' => $this->settings['referee_reward_type'], |
| | | 'status' => 'available', |
| | | 'created_at' => current_time('mysql') |
| | | ], |
| | | ['%d', '%d', '%s', '%f', '%s', '%s', '%s'] |
| | | ); |
| | | 'user_id' => $referral->to_user, |
| | | 'reward_type' => 'to_user', |
| | | 'amount' => $to_user_amount, |
| | | 'reward_calculation' => $this->settings['to_user_reward_type'] |
| | | ]); |
| | | |
| | | } |
| | | |
| | | /** |
| | |
| | | 'status' => 'all', |
| | | 'limit' => 100, |
| | | 'offset' => 0, |
| | | 'orderby' => 'referred_at', |
| | | 'orderby' => 'created_at', |
| | | 'order' => 'DESC' |
| | | ]; |
| | | |
| | | $args = wp_parse_args($args, $defaults); |
| | | |
| | | return $this->requestCache->remember( |
| | | $this->requestCache->generateKey(array_merge(['user'=>$user_id], $args)), |
| | | function() use ($user_id, $args) { |
| | | $where = $this->wpdb->prepare("WHERE referrer_id = %d", $user_id); |
| | | |
| | | if ($args['status'] !== 'all') { |
| | | $where .= $this->wpdb->prepare(" AND status = %s", $args['status']); |
| | | $args['status'] = strtolower($args['status']); |
| | | $tableArgs = [ |
| | | 'where' => [ |
| | | 'from_user' => $user_id, |
| | | ], |
| | | 'limit' => $args['limit'], |
| | | 'offset' => $args['offset'], |
| | | 'orderby' => $args['orderby'], |
| | | 'order' => $args['order'] |
| | | ]; |
| | | if (in_array($args['status'], ['pending', 'consulted', 'treated', 'cancelled'])){ |
| | | $tableArgs['status'] = $args['status']; |
| | | } |
| | | |
| | | $query = "SELECT * FROM {$this->referrals_table} |
| | | {$where} |
| | | ORDER BY {$args['orderby']} {$args['order']} |
| | | LIMIT {$args['limit']} OFFSET {$args['offset']}"; |
| | | |
| | | $results = $this->wpdb->get_results($query); |
| | | $referrals = $this->referrals->getMany($tableArgs); |
| | | |
| | | return array_map(function($referral) { |
| | | $last_invite = get_transient('referral_last_invite_' . md5($referral->referee_email)); |
| | | $last_invite = get_transient('referral_last_invite_' . md5($referral->to_email)); |
| | | $can_resend = !$last_invite || (time() - $last_invite) > WEEK_IN_SECONDS; |
| | | $status = match($referral->status) { |
| | | 'consulted' => 'Awaiting Treatment', |
| | |
| | | }; |
| | | return [ |
| | | 'id' => $referral->id, |
| | | 'referee_name' => $referral->referee_name, |
| | | 'referee_email' => $referral->referee_email, |
| | | 'to_name' => $referral->to_name, |
| | | 'to_email' => $referral->to_email, |
| | | 'referred_at' => JVB()->routes('referral')->formatTimestamp($referral->referred_at), |
| | | 'referral_status'=> $status, |
| | | 'can_resend' => $can_resend |
| | | ]; |
| | | }, $results); |
| | | } |
| | | ); |
| | | }, $referrals); |
| | | |
| | | } |
| | | |
| | |
| | | return $this->statsCache->remember( |
| | | $user_id, |
| | | function() use ($user_id) { |
| | | $stats = $this->wpdb->get_row($this->wpdb->prepare( |
| | | $stats = $this->referrals->queryResults( |
| | | "SELECT |
| | | COUNT(*) as code_used, |
| | | SUM(CASE WHEN status IN ('consulted', 'treated') THEN 1 ELSE 0 END) as consultations, |
| | | SUM(CASE WHEN status = 'treated' THEN 1 ELSE 0 END) as treatments, |
| | | SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending |
| | | FROM {$this->referrals_table} |
| | | WHERE referrer_id = %d", |
| | | $user_id |
| | | ), ARRAY_A); |
| | | FROM {table} |
| | | WHERE from_user = %d", |
| | | [$user_id], |
| | | ARRAY_A |
| | | ); |
| | | $stats = $stats[0] ?? []; |
| | | |
| | | // Get total rewards earned (available + redeemed) |
| | | $rewards = $this->wpdb->get_var($this->wpdb->prepare( |
| | | "SELECT SUM(amount) |
| | | FROM {$this->rewards_table} |
| | | WHERE user_id = %d AND reward_type = 'referrer'", |
| | | $user_id |
| | | )); |
| | | $rewards = $this->rewards->queryVar( |
| | | "SELECT SUM(amount) FROM {table} WHERE user_id = %d AND reward_type = 'from_user'", |
| | | [$user_id] |
| | | ); |
| | | |
| | | $stats['total_rewards'] = floatval($rewards ?? 0); |
| | | $stats['user_id'] = $user_id; |
| | |
| | | } |
| | | |
| | | /** |
| | | * Get top referrers for a time period |
| | | * Get top from_users for a time period |
| | | * |
| | | * @param int $limit |
| | | * @param string $period 'day'|'week'|'month'|'all' |
| | |
| | | $this->statsCache->generateKey(['limit'=>$limit, 'period' => $period]), |
| | | function() use ($limit, $period) { |
| | | $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)", |
| | | $date_clause = match($period) { |
| | | 'day' => "created_at >= DATE_SUB(NOW(), INTERVAL 1 DAY)", |
| | | 'week' => "created_at >= DATE_SUB(NOW(), INTERVAL 1 WEEK)", |
| | | 'month' => "created_at >= DATE_SUB(NOW(), INTERVAL 1 MONTH)", |
| | | default => "1=1" |
| | | }; |
| | | |
| | | $where = "WHERE {$date_where}"; |
| | | $where = "WHERE {$date_clause}"; |
| | | } |
| | | |
| | | $query = "SELECT |
| | | referrer_id, |
| | | $results = $this->referrals->queryResults( |
| | | "SELECT |
| | | from_user, |
| | | COUNT(*) as referral_count, |
| | | SUM(CASE WHEN status = 'treated' THEN 1 ELSE 0 END) as treated_count |
| | | FROM {$this->referrals_table} |
| | | FROM {table} |
| | | {$where} |
| | | GROUP BY referrer_id |
| | | GROUP BY from_user |
| | | ORDER BY referral_count DESC |
| | | LIMIT {$limit}"; |
| | | LIMIT {$limit}" |
| | | ); |
| | | |
| | | $results = $this->wpdb->get_results($query); |
| | | |
| | | // Enrich with user data |
| | | foreach ($results as &$result) { |
| | | $user = get_user_by('ID', $result->referrer_id); |
| | | $user = get_user_by('ID', $result->from_user); |
| | | $result->user_name = $user ? $user->display_name : 'Unknown'; |
| | | $result->user_email = $user ? $user->user_email : ''; |
| | | } |
| | |
| | | return $results; |
| | | } |
| | | ); |
| | | |
| | | } |
| | | |
| | | /** |
| | |
| | | { |
| | | $yesterday = date('Y-m-d', strtotime('-1 day')); |
| | | |
| | | $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 |
| | | )); |
| | | $new_referrals = $this->referrals->queryResults( |
| | | "SELECT {table}.*, u.display_name as from_user_name |
| | | FROM {table} |
| | | JOIN {$this->referrals->getUserTable()} u ON {table}.from_user = u.ID |
| | | WHERE DATE({table}.created_at) = %s |
| | | ORDER BY {table}.created_at DESC", |
| | | [$yesterday] |
| | | ); |
| | | |
| | | if (empty($new_referrals)) { |
| | | return; |
| | |
| | | foreach ($new_referrals as $ref) { |
| | | $cardContent = sprintf( |
| | | '<p><strong>%s</strong> (%s)</p>', |
| | | esc_html($ref->referee_name), |
| | | esc_html($ref->referee_email) |
| | | esc_html($ref->to_name), |
| | | esc_html($ref->to_email) |
| | | ); |
| | | $cardContent .= sprintf( |
| | | '<p style="font-size:13px;color:%s;">Referred by: %s | Code: %s</p>', |
| | | JVB()->email()->colours['dark-200'], |
| | | esc_html($ref->referrer_name), |
| | | esc_html($ref->from_name), |
| | | JVB()->email()->badge($ref->referral_code, 'info') |
| | | ); |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * Send weekly report with top referrers |
| | | * Send weekly report with top from_users |
| | | */ |
| | | 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)" |
| | | $top_from_users = $this->getTopReferrers(10, 'week'); |
| | | $total_referrals = $this->referrals->queryVar( |
| | | "SELECT COUNT(*) FROM {table} WHERE created_at >= DATE_SUB(NOW(), INTERVAL 1 WEEK)" |
| | | ); |
| | | |
| | | if ($total_referrals == 0) { |
| | |
| | | |
| | | // Leaderboard style |
| | | $rank = 1; |
| | | foreach ($top_referrers as $referrer) { |
| | | foreach ($top_from_users as $from_user) { |
| | | $rankBadge = $rank <= 3 |
| | | ? JVB()->email()->badge('#' . $rank, $rank === 1 ? 'success' : 'info') |
| | | : '<span style="font-weight:600;color:' . JVB()->email()->colours['dark-200'] . ';">#' . $rank . '</span>'; |
| | |
| | | $cardContent = sprintf( |
| | | '<p>%s <strong>%s</strong></p>', |
| | | $rankBadge, |
| | | esc_html($referrer->user_name) |
| | | esc_html($from_user->user_name) |
| | | ); |
| | | |
| | | $stats = [ |
| | | JVB()->email()->stat($referrer->referral_count, 'Total Referrals'), |
| | | JVB()->email()->stat($referrer->treated_count, 'Treated') |
| | | JVB()->email()->stat($from_user->referral_count, 'Total Referrals'), |
| | | JVB()->email()->stat($from_user->treated_count, 'Treated') |
| | | ]; |
| | | $cardContent .= JVB()->email()->grid($stats, 2); |
| | | |
| | |
| | | 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->from_name ?? 'Unknown', |
| | | $referral->to_name, |
| | | $referral->to_email, |
| | | $referral->to_phone, |
| | | $referral->referral_code, |
| | | $referral->status, |
| | | $referral->referred_at, |
| | |
| | | /** |
| | | * Generate HTML email for weekly report |
| | | * |
| | | * @param array $top_referrers |
| | | * @param array $top_from_users |
| | | * @param int $total_referrals |
| | | * @return string |
| | | */ |
| | | protected function generateWeeklyReportEmail(array $top_referrers, int $total_referrals): string |
| | | protected function generateWeeklyReportEmail(array $top_from_users, int $total_referrals): string |
| | | { |
| | | $content = sprintf( |
| | | '<p>This week you had <strong>%d total referral%s</strong>.</p>', |
| | |
| | | $total_referrals !== 1 ? 's' : '' |
| | | ); |
| | | |
| | | $referrers = []; |
| | | $from_users = []; |
| | | $rank = 1; |
| | | foreach ($top_referrers as $referrer) { |
| | | $referrers[] = [ |
| | | 'label' => '#' . $rank++ . ' - ' . esc_html($referrer->user_name), |
| | | foreach ($top_from_users as $from_user) { |
| | | $from_users[] = [ |
| | | 'label' => '#' . $rank++ . ' - ' . esc_html($from_user->user_name), |
| | | 'value' => sprintf( |
| | | '<strong>Total Referrals:</strong> %d | <strong>Treated:</strong> %d', |
| | | $referrer->referral_count, |
| | | $referrer->treated_count |
| | | $from_user->referral_count, |
| | | $from_user->treated_count |
| | | ) |
| | | ]; |
| | | } |
| | | |
| | | $content .= JVB()->email()->table($referrers, 'Top 10 Referrers This Week'); |
| | | $content .= JVB()->email()->table($from_users, 'Top 10 Referrers This Week'); |
| | | |
| | | return $content; |
| | | } |
| | |
| | | <tbody> |
| | | <?php foreach ($referrals as $referral): ?> |
| | | <tr> |
| | | <td><?php echo esc_html($referral->referee_name); ?></td> |
| | | <td><?php echo esc_html($referral->referee_email); ?></td> |
| | | <td><?php echo esc_html($referral->to_name); ?></td> |
| | | <td><?php echo esc_html($referral->to_email); ?></td> |
| | | <td><?php echo esc_html(ucfirst($referral->status)); ?></td> |
| | | <td><?php echo esc_html($referral->referred_at); ?></td> |
| | | <td> |
| | |
| | | |
| | | // Pre-fill code if from referral link |
| | | $prefill_code = $_GET['ref'] ?? ''; |
| | | $referrer_name = ''; |
| | | $from_user_name = ''; |
| | | if ($prefill_code) { |
| | | $referrer = $this->getUserByReferralCode($prefill_code); |
| | | $referrer_name = $referrer ? strtok($referrer->display_name, ' ') : ''; |
| | | $from_user = $this->getUserByReferralCode($prefill_code); |
| | | $from_user_name = $from_user ? strtok($from_user->display_name, ' ') : ''; |
| | | } |
| | | |
| | | $header = sprintf( |
| | | '<header><h2>%sGet %s.</h2></header><h3>Have a code?</h3>%s<p>Enter your referral code to get started!</p>', |
| | | jvbIcon('confetti'), |
| | | esc_html($reward_text), |
| | | ($referrer_name ? '<p>' . esc_html($referrer_name) . ' invited you to join us</p>' : '') |
| | | ($from_user_name ? '<p>' . esc_html($from_user_name) . ' invited you to join us</p>' : '') |
| | | ); |
| | | |
| | | $codeForm = sprintf( |
| | |
| | | 'pattern' => '[A-Za-z0-9]+', |
| | | 'maxLength' => 20, |
| | | 'autocomplete'=>'off', |
| | | 'data-referrer' => $referrer_name |
| | | 'data-from-user' => $from_user_name |
| | | ]), |
| | | $turnstile, |
| | | jvbIcon('check-circle') |
| | |
| | | |
| | | protected function getReferralSuccessMessage(string $code): string |
| | | { |
| | | $referrer = $this->getUserByReferralCode($code); |
| | | $from_user = $this->getUserByReferralCode($code); |
| | | |
| | | if (!$referrer) { |
| | | if (!$from_user) { |
| | | return ''; |
| | | } |
| | | |
| | | $reward_amount = $this->settings['referee_reward_amount'] ?? 20; |
| | | $reward_type = $this->settings['referee_reward_type'] ?? 'percentage'; |
| | | $reward_amount = $this->settings['to_user_reward_amount'] ?? 20; |
| | | $reward_type = $this->settings['to_user_reward_type'] ?? 'percentage'; |
| | | |
| | | $reward_text = $reward_type === 'percentage' |
| | | ? $reward_amount . '% off' |
| | |
| | | </a> |
| | | |
| | | <div class="referred-by"> |
| | | Referred by <strong><?php echo esc_html($referrer->display_name); ?></strong> |
| | | Referred by <strong><?php echo esc_html($from_user->display_name); ?></strong> |
| | | </div> |
| | | </div> |
| | | <?php |
| | |
| | | return $content; |
| | | } |
| | | |
| | | $reward_amount = $this->settings['referee_reward_amount'] ?? 20; |
| | | $reward_type = $this->settings['referee_reward_type'] ?? 'percentage'; |
| | | $reward_amount = $this->settings['to_user_reward_amount'] ?? 20; |
| | | $reward_type = $this->settings['to_user_reward_type'] ?? 'percentage'; |
| | | |
| | | $reward_text = $reward_type === 'percentage' |
| | | ? $reward_amount . '% off' |
| | |
| | | return new WP_Error('user_exists', 'This person already has an account'); |
| | | } |
| | | |
| | | // Get referrer info |
| | | $referrer = get_user_by('ID', $user_id); |
| | | // Get from_user info |
| | | $from_user = get_user_by('ID', $user_id); |
| | | $referral_code = $this->getUserReferralCode($user_id); |
| | | |
| | | if ($referral_code) { |
| | |
| | | ], home_url('/')); |
| | | |
| | | // Get reward text for email |
| | | $reward_text = $this->settings['referee_reward_type'] === 'percentage' |
| | | ? "{$this->settings['referee_reward_amount']}% off" |
| | | : "\${$this->settings['referee_reward_amount']} off"; |
| | | $reward_text = $this->settings['to_user_reward_type'] === 'percentage' |
| | | ? "{$this->settings['to_user_reward_amount']}% off" |
| | | : "\${$this->settings['to_user_reward_amount']} off"; |
| | | |
| | | // Build email content |
| | | $email_content = |
| | |
| | | <p>Click the button below to register and claim your reward:</p> |
| | | %s |
| | | <p><small>This invitation expires in 30 days.</small></p>', |
| | | esc_html($referrer->display_name), |
| | | esc_html($from_user->display_name), |
| | | esc_html(get_bloginfo('name')), |
| | | nl2br(esc_html($message)), |
| | | esc_html($reward_text), |
| | |
| | | } |
| | | |
| | | // 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 |
| | | )); |
| | | $existing = $this->referrals->pluck('id', ['to_email' => $email]); |
| | | |
| | | return !empty($existing); |
| | | } |
| | |
| | | */ |
| | | public function exportReferrals(string $start_date, string $end_date): string |
| | | { |
| | | $referrals = $this->wpdb->get_results($this->wpdb->prepare( |
| | | $referrals = $this->referrals->queryResults( |
| | | "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 |
| | | )); |
| | | {table}.id, |
| | | {table}.to_name, |
| | | {table}.to_email, |
| | | {table}.to_phone, |
| | | {table}.referral_code, |
| | | {table}.created_at, |
| | | {table}.status, |
| | | {table}.treated_at, |
| | | u.display_name as from_name, |
| | | u.user_email as from_email |
| | | FROM {table} |
| | | JOIN {$this->referrals->getUserTable()} u ON {table}.from_user = u.ID |
| | | WHERE DATE({table}.created_at) BETWEEN %s AND %s |
| | | ORDER BY {table}.created_at DESC", |
| | | [$start_date, $end_date] |
| | | ); |
| | | |
| | | // Build CSV |
| | | $csv_lines = []; |
| | |
| | | foreach ($referrals as $ref) { |
| | | $csv_lines[] = [ |
| | | $ref->id, |
| | | $ref->referee_name, |
| | | $ref->referee_email, |
| | | $ref->referee_phone ?: 'N/A', |
| | | $ref->to_name, |
| | | $ref->to_email, |
| | | $ref->to_phone ?: 'N/A', |
| | | $ref->referral_code, |
| | | $ref->referred_at, |
| | | ucfirst($ref->status), |
| | | $ref->treated_at ?: 'N/A', |
| | | $ref->referrer_name, |
| | | $ref->referrer_email |
| | | $ref->from_name, |
| | | $ref->from_email |
| | | ]; |
| | | } |
| | | |
| | |
| | | public function sanitizeRewardSettings(array $settings): array |
| | | { |
| | | return [ |
| | | 'referrer_reward_applies_to' => in_array($settings['referrer_reward_applies_to'] ?? '', ['per_user', 'flat_total']) |
| | | ? $settings['referrer_reward_applies_to'] |
| | | 'from_user_reward_applies_to' => in_array($settings['from_user_reward_applies_to'] ?? '', ['per_user', 'flat_total']) |
| | | ? $settings['from_user_reward_applies_to'] |
| | | : 'per_user', |
| | | 'referrer_reward_amount' => floatval($settings['referrer_reward_amount'] ?? 25.00), |
| | | 'referrer_reward_type' => in_array($settings['referrer_reward_type'] ?? '', ['fixed', 'percentage']) |
| | | ? $settings['referrer_reward_type'] |
| | | 'from_user_reward_amount' => floatval($settings['from_user_reward_amount'] ?? 25.00), |
| | | 'from_user_reward_type' => in_array($settings['from_user_reward_type'] ?? '', ['fixed', 'percentage']) |
| | | ? $settings['from_user_reward_type'] |
| | | : 'fixed', |
| | | 'referee_reward_type' => in_array($settings['referee_reward_type'] ?? '', ['percentage', 'fixed']) |
| | | ? $settings['referee_reward_type'] |
| | | 'to_user_reward_type' => in_array($settings['to_user_reward_type'] ?? '', ['percentage', 'fixed']) |
| | | ? $settings['to_user_reward_type'] |
| | | : 'percentage', |
| | | 'referee_reward_amount' => floatval($settings['referee_reward_amount'] ?? 20), |
| | | 'referee_reward_applies_to' => in_array($settings['referee_reward_applies_to'] ?? '', ['first_order', 'all_orders']) |
| | | ? $settings['referee_reward_applies_to'] |
| | | 'to_user_reward_amount' => floatval($settings['to_user_reward_amount'] ?? 20), |
| | | 'to_user_reward_applies_to' => in_array($settings['to_user_reward_applies_to'] ?? '', ['first_order', 'all_orders']) |
| | | ? $settings['to_user_reward_applies_to'] |
| | | : 'first_order', |
| | | ]; |
| | | } |
| | |
| | | } else { |
| | | data.items.forEach(function(ref) { |
| | | html += '<tr>'; |
| | | html += '<td>' + (ref.referrer_name || 'Unknown') + '</td>'; |
| | | html += '<td>' + (ref.referee_display_name || ref.referee_name) + '</td>'; |
| | | html += '<td>' + (ref.referee_display_email || ref.referee_email) + '</td>'; |
| | | html += '<td>' + (ref.from_name || 'Unknown') + '</td>'; |
| | | html += '<td>' + (ref.to_user_display_name || ref.to_name) + '</td>'; |
| | | html += '<td>' + (ref.to_user_display_email || ref.to_email) + '</td>'; |
| | | html += '<td><span class="referral-status ' + ref.status + '">' + ref.status + '</span></td>'; |
| | | html += '<td>' + new Date(ref.referred_at).toLocaleDateString() + '</td>'; |
| | | html += '<td>' + (ref.referrer_total_referrals || 0) + '</td>'; |
| | | html += '<td>' + (ref.from_user_total_referrals || 0) + '</td>'; |
| | | html += '<td class="referral-actions">'; |
| | | |
| | | if (ref.status === 'pending') { |
| | |
| | | </tr> |
| | | <tr> |
| | | <th scope="row"> |
| | | <label for="referrer_reward_type">Reward Type</label> |
| | | <label for="from_user_reward_type">Reward Type</label> |
| | | </th> |
| | | <td> |
| | | <select name="referrer_reward_type" id="referrer_reward_type"> |
| | | <option value="fixed" <?php selected($this->settings['referrer_reward_type'], 'fixed'); ?>>Fixed Amount</option> |
| | | <option value="percentage" <?php selected($this->settings['referrer_reward_type'], 'percentage'); ?>>Percentage</option> |
| | | <select name="from_user_reward_type" id="from_user_reward_type"> |
| | | <option value="fixed" <?php selected($this->settings['from_user_reward_type'], 'fixed'); ?>>Fixed Amount</option> |
| | | <option value="percentage" <?php selected($this->settings['from_user_reward_type'], 'percentage'); ?>>Percentage</option> |
| | | </select> |
| | | </td> |
| | | </tr> |
| | | <tr> |
| | | <th scope="row"> |
| | | <label for="referrer_reward_amount">Reward Amount</label> |
| | | <label for="from_user_reward_amount">Reward Amount</label> |
| | | </th> |
| | | <td> |
| | | <input type="number" |
| | | name="referrer_reward_amount" |
| | | id="referrer_reward_amount" |
| | | value="<?= esc_attr($this->settings['referrer_reward_amount']) ?>" |
| | | name="from_user_reward_amount" |
| | | id="from_user_reward_amount" |
| | | value="<?= esc_attr($this->settings['from_user_reward_amount']) ?>" |
| | | step="0.01" |
| | | min="0"> |
| | | <p class="description">Amount in dollars or percentage</p> |
| | |
| | | </tr> |
| | | <tr> |
| | | <th scope="row"> |
| | | <label for="referrer_reward_applies_to">Applies To</label> |
| | | <label for="from_user_reward_applies_to">Applies To</label> |
| | | </th> |
| | | <td> |
| | | <select name="referrer_reward_applies_to" id="referrer_reward_applies_to"> |
| | | <option value="per_user" <?php selected($this->settings['referrer_reward_applies_to'], 'per_user'); ?>>Per User Referred</option> |
| | | <option value="flat_total" <?php selected($this->settings['referrer_reward_applies_to'], 'flat_total'); ?>>Flat Total</option> |
| | | <select name="from_user_reward_applies_to" id="from_user_reward_applies_to"> |
| | | <option value="per_user" <?php selected($this->settings['from_user_reward_applies_to'], 'per_user'); ?>>Per User Referred</option> |
| | | <option value="flat_total" <?php selected($this->settings['from_user_reward_applies_to'], 'flat_total'); ?>>Flat Total</option> |
| | | </select> |
| | | </td> |
| | | </tr> |
| | |
| | | </tr> |
| | | <tr> |
| | | <th scope="row"> |
| | | <label for="referee_reward_type">Reward Type</label> |
| | | <label for="to_user_reward_type">Reward Type</label> |
| | | </th> |
| | | <td> |
| | | <select name="referee_reward_type" id="referee_reward_type"> |
| | | <option value="percentage" <?php selected($this->settings['referee_reward_type'], 'percentage'); ?>>Percentage</option> |
| | | <option value="fixed" <?php selected($this->settings['referee_reward_type'], 'fixed'); ?>>Fixed Amount</option> |
| | | <select name="to_user_reward_type" id="to_user_reward_type"> |
| | | <option value="percentage" <?php selected($this->settings['to_user_reward_type'], 'percentage'); ?>>Percentage</option> |
| | | <option value="fixed" <?php selected($this->settings['to_user_reward_type'], 'fixed'); ?>>Fixed Amount</option> |
| | | </select> |
| | | </td> |
| | | </tr> |
| | | <tr> |
| | | <th scope="row"> |
| | | <label for="referee_reward_amount">Reward Amount</label> |
| | | <label for="to_user_reward_amount">Reward Amount</label> |
| | | </th> |
| | | <td> |
| | | <input type="number" |
| | | name="referee_reward_amount" |
| | | id="referee_reward_amount" |
| | | value="<?= esc_attr($this->settings['referee_reward_amount']) ?>" |
| | | name="to_user_reward_amount" |
| | | id="to_user_reward_amount" |
| | | value="<?= esc_attr($this->settings['to_user_reward_amount']) ?>" |
| | | step="0.01" |
| | | min="0"> |
| | | <p class="description">Amount in dollars or percentage</p> |
| | |
| | | </tr> |
| | | <tr> |
| | | <th scope="row"> |
| | | <label for="referee_reward_applies_to">Applies To</label> |
| | | <label for="to_user_reward_applies_to">Applies To</label> |
| | | </th> |
| | | <td> |
| | | <select name="referee_reward_applies_to" id="referee_reward_applies_to"> |
| | | <option value="first_order" <?php selected($this->settings['referee_reward_applies_to'], 'first_order'); ?>>First Order Only</option> |
| | | <option value="all_orders" <?php selected($this->settings['referee_reward_applies_to'], 'all_orders'); ?>>All Orders</option> |
| | | <select name="to_user_reward_applies_to" id="to_user_reward_applies_to"> |
| | | <option value="first_order" <?php selected($this->settings['to_user_reward_applies_to'], 'first_order'); ?>>First Order Only</option> |
| | | <option value="all_orders" <?php selected($this->settings['to_user_reward_applies_to'], 'all_orders'); ?>>All Orders</option> |
| | | </select> |
| | | </td> |
| | | </tr> |
| | |
| | | ->content('referral', 'Referral', 'Referrals') |
| | | // ->initMeta('custom', 'referral') |
| | | ->setFields([ |
| | | 'referee_name' => [ |
| | | 'to_name' => [ |
| | | 'label' => 'Name', |
| | | 'type' => 'text', |
| | | ], |
| | | 'referee_email' => [ |
| | | 'to_email' => [ |
| | | 'label' => 'Email', |
| | | 'type' => 'text', |
| | | ], |
| | |
| | | |
| | | // Save reward settings |
| | | $settings = [ |
| | | 'referrer_reward_type' => sanitize_text_field($post_data['referrer_reward_type'] ?? 'fixed'), |
| | | 'referrer_reward_amount' => floatval($post_data['referrer_reward_amount'] ?? 25.00), |
| | | 'referrer_reward_applies_to' => sanitize_text_field($post_data['referrer_reward_applies_to'] ?? 'per_user'), |
| | | 'referee_reward_type' => sanitize_text_field($post_data['referee_reward_type'] ?? 'percentage'), |
| | | 'referee_reward_amount' => floatval($post_data['referee_reward_amount'] ?? 20), |
| | | 'referee_reward_applies_to' => sanitize_text_field($post_data['referee_reward_applies_to'] ?? 'first_order') |
| | | 'from_user_reward_type' => sanitize_text_field($post_data['from_user_reward_type'] ?? 'fixed'), |
| | | 'from_user_reward_amount' => floatval($post_data['from_user_reward_amount'] ?? 25.00), |
| | | 'from_user_reward_applies_to' => sanitize_text_field($post_data['from_user_reward_applies_to'] ?? 'per_user'), |
| | | 'to_user_reward_type' => sanitize_text_field($post_data['to_user_reward_type'] ?? 'percentage'), |
| | | 'to_user_reward_amount' => floatval($post_data['to_user_reward_amount'] ?? 20), |
| | | 'to_user_reward_applies_to' => sanitize_text_field($post_data['to_user_reward_applies_to'] ?? 'first_order') |
| | | ]; |
| | | |
| | | update_option(BASE . 'referral_settings', $settings); |
| | |
| | | } |
| | | |
| | | /** |
| | | * Get formatted reward text for referee |
| | | * Get formatted reward text for to_user |
| | | * |
| | | * @param bool $full Include "off your first treatment" text |
| | | * @return string |
| | | */ |
| | | public function getRewardText(bool $full = true): string |
| | | { |
| | | $reward_amount = $this->settings['referee_reward_amount'] ?? 20; |
| | | $reward_type = $this->settings['referee_reward_type'] ?? 'percentage'; |
| | | $reward_amount = $this->settings['to_user_reward_amount'] ?? 20; |
| | | $reward_type = $this->settings['to_user_reward_type'] ?? 'percentage'; |
| | | |
| | | $reward_text = $reward_type === 'percentage' |
| | | ? $reward_amount . '% off' |
| | |
| | | } |
| | | |
| | | /** |
| | | * Send notification to referrer when someone registers |
| | | * Send notification to from_user when someone registers |
| | | * |
| | | * @param int $referrer_id |
| | | * @param string $referee_name |
| | | * @param int $from_user_id |
| | | * @param string $to_name |
| | | */ |
| | | protected function sendReferrerNotification(int $referrer_id, string $referee_name): void |
| | | protected function sendReferrerNotification(int $from_user_id, string $to_name): void |
| | | { |
| | | $referrer = get_userdata($referrer_id); |
| | | if (!$referrer) { |
| | | $from_user = get_userdata($from_user_id); |
| | | if (!$from_user) { |
| | | return; |
| | | } |
| | | |
| | | $subject = sprintf('%s signed up with your referral code!', $referee_name); |
| | | $subject = sprintf('%s signed up with your referral code!', $to_name); |
| | | $message = sprintf( |
| | | "Great news! %s just signed up using your referral code.\n\n" . |
| | | "View your referrals: %s", |
| | | $referee_name, |
| | | $to_name, |
| | | home_url('/dash/referrals') |
| | | ); |
| | | |
| | | JVB()->email()->sendEmail( |
| | | $referrer->user_email, |
| | | $from_user->user_email, |
| | | $subject, |
| | | $message |
| | | ); |
| | |
| | | return ''; |
| | | } |
| | | |
| | | // Get referrer name |
| | | $referrer = get_userdata($referral->referrer_id); |
| | | $referrer_first_name = $referrer ? strtok($referrer->display_name, ' ') : 'Your friend'; |
| | | // Get from_user name |
| | | $from_user = get_userdata($referral->from_user); |
| | | $from_user_first_name = $from_user ? strtok($from_user->display_name, ' ') : 'Your friend'; |
| | | |
| | | // Get reward text |
| | | $reward_text = $this->getRewardText(); // Just "20% off" or "$25 off" |
| | |
| | | ?> |
| | | <div class="welcome-banner referral-welcome"> |
| | | <div class="banner-content"> |
| | | <h3><?= jvbIcon('confetti') ?>Welcome! <small><b><?= esc_html($referrer_first_name) ?></b> invited you to save <b><?= esc_html($reward_text) ?></b>!</small></h3> |
| | | <h3><?= jvbIcon('confetti') ?>Welcome! <small><b><?= esc_html($from_user_first_name) ?></b> invited you to save <b><?= esc_html($reward_text) ?></b>!</small></h3> |
| | | <p>But we're not done yet! Here's what happens next:</p> |
| | | <div class="callout"> |
| | | <ol> |
| | |
| | | <?php |
| | | return ob_get_clean(); |
| | | } |
| | | |
| | | public function updateStatus(int $referral_id, string $status): bool|WP_Error |
| | | { |
| | | $referral = $this->referrals->get(['id' => $referral_id]); |
| | | if (!$referral) { |
| | | return new WP_Error('not_found', 'Referral not found'); |
| | | } |
| | | |
| | | $data = ['status' => $status, "{$status}_at" => current_time('mysql')]; |
| | | if ($status === 'treated') { |
| | | $data['treatment_count'] = ($referral->treatment_count ?? 0) + 1; |
| | | } |
| | | |
| | | $result = $this->referrals->update($data, ['id' => $referral_id]); |
| | | if ($result === false) { |
| | | return new WP_Error('update_failed', 'Failed to update referral status'); |
| | | } |
| | | |
| | | if ($status === 'treated') { |
| | | $this->createRewardRecords($referral_id); |
| | | } |
| | | |
| | | $this->cache->flush(); |
| | | return true; |
| | | } |
| | | |
| | | public function removeReferral(int $referral_id, int $user_id): bool|WP_Error |
| | | { |
| | | $referral = $this->referrals->get(['id' => $referral_id]); |
| | | if (!$referral) { |
| | | return new WP_Error('not_found', 'Referral not found'); |
| | | } |
| | | |
| | | if ($referral->from_user != $user_id && !current_user_can('manage_options')) { |
| | | return new WP_Error('unauthorized', 'Unauthorized'); |
| | | } |
| | | |
| | | if ($referral->status !== 'pending') { |
| | | return new WP_Error('invalid_status', 'Can only remove pending referrals'); |
| | | } |
| | | |
| | | $this->referrals->delete(['id' => $referral_id]); |
| | | $this->cache->flush(); |
| | | return true; |
| | | } |
| | | |
| | | public function resendInvitation(int $referral_id, int $user_id): bool|WP_Error |
| | | { |
| | | $referral = $this->referrals->where(['id' => $referral_id, 'from_user' => $user_id])->first(); |
| | | if (!$referral) { |
| | | return new WP_Error('not_found', 'Referral not found'); |
| | | } |
| | | |
| | | $transient_key = 'referral_last_invite_' . md5($referral->to_email); |
| | | if (get_transient($transient_key)) { |
| | | return new WP_Error('rate_limit', 'Can only resend once per week'); |
| | | } |
| | | |
| | | $result = $this->sendReferralInvitation( |
| | | $user_id, |
| | | $referral->to_email, |
| | | $referral->to_name, |
| | | sprintf('Reminder: Join %s', get_bloginfo('name')), |
| | | 'Just a friendly reminder about my invitation!' |
| | | ); |
| | | |
| | | if (is_wp_error($result)) { |
| | | return $result; |
| | | } |
| | | |
| | | set_transient($transient_key, time(), WEEK_IN_SECONDS); |
| | | return true; |
| | | } |
| | | |
| | | public function getAllReferrals(array $args = []): array |
| | | { |
| | | $conditions = ['1=1']; |
| | | $values = []; |
| | | |
| | | if (!empty($args['status']) && $args['status'] !== 'all') { |
| | | $conditions[] = '{table}.status = %s'; |
| | | $values[] = $args['status']; |
| | | } |
| | | |
| | | if (!empty($args['date_start'])) { |
| | | $conditions[] = 'DATE({table}.created_at) >= %s'; |
| | | $values[] = $args['date_start']; |
| | | } |
| | | |
| | | if (!empty($args['date_end'])) { |
| | | $conditions[] = 'DATE({table}.created_at) <= %s'; |
| | | $values[] = $args['date_end']; |
| | | } |
| | | |
| | | if (!empty($args['search'])) { |
| | | global $wpdb; |
| | | $like = '%' . $wpdb->esc_like($args['search']) . '%'; |
| | | $conditions[] = '({table}.to_name LIKE %s OR {table}.to_email LIKE %s OR {table}.referral_code LIKE %s OR u.display_name LIKE %s)'; |
| | | array_push($values, $like, $like, $like, $like); |
| | | } |
| | | |
| | | array_push($values, absint($args['limit'] ?? 50), absint($args['offset'] ?? 0)); |
| | | |
| | | return $this->referrals->queryResults( |
| | | "SELECT {table}.*, u.display_name as from_name |
| | | FROM {table} |
| | | LEFT JOIN {$this->referrals->getUserTable()} u ON {table}.from_user = u.ID |
| | | WHERE " . implode(' AND ', $conditions) . " |
| | | ORDER BY {table}.created_at DESC |
| | | LIMIT %d OFFSET %d", |
| | | $values |
| | | ); |
| | | } |
| | | } |
| | | |